Merge branch 'image-modals' into 'main'

Image modals

See merge request frontend/squiz!82
This commit is contained in:
Nastya 2023-12-19 20:52:35 +00:00
commit 7ee8c89172
41 changed files with 1266 additions and 1040 deletions

@ -6,7 +6,7 @@
"@craco/craco": "^7.0.0", "@craco/craco": "^7.0.0",
"@emotion/react": "^11.10.5", "@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.54", "@frontend/kitui": "^1.0.55",
"@mui/icons-material": "^5.10.14", "@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14", "@mui/material": "^5.10.14",
"@mui/x-date-pickers": "^6.16.1", "@mui/x-date-pickers": "^6.16.1",

@ -16,8 +16,8 @@ import { ResultSettings } from "./pages/ResultPage/ResultSettings";
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull"; import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
import Main from "./pages/main"; import Main from "./pages/main";
import EditPage from "./pages/startPage/EditPage"; import EditPage from "./pages/startPage/EditPage";
import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui"; import { clearAuthToken, getMessageFromFetchError, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui";
import { clearUserData, setUser, useUserStore } from "@root/user"; import { clearUserData, setUser, setUserAccount, useUserStore } from "@root/user";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import PrivateRoute from "@ui_kit/PrivateRoute"; import PrivateRoute from "@ui_kit/PrivateRoute";
@ -51,6 +51,22 @@ export default function App() {
} }
}, },
}); });
useUserAccountFetcher({
url: "https://squiz.pena.digital/customer/account",
userId,
onNewUserAccount: setUserAccount,
onError: (error) => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) {
enqueueSnackbar(errorMessage);
clearUserData();
clearAuthToken();
navigate("/signin");
}
},
});
if (location.state?.redirectTo) if (location.state?.redirectTo)
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />; return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />;

@ -18,7 +18,6 @@ function createQuestion(body: CreateQuestionRequest) {
} }
async function getQuestionList(body?: Partial<GetQuestionListRequest>) { async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
console.log("body" , body)
if (!body?.quiz_id) return null; if (!body?.quiz_id) return null;
const response = await makeRequest<GetQuestionListRequest, GetQuestionListResponse>({ const response = await makeRequest<GetQuestionListRequest, GetQuestionListResponse>({

@ -17,8 +17,6 @@ interface Props {
export default function ButtonsNewField ({SSHC, switchState, type}:Props) { export default function ButtonsNewField ({SSHC, switchState, type}:Props) {
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz() const quiz = useCurrentQuiz()
console.log(quiz)
console.log(type)
const buttonSetting: {icon: JSX.Element; title: string; value: string} [] =[ const buttonSetting: {icon: JSX.Element; title: string; value: string} [] =[
{icon: <NameIcon color={switchState === 'name' ? '#ffffff' : theme.palette.grey3.main}/>, title: 'Имя', value: 'name'}, {icon: <NameIcon color={switchState === 'name' ? '#ffffff' : theme.palette.grey3.main}/>, title: 'Имя', value: 'name'},
{icon: <EmailIcon color={switchState === 'email' ? '#ffffff' : theme.palette.grey3.main}/>, title: 'Email', value: 'email'}, {icon: <EmailIcon color={switchState === 'email' ? '#ffffff' : theme.palette.grey3.main}/>, title: 'Email', value: 'email'},

@ -18,7 +18,6 @@ interface Props {
export default function NewFieldParent({ drawerNewFieldHC, defaultValue, placeholderHelp, placeholderField, outerContainerSx: sx, children }: Props) { export default function NewFieldParent({ drawerNewFieldHC, defaultValue, placeholderHelp, placeholderField, outerContainerSx: sx, children }: Props) {
const quiz = useCurrentQuiz() const quiz = useCurrentQuiz()
console.log({ defaultValue, placeholderHelp, placeholderField, outerContainerSx: sx, children })
return ( return (
<Box sx={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '20px' }}> <Box sx={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '20px' }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '15px' }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
@ -73,7 +72,6 @@ export default function NewFieldParent({ drawerNewFieldHC, defaultValue, placeho
<CustomCheckbox <CustomCheckbox
checked={quiz.config.formContact[defaultValue].required} checked={quiz.config.formContact[defaultValue].required}
handleChange={({ target }) => { handleChange={({ target }) => {
console.log("click")
updateQuiz(quiz.id, (quiz) => { updateQuiz(quiz.id, (quiz) => {
quiz.config.formContact[defaultValue].required = target.checked quiz.config.formContact[defaultValue].required = target.checked
}) })

@ -14,7 +14,6 @@ interface Props {
export default function SwitchNewField({switchState ='name', drawerNewFieldHC}: Props) { export default function SwitchNewField({switchState ='name', drawerNewFieldHC}: Props) {
const [SwitchMask, setSwitchMask] = React.useState(false); const [SwitchMask, setSwitchMask] = React.useState(false);
console.log(switchState)
const SwitchMaskHC = (bool:boolean) => { const SwitchMaskHC = (bool:boolean) => {
setSwitchMask(bool) setSwitchMask(bool)
} }

@ -48,7 +48,6 @@ type BackgroundType = "text" | "video";
export default function InstallQuiz() { export default function InstallQuiz() {
const quiz = useCurrentQuiz() const quiz = useCurrentQuiz()
console.log(quiz)
const [display, setDisplay] = React.useState("1"); const [display, setDisplay] = React.useState("1");
const handleChange = (event: SelectChangeEvent) => { const handleChange = (event: SelectChangeEvent) => {

@ -1,6 +1,6 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Cytoscape from "cytoscape"; import Cytoscape from "cytoscape";
import { Button } from "@mui/material"; import { Button, Box } from "@mui/material";
import CytoscapeComponent from "react-cytoscapejs"; import CytoscapeComponent from "react-cytoscapejs";
import popper from "cytoscape-popper"; import popper from "cytoscape-popper";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
@ -8,9 +8,10 @@ import { updateRootContentId } from "@root/quizes/actions"
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared" import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { deleteQuestion, updateQuestion, getQuestionByContentId, clearRuleForAll, createFrontResult } from "@root/questions/actions"; import { deleteQuestion, updateQuestion, getQuestionByContentId, clearRuleForAll, createFrontResult } from "@root/questions/actions";
import { updateOpenedModalSettingsId, } from "@root/uiTools/actions"; import { updateCanCreatePublic, updateModalInfoWhyCantCreate, updateOpenedModalSettingsId, } from "@root/uiTools/actions";
import { cleardragQuestionContentId } from "@root/uiTools/actions"; import { cleardragQuestionContentId } from "@root/uiTools/actions";
import { withErrorBoundary } from "react-error-boundary"; import { withErrorBoundary } from "react-error-boundary";
import { ProblemIcon } from "@ui_kit/ProblemIcon";
import { storeToNodes } from "./helper"; import { storeToNodes } from "./helper";
@ -115,6 +116,7 @@ interface Props {
} }
function CsComponent({ function CsComponent({
modalQuestionParentContentId, modalQuestionParentContentId,
modalQuestionTargetContentId, modalQuestionTargetContentId,
@ -124,7 +126,7 @@ function CsComponent({
}: Props) { }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { dragQuestionContentId, desireToOpenABranchingModal } = useUiTools() const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools()
const trashQuestions = useQuestionsStore().questions const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null) const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null)
const [startCreate, setStartCreate] = useState(""); const [startCreate, setStartCreate] = useState("");
@ -136,6 +138,13 @@ function CsComponent({
const crossesContainer = useRef<HTMLDivElement | null>(null); const crossesContainer = useRef<HTMLDivElement | null>(null);
const gearsContainer = useRef<HTMLDivElement | null>(null); const gearsContainer = useRef<HTMLDivElement | null>(null);
useEffect(() => {
return () => {
// if (!canCreatePublic) updateModalInfoWhyCantCreate(true)
}
}, []);
useLayoutEffect(() => { useLayoutEffect(() => {
const cy = cyRef?.current const cy = cyRef?.current
if (desireToOpenABranchingModal) { if (desireToOpenABranchingModal) {
@ -188,7 +197,6 @@ function CsComponent({
} }
]) ])
cy?.layout(lyopts).run() cy?.layout(lyopts).run()
console.log(es)
cy?.center(es) cy?.center(es)
} else { } else {
enqueueSnackbar("Добавляемый вопрос не найден") enqueueSnackbar("Добавляемый вопрос не найден")
@ -203,7 +211,6 @@ function CsComponent({
//смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен //смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен
trashQuestions.forEach((targetQuestion) => { trashQuestions.forEach((targetQuestion) => {
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) { if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
console.log('deleteQ', targetQuestion.id)
deleteQuestion(targetQuestion.id); deleteQuestion(targetQuestion.id);
} }
}) })
@ -228,7 +235,6 @@ function CsComponent({
const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => {
console.log("старт удаление")
const deleteNodes = [] as string[] const deleteNodes = [] as string[]
const deleteEdges: any = [] const deleteEdges: any = []
const cy = cyRef?.current const cy = cyRef?.current
@ -272,7 +278,7 @@ function CsComponent({
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) { if (targetNodeContentId && parentQuestionContentId) {
if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0)
createFrontResult(quiz.backendId, parentQuestionContentId) createFrontResult(quiz.backendId, parentQuestionContentId)
clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId })
cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run()
@ -640,7 +646,7 @@ if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0)
return crossElement; return crossElement;
}, },
}); });
let gearsPopper = null let gearsPopper = null
if (node.data().root !== true) { if (node.data().root !== true) {
gearsPopper = node.popper({ gearsPopper = node.popper({
popper: { popper: {
@ -663,7 +669,6 @@ let gearsPopper = null
gearElement.style.zIndex = "1" gearElement.style.zIndex = "1"
gearsContainer.current?.appendChild(gearElement); gearsContainer.current?.appendChild(gearElement);
gearElement.addEventListener("mouseup", (e) => { gearElement.addEventListener("mouseup", (e) => {
console.log("up")
updateOpenedModalSettingsId(item.id()) updateOpenedModalSettingsId(item.id())
}); });
@ -786,9 +791,10 @@ let gearsPopper = null
return ( return (
<> <>
<Box
mb="20px">
<Button <Button
sx={{ sx={{
mb: "20px",
height: "27px", height: "27px",
color: "#7E2AEA", color: "#7E2AEA",
textDecoration: "underline", textDecoration: "underline",
@ -803,6 +809,9 @@ let gearsPopper = null
> >
Выровнять Выровнять
</Button> </Button>
<ProblemIcon blink={!canCreatePublic} onClick={() => updateModalInfoWhyCantCreate(true)} />
</Box>
<CytoscapeComponent <CytoscapeComponent
wheelSensitivity={0.1} wheelSensitivity={0.1}
elements={[]} elements={[]}
@ -815,16 +824,6 @@ let gearsPopper = null
}} }}
autoungrabify={true} autoungrabify={true}
/> />
{/* <button onClick={() => {
console.log("NODES____________________________")
cyRef.current?.elements().forEach((ele: any) => {
console.log(ele.data())
})
}}>nodes</button>
<button onClick={() => {
console.log("ELEMENTS____________________________")
console.log(questions)
}}>elements</button> */}
</> </>
); );
}; };

@ -18,7 +18,6 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
useLayoutEffect(() => { useLayoutEffect(() => {
updateOpenedModalSettingsId() updateOpenedModalSettingsId()
console.log("first render firstComponent")
updateRootContentId(quiz.id, "") updateRootContentId(quiz.id, "")
clearRuleForAll() clearRuleForAll()
}, []) }, [])

@ -106,7 +106,6 @@ export default function ButtonsOptions({
deleteQuestion(question.id); deleteQuestion(question.id);
} else { } else {
console.log("удаляю безтипогово");
deleteQuestion(question.id); deleteQuestion(question.id);
} }
}; };
@ -149,7 +148,6 @@ export default function ButtonsOptions({
title: "Ветвление", title: "Ветвление",
value: "branching", value: "branching",
myFunc: (question) => { myFunc: (question) => {
console.log("buttons opiums")
updateOpenBranchingPanel(true); updateOpenBranchingPanel(true);
updateDesireToOpenABranchingModal(question.content.id); updateDesireToOpenABranchingModal(question.content.id);
} }

@ -107,7 +107,6 @@ export default function ButtonsOptionsAndPict({
deleteQuestion(question.id); deleteQuestion(question.id);
} else { } else {
console.log("удаляю безтипогово");
deleteQuestion(question.id); deleteQuestion(question.id);
} }
}; };
@ -254,7 +253,6 @@ export default function ButtonsOptionsAndPict({
onMouseEnter={() => setButtonHover("branching")} onMouseEnter={() => setButtonHover("branching")}
onMouseLeave={() => setButtonHover("")} onMouseLeave={() => setButtonHover("")}
onClick={() => { onClick={() => {
console.log("buttonsOptions")
updateOpenBranchingPanel(true); updateOpenBranchingPanel(true);
updateDesireToOpenABranchingModal(question.content.id); updateDesireToOpenABranchingModal(question.content.id);
}} }}

@ -123,7 +123,6 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
deleteQuestion(question.id); deleteQuestion(question.id);
} else { } else {
console.log("удаляю безтипогово");
deleteQuestion(question.id); deleteQuestion(question.id);
} }
}; };

@ -51,6 +51,7 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr
}); });
}, 200); }, 200);
console.log(question)
return ( return (
<> <>
<Paper <Paper
@ -85,7 +86,7 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr
> >
<CustomTextField <CustomTextField
placeholder={`Заголовок ${questionIndex + 1} вопроса`} placeholder={`Заголовок ${questionIndex + 1} вопроса`}
text={question.title} value={question.title}
onChange={({ target }) => setTitle(target.value)} onChange={({ target }) => setTitle(target.value)}
sx={{ width: "100%" }} sx={{ width: "100%" }}
InputProps={{ InputProps={{

@ -6,8 +6,7 @@ import FormDraggableListItem from "./FormDraggableListItem";
import { useQuestions } from "@root/questions/hooks"; import { useQuestions } from "@root/questions/hooks";
export const FormDraggableList = () => { export const FormDraggableList = () => {
const { questions } = useQuestions(); const questions = useQuestions().questions.filter((q) => q.type !== "result");
const onDragEnd = ({ destination, source }: DropResult) => { const onDragEnd = ({ destination, source }: DropResult) => {
if (destination) reorderQuestions(source.index, destination.index); if (destination) reorderQuestions(source.index, destination.index);
}; };

@ -13,6 +13,7 @@ export default function FormQuestionsPage() {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
if (!quiz) return null; if (!quiz) return null;
console.log("Анкета")
return ( return (
<> <>

@ -14,8 +14,6 @@ export const QuestionSwitchWindowTool = () => {
const {openBranchingPanel} = useUiTools() const {openBranchingPanel} = useUiTools()
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
console.log("questions ", questions)
console.log("rules ", questions.filter((q) => q.type !== null).map((q) => ({id: q.content.id, rule: q.content.rule})))
return ( return (
<Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap", marginBottom: isMobile ? "20px" : undefined }}> <Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap", marginBottom: isMobile ? "20px" : undefined }}>
<Box sx={{ flexBasis: "796px" }}> <Box sx={{ flexBasis: "796px" }}>

@ -12,7 +12,7 @@ import { useUiTools } from "@root/uiTools/store";
export const SwitchBranchingPanel = () => { export const SwitchBranchingPanel = () => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(660)); const isMobile = useMediaQuery(theme.breakpoints.down(660));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1446));
const {openBranchingPanel} = useUiTools() const {openBranchingPanel} = useUiTools()
const ref = useRef() const ref = useRef()

@ -42,7 +42,6 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
const acceptedFormats = accept ? accept.map((format) => "." + format).join(", ") : ""; const acceptedFormats = accept ? accept.map((format) => "." + format).join(", ") : "";
console.log(acceptedFormats);
return ( return (
<Modal <Modal

@ -120,7 +120,6 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
} }
export const ResultCard = ({ resultContract, resultData }: Props) => { export const ResultCard = ({ resultContract, resultData }: Props) => {
console.log("resultData", resultData)
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const theme = useTheme(); const theme = useTheme();

@ -78,7 +78,6 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
}, [question, answers]); }, [question, answers]);
const showResult = (nextQuestion) => { const showResult = (nextQuestion) => {
console.log(nextQuestion);
if (nextQuestion && quiz?.config.resultInfo.when === "before") { if (nextQuestion && quiz?.config.resultInfo.when === "before") {
setShowResultForm(true); setShowResultForm(true);
@ -154,14 +153,9 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
const questionIndex = questions.findIndex(({ id }) => id === question.id); const questionIndex = questions.findIndex(({ id }) => id === question.id);
const nextQuestion = questions[questionIndex + 1]; const nextQuestion = questions[questionIndex + 1];
console.log("questionIndex ", questionIndex);
console.log(nextQuestion);
console.log(questions);
if (nextQuestion && nextQuestion.type !== "result") { if (nextQuestion && nextQuestion.type !== "result") {
console.log("следующий вопрос результирующий ", nextQuestion.type === "result");
setCurrentQuestion(nextQuestion); setCurrentQuestion(nextQuestion);
} else { } else {
console.log("следующий вопрос результирующий ", nextQuestion.type === "result");
showResult(nextQuestion); showResult(nextQuestion);
} }
@ -173,7 +167,6 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
if (nextQuestionId) { if (nextQuestionId) {
const nextQuestion = getQuestionByContentId(nextQuestionId); const nextQuestion = getQuestionByContentId(nextQuestionId);
console.log(nextQuestion);
if (nextQuestion?.type && nextQuestion.type !== "result") { if (nextQuestion?.type && nextQuestion.type !== "result") {
setCurrentQuestion(nextQuestion); setCurrentQuestion(nextQuestion);
return; return;

@ -43,7 +43,8 @@ const QUESTIONS_MAP: any = {
export const Question = ({ questions }: QuestionProps) => { export const Question = ({ questions }: QuestionProps) => {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const [currentQuestion, setCurrentQuestion] = useState<AnyTypedQuizQuestion>(); const [currentQuestion, setCurrentQuestion] =
useState<AnyTypedQuizQuestion>();
const [showContactForm, setShowContactForm] = useState<boolean>(false); const [showContactForm, setShowContactForm] = useState<boolean>(false);
const [showResultForm, setShowResultForm] = useState<boolean>(false); const [showResultForm, setShowResultForm] = useState<boolean>(false);
@ -61,9 +62,9 @@ export const Question = ({ questions }: QuestionProps) => {
if (!currentQuestion) return <>не смог отобразить вопрос</>; if (!currentQuestion) return <>не смог отобразить вопрос</>;
const QuestionComponent = QUESTIONS_MAP[currentQuestion.type as Exclude<QuestionType, "nonselected">]; const QuestionComponent =
QUESTIONS_MAP[currentQuestion.type as Exclude<QuestionType, "nonselected">];
console.log("showResultForm " , showResultForm)
return ( return (
<Box> <Box>
{!showContactForm && !showResultForm && ( {!showContactForm && !showResultForm && (

@ -30,17 +30,13 @@ export const ResultForm = ({
); );
console.log(currentQuestion)
console.log(resultQuestion
)
console.log(questions)
console.log(quiz?.config.resultInfo.when)
const followNextForm = () => { const followNextForm = () => {
setShowResultForm(false); setShowResultForm(false);
setShowContactForm(true); setShowContactForm(true);
}; };
if (resultQuestion === undefined) return <></>
return ( return (
<Box <Box

@ -74,6 +74,7 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
className="quiz-preview-draghandle" className="quiz-preview-draghandle"
sx={{ sx={{
height: "100vh", height: "100vh",
width: "100vw",
background: background:
quiz.config.startpageType === "expanded" quiz.config.startpageType === "expanded"
? quiz.config.startpage.position === "left" ? quiz.config.startpage.position === "left"
@ -82,6 +83,7 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
? "linear-gradient(180deg,transparent,#272626)" ? "linear-gradient(180deg,transparent,#272626)"
: "linear-gradient(270deg,#272626,transparent)" : "linear-gradient(270deg,#272626,transparent)"
: "", : "",
color: quiz.config.startpageType === "expanded" ? "white" : "black", color: quiz.config.startpageType === "expanded" ? "white" : "black",
}} }}
> >
@ -142,6 +144,9 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
fontStyle: "normal", fontStyle: "normal",
fontStretch: "normal", fontStretch: "normal",
lineHeight: "1.2", lineHeight: "1.2",
overflowWrap: "break-word",
width: "100%",
textAlign: quiz.config.startpageType === "centered" ? "center" : "-moz-initial",
}} }}
> >
{quiz.name} {quiz.name}
@ -150,6 +155,9 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
sx={{ sx={{
fontSize: "16px", fontSize: "16px",
m: "16px 0", m: "16px 0",
overflowWrap: "break-word",
width: "100%",
textAlign: quiz.config.startpageType === "centered" ? "center" : "-moz-initial",
}} }}
> >
{quiz.config.startpage.description} {quiz.config.startpage.description}
@ -172,7 +180,7 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
<Box <Box
sx={{ mt: "46px", display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }} sx={{ mt: "46px", display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}
> >
<Box> <Box sx={{ maxWidth: "300px" }}>
{quiz.config.info.clickable ? ( {quiz.config.info.clickable ? (
isMobileDevice ? ( isMobileDevice ? (
<Link href={`tel:${quiz.config.info.phonenumber}`}> <Link href={`tel:${quiz.config.info.phonenumber}`}>
@ -192,7 +200,9 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
{quiz.config.info.phonenumber} {quiz.config.info.phonenumber}
</Typography> </Typography>
)} )}
<Typography sx={{ fontSize: "12px", textAlign: "end" }}>{quiz.config.info.law}</Typography> <Typography sx={{ width: "100%", overflowWrap: "break-word", fontSize: "12px", textAlign: "end" }}>
{quiz.config.info.law}
</Typography>
</Box> </Box>
<Box <Box
@ -288,7 +298,7 @@ function QuizPreviewLayoutByType({
width: "40%", width: "40%",
position: "relative", position: "relative",
padding: "16px", padding: "16px",
zIndex: 2, zIndex: 3,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between", justifyContent: "space-between",
@ -301,11 +311,11 @@ function QuizPreviewLayoutByType({
<Box <Box
sx={{ sx={{
position: "absolute", position: "absolute",
zIndex: -1,
left: 0, left: 0,
top: 0, top: 0,
height: "100%", height: "100%",
width: "100%", width: "100%",
zIndex: 1,
overflow: "hidden", overflow: "hidden",
}} }}
> >
@ -329,7 +339,16 @@ function QuizPreviewLayoutByType({
}} }}
> >
{quizHeaderBlock} {quizHeaderBlock}
{backgroundBlock && <Box>{backgroundBlock}</Box>} {backgroundBlock && (
<Box
sx={{
width: "60%",
overflow: "hidden",
}}
>
{backgroundBlock}
</Box>
)}
{quizMainBlock} {quizMainBlock}
</Box> </Box>
); );

@ -52,9 +52,11 @@ export const ViewPage = () => {
questions.filter(({ type }) => type) as AnyTypedQuizQuestion[] questions.filter(({ type }) => type) as AnyTypedQuizQuestion[]
).sort((previousItem, item) => previousItem.page - item.page); ).sort((previousItem, item) => previousItem.page - item.page);
console.log("visualStartPage ", visualStartPage);
console.log(questions)
if (visualStartPage === undefined) return <></>; if (visualStartPage === undefined) return <></>;
if (questions.length === 0) return <ApologyPage message="Нет созданных вопросов"/> if (questions.length === 0 || (questions.length === 1 && questions[0].type === "result")) return <ApologyPage message="Нет созданных вопросов"/>
return ( return (
<Box> <Box>
{!visualStartPage ? ( {!visualStartPage ? (

@ -31,8 +31,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
({ id }) => answer === id ({ id }) => answer === id
); );
console.log(currentQuestion)
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>

@ -1,5 +1,5 @@
import { quizApi } from "@api/quiz"; import { quizApi } from "@api/quiz";
import { devlog } from "@frontend/kitui"; import { LogoutButton } from "@ui_kit/LogoutButton";
import BackArrowIcon from "@icons/BackArrowIcon"; import BackArrowIcon from "@icons/BackArrowIcon";
import { Burger } from "@icons/Burger"; import { Burger } from "@icons/Burger";
import EyeIcon from "@icons/EyeIcon"; import EyeIcon from "@icons/EyeIcon";
@ -16,7 +16,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import {decrementCurrentStep, resetEditConfig, setQuizes, updateQuiz} from "@root/quizes/actions"; import { decrementCurrentStep, resetEditConfig, setQuizes, updateQuiz } from "@root/quizes/actions";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuizStore } from "@root/quizes/store"; import { useQuizStore } from "@root/quizes/store";
import CustomAvatar from "@ui_kit/Header/Avatar"; import CustomAvatar from "@ui_kit/Header/Avatar";
@ -30,9 +30,10 @@ import { enqueueSnackbar } from "notistack";
import React, { useEffect, useLayoutEffect, useState } from "react"; import React, { useEffect, useLayoutEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import useSWR from "swr"; import useSWR from "swr";
import { useDebouncedCallback } from "use-debounce";
import { SidebarMobile } from "./Sidebar/SidebarMobile"; import { SidebarMobile } from "./Sidebar/SidebarMobile";
import { cleanQuestions, setQuestions } from "@root/questions/actions"; import { cleanQuestions, setQuestions } from "@root/questions/actions";
import { updateOpenBranchingPanel } from "@root/uiTools/actions"; import { updateOpenBranchingPanel, updateCanCreatePublic, updateModalInfoWhyCantCreate } from "@root/uiTools/actions";
import { BranchingPanel } from "../Questions/BranchingPanel"; import { BranchingPanel } from "../Questions/BranchingPanel";
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { useQuizes } from "@root/quizes/hooks"; import { useQuizes } from "@root/quizes/hooks";
@ -40,10 +41,16 @@ import { questionApi } from "@api/question";
import { useUiTools } from "@root/uiTools/store"; import { useUiTools } from "@root/uiTools/store";
import Logotip from "../Landing/images/icons/QuizLogo"; import Logotip from "../Landing/images/icons/QuizLogo";
import { clearUserData } from "@root/user";
import { clearAuthToken } from "@frontend/kitui";
import { logout } from "@api/auth";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { ModalInfoWhyCantCreate } from "./ModalInfoWhyCantCreate";
export default function EditPage() { export default function EditPage() {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { editQuizId } = useQuizStore(); const { editQuizId } = useQuizStore();
const { questions } = useQuestionsStore();
useEffect(() => { useEffect(() => {
const getData = async () => { const getData = async () => {
@ -56,7 +63,7 @@ export default function EditPage() {
getData(); getData();
}, []); }, []);
const { openBranchingPanel } = useUiTools(); const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools();
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const currentStep = useQuizStore((state) => state.currentStep); const currentStep = useQuizStore((state) => state.currentStep);
@ -74,11 +81,61 @@ export default function EditPage() {
() => () => { () => () => {
resetEditConfig(); resetEditConfig();
cleanQuestions(); cleanQuestions();
updateModalInfoWhyCantCreate(false)
}, },
[] []
); );
console.log(currentStep)
console.log(quizConfig)
const updateQuestionHint = useDebouncedCallback((questions: AnyTypedQuizQuestion[]) => {
const problems: any = {}
questions.forEach((question) => {
//Если не участвует в ветвлении, или безтиповый, или резулт - он нам не интересен
if (question.type === null
|| question.type === "result"
|| question.content.rule.parentId.length === 0) return
//если есть дети, но нет дефолта - логическая ошибка. Так нельзя
if (question.content.rule.children.length > 0 && question.content.rule.default.length === 0) {
problems[question.content.id] = {
name: question.title,
problems: ["Не выбран дефолтный вопрос"]
}
}
})
useUiTools.setState({ whyCantCreatePublic: problems })
if (Object.keys(problems).length > 0) {
updateQuiz(quiz?.id, (state) => { state.status = "stop" })
updateCanCreatePublic(false)
} else {
updateCanCreatePublic(true)
}
}, 600);
useEffect(() => {
updateQuestionHint(questions)
}, [questions]);
async function handleLogoutClick() {
const [, logoutError] = await logout();
if (logoutError) {
return enqueueSnackbar(logoutError);
}
clearAuthToken();
clearUserData();
navigate("/");
}
if (!quizConfig) return <></> if (!quizConfig) return <></>
return ( return (
@ -99,7 +156,7 @@ export default function EditPage() {
}} }}
> >
<Link to="/" style={{ display: "flex" }}> <Link to="/" style={{ display: "flex" }}>
{isMobile ? <PenaLogoIcon style={{ fontSize: "39px", color: "white" }} /> : <PenaLogo width={124} />} {isMobile ? <Logotip width={100} /> : <Logotip width={124} />}
</Link> </Link>
<Box <Box
sx={{ sx={{
@ -155,6 +212,7 @@ export default function EditPage() {
style={{ fontSize: "30px", color: "white", cursor: "pointer" }} style={{ fontSize: "30px", color: "white", cursor: "pointer" }}
/> />
) : ( ) : (
<Box>
<CustomAvatar <CustomAvatar
sx={{ sx={{
ml: "11px", ml: "11px",
@ -163,6 +221,7 @@ export default function EditPage() {
width: "36px", width: "36px",
}} }}
/> />
</Box>
)} )}
</Box> </Box>
) : ( ) : (
@ -196,6 +255,7 @@ export default function EditPage() {
width: "36px", width: "36px",
}} }}
/> />
<LogoutButton onClick={handleLogoutClick} />
</Box> </Box>
</> </>
)} )}
@ -230,6 +290,7 @@ export default function EditPage() {
</> </>
)} )}
</Box> </Box>
<Box <Box
sx={{ sx={{
position: "absolute", position: "absolute",
@ -304,20 +365,22 @@ export default function EditPage() {
</Box> </Box>
</Box> </Box>
)} )}
{disableTest ? (
{!canCreatePublic && quiz.config.type !== "form" ?
<Button <Button
variant="contained" variant="contained"
disabled // disabled
sx={{ sx={{
fontSize: "14px", fontSize: "14px",
lineHeight: "18px", lineHeight: "18px",
height: "34px", height: "34px",
minWidth: "130px", minWidth: "130px",
}} }}
onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => {} : updateModalInfoWhyCantCreate(true)}
> >
Тестовый просмотр Тестовый просмотр
</Button> </Button>
) : ( :
<a href={`/view`} target="_blank" rel="noreferrer" style={{ textDecoration: "none" }}> <a href={`/view`} target="_blank" rel="noreferrer" style={{ textDecoration: "none" }}>
<Button <Button
variant="contained" variant="contained"
@ -331,7 +394,7 @@ export default function EditPage() {
Тестовый просмотр Тестовый просмотр
</Button> </Button>
</a> </a>
)} }
<Button <Button
variant="outlined" variant="outlined"
@ -343,24 +406,28 @@ export default function EditPage() {
backgroundColor: quiz?.status === "start" ? theme.palette.brightPurple.main : "transparent", backgroundColor: quiz?.status === "start" ? theme.palette.brightPurple.main : "transparent",
color: quiz?.status === "start" ? "#FFFFFF" : theme.palette.brightPurple.main, color: quiz?.status === "start" ? "#FFFFFF" : theme.palette.brightPurple.main,
}} }}
onClick={() => { onClick={
updateQuiz( Object.keys(whyCantCreatePublic).length === 0 ?
quiz?.id, (state) => { () => updateQuiz(quiz?.id, (state) => {
state.status = quiz?.status === "start" ? "stop" : "start" state.status = quiz?.status === "start" ? "stop" : "start";
})
:
() => updateModalInfoWhyCantCreate(true)
} }
) >
}} {quiz?.status === "start" ? "Стоп" : "Старт"}
>{quiz?.status === "start" ? "Стоп" : "Старт"}</Button> </Button>
{quiz?.status === "start" && <Box {quiz?.status === "start" && <Box
component={Link} component={Link}
sx={{ sx={{
color: "#7e2aea", color: "#7e2aea",
fontSize: "14px" fontSize: "14px"
}} }}
target="_blank" to={"https://hbpn.link/" + quiz.qid}>https://hbpn.link/{quiz.qid}</Box> } target="_blank" to={"https://hbpn.link/" + quiz.qid}>https://hbpn.link/{quiz.qid}
</Box> </Box>}
</Box> </Box>
</Box >
<ModalInfoWhyCantCreate />
</> </>
); );
} }

@ -0,0 +1,49 @@
import { Box, Modal, Typography, Divider } from "@mui/material"
import { useUiTools } from "@root/uiTools/store";
import { updateModalInfoWhyCantCreate } from "@root/uiTools/actions";
import { useLayoutEffect } from "react";
export const ModalInfoWhyCantCreate = () => {
const { whyCantCreatePublic, openModalInfoWhyCantCreate } = useUiTools();
return (
<Modal
open={openModalInfoWhyCantCreate}
onClose={() => updateModalInfoWhyCantCreate(false)}
>
<Box sx={{
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
maxWidth: '620px',
width: '100%',
bgcolor: 'background.paper',
borderRadius: '12px',
boxShadow: 24,
p: "25px",
minHeight: "60vh",
maxHeight: "90vh",
overflow: "auto"
}}
>
{
Object.values(whyCantCreatePublic).map((data) => {
return (
<Box>
<Typography color="#7e2aea">У вопроса "{data.name}"</Typography>
{
data.problems.map((problem) => <Typography p="5px 0">{problem}</Typography>)
}
<Divider/>
</Box>
)
})
}
</Box>
</Modal>
)
}

@ -41,7 +41,6 @@ export const Restore: FC = () => {
initialValues, initialValues,
validationSchema, validationSchema,
onSubmit: (values) => { onSubmit: (values) => {
console.log(values);
}, },
}); });

@ -18,7 +18,7 @@ import {
Select, Select,
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";
@ -35,27 +35,13 @@ import SelectableIconButton from "./SelectableIconButton";
import { DropZone } from "./dropZone"; import { DropZone } from "./dropZone";
import Extra from "./extra"; import Extra from "./extra";
const designTypes = [ const designTypes = [
[ ["standard", (color: string) => <LayoutStandartIcon color={color} />, "Standard"],
"standard", ["expanded", (color: string) => <LayoutExpandedIcon color={color} />, "Expanded"],
(color: string) => <LayoutStandartIcon color={color} />, ["centered", (color: string) => <LayoutCenteredIcon color={color} />, "Centered"],
"Standard",
],
[
"expanded",
(color: string) => <LayoutExpandedIcon color={color} />,
"Expanded",
],
[
"centered",
(color: string) => <LayoutCenteredIcon color={color} />,
"Centered",
],
] as const; ] as const;
// type DesignType = typeof designTypes[number][0]; // type DesignType = typeof designTypes[number][0];
export default function StartPageSettings() { export default function StartPageSettings() {
const theme = useTheme(); const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500)); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
@ -75,14 +61,14 @@ export default function StartPageSettings() {
const favIconDropZoneElement = ( const favIconDropZoneElement = (
<FaviconDropZone <FaviconDropZone
imageUrl={quiz.config.startpage.favIcon} imageUrl={quiz.config.startpage.favIcon}
onImageUploadClick={async file => { onImageUploadClick={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;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.favIcon = null; quiz.config.startpage.favIcon = null;
}); });
}} }}
@ -91,10 +77,7 @@ export default function StartPageSettings() {
return ( return (
<> <>
<Typography <Typography variant="h5" sx={{ marginTop: "60px", marginBottom: isSmallMonitor ? "0" : "40px" }}>
variant="h5"
sx={{ marginTop: "60px", marginBottom: isSmallMonitor ? "0" : "40px" }}
>
Стартовая страница Стартовая страница
</Typography> </Typography>
{isSmallMonitor && ( {isSmallMonitor && (
@ -105,10 +88,7 @@ export default function StartPageSettings() {
fontWeight: 500, fontWeight: 500,
fontSize: "16px", fontSize: "16px",
color: formState === "design" ? "#7E2AEA" : "#7D7E86", color: formState === "design" ? "#7E2AEA" : "#7D7E86",
borderBottom: borderBottom: formState === "design" ? "2px solid #7E2AEA" : "1px solid transparent",
formState === "design"
? "2px solid #7E2AEA"
: "1px solid transparent",
}} }}
> >
Дизайн Дизайн
@ -120,10 +100,7 @@ export default function StartPageSettings() {
fontWeight: 500, fontWeight: 500,
fontSize: "16px", fontSize: "16px",
color: formState === "content" ? "#7E2AEA" : "#7D7E86", color: formState === "content" ? "#7E2AEA" : "#7D7E86",
borderBottom: borderBottom: formState === "content" ? "2px solid #7E2AEA" : "1px solid transparent",
formState === "content"
? "2px solid #7E2AEA"
: "1px solid transparent",
}} }}
> >
Контент Контент
@ -153,8 +130,7 @@ export default function StartPageSettings() {
<Box <Box
sx={{ sx={{
pr: "20px", pr: "20px",
borderRight: `1px solid ${isTablet ? "transparent" : theme.palette.grey2.main borderRight: `1px solid ${isTablet ? "transparent" : theme.palette.grey2.main}`,
}`,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
flex: `1 1 361px`, flex: `1 1 361px`,
@ -183,9 +159,11 @@ export default function StartPageSettings() {
variant="outlined" variant="outlined"
value={designType} value={designType}
displayEmpty displayEmpty
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpageType = e.target.value as QuizStartpageType; quiz.config.startpageType = e.target.value as QuizStartpageType;
})} })
}
sx={{ sx={{
height: "48px", height: "48px",
borderRadius: "8px", borderRadius: "8px",
@ -240,11 +218,7 @@ export default function StartPageSettings() {
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
}} }}
> >
{type[1]( {type[1](type[0] === designType ? theme.palette.orange.main : theme.palette.grey2.main)}
type[0] === designType
? theme.palette.orange.main
: theme.palette.grey2.main
)}
{type[2]} {type[2]}
</MenuItem> </MenuItem>
))} ))}
@ -268,17 +242,21 @@ export default function StartPageSettings() {
> >
<SelectableButton <SelectableButton
isSelected={quiz.config.startpage.background.type === "image"} isSelected={quiz.config.startpage.background.type === "image"}
onClick={() => updateQuiz(quiz.id, quiz => { onClick={() =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.background.type = "image"; quiz.config.startpage.background.type = "image";
})} })
}
> >
Изображение Изображение
</SelectableButton> </SelectableButton>
<SelectableButton <SelectableButton
isSelected={quiz.config.startpage.background.type === "video"} isSelected={quiz.config.startpage.background.type === "video"}
onClick={() => updateQuiz(quiz.id, quiz => { onClick={() =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.background.type = "video"; quiz.config.startpage.background.type = "video";
})} })
}
> >
Видео Видео
</SelectableButton> </SelectableButton>
@ -311,19 +289,19 @@ export default function StartPageSettings() {
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.startpage.background.desktop} imageUrl={quiz.config.startpage.background.desktop}
originalImageUrl={quiz.config.startpage.background.originalDesktop} originalImageUrl={quiz.config.startpage.background.originalDesktop}
onImageUploadClick={file => { onImageUploadClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.background.desktop = url; quiz.config.startpage.background.desktop = url;
quiz.config.startpage.background.originalDesktop = url; quiz.config.startpage.background.originalDesktop = url;
}); });
}} }}
onImageSaveClick={file => { onImageSaveClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.background.desktop = url; quiz.config.startpage.background.desktop = url;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.background.desktop = null; quiz.config.startpage.background.desktop = null;
}); });
}} }}
@ -331,8 +309,6 @@ export default function StartPageSettings() {
</Box> </Box>
<ModalSizeImage /> <ModalSizeImage />
</Box> </Box>
<Box <Box
@ -345,14 +321,15 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="URL видео" placeholder="URL видео"
text={quiz.config.startpage.background.video ?? ""} text={quiz.config.startpage.background.video ?? ""}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.background.video = e.target.value; quiz.config.startpage.background.video = e.target.value;
})} })
}
/> />
</Box> </Box>
{designType !== "centered" && {designType !== "centered" && (
<> <>
<Typography <Typography
sx={{ sx={{
@ -371,31 +348,36 @@ export default function StartPageSettings() {
}} }}
> >
<SelectableIconButton <SelectableIconButton
onClick={() => updateQuiz(quiz.id, quiz => { onClick={() =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.position = "left"; quiz.config.startpage.position = "left";
})} })
}
isActive={quiz.config.startpage.position === "left"} isActive={quiz.config.startpage.position === "left"}
Icon={AlignLeftIcon} Icon={AlignLeftIcon}
/> />
<SelectableIconButton <SelectableIconButton
onClick={() => updateQuiz(quiz.id, quiz => { onClick={() =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.position = "center"; quiz.config.startpage.position = "center";
})} })
}
isActive={quiz.config.startpage.position === "center"} isActive={quiz.config.startpage.position === "center"}
Icon={AlignCenterIcon} Icon={AlignCenterIcon}
sx={{ display: designType === "standard" ? "none" : "flex" }} sx={{ display: designType === "standard" ? "none" : "flex" }}
/> />
<SelectableIconButton <SelectableIconButton
onClick={() => updateQuiz(quiz.id, quiz => { onClick={() =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.position = "right"; quiz.config.startpage.position = "right";
})} })
}
isActive={quiz.config.startpage.position === "right"} isActive={quiz.config.startpage.position === "right"}
Icon={AlignRightIcon} Icon={AlignRightIcon}
/> />
</Box> </Box>
</> </>
)}
}
{(isTablet || !isSmallMonitor) && ( {(isTablet || !isSmallMonitor) && (
<> <>
<Box <Box
@ -418,19 +400,19 @@ export default function StartPageSettings() {
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.startpage.logo} imageUrl={quiz.config.startpage.logo}
originalImageUrl={quiz.config.startpage.originalLogo} originalImageUrl={quiz.config.startpage.originalLogo}
onImageUploadClick={file => { onImageUploadClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url; quiz.config.startpage.logo = url;
quiz.config.startpage.originalLogo = url; quiz.config.startpage.originalLogo = url;
}); });
}} }}
onImageSaveClick={file => { onImageSaveClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url; quiz.config.startpage.logo = url;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.logo = null; quiz.config.startpage.logo = null;
}); });
}} }}
@ -482,19 +464,19 @@ export default function StartPageSettings() {
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.startpage.logo} imageUrl={quiz.config.startpage.logo}
originalImageUrl={quiz.config.startpage.originalLogo} originalImageUrl={quiz.config.startpage.originalLogo}
onImageUploadClick={file => { onImageUploadClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url; quiz.config.startpage.logo = url;
quiz.config.startpage.originalLogo = url; quiz.config.startpage.originalLogo = url;
}); });
}} }}
onImageSaveClick={file => { onImageSaveClick={(file) => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.startpage.logo = url; quiz.config.startpage.logo = url;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.logo = null; quiz.config.startpage.logo = null;
}); });
}} }}
@ -528,9 +510,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="Имя заголовка об опроснике для подбора табуретки" placeholder="Имя заголовка об опроснике для подбора табуретки"
text={quiz.name} text={quiz.name}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.name = e.target.value; quiz.name = e.target.value;
})} })
}
maxLength={40}
/> />
<Typography <Typography
sx={{ sx={{
@ -545,9 +530,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="Внимательно заполняйте поля ответов" placeholder="Внимательно заполняйте поля ответов"
text={quiz.config.startpage.description} text={quiz.config.startpage.description}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.description = e.target.value; quiz.config.startpage.description = e.target.value;
})} })
}
maxLength={70}
/> />
<Typography <Typography
sx={{ sx={{
@ -562,9 +550,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="Начать опрос" placeholder="Начать опрос"
text={quiz.config.startpage.button} text={quiz.config.startpage.button}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.startpage.button = e.target.value; quiz.config.startpage.button = e.target.value;
})} })
}
maxLength={15}
/> />
<Typography <Typography
sx={{ sx={{
@ -579,17 +570,22 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="8-800-000-00-00" placeholder="8-800-000-00-00"
text={quiz.config.info.phonenumber} text={quiz.config.info.phonenumber}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.info.phonenumber = e.target.value; quiz.config.info.phonenumber = e.target.value;
})} })
}
maxLength={20}
/> />
<CustomCheckbox <CustomCheckbox
sx={{ margin: "10px 0" }} sx={{ margin: "10px 0" }}
label="Кликабельный" label="Кликабельный"
checked={quiz.config.info.clickable} checked={quiz.config.info.clickable}
handleChange={e => updateQuiz(quiz.id, quiz => { handleChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.info.clickable = e.target.checked; quiz.config.info.clickable = e.target.checked;
})} })
}
/> />
<Typography <Typography
sx={{ sx={{
@ -604,9 +600,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="Только лучшее" placeholder="Только лучшее"
text={quiz.config.info.orgname} text={quiz.config.info.orgname}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.info.orgname = e.target.value; quiz.config.info.orgname = e.target.value;
})} })
}
maxLength={25}
/> />
<Typography <Typography
sx={{ sx={{
@ -621,9 +620,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="https://mysite.com" placeholder="https://mysite.com"
text={quiz.config.info.site} text={quiz.config.info.site}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.info.site = e.target.value; quiz.config.info.site = e.target.value;
})} })
}
maxLength={25}
/> />
<Typography <Typography
sx={{ sx={{
@ -638,9 +640,12 @@ export default function StartPageSettings() {
<CustomTextField <CustomTextField
placeholder="Данные наших документов" placeholder="Данные наших документов"
text={quiz.config.info.law} text={quiz.config.info.law}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.info.law = e.target.value; quiz.config.info.law = e.target.value;
})} })
}
maxLength={45}
/> />
<Extra /> <Extra />
</> </>
@ -663,7 +668,7 @@ export default function StartPageSettings() {
textUnderlineOffset: "2px", textUnderlineOffset: "2px",
}} }}
> >
<ArrowLeft right={false}/> Вернуться к дизайну <ArrowLeft right={false} /> Вернуться к дизайну
</Button> </Button>
)} )}
{formState === "design" && ( {formState === "design" && (
@ -681,7 +686,7 @@ export default function StartPageSettings() {
textUnderlineOffset: "2px", textUnderlineOffset: "2px",
}} }}
> >
Далее заполнить контент <ArrowLeft right={true}/> Далее заполнить контент <ArrowLeft right={true} />
</Button> </Button>
)} )}
</Box> </Box>
@ -694,7 +699,7 @@ export default function StartPageSettings() {
justifyContent: "flex-end", justifyContent: "flex-end",
flexDirection: isTablet ? "column" : "row", flexDirection: isTablet ? "column" : "row",
marginTop: "30px", marginTop: "30px",
marginBottom: isTablet ? "90px" : undefined marginBottom: isTablet ? "90px" : undefined,
}} }}
> >
<FormControlLabel <FormControlLabel
@ -726,9 +731,11 @@ export default function StartPageSettings() {
borderRadius: 0, borderRadius: 0,
padding: 0, padding: 0,
}} }}
onChange={e => updateQuiz(quiz.id, quiz => { onChange={(e) =>
updateQuiz(quiz.id, (quiz) => {
quiz.config.noStartPage = e.target.checked; quiz.config.noStartPage = e.target.checked;
})} })
}
checked={quiz.config.noStartPage} checked={quiz.config.noStartPage}
/> />
} }
@ -740,11 +747,7 @@ export default function StartPageSettings() {
justifyContent: "flex-end", justifyContent: "flex-end",
}} }}
/> />
<Button <Button variant="contained" data-cy="setup-questions" onClick={incrementCurrentStep}>
variant="contained"
data-cy="setup-questions"
onClick={incrementCurrentStep}
>
Настроить вопросы Настроить вопросы
</Button> </Button>
</Box> </Box>

@ -297,7 +297,6 @@ export const uploadQuestionImage = async (
if (!question || !quizQid) return; if (!question || !quizQid) return;
try { try {
console.log(question.quizId)
const response = await quizApi.addImages(question.quizId, blob); const response = await quizApi.addImages(question.quizId, blob);
const values = Object.values(response); const values = Object.values(response);
@ -387,16 +386,13 @@ export const createTypedQuestion = async (
}); });
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => { export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
console.log("Я получил запрос на удаление. ИД - ", questionId)
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
console.log("delete question ", question)
if (!question) return; if (!question) return;
if (question.type === null) { if (question.type === null) {
console.log("removeQuestion")
removeQuestion(questionId); removeQuestion(questionId);
return; return;
} }

@ -179,7 +179,6 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async (
export const updateRootContentId = (quizId: string, id:string) => updateQuiz( export const updateRootContentId = (quizId: string, id:string) => updateQuiz(
quizId, quizId,
quiz => { quiz => {
console.log("Я изменение статуса корня проекта дерева, меняю на ", id)
quiz.config.haveRoot = id quiz.config.haveRoot = id
}, },
); );

@ -32,3 +32,8 @@ export const updateEditSomeQuestion = (contentId?: string) => {
export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState({ openedModalSettingsId: id ? id : null }); export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState({ openedModalSettingsId: id ? id : null });
export const updateCanCreatePublic = (can: boolean) => useUiTools.setState({ canCreatePublic: can });
export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can });

@ -8,7 +8,15 @@ export type UiTools = {
openBranchingPanel: boolean; openBranchingPanel: boolean;
desireToOpenABranchingModal: string | null; desireToOpenABranchingModal: string | null;
editSomeQuestion: string | null; editSomeQuestion: string | null;
canCreatePublic: boolean;
whyCantCreatePublic: Record<string, WhyCantCreatePublic>//ид вопроса и список претензий к нему
openModalInfoWhyCantCreate: boolean;
}; };
export type WhyCantCreatePublic = {
name: string;
problems: string[]
}
const initialState: UiTools = { const initialState: UiTools = {
openedModalSettingsId: null as null, openedModalSettingsId: null as null,
@ -16,6 +24,9 @@ const initialState: UiTools = {
openBranchingPanel: false, openBranchingPanel: false,
desireToOpenABranchingModal: null as null, desireToOpenABranchingModal: null as null,
editSomeQuestion: null as null, editSomeQuestion: null as null,
canCreatePublic: false,
whyCantCreatePublic: {},
openModalInfoWhyCantCreate: false
}; };
export const useUiTools = create<UiTools>()( export const useUiTools = create<UiTools>()(

@ -1,4 +1,4 @@
import { User } from "@frontend/kitui"; import { User, UserAccount } from "@frontend/kitui";
import { produce } from "immer"; import { produce } from "immer";
import { create } from "zustand"; import { create } from "zustand";
import { createJSONStorage, devtools, persist } from "zustand/middleware"; import { createJSONStorage, devtools, persist } from "zustand/middleware";
@ -6,12 +6,13 @@ import { createJSONStorage, devtools, persist } from "zustand/middleware";
interface UserStore { interface UserStore {
userId: string | null; userId: string | null;
user: User | null; user: User | null;
// userAccount: UserAccount | null; userAccount: UserAccount | null;
} }
const initialState: UserStore = { const initialState: UserStore = {
userId: null, userId: null,
user: null, user: null,
userAccount: null,
}; };
export const useUserStore = create<UserStore>()( export const useUserStore = create<UserStore>()(
@ -19,12 +20,11 @@ export const useUserStore = create<UserStore>()(
devtools((set, get) => initialState, { devtools((set, get) => initialState, {
name: "User", name: "User",
enabled: process.env.NODE_ENV === "development", enabled: process.env.NODE_ENV === "development",
trace: true, trace: process.env.NODE_ENV === "development",
}), }),
{ {
version: 2, version: 2,
name: "user", name: "user",
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ partialize: (state) => ({
userId: state.userId, userId: state.userId,
user: state.user, user: state.user,
@ -46,3 +46,5 @@ export const setUser = (user: User) =>
); );
export const clearUserData = () => useUserStore.setState({ ...initialState }); export const clearUserData = () => useUserStore.setState({ ...initialState });
export const setUserAccount = (userAccount: UserAccount) => useUserStore.setState({ userAccount });

@ -1,7 +1,7 @@
import { FormControl, TextField, useTheme, SxProps, Theme } from "@mui/material"; import React, { useState } from "react";
import { Box, FormControl, TextField, Typography, useTheme } from "@mui/material";
import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react"; import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react";
import type { InputProps } from "@mui/material"; import type { InputProps, SxProps, Theme } from "@mui/material";
interface CustomTextFieldProps { interface CustomTextFieldProps {
placeholder: string; placeholder: string;
@ -11,6 +11,7 @@ interface CustomTextFieldProps {
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void; onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void; onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
text?: string; text?: string;
maxLength?: number;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
InputProps?: Partial<InputProps>; InputProps?: Partial<InputProps>;
} }
@ -18,28 +19,54 @@ interface CustomTextFieldProps {
export default function CustomTextField({ export default function CustomTextField({
placeholder, placeholder,
value, value,
text,
sx,
error,
onChange, onChange,
onKeyDown, onKeyDown,
onBlur, onBlur,
text,
sx,
error,
InputProps, InputProps,
maxLength = 200,
}: CustomTextFieldProps) { }: CustomTextFieldProps) {
const theme = useTheme(); const theme = useTheme();
const [inputValue, setInputValue] = useState(value || text || "");
const [isInputActive, setIsInputActive] = useState(false);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = event.target.value;
setInputValue(inputValue);
if (onChange) {
onChange(event);
}
};
const handleInputFocus = () => {
setIsInputActive(true);
};
const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setIsInputActive(false);
if (onBlur) {
onBlur(event);
}
};
return ( return (
<FormControl fullWidth variant="standard" sx={{ p: 0 }}> <FormControl fullWidth variant="standard" sx={{ p: 0 }}>
<TextField <TextField
defaultValue={text} defaultValue={text}
fullWidth fullWidth
value={value} value={inputValue}
placeholder={placeholder} placeholder={placeholder}
onChange={handleInputChange}
error={!!error} error={!!error}
label={error} label={error}
onChange={onChange} onFocus={handleInputFocus}
onBlur={handleInputBlur}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onBlur={onBlur}
sx={{ sx={{
"& .MuiInputBase-root": { "& .MuiInputBase-root": {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
@ -50,19 +77,36 @@ export default function CustomTextField({
fontSize: "13.5px", fontSize: "13.5px",
marginTop: "3px", marginTop: "3px",
}, },
...sx,
}} }}
InputProps={InputProps} InputProps={InputProps}
inputProps={{ inputProps={{
maxLength: maxLength,
sx: { sx: {
borderRadius: "10px", borderRadius: "10px",
fontSize: "18px", fontSize: "18px",
lineHeight: "21px", lineHeight: "21px",
py: 0, py: 0,
}, },
...sx,
}} }}
data-cy="textfield" data-cy="textfield"
/> />
{isInputActive && inputValue.length >= maxLength - 7 && (
<Box
sx={{
display: "flex",
marginTop: "5px",
marginLeft: "auto",
position: "absolute",
bottom: "-25px",
right: "0",
}}
>
<Typography fontSize="14px">{inputValue.length}</Typography>
<span>/</span>
<Typography fontSize="14px">{maxLength}</Typography>
</Box>
)}
</FormControl> </FormControl>
); );
} }

@ -18,6 +18,7 @@ import { FC, useEffect, useMemo, useRef, useState } from "react";
import ReactCrop, { PercentCrop, PixelCrop } from "react-image-crop"; import ReactCrop, { PercentCrop, PixelCrop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css"; import "react-image-crop/dist/ReactCrop.css";
import { getCroppedImageBlob, getDarkenedAndResizedImageBlob, getRotatedImageBlob } from "./utils/imageManipulation"; import { getCroppedImageBlob, getDarkenedAndResizedImageBlob, getRotatedImageBlob } from "./utils/imageManipulation";
import { isImageBlobAGifFile } from "../../utils/isImageBlobAGifFile";
const styleSlider: SxProps<Theme> = { const styleSlider: SxProps<Theme> = {
@ -324,6 +325,9 @@ export function useCropModalState(initialOpenState = false) {
image = await response.blob(); image = await response.blob();
} }
const isGif = await isImageBlobAGifFile(image);
if (isGif) return;
setCropModalImageBlob(image); setCropModalImageBlob(image);
setOriginalImageUrl(originalImageUrl); setOriginalImageUrl(originalImageUrl);
setOpened(true); setOpened(true);

@ -0,0 +1,23 @@
import Info from "@icons/Info";
interface Props {
blink: boolean;
onClick: (a: boolean) => void;
}
export const ProblemIcon = ({ blink, onClick }: Props) => {
return (
<Info
sx={{
"MuiIconButton-root": {
boxShadow: "0 0 10px 10px red"
}
}}
className={blink ? "blink" : ""}
onClick={onClick}
/>
)
}

@ -24,6 +24,7 @@ export default function SwitchStepPages({
quizStartPageType, quizStartPageType,
quizResults, quizResults,
}: Props) { }: Props) {
console.log("Выбор текущей странички")
switch (activeStep) { switch (activeStep) {
case 0: { case 0: {
if (!quizType) return <StepOne />; if (!quizType) return <StepOne />;

@ -0,0 +1,15 @@
const gifSignature = [0x47, 0x49, 0x46, 0x38];
export async function isImageBlobAGifFile(blob: Blob) {
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
if (
uint8Array[0] === gifSignature[0]
&& uint8Array[1] === gifSignature[1]
&& uint8Array[2] === gifSignature[2]
&& uint8Array[3] === gifSignature[3]
) return true;
return false;
}

@ -1407,10 +1407,10 @@
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz" resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz"
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
"@frontend/kitui@^1.0.54": "@frontend/kitui@^1.0.55":
version "1.0.54" version "1.0.55"
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.54.tgz#0235d5a8effb9b92351471c3c7775f28cb2839f6" resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.55.tgz#6e02f80b2c13828142ffeacaf9704e50e54324d0"
integrity sha1-AjXVqO/7m5I1FHHDx3dfKMsoOfY= integrity sha1-bgL4CywTgoFC/+rK+XBOUOVDJNA=
dependencies: dependencies:
immer "^10.0.2" immer "^10.0.2"
reconnecting-eventsource "^1.6.2" reconnecting-eventsource "^1.6.2"