Merge branch 'dev' into 'main'
все роуты в компоненте Main и это поломало отображение. Весело :) Страница... See merge request frontend/squiz!105
This commit is contained in:
commit
25429abe7b
32
src/App.tsx
32
src/App.tsx
@ -23,6 +23,7 @@ import { ResultSettings } from "./pages/ResultPage/ResultSettings";
|
|||||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
||||||
import Main from "./pages/main";
|
import Main from "./pages/main";
|
||||||
import EditPage from "./pages/startPage/EditPage";
|
import EditPage from "./pages/startPage/EditPage";
|
||||||
|
import { Tariffs } from "./pages/Tariffs/Tariffs";
|
||||||
import {
|
import {
|
||||||
clearAuthToken,
|
clearAuthToken,
|
||||||
getMessageFromFetchError,
|
getMessageFromFetchError,
|
||||||
@ -100,16 +101,20 @@ export function useUserAccountFetcher({
|
|||||||
dayjs.locale("ru");
|
dayjs.locale("ru");
|
||||||
|
|
||||||
const routeslink = [
|
const routeslink = [
|
||||||
{ path: "/list", page: <MyQuizzesFull />, header: false, sidebar: false },
|
|
||||||
{
|
{
|
||||||
path: "/questions/:quizId",
|
path: "/edit",
|
||||||
page: <QuestionsPage />,
|
page: EditPage,
|
||||||
header: true,
|
header: true,
|
||||||
sidebar: true,
|
sidebar: true,
|
||||||
|
footer: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/design",
|
||||||
|
page: DesignPage,
|
||||||
|
header: true,
|
||||||
|
sidebar: true,
|
||||||
|
footer: true,
|
||||||
},
|
},
|
||||||
{ path: "/contacts", page: <ContactFormPage />, header: true, sidebar: true },
|
|
||||||
{ path: "/result", page: <Result />, header: true, sidebar: true },
|
|
||||||
{ path: "/settings", page: <ResultSettings />, header: true, sidebar: true },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@ -185,21 +190,24 @@ export default function App() {
|
|||||||
<Navigate to="/" replace state={{ redirectTo: "/restore" }} />
|
<Navigate to="/" replace state={{ redirectTo: "/restore" }} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route path="/list" element={<MyQuizzesFull />} />
|
||||||
|
<Route path={"/view"} element={<ViewPage />} />
|
||||||
|
<Route path={"/tariffs"} element={<Tariffs />} />
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
{routeslink.map((e, i) => (
|
{routeslink.map((e, i) => (
|
||||||
<Route
|
<Route
|
||||||
key={i}
|
key={i}
|
||||||
path={e.path}
|
path={e.path}
|
||||||
element={
|
element={
|
||||||
<Main page={e.page} header={e.header} sidebar={e.sidebar} />
|
<Main
|
||||||
|
Page={e.page}
|
||||||
|
header={e.header}
|
||||||
|
sidebar={e.sidebar}
|
||||||
|
footer={e.footer}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Route path="edit" element={<EditPage />} />
|
|
||||||
<Route path="crop" element={<ImageCrop />} />
|
|
||||||
<Route path="/view" element={<ViewPage />} />
|
|
||||||
<Route path="/design" element={<DesignPage />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -9,9 +9,9 @@ export const BackButtonIcon = () => (
|
|||||||
<path
|
<path
|
||||||
d="M7.86747 18C8.26104 18 9.9253 18 13.4337 18C17.8193 18 19 14.703 19 13.2194C19 11.7358 17.8193 8.93333 13.4337 8.93333C10.1773 8.93333 6.59726 8.93333 5 8.93333M5 8.93333L7.86747 6M5 8.93333L7.86747 11.8182"
|
d="M7.86747 18C8.26104 18 9.9253 18 13.4337 18C17.8193 18 19 14.703 19 13.2194C19 11.7358 17.8193 8.93333 13.4337 8.93333C10.1773 8.93333 6.59726 8.93333 5 8.93333M5 8.93333L7.86747 6M5 8.93333L7.86747 11.8182"
|
||||||
stroke="white"
|
stroke="white"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
208
src/assets/icons/NumberIcon.tsx
Normal file
208
src/assets/icons/NumberIcon.tsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
number: number;
|
||||||
|
color: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NumberIcon({
|
||||||
|
number,
|
||||||
|
backgroundColor = "rgb(0 0 0 / 0)",
|
||||||
|
color,
|
||||||
|
sx,
|
||||||
|
}: Props) {
|
||||||
|
number = number % 100;
|
||||||
|
|
||||||
|
const firstDigit = Math.floor(number / 10);
|
||||||
|
const secondDigit = number % 10;
|
||||||
|
|
||||||
|
const firstDigitTranslateX = 6;
|
||||||
|
const secondDigitTranslateX = number < 10 ? 9 : number < 20 ? 11 : 12;
|
||||||
|
|
||||||
|
const firstDigitElement = digitSvgs[firstDigit](firstDigitTranslateX);
|
||||||
|
const secondDigitElement = digitSvgs[secondDigit](secondDigitTranslateX);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor,
|
||||||
|
color,
|
||||||
|
width: "36px",
|
||||||
|
height: "36px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexShrink: 0,
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="26"
|
||||||
|
height="26"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{circleSvg}
|
||||||
|
{number > 9 && firstDigitElement}
|
||||||
|
{secondDigitElement}
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const circleSvg = (
|
||||||
|
<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="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const digitSvgs: Record<number, (translateX: number) => ReactElement> = {
|
||||||
|
0: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M3 8.75C4.24264 8.75 5.25 7.07107 5.25 5C5.25 2.92893 4.24264 1.25 3 1.25C1.75736 1.25 0.75 2.92893 0.75 5C0.75 7.07107 1.75736 8.75 3 8.75Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
1: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M1.125 2.75L3.375 1.25V8.75015"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
2: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M1.27158 2.39455C1.44019 1.99638 1.74115 1.66868 2.12357 1.46688C2.50598 1.26507 2.94636 1.20155 3.37021 1.28705C3.79407 1.37256 4.17538 1.60185 4.44964 1.93613C4.7239 2.27041 4.87428 2.68916 4.87534 3.12156C4.87703 3.49512 4.76526 3.8604 4.55483 4.16907V4.16907L1.12305 8.75H4.87534"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
3: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M1.125 1.25H4.875L2.6875 4.375C3.04723 4.37503 3.40139 4.46376 3.71863 4.63336C4.03587 4.80295 4.30639 5.04816 4.50623 5.34727C4.70607 5.64637 4.82906 5.99015 4.86431 6.34814C4.89956 6.70614 4.84598 7.0673 4.70832 7.39964C4.57066 7.73198 4.35316 8.02525 4.07509 8.25345C3.79702 8.48166 3.46696 8.63777 3.11415 8.70796C2.76133 8.77815 2.39666 8.76024 2.05242 8.65583C1.70818 8.55142 1.395 8.36373 1.14062 8.10938"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
4: (translateX: number) => (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M2.62508 1.07788L0.75 6.3906H4.50015"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M4.5 3.89038V8.89058"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
5: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M5.0625 1.25H1.92188L1.3125 5.01562C1.61844 4.70972 2.00821 4.5014 2.43254 4.41702C2.85687 4.33263 3.29669 4.37596 3.69639 4.54153C4.09609 4.70711 4.43772 4.98749 4.67807 5.34721C4.91843 5.70694 5.04672 6.12986 5.04672 6.5625C5.04672 6.99514 4.91843 7.41806 4.67807 7.77779C4.43772 8.13751 4.09609 8.41789 3.69639 8.58346C3.29669 8.74904 2.85687 8.79237 2.43254 8.70798C2.00821 8.6236 1.61844 8.41528 1.3125 8.10937"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
6: (translateX: number) => (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX - 1} 7)`}
|
||||||
|
d="M2.00977 5.30469L4.65117 0.875"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX - 1} 7)`}
|
||||||
|
d="M3.99609 8.75C5.26462 8.75 6.29297 7.72165 6.29297 6.45312C6.29297 5.1846 5.26462 4.15625 3.99609 4.15625C2.72756 4.15625 1.69922 5.1846 1.69922 6.45312C1.69922 7.72165 2.72756 8.75 3.99609 8.75Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
7: (translateX: number) => (
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M1.125 1.25H4.875L2.375 8.75"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
8: (translateX: number) => (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M4.72779 2.97356C4.72547 3.36962 4.58601 3.75265 4.33315 4.05749C4.08029 4.36233 3.72963 4.57017 3.34082 4.64564C2.95201 4.72111 2.54906 4.65956 2.20051 4.47146C1.85196 4.28336 1.57934 3.98032 1.42901 3.6139C1.27868 3.24747 1.25993 2.84028 1.37595 2.46158C1.49196 2.08289 1.73559 1.75608 2.06537 1.53675C2.39516 1.31741 2.79075 1.21909 3.18485 1.25851C3.57895 1.29793 3.94722 1.47266 4.22703 1.75298C4.54727 2.07862 4.72705 2.51684 4.72779 2.97356Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M5.04125 6.72925C5.03995 7.19778 4.87634 7.65138 4.57827 8.01287C4.28019 8.37436 3.86607 8.62139 3.40637 8.71193C2.94667 8.80247 2.4698 8.73092 2.05691 8.50946C1.64403 8.288 1.32063 7.93031 1.14177 7.49727C0.962899 7.06422 0.939612 6.58258 1.07587 6.1343C1.21213 5.68602 1.49951 5.2988 1.8891 5.03854C2.2787 4.77829 2.74645 4.66107 3.21273 4.70684C3.67902 4.75261 4.11505 4.95854 4.44661 5.28958C4.63578 5.47847 4.78572 5.70292 4.88777 5.95C4.98983 6.19709 5.04199 6.46192 5.04125 6.72925Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
9: (translateX: number) => (
|
||||||
|
<>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M5.03203 4.47046L2.39062 8.90015"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
transform={`translate(${translateX} 7)`}
|
||||||
|
d="M3.04688 5.6189C4.3154 5.6189 5.34375 4.59055 5.34375 3.32202C5.34375 2.05349 4.3154 1.02515 3.04688 1.02515C1.77835 1.02515 0.75 2.05349 0.75 3.32202C0.75 4.59055 1.77835 5.6189 3.04688 5.6189Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
5
src/model/privilege.ts
Normal file
5
src/model/privilege.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Privilege, PrivilegeWithAmount } from "@frontend/kitui";
|
||||||
|
|
||||||
|
export type ServiceKeyToPrivilegesMap = Record<string, Privilege[]>;
|
||||||
|
|
||||||
|
export type PrivilegeWithoutPrice = Omit<PrivilegeWithAmount, "price">;
|
||||||
6
src/model/tariff.ts
Normal file
6
src/model/tariff.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Tariff } from "@frontend/kitui";
|
||||||
|
|
||||||
|
export interface GetTariffsResponse {
|
||||||
|
totalPages: number;
|
||||||
|
tariffs: Tariff[];
|
||||||
|
}
|
||||||
@ -138,20 +138,6 @@ export const DesignFilling = () => {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Box sx={{ textAlign: "end" }}>
|
|
||||||
<IconButton
|
|
||||||
onClick={toggleQuizPreview}
|
|
||||||
sx={{
|
|
||||||
pointerEvents: "auto",
|
|
||||||
marginLeft: "auto",
|
|
||||||
position: "relative",
|
|
||||||
zIndex: "999999",
|
|
||||||
alignItems: "end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VisibilityIcon sx={{ height: "30px", width: "30px" }} />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useQuizStore } from "@root/quizes/store";
|
|||||||
import Sidebar from "@ui_kit/Sidebar";
|
import Sidebar from "@ui_kit/Sidebar";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { SidebarMobile } from "../startPage/Sidebar/SidebarMobile";
|
import { SidebarMobile } from "../../ui_kit/Sidebar/SidebarMobile";
|
||||||
import { cleanQuestions, setQuestions } from "@root/questions/actions";
|
import { cleanQuestions, setQuestions } from "@root/questions/actions";
|
||||||
import {
|
import {
|
||||||
updateModalInfoWhyCantCreate,
|
updateModalInfoWhyCantCreate,
|
||||||
@ -17,7 +17,7 @@ import { questionApi } from "@api/question";
|
|||||||
import { useUiTools } from "@root/uiTools/store";
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
|
|
||||||
import { ConfirmLeaveModal } from "../startPage/ConfirmLeaveModal";
|
import { ConfirmLeaveModal } from "../startPage/ConfirmLeaveModal";
|
||||||
import { Header } from "../startPage/Header";
|
import { Header } from "@ui_kit/Header/Header";
|
||||||
import { DesignFilling } from "./DesignFilling";
|
import { DesignFilling } from "./DesignFilling";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||||
@ -25,19 +25,6 @@ import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
|||||||
export const DesignPage = () => {
|
export const DesignPage = () => {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const { editQuizId } = useQuizStore();
|
const { editQuizId } = useQuizStore();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getData = async () => {
|
|
||||||
const quizes = await quizApi.getList();
|
|
||||||
setQuizes(quizes);
|
|
||||||
if (editQuizId) {
|
|
||||||
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
|
||||||
setQuestions(questions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { showConfirmLeaveModal } = useUiTools();
|
const { showConfirmLeaveModal } = useUiTools();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -82,13 +69,7 @@ export const DesignPage = () => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header setMobileSidebar={setMobileSidebar} />
|
|
||||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||||
{isMobile ? (
|
|
||||||
<SidebarMobile open={mobileSidebar} changePage={changePage} />
|
|
||||||
) : (
|
|
||||||
<Sidebar changePage={changePage} />
|
|
||||||
)}
|
|
||||||
<DesignFilling />
|
<DesignFilling />
|
||||||
{createPortal(<QuizPreview />, document.body)}
|
{createPortal(<QuizPreview />, document.body)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -108,38 +108,38 @@ export const AnswerItem = ({
|
|||||||
),
|
),
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton
|
{/*<IconButton*/}
|
||||||
sx={{ padding: "0" }}
|
{/* sx={{ padding: "0" }}*/}
|
||||||
aria-describedby="my-popover-id"
|
{/* aria-describedby="my-popover-id"*/}
|
||||||
onClick={handleClick}
|
{/* onClick={handleClick}*/}
|
||||||
>
|
{/*>*/}
|
||||||
<MessageIcon
|
{/* <MessageIcon*/}
|
||||||
style={{
|
{/* style={{*/}
|
||||||
color: "#9A9AAF",
|
{/* color: "#9A9AAF",*/}
|
||||||
fontSize: "30px",
|
{/* fontSize: "30px",*/}
|
||||||
marginRight: "6.5px",
|
{/* marginRight: "6.5px",*/}
|
||||||
}}
|
{/* }}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
</IconButton>
|
{/*</IconButton>*/}
|
||||||
<Popover
|
{/*<Popover*/}
|
||||||
id="my-popover-id"
|
{/* id="my-popover-id"*/}
|
||||||
open={isOpen}
|
{/* open={isOpen}*/}
|
||||||
anchorEl={anchorEl}
|
{/* anchorEl={anchorEl}*/}
|
||||||
onClose={handleClose}
|
{/* onClose={handleClose}*/}
|
||||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
{/* anchorOrigin={{ vertical: "bottom", horizontal: "left" }}*/}
|
||||||
>
|
{/*>*/}
|
||||||
<TextareaAutosize
|
{/* <TextareaAutosize*/}
|
||||||
style={{ margin: "10px" }}
|
{/* style={{ margin: "10px" }}*/}
|
||||||
placeholder="Подсказка для этого ответа"
|
{/* placeholder="Подсказка для этого ответа"*/}
|
||||||
value={variant.hints}
|
{/* value={variant.hints}*/}
|
||||||
onChange={(e) =>
|
{/* onChange={(e) =>*/}
|
||||||
setQuestionVariantAnswer(e.target.value || " ")
|
{/* setQuestionVariantAnswer(e.target.value || " ")*/}
|
||||||
}
|
{/* }*/}
|
||||||
onKeyDown={(
|
{/* onKeyDown={(*/}
|
||||||
event: KeyboardEvent<HTMLTextAreaElement>,
|
{/* event: KeyboardEvent<HTMLTextAreaElement>,*/}
|
||||||
) => event.stopPropagation()}
|
{/* ) => event.stopPropagation()}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
</Popover>
|
{/*</Popover>*/}
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{ padding: "0" }}
|
sx={{ padding: "0" }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
</Box> */}
|
</Box> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
pt: isMobile ? "0px" : "20px",
|
pt: "20px",
|
||||||
pb: "20px",
|
pb: "20px",
|
||||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||||
pr: isFigmaTablte ? "19px" : "20px",
|
pr: isFigmaTablte ? "19px" : "20px",
|
||||||
|
|||||||
@ -47,8 +47,8 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
pt: isMobile ? "25px" : "20px",
|
pt: isMobile ? "0" : "20px",
|
||||||
pb: isMobile ? "25px" : "20px",
|
pb: isMobile ? "0" : "20px",
|
||||||
pl: "20px",
|
pl: "20px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
|||||||
@ -74,7 +74,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
</Box> */}
|
</Box> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
pt: isMobile ? "0px" : "20px",
|
pt: "20px",
|
||||||
pb: "20px",
|
pb: "20px",
|
||||||
pl: "20px",
|
pl: "20px",
|
||||||
pr: isFigmaTablte ? "30px" : "20px",
|
pr: isFigmaTablte ? "30px" : "20px",
|
||||||
|
|||||||
@ -92,8 +92,8 @@ export default function QuestionsPageCard({
|
|||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
paddingBottom: "20px",
|
paddingBottom: "20px",
|
||||||
borderRadius: "0",
|
borderRadius: "0",
|
||||||
borderTopLeftRadius: "12px",
|
borderTopLeftRadius: questionIndex == 0 ? "12px" : 0,
|
||||||
borderTopRightRadius: "12px",
|
borderTopRightRadius: questionIndex == 0 ? "12px" : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -123,7 +123,7 @@ export default function QuestionsPageCard({
|
|||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
|
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
|
||||||
value={question.title}
|
defaultValue={question.title}
|
||||||
onChange={({ target }) => setTitle(target.value)}
|
onChange={({ target }) => setTitle(target.value)}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -365,8 +365,6 @@ const IconAndrom = (questionType: QuestionType | null) => {
|
|||||||
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return null;
|
||||||
<AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -126,7 +126,7 @@ export default function SettingTextField({ question }: SettingTextFieldProps) {
|
|||||||
</Box> */}
|
</Box> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
pt: isMobile ? "0px" : "20px",
|
pt: "20px",
|
||||||
pb: "20px",
|
pb: "20px",
|
||||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||||
pr: "20px",
|
pr: "20px",
|
||||||
|
|||||||
@ -1,5 +1,18 @@
|
|||||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
import {
|
||||||
import { updateQuestion } from "@root/questions/actions";
|
Box,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
copyQuestion,
|
||||||
|
deleteQuestion,
|
||||||
|
deleteQuestionWithTimeout,
|
||||||
|
updateQuestion,
|
||||||
|
} from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -8,6 +21,12 @@ import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
|||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import SwitchPageOptions from "./switchPageOptions";
|
import SwitchPageOptions from "./switchPageOptions";
|
||||||
import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay";
|
import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay";
|
||||||
|
import { CopyIcon } from "@icons/questionsPage/CopyIcon";
|
||||||
|
import { DeleteFunction } from "@utils/deleteFunc";
|
||||||
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disableInput?: boolean;
|
disableInput?: boolean;
|
||||||
@ -29,6 +48,14 @@ export default function PageOptions({ disableInput, question }: Props) {
|
|||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const { questions } = useQuestionsStore.getState();
|
||||||
|
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const openedModal = () => {
|
||||||
|
updateDesireToOpenABranchingModal(question.content.id);
|
||||||
|
};
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
};
|
};
|
||||||
@ -59,12 +86,105 @@ export default function PageOptions({ disableInput, question }: Props) {
|
|||||||
|
|
||||||
<MediaSelectionAndDisplay resultData={question} />
|
<MediaSelectionAndDisplay resultData={question} />
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions
|
<Box
|
||||||
switchState={switchState}
|
sx={{
|
||||||
SSHC={SSHC}
|
display: "flex",
|
||||||
question={question}
|
alignItems: "center",
|
||||||
/>
|
justifyContent: "end",
|
||||||
<SwitchPageOptions switchState={switchState} question={question} />
|
width: "100%",
|
||||||
|
background: "#f2f3f7",
|
||||||
|
height: isMobile ? "92px" : "70px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "20px",
|
||||||
|
display: "flex",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||||
|
<HideIcon style={{ color: "#4D4D4D" }} />
|
||||||
|
</IconButton> */}
|
||||||
|
<IconButton
|
||||||
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
|
onClick={() => copyQuestion(question.id, question.quizId)}
|
||||||
|
>
|
||||||
|
<CopyIcon color={"#4D4D4D"} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (question.type === null) {
|
||||||
|
deleteQuestion(question.id);
|
||||||
|
}
|
||||||
|
if (question.content.rule.parentId.length !== 0) {
|
||||||
|
setOpenDelete(true);
|
||||||
|
} else {
|
||||||
|
deleteQuestionWithTimeout(question.id, () =>
|
||||||
|
DeleteFunction(questions, question, quiz),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
|
>
|
||||||
|
<DeleteIcon color={"#4D4D4D"} />
|
||||||
|
</IconButton>
|
||||||
|
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
padding: "30px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||||
|
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||||
|
потеряют данные ветвления. Вы уверены, что хотите удалить
|
||||||
|
вопрос?
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: "30px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => setOpenDelete(false)}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => {
|
||||||
|
deleteQuestionWithTimeout(question.id, () =>
|
||||||
|
DeleteFunction(questions, question, quiz),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подтвердить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/*<ButtonsOptions*/}
|
||||||
|
{/* switchState={switchState}*/}
|
||||||
|
{/* SSHC={SSHC}*/}
|
||||||
|
{/* question={question}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
{/*<SwitchPageOptions switchState={switchState} question={question} />*/}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,19 +62,21 @@ export default function QuestionsPage({
|
|||||||
<Typography variant={"h5"}>
|
<Typography variant={"h5"}>
|
||||||
{quiz.name ? quiz.name : "Заголовок quiz"}
|
{quiz.name ? quiz.name : "Заголовок quiz"}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
{!openBranchingPage && (
|
||||||
sx={{
|
<Button
|
||||||
fontSize: "16px",
|
sx={{
|
||||||
lineHeight: "19px",
|
fontSize: "16px",
|
||||||
padding: 0,
|
lineHeight: "19px",
|
||||||
textDecoration: "underline",
|
padding: 0,
|
||||||
color: theme.palette.brightPurple.main,
|
textDecoration: "underline",
|
||||||
textDecorationColor: theme.palette.brightPurple.main,
|
color: theme.palette.brightPurple.main,
|
||||||
}}
|
textDecorationColor: theme.palette.brightPurple.main,
|
||||||
onClick={collapseAllQuestions}
|
}}
|
||||||
>
|
onClick={collapseAllQuestions}
|
||||||
Свернуть всё
|
>
|
||||||
</Button>
|
Свернуть всё
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<QuestionSwitchWindowTool
|
<QuestionSwitchWindowTool
|
||||||
openBranchingPage={openBranchingPage}
|
openBranchingPage={openBranchingPage}
|
||||||
@ -85,6 +87,7 @@ export default function QuestionsPage({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
maxWidth: "796px",
|
maxWidth: "796px",
|
||||||
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -93,7 +96,6 @@ export default function QuestionsPage({
|
|||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
left: isMobile ? "20px" : "250px",
|
|
||||||
bottom: "140px",
|
bottom: "140px",
|
||||||
}}
|
}}
|
||||||
data-cy="create-question"
|
data-cy="create-question"
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
AnyTypedQuizQuestion,
|
AnyTypedQuizQuestion,
|
||||||
UntypedQuizQuestion,
|
UntypedQuizQuestion,
|
||||||
} from "@model/questionTypes/shared";
|
} from "@model/questionTypes/shared";
|
||||||
import { Pencil } from "../../startPage/Sidebar/icons/Pencil";
|
import { Pencil } from "../../../ui_kit/Sidebar/icons/Pencil";
|
||||||
import {
|
import {
|
||||||
updateEditSomeQuestion,
|
updateEditSomeQuestion,
|
||||||
updateDragQuestionContentId,
|
updateDragQuestionContentId,
|
||||||
|
|||||||
@ -38,7 +38,16 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
|||||||
width: isMobile ? "auto" : "100%",
|
width: isMobile ? "auto" : "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>Настройки вопроса</Typography>
|
<Typography
|
||||||
|
sx={{
|
||||||
|
height: isMobile ? "18px" : "auto",
|
||||||
|
fontWeight: "500",
|
||||||
|
fontSize: "18px",
|
||||||
|
color: " #4D4D4D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Настройки вопросов
|
||||||
|
</Typography>
|
||||||
{/* <CustomCheckbox
|
{/* <CustomCheckbox
|
||||||
sx={{
|
sx={{
|
||||||
display: isMobile ? "flex" : "block",
|
display: isMobile ? "flex" : "block",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
TextField,
|
TextField,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import UploadIcon from "../../../assets/icons/UploadIcon";
|
import UploadIcon from "../../../assets/icons/UploadIcon";
|
||||||
import SearchIcon from "../../../assets/icons/SearchIcon";
|
import SearchIcon from "../../../assets/icons/SearchIcon";
|
||||||
@ -32,7 +33,7 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dropZone = useRef<HTMLDivElement>(null);
|
const dropZone = useRef<HTMLDivElement>(null);
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
|
const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setReady(true);
|
setReady(true);
|
||||||
@ -65,7 +66,7 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
|
|||||||
top: "50%",
|
top: "50%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
maxWidth: "690px",
|
maxWidth: isMobile ? "300px" : "690px",
|
||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
@ -106,13 +107,14 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
|
|||||||
ref={dropZone}
|
ref={dropZone}
|
||||||
sx={{
|
sx={{
|
||||||
width: "580px",
|
width: "580px",
|
||||||
padding: "33px 10px 33px 55px",
|
padding: isMobile ? "33px" : "33px 10px 33px 55px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
|
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
gap: "55px",
|
gap: "55px",
|
||||||
|
flexDirection: isMobile ? "column" : undefined,
|
||||||
}}
|
}}
|
||||||
onDragEnter={handleDragEnter} // Применяем обработчик onDragEnter напрямую
|
onDragEnter={handleDragEnter} // Применяем обработчик onDragEnter напрямую
|
||||||
>
|
>
|
||||||
|
|||||||
@ -98,7 +98,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
pt: isMobile ? "0px" : "20px",
|
pt: "20px",
|
||||||
pb: "20px",
|
pb: "20px",
|
||||||
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px",
|
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px",
|
||||||
pr: isFigmaTablte ? "19px" : "20px",
|
pr: isFigmaTablte ? "19px" : "20px",
|
||||||
|
|||||||
222
src/pages/Tariffs/Tariffs.tsx
Normal file
222
src/pages/Tariffs/Tariffs.tsx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { GetTariffsResponse } from "@model/tariff";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { Tariff, getMessageFromFetchError } from "@frontend/kitui";
|
||||||
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
|
import { createTariffElements } from "./tariffsUtils/createTariffElements";
|
||||||
|
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||||
|
|
||||||
|
function TariffPage() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [tariffs, setTariffs] = useState();
|
||||||
|
const [user, setUser] = useState();
|
||||||
|
const [discounts, setDiscounts] = useState();
|
||||||
|
const [cartTariffMap, setCartTariffMap] = useState();
|
||||||
|
const [openModal, setOpenModal] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const get = async () => {
|
||||||
|
const user = await makeRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "https://squiz.pena.digital/customer/account",
|
||||||
|
});
|
||||||
|
const tariffs = await makeRequest<never, GetTariffsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: "https://squiz.pena.digital/strator/tariff?page=1&limit=100",
|
||||||
|
});
|
||||||
|
const discounts = await makeRequest({
|
||||||
|
method: "GET",
|
||||||
|
url: "https://squiz.pena.digital/price/discounts",
|
||||||
|
});
|
||||||
|
setUser(user);
|
||||||
|
setTariffs(tariffs);
|
||||||
|
setDiscounts(discounts.Discounts);
|
||||||
|
};
|
||||||
|
get();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!user || !tariffs || !discounts) return <LoadingPage />;
|
||||||
|
|
||||||
|
console.log("user ", user);
|
||||||
|
console.log("tariffs ", tariffs);
|
||||||
|
console.log("discounts ", discounts);
|
||||||
|
|
||||||
|
const openModalHC = (tariffInfo: any) => setOpenModal(tariffInfo);
|
||||||
|
const tryBuy = async ({ id, price }: { id: string; price: number }) => {
|
||||||
|
openModalHC({});
|
||||||
|
//Если в корзине что-то было - выкладываем содержимое и запоминаем чо там лежало
|
||||||
|
if (user.cart.length > 0) {
|
||||||
|
outCart(user.cart);
|
||||||
|
}
|
||||||
|
//Добавляем желаемый тариф в корзину
|
||||||
|
await makeRequest({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `https://hub.pena.digital/customer/cart?id=${id}`,
|
||||||
|
});
|
||||||
|
//Если нам хватает денежек - покупаем тариф
|
||||||
|
if (price <= user.wallet.cash) {
|
||||||
|
try {
|
||||||
|
await makeRequest({
|
||||||
|
method: "POST",
|
||||||
|
url: "https://suiz.pena.digital/customer/cart/pay",
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar("Произошла ошибка. Попробуйте позже");
|
||||||
|
}
|
||||||
|
//Развращаем товары в корзину
|
||||||
|
inCart();
|
||||||
|
} else {
|
||||||
|
//Деняк не хватило
|
||||||
|
// history.pushState({}, null, "https://hub.pena.digital/wallet?action=squizpay");
|
||||||
|
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.href = `https://hub.pena.digital/payment?action=squizpay&dif=${
|
||||||
|
(price - Number(user.wallet.cash)) * 100
|
||||||
|
}`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
// link.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const purchasesAmount = user?.wallet.purchasesAmount ?? 0;
|
||||||
|
const isUserNko = user?.status === "nko";
|
||||||
|
const filteredTariffs = tariffs.tariffs.filter((tariff) => {
|
||||||
|
return (
|
||||||
|
tariff.privileges[0].serviceKey === "squiz" &&
|
||||||
|
!tariff.isDeleted &&
|
||||||
|
!tariff.isCustom
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderFull />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
justifyContent: "left",
|
||||||
|
mt: "40px",
|
||||||
|
mb: "30px",
|
||||||
|
display: "grid",
|
||||||
|
gap: "40px",
|
||||||
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
|
||||||
|
isTablet ? "436px" : "360px"
|
||||||
|
}))`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createTariffElements(
|
||||||
|
filteredTariffs,
|
||||||
|
true,
|
||||||
|
user,
|
||||||
|
discounts,
|
||||||
|
openModalHC,
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Modal
|
||||||
|
open={Object.values(openModal).length > 0}
|
||||||
|
onClose={() => setOpenModal({})}
|
||||||
|
>
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
position: "absolute" as "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
id="modal-modal-title"
|
||||||
|
variant="h6"
|
||||||
|
component="h2"
|
||||||
|
mb="20px"
|
||||||
|
>
|
||||||
|
Вы подтверждаете платёж в сумму {openModal.price} ₽
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" onClick={() => tryBuy(openModal)}>
|
||||||
|
купить
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tariffs = withErrorBoundary(TariffPage, {
|
||||||
|
fallback: (
|
||||||
|
<Typography mt="8px" textAlign="center">
|
||||||
|
Ошибка загрузки тарифов
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
onError: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoadingPage = () => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ textAlign: "center" }}>
|
||||||
|
{"Подождите, пожалуйста, идёт загрузка :)"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const inCart = () => {
|
||||||
|
let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]");
|
||||||
|
saveCart.forEach(async (id: string) => {
|
||||||
|
try {
|
||||||
|
await makeRequest({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `https://hub.pena.digital/customer/cart?id=${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let index = saveCart.indexOf("green");
|
||||||
|
if (index !== -1) {
|
||||||
|
saveCart.splice(index, 1);
|
||||||
|
}
|
||||||
|
localStorage.setItem("saveCart", JSON.stringify(saveCart));
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Я не смог добавить тариф в корзину :( " + id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const outCart = (cart: string[]) => {
|
||||||
|
//Сделаем муторно и подольше, зато при прерывании сессии данные потеряются минимально
|
||||||
|
cart.forEach(async (id: string) => {
|
||||||
|
try {
|
||||||
|
await makeRequest({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `https://suiz.pena.digital/customer/cart?id=${id}`,
|
||||||
|
});
|
||||||
|
let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]");
|
||||||
|
saveCart = saveCart.push(id);
|
||||||
|
localStorage.setItem("saveCart", JSON.stringify(saveCart));
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Я не смог удалить из корзины тариф :(");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
33
src/pages/Tariffs/tariffsUtils/FreeTariffCard.tsx
Normal file
33
src/pages/Tariffs/tariffsUtils/FreeTariffCard.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import TariffCard from "./TariffCard";
|
||||||
|
import NumberIcon from "@icons/NumberIcon";
|
||||||
|
import { useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
export default function FreeTariffCard() {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TariffCard
|
||||||
|
icon={<NumberIcon number={0} color="#7e2aea" backgroundColor="white" />}
|
||||||
|
discount={""}
|
||||||
|
headerText="бесплатно"
|
||||||
|
text="Первые 14 дней после регистрации, вы можете пользоваться полным функционалом сервиса совершенно бесплатно"
|
||||||
|
price={
|
||||||
|
<Typography variant="price" color="white">
|
||||||
|
0 руб.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#7e2aea",
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
buttonProps={{
|
||||||
|
text: "Выбрать",
|
||||||
|
sx: {
|
||||||
|
color: "white",
|
||||||
|
borderColor: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
145
src/pages/Tariffs/tariffsUtils/TariffCard.tsx
Normal file
145
src/pages/Tariffs/tariffsUtils/TariffCard.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
SxProps,
|
||||||
|
Theme,
|
||||||
|
Button,
|
||||||
|
Badge,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: ReactNode;
|
||||||
|
headerText: string;
|
||||||
|
discount?: string;
|
||||||
|
text: string | string[];
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
buttonProps?: {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
text?: string;
|
||||||
|
};
|
||||||
|
price?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TariffCard({
|
||||||
|
icon,
|
||||||
|
headerText,
|
||||||
|
text,
|
||||||
|
sx,
|
||||||
|
price,
|
||||||
|
buttonProps,
|
||||||
|
discount,
|
||||||
|
}: Props) {
|
||||||
|
text = Array.isArray(text) ? text : [text];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "250px",
|
||||||
|
bgcolor: "white",
|
||||||
|
borderRadius: "12px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "start",
|
||||||
|
p: "20px",
|
||||||
|
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{price && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "baseline",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
columnGap: "10px",
|
||||||
|
rowGap: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{price}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{discount && discount !== "0%" && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "5px 10px",
|
||||||
|
position: "absolute",
|
||||||
|
left: "50px",
|
||||||
|
background: "#ff4904",
|
||||||
|
borderRadius: "8px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography color="white">-{discount}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Tooltip title={<Typography>{headerText}</Typography>} placement="top">
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
mt: "14px",
|
||||||
|
mb: "10px",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{headerText}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
title={text.map((line, index) => (
|
||||||
|
<Typography key={index}>{line}</Typography>
|
||||||
|
))}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
mb: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text.map((line, index) => (
|
||||||
|
<Typography
|
||||||
|
sx={{ overflow: "hidden", textOverflow: "ellipsis" }}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{line}
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
{buttonProps && (
|
||||||
|
<Button
|
||||||
|
onClick={buttonProps.onClick}
|
||||||
|
variant="pena-outlined-purple"
|
||||||
|
sx={{
|
||||||
|
mt: "10px",
|
||||||
|
...buttonProps.sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{buttonProps.text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
250
src/pages/Tariffs/tariffsUtils/calcCart.ts
Normal file
250
src/pages/Tariffs/tariffsUtils/calcCart.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import {
|
||||||
|
CartData,
|
||||||
|
Discount,
|
||||||
|
PrivilegeCartData,
|
||||||
|
Tariff,
|
||||||
|
TariffCartData,
|
||||||
|
findPrivilegeDiscount,
|
||||||
|
findDiscountFactor,
|
||||||
|
applyLoyaltyDiscount,
|
||||||
|
} from "@frontend/kitui";
|
||||||
|
|
||||||
|
function applyPrivilegeDiscounts(cartData: CartData, discounts: Discount[]) {
|
||||||
|
cartData.services.forEach((service) => {
|
||||||
|
const privMap = new Map();
|
||||||
|
service.tariffs.forEach((tariff) =>
|
||||||
|
tariff.privileges.forEach((p) => {
|
||||||
|
privMap.set(
|
||||||
|
p.privilegeId,
|
||||||
|
p.amount + (privMap.get(p.privilegeId) || 0),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
service.tariffs.forEach((tariff) => {
|
||||||
|
tariff.privileges.forEach((privilege) => {
|
||||||
|
const privilegeDiscount = findPrivilegeDiscount(
|
||||||
|
privilege.privilegeId,
|
||||||
|
privMap.get(privilege.privilegeId) || 0,
|
||||||
|
discounts,
|
||||||
|
);
|
||||||
|
if (!privilegeDiscount) return;
|
||||||
|
|
||||||
|
const discountAmount =
|
||||||
|
privilege.price * (1 - findDiscountFactor(privilegeDiscount));
|
||||||
|
privilege.price -= discountAmount;
|
||||||
|
cartData.allAppliedDiscounts.push(privilegeDiscount);
|
||||||
|
privilege.appliedPrivilegeDiscount = privilegeDiscount;
|
||||||
|
|
||||||
|
tariff.price -= discountAmount;
|
||||||
|
service.price -= discountAmount;
|
||||||
|
cartData.priceAfterDiscounts -= discountAmount;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function findServiceDiscount(
|
||||||
|
serviceKey: string,
|
||||||
|
currentPrice: number,
|
||||||
|
discounts: Discount[],
|
||||||
|
): Discount | null {
|
||||||
|
const applicableDiscounts = discounts.filter((discount) => {
|
||||||
|
return (
|
||||||
|
discount.Layer === 2 &&
|
||||||
|
discount.Condition.Group === serviceKey &&
|
||||||
|
currentPrice >= Number(discount.Condition.PriceFrom)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!applicableDiscounts.length) return null;
|
||||||
|
|
||||||
|
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||||
|
return Number(current.Condition.PriceFrom) >
|
||||||
|
Number(prev.Condition.PriceFrom)
|
||||||
|
? current
|
||||||
|
: prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
return maxValueDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCartDiscount(
|
||||||
|
cartPurchasesAmount: number,
|
||||||
|
discounts: Discount[],
|
||||||
|
): Discount | null {
|
||||||
|
const applicableDiscounts = discounts.filter((discount) => {
|
||||||
|
return (
|
||||||
|
discount.Layer === 3 &&
|
||||||
|
cartPurchasesAmount >= Number(discount.Condition.CartPurchasesAmount)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
console.log("FCD", applicableDiscounts);
|
||||||
|
|
||||||
|
if (!applicableDiscounts.length) return null;
|
||||||
|
|
||||||
|
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||||
|
return Number(current.Condition.CartPurchasesAmount) >
|
||||||
|
Number(prev.Condition.CartPurchasesAmount)
|
||||||
|
? current
|
||||||
|
: prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
return maxValueDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCartDiscount(cartData: CartData, discounts: Discount[]) {
|
||||||
|
const cartDiscount = findCartDiscount(
|
||||||
|
cartData.priceAfterDiscounts,
|
||||||
|
discounts,
|
||||||
|
);
|
||||||
|
if (!cartDiscount) return;
|
||||||
|
|
||||||
|
cartData.priceAfterDiscounts *= findDiscountFactor(cartDiscount);
|
||||||
|
cartData.allAppliedDiscounts.push(cartDiscount);
|
||||||
|
cartData.appliedCartPurchasesDiscount = cartDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyServiceDiscounts(cartData: CartData, discounts: Discount[]) {
|
||||||
|
const privMap = new Map();
|
||||||
|
cartData.services.forEach((service) => {
|
||||||
|
service.tariffs.forEach((tariff) =>
|
||||||
|
tariff.privileges.forEach((p) => {
|
||||||
|
privMap.set(p.serviceKey, p.price + (privMap.get(p.serviceKey) || 0));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cartData.services.forEach((service) => {
|
||||||
|
service.tariffs.map((tariff) => {
|
||||||
|
tariff.privileges.forEach((privilege) => {
|
||||||
|
const privilegeDiscount = findServiceDiscount(
|
||||||
|
privilege.serviceKey,
|
||||||
|
privMap.get(privilege.serviceKey),
|
||||||
|
discounts,
|
||||||
|
);
|
||||||
|
if (!privilegeDiscount) return;
|
||||||
|
|
||||||
|
const discountAmount =
|
||||||
|
privilege.price * (1 - findDiscountFactor(privilegeDiscount));
|
||||||
|
privilege.price -= discountAmount;
|
||||||
|
cartData.allAppliedDiscounts.push(privilegeDiscount);
|
||||||
|
service.appliedServiceDiscount = privilegeDiscount;
|
||||||
|
|
||||||
|
tariff.price -= discountAmount;
|
||||||
|
service.price -= discountAmount;
|
||||||
|
cartData.priceAfterDiscounts -= discountAmount;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcCart(
|
||||||
|
tariffs: Tariff[],
|
||||||
|
discounts: Discount[],
|
||||||
|
purchasesAmount: number,
|
||||||
|
isUserNko?: boolean,
|
||||||
|
): CartData {
|
||||||
|
const cartData: CartData = {
|
||||||
|
services: [],
|
||||||
|
priceBeforeDiscounts: 0,
|
||||||
|
priceAfterDiscounts: 0,
|
||||||
|
itemCount: 0,
|
||||||
|
appliedCartPurchasesDiscount: null,
|
||||||
|
appliedLoyaltyDiscount: null,
|
||||||
|
allAppliedDiscounts: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
tariffs.forEach((tariff) => {
|
||||||
|
if (tariff.price !== undefined && tariff.privileges.length !== 1)
|
||||||
|
throw new Error("Price is defined for tariff with several");
|
||||||
|
|
||||||
|
let serviceData = cartData.services.find(
|
||||||
|
(service) => service.serviceKey === "custom" && tariff.isCustom,
|
||||||
|
);
|
||||||
|
if (!serviceData && !tariff.isCustom)
|
||||||
|
serviceData = cartData.services.find(
|
||||||
|
(service) => service.serviceKey === tariff.privileges[0].serviceKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!serviceData) {
|
||||||
|
serviceData = {
|
||||||
|
serviceKey: tariff.isCustom
|
||||||
|
? "custom"
|
||||||
|
: tariff.privileges[0].serviceKey,
|
||||||
|
tariffs: [],
|
||||||
|
price: 0,
|
||||||
|
appliedServiceDiscount: null,
|
||||||
|
};
|
||||||
|
cartData.services.push(serviceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tariffCartData: TariffCartData = {
|
||||||
|
price: tariff.price ?? 0,
|
||||||
|
isCustom: tariff.isCustom,
|
||||||
|
privileges: [],
|
||||||
|
id: tariff._id,
|
||||||
|
name: tariff.name,
|
||||||
|
};
|
||||||
|
serviceData.tariffs.push(tariffCartData);
|
||||||
|
|
||||||
|
tariff.privileges.forEach((privilege) => {
|
||||||
|
let privilegePrice = privilege.amount * privilege.price;
|
||||||
|
if (!tariff.price) tariffCartData.price += privilegePrice;
|
||||||
|
else privilegePrice = tariff.price;
|
||||||
|
|
||||||
|
const privilegeCartData: PrivilegeCartData = {
|
||||||
|
serviceKey: privilege.serviceKey,
|
||||||
|
privilegeId: privilege.privilegeId,
|
||||||
|
description: privilege.description,
|
||||||
|
price: privilegePrice,
|
||||||
|
amount: privilege.amount,
|
||||||
|
appliedPrivilegeDiscount: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
tariffCartData.privileges.push(privilegeCartData);
|
||||||
|
cartData.priceAfterDiscounts += privilegePrice;
|
||||||
|
cartData.itemCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
cartData.priceBeforeDiscounts += tariffCartData.price;
|
||||||
|
serviceData.price += tariffCartData.price;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nkoDiscount = findNkoDiscount(discounts);
|
||||||
|
if (isUserNko && nkoDiscount) {
|
||||||
|
applyNkoDiscount(cartData, nkoDiscount);
|
||||||
|
} else {
|
||||||
|
applyPrivilegeDiscounts(cartData, discounts);
|
||||||
|
applyServiceDiscounts(cartData, discounts);
|
||||||
|
applyCartDiscount(cartData, discounts);
|
||||||
|
applyLoyaltyDiscount(cartData, discounts, purchasesAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
cartData.allAppliedDiscounts = Array.from(
|
||||||
|
new Set(cartData.allAppliedDiscounts),
|
||||||
|
);
|
||||||
|
|
||||||
|
return cartData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyNkoDiscount(cartData: CartData, discount: Discount) {
|
||||||
|
cartData.priceAfterDiscounts *= discount.Target.Factor;
|
||||||
|
cartData.allAppliedDiscounts.push(discount);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findNkoDiscount(discounts: Discount[]): Discount | null {
|
||||||
|
const applicableDiscounts = discounts.filter(
|
||||||
|
(discount) => discount.Condition.UserType === "nko",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!applicableDiscounts.length) return null;
|
||||||
|
|
||||||
|
const maxValueDiscount = applicableDiscounts.reduce((prev, current) => {
|
||||||
|
return current.Condition.CartPurchasesAmount >
|
||||||
|
prev.Condition.CartPurchasesAmount
|
||||||
|
? current
|
||||||
|
: prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
return maxValueDiscount;
|
||||||
|
}
|
||||||
54
src/pages/Tariffs/tariffsUtils/calcTariffPrices.ts
Normal file
54
src/pages/Tariffs/tariffsUtils/calcTariffPrices.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Discount, Tariff, findDiscountFactor } from "@frontend/kitui";
|
||||||
|
import { calcCart } from "./calcCart";
|
||||||
|
|
||||||
|
export function calcIndividualTariffPrices(
|
||||||
|
tariff: Tariff,
|
||||||
|
discounts: Discount[],
|
||||||
|
purchasesAmount: number,
|
||||||
|
currentTariffs: Tariff[],
|
||||||
|
isUserNko?: boolean,
|
||||||
|
): {
|
||||||
|
priceBeforeDiscounts: number;
|
||||||
|
priceAfterDiscounts: number;
|
||||||
|
} {
|
||||||
|
const priceBeforeDiscounts =
|
||||||
|
tariff.price ||
|
||||||
|
tariff.privileges.reduce(
|
||||||
|
(sum, privilege) => sum + privilege.amount * privilege.price,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let priceAfterDiscounts = 0;
|
||||||
|
|
||||||
|
const cart = calcCart(
|
||||||
|
[...currentTariffs, tariff],
|
||||||
|
discounts,
|
||||||
|
purchasesAmount,
|
||||||
|
isUserNko,
|
||||||
|
);
|
||||||
|
if (cart.allAppliedDiscounts[0]?.Target.Overhelm)
|
||||||
|
return {
|
||||||
|
priceBeforeDiscounts: priceBeforeDiscounts,
|
||||||
|
priceAfterDiscounts:
|
||||||
|
priceBeforeDiscounts * cart.allAppliedDiscounts[0].Target.Factor,
|
||||||
|
};
|
||||||
|
cart.services.forEach((s) => {
|
||||||
|
if (s.serviceKey === tariff.privileges[0].serviceKey) {
|
||||||
|
let processed = false;
|
||||||
|
s.tariffs.forEach((t) => {
|
||||||
|
if (t.id === tariff._id && !processed) {
|
||||||
|
processed = true;
|
||||||
|
t.privileges.forEach((p) => (priceAfterDiscounts += p.price));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
priceAfterDiscounts *= findDiscountFactor(s.appliedServiceDiscount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
priceAfterDiscounts *= findDiscountFactor(cart.appliedLoyaltyDiscount);
|
||||||
|
priceAfterDiscounts *= findDiscountFactor(cart.appliedCartPurchasesDiscount);
|
||||||
|
|
||||||
|
// cart.allAppliedDiscounts.forEach((discount) => {
|
||||||
|
// priceAfterDiscounts *= findDiscountFactor(discount)
|
||||||
|
// })
|
||||||
|
//priceAfterDiscounts = cart.priceAfterDiscounts
|
||||||
|
return { priceBeforeDiscounts, priceAfterDiscounts };
|
||||||
|
}
|
||||||
77
src/pages/Tariffs/tariffsUtils/createTariffElements.tsx
Normal file
77
src/pages/Tariffs/tariffsUtils/createTariffElements.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { Tariff } from "@frontend/kitui";
|
||||||
|
import TariffCard from "./TariffCard";
|
||||||
|
import NumberIcon from "@icons/NumberIcon";
|
||||||
|
import { calcIndividualTariffPrices } from "./calcTariffPrices";
|
||||||
|
import { currencyFormatter } from "./currencyFormatter";
|
||||||
|
import FreeTariffCard from "./FreeTariffCard";
|
||||||
|
import { Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
export const createTariffElements = (
|
||||||
|
filteredTariffs: Tariff[],
|
||||||
|
addFreeTariff = false,
|
||||||
|
user: any,
|
||||||
|
discounts: any,
|
||||||
|
onclick: any,
|
||||||
|
) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const tariffElements = filteredTariffs
|
||||||
|
.filter((tariff) => tariff.privileges.length > 0)
|
||||||
|
.map((tariff, index) => {
|
||||||
|
const { priceBeforeDiscounts, priceAfterDiscounts } =
|
||||||
|
calcIndividualTariffPrices(
|
||||||
|
tariff,
|
||||||
|
discounts,
|
||||||
|
user.purchasesAmount,
|
||||||
|
[],
|
||||||
|
user.isUserNko,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TariffCard
|
||||||
|
key={tariff._id}
|
||||||
|
discount={
|
||||||
|
priceBeforeDiscounts - priceAfterDiscounts
|
||||||
|
? `${(
|
||||||
|
(priceBeforeDiscounts - priceAfterDiscounts) /
|
||||||
|
(priceBeforeDiscounts / 100)
|
||||||
|
).toFixed(0)}%`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
<NumberIcon
|
||||||
|
number={index + 1}
|
||||||
|
color={"#7e2aea"}
|
||||||
|
backgroundColor={"#EEE4FC"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
buttonProps={{
|
||||||
|
text: "Выбрать",
|
||||||
|
onClick: () =>
|
||||||
|
onclick({ id: tariff._id, price: priceBeforeDiscounts / 100 }),
|
||||||
|
}}
|
||||||
|
headerText={tariff.name}
|
||||||
|
text={tariff.privileges.map((p) => `${p.name} - ${p.amount}`)}
|
||||||
|
price={
|
||||||
|
<>
|
||||||
|
{priceBeforeDiscounts !== priceAfterDiscounts && (
|
||||||
|
<Typography variant="oldPrice">
|
||||||
|
{currencyFormatter.format(priceBeforeDiscounts / 100)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<Typography variant="price">
|
||||||
|
{currencyFormatter.format(priceAfterDiscounts / 100)}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (addFreeTariff) {
|
||||||
|
if (tariffElements.length < 6)
|
||||||
|
tariffElements.push(<FreeTariffCard key="free_tariff_card" />);
|
||||||
|
else tariffElements.splice(5, 0, <FreeTariffCard key="free_tariff_card" />);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tariffElements;
|
||||||
|
};
|
||||||
6
src/pages/Tariffs/tariffsUtils/currencyFormatter.ts
Normal file
6
src/pages/Tariffs/tariffsUtils/currencyFormatter.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const currencyFormatter = new Intl.NumberFormat("ru", {
|
||||||
|
currency: "RUB",
|
||||||
|
style: "currency",
|
||||||
|
compactDisplay: "short",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
});
|
||||||
@ -15,6 +15,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { resetEditConfig } from "@root/quizes/actions";
|
import { resetEditConfig } from "@root/quizes/actions";
|
||||||
import FirstQuiz from "./FirstQuiz";
|
import FirstQuiz from "./FirstQuiz";
|
||||||
import QuizCard from "./QuizCard";
|
import QuizCard from "./QuizCard";
|
||||||
|
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
outerContainerSx?: SxProps<Theme>;
|
outerContainerSx?: SxProps<Theme>;
|
||||||
@ -32,10 +33,14 @@ export default function MyQuizzesFull({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<HeaderFull />
|
||||||
{quizes.length === 0 ? (
|
{quizes.length === 0 ? (
|
||||||
<FirstQuiz />
|
<FirstQuiz />
|
||||||
) : (
|
) : (
|
||||||
<SectionWrapper maxWidth="lg">
|
<SectionWrapper
|
||||||
|
maxWidth="lg"
|
||||||
|
sx={{ padding: isMobile ? "0 16px" : "20px" }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef, useLayoutEffect } from "react";
|
||||||
import ChartIcon from "@icons/ChartIcon";
|
import ChartIcon from "@icons/ChartIcon";
|
||||||
import LinkIcon from "@icons/LinkIcon";
|
import LinkIcon from "@icons/LinkIcon";
|
||||||
import PencilIcon from "@icons/PencilIcon";
|
import PencilIcon from "@icons/PencilIcon";
|
||||||
@ -15,6 +15,9 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { inCart } from "../../pages/Tariffs/Tariffs";
|
||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
quiz: Quiz;
|
quiz: Quiz;
|
||||||
@ -42,6 +45,28 @@ export default function QuizCard({
|
|||||||
|
|
||||||
const questionCount = useRef(quiz.questions_count.toString() || "");
|
const questionCount = useRef(quiz.questions_count.toString() || "");
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const pay = async () => {
|
||||||
|
try {
|
||||||
|
await makeRequest({
|
||||||
|
method: "POST",
|
||||||
|
url: "https://suiz.pena.digital/customer/cart/pay",
|
||||||
|
});
|
||||||
|
inCart();
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar(
|
||||||
|
"Попробуйте снова купить тариф после зачисления средств",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const fromSquiz = params.get("action");
|
||||||
|
if (fromSquiz === "fromhub") {
|
||||||
|
window.history.replaceState(null, '', "/list")
|
||||||
|
pay();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -67,6 +92,7 @@ export default function QuizCard({
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
widows: "100%",
|
widows: "100%",
|
||||||
|
maxWidth: "280px",
|
||||||
}}
|
}}
|
||||||
variant="h5"
|
variant="h5"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,39 +1,255 @@
|
|||||||
import Header from "@ui_kit/Header/Header";
|
import { Header } from "@ui_kit/Header/Header";
|
||||||
import Sidebar from "@ui_kit/Sidebar";
|
import Sidebar from "@ui_kit/Sidebar";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useTheme, useMediaQuery } from "@mui/material";
|
import { useTheme, useMediaQuery, IconButton } from "@mui/material";
|
||||||
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { SidebarMobile } from "../ui_kit/Sidebar/SidebarMobile";
|
||||||
|
import { setShowConfirmLeaveModal } from "@root/uiTools/actions";
|
||||||
|
import { setCurrentStep, setQuizes } from "@root/quizes/actions";
|
||||||
|
import { useQuizStore } from "@root/quizes/store";
|
||||||
|
import { SmallSwitchQuestionListGraph } from "@ui_kit/Toolbars/SmallSwitchQuestionListGraph";
|
||||||
|
import { PanelSwitchQuestionListGraph } from "@ui_kit/Toolbars/PanelSwitchQuestionListGraph";
|
||||||
|
import { ButtonTestPublication } from "@ui_kit/Toolbars/ButtonTestPublication";
|
||||||
|
import { ButtonRecallQuiz } from "@ui_kit/Toolbars/ButtonRecallQuiz";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { LinkSimple } from "@icons/LinkSimple";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import { quizApi } from "@api/quiz";
|
||||||
|
import { questionApi } from "@api/question";
|
||||||
|
import { createResult, setQuestions } from "@root/questions/actions";
|
||||||
|
import { toggleQuizPreview } from "@root/quizPreview";
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sidebar: boolean;
|
sidebar: boolean;
|
||||||
header?: boolean;
|
header?: boolean;
|
||||||
page: JSX.Element;
|
footer?: boolean;
|
||||||
|
Page?: React.Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Main({ sidebar, header, page }: Props) {
|
export default function Main({ sidebar, header, footer, Page }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const quiz = useCurrentQuiz();
|
||||||
|
const quizConfig = quiz?.config;
|
||||||
|
const { questions } = useQuestionsStore();
|
||||||
|
const { editQuizId } = useQuizStore();
|
||||||
|
const currentStep = useQuizStore((state) => state.currentStep);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
|
const quizes = await quizApi.getList();
|
||||||
|
setQuizes(quizes);
|
||||||
|
|
||||||
|
if (editQuizId) {
|
||||||
|
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
||||||
|
|
||||||
|
setQuestions(questions);
|
||||||
|
//Всегда должен существовать хоть 1 резулт - "line"
|
||||||
|
if (
|
||||||
|
!questions?.find(
|
||||||
|
(q) =>
|
||||||
|
(q.type === "result" && q.content.includes(':"line"')) ||
|
||||||
|
q.content.includes(":'line'"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
createResult(quiz?.backendId, "line");
|
||||||
|
console.log("Я не нашёл линейный резулт и собираюсь создать новый");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||||
|
const isMobileSm = useMediaQuery(theme.breakpoints.down(370));
|
||||||
|
const isBranchingLogic = useMediaQuery(theme.breakpoints.down(1100));
|
||||||
|
const isLinkButton = useMediaQuery(theme.breakpoints.down(708));
|
||||||
|
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
|
||||||
|
const [nextStep, setNextStep] = useState<number>(0);
|
||||||
|
const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const openBranchingPageHC = () => {
|
||||||
|
if (!openBranchingPage) {
|
||||||
|
deleteTimeoutedQuestions(questions, quiz);
|
||||||
|
}
|
||||||
|
setOpenBranchingPage((old) => !old);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!quizConfig) return <></>;
|
||||||
|
|
||||||
|
const isConditionMet =
|
||||||
|
[1].includes(currentStep) && quizConfig.type !== "form";
|
||||||
|
|
||||||
|
const changePage = (index: number) => {
|
||||||
|
if (currentStep === 2) {
|
||||||
|
setNextStep(index);
|
||||||
|
setShowConfirmLeaveModal(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentStep(index);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{header ? <Header /> : <HeaderFull />}
|
<Header setMobileSidebar={setMobileSidebar} />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
flexDirection: isMobile ? "column" : "row",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sidebar ? <Sidebar /> : <></>}
|
{sidebar ? (
|
||||||
|
<>
|
||||||
|
{isMobile ? (
|
||||||
|
<SidebarMobile open={mobileSidebar} changePage={changePage} />
|
||||||
|
) : (
|
||||||
|
<Sidebar changePage={changePage} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
background: theme.palette.background.default,
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
padding: isMobile ? "15px" : "25px",
|
height: isMobile ? "calc(100vh - 51px)" : "calc(100vh - 80px)",
|
||||||
height: "calc(100vh - 80px)",
|
display: "flex",
|
||||||
overflow: "auto",
|
flexDirection: "column",
|
||||||
boxSizing: "border-box",
|
justifyContent: "space-between",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{page}
|
<Box
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
width: "100%",
|
||||||
|
overflow: "auto",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Page
|
||||||
|
openBranchingPage={openBranchingPage}
|
||||||
|
setOpenBranchingPage={openBranchingPageHC}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{footer && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
padding: isMobile ? "20px 16px" : "20px 20px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: isMobile
|
||||||
|
? isMobileSm
|
||||||
|
? "center"
|
||||||
|
: "flex-end"
|
||||||
|
: "flex-start",
|
||||||
|
flexDirection: isMobile ? "row-reverse" : "-moz-initial",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "15px",
|
||||||
|
background: "#FFF",
|
||||||
|
borderTop: "#f2f3f7 2px solid",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isConditionMet &&
|
||||||
|
(isBranchingLogic ? (
|
||||||
|
<SmallSwitchQuestionListGraph
|
||||||
|
openBranchingPage={openBranchingPage}
|
||||||
|
setOpenBranchingPage={openBranchingPageHC}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PanelSwitchQuestionListGraph
|
||||||
|
openBranchingPage={openBranchingPage}
|
||||||
|
setOpenBranchingPage={openBranchingPageHC}
|
||||||
|
hideText
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{/* Кнопка тестового просмотра */}
|
||||||
|
<ButtonTestPublication />
|
||||||
|
{/* Кнопка отозвать */}
|
||||||
|
<ButtonRecallQuiz />
|
||||||
|
{/* Ссылка */}
|
||||||
|
{quiz?.status === "start" &&
|
||||||
|
(!isLinkButton ? (
|
||||||
|
<Box
|
||||||
|
component={Link}
|
||||||
|
sx={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: isMobile ? "none" : "block",
|
||||||
|
color: "#7E2AEA",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
target="_blank"
|
||||||
|
to={"https://hbpn.link/" + quiz.qid}
|
||||||
|
>
|
||||||
|
https://hbpn.link/{quiz.qid}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
component={Link}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
minWidth: "34px",
|
||||||
|
height: "34px",
|
||||||
|
color: "#7E2AEA",
|
||||||
|
fontSize: "14px",
|
||||||
|
display: isMobile ? "none" : "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "Center",
|
||||||
|
background: "#EEE4FC",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
target="_blank"
|
||||||
|
to={"https://hbpn.link/" + quiz.qid}
|
||||||
|
>
|
||||||
|
<LinkSimple />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{/* Маленькая кнопка ссылки */}
|
||||||
|
{isMobile && quiz?.status === "start" && (
|
||||||
|
<Box
|
||||||
|
component={Link}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "34px",
|
||||||
|
height: "34px",
|
||||||
|
color: "#7E2AEA",
|
||||||
|
fontSize: "14px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "Center",
|
||||||
|
background: "#EEE4FC",
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
target="_blank"
|
||||||
|
to={"https://hbpn.link/" + quiz.qid}
|
||||||
|
>
|
||||||
|
<LinkSimple />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box sx={{ textAlign: "end", width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={toggleQuizPreview}
|
||||||
|
sx={{
|
||||||
|
pointerEvents: "auto",
|
||||||
|
marginLeft: "auto",
|
||||||
|
position: "relative",
|
||||||
|
zIndex: "999999",
|
||||||
|
alignItems: "end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VisibilityIcon sx={{ height: "30px", width: "30px" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import SwitchStepPages from "@ui_kit/switchStepPages";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
import { SidebarMobile } from "../../ui_kit/Sidebar/SidebarMobile";
|
||||||
import {
|
import {
|
||||||
cleanQuestions,
|
cleanQuestions,
|
||||||
createResult,
|
createResult,
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
setShowConfirmLeaveModal,
|
setShowConfirmLeaveModal,
|
||||||
updateSomeWorkBackend,
|
updateSomeWorkBackend,
|
||||||
} from "@root/uiTools/actions";
|
} from "@root/uiTools/actions";
|
||||||
import { Header } from "./Header";
|
import { Header } from "@ui_kit/Header/Header";
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
import { questionApi } from "@api/question";
|
import { questionApi } from "@api/question";
|
||||||
import { useUiTools } from "@root/uiTools/store";
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
@ -41,64 +41,28 @@ import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
|||||||
import { ModalInfoWhyCantCreate } from "./ModalInfoWhyCantCreate";
|
import { ModalInfoWhyCantCreate } from "./ModalInfoWhyCantCreate";
|
||||||
import { ConfirmLeaveModal } from "./ConfirmLeaveModal";
|
import { ConfirmLeaveModal } from "./ConfirmLeaveModal";
|
||||||
import { checkQuestionHint } from "@utils/checkQuestionHint";
|
import { checkQuestionHint } from "@utils/checkQuestionHint";
|
||||||
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
|
|
||||||
import { toggleQuizPreview } from "@root/quizPreview";
|
|
||||||
import { LinkSimple } from "@icons/LinkSimple";
|
|
||||||
import { SmallSwitchQuestionListGraph } from "@ui_kit/Toolbars/SmallSwitchQuestionListGraph";
|
|
||||||
import { PanelSwitchQuestionListGraph } from "@ui_kit/Toolbars/PanelSwitchQuestionListGraph";
|
|
||||||
import { ButtonTestPublication } from "@ui_kit/Toolbars/ButtonTestPublication";
|
|
||||||
import { ButtonRecallQuiz } from "@ui_kit/Toolbars/ButtonRecallQuiz";
|
|
||||||
|
|
||||||
export default function EditPage() {
|
interface Props {
|
||||||
|
openBranchingPage: boolean;
|
||||||
|
setOpenBranchingPage: (a: boolean) => void;
|
||||||
|
}
|
||||||
|
export default function EditPage({
|
||||||
|
openBranchingPage,
|
||||||
|
setOpenBranchingPage,
|
||||||
|
}: Props) {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const { editQuizId } = useQuizStore();
|
const { editQuizId } = useQuizStore();
|
||||||
const { questions } = useQuestionsStore();
|
const { questions } = useQuestionsStore();
|
||||||
console.log(questions);
|
console.log(questions);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getData = async () => {
|
|
||||||
const quizes = await quizApi.getList();
|
|
||||||
setQuizes(quizes);
|
|
||||||
|
|
||||||
if (editQuizId) {
|
|
||||||
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
|
||||||
|
|
||||||
setQuestions(questions);
|
|
||||||
//Всегда должен существовать хоть 1 резулт - "line"
|
|
||||||
if (
|
|
||||||
!questions?.find(
|
|
||||||
(q) =>
|
|
||||||
(q.type === "result" && q.content.includes(':"line"')) ||
|
|
||||||
q.content.includes(":'line'"),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
createResult(quiz?.backendId, "line");
|
|
||||||
console.log("Я не нашёл линейный резулт и собираюсь создать новый");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { whyCantCreatePublic, showConfirmLeaveModal } = useUiTools();
|
const { whyCantCreatePublic, showConfirmLeaveModal } = useUiTools();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentStep = useQuizStore((state) => state.currentStep);
|
const currentStep = useQuizStore((state) => state.currentStep);
|
||||||
const isBranchingLogic = useMediaQuery(theme.breakpoints.down(1100));
|
const isBranchingLogic = useMediaQuery(theme.breakpoints.down(1100));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
const isLinkButton = useMediaQuery(theme.breakpoints.down(708));
|
|
||||||
const isMobileSm = useMediaQuery(theme.breakpoints.down(370));
|
|
||||||
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
|
|
||||||
const [nextStep, setNextStep] = useState<number>(0);
|
const [nextStep, setNextStep] = useState<number>(0);
|
||||||
const quizConfig = quiz?.config;
|
const quizConfig = quiz?.config;
|
||||||
const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false);
|
// const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false);
|
||||||
|
|
||||||
const openBranchingPageHC = () => {
|
|
||||||
if (!openBranchingPage) {
|
|
||||||
deleteTimeoutedQuestions(questions, quiz);
|
|
||||||
}
|
|
||||||
setOpenBranchingPage((old) => !old);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editQuizId === null) navigate("/list");
|
if (editQuizId === null) navigate("/list");
|
||||||
@ -144,33 +108,14 @@ export default function EditPage() {
|
|||||||
const isConditionMet =
|
const isConditionMet =
|
||||||
[1].includes(currentStep) && quizConfig.type !== "form";
|
[1].includes(currentStep) && quizConfig.type !== "form";
|
||||||
|
|
||||||
const changePage = (index: number) => {
|
|
||||||
if (currentStep === 2) {
|
|
||||||
setNextStep(index);
|
|
||||||
setShowConfirmLeaveModal(true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentStep(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header setMobileSidebar={setMobileSidebar} />
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: isMobile ? "block" : "flex",
|
display: isMobile ? "block" : "flex",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isMobile ? (
|
|
||||||
<SidebarMobile open={mobileSidebar} changePage={changePage} />
|
|
||||||
) : (
|
|
||||||
<Sidebar changePage={changePage} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
@ -207,103 +152,6 @@ export default function EditPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
padding: isMobile ? "20px 16px" : "20px 20px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: isMobile
|
|
||||||
? isMobileSm
|
|
||||||
? "center"
|
|
||||||
: "flex-end"
|
|
||||||
: "flex-start",
|
|
||||||
flexDirection: isMobile ? "row-reverse" : "-moz-initial",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "15px",
|
|
||||||
background: "#FFF",
|
|
||||||
borderTop: "#f2f3f7 2px solid",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isConditionMet &&
|
|
||||||
(isBranchingLogic ? (
|
|
||||||
<SmallSwitchQuestionListGraph
|
|
||||||
openBranchingPage={openBranchingPage}
|
|
||||||
setOpenBranchingPage={openBranchingPageHC}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<PanelSwitchQuestionListGraph
|
|
||||||
openBranchingPage={openBranchingPage}
|
|
||||||
setOpenBranchingPage={openBranchingPageHC}
|
|
||||||
hideText
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{/* Кнопка тестового просмотра */}
|
|
||||||
<ButtonTestPublication />
|
|
||||||
{/* Кнопка отозвать */}
|
|
||||||
<ButtonRecallQuiz />
|
|
||||||
{/* Ссылка */}
|
|
||||||
{quiz?.status === "start" &&
|
|
||||||
(!isLinkButton ? (
|
|
||||||
<Box
|
|
||||||
component={Link}
|
|
||||||
sx={{
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
display: isMobile ? "none" : "block",
|
|
||||||
color: "#7E2AEA",
|
|
||||||
fontSize: "14px",
|
|
||||||
}}
|
|
||||||
target="_blank"
|
|
||||||
to={"https://hbpn.link/" + quiz.qid}
|
|
||||||
>
|
|
||||||
https://hbpn.link/{quiz.qid}
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
component={Link}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
minWidth: "34px",
|
|
||||||
height: "34px",
|
|
||||||
color: "#7E2AEA",
|
|
||||||
fontSize: "14px",
|
|
||||||
display: isMobile ? "none" : "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "Center",
|
|
||||||
background: "#EEE4FC",
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
target="_blank"
|
|
||||||
to={"https://hbpn.link/" + quiz.qid}
|
|
||||||
>
|
|
||||||
<LinkSimple />
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
{/* Маленькая кнопка ссылки */}
|
|
||||||
{isMobile && quiz?.status === "start" && (
|
|
||||||
<Box
|
|
||||||
component={Link}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
width: "34px",
|
|
||||||
height: "34px",
|
|
||||||
color: "#7E2AEA",
|
|
||||||
fontSize: "14px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "Center",
|
|
||||||
background: "#EEE4FC",
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
target="_blank"
|
|
||||||
to={"https://hbpn.link/" + quiz.qid}
|
|
||||||
>
|
|
||||||
<LinkSimple />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
import { LogoutButton } from "@ui_kit/LogoutButton";
|
|
||||||
import BackArrowIcon from "@icons/BackArrowIcon";
|
|
||||||
import { Burger } from "@icons/Burger";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Container,
|
|
||||||
FormControl,
|
|
||||||
IconButton,
|
|
||||||
TextField,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { updateQuiz } from "@root/quizes/actions";
|
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
|
||||||
import CustomAvatar from "@ui_kit/Header/Avatar";
|
|
||||||
import NavMenuItem from "@ui_kit/Header/NavMenuItem";
|
|
||||||
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import Logotip from "../Landing/images/icons/QuizLogo";
|
|
||||||
import { clearUserData } from "@root/user";
|
|
||||||
import { clearAuthToken } from "@frontend/kitui";
|
|
||||||
import { logout } from "@api/auth";
|
|
||||||
|
|
||||||
type HeaderProps = {
|
|
||||||
setMobileSidebar: (callback: (visible: boolean) => boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Header = ({ setMobileSidebar }: HeaderProps) => {
|
|
||||||
const quiz = useCurrentQuiz();
|
|
||||||
const theme = useTheme();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
|
||||||
const [, logoutError] = await logout();
|
|
||||||
|
|
||||||
if (logoutError) {
|
|
||||||
return enqueueSnackbar(logoutError);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAuthToken();
|
|
||||||
clearUserData();
|
|
||||||
navigate("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
component="nav"
|
|
||||||
maxWidth={false}
|
|
||||||
disableGutters
|
|
||||||
sx={{
|
|
||||||
px: "16px",
|
|
||||||
display: "flex",
|
|
||||||
height: isMobile ? "51px" : "80px",
|
|
||||||
alignItems: "center",
|
|
||||||
bgcolor: isMobile ? "#333647" : "white",
|
|
||||||
borderBottom: "1px solid #E3E3E3",
|
|
||||||
zIndex: theme.zIndex.drawer + 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link to="/" style={{ display: "flex" }}>
|
|
||||||
{isMobile ? <Logotip width={100} /> : <Logotip width={124} />}
|
|
||||||
</Link>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: isMobile ? "none" : "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
ml: "37px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link to="/list">
|
|
||||||
<IconButton sx={{ p: "6px" }}>
|
|
||||||
<BackArrowIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Link>
|
|
||||||
<FormControl fullWidth variant="standard">
|
|
||||||
<TextField
|
|
||||||
value={quiz.name}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateQuiz(quiz.id, (quiz) => {
|
|
||||||
quiz.name = e.target.value;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
id="project-name"
|
|
||||||
placeholder="Название проекта окно"
|
|
||||||
sx={{
|
|
||||||
width: "270px",
|
|
||||||
"& .MuiInputBase-root": {
|
|
||||||
height: "34px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
p: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
inputProps={{
|
|
||||||
sx: {
|
|
||||||
height: "20px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "20px",
|
|
||||||
p: "7px",
|
|
||||||
color: "black",
|
|
||||||
"&::placeholder": {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
{isTablet ? (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
ml: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isMobile ? (
|
|
||||||
<Burger
|
|
||||||
onClick={() => setMobileSidebar((visible: boolean) => !visible)}
|
|
||||||
style={{ fontSize: "30px", color: "white", cursor: "pointer" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box>
|
|
||||||
{/* <CustomAvatar
|
|
||||||
sx={{
|
|
||||||
ml: "11px",
|
|
||||||
backgroundColor: theme.palette.orange.main,
|
|
||||||
height: "36px",
|
|
||||||
width: "36px",
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "30px",
|
|
||||||
overflow: "hidden",
|
|
||||||
ml: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* <NavMenuItem text="Редактировать" isActive /> */}
|
|
||||||
{/* <NavMenuItem text="Заявки" />
|
|
||||||
<NavMenuItem text="Аналитика" />
|
|
||||||
<NavMenuItem text="История" />
|
|
||||||
<NavMenuItem text="Помощь" /> */}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
ml: "auto",
|
|
||||||
gap: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* <CustomAvatar
|
|
||||||
sx={{
|
|
||||||
ml: "11px",
|
|
||||||
backgroundColor: theme.palette.orange.main,
|
|
||||||
height: "36px",
|
|
||||||
width: "36px",
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<LogoutButton onClick={handleLogoutClick} />
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
133
src/ui_kit/Header/Header.tsx
Executable file → Normal file
133
src/ui_kit/Header/Header.tsx
Executable file → Normal file
@ -1,8 +1,9 @@
|
|||||||
|
import { LogoutButton } from "@ui_kit/LogoutButton";
|
||||||
import BackArrowIcon from "@icons/BackArrowIcon";
|
import BackArrowIcon from "@icons/BackArrowIcon";
|
||||||
import EyeIcon from "@icons/EyeIcon";
|
import { Burger } from "@icons/Burger";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
Container,
|
Container,
|
||||||
FormControl,
|
FormControl,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -10,15 +11,43 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { decrementCurrentStep } from "@root/quizes/actions";
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
import PenaLogo from "../PenaLogo";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import CustomAvatar from "./Avatar";
|
import CustomAvatar from "@ui_kit/Header/Avatar";
|
||||||
import NavMenuItem from "./NavMenuItem";
|
import NavMenuItem from "@ui_kit/Header/NavMenuItem";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export default function Header() {
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import Logotip from "../../pages/Landing/images/icons/QuizLogo";
|
||||||
|
import { clearUserData } from "@root/user";
|
||||||
|
import { clearAuthToken } from "@frontend/kitui";
|
||||||
|
import { logout } from "@api/auth";
|
||||||
|
import { ToTariffsButton } from "@ui_kit/Toolbars/ToTariffsButton";
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
setMobileSidebar: (callback: (visible: boolean) => boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = ({ setMobileSidebar }: HeaderProps) => {
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||||
|
|
||||||
|
async function handleLogoutClick() {
|
||||||
|
const [, logoutError] = await logout();
|
||||||
|
|
||||||
|
if (logoutError) {
|
||||||
|
return enqueueSnackbar(logoutError);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAuthToken();
|
||||||
|
clearUserData();
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
@ -28,31 +57,43 @@ export default function Header() {
|
|||||||
sx={{
|
sx={{
|
||||||
px: "16px",
|
px: "16px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: "80px",
|
height: isMobile ? "51px" : "80px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
bgcolor: "white",
|
bgcolor: isMobile ? "#333647" : "white",
|
||||||
borderBottom: "1px solid #E3E3E3",
|
borderBottom: "1px solid #E3E3E3",
|
||||||
zIndex: theme.zIndex.drawer + 1,
|
zIndex: theme.zIndex.drawer + 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link to="/">
|
<Link to="/" style={{ display: "flex" }}>
|
||||||
<PenaLogo width={124} />
|
{isMobile ? (
|
||||||
|
<Logotip width={100} color={"white"} />
|
||||||
|
) : (
|
||||||
|
<Logotip width={124} />
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: isMobile ? "none" : "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
ml: "37px",
|
ml: "37px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton sx={{ p: "6px" }} onClick={decrementCurrentStep}>
|
<Link to="/list">
|
||||||
<BackArrowIcon />
|
<IconButton sx={{ p: "6px" }}>
|
||||||
</IconButton>
|
<BackArrowIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Link>
|
||||||
<FormControl fullWidth variant="standard">
|
<FormControl fullWidth variant="standard">
|
||||||
<TextField
|
<TextField
|
||||||
|
value={quiz.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.name = e.target.value;
|
||||||
|
})
|
||||||
|
}
|
||||||
fullWidth
|
fullWidth
|
||||||
id="project-name"
|
id="project-name"
|
||||||
placeholder="Название проекта окно"
|
placeholder="Название проекта"
|
||||||
sx={{
|
sx={{
|
||||||
width: "270px",
|
width: "270px",
|
||||||
"& .MuiInputBase-root": {
|
"& .MuiInputBase-root": {
|
||||||
@ -77,36 +118,32 @@ export default function Header() {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box
|
||||||
{isTablet ? null : (
|
sx={{
|
||||||
<>
|
display: "flex",
|
||||||
<Box
|
ml: "auto",
|
||||||
sx={{
|
}}
|
||||||
display: "flex",
|
>
|
||||||
gap: "31px",
|
{isMobile ? (
|
||||||
overflow: "hidden",
|
<Burger
|
||||||
ml: "20px",
|
onClick={() => setMobileSidebar((visible: boolean) => !visible)}
|
||||||
}}
|
style={{ fontSize: "30px", color: "white", cursor: "pointer" }}
|
||||||
>
|
/>
|
||||||
{/* <NavMenuItem text="Редактировать" isActive /> */}
|
) : (
|
||||||
{/* <NavMenuItem text="Заявки" />
|
<>
|
||||||
<NavMenuItem text="Аналитика" />
|
<Box
|
||||||
<NavMenuItem text="История" />
|
sx={{
|
||||||
<NavMenuItem text="Помощь" /> */}
|
display: "flex",
|
||||||
</Box>
|
ml: "auto",
|
||||||
<Box
|
gap: "15px",
|
||||||
sx={{
|
}}
|
||||||
display: "flex",
|
>
|
||||||
ml: "auto",
|
<LogoutButton onClick={handleLogoutClick} />
|
||||||
gap: "15px",
|
</Box>
|
||||||
}}
|
<ToTariffsButton />
|
||||||
>
|
</>
|
||||||
{/* <CustomAvatar
|
)}
|
||||||
sx={{ ml: "11px", backgroundColor: theme.palette.orange.main, height: "36px", width: "36px" }}
|
</Box>
|
||||||
/> */}
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { clearUserData } from "@root/user";
|
import { clearUserData } from "@root/user";
|
||||||
import { LogoutButton } from "@ui_kit/LogoutButton";
|
import { LogoutButton } from "@ui_kit/LogoutButton";
|
||||||
|
import { ToTariffsButton } from "@ui_kit/Toolbars/ToTariffsButton";
|
||||||
|
|
||||||
export default function HeaderFull() {
|
export default function HeaderFull() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -47,18 +48,12 @@ export default function HeaderFull() {
|
|||||||
height: "80px",
|
height: "80px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: isTablet ? "20px" : "60px",
|
gap: isTablet ? "20px" : "60px",
|
||||||
flexDirection: isMobile ? "row-reverse" : "row",
|
flexDirection: "row",
|
||||||
justifyContent: isMobile ? "space-between" : "center",
|
justifyContent: isMobile ? "space-between" : "center",
|
||||||
bgcolor: "white",
|
bgcolor: "white",
|
||||||
borderBottom: "1px solid #E3E3E3",
|
borderBottom: "1px solid #E3E3E3",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isTablet && (
|
|
||||||
<Burger
|
|
||||||
// onClick={() => setMobileSidebar(!mobileSidebar)}
|
|
||||||
style={{ fontSize: "30px", color: "#000000", cursor: "pointer" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Logotip width={124} />
|
<Logotip width={124} />
|
||||||
</Link>
|
</Link>
|
||||||
@ -69,57 +64,17 @@ export default function HeaderFull() {
|
|||||||
gap: "30px",
|
gap: "30px",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
></Box>
|
||||||
{/* <NavMenuItem text="Квизы" />
|
|
||||||
<NavMenuItem text="Меню 2" isActive />
|
|
||||||
<NavMenuItem text="Меню 3" />
|
|
||||||
<NavMenuItem text="Меню 4" />
|
|
||||||
<NavMenuItem text="Меню 5" />
|
|
||||||
<NavMenuItem text="Меню 1" />
|
|
||||||
<NavMenuItem text="Меню 2" /> */}
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
<Box sx={{ display: isMobile ? "none" : "flex", ml: "auto" }}>
|
<Box sx={{ display: "flex", ml: "auto" }}>
|
||||||
{/* {!isTablet && (
|
<LogoutButton
|
||||||
<>
|
onClick={handleLogoutClick}
|
||||||
<IconButton sx={{ p: 0 }}>
|
sx={{
|
||||||
<WalletIcon color={theme.palette.grey2.main} bgcolor="#F2F3F7" />
|
ml: "20px",
|
||||||
</IconButton>
|
}}
|
||||||
<Box sx={{ ml: "8px", whiteSpace: "nowrap" }}>
|
/>
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "12px",
|
|
||||||
lineHeight: "14px",
|
|
||||||
color: theme.palette.grey3.main,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Мой баланс
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" color={theme.palette.brightPurple.main}>
|
|
||||||
00.00 руб.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)} */}
|
|
||||||
{!isMobile && (
|
|
||||||
<>
|
|
||||||
{/* <CustomAvatar
|
|
||||||
sx={{
|
|
||||||
ml: "27px",
|
|
||||||
backgroundColor: theme.palette.orange.main,
|
|
||||||
height: "36px",
|
|
||||||
width: "36px",
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<LogoutButton
|
|
||||||
onClick={handleLogoutClick}
|
|
||||||
sx={{
|
|
||||||
ml: "20px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<ToTariffsButton />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export default function NavMenuItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={href} underline="none" onClick={onClick}>
|
<Link href={href} underline="none" onClick={onClick}>
|
||||||
|
я есть навбар меню итем
|
||||||
<Typography
|
<Typography
|
||||||
color={isActive ? theme.palette.brightPurple.main : undefined}
|
color={isActive ? theme.palette.brightPurple.main : undefined}
|
||||||
variant="body2"
|
variant="body2"
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
я подписан как навбар коллапсед
|
||||||
<PenaLogo width={100} />
|
<PenaLogo width={100} />
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@ -170,6 +170,10 @@ export const CropModal: FC<Props> = ({
|
|||||||
padding: "20px",
|
padding: "20px",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
width: isMobile ? "343px" : "620px",
|
width: isMobile ? "343px" : "620px",
|
||||||
|
height: isMobile ? "80vh" : undefined,
|
||||||
|
display: isMobile ? "flex" : undefined,
|
||||||
|
flexDirection: isMobile ? "column" : undefined,
|
||||||
|
justifyContent: isMobile ? "space-evenly" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -258,7 +262,7 @@ export const CropModal: FC<Props> = ({
|
|||||||
sx={[
|
sx={[
|
||||||
styleSlider,
|
styleSlider,
|
||||||
{
|
{
|
||||||
width: isMobile ? "350px" : "250px",
|
width: isMobile ? undefined : "250px",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={scale * 100}
|
value={scale * 100}
|
||||||
@ -278,7 +282,7 @@ export const CropModal: FC<Props> = ({
|
|||||||
sx={[
|
sx={[
|
||||||
styleSlider,
|
styleSlider,
|
||||||
{
|
{
|
||||||
width: isMobile ? "350px" : "250px",
|
width: isMobile ? undefined : "250px",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={darken}
|
value={darken}
|
||||||
@ -295,6 +299,7 @@ export const CropModal: FC<Props> = ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
|
flexWrap: isMobile ? "wrap" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { useState } from "react";
|
|||||||
import MenuItem from "./MenuItem";
|
import MenuItem from "./MenuItem";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { setCurrentStep } from "@root/quizes/actions";
|
||||||
|
|
||||||
const quizSettingsMenuItems = [
|
const quizSettingsMenuItems = [
|
||||||
[TagIcon, "Дополнения"],
|
[TagIcon, "Дополнения"],
|
||||||
@ -147,7 +148,10 @@ export default function Sidebar({ changePage, disableCollapse }: SidebarProps) {
|
|||||||
)}
|
)}
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => navigate("/design")}
|
onClick={() => {
|
||||||
|
navigate("/design");
|
||||||
|
setCurrentStep(15);
|
||||||
|
}}
|
||||||
text={"Дизайн"}
|
text={"Дизайн"}
|
||||||
isCollapsed={isMenuCollapsed}
|
isCollapsed={isMenuCollapsed}
|
||||||
isActive={currentStep === 6}
|
isActive={currentStep === 6}
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useEffect, useRef, useState } from "react";
|
||||||
import { Box, Popper, Typography } from "@mui/material";
|
import {
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Popper,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
import { People } from "@mui/icons-material";
|
import { People } from "@mui/icons-material";
|
||||||
|
|
||||||
import { SidebarModal } from "./SidebarModal";
|
import { SidebarModal } from "./SidebarModal";
|
||||||
@ -12,6 +19,9 @@ import { Settings } from "./icons/Settings";
|
|||||||
import { Pencil } from "./icons/Pencil";
|
import { Pencil } from "./icons/Pencil";
|
||||||
import { ArrowDown } from "./icons/ArrowDown";
|
import { ArrowDown } from "./icons/ArrowDown";
|
||||||
import Sidebar from "@ui_kit/Sidebar";
|
import Sidebar from "@ui_kit/Sidebar";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
|
||||||
interface Iprops {
|
interface Iprops {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -32,14 +42,25 @@ const quizSetupSteps = [
|
|||||||
|
|
||||||
export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => {
|
export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => {
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const [inputOpen, setInputOpen] = useState<boolean>(false);
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
setAnchorEl(anchorEl ? null : event.currentTarget);
|
setAnchorEl(anchorEl ? null : event.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clickInput = (event) => {
|
||||||
|
if (ref.current && !ref.current.contains(event.target)) setInputOpen(false);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", clickInput);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", clickInput);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
const openPopper = Boolean(anchorEl);
|
const openPopper = Boolean(anchorEl);
|
||||||
const id = openPopper ? "simple-popper" : undefined;
|
const id = openPopper ? "simple-popper" : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -54,27 +75,75 @@ export const SidebarMobile: FC<Iprops> = ({ open, changePage }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", position: "relative" }}>
|
<Box sx={{ display: "flex", alignItems: "center", position: "relative" }}>
|
||||||
<BackArrowIcon color="white" />
|
<Link to="/list">
|
||||||
|
<IconButton sx={{ p: "6px" }}>
|
||||||
|
<BackArrowIcon color={"white"} />
|
||||||
|
</IconButton>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<Box sx={{ ml: "15px", display: "flex", alignItems: "end" }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
ml: "15px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "end",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{ fontSize: "12px", color: "#9A9AAF" }}>
|
<Typography sx={{ fontSize: "12px", color: "#9A9AAF" }}>
|
||||||
Название
|
Название
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
{inputOpen ? (
|
||||||
sx={{ color: "#FFF", fontSize: "18px", fontWeight: "500" }}
|
<FormControl fullWidth variant="standard">
|
||||||
>
|
<TextField
|
||||||
Название проекта
|
ref={ref}
|
||||||
</Typography>
|
value={quiz.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.name = e.target.value;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
id="project-name"
|
||||||
|
placeholder="Название проекта"
|
||||||
|
sx={{
|
||||||
|
width: "270px",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
height: "34px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
p: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
height: "20px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "20px",
|
||||||
|
p: "7px",
|
||||||
|
color: "white",
|
||||||
|
"&::placeholder": {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
) : (
|
||||||
|
<Typography color={"white"}>{quiz.name}</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Pencil
|
<IconButton onClick={() => setInputOpen(true)}>
|
||||||
style={{
|
<Pencil
|
||||||
position: "absolute",
|
style={{
|
||||||
right: "0",
|
position: "absolute",
|
||||||
color: "white",
|
right: "0",
|
||||||
fontSize: "24px",
|
color: "white",
|
||||||
}}
|
fontSize: "24px",
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
10
src/ui_kit/Toolbars/ToTariffsButton.tsx
Normal file
10
src/ui_kit/Toolbars/ToTariffsButton.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Button } from "@mui/material";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export const ToTariffsButton = () => {
|
||||||
|
return (
|
||||||
|
<Link to="/tariffs">
|
||||||
|
<Button>Пополнить</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user