Merge branch 'backend-integration' into view-logic
This commit is contained in:
commit
5ea76afceb
@ -12,10 +12,10 @@ import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
|||||||
import Landing from "./pages/Landing/Landing";
|
import Landing from "./pages/Landing/Landing";
|
||||||
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
||||||
import { Result } from "./pages/ResultPage/Result";
|
import { Result } from "./pages/ResultPage/Result";
|
||||||
import { Setting } from "./pages/ResultPage/Setting";
|
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 StartPage from "./pages/startPage/StartPage";
|
import EditPage from "./pages/startPage/EditPage";
|
||||||
import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui";
|
import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui";
|
||||||
import { clearUserData, setUser, useUserStore } from "@root/user";
|
import { clearUserData, setUser, useUserStore } from "@root/user";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
@ -28,7 +28,7 @@ const routeslink = [
|
|||||||
{ path: "/questions/:quizId", page: <QuestionsPage />, header: true, sidebar: true, },
|
{ path: "/questions/:quizId", page: <QuestionsPage />, header: true, sidebar: true, },
|
||||||
{ path: "/contacts", page: <ContactFormPage />, header: true, sidebar: true },
|
{ path: "/contacts", page: <ContactFormPage />, header: true, sidebar: true },
|
||||||
{ path: "/result", page: <Result />, header: true, sidebar: true },
|
{ path: "/result", page: <Result />, header: true, sidebar: true },
|
||||||
{ path: "/settings", page: <Setting />, header: true, sidebar: true },
|
{ path: "/settings", page: <ResultSettings />, header: true, sidebar: true },
|
||||||
{ path: "/install", page: <InstallQuiz />, header: true, sidebar: true },
|
{ path: "/install", page: <InstallQuiz />, header: true, sidebar: true },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ export default function App() {
|
|||||||
{routeslink.map((e, i) => (
|
{routeslink.map((e, i) => (
|
||||||
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
|
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
|
||||||
))}
|
))}
|
||||||
<Route path="edit" element={<StartPage />} />
|
<Route path="edit" element={<EditPage />} />
|
||||||
<Route path="crop" element={<ImageCrop />} />
|
<Route path="crop" element={<ImageCrop />} />
|
||||||
<Route path="/" element={<Landing />} />
|
<Route path="/" element={<Landing />} />
|
||||||
<Route path="/signin" element={<SigninDialog />} />
|
<Route path="/signin" element={<SigninDialog />} />
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 823 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
18
src/assets/icons/ExpandLessIconBG.tsx
Normal file
18
src/assets/icons/ExpandLessIconBG.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { useTheme, SxProps, Box } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sx?: SxProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ExpandIcon({ sx }: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ ...sx }}>
|
||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="30" height="30" rx="6" fill="#EEE4FC" />
|
||||||
|
<path d="M22.5 11.25L15 18.75L7.5 11.25" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M18 -2.62268e-07C21.3137 -1.17422e-07 24 2.68629 24 6L24 18C24 21.3137 21.3137 24 18 24L6 24C2.68629 24 -9.31652e-07 21.3137 -7.86805e-07 18L-5.24537e-07 12L-2.62268e-07 6C-1.17422e-07 2.68629 2.68629 -9.31652e-07 6 -7.86805e-07L18 -2.62268e-07Z" fill="#9A9AAF" fill-opacity="0.7"/>
|
<path d="M18 -2.62268e-07C21.3137 -1.17422e-07 24 2.68629 24 6L24 18C24 21.3137 21.3137 24 18 24L6 24C2.68629 24 -9.31652e-07 21.3137 -7.86805e-07 18L-5.24537e-07 12L-2.62268e-07 6C-1.17422e-07 2.68629 2.68629 -9.31652e-07 6 -7.86805e-07L18 -2.62268e-07Z" fill="#9A9AAF" fill-opacity="0.7"/>
|
||||||
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" strokeLinejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 494 B |
@ -8,19 +8,10 @@ export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId">
|
|||||||
type: "result",
|
type: "result",
|
||||||
content: {
|
content: {
|
||||||
...QUIZ_QUESTION_BASE.content,
|
...QUIZ_QUESTION_BASE.content,
|
||||||
multi: false,
|
video: "",
|
||||||
own: false,
|
|
||||||
innerNameCheck: false,
|
|
||||||
innerName: "",
|
innerName: "",
|
||||||
required: false,
|
text: "",
|
||||||
variants: [
|
price: [0],
|
||||||
{
|
rangePrice: false
|
||||||
id: nanoid(),
|
|
||||||
answer: "",
|
|
||||||
extendedText: "",
|
|
||||||
hints: "",
|
|
||||||
originalImageUrl: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionVariant,
|
QuestionBranchingRule,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
PreviewRule,
|
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionResult extends QuizQuestionBase {
|
export interface QuizQuestionResult extends QuizQuestionBase {
|
||||||
type: "result";
|
type: "result";
|
||||||
content: {
|
content: {
|
||||||
id: string;
|
id: string;
|
||||||
/** Чекбокс "Можно несколько" */
|
|
||||||
multi: boolean;
|
|
||||||
/** Чекбокс "Вариант "свой ответ"" */
|
|
||||||
own: boolean;
|
|
||||||
/** Чекбокс "Внутреннее название вопроса" */
|
|
||||||
innerNameCheck: boolean;
|
|
||||||
/** Поле "Внутреннее название вопроса" */
|
|
||||||
innerName: string;
|
|
||||||
/** Чекбокс "Необязательный вопрос" */
|
|
||||||
required: boolean;
|
|
||||||
variants: QuestionVariant[];
|
|
||||||
hint: QuestionHint;
|
|
||||||
rule: PreviewRule;
|
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
|
video: string;
|
||||||
|
innerName: string;
|
||||||
|
text: string;
|
||||||
|
price: [number] | [number, number];
|
||||||
|
rangePrice: boolean;
|
||||||
|
rule: QuestionBranchingRule,
|
||||||
|
hint: QuestionHint;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import type { QuizQuestionSelect } from "./select";
|
|||||||
import type { QuizQuestionText } from "./text";
|
import type { QuizQuestionText } from "./text";
|
||||||
import type { QuizQuestionVariant } from "./variant";
|
import type { QuizQuestionVariant } from "./variant";
|
||||||
import type { QuizQuestionVarImg } from "./varimg";
|
import type { QuizQuestionVarImg } from "./varimg";
|
||||||
|
import type { QuizQuestionResult } from "./result";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export interface QuestionBranchingRuleMain {
|
export interface QuestionBranchingRuleMain {
|
||||||
@ -92,7 +93,8 @@ export type AnyTypedQuizQuestion =
|
|||||||
| QuizQuestionNumber
|
| QuizQuestionNumber
|
||||||
| QuizQuestionFile
|
| QuizQuestionFile
|
||||||
| QuizQuestionPage
|
| QuizQuestionPage
|
||||||
| QuizQuestionRating;
|
| QuizQuestionRating
|
||||||
|
| QuizQuestionResult;
|
||||||
|
|
||||||
type FilterQuestionsWithVariants<T> = T extends {
|
type FilterQuestionsWithVariants<T> = T extends {
|
||||||
content: { variants: QuestionVariant[]; };
|
content: { variants: QuestionVariant[]; };
|
||||||
|
@ -33,12 +33,19 @@ export interface QuizConfig {
|
|||||||
startpageType: QuizStartpageType;
|
startpageType: QuizStartpageType;
|
||||||
results: QuizResultsType;
|
results: QuizResultsType;
|
||||||
haveRoot: string | null;
|
haveRoot: string | null;
|
||||||
|
resultInfo: {
|
||||||
|
when: 'before' | 'after' | 'email',
|
||||||
|
share: true | false,
|
||||||
|
replay: true | false,
|
||||||
|
theme: string,
|
||||||
|
reply: string,
|
||||||
|
replname: string,
|
||||||
|
}
|
||||||
startpage: {
|
startpage: {
|
||||||
description: string;
|
description: string;
|
||||||
button: string;
|
button: string;
|
||||||
position: QuizStartpageAlignType;
|
position: QuizStartpageAlignType;
|
||||||
favIcon: string | null;
|
favIcon: string | null;
|
||||||
originalFavIcon: string | null;
|
|
||||||
logo: string | null;
|
logo: string | null;
|
||||||
originalLogo: string | null;
|
originalLogo: string | null;
|
||||||
background: {
|
background: {
|
||||||
@ -67,12 +74,19 @@ export const defaultQuizConfig: QuizConfig = {
|
|||||||
startpageType: null,
|
startpageType: null,
|
||||||
results: null,
|
results: null,
|
||||||
haveRoot: null,
|
haveRoot: null,
|
||||||
|
resultInfo: {
|
||||||
|
when: 'after',
|
||||||
|
share: false,
|
||||||
|
replay: false,
|
||||||
|
theme: "",
|
||||||
|
reply: "",
|
||||||
|
replname: "",
|
||||||
|
},
|
||||||
startpage: {
|
startpage: {
|
||||||
description: "",
|
description: "",
|
||||||
button: "",
|
button: "",
|
||||||
position: "left",
|
position: "left",
|
||||||
favIcon: null,
|
favIcon: null,
|
||||||
originalFavIcon: null,
|
|
||||||
logo: null,
|
logo: null,
|
||||||
originalLogo: null,
|
originalLogo: null,
|
||||||
background: {
|
background: {
|
||||||
|
@ -122,7 +122,9 @@ function CsComponent ({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore()
|
const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
|
||||||
|
const trashQuestions = useQuestionsStore().questions
|
||||||
|
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||||
const [startCreate, setStartCreate] = useState("");
|
const [startCreate, setStartCreate] = useState("");
|
||||||
const [startRemove, setStartRemove] = useState("");
|
const [startRemove, setStartRemove] = useState("");
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
|
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const { dragQuestionContentId, questions } = useQuestionsStore()
|
const { dragQuestionContentId } = useQuestionsStore()
|
||||||
const Container = useRef<HTMLDivElement | null>(null);
|
const Container = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const modalOpen = () => setOpenedModalQuestions(true)
|
const modalOpen = () => setOpenedModalQuestions(true)
|
||||||
|
@ -15,7 +15,8 @@ export const BranchingQuestionsModal = ({
|
|||||||
setModalQuestionTargetContentId,
|
setModalQuestionTargetContentId,
|
||||||
setModalQuestionParentContentId
|
setModalQuestionParentContentId
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { questions } = useQuestionsStore();
|
const trashQuestions = useQuestionsStore().questions
|
||||||
|
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setOpenedModalQuestions(false);
|
setOpenedModalQuestions(false);
|
||||||
|
@ -39,7 +39,7 @@ import SwitchQuestionsPage from "../SwitchQuestionsPage";
|
|||||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||||
import TypeQuestions from "../TypeQuestions";
|
import TypeQuestions from "../TypeQuestions";
|
||||||
import { QuestionType } from "@model/question/question";
|
import { QuestionType } from "@model/question/question";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
||||||
@ -99,7 +99,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
<TextField
|
<TextField
|
||||||
defaultValue={question.title}
|
defaultValue={question.title}
|
||||||
placeholder={"Заголовок вопроса"}
|
placeholder={"Заголовок вопроса"}
|
||||||
onChange={({ target }: { target: HTMLInputElement }) => setTitle(target.value)}
|
onChange={({ target }: { target: HTMLInputElement; }) => setTitle(target.value)}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<Box>
|
<Box>
|
||||||
@ -239,7 +239,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
margin: "0 5px 0 10px",
|
margin: "0 5px 0 10px",
|
||||||
}}
|
}}
|
||||||
onClick={() => { // TODO
|
onClick={() => { // TODO
|
||||||
const removedId = question.id;
|
const removedId = question.id;
|
||||||
// if (question.deleteTimeoutId) {
|
// if (question.deleteTimeoutId) {
|
||||||
// clearTimeout(question.deleteTimeoutId);
|
// clearTimeout(question.deleteTimeoutId);
|
||||||
// }
|
// }
|
||||||
@ -265,26 +265,28 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box
|
{question.type !== null &&
|
||||||
style={{
|
<Box
|
||||||
display: "flex",
|
style={{
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
justifyContent: "center",
|
alignItems: "center",
|
||||||
height: "30px",
|
justifyContent: "center",
|
||||||
width: "30px",
|
height: "30px",
|
||||||
marginLeft: "3px",
|
width: "30px",
|
||||||
borderRadius: "50%",
|
marginLeft: "3px",
|
||||||
fontSize: "16px",
|
borderRadius: "50%",
|
||||||
color: question.expanded
|
fontSize: "16px",
|
||||||
? theme.palette.brightPurple.main
|
color: question.expanded
|
||||||
: "#FFF",
|
? theme.palette.brightPurple.main
|
||||||
background: question.expanded
|
: "#FFF",
|
||||||
? "#EEE4FC"
|
background: question.expanded
|
||||||
: theme.palette.brightPurple.main,
|
? "#EEE4FC"
|
||||||
}}
|
: theme.palette.brightPurple.main,
|
||||||
>
|
}}
|
||||||
{index + 1}
|
>
|
||||||
</Box>
|
{question.page + 1}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
<IconButton
|
<IconButton
|
||||||
disableRipple
|
disableRipple
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { reorderQuestions } from "@root/questions/actions";
|
import { reorderQuestions } from "@root/questions/actions";
|
||||||
import { useQuestions } from "@root/questions/hooks";
|
|
||||||
import type { DropResult } from "react-beautiful-dnd";
|
import type { DropResult } from "react-beautiful-dnd";
|
||||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||||
import DraggableListItem from "./DraggableListItem";
|
import DraggableListItem from "./DraggableListItem";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
|
||||||
|
|
||||||
export const DraggableList = () => {
|
export const DraggableList = () => {
|
||||||
const { questions, isLoading } = useQuestions();
|
const { questions } = useQuestionsStore()
|
||||||
|
const filteredQuestions = questions.filter((question) => question.type !== "result")
|
||||||
|
console.log(questions)
|
||||||
|
console.log(filteredQuestions)
|
||||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||||
if (destination) reorderQuestions(source.index, destination.index);
|
if (destination) reorderQuestions(source.index, destination.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<Droppable droppableId="droppable-list">
|
<Droppable droppableId="droppable-list">
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{questions.map((question, index) => (
|
{filteredQuestions.map((question, index) => (
|
||||||
<DraggableListItem
|
<DraggableListItem
|
||||||
key={question.id}
|
key={question.id}
|
||||||
question={question}
|
question={question}
|
||||||
|
@ -48,7 +48,8 @@ export default function QuestionsPage() {
|
|||||||
margin: "60px 0 40px 0",
|
margin: "60px 0 40px 0",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant={"h5"}>Заголовок квиза</Typography>
|
<Typography variant={"h5"}>{
|
||||||
|
quiz.name ? quiz.name : "Заголовок квиза" }</Typography>
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
display: openBranchingPanel ? "none" : "flex",
|
display: openBranchingPanel ? "none" : "flex",
|
||||||
|
@ -24,7 +24,9 @@ const getItemStyle = (isDragging: any, draggableStyle: any) => ({
|
|||||||
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
|
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
|
||||||
|
|
||||||
export const QuestionsList = () => {
|
export const QuestionsList = () => {
|
||||||
const { questions, desireToOpenABranchingModal } = useQuestionsStore()
|
const { desireToOpenABranchingModal } = useQuestionsStore()
|
||||||
|
const trashQuestions = useQuestionsStore().questions
|
||||||
|
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ padding: "15px" }}>
|
<Box sx={{ padding: "15px" }}>
|
||||||
@ -85,7 +87,7 @@ export const QuestionsList = () => {
|
|||||||
updateEditSomeQuestion(content.id)
|
updateEditSomeQuestion(content.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pencil />
|
<Pencil style={{color: "#7e2aea"}}/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{content.rule.parentId && <CheckedIcon />}
|
{content.rule.parentId && <CheckedIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -39,24 +39,6 @@ const priceButtonsArray: { title: string; type: string; sx: SxProps<Theme> }[] =
|
|||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "ƒ",
|
|
||||||
type: "ƒ",
|
|
||||||
sx: {
|
|
||||||
width: "38px",
|
|
||||||
height: "48px",
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Скидка",
|
|
||||||
type: "discount",
|
|
||||||
sx: {
|
|
||||||
width: "93px",
|
|
||||||
height: "48px",
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -74,9 +56,6 @@ export default function PriceButtons({
|
|||||||
<Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}>
|
<Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}>
|
||||||
Стоимость
|
Стоимость
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
|
||||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
component="div"
|
component="div"
|
||||||
|
@ -1,76 +1,112 @@
|
|||||||
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
|
||||||
|
|
||||||
type Props = {
|
import { ResultSettings } from "./ResultSettings"
|
||||||
text: string;
|
import { createFrontResult } from "@root/questions/actions";
|
||||||
text2: string;
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
image: string;
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
};
|
import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material";
|
||||||
|
import image from "../../assets/Rectangle 110.png";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
export default function CreationFullCard({ text, text2, image }: Props) {
|
export const FirstEntry = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
|
const quiz = useCurrentQuiz();
|
||||||
|
const { questions } = useQuestionsStore();
|
||||||
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250));
|
||||||
|
|
||||||
|
const create = () => {
|
||||||
|
if (quiz?.config.haveRoot) {
|
||||||
|
if (questions.length === 0) {
|
||||||
|
enqueueSnackbar("У вас не добавлено ни одного вопроса")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
questions
|
||||||
|
.filter((question) => question.content.rule.parentId.length !== 0 && question.content.rule.default.length === 0)
|
||||||
|
.forEach(question => {
|
||||||
|
createFrontResult(quiz.id, question.content.id)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
createFrontResult(quiz.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<>
|
||||||
sx={{
|
<Box
|
||||||
flexGrow: 1,
|
sx={{
|
||||||
backgroundColor: "white",
|
flexGrow: 1,
|
||||||
p: "20px",
|
backgroundColor: "white",
|
||||||
marginTop: "50px",
|
p: "20px",
|
||||||
borderRadius: "12px",
|
marginTop: "50px",
|
||||||
display: isSmallMonitor ? "block" : "flex",
|
borderRadius: "12px",
|
||||||
flexDirection: isSmallMonitor ? "column" : "row",
|
display: isSmallMonitor ? "block" : "flex",
|
||||||
gap: "20px",
|
flexDirection: isSmallMonitor ? "column" : "row",
|
||||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
gap: "20px",
|
||||||
|
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||||
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
|
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
|
||||||
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
|
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
|
||||||
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
|
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
|
||||||
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
|
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
|
||||||
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
|
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
mr: !isSmallMonitor ? "104px" : 0,
|
|
||||||
marginBottom: "20px",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
|
|
||||||
Результаты квиза в зависимости от ответов
|
|
||||||
</Typography>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
mr: !isSmallMonitor ? "104px" : 0,
|
||||||
flexDirection: "column",
|
marginBottom: isSmallMonitor ? "20px" : 0,
|
||||||
justifyContent: "space-between",
|
position: "relative",
|
||||||
height: "100%",
|
height: "100%"
|
||||||
maxHeight: isSmallMonitor ? "none" : "220px",
|
|
||||||
gap: "25px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
|
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
|
||||||
{text}
|
Результаты квиза в зависимости от ответов
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
color: "#9A9AAF",
|
display: "flex",
|
||||||
width: "100%",
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
height: "100%",
|
||||||
|
gap: "25px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{text2}
|
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
|
||||||
</Typography>
|
Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке.
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#9A9AAF",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Этот шаг - необязательный, квиз будет работать и без автоматических результатов.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
alt="quiz creation"
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: isSmallMonitor ? "100%" : "auto",
|
||||||
|
maxHeight: isSmallMonitor ? "none" : "270px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<img
|
<Button
|
||||||
src={image}
|
onClick={create}
|
||||||
alt="quiz creation"
|
variant="contained"
|
||||||
style={{
|
sx={{
|
||||||
display: "block",
|
backgroundColor: "#7E2AEA",
|
||||||
width: isSmallMonitor ? "100%" : "auto",
|
fontSize: "18px",
|
||||||
maxHeight: isSmallMonitor ? "none" : "270px",
|
lineHeight: "18px",
|
||||||
|
width: "216px",
|
||||||
|
height: "44px",
|
||||||
|
mt: "30px",
|
||||||
|
p: "10px 20px"
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</Box>
|
Создать результаты
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions";
|
|||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import image from "../../assets/Rectangle 110.png";
|
import image from "../../assets/Rectangle 110.png";
|
||||||
import Info from "../../assets/icons/Info";
|
import Info from "../../assets/icons/Info";
|
||||||
import CreationFullCard from "./FirstEntry";
|
// import CreationFullCard from "./FirstEntry";
|
||||||
|
|
||||||
|
|
||||||
export const Result = () => {
|
export const Result = () => {
|
||||||
@ -13,11 +13,11 @@ export const Result = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="section">
|
<Box component="section">
|
||||||
<CreationFullCard
|
{/* <CreationFullCard
|
||||||
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
|
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
|
||||||
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
|
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
|
||||||
image={image}
|
image={image}
|
||||||
/>
|
/> */}
|
||||||
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
|
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import { useQuestionsStore } from "@root/questions/store";
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
import FirstEntry from "./FirstEntry"
|
import { FirstEntry } from "./FirstEntry"
|
||||||
|
import { ResultSettings } from "./ResultSettings"
|
||||||
|
|
||||||
|
export const ResultPage = () => {
|
||||||
export default function ResultPage() {
|
|
||||||
const { questions } = useQuestionsStore();
|
const { questions } = useQuestionsStore();
|
||||||
//ищём хотя бы один result
|
//ищём хотя бы один result
|
||||||
const haveResult = questions.some((question) => {
|
const haveResult = questions.some((question) => question.type === "result")
|
||||||
question.type === "result"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
haveResult ?
|
||||||
|
<ResultSettings />
|
||||||
</>
|
:
|
||||||
|
<FirstEntry />
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -2,19 +2,31 @@ import IconPlus from "@icons/IconPlus";
|
|||||||
import Info from "@icons/Info";
|
import Info from "@icons/Info";
|
||||||
import Plus from "@icons/Plus";
|
import Plus from "@icons/Plus";
|
||||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||||
import { Box, Button, Typography } from "@mui/material";
|
import { Box, Button, Typography, Paper, FormControl, TextField } from "@mui/material";
|
||||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||||
import CustomWrapper from "@ui_kit/CustomWrapper";
|
import CustomWrapper from "@ui_kit/CustomWrapper";
|
||||||
import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
|
import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
|
||||||
import { ResultListForm } from "./ResultListForm";
|
import { ResultListForm } from "./ResultListForm";
|
||||||
import { SettingForm } from "./SettingForm";
|
import { SettingForm } from "./SettingForm";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { WhenCard } from "./cards/WhenCard";
|
||||||
|
import { ResultCard } from "./cards/ResultCard";
|
||||||
|
import { EmailSettingsCard } from "./cards/EmailSettingsCard";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
|
|
||||||
export const Setting = () => {
|
export const ResultSettings = () => {
|
||||||
|
const quiz = useCurrentQuiz()
|
||||||
|
const [quizExpand, setQuizExpand] = useState(true)
|
||||||
|
const [resultContract, setResultContract] = useState(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ maxWidth: "796px" }}>
|
<Box sx={{ maxWidth: "796px" }}>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", mb: "40px" }}>
|
<Box sx={{
|
||||||
<Typography sx={{ pr: "10px" }} variant="h5">
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
margin: "60px 0 40px 0",
|
||||||
|
}}>
|
||||||
|
<Typography variant="h5">
|
||||||
Настройки результатов
|
Настройки результатов
|
||||||
</Typography>
|
</Typography>
|
||||||
<Info />
|
<Info />
|
||||||
@ -33,12 +45,17 @@ export const Setting = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
variant="text"
|
variant="text"
|
||||||
|
onClick={() => setQuizExpand(!quizExpand)}
|
||||||
>
|
>
|
||||||
Свернуть
|
Свернуть
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<CustomWrapper sx={{ mt: "30px" }} text="Показывать результат" />
|
|
||||||
<CustomWrapper sx={{ mt: "30px" }} text="Настройки почты" />
|
|
||||||
|
<WhenCard quizExpand={quizExpand} />
|
||||||
|
{quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />}
|
||||||
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
|
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
|
||||||
>
|
>
|
||||||
@ -60,52 +77,14 @@ export const Setting = () => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
variant="text"
|
variant="text"
|
||||||
|
onClick={() => setResultContract(!resultContract)}
|
||||||
>
|
>
|
||||||
Развернуть все
|
Развернуть все
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<CustomWrapper result={true} text="Показывать результат" />
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center",
|
|
||||||
columnGap: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
boxSizing: "border-box",
|
|
||||||
width: "100%",
|
|
||||||
height: "1px",
|
|
||||||
backgroundPosition: "bottom",
|
|
||||||
backgroundRepeat: "repeat-x",
|
|
||||||
backgroundSize: "20px 1px",
|
|
||||||
backgroundImage:
|
|
||||||
"radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<IconPlus />
|
|
||||||
</Box>
|
|
||||||
<CustomWrapper result={true} text="Настройки почты" />
|
|
||||||
<Box sx={{ pt: "30px", display: "flex", alignItems: "center" }}>
|
|
||||||
<Plus />
|
|
||||||
<Typography component="div" sx={{ ml: "auto" }}>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
sx={{ padding: "10px 20px", borderRadius: "8px" }}
|
|
||||||
>
|
|
||||||
<ArrowLeft />
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" sx={{ ml: "10px" }} onClick={incrementCurrentStep}>
|
|
||||||
Настроить форму
|
|
||||||
</Button>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<SettingForm />
|
|
||||||
<ResultListForm />
|
<ResultCard resultContract={resultContract} />
|
||||||
<DescriptionForm />
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -5,10 +5,11 @@ import * as React from "react";
|
|||||||
interface Props {
|
interface Props {
|
||||||
text: string;
|
text: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
onClick?: () => void;
|
onClick?: (a:any) => void;
|
||||||
|
value: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SwitchSetting = ({ text, icon, onClick }: Props) => {
|
export const SwitchSetting = ({ text, icon, onClick, value }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -24,7 +25,7 @@ export const SwitchSetting = ({ text, icon, onClick }: Props) => {
|
|||||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", maxWidth: "756px", width: "100%" }}>
|
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", maxWidth: "756px", width: "100%" }}>
|
||||||
<img src={icon} alt="icon" />
|
<img src={icon} alt="icon" />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="start"
|
checked={value}
|
||||||
control={<CustomizedSwitch />}
|
control={<CustomizedSwitch />}
|
||||||
label={text}
|
label={text}
|
||||||
labelPlacement="start"
|
labelPlacement="start"
|
||||||
|
251
src/pages/ResultPage/cards/EmailSettingsCard.tsx
Normal file
251
src/pages/ResultPage/cards/EmailSettingsCard.tsx
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||||
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||||
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
quizExpand: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailSettingsCard = ({ quizExpand }: Props) => {
|
||||||
|
const quiz = useCurrentQuiz()
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||||
|
|
||||||
|
const [expand, setExpand] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpand(false)
|
||||||
|
}, [quizExpand])
|
||||||
|
|
||||||
|
const debouncedCallback = useDebouncedCallback((callback) => {
|
||||||
|
callback();
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
data-cy="quiz-question-card"
|
||||||
|
sx={{
|
||||||
|
maxWidth: "796px",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "12px",
|
||||||
|
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||||
|
border: expand ? "none" : "1px solid #9A9AAF",
|
||||||
|
boxShadow: "0px 10px 30px #e7e7e7",
|
||||||
|
m: "20px 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: expand ? isMobile ? "10px 10px 0 10px" : "20px 20px 0 20px" : isMobile ? "10px" : "20px",
|
||||||
|
flexDirection: isMobile ? "column" : null,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
minHeight: "40px",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
color: expand ? "#9A9AAF" : "#000000",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Настройки почты
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ padding: "0", margin: "5px" }}
|
||||||
|
disableRipple
|
||||||
|
data-cy="expand-question"
|
||||||
|
onClick={() => setExpand(!expand)}
|
||||||
|
>
|
||||||
|
{expand ? (
|
||||||
|
<ExpandLessIconBG />
|
||||||
|
) : (
|
||||||
|
<ExpandLessIcon
|
||||||
|
sx={{
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fill: theme.palette.brightPurple.main,
|
||||||
|
background: "#FFF",
|
||||||
|
borderRadius: "6px",
|
||||||
|
height: "30px",
|
||||||
|
width: "30px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{expand && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: "0 20px 20px 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#4D4D4D",
|
||||||
|
m: "20px 0 14px 0",
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 500
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Тема письма
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
value={quiz.config.resultInfo.theme}
|
||||||
|
placeholder={"Заголовок вопроса"}
|
||||||
|
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.config.resultInfo.theme = target.value
|
||||||
|
})) }}
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
width:"100%",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
color: "#000000",
|
||||||
|
backgroundColor: expand
|
||||||
|
? theme.palette.background.default
|
||||||
|
: "transparent",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderWidth: "1px !important",
|
||||||
|
border: expand ? "none" : null,
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-input::placeholder": {
|
||||||
|
color: "#4D4D4D",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
paddingLeft: "18px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#4D4D4D",
|
||||||
|
m: "20px 0 14px 0",
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 500
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
E-mail ответа
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
value={quiz.config.resultInfo.reply}
|
||||||
|
placeholder={"noreplay@example.ru"}
|
||||||
|
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.config.resultInfo.reply = target.value
|
||||||
|
})) }}
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
width:"100%",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
color: "#000000",
|
||||||
|
backgroundColor: expand
|
||||||
|
? theme.palette.background.default
|
||||||
|
: "transparent",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderWidth: "1px !important",
|
||||||
|
border: expand ? "none" : null,
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-input::placeholder": {
|
||||||
|
color: "#4D4D4D",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
paddingLeft: "18px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#4D4D4D",
|
||||||
|
m: "20px 0 14px 0",
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 500
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Имя отправителя
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
value={quiz.config.resultInfo.replname}
|
||||||
|
placeholder={"Название компании"}
|
||||||
|
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.config.resultInfo.replname = target.value
|
||||||
|
})) }}
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
width:"100%",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
color: "#000000",
|
||||||
|
backgroundColor: expand
|
||||||
|
? theme.palette.background.default
|
||||||
|
: "transparent",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
".MuiOutlinedInput-notchedOutline": {
|
||||||
|
borderWidth: "1px !important",
|
||||||
|
border: expand ? "none" : null,
|
||||||
|
},
|
||||||
|
"& .MuiInputBase-input::placeholder": {
|
||||||
|
color: "#4D4D4D",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
paddingLeft: "18px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
291
src/pages/ResultPage/cards/ResultCard.tsx
Normal file
291
src/pages/ResultPage/cards/ResultCard.tsx
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { updateQuiz } from "@root/quizes/actions"
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
|
|
||||||
|
import { SwitchSetting } from "../SwichResult";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||||
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||||
|
import ShareNetwork from "@icons/ShareNetwork.svg";
|
||||||
|
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
import SwitchResult from "../DescriptionForm/SwitchResult";
|
||||||
|
import ButtonsOptionsForm from "../DescriptionForm/ButtinsOptionsForm";
|
||||||
|
import PriceButtons from "../DescriptionForm/PriceButton";
|
||||||
|
import DiscountButtons from "../DescriptionForm/DiscountButtons";
|
||||||
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { OneIcon } from "@icons/questionsPage/OneIcon";
|
||||||
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
|
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||||
|
import Info from "@icons/Info";
|
||||||
|
import ImageAndVideoButtons from "../DescriptionForm/ImageAndVideoButtons";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
resultContract: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ResultCard = ({ resultContract }:Props) => {
|
||||||
|
const quiz = useCurrentQuiz()
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||||
|
|
||||||
|
const [expand, setExpand] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpand(true)
|
||||||
|
}, [resultContract])
|
||||||
|
|
||||||
|
|
||||||
|
const [switchState, setSwitchState] = useState<string>("");
|
||||||
|
const [priceButtonsActive, setPriceButtonsActive] = useState<number>(0);
|
||||||
|
const [priceButtonsType, setPriceButtonsType] = useState<string>();
|
||||||
|
const [forwarding, setForwarding] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const buttonsActive = (index: number, type: string) => {
|
||||||
|
setPriceButtonsActive(index);
|
||||||
|
setPriceButtonsType(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SSHC = (data: string) => {
|
||||||
|
setSwitchState(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Paper
|
||||||
|
data-cy="quiz-question-card"
|
||||||
|
sx={{
|
||||||
|
maxWidth: "796px",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "12px",
|
||||||
|
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||||
|
border: expand ? "none" : "1px solid #9A9AAF",
|
||||||
|
boxShadow: "0px 10px 30px #e7e7e7",
|
||||||
|
m: "20px 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: isMobile ? "10px" : "20px",
|
||||||
|
flexDirection: isMobile ? "column" : null,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
minHeight: "40px",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
color: expand ? "#9A9AAF" : "#000000",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Заголовок результата
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ padding: "0", margin: "5px" }}
|
||||||
|
disableRipple
|
||||||
|
data-cy="expand-question"
|
||||||
|
onClick={() => setExpand(!expand)}
|
||||||
|
>
|
||||||
|
{expand ? (
|
||||||
|
<ExpandLessIconBG />
|
||||||
|
) : (
|
||||||
|
<ExpandLessIcon
|
||||||
|
sx={{
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fill: theme.palette.brightPurple.main,
|
||||||
|
background: "#FFF",
|
||||||
|
borderRadius: "6px",
|
||||||
|
height: "30px",
|
||||||
|
width: "30px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{expand && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
overflow: "hidden",
|
||||||
|
maxWidth: "796px",
|
||||||
|
height: "100%",
|
||||||
|
bgcolor: "#FFFFFF",
|
||||||
|
borderRadius: "12px",
|
||||||
|
boxShadow: "0px 10px 30px #e7e7e7",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ p: "0 20px", pt: "30px" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "760px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "10px",
|
||||||
|
mb: "19px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomTextField placeholder="Заголовок вопроса" text={""} />
|
||||||
|
<IconButton>
|
||||||
|
<ExpandMoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
<OneIcon />
|
||||||
|
<PointsIcon style={{ color: "#9A9AAF" }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<PriceButtons
|
||||||
|
ButtonsActive={buttonsActive}
|
||||||
|
priceButtonsActive={priceButtonsActive}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
placeholder="Описание"
|
||||||
|
sx={{
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
width: "100%",
|
||||||
|
height: "110px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: "10px",
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ImageAndVideoButtons />
|
||||||
|
{priceButtonsType === "smooth" ? (
|
||||||
|
<Box sx={{ mb: "20px" }}>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", mb: "14xp" }}>
|
||||||
|
<Typography
|
||||||
|
component={"h6"}
|
||||||
|
sx={{ weight: "500", fontSize: "18px" }}
|
||||||
|
>
|
||||||
|
Призыв к действию
|
||||||
|
</Typography>
|
||||||
|
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||||
|
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<TextField
|
||||||
|
placeholder="Узнать подробнее"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
width: "410px",
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
width: "410px",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: "10px",
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => setForwarding(true)}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
display: forwarding ? "none" : "",
|
||||||
|
ml: "20px",
|
||||||
|
mb: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Переадресация +
|
||||||
|
</Button>
|
||||||
|
{forwarding ? (
|
||||||
|
<Box sx={{ ml: "20px", mt: "-36px" }}>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", alignItems: "center", mb: "14xp" }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component={"h6"}
|
||||||
|
sx={{ weight: "500", fontSize: "18px" }}
|
||||||
|
>
|
||||||
|
Переадресация
|
||||||
|
</Typography>
|
||||||
|
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||||
|
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Info />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
placeholder="https://exemple.ru"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
width: "326px",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: "10px",
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
py: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Button variant="outlined" sx={{ mb: "20px" }}>
|
||||||
|
Кнопка +
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<ButtonsOptionsForm switchState={switchState} SSHC={SSHC} />
|
||||||
|
<SwitchResult switchState={switchState} totalIndex={0} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
187
src/pages/ResultPage/cards/WhenCard.tsx
Normal file
187
src/pages/ResultPage/cards/WhenCard.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { updateQuiz } from "@root/quizes/actions"
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
|
|
||||||
|
import { SwitchSetting } from "../SwichResult";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||||
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||||
|
import ShareNetwork from "@icons/ShareNetwork.svg";
|
||||||
|
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
|
||||||
|
|
||||||
|
const whenValues = [
|
||||||
|
{
|
||||||
|
title: "До формы контактов",
|
||||||
|
value: "before",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "После формы контактов",
|
||||||
|
value: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Отправить на E-mail",
|
||||||
|
value: "email",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
quizExpand: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WhenCard = ({ quizExpand }: Props) => {
|
||||||
|
const quiz = useCurrentQuiz()
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||||
|
|
||||||
|
const [expand, setExpand] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setExpand(false)
|
||||||
|
}, [quizExpand])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
data-cy="quiz-question-card"
|
||||||
|
sx={{
|
||||||
|
maxWidth: "796px",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "12px",
|
||||||
|
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||||
|
border: expand ? "none" : "1px solid #9A9AAF",
|
||||||
|
boxShadow: "0px 10px 30px #e7e7e7",
|
||||||
|
m: "20px 0"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: isMobile ? "10px" : "20px",
|
||||||
|
flexDirection: isMobile ? "column" : null,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
minHeight: "40px",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
margin: isMobile ? "10px 0" : 0,
|
||||||
|
color: expand ? "#9A9AAF" : "#000000",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Показывать результат
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ padding: "0", margin: "5px" }}
|
||||||
|
disableRipple
|
||||||
|
data-cy="expand-question"
|
||||||
|
onClick={() => setExpand(!expand)}
|
||||||
|
>
|
||||||
|
{expand ? (
|
||||||
|
<ExpandLessIconBG />
|
||||||
|
) : (
|
||||||
|
<ExpandLessIcon
|
||||||
|
sx={{
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fill: theme.palette.brightPurple.main,
|
||||||
|
background: "#FFF",
|
||||||
|
borderRadius: "6px",
|
||||||
|
height: "30px",
|
||||||
|
width: "30px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{expand && (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: "33px 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: isSmallMonitor ? "column" : "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "20px",
|
||||||
|
mb: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{whenValues.map(({ title, value }, index) => (
|
||||||
|
<Button
|
||||||
|
onClick={() => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.when = value)}
|
||||||
|
key={title}
|
||||||
|
sx={{
|
||||||
|
bgcolor: quiz?.config.resultInfo.when === value ? " #7E2AEA" : "#F2F3F7",
|
||||||
|
color: quiz?.config.resultInfo.when === value ? " white" : "#9A9AAF",
|
||||||
|
minWidth: isSmallMonitor ? "300px" : "auto",
|
||||||
|
borderRadius: "8px",
|
||||||
|
width: "237px",
|
||||||
|
height: "44px",
|
||||||
|
border: quiz?.config.resultInfo.when === value ? "none" : "1px solid #9A9AAF",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: quiz?.config.resultInfo.when === value ? "#581CA7" : "#7E2AEA",
|
||||||
|
color: "white"
|
||||||
|
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
(quiz?.config.resultInfo.when !== "email") && <SwitchSetting
|
||||||
|
icon={ShareNetwork}
|
||||||
|
text="Поделиться результатами"
|
||||||
|
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.share = event.target.checked)}
|
||||||
|
value={quiz?.config.resultInfo.share}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
quiz?.config.resultInfo.when === "before" && <SwitchSetting
|
||||||
|
icon={ArrowCounterClockWise}
|
||||||
|
text="Кнопка `Пройти тест заново`"
|
||||||
|
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.replay = event.target.checked)}
|
||||||
|
value={quiz?.config.resultInfo.replay}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { useLayoutEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
import { StartPageViewPublication } from "./StartPageViewPublication";
|
import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||||
@ -13,6 +13,13 @@ export const ViewPage = () => {
|
|||||||
const { questions } = useQuestions();
|
const { questions } = useQuestions();
|
||||||
const [visualStartPage, setVisualStartPage] = useState<boolean>(!quiz?.config.noStartPage);
|
const [visualStartPage, setVisualStartPage] = useState<boolean>(!quiz?.config.noStartPage);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const link = document.querySelector('link[rel="icon"]');
|
||||||
|
|
||||||
|
if (link && quiz?.config.startpage.favIcon) {
|
||||||
|
link.setAttribute("href", quiz.config.startpage.favIcon);
|
||||||
|
}
|
||||||
|
}, [quiz?.config.startpage.favIcon]);
|
||||||
|
|
||||||
const filteredQuestions = questions.filter(
|
const filteredQuestions = questions.filter(
|
||||||
({ type }) => type
|
({ type }) => type
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
RadioGroup,
|
RadioGroup,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio,
|
Radio,
|
||||||
useTheme,
|
useTheme, FormControl,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||||
@ -50,31 +50,49 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
<Box sx={{ display: "flex", width: "100%", gap: "42px" }}>
|
||||||
{currentQuestion.content.variants.map(
|
{currentQuestion.content.variants.map(
|
||||||
({ id, answer, extendedText }, index) => (
|
({ id, answer, extendedText }, index) => (
|
||||||
<FormControlLabel
|
<FormControl
|
||||||
key={id}
|
key={id}
|
||||||
sx={{
|
sx={{
|
||||||
marginBottom: "15px",
|
borderRadius: "12px",
|
||||||
borderRadius: "5px",
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
padding: "15px",
|
overflow: "hidden",
|
||||||
color: theme.palette.grey2.main,
|
maxWidth: "317px",
|
||||||
border: `1px solid ${theme.palette.grey2.main}`,
|
width: "100%",
|
||||||
display: "flex",
|
height: "255px"
|
||||||
gap: "10px",
|
}}
|
||||||
}}
|
>
|
||||||
value={index}
|
<Box
|
||||||
control={
|
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
|
||||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
>
|
||||||
}
|
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||||
label={
|
{extendedText && (
|
||||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
<Typography fontSize={"100px"}>{extendedText}</Typography>
|
||||||
<Typography>{extendedText}</Typography>
|
)}
|
||||||
<Typography>{answer}</Typography>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
<FormControlLabel
|
||||||
/>
|
key={id}
|
||||||
|
sx={{
|
||||||
|
margin: 0,
|
||||||
|
padding: "15px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||||
|
<Typography>{answer}</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -91,7 +91,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<FormControl
|
<FormControlLabel
|
||||||
key={id}
|
key={id}
|
||||||
sx={{
|
sx={{
|
||||||
display: "block",
|
display: "block",
|
||||||
|
@ -34,7 +34,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||||
<Box sx={{ display: "flex" }}>
|
<Box sx={{ display: "flex", marginTop: "20px", }}>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name={currentQuestion.id}
|
name={currentQuestion.id}
|
||||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
||||||
@ -50,7 +50,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
flexBasis: "100%",
|
flexBasis: "100%",
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
@ -61,7 +60,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
marginBottom: "15px",
|
marginBottom: "15px",
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
padding: "15px",
|
padding: "15px",
|
||||||
color: theme.palette.grey2.main,
|
color: "#4D4D4D",
|
||||||
border: `1px solid ${theme.palette.grey2.main}`,
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
@ -75,7 +74,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
{(variant?.extendedText || currentQuestion.content.back) && (
|
{(variant?.extendedText || currentQuestion.content.back) && (
|
||||||
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
<Box sx={{ maxWidth: "450px", width: "100%", height: "450px", border: "1px solid #E3E3E3", borderRadius: "12px", overflow: "hidden", }}>
|
||||||
<img
|
<img
|
||||||
src={answer ? variant?.extendedText : currentQuestion.content.back}
|
src={answer ? variant?.extendedText : currentQuestion.content.back}
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
@ -32,9 +32,10 @@ import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
|||||||
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
|
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
|
||||||
import {BranchingPanel} from "../Questions/BranchingPanel";
|
import {BranchingPanel} from "../Questions/BranchingPanel";
|
||||||
import {useQuestionsStore} from "@root/questions/store";
|
import {useQuestionsStore} from "@root/questions/store";
|
||||||
|
import { useQuestions } from "@root/questions/hooks";
|
||||||
|
|
||||||
|
|
||||||
export default function StartPage() {
|
export default function EditPage() {
|
||||||
useSWR("quizes", () => quizApi.getList(), {
|
useSWR("quizes", () => quizApi.getList(), {
|
||||||
onSuccess: setQuizes,
|
onSuccess: setQuizes,
|
||||||
onError: error => {
|
onError: error => {
|
||||||
@ -44,6 +45,9 @@ export default function StartPage() {
|
|||||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const editQuizId = useQuizStore(state => state.editQuizId);
|
const editQuizId = useQuizStore(state => state.editQuizId);
|
||||||
@ -54,6 +58,7 @@ export default function StartPage() {
|
|||||||
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
|
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
|
||||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||||
const quizConfig = quiz?.config;
|
const quizConfig = quiz?.config;
|
||||||
|
const { questions, isLoading } = useQuestions();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editQuizId === null) navigate("/list");
|
if (editQuizId === null) navigate("/list");
|
||||||
@ -214,6 +219,7 @@ export default function StartPage() {
|
|||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Выбор текущей страницы редактирования чего-либо находится здесь */}
|
||||||
{quizConfig &&
|
{quizConfig &&
|
||||||
<>
|
<>
|
||||||
<Stepper activeStep={currentStep} />
|
<Stepper activeStep={currentStep} />
|
125
src/pages/startPage/FaviconDropZone.tsx
Normal file
125
src/pages/startPage/FaviconDropZone.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
|
import { Box, ButtonBase, Typography, useTheme } from "@mui/material";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { UploadImageModal } from "../../pages/Questions/UploadImage/UploadImageModal";
|
||||||
|
import { useDisclosure } from "../../utils/useDisclosure";
|
||||||
|
|
||||||
|
|
||||||
|
const allowedFileTypes = ["image/png", "image/jpeg", "image/gif"];
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
imageUrl: string | null;
|
||||||
|
onImageUploadClick: (image: Blob) => void;
|
||||||
|
onDeleteClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FaviconDropZone({ imageUrl, onImageUploadClick, onDeleteClick }: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const [isDropReady, setIsDropReady] = useState<boolean>(false);
|
||||||
|
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||||
|
|
||||||
|
if (!quiz) return null; // TODO throw and catch with error boundary
|
||||||
|
|
||||||
|
async function handleImageUpload(file: File) {
|
||||||
|
if (file.size > 5 * 2 ** 20) return enqueueSnackbar("Размер картинки слишком велик");
|
||||||
|
if (!allowedFileTypes.includes(file.type)) return enqueueSnackbar("Допустимые форматы изображений: png, jpeg, gif");
|
||||||
|
|
||||||
|
onImageUploadClick(file);
|
||||||
|
closeImageUploadModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsDropReady(false);
|
||||||
|
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
if (!file || imageUrl) return;
|
||||||
|
|
||||||
|
handleImageUpload(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}>
|
||||||
|
<UploadImageModal
|
||||||
|
isOpen={isImageUploadOpen}
|
||||||
|
onClose={closeImageUploadModal}
|
||||||
|
handleImageChange={handleImageUpload}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
onDragEnter={() => !imageUrl && setIsDropReady(true)}
|
||||||
|
onDragExit={() => setIsDropReady(false)}
|
||||||
|
onDragOver={e => e.preventDefault()}
|
||||||
|
onDrop={onDrop}
|
||||||
|
sx={{
|
||||||
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
border: `1px solid ${isDropReady ? "red" : theme.palette.grey2.main}`,
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}>
|
||||||
|
<ButtonBase
|
||||||
|
onClick={imageUrl ? undefined : openImageUploadModal}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: "8px",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{imageUrl ?
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "scale-down",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<UploadIcon />
|
||||||
|
}
|
||||||
|
</ButtonBase>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "start",
|
||||||
|
}}>
|
||||||
|
{imageUrl &&
|
||||||
|
<ButtonBase onClick={onDeleteClick}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.orange.main,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
textDecoration: "underline",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Удалить
|
||||||
|
</Typography>
|
||||||
|
</ButtonBase>
|
||||||
|
}
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.orange.main,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
textDecoration: "underline",
|
||||||
|
mt: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
5 MB максимум
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,6 @@ import AlignCenterIcon from "@icons/AlignCenterIcon";
|
|||||||
import AlignLeftIcon from "@icons/AlignLeftIcon";
|
import AlignLeftIcon from "@icons/AlignLeftIcon";
|
||||||
import AlignRightIcon from "@icons/AlignRightIcon";
|
import AlignRightIcon from "@icons/AlignRightIcon";
|
||||||
import ArrowDown from "@icons/ArrowDownIcon";
|
import ArrowDown from "@icons/ArrowDownIcon";
|
||||||
import InfoIcon from "@icons/InfoIcon";
|
|
||||||
import LayoutCenteredIcon from "@icons/LayoutCenteredIcon";
|
import LayoutCenteredIcon from "@icons/LayoutCenteredIcon";
|
||||||
import LayoutExpandedIcon from "@icons/LayoutExpandedIcon";
|
import LayoutExpandedIcon from "@icons/LayoutExpandedIcon";
|
||||||
import LayoutStandartIcon from "@icons/LayoutStandartIcon";
|
import LayoutStandartIcon from "@icons/LayoutStandartIcon";
|
||||||
@ -11,16 +10,14 @@ import { QuizStartpageType } from "@model/quizSettings";
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ButtonBase,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
Tooltip,
|
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { incrementCurrentStep, updateQuiz, uploadQuizImage } from "@root/quizes/actions";
|
import { incrementCurrentStep, updateQuiz, uploadQuizImage } from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
@ -28,15 +25,14 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
|||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import SelectableButton from "@ui_kit/SelectableButton";
|
import SelectableButton from "@ui_kit/SelectableButton";
|
||||||
import { StartPagePreview } from "@ui_kit/StartPagePreview";
|
import { StartPagePreview } from "@ui_kit/StartPagePreview";
|
||||||
import UploadBox from "@ui_kit/UploadBox";
|
import { resizeFavIcon } from "@ui_kit/reactImageFileResizer";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
import FaviconDropZone from "./FaviconDropZone";
|
||||||
import ModalSizeImage from "./ModalSizeImage";
|
import ModalSizeImage from "./ModalSizeImage";
|
||||||
import SelectableIconButton from "./SelectableIconButton";
|
import SelectableIconButton from "./SelectableIconButton";
|
||||||
import { DropZone } from "./dropZone";
|
import { DropZone } from "./dropZone";
|
||||||
import Extra from "./extra";
|
import Extra from "./extra";
|
||||||
import { resizeFavIcon } from "@ui_kit/reactImageFileResizer";
|
|
||||||
|
|
||||||
|
|
||||||
const designTypes = [
|
const designTypes = [
|
||||||
@ -65,40 +61,20 @@ export default function StartPageSettings() {
|
|||||||
const isTablet = useMediaQuery(theme.breakpoints.down(950));
|
const isTablet = useMediaQuery(theme.breakpoints.down(950));
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const [formState, setFormState] = useState<"design" | "content">("design");
|
const [formState, setFormState] = useState<"design" | "content">("design");
|
||||||
|
|
||||||
const designType = quiz?.config?.startpageType;
|
|
||||||
|
|
||||||
const videoHC = (videoInp: HTMLInputElement) => {
|
|
||||||
const file = videoInp.files?.[0];
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
setVideo(URL.createObjectURL(file));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [video, setVideo] = useState("");
|
|
||||||
|
|
||||||
const [mobileVersion, setMobileVersion] = useState(false);
|
const [mobileVersion, setMobileVersion] = useState(false);
|
||||||
|
|
||||||
|
if (!quiz) return null; // TODO throw and catch with error boundary
|
||||||
|
|
||||||
const MobileVersionHC = (bool: boolean) => {
|
const MobileVersionHC = (bool: boolean) => {
|
||||||
setMobileVersion(bool);
|
setMobileVersion(bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!quiz) return null; // TODO throw and catch with error boundary
|
const designType = quiz?.config?.startpageType;
|
||||||
|
|
||||||
const favIconDropZoneElement = (
|
const favIconDropZoneElement = (
|
||||||
<DropZone
|
<FaviconDropZone
|
||||||
sx={{ height: "48px", width: "48px" }}
|
|
||||||
deleteIconSx={{ right: -40, top: -10 }}
|
|
||||||
imageUrl={quiz.config.startpage.favIcon}
|
imageUrl={quiz.config.startpage.favIcon}
|
||||||
originalImageUrl={quiz.config.startpage.originalFavIcon}
|
|
||||||
onImageUploadClick={async file => {
|
onImageUploadClick={async file => {
|
||||||
const resizedImage = await resizeFavIcon(file);
|
|
||||||
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
|
|
||||||
quiz.config.startpage.favIcon = url;
|
|
||||||
quiz.config.startpage.originalFavIcon = url;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onImageSaveClick={async file => {
|
|
||||||
const resizedImage = await resizeFavIcon(file);
|
const resizedImage = await resizeFavIcon(file);
|
||||||
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
|
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
|
||||||
quiz.config.startpage.favIcon = url;
|
quiz.config.startpage.favIcon = url;
|
||||||
@ -429,49 +405,16 @@ export default function StartPageSettings() {
|
|||||||
sx={{
|
sx={{
|
||||||
display: quiz.config.startpage.background.type === "image" ? "none" : "flex",
|
display: quiz.config.startpage.background.type === "image" ? "none" : "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
mt: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<CustomTextField
|
||||||
sx={{
|
placeholder="URL видео"
|
||||||
display: "flex",
|
text={quiz.config.startpage.background.video ?? ""}
|
||||||
alignItems: "center",
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
gap: "7px",
|
quiz.config.startpage.background.video = e.target.value;
|
||||||
mt: "20px",
|
})}
|
||||||
mb: "14px",
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{ fontWeight: 500, color: theme.palette.grey3.main }}
|
|
||||||
>
|
|
||||||
Добавить видео
|
|
||||||
</Typography>
|
|
||||||
<Tooltip title="Можно загрузить видео." placement="top">
|
|
||||||
<Box>
|
|
||||||
<InfoIcon />
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
<ButtonBase
|
|
||||||
component="label"
|
|
||||||
sx={{ justifyContent: "flex-start" }}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
onChange={(event) => videoHC(event.target)}
|
|
||||||
hidden
|
|
||||||
accept=".mp4"
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<UploadBox
|
|
||||||
icon={<UploadIcon />}
|
|
||||||
sx={{
|
|
||||||
height: "48px",
|
|
||||||
width: "48px",
|
|
||||||
marginBottom: "20px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ButtonBase>
|
|
||||||
{video ? <video src={video} width="400" controls /> : null}
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@ -613,25 +556,7 @@ export default function StartPageSettings() {
|
|||||||
>
|
>
|
||||||
Favicon
|
Favicon
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
{favIconDropZoneElement}
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "end",
|
|
||||||
gap: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{favIconDropZoneElement}
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.orange.main,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
textDecoration: "underline",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
5 MB максимум
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -695,25 +620,7 @@ export default function StartPageSettings() {
|
|||||||
>
|
>
|
||||||
Favicon
|
Favicon
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
{favIconDropZoneElement}
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "end",
|
|
||||||
gap: "10px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{favIconDropZoneElement}
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.orange.main,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
textDecoration: "underline",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
5 MB максимум
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(!isSmallMonitor || (isSmallMonitor && formState === "content")) && (
|
{(!isSmallMonitor || (isSmallMonitor && formState === "content")) && (
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { questionApi } from "@api/question";
|
import { questionApi } from "@api/question";
|
||||||
import { quizApi } from "@api/quiz";
|
import { quizApi } from "@api/quiz";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
@ -18,10 +17,10 @@ import { withErrorBoundary } from "react-error-boundary";
|
|||||||
|
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||||
const untypedQuestions = state.questions.filter(q => q.type === null);
|
const untypedResultQuestions = state.questions.filter(q => q.type === null || q.type === "result");
|
||||||
|
|
||||||
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
||||||
state.questions.push(...untypedQuestions);
|
state.questions.push(...untypedResultQuestions);
|
||||||
}, {
|
}, {
|
||||||
type: "setQuestions",
|
type: "setQuestions",
|
||||||
questions,
|
questions,
|
||||||
@ -87,6 +86,18 @@ const setQuestionBackendId = (questionId: string, backendId: number) => setProdu
|
|||||||
backendId,
|
backendId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateQuestionOrders = () => {
|
||||||
|
const questions = useQuestionsStore.getState().questions.filter(
|
||||||
|
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||||||
|
);
|
||||||
|
|
||||||
|
questions.forEach((question, index) => {
|
||||||
|
updateQuestion(question.id, question => {
|
||||||
|
question.page = index;
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const reorderQuestions = (
|
export const reorderQuestions = (
|
||||||
sourceIndex: number,
|
sourceIndex: number,
|
||||||
destinationIndex: number,
|
destinationIndex: number,
|
||||||
@ -101,6 +112,8 @@ export const reorderQuestions = (
|
|||||||
sourceIndex,
|
sourceIndex,
|
||||||
destinationIndex,
|
destinationIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateQuestionOrders();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleExpandQuestion = (questionId: string) => setProducedState(state => {
|
export const toggleExpandQuestion = (questionId: string) => setProducedState(state => {
|
||||||
@ -125,6 +138,7 @@ let requestTimeoutId: ReturnType<typeof setTimeout>;
|
|||||||
export const updateQuestion = (
|
export const updateQuestion = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
updateFn: (question: AnyTypedQuizQuestion) => void,
|
updateFn: (question: AnyTypedQuizQuestion) => void,
|
||||||
|
skipQueue = false,
|
||||||
) => {
|
) => {
|
||||||
setProducedState(state => {
|
setProducedState(state => {
|
||||||
const question = state.questions.find(q => q.id === questionId) || state.questions.find(q => q.type !== null && q.content.id === questionId);
|
const question = state.questions.find(q => q.id === questionId) || state.questions.find(q => q.type !== null && q.content.id === questionId);
|
||||||
@ -139,21 +153,31 @@ export const updateQuestion = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
// clearTimeout(requestTimeoutId);
|
// clearTimeout(requestTimeoutId);
|
||||||
// requestTimeoutId = setTimeout(() => {
|
|
||||||
requestQueue.enqueue(async () => {
|
const request = async () => {
|
||||||
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.type !== null && q.content.id === questionId);
|
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.type !== null && q.content.id === questionId);
|
||||||
if (!q) return;
|
if (!q) return;
|
||||||
if (q.type === null) throw new Error("Cannot send update request for untyped question");
|
if (q.type === null) throw new Error("Cannot send update request for untyped question");
|
||||||
|
|
||||||
const response = await questionApi.edit(questionToEditQuestionRequest(q));
|
try {
|
||||||
|
const response = await questionApi.edit(questionToEditQuestionRequest(q));
|
||||||
|
|
||||||
setQuestionBackendId(questionId, response.updated);
|
setQuestionBackendId(questionId, response.updated);
|
||||||
}).catch(error => {
|
} catch (error) {
|
||||||
if (isAxiosCanceledError(error)) return;
|
if (isAxiosCanceledError(error)) return;
|
||||||
|
|
||||||
devlog("Error editing question", { error, questionId });
|
devlog("Error editing question", { error, questionId });
|
||||||
enqueueSnackbar("Не удалось сохранить вопрос");
|
enqueueSnackbar("Не удалось сохранить вопрос");
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (skipQueue) {
|
||||||
|
request();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestTimeoutId = setTimeout(() => {
|
||||||
|
requestQueue.enqueue(request);
|
||||||
// }, REQUEST_DEBOUNCE);
|
// }, REQUEST_DEBOUNCE);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -263,13 +287,13 @@ export const changeQuestionType = (
|
|||||||
type: QuestionType,
|
type: QuestionType,
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
const oldId = question.content.id
|
const oldId = question.content.id;
|
||||||
const oldRule = question.content.rule
|
const oldRule = question.content.rule;
|
||||||
oldRule.main = []
|
oldRule.main = [];
|
||||||
question.type = type;
|
question.type = type;
|
||||||
question.content = defaultQuestionByType[type].content;
|
question.content = defaultQuestionByType[type].content;
|
||||||
question.content.id = oldId
|
question.content.id = oldId;
|
||||||
question.content.rule = oldRule
|
question.content.rule = oldRule;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -277,7 +301,8 @@ export const createTypedQuestion = async (
|
|||||||
questionId: string,
|
questionId: string,
|
||||||
type: QuestionType,
|
type: QuestionType,
|
||||||
) => requestQueue.enqueue(async () => {
|
) => requestQueue.enqueue(async () => {
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
const questions = useQuestionsStore.getState().questions;
|
||||||
|
const question = questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
||||||
|
|
||||||
@ -287,7 +312,7 @@ export const createTypedQuestion = async (
|
|||||||
type,
|
type,
|
||||||
title: question.title,
|
title: question.title,
|
||||||
description: question.description,
|
description: question.description,
|
||||||
page: 0,
|
page: questions.length,
|
||||||
required: false,
|
required: false,
|
||||||
content: JSON.stringify(defaultQuestionByType[type].content),
|
content: JSON.stringify(defaultQuestionByType[type].content),
|
||||||
});
|
});
|
||||||
@ -310,8 +335,8 @@ export const createTypedQuestion = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const deleteQuestion = async (questionId: string, quizId: string) => requestQueue.enqueue(async () => {
|
export const deleteQuestion = async (questionId: string, quizId: string) => requestQueue.enqueue(async () => {
|
||||||
const { questions } = useQuestionsStore.getState()
|
|
||||||
const question = questions.find(q => q.id === questionId);
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
if (question.type === null) {
|
if (question.type === null) {
|
||||||
@ -394,9 +419,9 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
|
|||||||
|
|
||||||
const copiedQuestion = structuredClone(question);
|
const copiedQuestion = structuredClone(question);
|
||||||
copiedQuestion.backendId = newQuestionId;
|
copiedQuestion.backendId = newQuestionId;
|
||||||
copiedQuestion.id = frontId
|
copiedQuestion.id = frontId;
|
||||||
copiedQuestion.content.id = frontId
|
copiedQuestion.content.id = frontId;
|
||||||
copiedQuestion.content.rule = { main: [], parentId: "", default: "" }
|
copiedQuestion.content.rule = { main: [], parentId: "", default: "" };
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(state => {
|
||||||
state.questions.push(copiedQuestion);
|
state.questions.push(copiedQuestion);
|
||||||
@ -405,6 +430,8 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
|
|||||||
questionId,
|
questionId,
|
||||||
quizId,
|
quizId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateQuestionOrders();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error copying question", error);
|
devlog("Error copying question", error);
|
||||||
enqueueSnackbar("Не удалось скопировать вопрос");
|
enqueueSnackbar("Не удалось скопировать вопрос");
|
||||||
@ -439,7 +466,7 @@ export const getQuestionByContentId = (questionContentId: string | null) => {
|
|||||||
export const updateOpenedModalSettingsId = (id?: string) => useQuestionsStore.setState({ openedModalSettingsId: id ? id : null });
|
export const updateOpenedModalSettingsId = (id?: string) => useQuestionsStore.setState({ openedModalSettingsId: id ? id : null });
|
||||||
export const updateDragQuestionContentId = (contentId?: string) => {
|
export const updateDragQuestionContentId = (contentId?: string) => {
|
||||||
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
||||||
}
|
};
|
||||||
|
|
||||||
export const clearRuleForAll = () => {
|
export const clearRuleForAll = () => {
|
||||||
const { questions } = useQuestionsStore.getState()
|
const { questions } = useQuestionsStore.getState()
|
||||||
@ -472,15 +499,22 @@ export const updateEditSomeQuestion = (contentId?: string) => {
|
|||||||
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
|
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createFrontResult = (quizId: number) => setProducedState(state => {
|
export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => {
|
||||||
|
const frontId = nanoid()
|
||||||
|
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content))
|
||||||
|
content.id = frontId
|
||||||
|
if (parentContentId) content.rule.parentId = parentContentId
|
||||||
state.questions.push({
|
state.questions.push({
|
||||||
id: nanoid(),
|
id: frontId,
|
||||||
quizId,
|
quizId,
|
||||||
type: "result",
|
type: "result",
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
deleted: false,
|
deleted: false,
|
||||||
expanded: true,
|
expanded: true,
|
||||||
|
page: 101,
|
||||||
|
required: true,
|
||||||
|
content
|
||||||
});
|
});
|
||||||
}, {
|
}, {
|
||||||
type: "createFrontResult",
|
type: "createFrontResult",
|
||||||
@ -494,7 +528,7 @@ export const createBackResult = async (
|
|||||||
) => requestQueue.enqueue(async () => {
|
) => requestQueue.enqueue(async () => {
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
if (question.type !== "result") throw new Error("Cannot upgrade already typed question");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createdQuestion = await questionApi.create({
|
const createdQuestion = await questionApi.create({
|
||||||
@ -523,9 +557,3 @@ export const createBackResult = async (
|
|||||||
enqueueSnackbar("Не удалось создать вопрос");
|
enqueueSnackbar("Не удалось создать вопрос");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateResult = () => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
|
|
||||||
|
|
||||||
export function useQuestions() {
|
export function useQuestions() {
|
||||||
|
console.log("вызываю вопросы")
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const { isLoading, error, isValidating } = useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
const { isLoading, error, isValidating } = useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
||||||
onSuccess: setQuestions,
|
onSuccess: setQuestions,
|
||||||
|
@ -107,7 +107,7 @@ const REQUEST_DEBOUNCE = 200;
|
|||||||
const requestQueue = new RequestQueue();
|
const requestQueue = new RequestQueue();
|
||||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
export const updateQuiz = async (
|
export const updateQuiz = (
|
||||||
quizId: string | null | undefined,
|
quizId: string | null | undefined,
|
||||||
updateFn: (quiz: Quiz) => void,
|
updateFn: (quiz: Quiz) => void,
|
||||||
) => {
|
) => {
|
||||||
|
@ -7,12 +7,14 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography, useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import InfoIcon from "@icons/InfoIcon";
|
import InfoIcon from "@icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionEmoji } from "model/questionTypes/emoji";
|
import type { QuizQuestionEmoji } from "model/questionTypes/emoji";
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: QuizQuestionEmoji;
|
question: QuizQuestionEmoji;
|
||||||
@ -20,45 +22,78 @@ interface Props {
|
|||||||
|
|
||||||
export default function Emoji({ question }: Props) {
|
export default function Emoji({ question }: Props) {
|
||||||
const [value, setValue] = useState<string | null>(null);
|
const [value, setValue] = useState<string | null>(null);
|
||||||
|
const theme = useTheme();
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setValue((event.target as HTMLInputElement).value);
|
setValue((event.target as HTMLInputElement).value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<FormLabel id="quiz-question-radio-group" data-cy="question-title">{question.title}</FormLabel>
|
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{fontSize: "24px", fontWeight: 500, marginBottom: "25px"}}>{question.title}</FormLabel>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-labelledby="quiz-question-radio-group"
|
aria-labelledby="quiz-question-radio-group"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
sx={{display: "flex",
|
||||||
|
flexDirection: "row", gap: "42px"}}
|
||||||
>
|
>
|
||||||
{question.content.variants
|
{question.content.variants
|
||||||
.filter(({ answer }) => answer)
|
.filter(({ answer }) => answer)
|
||||||
.map((variant, index) => (
|
.map((variant, index) => (
|
||||||
<FormControlLabel
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
value={variant.answer}
|
sx={{
|
||||||
control={
|
borderRadius: "12px",
|
||||||
<Radio
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
inputProps={{
|
overflow: "hidden",
|
||||||
"data-cy": "variant-radio",
|
maxWidth: "317px",
|
||||||
|
width: "100%",
|
||||||
|
height: "255px"
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
}
|
<Box
|
||||||
label={
|
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
|
>
|
||||||
<Typography>{`${variant.extendedText} ${variant.answer}`}</Typography>
|
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||||
{variant.hints && (
|
{variant.extendedText && (
|
||||||
<Tooltip title="Подсказка" placement="right">
|
<Typography fontSize={"100px"}>{`${variant.extendedText}`}</Typography>
|
||||||
<Box>
|
)}
|
||||||
<InfoIcon />
|
</Box>
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
<FormControlLabel
|
||||||
/>
|
key={index}
|
||||||
|
value={variant.answer}
|
||||||
|
sx={{
|
||||||
|
margin: 0,
|
||||||
|
padding: "15px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
background: "#f2f3f7"
|
||||||
|
}}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
inputProps={{
|
||||||
|
"data-cy": "variant-radio",
|
||||||
|
}}
|
||||||
|
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||||
|
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
|
||||||
|
<Typography>{`${variant.answer}`}</Typography>
|
||||||
|
{variant.hints && (
|
||||||
|
<Tooltip title="Подсказка" placement="right">
|
||||||
|
<Box>
|
||||||
|
<InfoIcon />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, ChangeEvent, useEffect } from "react";
|
import { useState, ChangeEvent } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -7,122 +7,129 @@ import {
|
|||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography, useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import InfoIcon from "@icons/InfoIcon";
|
import InfoIcon from "@icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuestionVariant } from "model/questionTypes/shared";
|
import type { QuestionVariant } from "model/questionTypes/shared";
|
||||||
import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
|
import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: QuizQuestionVarImg;
|
question: QuizQuestionVarImg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Varimg({ question }: Props) {
|
export default function Varimg({ question }: Props) {
|
||||||
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
|
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
|
||||||
|
const theme = useTheme();
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setSelectedVariantIndex(
|
setSelectedVariantIndex(
|
||||||
question.content.variants.findIndex(
|
question.content.variants.findIndex(
|
||||||
(variant) => variant.answer === event.target.value
|
(variant) => variant.answer === event.target.value
|
||||||
)
|
)
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentVariant: QuestionVariant | undefined =
|
|
||||||
question.content.variants[selectedVariantIndex];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
gap: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<FormLabel
|
|
||||||
id="quiz-question-radio-group"
|
|
||||||
data-cy="question-title"
|
|
||||||
>
|
|
||||||
{question.title}
|
|
||||||
</FormLabel>
|
|
||||||
<RadioGroup
|
|
||||||
aria-labelledby="quiz-question-radio-group"
|
|
||||||
value={currentVariant?.answer ?? ""}
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
|
||||||
{question.content.variants
|
|
||||||
.filter(({ answer }) => answer)
|
|
||||||
.map((variant, index) => (
|
|
||||||
<FormControlLabel
|
|
||||||
key={index}
|
|
||||||
value={variant.answer}
|
|
||||||
data-cy="variant-answer"
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
inputProps={{
|
|
||||||
"data-cy": "variant-radio",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
|
||||||
<Typography>{variant.answer}</Typography>
|
|
||||||
{variant.hints && (
|
|
||||||
<Tooltip title={variant.hints} placement="right">
|
|
||||||
<Box>
|
|
||||||
<InfoIcon />
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
border: "1px solid #E3E3E3",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentVariant ?
|
|
||||||
currentVariant.extendedText ? (
|
|
||||||
<img
|
|
||||||
src={currentVariant.extendedText}
|
|
||||||
data-cy="variant-image"
|
|
||||||
alt="question variant"
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
display: "block",
|
|
||||||
objectFit: "scale-down",
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Typography p={2}>Картинка отсутствует</Typography>
|
|
||||||
) : question.content.back ? (
|
|
||||||
<img
|
|
||||||
src={question.content.back}
|
|
||||||
data-cy="variant-image"
|
|
||||||
alt="question variant"
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
display: "block",
|
|
||||||
objectFit: "scale-down",
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Typography p={2}>Выберите вариант</Typography>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentVariant: QuestionVariant | undefined =
|
||||||
|
question.content.variants[selectedVariantIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "40px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl sx={{maxWidth: "900px",
|
||||||
|
width: "100%", gap: "25px"}}>
|
||||||
|
<FormLabel
|
||||||
|
id="quiz-question-radio-group"
|
||||||
|
data-cy="question-title"
|
||||||
|
sx={{fontSize: "24px", fontWeight: 500}}
|
||||||
|
>
|
||||||
|
{question.title}
|
||||||
|
</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
aria-labelledby="quiz-question-radio-group"
|
||||||
|
value={currentVariant?.answer ?? ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{gap: "20px"}}
|
||||||
|
>
|
||||||
|
{question.content.variants
|
||||||
|
.filter(({ answer }) => answer)
|
||||||
|
.map((variant, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={index}
|
||||||
|
value={variant.answer}
|
||||||
|
data-cy="variant-answer"
|
||||||
|
sx={{
|
||||||
|
margin: 0,
|
||||||
|
borderRadius: "5px",
|
||||||
|
padding: "15px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
inputProps={{
|
||||||
|
"data-cy": "variant-radio",
|
||||||
|
}}
|
||||||
|
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||||
|
<Typography>{variant.answer}</Typography>
|
||||||
|
{variant.hints && (
|
||||||
|
<Tooltip title={variant.hints} placement="right">
|
||||||
|
<Box>
|
||||||
|
<InfoIcon />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: "1px solid #E3E3E3",
|
||||||
|
maxWidth: "400px",
|
||||||
|
height: "400px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: "12px",
|
||||||
|
marginTop: "50px",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentVariant?.extendedText ? (
|
||||||
|
<img
|
||||||
|
src={currentVariant.extendedText}
|
||||||
|
data-cy="variant-image"
|
||||||
|
alt="question variant"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "block",
|
||||||
|
objectFit: "scale-down",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography p={2}>
|
||||||
|
{selectedVariantIndex === -1
|
||||||
|
? "Выберите вариант"
|
||||||
|
: "Картинка отсутствует"}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import YoutubeEmbedIframe from "./YoutubeEmbedIframe";
|
||||||
|
|
||||||
|
|
||||||
export default function QuizPreviewLayout() {
|
export default function QuizPreviewLayout() {
|
||||||
@ -52,18 +53,17 @@ export default function QuizPreviewLayout() {
|
|||||||
gap: "20px",
|
gap: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{quiz.config.startpage.background.type === "image" &&
|
{quiz.config.startpage.logo && (
|
||||||
quiz.config.startpage.logo && (
|
<img
|
||||||
<img
|
src={quiz.config.startpage.logo}
|
||||||
src={quiz.config.startpage.logo}
|
style={{
|
||||||
style={{
|
height: "30px",
|
||||||
height: "30px",
|
maxWidth: "50px",
|
||||||
maxWidth: "50px",
|
objectFit: "cover",
|
||||||
objectFit: "cover",
|
}}
|
||||||
}}
|
alt=""
|
||||||
alt=""
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
<Typography sx={{ fontSize: "12px" }}>
|
<Typography sx={{ fontSize: "12px" }}>
|
||||||
{quiz.config.info.orgname}
|
{quiz.config.info.orgname}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -122,14 +122,7 @@ export default function QuizPreviewLayout() {
|
|||||||
)}
|
)}
|
||||||
{quiz.config.startpage.background.type === "video" &&
|
{quiz.config.startpage.background.type === "video" &&
|
||||||
quiz.config.startpage.background.video && (
|
quiz.config.startpage.background.video && (
|
||||||
<video
|
<YoutubeEmbedIframe videoUrl={quiz.config.startpage.background.video} />
|
||||||
src={quiz.config.startpage.background.video}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
34
src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx
Normal file
34
src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
videoUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function YoutubeEmbedIframe({ videoUrl }: Props) {
|
||||||
|
const extractYoutubeVideoId = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/gi;
|
||||||
|
const videoId = extractYoutubeVideoId.exec(videoUrl)?.[1];
|
||||||
|
if (!videoId) return null;
|
||||||
|
|
||||||
|
const embedUrl = `https://www.youtube.com/embed/${videoId}?controls=0&autoplay=1&modestbranding=0&showinfo=0&disablekb=1&mute=1&loop=1`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
"& iframe": {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<iframe
|
||||||
|
src={embedUrl}
|
||||||
|
title="YouTube video player"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowFullScreen
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -4,8 +4,8 @@ import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
|
|||||||
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
||||||
import QuestionsPage from "../pages/Questions/QuestionsPage";
|
import QuestionsPage from "../pages/Questions/QuestionsPage";
|
||||||
import { QuestionsMap } from "../pages/QuestionsMap";
|
import { QuestionsMap } from "../pages/QuestionsMap";
|
||||||
import { Result } from "../pages/ResultPage/Result";
|
import { ResultPage } from "../pages/ResultPage/ResultPage";
|
||||||
import { Setting } from "../pages/ResultPage/Setting";
|
import { ResultSettings } from "../pages/ResultPage/ResultSettings";
|
||||||
import StartPageSettings from "../pages/startPage/StartPageSettings";
|
import StartPageSettings from "../pages/startPage/StartPageSettings";
|
||||||
import StepOne from "../pages/startPage/stepOne";
|
import StepOne from "../pages/startPage/stepOne";
|
||||||
import Steptwo from "../pages/startPage/steptwo";
|
import Steptwo from "../pages/startPage/steptwo";
|
||||||
@ -31,10 +31,7 @@ export default function SwitchStepPages({
|
|||||||
return <StartPageSettings />;
|
return <StartPageSettings />;
|
||||||
}
|
}
|
||||||
case 1: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
|
case 1: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
|
||||||
case 2: {
|
case 2: return <ResultPage />;
|
||||||
if (!quizResults) return <Result />;
|
|
||||||
return <Setting />;
|
|
||||||
}
|
|
||||||
case 3: return <QuestionsMap />;
|
case 3: return <QuestionsMap />;
|
||||||
case 4: return <ContactFormPage />;
|
case 4: return <ContactFormPage />;
|
||||||
case 5: return <InstallQuiz />;
|
case 5: return <InstallQuiz />;
|
||||||
|
Loading…
Reference in New Issue
Block a user