Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squiz!339
This commit is contained in:
Nastya 2024-06-09 16:51:38 +00:00
commit 8dbdcd5385
55 changed files with 23601 additions and 702 deletions

21621
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

@ -55,6 +55,7 @@
"react-scripts": "5.0.1",
"react-slick": "^0.29.0",
"slick-carousel": "^1.8.1",
"swiper": "^11.1.4",
"swr": "^2.2.4",
"typescript": "^5.2.2",
"use-debounce": "^9.0.4",
@ -108,6 +109,7 @@
"endOfLine": "auto",
"bracketSpacing": true,
"arrowParens": "always",
"jsxSingleQuote": false
"jsxSingleQuote": false,
"singleAttributePerLine": true
}
}

@ -108,7 +108,7 @@ export default function App() {
});
useUserAccountFetcher<UserAccount>({
url: `${process.env.REACT_APP_DOMAIN}/customer/account`,
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0/account`,
userId,
onNewUserAccount: setCustomerAccount,
onError: (error) => {

@ -4,7 +4,7 @@ import { parseAxiosError } from "@utils/parse-error";
import type { UserAccount } from "@frontend/kitui";
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/cart`;
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0/cart`;
const payCart = async (): Promise<[UserAccount | null, string?]> => {
try {

@ -13,7 +13,7 @@ export const getUser = async (): Promise<[UserAccount | null, string?]> => {
try {
const user = await makeRequest<never, UserAccount>({
method: "GET",
url: `${process.env.REACT_APP_DOMAIN}/customer/account`,
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.0/account`,
});
return [user];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 119 KiB

@ -0,0 +1,22 @@
import { FC } from "react";
import { Box } from "@mui/material";
export const AlignIcon: FC = () => (
<Box sx={{ width: `20px`, height: "20px" }}>
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 8.18348L11.8 8.22299V1M8.2 19V11.8165L1 11.7752"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
import { FC } from "react";
import { Box } from "@mui/material";
export const ExpandIcon: FC = () => (
<Box sx={{ width: `24px`, height: "24px" }}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6.58545L12.4984 2.17075C12.434 2.11677 12.3566 2.07382 12.271 2.04447C12.1853 2.01513 12.0931 2 12 2C11.9069 2 11.8147 2.01513 11.729 2.04447C11.6434 2.07382 11.566 2.11677 11.5016 2.17075L6 6.58545M18 17.4146L12.4984 21.8293C12.434 21.8832 12.3566 21.9262 12.271 21.9555C12.1853 21.9849 12.0931 22 12 22C11.9069 22 11.8147 21.9849 11.729 21.9555C11.6434 21.9262 11.566 21.8832 11.5016 21.8293L6 17.4146"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.9988 14.0054C13.0682 14.0054 13.9351 13.1385 13.9351 12.0691C13.9351 10.9997 13.0682 10.1328 11.9988 10.1328C10.9294 10.1328 10.0625 10.9997 10.0625 12.0691C10.0625 13.1385 10.9294 14.0054 11.9988 14.0054Z"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);

@ -0,0 +1,29 @@
import { FC } from "react";
import { Box } from "@mui/material";
export const GrayPlus: FC = () => (
<Box sx={{ width: `32px` }}>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.0029 1V31"
stroke="#9A9AAF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M31 15.9941L1 15.9941"
stroke="#9A9AAF"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);

@ -0,0 +1,38 @@
import { FC } from "react";
import { Box } from "@mui/material";
export const RoundedCheckedIcon: FC = () => (
<Box
sx={{
width: `26px`,
height: "26px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg
width="26"
height="27"
viewBox="0 0 26 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="0.5"
y="1"
width="25"
height="25"
rx="12.5"
fill="#F2F3F7"
stroke="#F2F3F7"
/>
<path
d="M8 13.5L12.2857 17.5L18 10"
stroke="#9A9AAF"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);

@ -15,12 +15,16 @@ import {
Step,
Tag,
} from "@api/integration";
import type { TagQuestionObject } from "@/pages/IntegrationsPage/IntegrationsModal/AmoCRMModal"
type CustomRadioGroupProps = {
type?: string;
selectedValue: string | null;
setSelectedValue: (value: string | null) => void;
pipelineId?: number | null;
items: TagQuestionObject[]
tags: Tag[]
setTags?: (setValueFunc: (value: Tag[]) => Tag[]) => void;
};
const SIZE = 25;
@ -30,6 +34,9 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
selectedValue,
setSelectedValue,
pipelineId,
items,
tags = [],
setTags,
}) => {
const theme = useTheme();
@ -40,13 +47,15 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const [tags, setTags] = useState<Tag[]>([]);
const [steps, setSteps] = useState<Step[]>([]);
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
const [hasMoreItems, setHasMoreItems] = useState(true);
const boxRef = useRef(null);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log("применился хенделчейндж")
console.log((event.target as HTMLInputElement).value)
setSelectedValue((event.target as HTMLInputElement).value);
setCurrentValue((event.target as HTMLInputElement).value);
};
@ -61,9 +70,12 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
setPage((prevPage) => prevPage + 1);
}
};
console.log(type)
console.log(items)
console.log(type === "typeQuestions" && items && items.length !== 0)
useEffect(() => {
if (type === "typeTags" && hasMoreItems) {
if (type === "typeTags" && hasMoreItems && setTags !== undefined) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
@ -71,7 +83,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
};
getTags(pagination).then(([response]) => {
if (response && response.items !== null) {
setTags((prevItems) => [...prevItems, ...response.items]);
setTags((prevItems:Tag[]) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
@ -118,9 +130,8 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
if (type === "typeTags" && tags && tags.length !== 0) {
return tags.map((item) => (
<FormControlLabel
key={item.ID}
key={item.AmoID}
sx={{
color: "red",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
@ -128,11 +139,11 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === item.Name
currentValue === item.AmoID.toString()
? theme.palette.background.default
: theme.palette.common.white,
}}
value={item.Name}
value={item.AmoID.toString()}
control={
<Radio
checkedIcon={
@ -232,6 +243,46 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
/>
));
}
if (type === "typeQuestions" && items && items.length !== 0) {
return items.map(({ backendId, title }) => (
<FormControlLabel
key={backendId}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === backendId
? theme.palette.background.default
: theme.palette.common.white,
"&.MuiFormControlLabel-root > .MuiTypography-root": {
width: "200px",
overflow: "hidden",
textOverflow: "ellipsis"
}
}}
value={backendId}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={title}
labelPlacement={"start"}
/>
));
}
return (
<Box
sx={{

@ -1,16 +1,10 @@
import { WidgetType } from "@frontend/squzanswerer";
import {
Box,
Button,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useState } from "react";
import InstallationStepButton from "./InstallationStepButton";
import BannerWidgetSetup from "./WidgetSetupByType/BannerWidgetSetup/BannerWidgetSetup";
import ButtonWidgetSetup from "./WidgetSetupByType/ButtonWidgetSetup";
import ButtonWidgetSetup from "./WidgetSetupByType/ButtonWidgetSetup/ButtonWidgetSetup";
import ContainerWidgetSetup from "./WidgetSetupByType/ContainerWidgetSetup";
import PopupWidgetSetup from "./WidgetSetupByType/PopupWidgetSetup";
import SideWidgetSetup from "./WidgetSetupByType/SideWidgetSetup/SideWidgetSetup";
@ -30,16 +24,15 @@ export default function QuizInstallationCard() {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const [widgetSetupSettings, setWidgetSetupSettings] =
useState<WidgetSetupSettings>({ step: 1, widgetType: "container" });
const [widgetSetupSettings, setWidgetSetupSettings] = useState<WidgetSetupSettings>({
step: 1,
widgetType: "container",
});
if (!quiz) return null;
const nextButton = (
<Button
onClick={() => setWidgetSetupSettings((prev) => ({ ...prev, step: 3 }))}
variant="contained"
>
<Button onClick={() => setWidgetSetupSettings((prev) => ({ ...prev, step: 3 }))} variant="contained">
Далее
</Button>
);
@ -187,34 +180,19 @@ export default function QuizInstallationCard() {
) : (
<>
{widgetSetupSettings.widgetType === "container" && (
<ContainerWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
<ContainerWidgetSetup step={widgetSetupSettings.step} nextButton={nextButton} />
)}
{widgetSetupSettings.widgetType === "button" && (
<ButtonWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
<ButtonWidgetSetup step={widgetSetupSettings.step} nextButton={nextButton} />
)}
{widgetSetupSettings.widgetType === "banner" && (
<BannerWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
<BannerWidgetSetup step={widgetSetupSettings.step} nextButton={nextButton} />
)}
{widgetSetupSettings.widgetType === "popup" && (
<PopupWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
<PopupWidgetSetup step={widgetSetupSettings.step} nextButton={nextButton} />
)}
{widgetSetupSettings.widgetType === "side" && (
<SideWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
<SideWidgetSetup step={widgetSetupSettings.step} nextButton={nextButton} />
)}
</>
)}

@ -0,0 +1,157 @@
import TaskIcon from "@/ui_kit/TaskIcon";
import CloseIcon from "@mui/icons-material/Close";
import { Box, Typography } from "@mui/material";
import RunningStripe from "@ui_kit/RunningStripe";
interface Props {
position: "topleft" | "topright" | "bottomleft" | "bottomright";
bannerFullWidth: boolean;
pulsation: boolean;
rounded: boolean;
withShadow: boolean;
buttonFlash: boolean;
buttonBackgroundColor: string;
buttonTextColor: string;
appealText: string;
quizHeaderText: string;
}
export default function BannerWidgetPreviewDesktop({
bannerFullWidth,
buttonBackgroundColor,
buttonFlash,
buttonTextColor,
position,
pulsation,
rounded,
withShadow,
appealText,
quizHeaderText,
}: Props) {
return (
<Box
sx={[
{
position: "absolute",
width: "calc(196 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
transitionProperty: "width, height, top, left",
transitionDuration: "0.5s",
transitionTimingFunction: "ease",
},
position === "topleft" && {
top: "calc(12.5 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
},
position === "topright" && {
top: "calc(12.5 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
position === "bottomleft" && {
top: "calc(178 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
},
position === "bottomright" && {
top: "calc(178 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
bannerFullWidth &&
position.startsWith("top") && {
top: "calc(7 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
bannerFullWidth &&
position.startsWith("bottom") && {
top: "calc(185 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
pulsation && {
":before": {
content: "''",
position: "absolute",
height: "100%",
width: "100%",
pointerEvents: "none",
willChange: "box-shadow",
borderRadius: "30px",
animation: "pena-pulsation linear 5s infinite",
"@keyframes pena-pulsation": {
"0%": {
boxShadow: "0 0 0 0 rgba(126, 42, 234, 0.5)",
},
"30%": {
boxShadow: "0 0 0 15px rgba(0, 0, 0, 0)",
},
"100%": {
boxShadow: "0 0 0 0 rgba(0, 0, 0, 0)",
},
},
},
},
]}
>
<Box
sx={{
width: "100%",
height: "100%",
overflow: "hidden",
display: "flex",
alignItems: "center",
backgroundColor: buttonBackgroundColor,
containerType: "size",
gap: "calc(5 / 196 * 100%)",
borderRadius: rounded && !bannerFullWidth ? "30px" : 0,
boxShadow: withShadow ? "3px 6px 25px 3px rgba(25, 6, 50, 0.4), 0 3px 13px 0 rgba(35, 17, 58, 0.1)" : "none",
}}
>
<TaskIcon
sx={{
width: "auto",
height: "calc(18.57 / 29.5 * 100%)",
ml: "calc(7.36 / 196 * 100%)",
aspectRatio: 1,
color: buttonTextColor,
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "column",
color: buttonTextColor,
}}
>
<Typography
fontSize="calc(6 / 29.5 * 100cqh)"
lineHeight="120%"
>
{appealText || "Пройти тест"}
</Typography>
<Typography
fontSize="calc(11 / 29.5 * 100cqh)"
lineHeight="120%"
>
{quizHeaderText || "Заголовок теста"}
</Typography>
</Box>
{buttonFlash && <RunningStripe />}
</Box>
<CloseIcon
sx={{
position: "absolute",
top: 0,
right: 0,
color: "white",
height: "calc(10 / 29.5 * 100%)",
width: "auto",
aspectRatio: 1,
backgroundColor: rounded || bannerFullWidth ? "#581ca763" : undefined,
borderRadius: "50%",
}}
/>
</Box>
);
}

@ -0,0 +1,128 @@
import TaskIcon from "@/ui_kit/TaskIcon";
import CloseIcon from "@mui/icons-material/Close";
import { Box, Typography } from "@mui/material";
import RunningStripe from "@ui_kit/RunningStripe";
interface Props {
position: "topleft" | "topright" | "bottomleft" | "bottomright";
pulsation: boolean;
withShadow: boolean;
buttonFlash: boolean;
buttonBackgroundColor: string;
buttonTextColor: string;
appealText: string;
quizHeaderText: string;
}
export default function BannerWidgetPreviewMobile({
buttonBackgroundColor,
buttonFlash,
buttonTextColor,
position,
pulsation,
withShadow,
appealText,
quizHeaderText,
}: Props) {
return (
<Box
sx={[
{
position: "absolute",
width: "calc(94 / 520 * 100%)",
height: "calc(21 / 262 * 100%)",
transitionProperty: "width, height, top, left",
transitionDuration: "0.5s",
transitionTimingFunction: "ease",
},
position.startsWith("top") && {
top: "calc(66 / 262 * 100%)",
left: "calc(422 / 520 * 100%)",
},
position.startsWith("bottom") && {
top: "calc(208 / 262 * 100%)",
left: "calc(422 / 520 * 100%)",
},
pulsation && {
":before": {
content: "''",
position: "absolute",
height: "100%",
width: "100%",
pointerEvents: "none",
willChange: "box-shadow",
borderRadius: "30px",
animation: "pena-pulsation linear 5s infinite",
"@keyframes pena-pulsation": {
"0%": {
boxShadow: "0 0 0 0 rgba(126, 42, 234, 0.5)",
},
"30%": {
boxShadow: "0 0 0 15px rgba(0, 0, 0, 0)",
},
"100%": {
boxShadow: "0 0 0 0 rgba(0, 0, 0, 0)",
},
},
},
},
]}
>
<Box
sx={{
width: "100%",
height: "100%",
overflow: "hidden",
display: "flex",
alignItems: "center",
backgroundColor: buttonBackgroundColor,
containerType: "size",
gap: "calc(5 / 196 * 100%)",
boxShadow: withShadow ? "3px 6px 25px 3px rgba(25, 6, 50, 0.4), 0 3px 13px 0 rgba(35, 17, 58, 0.1)" : "none",
}}
>
<TaskIcon
sx={{
width: "auto",
height: "calc(18.57 / 29.5 * 100%)",
ml: "calc(7.36 / 196 * 100%)",
aspectRatio: 1,
color: buttonTextColor,
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "column",
color: buttonTextColor,
}}
>
<Typography
fontSize="calc(6 / 29.5 * 100cqh)"
lineHeight="120%"
>
{appealText || "Пройти тест"}
</Typography>
<Typography
fontSize="calc(11 / 29.5 * 100cqh)"
lineHeight="120%"
>
{quizHeaderText || "Заголовок теста"}
</Typography>
</Box>
{buttonFlash && <RunningStripe />}
</Box>
<CloseIcon
sx={{
position: "absolute",
top: 0,
right: 0,
color: "white",
height: "calc(8 / 29.5 * 100%)",
width: "auto",
aspectRatio: 1,
}}
/>
</Box>
);
}

@ -1,5 +1,4 @@
import { BannerWidgetParams } from "@frontend/squzanswerer";
import CloseIcon from "@mui/icons-material/Close";
import {
Box,
Button,
@ -19,12 +18,13 @@ import CustomCheckbox from "@ui_kit/CustomCheckbox";
import PenaTextField from "@ui_kit/PenaTextField";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
import RunningStripe from "@ui_kit/RunningStripe";
import { ReactNode, useState } from "react";
import fixedButtonWidgetPreview from "../../../../../assets/banner-widget-preview.png";
import fixedBannerWidgetPreview from "../../../../../assets/banner-widget-preview.png";
import WidgetScript from "../../WidgetScript";
import { createBannerWidgetScriptText } from "../../createWidgetScriptText";
import BannerWidgetPreviewIcon from "./BannerWidgetPreviewIcon";
import { useWidgetUrl } from "../../useWidgetUrl";
import BannerWidgetPreviewDesktop from "./BannerWidgetPreviewDesktop";
import BannerWidgetPreviewMobile from "./BannerWidgetPreviewMobile";
interface Props {
step: 2 | 3;
@ -35,6 +35,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const widgetUrl = useWidgetUrl();
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] = useState<boolean>(false);
@ -47,7 +48,7 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false);
const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10);
const [openOnLeaveAttempt, setOpenOnLeaveAttempt] = useState<boolean>(false);
const [position, setPosition] = useState<BannerWidgetParams["position"]>("bottomleft");
const [position, setPosition] = useState<BannerWidgetParams["position"]>("topleft");
const [bannerFullWidth, setBannerFullWidth] = useState<boolean>(false);
const [autoShowWidgetTime, setAutoShowWidgetTime] = useState<number>(10);
const [appealText, setAppealText] = useState<string>("");
@ -58,31 +59,34 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
if (!quiz) return null;
if (step === 3) {
const scriptText = createBannerWidgetScriptText({
quizId: quiz.qid,
position,
hideOnMobile: hideOnMobile || undefined,
rounded: bannerFullWidth ? undefined : rounded || undefined,
withShadow: withShadow || undefined,
buttonFlash: buttonFlash || undefined,
buttonBackgroundColor,
buttonTextColor,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
bannerFullWidth: bannerFullWidth || undefined,
autoShowWidgetTime,
appealText: appealText || undefined,
quizHeaderText: quizHeaderText || undefined,
pulsation: pulsation || undefined,
fullScreen: fullScreen || undefined,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
});
const scriptText = createBannerWidgetScriptText(
{
quizId: quiz.qid,
position,
hideOnMobile: hideOnMobile || undefined,
rounded: bannerFullWidth ? undefined : rounded || undefined,
withShadow: withShadow || undefined,
buttonFlash: buttonFlash || undefined,
buttonBackgroundColor,
buttonTextColor,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
bannerFullWidth: bannerFullWidth || undefined,
autoShowWidgetTime,
appealText: appealText || undefined,
quizHeaderText: quizHeaderText || undefined,
pulsation: pulsation || undefined,
fullScreen: fullScreen || undefined,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
},
widgetUrl
);
return <WidgetScript scriptText={scriptText} helperText="Установите код внутрь тэга head" />;
}
@ -109,129 +113,35 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
}}
>
<img
src={fixedButtonWidgetPreview}
src={fixedBannerWidgetPreview}
style={{
display: "block",
width: "100%",
height: "100%",
}}
/>
<Box
sx={[
{
position: "absolute",
width: "calc(196 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
position === "topleft" && {
top: "calc(12.5 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
},
position === "topright" && {
top: "calc(12.5 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
position === "bottomleft" && {
top: "calc(178 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
},
position === "bottomright" && {
top: "calc(178 / 262 * 100%)",
left: "calc(190 / 520 * 100%)",
},
bannerFullWidth &&
position.startsWith("top") && {
top: "calc(7 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
bannerFullWidth &&
position.startsWith("bottom") && {
top: "calc(185 / 262 * 100%)",
left: "calc(45 / 520 * 100%)",
width: "calc(348 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
pulsation && {
":before": {
content: "''",
position: "absolute",
height: "100%",
width: "100%",
pointerEvents: "none",
willChange: "box-shadow",
borderRadius: "30px",
animation: "pena-pulsation linear 5s infinite",
"@keyframes pena-pulsation": {
"0%": {
boxShadow: "0 0 0 0 rgba(126, 42, 234, 0.5)",
},
"30%": {
boxShadow: "0 0 0 15px rgba(0, 0, 0, 0)",
},
"100%": {
boxShadow: "0 0 0 0 rgba(0, 0, 0, 0)",
},
},
},
},
]}
>
<Box
sx={{
width: "100%",
height: "100%",
overflow: "hidden",
display: "flex",
alignItems: "center",
backgroundColor: theme.palette.brightPurple.main,
containerType: "size",
gap: "calc(5 / 196 * 100%)",
borderRadius: rounded && !bannerFullWidth ? "30px" : 0,
boxShadow: withShadow
? "3px 6px 25px 3px rgba(25, 6, 50, 0.4), 0 3px 13px 0 rgba(35, 17, 58, 0.1)"
: "none",
}}
>
<BannerWidgetPreviewIcon
sx={{
height: "calc(18.57 / 29.5 * 100%)",
width: "auto",
aspectRatio: 1,
ml: "calc(7.36 / 196 * 100%)",
}}
/>
<Box
sx={{
display: "flex",
flexDirection: "column",
color: "white",
}}
>
<Typography fontSize="calc(6 / 29.5 * 100cqh)" lineHeight="120%">
{appealText || "Пройти тест"}
</Typography>
<Typography fontSize="calc(11 / 29.5 * 100cqh)" lineHeight="120%">
{quizHeaderText || "Заголовок теста"}
</Typography>
</Box>
{buttonFlash && <RunningStripe />}
</Box>
<CloseIcon
sx={{
position: "absolute",
top: 0,
right: 0,
color: "white",
height: "calc(10 / 29.5 * 100%)",
width: "auto",
aspectRatio: 1,
backgroundColor: rounded || bannerFullWidth ? "#581CA7" : undefined,
borderRadius: "50%",
}}
/>
</Box>
<BannerWidgetPreviewDesktop
appealText={appealText}
quizHeaderText={quizHeaderText}
position={position}
pulsation={pulsation}
withShadow={withShadow}
buttonFlash={buttonFlash}
bannerFullWidth={bannerFullWidth}
buttonBackgroundColor={buttonBackgroundColor}
buttonTextColor={buttonTextColor}
rounded={rounded}
/>
<BannerWidgetPreviewMobile
appealText={appealText}
quizHeaderText={quizHeaderText}
position={position}
pulsation={pulsation}
withShadow={withShadow}
buttonFlash={buttonFlash}
buttonBackgroundColor={buttonBackgroundColor}
buttonTextColor={buttonTextColor}
/>
</Box>
</Box>
<Box
@ -327,9 +237,9 @@ export default function BannerWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет кнопки</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет баннера</Typography>
<CircleColorPicker color={buttonBackgroundColor} onChange={(color) => setButtonBackgroundColor(color)} />
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста кнопки</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста баннера</Typography>
<CircleColorPicker color={buttonTextColor} onChange={(color) => setButtonTextColor(color)} />
</Box>
<CustomCheckbox

@ -0,0 +1,73 @@
import RunningStripe from "@/ui_kit/RunningStripe";
import TaskIcon from "@/ui_kit/TaskIcon";
import { Box, Typography } from "@mui/material";
interface Props {
buttonBackgroundColor: string;
rounded: boolean;
withShadow: boolean;
fixedSide: "left" | "right";
buttonTextColor: string;
buttonText: string;
buttonFlash: boolean;
}
export default function ButtonWidgetPreviewDesktop({
buttonBackgroundColor,
buttonFlash,
buttonText,
buttonTextColor,
fixedSide,
rounded,
withShadow,
}: Props) {
return (
<Box
sx={[
{
position: "absolute",
height: "calc(20 / 262 * 100%)",
px: "calc(1%)",
display: "flex",
alignItems: "center",
backgroundColor: buttonBackgroundColor,
borderRadius: rounded ? "30px" : 0,
transform: "rotate(-90deg)",
transformOrigin: "left",
transitionProperty: "width, height, top, left",
transitionDuration: "0.5s",
transitionTimingFunction: "ease",
whiteSpace: "nowrap",
overflow: "hidden",
boxShadow: withShadow ? "2px 5px 20px 2px rgba(25, 6, 50, 0.4), 0 2px 10px 0 rgba(35, 17, 58, 0.1)" : "none",
},
fixedSide === "left" && {
top: "calc(57%)",
left: "calc(55 / 520 * 100%)",
},
fixedSide === "right" && {
top: "calc(57%)",
left: "calc(383 / 520 * 100%)",
},
]}
>
<TaskIcon
sx={{
width: "auto",
height: "calc(18.57 / 29.5 * 100%)",
aspectRatio: 1,
color: buttonTextColor,
}}
/>
<Typography
fontSize="calc(3cqh)"
lineHeight="120%"
ml="3px"
color={buttonTextColor}
>
{buttonText || "Пройти тест"}
</Typography>
{buttonFlash && <RunningStripe />}
</Box>
);
}

@ -0,0 +1,47 @@
import TaskIcon from "@/ui_kit/TaskIcon";
import { Box } from "@mui/material";
interface Props {
fixedSide: "left" | "right";
buttonBackgroundColor: string;
buttonTextColor: string;
}
export default function ButtonWidgetPreviewMobile({ buttonBackgroundColor, fixedSide, buttonTextColor }: Props) {
return (
<Box
sx={[
{
position: "absolute",
width: "calc(22 / 520 * 100%)",
height: "calc(22 / 262 * 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: buttonBackgroundColor,
borderRadius: "50%",
transitionProperty: "width, height, top, left",
transitionDuration: "0.5s",
transitionTimingFunction: "ease",
},
fixedSide === "left" && {
top: "calc(88%)",
left: "calc(82%)",
},
fixedSide === "right" && {
top: "calc(88%)",
left: "calc(94%)",
},
]}
>
<TaskIcon
sx={{
width: "auto",
height: "calc(80%)",
aspectRatio: 1,
color: buttonTextColor,
}}
/>
</Box>
);
}

@ -1,7 +1,4 @@
import {
ButtonWidgetFixedParams,
ButtonWidgetParams,
} from "@frontend/squzanswerer";
import { ButtonWidgetFixedParams, ButtonWidgetParams } from "@frontend/squzanswerer";
import {
Box,
Button,
@ -24,10 +21,13 @@ import RadioIcon from "@ui_kit/RadioIcon";
import RunningStripe from "@ui_kit/RunningStripe";
import { nanoid } from "nanoid";
import { ReactNode, useState } from "react";
import Dots from "../../../../assets/dots.png";
import fixedButtonWidgetPreview from "../../../../assets/fixed-button-widget-preview.png";
import WidgetScript from "../WidgetScript";
import { createButtonWidgetScriptText } from "../createWidgetScriptText";
import Dots from "../../../../../assets/dots.png";
import fixedButtonWidgetPreview from "../../../../../assets/fixed-button-widget-preview.png";
import WidgetScript from "../../WidgetScript";
import { createButtonWidgetScriptText } from "../../createWidgetScriptText";
import { useWidgetUrl } from "../../useWidgetUrl";
import ButtonWidgetPreviewDesktop from "./ButtonWidgetPreviewDesktop";
import ButtonWidgetPreviewMobile from "./ButtonWidgetPreviewMobile";
interface Props {
step: 2 | 3;
@ -38,25 +38,21 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const widgetUrl = useWidgetUrl();
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] =
useState<boolean>(false);
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] = useState<boolean>(false);
const [hideOnMobile, setHideOnMobile] = useState<boolean>(false);
const [rounded, setRounded] = useState<boolean>(false);
const [withShadow, setWithShadow] = useState<boolean>(false);
const [buttonFlash, setButtonFlash] = useState<boolean>(false);
const [buttonText, setButtonText] = useState<string>("");
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(
theme.palette.brightPurple.main,
);
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(theme.palette.brightPurple.main);
const [buttonTextColor, setButtonTextColor] = useState<string>("#FFFFFF");
const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false);
const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10);
const [openOnLeaveAttempt, setOpenOnLeaveAttempt] = useState<boolean>(false);
const [fixedSide, setFixedSide] = useState<
null | ButtonWidgetFixedParams["fixedSide"]
>(null);
const [fixedSide, setFixedSide] = useState<null | ButtonWidgetFixedParams["fixedSide"]>(null);
const [fullScreen, setFullScreen] = useState<boolean>(false);
if (!quiz) return null;
@ -96,7 +92,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
};
}
const scriptText = createButtonWidgetScriptText(params);
const scriptText = createButtonWidgetScriptText(params, widgetUrl);
return (
<WidgetScript
@ -186,14 +182,47 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
pt: "40px",
}}
>
<img
src={fixedButtonWidgetPreview}
style={{
display: "block",
width: "100%",
height: "100%",
<Box
sx={{
position: "relative",
}}
/>
>
<img
src={fixedButtonWidgetPreview}
style={{
display: "block",
width: "100%",
height: "100%",
}}
/>
<Box
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
containerType: "size",
}}
>
<ButtonWidgetPreviewDesktop
fixedSide={fixedSide}
buttonBackgroundColor={buttonBackgroundColor}
buttonTextColor={buttonTextColor}
buttonFlash={buttonFlash}
buttonText={buttonText}
rounded={rounded}
withShadow={withShadow}
/>
{!hideOnMobile && (
<ButtonWidgetPreviewMobile
fixedSide={fixedSide}
buttonBackgroundColor={buttonBackgroundColor}
buttonTextColor={buttonTextColor}
/>
)}
</Box>
</Box>
</Box>
)}
</Box>
@ -205,7 +234,10 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
flex: "1 1 0",
}}
>
<Typography fontWeight={500} color="#4D4D4D">
<Typography
fontWeight={500}
color="#4D4D4D"
>
1. Задайте размеры окна квиза (опционально)
</Typography>
<CustomCheckbox
@ -216,9 +248,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
<Collapse in={!fullScreen}>
<Box sx={{ display: "flex", gap: "40px" }}>
<Box sx={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<Typography sx={{ color: theme.palette.grey2.main }}>
Ширина (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Ширина (px)</Typography>
<PenaTextField
type="number"
value={widgetWidth}
@ -228,9 +258,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<Typography sx={{ color: theme.palette.grey2.main }}>
Высота (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Высота (px)</Typography>
<PenaTextField
type="number"
value={widgetHeight}
@ -249,7 +277,10 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
alignItems: "start",
}}
>
<Typography fontWeight={500} color="#4D4D4D">
<Typography
fontWeight={500}
color="#4D4D4D"
>
2. Конструктор кнопки
</Typography>
<Box
@ -259,16 +290,12 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет кнопки
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет кнопки</Typography>
<CircleColorPicker
color={buttonBackgroundColor}
onChange={(color) => setButtonBackgroundColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет текста кнопки
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста кнопки</Typography>
<CircleColorPicker
color={buttonTextColor}
onChange={(color) => setButtonTextColor(color)}
@ -299,7 +326,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
checked={fixedSide !== null}
handleChange={(e) => setFixedSide(e.target.checked ? "left" : null)}
/>
{fixedSide && (
<Collapse in={fixedSide !== null}>
<FormControl>
<RadioGroup
sx={{
@ -309,15 +336,16 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
}}
value={fixedSide}
onChange={(e) => {
setFixedSide(
e.target.value as ButtonWidgetFixedParams["fixedSide"],
);
setFixedSide(e.target.value as ButtonWidgetFixedParams["fixedSide"]);
}}
>
<FormControlLabel
value="left"
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
label="Слева"
sx={{
@ -327,7 +355,10 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
<FormControlLabel
value="right"
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
label="Справа"
sx={{
@ -336,10 +367,8 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
/>
</RadioGroup>
</FormControl>
)}
<Typography sx={{ color: theme.palette.grey2.main }}>
Текст кнопки
</Typography>
</Collapse>
<Typography sx={{ color: theme.palette.grey2.main }}>Текст кнопки</Typography>
<PenaTextField
value={buttonText}
onChange={(e) => setButtonText(e.target.value)}
@ -399,22 +428,16 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
<PenaTextField
type="number"
value={autoShowQuizTime}
onChange={(e) =>
setAutoShowQuizTime(parseInt(e.target.value))
}
onChange={(e) => setAutoShowQuizTime(parseInt(e.target.value))}
FormControlSx={{
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
</Box>
</Box>
</Collapse>

@ -1,12 +1,4 @@
import {
Box,
Button,
Collapse,
Divider,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Box, Button, Collapse, Divider, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import CircleColorPicker from "@ui_kit/CircleColorPicker";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
@ -16,6 +8,7 @@ import { ReactNode, useState } from "react";
import Dots from "../../../../assets/dots.png";
import WidgetScript from "../WidgetScript";
import { createContainerWidgetScriptText } from "../createWidgetScriptText";
import { useWidgetUrl } from "../useWidgetUrl";
interface Props {
step: 2 | 3;
@ -26,8 +19,8 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] =
useState<boolean>(false);
const widgetUrl = useWidgetUrl();
const [isAutoopenQuizSettingsOpen, setIsAutoopenQuizSettingsOpen] = useState<boolean>(false);
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
const [hideOnMobile, setHideOnMobile] = useState<boolean>(false);
@ -36,9 +29,7 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
const [withShadow, setWithShadow] = useState<boolean>(false);
const [buttonFlash, setButtonFlash] = useState<boolean>(false);
const [buttonText, setButtonText] = useState<string>("");
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(
theme.palette.brightPurple.main,
);
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(theme.palette.brightPurple.main);
const [buttonTextColor, setButtonTextColor] = useState<string>("#FFFFFF");
const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false);
const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10);
@ -47,34 +38,32 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
if (!quiz) return null;
if (step === 3) {
const scriptText = createContainerWidgetScriptText({
quizId: quiz.qid,
selector: `#pena-quiz-container-${nanoid(6)}`,
dimensions:
widgetWidth || widgetHeight
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
hideOnMobile: hideOnMobile || undefined,
showButtonOnMobile: showButtonOnMobile || undefined,
rounded: rounded || undefined,
withShadow: withShadow || undefined,
buttonFlash: buttonFlash || undefined,
buttonText: buttonText || undefined,
buttonBackgroundColor,
buttonTextColor,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
});
return (
<WidgetScript
scriptText={scriptText}
helperText="Установите код в место, в котором должен быть квиз"
/>
const scriptText = createContainerWidgetScriptText(
{
quizId: quiz.qid,
selector: `#pena-quiz-container-${nanoid(6)}`,
dimensions:
widgetWidth || widgetHeight
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
hideOnMobile: hideOnMobile || undefined,
showButtonOnMobile: showButtonOnMobile || undefined,
rounded: rounded || undefined,
withShadow: withShadow || undefined,
buttonFlash: buttonFlash || undefined,
buttonText: buttonText || undefined,
buttonBackgroundColor,
buttonTextColor,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
},
widgetUrl
);
return <WidgetScript scriptText={scriptText} helperText="Установите код в место, в котором должен быть квиз" />;
}
return (
@ -139,15 +128,11 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
maxWidth: "365px",
}}
>
Quiz будет открыть прямо в том месте, где вы установите код на
сайте
Quiz будет открыть прямо в том месте, где вы установите код на сайте
</Typography>
</Box>
<Typography
sx={{ maxWidth: "414px", fontSize: "14px", alignSelf: "start" }}
>
В мобильной версии будет показана кнопка, открывающая quiz в
модальном окне
<Typography sx={{ maxWidth: "414px", fontSize: "14px", alignSelf: "start" }}>
В мобильной версии будет показана кнопка, открывающая quiz в модальном окне
</Typography>
</Box>
</Box>
@ -164,9 +149,7 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
</Typography>
<Box sx={{ display: "flex", gap: "40px" }}>
<Box sx={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<Typography sx={{ color: theme.palette.grey2.main }}>
Ширина (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Ширина (px)</Typography>
<PenaTextField
type="number"
value={widgetWidth}
@ -176,9 +159,7 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
/>
</Box>
<Box sx={{ display: "flex", flexDirection: "column", gap: "20px" }}>
<Typography sx={{ color: theme.palette.grey2.main }}>
Высота (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Высота (px)</Typography>
<PenaTextField
type="number"
value={widgetHeight}
@ -205,36 +186,18 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
gap: "40px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет кнопки
</Typography>
<CircleColorPicker
color={buttonBackgroundColor}
onChange={(color) => setButtonBackgroundColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет текста кнопки
</Typography>
<CircleColorPicker
color={buttonTextColor}
onChange={(color) => setButtonTextColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет кнопки</Typography>
<CircleColorPicker color={buttonBackgroundColor} onChange={(color) => setButtonBackgroundColor(color)} />
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста кнопки</Typography>
<CircleColorPicker color={buttonTextColor} onChange={(color) => setButtonTextColor(color)} />
</Box>
<CustomCheckbox
label="Отключить на мобильных устройствах"
checked={hideOnMobile}
handleChange={(e) => setHideOnMobile(e.target.checked)}
/>
<CustomCheckbox
label="Закругленная"
checked={rounded}
handleChange={(e) => setRounded(e.target.checked)}
/>
<CustomCheckbox
label="С тенью"
checked={withShadow}
handleChange={(e) => setWithShadow(e.target.checked)}
/>
<CustomCheckbox label="Закругленная" checked={rounded} handleChange={(e) => setRounded(e.target.checked)} />
<CustomCheckbox label="С тенью" checked={withShadow} handleChange={(e) => setWithShadow(e.target.checked)} />
<CustomCheckbox
label="С бликом"
checked={buttonFlash}
@ -245,9 +208,7 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
checked={showButtonOnMobile}
handleChange={(e) => setShowButtonOnMobile(e.target.checked)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Текст кнопки
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Текст кнопки</Typography>
<PenaTextField
value={buttonText}
onChange={(e) => setButtonText(e.target.value)}
@ -307,22 +268,16 @@ export default function ContainerWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
<PenaTextField
type="number"
value={autoShowQuizTime}
onChange={(e) =>
setAutoShowQuizTime(parseInt(e.target.value))
}
onChange={(e) => setAutoShowQuizTime(parseInt(e.target.value))}
FormControlSx={{
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
</Box>
</Box>
</Collapse>

@ -1,10 +1,4 @@
import {
Box,
Collapse,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Box, Collapse, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import PenaTextField from "@ui_kit/PenaTextField";
@ -12,6 +6,7 @@ import { ReactNode, useState } from "react";
import Dots from "../../../../assets/dots.png";
import WidgetScript from "../WidgetScript";
import { createPopupWidgetScriptText } from "../createWidgetScriptText";
import { useWidgetUrl } from "../useWidgetUrl";
interface Props {
step: 2 | 3;
@ -22,6 +17,7 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const widgetUrl = useWidgetUrl();
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
const [hideOnMobile, setHideOnMobile] = useState<boolean>(false);
@ -32,27 +28,25 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
if (!quiz) return null;
if (step === 3) {
const scriptText = createPopupWidgetScriptText({
quizId: quiz.qid,
hideOnMobile: hideOnMobile || undefined,
autoShowQuizTime: autoShowQuizTime,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
fullScreen: fullScreen || undefined,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
});
return (
<WidgetScript
scriptText={scriptText}
helperText="Установите код внутрь тэга head"
/>
const scriptText = createPopupWidgetScriptText(
{
quizId: quiz.qid,
hideOnMobile: hideOnMobile || undefined,
autoShowQuizTime: autoShowQuizTime,
openOnLeaveAttempt: openOnLeaveAttempt || undefined,
fullScreen: fullScreen || undefined,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
},
widgetUrl
);
return <WidgetScript scriptText={scriptText} helperText="Установите код внутрь тэга head" />;
}
return (
@ -147,9 +141,7 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Ширина окна (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Ширина окна (px)</Typography>
<PenaTextField
type="number"
value={widgetWidth}
@ -165,9 +157,7 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Высота окна (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Высота окна (px)</Typography>
<PenaTextField
type="number"
value={widgetHeight}
@ -185,9 +175,7 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
<PenaTextField
type="number"
value={autoShowQuizTime}
@ -196,9 +184,7 @@ export default function PopupWidgetSetup({ step, nextButton }: Props) {
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
</Box>
<CustomCheckbox
label="Открывать квиз при попытке уйти с сайта"

@ -1,11 +1,5 @@
import { SideWidgetParams } from "@frontend/squzanswerer";
import {
Box,
Collapse,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Box, Collapse, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import CircleColorPicker from "@ui_kit/CircleColorPicker";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
@ -15,6 +9,7 @@ import sideWidgetPreviewLeft from "../../../../../assets/side-widget-preview-lef
import sideWidgetPreviewRight from "../../../../../assets/side-widget-preview-right.png";
import WidgetScript from "../../WidgetScript";
import { createSideWidgetScriptText } from "../../createWidgetScriptText";
import { useWidgetUrl } from "../../useWidgetUrl";
import SideWidgetPositionButton from "./SideWidgetPositionButton";
interface Props {
@ -26,49 +21,45 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const widgetUrl = useWidgetUrl();
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
const [hideOnMobile, setHideOnMobile] = useState<boolean>(false);
const [autoShowQuiz, setAutoShowQuiz] = useState<boolean>(false);
const [autoShowQuizTime, setAutoShowQuizTime] = useState<number>(10);
const [autoShowWidgetTime, setAutoShowWidgetTime] = useState<number>(10);
const [position, setPosition] =
useState<SideWidgetParams["position"]>("left");
const [position, setPosition] = useState<SideWidgetParams["position"]>("left");
const [fullScreen, setFullScreen] = useState<boolean>(false);
const [buttonFlash, setButtonFlash] = useState<boolean>(false);
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(
theme.palette.brightPurple.main,
);
const [buttonBackgroundColor, setButtonBackgroundColor] = useState<string>(theme.palette.brightPurple.main);
const [buttonTextColor, setButtonTextColor] = useState<string>("#FFFFFF");
if (!quiz) return null;
if (step === 3) {
const scriptText = createSideWidgetScriptText({
quizId: quiz.qid,
position: position,
hideOnMobile: hideOnMobile || undefined,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
autoShowWidgetTime: autoShowWidgetTime || undefined,
fullScreen: fullScreen || undefined,
buttonFlash: buttonFlash || undefined,
buttonTextColor,
buttonBackgroundColor,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
});
return (
<WidgetScript
scriptText={scriptText}
helperText="Установите код внутрь тэга head"
/>
const scriptText = createSideWidgetScriptText(
{
quizId: quiz.qid,
position: position,
hideOnMobile: hideOnMobile || undefined,
autoShowQuizTime: autoShowQuiz ? autoShowQuizTime : undefined,
autoShowWidgetTime: autoShowWidgetTime || undefined,
fullScreen: fullScreen || undefined,
buttonFlash: buttonFlash || undefined,
buttonTextColor,
buttonBackgroundColor,
dialogDimensions:
!fullScreen && (widgetWidth || widgetHeight)
? {
width: widgetWidth ? `${widgetWidth}px` : "100%",
height: widgetHeight ? `${widgetHeight}px` : "100%",
}
: undefined,
},
widgetUrl
);
return <WidgetScript scriptText={scriptText} helperText="Установите код внутрь тэга head" />;
}
return (
@ -92,11 +83,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
}}
>
<img
src={
position === "left"
? sideWidgetPreviewLeft
: sideWidgetPreviewRight
}
src={position === "left" ? sideWidgetPreviewLeft : sideWidgetPreviewRight}
style={{
display: "block",
width: "100%",
@ -133,9 +120,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
position="left"
onClick={() => setPosition("left")}
/>
<Typography color={theme.palette.grey2.main}>
Слева снизу
</Typography>
<Typography color={theme.palette.grey2.main}>Слева снизу</Typography>
</Box>
<Box
sx={{
@ -150,9 +135,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
position="right"
onClick={() => setPosition("right")}
/>
<Typography color={theme.palette.grey2.main}>
Справа снизу
</Typography>
<Typography color={theme.palette.grey2.main}>Справа снизу</Typography>
</Box>
</Box>
<CustomCheckbox
@ -174,9 +157,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Ширина окна (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Ширина окна (px)</Typography>
<PenaTextField
type="number"
value={widgetWidth}
@ -192,9 +173,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Высота окна (px)
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Высота окна (px)</Typography>
<PenaTextField
type="number"
value={widgetHeight}
@ -212,20 +191,10 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет кнопки
</Typography>
<CircleColorPicker
color={buttonBackgroundColor}
onChange={(color) => setButtonBackgroundColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Цвет текста кнопки
</Typography>
<CircleColorPicker
color={buttonTextColor}
onChange={(color) => setButtonTextColor(color)}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет кнопки</Typography>
<CircleColorPicker color={buttonBackgroundColor} onChange={(color) => setButtonBackgroundColor(color)} />
<Typography sx={{ color: theme.palette.grey2.main }}>Цвет текста кнопки</Typography>
<CircleColorPicker color={buttonTextColor} onChange={(color) => setButtonTextColor(color)} />
</Box>
<Box
sx={{
@ -234,9 +203,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать кнопку через
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Показывать кнопку через</Typography>
<PenaTextField
type="number"
value={autoShowWidgetTime}
@ -245,15 +212,9 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
</Box>
<CustomCheckbox
label="С бликом"
checked={buttonFlash}
handleChange={(e) => setButtonFlash(e.target.checked)}
/>
<CustomCheckbox label="С бликом" checked={buttonFlash} handleChange={(e) => setButtonFlash(e.target.checked)} />
<Box>
<CustomCheckbox
label="Автооткрытие виджета при входе на сайт"
@ -269,9 +230,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
mt: "14px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>Показывать через</Typography>
<PenaTextField
type="number"
value={autoShowQuizTime}
@ -280,9 +239,7 @@ export default function SideWidgetSetup({ step, nextButton }: Props) {
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
<Typography sx={{ color: theme.palette.grey2.main }}>секунд</Typography>
</Box>
<Typography
sx={{

@ -5,50 +5,46 @@ import {
ContainerWidgetParams,
PopupWidgetParams,
SideWidgetParams,
WidgetType,
} from "@frontend/squzanswerer";
export function createContainerWidgetScriptText(params: ContainerWidgetParams) {
export function createContainerWidgetScriptText(params: ContainerWidgetParams, widgetUrl: string) {
return `<div id="${params.selector.slice(1)}"></div>
<script type="module">
import { ContainerWidget } from "https://hbpn.link/export/pub.js";
import { ContainerWidget } from "${widgetUrl}";
new ContainerWidget(${JSON.stringify(params, null, 4)});
</script>`;
}
export function createButtonWidgetScriptText(
params: ButtonWidgetParams | ButtonWidgetFixedParams,
) {
const widgetClassName =
"fixedSide" in params ? "ButtonWidgetFixed" : "ButtonWidget";
export function createButtonWidgetScriptText(params: ButtonWidgetParams | ButtonWidgetFixedParams, widgetUrl: string) {
const widgetClassName = "fixedSide" in params ? "ButtonWidgetFixed" : "ButtonWidget";
return `${"selector" in params ? `<div id="${params.selector.slice(1)}"></div>\n` : ""}<script type="module">
import { ${widgetClassName} } from "https://hbpn.link/export/pub.js";
import { ${widgetClassName} } from "${widgetUrl}";
new ${widgetClassName}(${JSON.stringify(params, null, 4)});
</script>`;
}
export function createPopupWidgetScriptText(params: PopupWidgetParams) {
export function createPopupWidgetScriptText(params: PopupWidgetParams, widgetUrl: string) {
return `<script type="module">
import { PopupWidget } from "https://hbpn.link/export/pub.js";
import { PopupWidget } from "${widgetUrl}";
new PopupWidget(${JSON.stringify(params, null, 4)});
</script>`;
}
export function createSideWidgetScriptText(params: SideWidgetParams) {
export function createSideWidgetScriptText(params: SideWidgetParams, widgetUrl: string) {
return `<script type="module">
import { SideWidget } from "https://hbpn.link/export/pub.js";
import { SideWidget } from "${widgetUrl}";
new SideWidget(${JSON.stringify(params, null, 4)});
</script>`;
}
export function createBannerWidgetScriptText(params: BannerWidgetParams) {
export function createBannerWidgetScriptText(params: BannerWidgetParams, widgetUrl: string) {
return `<script type="module">
import { BannerWidget } from "https://hbpn.link/export/pub.js";
import { BannerWidget } from "${widgetUrl}";
new BannerWidget(${JSON.stringify(params, null, 4)});
</script>`;

@ -0,0 +1,7 @@
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
export function useWidgetUrl() {
const { isTestServer } = useDomainDefine();
return `https://${isTestServer ? "s." : ""}hbpn.link/export/pub.js`;
}

@ -24,6 +24,10 @@ import { redirect } from "react-router-dom";
import { enqueueSnackbar } from "notistack";
export type TitleKeys = "contacts" | "company" | "deal" | "buyers";
export type TagQuestionObject = {
backendId: string;
title: string;
};
export type TQuestionEntity = Record<TitleKeys, string[] | []>;
type IntegrationsModalProps = {
@ -86,7 +90,7 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
const [account, error] = await getAccount();
if (error) {
enqueueSnackbar(error)
if (!error.includes("Not Found")) enqueueSnackbar(error)
setAccountInfo(null);
}
if (account) {
@ -97,7 +101,7 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
const [settingsResponse, error] = await getIntegrationRules(quizID.toString());
if (error) {
enqueueSnackbar(error)
if (!error.includes("Not Found")) enqueueSnackbar(error)
setIntegrationRules(null);
}
if (settingsResponse) {
@ -182,10 +186,10 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
isSettingsAvailable: true,
component: (
<AmoStep6
tags={tags}
tagsNames={tags}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
setTags={setTags}
setTagsNames={setTags}
/>
),
},

@ -4,6 +4,7 @@ import {
FC,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
@ -12,33 +13,34 @@ import { TagKeys, TTags } from "../AmoCRMModal";
import Box from "@mui/material/Box";
import { ItemsSelectionView } from "../IntegrationStep7/ItemsSelectionView/ItemsSelectionView";
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
import { Tag } from "@api/integration";
type Props = {
handleNextStep: () => void;
handlePrevStep: () => void;
tags: TTags;
setTags: Dispatch<SetStateAction<TTags>>;
tagsNames: TTags;
setTagsNames: Dispatch<SetStateAction<TTags>>;
};
export const AmoStep6: FC<Props> = ({
handleNextStep,
handlePrevStep,
tags,
setTags,
tagsNames,
setTagsNames
}) => {
const theme = useTheme();
const [isSelection, setIsSelection] = useState<boolean>(false);
const [activeItem, setActiveItem] = useState<string | null>(null);
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const [tags, setTags] = useState<Tag[]>([]);
const handleAdd = useCallback(() => {
if (!activeItem || !selectedValue) return;
setTags((prevState) => ({
setTagsNames((prevState) => ({
...prevState,
[activeItem]: [...prevState[activeItem as TagKeys], selectedValue],
}));
}, [activeItem, setTags, selectedValue]);
}, [activeItem, setTagsNames, selectedValue]);
const items = useMemo(
() => ["#тег с результатом 1", "#еще один тег с результатом 2", "#тег"],
@ -56,7 +58,8 @@ export const AmoStep6: FC<Props> = ({
>
{isSelection ? (
<ItemsSelectionView
items={items}
parentTags={tags}
setTags={setTags}
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}
type={"typeTags"}
@ -75,7 +78,7 @@ export const AmoStep6: FC<Props> = ({
setIsSelection={setIsSelection}
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
tags={tags}
tagsNames={tagsNames}
setActiveItem={setActiveItem}
/>
)}

@ -8,14 +8,14 @@ type TagsDetailsViewProps = {
setIsSelection: (value: boolean) => void;
handlePrevStep: () => void;
handleNextStep: () => void;
tags: TTags;
tagsNames: TTags;
setActiveItem: (value: string | null) => void;
};
export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
handlePrevStep,
handleNextStep,
tags,
tagsNames,
setActiveItem,
setIsSelection,
}) => {
@ -68,8 +68,8 @@ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
justifyContent: "start",
}}
>
{tags &&
Object.keys(tags).map((item) => (
{tagsNames &&
Object.keys(tagsNames).map((item) => (
<Item
key={item}
title={item as TagKeys}
@ -77,7 +77,7 @@ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
setIsSelection(true);
setActiveItem(item);
}}
data={tags}
data={tagsNames}
/>
))}
</Box>

@ -9,9 +9,11 @@ import {
} from "react";
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
import { TitleKeys, TQuestionEntity } from "../AmoCRMModal";
import { TagQuestionObject, TitleKeys, TQuestionEntity } from "../AmoCRMModal";
import Box from "@mui/material/Box";
import type { AllTypesQuestion } from "@model/questionTypes/shared"
import { getQuestionById } from "@/stores/questions/actions";
import { useQuestionsStore } from "@/stores/questions/store";
type Props = {
handlePrevStep: () => void;
@ -42,10 +44,33 @@ export const AmoStep7: FC<Props> = ({
}));
}, [activeItem, setQuestionEntity, selectedValue]);
const items = useMemo(
() => ["Город", "Имя", "Фамилия", "Отчество", "Контрагент"],
const items: TagQuestionObject[] = useMemo(
() => Object.values(questions)
.filter(({ type }) =>
type !== "result"
&& type !== null)
.map(({ backendId, title }) => ({
backendId: backendId.toString(),
title
})),
[],
);
const translatedQuestionEntity = useMemo(() => {
const translated = {} as TQuestionEntity;
for (let key in questionEntity) {
// /* ... делать что-то с obj[key] ... */
translated[key] = questionEntity[key].map((id) =>
questions.find((q) => q.backendId === Number(id))?.title || id
)
}
console.log("questionEntity", questionEntity)
console.log("translated", translated)
return translated
},
[questionEntity],
)
return (
<Box
@ -59,6 +84,7 @@ export const AmoStep7: FC<Props> = ({
{isSelection ? (
<ItemsSelectionView
items={items}
type="typeQuestions"
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}
onSmallBtnClick={() => {
@ -70,14 +96,13 @@ export const AmoStep7: FC<Props> = ({
setActiveItem(null);
setIsSelection(false);
}}
questions={questions}
/>
) : (
<ItemDetailsView
setIsSelection={setIsSelection}
handleLargeBtn={handleNextStep}
handleSmallBtn={handlePrevStep}
questionEntity={questionEntity}
questionEntity={translatedQuestionEntity}
setActiveItem={setActiveItem}
/>
)}

@ -12,6 +12,8 @@ type ItemProps = {
export const Item: FC<ItemProps> = ({ title, onAddBtnClick, data }) => {
const theme = useTheme();
console.log("title")
console.log(data)
const titleDictionary = {
contact: "Контакт",
company: "Компания",

@ -3,14 +3,18 @@ import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/Cus
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react";
import { AllTypesQuestion } from "@/model/questionTypes/shared";
import type { TagQuestionObject } from "@/pages/IntegrationsPage/IntegrationsModal/AmoCRMModal"
import { Tag } from "@api/integration";
type ItemsSelectionViewProps = {
type?: string;
items: string[];
items?: TagQuestionObject[];
selectedValue: string | null;
setSelectedValue: (value: string | null) => void;
onLargeBtnClick: () => void;
onSmallBtnClick: () => void;
setTags: (setValueFunc: (value: Tag[]) => Tag[]) => void;
parentTags: Tag[]
};
export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
@ -20,7 +24,11 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
onLargeBtnClick,
onSmallBtnClick,
type,
parentTags,
setTags
}) => {
console.log("items тегов")
console.log(items)
return (
<Box
sx={{
@ -42,6 +50,8 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
<CustomRadioGroup
type={type}
items={items}
tags={parentTags}
setTags={setTags}
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}
/>

@ -1,23 +1,18 @@
import React, { useEffect } from "react";
import React from "react";
import { CssBaseline, useMediaQuery, useTheme } from "@mui/material";
import Header from "./HeaderLanding";
import Footer from "./FooterLanding";
import Hero from "./Hero";
import Questions from "./Questions";
import Counter from "./Counter";
import Blog from "./Blog";
import HowItWorks from "./HowItWorks";
import BusinessPluses from "./BusinessPluses";
import HowToUse from "./HowToUse";
import StartWithTemplates from "./StartWithTemplates";
import WhatTheFeatures from "./WhatTheFeatures";
import FullScreenDialog from "./headerMobileLanding";
import Collaboration from "./Collaboration";
import { clearAuthToken, setAuthToken } from "@frontend/kitui";
import { useParams } from "react-router-dom";
import { clearUserData, setUserId, useUserStore } from "@root/user";
import { cleanAuthTicketData } from "@root/ticket";
import { Tariffs } from "@/pages/Landing/Tariffs/Tariffs";
export default function Landing() {
const theme = useTheme();
@ -47,6 +42,7 @@ export default function Landing() {
<Header />
<Hero />
<Counter />
<Tariffs />
{/* <Collaboration/> */}
<HowItWorks />
<BusinessPluses />

@ -0,0 +1,111 @@
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { DurationTariffIcon } from "@/pages/Landing/Tariffs/TariffCard/icons/DurationTariffIcon";
import { TrialTariffIcon } from "@/pages/Landing/Tariffs/TariffCard/icons/TrialTariffIcon";
import { RequestsTariffIcon } from "@/pages/Landing/Tariffs/TariffCard/icons/RequestsTariffIcon";
import { Link, useLocation } from "react-router-dom";
type TariffCardProps = {
type: "requests" | "duration" | "trial";
name: string;
description: string;
actualPrice: string;
oldPrice?: string;
discount?: string;
};
const icons = {
requests: <RequestsTariffIcon />,
duration: <DurationTariffIcon />,
trial: <TrialTariffIcon />,
};
export const TariffCard: FC<TariffCardProps> = ({ type, name, actualPrice, oldPrice, discount, description }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1140));
const location = useLocation();
return (
<Box
sx={{
width: isMobile ? "321px" : "360px",
height: "313px",
background: type === "trial" ? "#7E2AEA" : "#333646",
borderRadius: "12px",
display: "flex",
flexDirection: "column",
}}
>
<Box sx={{ display: "flex", padding: "20px", justifyContent: "space-between", alignItems: "center" }}>
<Box sx={{ display: "flex", gap: "15px" }}>
{icons[type]}
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
background: "#7E2AEA",
padding: "8px 10px",
borderRadius: "8px",
height: "fit-content",
}}
>
{discount && <Typography sx={{ color: "#FFF", lineHeight: "1" }}>-{discount}%</Typography>}
</Box>
</Box>
<Box sx={{ display: "flex", gap: "15px" }}>
{oldPrice && (
<Typography
sx={{
color: "#9A9AAF",
fontSize: "16px",
textDecoration: "line-through",
textDecorationColor: "#FB5607",
textDecorationThickness: "1px",
}}
>
{oldPrice}
</Typography>
)}
{actualPrice && (
<Typography
sx={{
color: "#FFF",
fontSize: "20px",
}}
>
{actualPrice}
</Typography>
)}
</Box>
</Box>
<Box sx={{ marginTop: "5px", padding: "0 20px", display: "flex", flexDirection: "column", gap: "10px" }}>
{name && <Typography sx={{ color: "#FFF", fontSize: "24px" }}>{name}</Typography>}
{description && <Typography sx={{ color: type === "trial" ? "#EEEFF4" : "#9A9AAF" }}>{description}</Typography>}
</Box>
<Box
sx={{
marginTop: "auto",
height: "64px",
background: type === "trial" ? "#5A1EA8" : "rgba(154, 154, 175, 0.5)",
borderRadius: "0 0 12px 12px",
}}
>
<Button
component={Link}
to={"/signin"}
state={{ backgroundLocation: location }}
sx={{
color: "#FFF",
width: "100%",
height: "100%",
}}
>
Выбрать
</Button>
</Box>
</Box>
);
};

@ -0,0 +1,18 @@
import { Box } from "@mui/material";
export const DurationTariffIcon = () => {
return (
<Box>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="#2B2C39" />
<circle cx="18" cy="18" r="10" stroke="#944FEE" strokeWidth="1.5" />
<path
d="M18 14V17.7324C18 17.8996 18.0836 18.0557 18.2226 18.1484L21 20"
stroke="#944FEE"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
</Box>
);
};

@ -0,0 +1,48 @@
import { Box } from "@mui/material";
export const RequestsTariffIcon = () => {
return (
<Box>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="#2B2C39" />
<circle
cx="3.40426"
cy="3.40426"
r="3.40426"
transform="matrix(-1 0 0 1 21.625 10.5)"
stroke="#944FEE"
strokeWidth="1.5"
/>
<path
d="M12.2578 22.3565C12.2578 21.6242 12.7181 20.9711 13.4077 20.7248V20.7248C16.5166 19.6145 19.9139 19.6145 23.0228 20.7248V20.7248C23.7124 20.9711 24.1727 21.6242 24.1727 22.3565V23.4761C24.1727 24.4866 23.2776 25.2629 22.2772 25.12L21.9437 25.0724C19.4706 24.7191 16.9599 24.7191 14.4869 25.0724L14.1533 25.12C13.1529 25.2629 12.2578 24.4866 12.2578 23.4761V22.3565Z"
stroke="#944FEE"
strokeWidth="1.5"
/>
<path
d="M23.3174 17.4042C24.7947 17.4042 25.9922 16.2067 25.9922 14.7295C25.9922 13.2522 24.7947 12.0547 23.3174 12.0547"
stroke="#944FEE"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M26.2471 23.5075L26.5092 23.5449C27.2952 23.6572 27.9985 23.0473 27.9985 22.2533V21.3736C27.9985 20.7983 27.6368 20.2851 27.095 20.0916C26.5546 19.8985 26.003 19.7482 25.4453 19.6406"
stroke="#944FEE"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M12.6826 17.4042C11.2053 17.4042 10.0078 16.2067 10.0078 14.7295C10.0078 13.2522 11.2053 12.0547 12.6826 12.0547"
stroke="#944FEE"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M9.75289 23.5075L9.49082 23.5449C8.70476 23.6572 8.0015 23.0473 8.0015 22.2533V21.3736C8.0015 20.7983 8.36316 20.2851 8.90496 20.0916C9.44541 19.8985 9.99696 19.7482 10.5547 19.6406"
stroke="#944FEE"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
</Box>
);
};

@ -0,0 +1,27 @@
import { Box } from "@mui/material";
export const TrialTariffIcon = () => {
return (
<Box>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="6" fill="white" />
<path d="M18 12.1694V13.8361" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M18 22.1694V23.8361" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M18 28C23.5228 28 28 23.5228 28 18C28 12.4772 23.5228 8 18 8C12.4772 8 8 12.4772 8 18C8 23.5228 12.4772 28 18 28Z"
stroke="#7E2AEA"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.4974 22.1639H19.2474C19.7999 22.1639 20.3298 21.9444 20.7205 21.5537C21.1112 21.163 21.3307 20.6331 21.3307 20.0806C21.3307 19.528 21.1112 18.9981 20.7205 18.6074C20.3298 18.2167 19.7999 17.9972 19.2474 17.9972H16.7474C16.1949 17.9972 15.665 17.7777 15.2743 17.387C14.8836 16.9963 14.6641 16.4664 14.6641 15.9139C14.6641 15.3614 14.8836 14.8315 15.2743 14.4408C15.665 14.0501 16.1949 13.8306 16.7474 13.8306H20.4974"
stroke="#7E2AEA"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
};

@ -0,0 +1,173 @@
import Box from "@mui/material/Box";
import { CssBaseline, Typography, useMediaQuery, useTheme } from "@mui/material";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination } from "swiper/modules";
import SectionStyled from "@/pages/Landing/SectionStyled";
import React, { useMemo } from "react";
import { TariffCard } from "@/pages/Landing/Tariffs/TariffCard/TariffCard";
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
export type Tariff = {
name: string;
type: "requests" | "duration" | "trial";
description: string;
actualPrice: string;
oldPrice?: string;
discount?: string;
};
const swiperStyles = `
.mySwiper .swiper-pagination-bullet {
background: #9A9AAF;
margin-top: 40px;
}
.mySwiper .swiper-pagination {
position: relative;
margin-top: 30px;
}
.mySwiper .swiper-button-next, .mySwiper .swiper-button-prev {
width: 37.5px;
height: 37.5px;
background: white;
border-radius: 50%;
margin-top: -40px;
}
.mySwiper .swiper-button-next:after, .mySwiper .swiper-button-prev:after {
color: black;
font-size: 20px;
}
`;
export const Tariffs = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const tariffs: Tariff[] = useMemo(
() => [
{
name: "Бесплатно 14 дней",
type: "trial",
description:
"Каждому пользователю все наши продукты в первые 14 дней доступны совершенно бесплатно (кроме доп.услуг)",
actualPrice: "0",
},
{
name: "Месяц",
type: "duration",
description: "30 дней безлимитного пользования сервисом",
actualPrice: "969",
oldPrice: "1024",
discount: "5",
},
{
name: "3 месяца",
type: "duration",
description: "90 дней безлимитного пользования сервисом",
actualPrice: " 2 325,6",
oldPrice: "3060",
discount: "24",
},
{
name: "Год",
type: "duration",
description: "365 дней безлимитного пользования сервисом",
actualPrice: " 7 501,84",
oldPrice: "12 410",
discount: "40",
},
{
name: "3 года",
type: "duration",
description: "1095 дней безлимитного пользования сервисом",
actualPrice: "16 939,65",
oldPrice: "37 230",
discount: "55",
},
{
name: "100 Заявок",
type: "requests",
description: "Полное прохождение 100 опросов респондентом",
actualPrice: "1 900",
oldPrice: "2 000",
discount: "5",
},
{
name: "1 000 Заявок",
type: "requests",
description: "Полное прохождение 1000 опросов респондентом",
actualPrice: "12 740",
oldPrice: "2 000",
discount: "36",
},
{
name: "10 000 Заявок",
type: "requests",
description: "Полное прохождение 10000 опросов респондентом",
actualPrice: "89 000",
oldPrice: "200 000",
discount: "56",
},
],
[]
);
return (
<SectionStyled
tag={"section"}
bg={"#282937"}
sxsect={{ marginTop: "-40px", padding: isMobile ? "0 16px" : isTablet ? "0 40px" : "0 20px" }}
mwidth={"1160px"}
sxcont={{
boxSizing: "border-box",
}}
>
<CssBaseline>
<style>{swiperStyles}</style>
</CssBaseline>
<Box
color="#ffffff"
sx={{
display: "flex",
justifyContent: "space-between",
flexWrap: "wrap",
marginTop: "140px",
marginBottom: "40px",
}}
>
<Typography variant="h6" fontSize="36px" lineHeight={1}>
Наши тарифы
</Typography>
</Box>
<Box sx={{ marginBottom: "80px" }}>
<Swiper
slidesPerView={"auto"}
spaceBetween={15}
pagination={{
clickable: true,
}}
modules={[Pagination, Navigation]}
navigation={true}
className="mySwiper"
>
{tariffs.map((tariff) => {
return (
<SwiperSlide style={{ width: isMobile ? "321px" : "360px" }}>
<TariffCard
name={tariff.name}
type={tariff.type}
description={tariff.description}
actualPrice={tariff.actualPrice}
discount={tariff.discount}
oldPrice={tariff.oldPrice}
/>
</SwiperSlide>
);
})}
</Swiper>
</Box>
</SectionStyled>
);
};

@ -1,6 +1,6 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { devlog } from "@frontend/kitui";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { Box, Button } from "@mui/material";
import { clearRuleForAll } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { updateRootContentId } from "@root/quizes/actions";
@ -14,7 +14,6 @@ import {
import { useUiTools } from "@root/uiTools/store";
import type { Core } from "cytoscape";
import { enqueueSnackbar } from "notistack";
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
import CytoscapeComponent from "react-cytoscapejs";
import { withErrorBoundary } from "react-error-boundary";
import { DeleteNodeModal } from "../DeleteNodeModal";
@ -23,8 +22,15 @@ import { addNode, layoutOptions, storeToNodes } from "./helper";
import { useRemoveNode } from "./hooks/useRemoveNode";
import "./style/styles.css";
import { stylesheet } from "./style/stylesheet";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { InfoBanner } from "./InfoBanner/InfoBanner";
import { PositionControl } from "./PositionControl/PositionControl";
import { ZoomControl } from "./ZoomControl/ZoomControl";
function CsComponent() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const [isBannerVisible, setBannerVisible] = useState(true);
const desireToOpenABranchingModal = useUiTools((state) => state.desireToOpenABranchingModal);
const modalQuestionParentContentId = useUiTools((state) => state.modalQuestionParentContentId);
const modalQuestionTargetContentId = useUiTools((state) => state.modalQuestionTargetContentId);
@ -82,37 +88,25 @@ function CsComponent() {
);
return (
<>
<Box mb="20px">
<Button
sx={{
height: "27px",
color: "#7E2AEA",
textDecoration: "underline",
fontSize: "16px",
}}
variant="text"
onClick={() => {
cyRef.current?.fit(undefined, 70);
}}
>
Выровнять
</Button>
</Box>
<Box
sx={{
position: "relative",
height: "480px",
background: "#F2F3F7",
}}
>
<Box
sx={{
width: "100%",
}}
>
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
<Box sx={{ position: "relative" }}>
{isBannerVisible && <InfoBanner setBannerVisible={setBannerVisible} />}
<PositionControl cyRef={cyRef} />
<ZoomControl cyRef={cyRef} />
<CytoscapeComponent
wheelSensitivity={0.1}
elements={csElements}
style={{
height: "100%",
width: "100%",
height: isMobile ? "327px" : "481px",
background: "#F2F3F7",
overflow: "hidden",
borderRadius: "12px",
width: "100%",
}}
stylesheet={stylesheet}
layout={layoutOptions}
@ -123,10 +117,9 @@ function CsComponent() {
autounselectify={true}
boxSelectionEnabled={false}
/>
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
</Box>
<DeleteNodeModal removeNode={removeNode} />
</>
</Box>
);
}

@ -8,14 +8,29 @@ import {
updateOpenedModalSettingsId,
} from "@root/uiTools/actions";
import { Core, EventObject, NodeSingular } from "cytoscape";
import { MutableRefObject, forwardRef, useEffect, useMemo, useRef } from "react";
import { addNode, isElementANode, isNodeInViewport, isQuestionProhibited, storeToNodes } from "./helper";
import {
forwardRef,
MutableRefObject,
useEffect,
useMemo,
useRef,
} from "react";
import { createPortal } from "react-dom";
import {
canAddChildToQuestion,
isElementANode,
isNodeInViewport,
isQuestionProhibited,
storeToNodes,
} from "./helper";
const csButtonTypes = ["delete", "add", "settings", "select"] as const;
type CsButtonType = (typeof csButtonTypes)[number];
type CsNodeButtonsByType = Partial<Record<CsButtonType, HTMLButtonElement | null>>;
type CsNodeButtonsByType = Partial<
Record<CsButtonType, HTMLButtonElement | null>
>;
type CsButtonsById = Record<string, CsNodeButtonsByType | undefined>;
@ -29,7 +44,10 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
const buttons = useMemo(() => {
const nodeElements = csElements.filter(isElementANode);
buttonRefsById.current = nodeElements.reduce<CsButtonsById>((acc, node) => ((acc[node.data.id] = {}), acc), {});
buttonRefsById.current = nodeElements.reduce<CsButtonsById>(
(acc, node) => ((acc[node.data.id] = {}), acc),
{},
);
return (
<Box
sx={{
@ -38,10 +56,7 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
"> *": {
pointerEvents: "auto",
},
borderRadius: "12px",
}}
>
{nodeElements.flatMap((csElement) => [
@ -61,23 +76,24 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
const buttonData = buttonRefsById.current[csElement.data.id];
if (buttonData) buttonData.add = r;
}}
onPointerUp={() => {
addNode({ parentNodeContentId: csElement.data.id });
cleardragQuestionContentId();
onClick={() => {
setModalQuestionParentContentId(csElement.data.id);
setOpenedModalQuestions(canAddChildToQuestion(csElement.data.id));
}}
/>,
!csElement.data.isRoot && !isQuestionProhibited(csElement.data.qtype) && (
<CsSettingsButton
key={`settings-${csElement.data.id}`}
ref={(r) => {
const buttonData = buttonRefsById.current[csElement.data.id];
if (buttonData) buttonData.settings = r;
}}
onClick={() => {
updateOpenedModalSettingsId(csElement.data.id);
}}
/>
),
!csElement.data.isRoot &&
!isQuestionProhibited(csElement.data.qtype) && (
<CsSettingsButton
key={`settings-${csElement.data.id}`}
ref={(r) => {
const buttonData = buttonRefsById.current[csElement.data.id];
if (buttonData) buttonData.settings = r;
}}
onClick={() => {
updateOpenedModalSettingsId(csElement.data.id);
}}
/>
),
//оболочка узла
<CsSelectButton
key={`select-${csElement.data.id}`}
@ -87,7 +103,7 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
}}
onClick={() => {
setModalQuestionParentContentId(csElement.data.id);
setOpenedModalQuestions(!(isQuestionProhibited(csElement.data.type) && csElement.data.children > 0));
setOpenedModalQuestions(canAddChildToQuestion(csElement.data.id));
}}
/>,
])}
@ -130,7 +146,10 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
};
}, []);
return buttons;
const container = cyRef.current?.container();
const buttonsPortal = container ? createPortal(buttons, container) : null;
return buttonsPortal;
}
const applyButtonStyleByType: Record<
@ -139,8 +158,10 @@ const applyButtonStyleByType: Record<
> = {
delete(button, node, zoom) {
const nodePosition = node.renderedPosition();
const shiftX = node.renderedWidth() / 2 - (CLOSE_BUTTON_WIDTH / 2 + 6) * zoom;
const shiftY = node.renderedHeight() / 2 - (CLOSE_BUTTON_HEIGHT / 2 + 6) * zoom;
const shiftX =
node.renderedWidth() / 2 - (CLOSE_BUTTON_WIDTH / 2 + 6) * zoom;
const shiftY =
node.renderedHeight() / 2 - (CLOSE_BUTTON_HEIGHT / 2 + 6) * zoom;
if (!isNodeInViewport(node, 100)) {
return button.style.setProperty("display", "none");
@ -151,12 +172,13 @@ const applyButtonStyleByType: Record<
button.style.setProperty("top", `${nodePosition.y}px`);
button.style.setProperty(
"transform",
`translate3d(calc(-50% + ${shiftX}px), calc(-50% - ${shiftY}px), 0) scale(${zoom})`
`translate3d(calc(-50% + ${shiftX}px), calc(-50% - ${shiftY}px), 0) scale(${zoom})`,
);
},
add(button, node, zoom) {
const nodePosition = node.renderedPosition();
const shiftX = node.renderedWidth() / 2 + (ADD_BUTTON_WIDTH / 2) * zoom;
const shiftX =
node.renderedWidth() / 2 + (ADD_BUTTON_WIDTH / 2 - 10) * zoom;
if (!isNodeInViewport(node, 100)) {
return button.style.setProperty("display", "none");
@ -165,11 +187,15 @@ const applyButtonStyleByType: Record<
button.style.setProperty("display", "flex");
button.style.setProperty("left", `${nodePosition.x}px`);
button.style.setProperty("top", `${nodePosition.y}px`);
button.style.setProperty("transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`);
button.style.setProperty(
"transform",
`translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`,
);
},
settings(button, node, zoom) {
const nodePosition = node.renderedPosition();
const shiftX = -node.renderedWidth() / 2 - (SETTINGS_BUTTON_WIDTH / 2) * zoom;
const shiftX =
-node.renderedWidth() / 2 - (SETTINGS_BUTTON_WIDTH / 2) * zoom;
if (!isNodeInViewport(node, 100)) {
return button.style.setProperty("display", "none");
@ -178,7 +204,10 @@ const applyButtonStyleByType: Record<
button.style.setProperty("display", "flex");
button.style.setProperty("left", `${nodePosition.x}px`);
button.style.setProperty("top", `${nodePosition.y}px`);
button.style.setProperty("transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`);
button.style.setProperty(
"transform",
`translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`,
);
},
select(button, node, zoom) {
const nodePosition = node.renderedPosition();
@ -190,7 +219,10 @@ const applyButtonStyleByType: Record<
button.style.setProperty("display", "flex");
button.style.setProperty("left", `${nodePosition.x}px`);
button.style.setProperty("top", `${nodePosition.y}px`);
button.style.setProperty("transform", `translate3d(-50%, -50%, 0) scale(${zoom})`);
button.style.setProperty(
"transform",
`translate3d(-50%, -50%, 0) scale(${zoom})`,
);
},
};
@ -233,9 +265,9 @@ const ADD_BUTTON_HEIGHT = ADD_BUTTON_WIDTH;
const CsAddButton = forwardRef<
HTMLButtonElement,
{
onPointerUp: () => void;
onClick: () => void;
}
>(({ onPointerUp }, ref) => (
>(({ onClick }, ref) => (
<IconButton
ref={ref}
sx={{
@ -252,7 +284,7 @@ const CsAddButton = forwardRef<
backgroundColor: "#f9f9fc",
},
}}
onPointerUp={onPointerUp}
onClick={onClick}
onMouseDownCapture={(event) => event.stopPropagation()}
onTouchStartCapture={(event) => event.stopPropagation()}
>
@ -260,8 +292,8 @@ const CsAddButton = forwardRef<
</IconButton>
));
const SETTINGS_BUTTON_WIDTH = 70;
const SETTINGS_BUTTON_HEIGHT = 60;
const SETTINGS_BUTTON_WIDTH = 50;
const SETTINGS_BUTTON_HEIGHT = 40;
const CsSettingsButton = forwardRef<
HTMLButtonElement,
@ -329,7 +361,8 @@ const CsSelectButton = forwardRef<
width: SELECT_BUTTON_WIDTH,
height: SELECT_BUTTON_HEIGHT,
backgroundColor: "rgb(0 0 0 / 0)",
borderRadius: "8px",
border: "1px solid #9A9AAF",
borderRadius: "6px",
p: 0,
zIndex: 0,
}}

@ -1,4 +1,10 @@
import { Box } from "@mui/material";
import {
Box,
Button,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
clearRuleForAll,
createResult,
@ -14,8 +20,11 @@ import {
import { useUiTools } from "@root/uiTools/store";
import { enqueueSnackbar } from "notistack";
import { useEffect, useLayoutEffect, useRef } from "react";
import { GrayPlus } from "@icons/questionsPage/GrayPlus";
export const FirstNodeField = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const quiz = useCurrentQuiz();
const modalQuestionTargetContentId = useUiTools(
(state) => state.modalQuestionTargetContentId,
@ -80,17 +89,49 @@ export const FirstNodeField = () => {
<Box
ref={Container}
sx={{
height: "100%",
height: isMobile ? "327px" : "481px",
width: "100%",
backgroundColor: "#f2f3f7",
backgroundColor: theme.palette.background.default,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#4d4d4d",
color: theme.palette.grey3.main,
fontSize: "50px",
borderRadius: "12px",
padding: "20px",
}}
>
+
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "20px",
width: "275px",
}}
>
<Typography
sx={{
color: theme.palette.grey2.main,
textAlign: "center",
}}
>
Добавьте созданные вопросы и настройте связи между ними
</Typography>
<Button
sx={{
width: "70px",
height: "70px",
borderRadius: "12px",
background: "rgba(154, 154, 175, 0.09)",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<GrayPlus />
</Button>
</Box>
</Box>
);
};

@ -0,0 +1,99 @@
import {
Box,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { Add } from "@mui/icons-material";
import { EditIcon } from "@icons/questionsPage/EditIcon";
import CloseIcon from "@mui/icons-material/Close";
import { Dispatch, FC, SetStateAction } from "react";
type InfoBannerProps = {
setBannerVisible: Dispatch<SetStateAction<boolean>>;
};
export const InfoBanner: FC<InfoBannerProps> = ({ setBannerVisible }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
return (
<Box
sx={{
position: "absolute",
top: "30px",
right: "30px",
padding: "20px",
borderRadius: "12px",
background: "#fff",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
zIndex: 2,
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
animation: "fadeIn 0.5s",
}}
>
<Box
sx={{
maxWidth: isMobile ? "253px" : "345px",
color: "#9A9AAF",
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography>Добавьте больше вопросов кнопкой</Typography>
<Box
sx={{
width: "20px",
height: "20px",
color: "rgba(154, 154, 175, 0.5)",
backgroundColor: "#eeeff4",
borderRadius: "6px",
border: "1.5px dashed rgba(154, 154, 175, 0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Add fontSize={"small"} />
</Box>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography>
Настраивайте условия их отображения в квизе с помощью
</Typography>
<EditIcon />
</Box>
</Box>
<IconButton
sx={{
position: "absolute",
top: "-10px",
right: "-10px",
width: "30px",
height: "30px",
background: "#4D4D4D",
opacity: "0.2",
BorderRadius: "50%",
color: "#fff",
"&:hover": {
background: "black",
},
}}
onClick={() => setBannerVisible(false)}
>
<CloseIcon fontSize={"medium"} />
</IconButton>
</Box>
);
};

@ -0,0 +1,64 @@
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
import { ExpandIcon } from "@icons/questionsPage/ExpandIcon";
import { AlignIcon } from "@icons/questionsPage/AlignIcon";
import { FC, MutableRefObject } from "react";
import { Core } from "cytoscape";
type PositionControlProps = {
cyRef: MutableRefObject<Core | null>;
};
export const PositionControl: FC<PositionControlProps> = ({ cyRef }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
return (
<Box
sx={{
position: "absolute",
bottom: isMobile ? "15px" : "20px",
left: isMobile ? "15px" : "20px",
display: "flex",
gap: "10px",
}}
>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#9A9AAF",
opacity: "0.7",
zIndex: 2,
}}
onClick={() => {
cyRef.current?.zoom(1);
cyRef.current?.center();
}}
>
<ExpandIcon />
</Button>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#9A9AAF",
opacity: "0.7",
zIndex: 2,
}}
onClick={() => {
cyRef.current?.fit(undefined, 70);
}}
>
<AlignIcon />
</Button>
</Box>
);
};

@ -0,0 +1,72 @@
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
import { FC, MutableRefObject } from "react";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import { Core } from "cytoscape";
type PositionControlProps = {
cyRef: MutableRefObject<Core | null>;
};
export const ZoomControl: FC<PositionControlProps> = ({ cyRef }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
return (
<Box
sx={{
position: "absolute",
bottom: isMobile ? "15px" : "20px",
right: isMobile ? "15px" : "20px",
display: "flex",
flexDirection: "column",
gap: "10px",
background: "#EEE4FC",
padding: "16px",
borderRadius: "8px",
zIndex: 2,
}}
>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#7E2AEA",
fontSize: "16px",
color: "#fff",
zIndex: 2,
}}
onClick={() => {
const currentZoom = cyRef.current?.zoom() || 1;
cyRef.current?.zoom(currentZoom + 0.1);
}}
>
<AddIcon />
</Button>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#7E2AEA",
fontSize: "16px",
color: "#fff",
zIndex: 2,
}}
onClick={() => {
const currentZoom = cyRef.current?.zoom() || 1;
cyRef.current?.zoom(currentZoom - 0.1);
}}
>
<RemoveIcon />
</Button>
</Box>
);
};

@ -1,4 +1,3 @@
import { QuestionType } from "@model/question/question";
import { QuizQuestionResult } from "@model/questionTypes/result";
import {
AnyTypedQuizQuestion,
@ -66,7 +65,8 @@ export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
question.title === "" || question.title === " "
? "noname"
: question.title;
if (label.length > 10) label = label.slice(0, 10).toLowerCase() + "…";
label = label.trim().replace(/\s+/g, " ");
if (label.length > 20) label = label.slice(0, 20).toLowerCase() + "…";
nodes.push({
data: {
@ -307,7 +307,7 @@ export function calcNodePosition(node: any) {
const width = n.data("subtreeWidth");
n.data("oldPos", {
x: 250 * n.data("layer"),
x: 350 * n.data("layer"),
y: yoffset + width / 2,
});
yoffset += width;
@ -326,6 +326,21 @@ export function calcNodePosition(node: any) {
}
}
export const canAddChildToQuestion = (parentNodeContentId: string) => {
const parentQuestion = {
...getQuestionByContentId(parentNodeContentId),
} as AnyTypedQuizQuestion;
if (
parentQuestion.type !== undefined &&
isQuestionProhibited(parentQuestion.type) &&
parentQuestion.content.rule.children.length > 0
) {
enqueueSnackbar("У вопроса этого типа может быть только 1 потомок");
return false;
}
return true;
};
export const addNode = ({
parentNodeContentId,
targetNodeContentId,

@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useUiTools } from "@root/uiTools/store";
import { BranchingQuestionsModal } from "../BranchingQuestionsModal";
@ -6,6 +6,8 @@ import CsComponent from "./CsComponent";
import { FirstNodeField } from "./FirstNodeField";
export const BranchingMap = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const quiz = useCurrentQuiz();
const dragQuestionContentId = useUiTools((state) => state.dragQuestionContentId);
@ -13,13 +15,15 @@ export const BranchingMap = () => {
<Box
sx={{
overflow: "hidden",
padding: "20px",
background: "#FFFFFF",
padding: isMobile ? "15px" : "20px",
background: theme.palette.common.white,
borderRadius: "12px",
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
marginBottom: "40px",
height: "568px",
border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed",
width: "100%",
border:
dragQuestionContentId === null
? "none"
: `${theme.palette.brightPurple.main} 2px dashed`,
}}
>
{quiz?.config.haveRoot ? <CsComponent /> : <FirstNodeField />}

@ -9,13 +9,14 @@ export const stylesheet: Stylesheet[] = [
height: 130,
backgroundColor: "#FFFFFF",
label: "data(label)",
"font-size": "16",
"font-size": "12",
color: "#4D4D4D",
"text-halign": "center",
"text-halign": "right",
"text-valign": "center",
"text-wrap": "wrap",
"text-max-width": "130px",
"text-overflow-wrap": "whitespace",
"text-margin-x": -115,
},
},
{
@ -40,7 +41,7 @@ export const stylesheet: Stylesheet[] = [
"line-color": "#DEDFE7",
"curve-style": "taxi",
"taxi-direction": "horizontal",
"taxi-turn": 60,
"taxi-turn": 100,
},
},
{

@ -1,34 +1,26 @@
import { useState, useRef, useEffect, useLayoutEffect } from "react";
import { useLayoutEffect, useState } from "react";
import {
Box,
Button,
FormControl,
Checkbox,
FormControlLabel,
Link,
Modal,
Radio,
RadioGroup,
Tooltip,
Typography,
useTheme,
Checkbox,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
AnyTypedQuizQuestion,
createBranchingRuleMain,
} from "../../../model/questionTypes/shared";
import { Select } from "../Select";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
import InfoIcon from "@icons/Info";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import { TypeSwitch, BlockRule } from "./Settings";
import { TypeSwitch } from "./Settings";
import {
getQuestionById,
getQuestionByContentId,
getQuestionById,
updateQuestion,
} from "@root/questions/actions";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
@ -101,7 +93,7 @@ export default function BranchingQuestions() {
left: "50%",
transform: "translate(-50%, -50%)",
maxWidth: "620px",
width: "100%",
width: "90%",
bgcolor: "background.paper",
borderRadius: "12px",
boxShadow: 24,
@ -114,30 +106,30 @@ export default function BranchingQuestions() {
boxSizing: "border-box",
background: "#F2F3F7",
height: "70px",
padding: "0 25px",
padding: "25px 20px",
display: "flex",
alignItems: "center",
color: "#9A9AAF",
gap: "10px",
}}
>
<Box sx={{ color: "#4d4d4d" }}>
<Typography
component="span"
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: isSmallMobile
? "250px"
: isMobile
? "350px"
: "450px",
display: "inline-block",
width: "100%",
}}
>
{targetQuestion.title}
</Typography>
</Box>
<Typography
component="span"
sx={{
lineHeight: "1",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: isSmallMobile
? "250px"
: isMobile
? "350px"
: "450px",
display: "inline-block",
}}
>
{targetQuestion.title}
</Typography>
{isMobile ? (
<TooltipClickInfo
title={
@ -150,7 +142,7 @@ export default function BranchingQuestions() {
placement="top"
>
<Box>
<InfoIcon />
<InfoIcon sx={{ padding: 0 }} />
</Box>
</Tooltip>
)}

@ -1,4 +1,14 @@
import { Box, Modal, Button, Typography } from "@mui/material";
import {
Box,
Button,
FormControlLabel,
Modal,
Radio,
RadioGroup,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useQuestionsStore } from "@root/questions/store";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { useUiTools } from "@root/uiTools/store";
@ -6,12 +16,20 @@ import {
setModalQuestionTargetContentId,
setOpenedModalQuestions,
} from "@root/uiTools/actions";
import React, { useEffect, useState } from "react";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
import { RoundedCheckedIcon } from "@icons/questionsPage/RoundedCheckedIcon";
export const BranchingQuestionsModal = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const trashQuestions = useQuestionsStore().questions;
const questions = trashQuestions.filter(
(question) => question.type !== "result",
);
const openedModalQuestions = useUiTools(
(state) => state.openedModalQuestions,
);
@ -21,14 +39,30 @@ export const BranchingQuestionsModal = () => {
};
const typedQuestions: AnyTypedQuizQuestion[] = questions.filter(
(question) =>
question.type &&
!question.content.rule.parentId &&
question.type !== "result",
(question) => question.type && question.type !== "result",
) as AnyTypedQuizQuestion[];
if (typedQuestions.length === 0) return <></>;
const [selectedQuestion, setSelectedQuestion] = useState<string | null>(null);
useEffect(() => {
if (openedModalQuestions) {
setSelectedQuestion(null);
}
}, [openedModalQuestions]);
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedQuestion(event.target.value);
};
const handleConfirm = () => {
if (selectedQuestion) {
setModalQuestionTargetContentId(selectedQuestion);
}
handleClose();
};
return (
<Modal open={openedModalQuestions} onClose={handleClose}>
<Box
@ -39,48 +73,91 @@ export const BranchingQuestionsModal = () => {
left: "50%",
transform: "translate(-50%, -50%)",
maxWidth: "620px",
width: "100%",
width: "90%",
bgcolor: "background.paper",
borderRadius: "12px",
boxShadow: 24,
padding: "30px 0",
height: "80vh",
}}
>
<Box sx={{ margin: "0 auto", maxWidth: "350px" }}>
{typedQuestions.map((question) => (
<Button
<Box
sx={{ width: "100%", background: theme.palette.background.default }}
>
<Typography
sx={{
padding: "25px 0 25px 20px",
color: theme.palette.grey2.main,
}}
>
Выберите вопрос, который вы хотите добавить в ветвление
</Typography>
</Box>
<RadioGroup
value={selectedQuestion}
onChange={handleRadioChange}
sx={{
height: "346px",
width: "100%",
overflow: "auto",
display: "flex",
flexDirection: "column",
flexWrap: "nowrap",
}}
>
{typedQuestions.map((question, index) => (
<FormControlLabel
key={question.content.id}
onClick={() => {
setModalQuestionTargetContentId(question.content.id);
handleClose();
}}
value={question.content.id}
control={
<Radio
checkedIcon={<RadioCheck />}
icon={
question.content.rule.parentId ? (
<RoundedCheckedIcon />
) : (
<RadioIcon />
)
}
/>
}
label={question.title || "нет заголовка"}
disabled={!!question.content.rule.parentId}
sx={{
padding: "8px 12px",
margin: 0,
width: "100%",
cursor: "pointer",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "12px",
background: "#FFFFFF",
borderRadius: "8px",
marginBottom: "20px",
boxShadow: "0px 10px 30px #e7e7e7",
backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='rgb(154, 154, 175)' strokeWidth='2' stroke-dasharray='8 8' stroke-dashoffset='0' strokeLinecap='square'/%3e%3c/svg%3e");
border-radius: 8px;`,
"&:last-child": { marginBottom: 0 },
backgroundColor:
index % 2 === 0
? theme.palette.common.white
: "rgba(242, 243, 247, 0.5)",
color: theme.palette.grey3.main,
}}
>
<Typography
sx={{
width: "100%",
color: "#000",
}}
>
{question.title || "нет заголовка"}
</Typography>
</Button>
/>
))}
</RadioGroup>
<Box
sx={{
margin: "20px",
width: "auto",
display: "flex",
justifyContent: isMobile ? "space-between" : "end",
gap: "10px",
}}
>
<Button
sx={{ width: isMobile ? "150px" : "130px", height: "44px" }}
onClick={handleClose}
variant={"outlined"}
>
Отмена
</Button>
<Button
sx={{ width: isMobile ? "150px" : "130px", height: "44px" }}
variant={"contained"}
onClick={handleConfirm}
>
Готово
</Button>
</Box>
</Box>
</Modal>

@ -1,10 +1,12 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { Box, Skeleton, useMediaQuery, useTheme } from "@mui/material";
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
import { useCallback } from "react";
import { BranchingMap } from "./BranchingMap";
import { lazy, Suspense, useCallback } from "react";
import { DraggableList } from "./DraggableList";
import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
const BranchingMap = lazy(() =>
import("./BranchingMap").then((module) => ({ default: module.BranchingMap })),
);
interface Props {
openBranchingPage: boolean;
setOpenBranchingPage: (a: boolean) => void;
@ -18,6 +20,7 @@ export const QuestionSwitchWindowTool = ({
}: Props) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const openBranchingPageHC = useCallback(() => {
if (!openBranchingPage) {
@ -30,14 +33,26 @@ export const QuestionSwitchWindowTool = ({
<Box
sx={{
display: "flex",
gap: "20px",
flexWrap: "wrap",
marginBottom: isMobile ? "20px" : undefined,
marginBottom: isMobile ? "25px" : "30px",
}}
>
<Box sx={{ flexBasis: "796px" }}>
<Box sx={{ width: isTablet ? "100%" : "796px" }}>
{openBranchingPage ? (
<BranchingMap />
<Suspense
fallback={
<Skeleton
sx={{
maxWidth: "796px",
width: "100%",
height: isMobile ? "357px" : "521px",
transform: "none",
}}
/>
}
>
<BranchingMap />
</Suspense>
) : (
<DraggableList
openBranchingPage={openBranchingPage}

@ -1,4 +1,11 @@
import { Box, Button, IconButton, Typography, useTheme } from "@mui/material";
import {
Box,
Button,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
collapseAllQuestions,
createUntypedQuestion,
@ -11,7 +18,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateEditSomeQuestion } from "@root/uiTools/actions";
import { useUiTools } from "@root/uiTools/store";
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
import { useLayoutEffect, useRef } from "react";
import { useLayoutEffect } from "react";
import { createPortal } from "react-dom";
import AddPlus from "../../assets/icons/questionsPage/addPlus";
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
@ -30,6 +37,7 @@ export default function QuestionsPage({
widthMain,
}: Props) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(650));
const { openedModalSettingsId } = useUiTools();
const quiz = useCurrentQuiz();
useLayoutEffect(() => {
@ -47,7 +55,7 @@ export default function QuestionsPage({
width: "100%",
display: "flex",
justifyContent: "space-between",
margin: "60px 0 40px 0",
margin: isMobile ? "25px 0" : "40px 0",
}}
>
<Typography variant={"h5"} sx={{ wordBreak: "break-word" }}>

@ -1,14 +1,6 @@
import {
Box,
Typography,
Switch,
useTheme,
Button,
useMediaQuery,
} from "@mui/material";
import { QuestionsList } from "./QuestionsList";
import { Box, useMediaQuery, useTheme } from "@mui/material";
import { PanelSwitchQuestionListGraph } from "@ui_kit/Toolbars/PanelSwitchQuestionListGraph";
interface Props {
openBranchingPage: boolean;
setOpenBranchingPage: () => void;
@ -31,9 +23,6 @@ export const SwitchBranchingPanel = ({
setOpenBranchingPage={setOpenBranchingPage}
/>
)}
{openBranchingPage && (
<QuestionsList setOpenBranchingPage={setOpenBranchingPage} />
)}
</Box>
) : (
<></>

@ -177,7 +177,7 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
gap: isMobile ? "5px" : "15px",
background: "#FFF",
borderTop: "#f2f3f7 2px solid",
zIndex: 1,
zIndex: 3,
position: isMobile ? "fixed" : undefined,
bottom: isMobile ? 0 : undefined,
}}

@ -4,7 +4,7 @@ interface Props {
sx?: SxProps<Theme>;
}
export default function BannerWidgetPreviewIcon({ sx = [] }: Props) {
export default function TaskIcon({ sx = [] }: Props) {
return (
<Box
sx={[
@ -13,6 +13,7 @@ export default function BannerWidgetPreviewIcon({ sx = [] }: Props) {
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
color: "white",
"& svg": {
width: "100%",
height: "100%",
@ -21,31 +22,35 @@ export default function BannerWidgetPreviewIcon({ sx = [] }: Props) {
...(Array.isArray(sx) ? sx : [sx]),
]}
>
<svg viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
viewBox="0 0 20 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.2979 2.94922H15.4949C15.6488 2.94922 15.7964 3.01036 15.9052 3.11919C16.0141 3.22802 16.0752 3.37563 16.0752 3.52954V6.77848M7.21163 2.94922H5.04907C4.89516 2.94922 4.74755 3.01036 4.63872 3.11919C4.52989 3.22802 4.46875 3.37563 4.46875 3.52954V15.7163C4.46875 15.8702 4.52989 16.0178 4.63872 16.1267C4.74755 16.2355 4.89516 16.2966 5.04907 16.2966H8.53802M7.95068 16.2966H15.4949C15.6488 16.2966 15.7964 16.2355 15.9052 16.1267C16.0141 16.0178 16.0752 15.8702 16.0752 15.7163V11.9923"
stroke="white"
stroke="currentColor"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.40182 13.7891H7.65735C7.58039 13.7891 7.50659 13.762 7.45217 13.7139C7.39776 13.6659 7.36719 13.6006 7.36719 13.5326V8.14708C7.36719 8.07906 7.39776 8.01383 7.45217 7.96574C7.50659 7.91764 7.58039 7.89062 7.65735 7.89062H9.10815H12.8802C12.9572 7.89062 13.031 7.91764 13.0854 7.96574C13.1398 8.01383 13.1704 8.07906 13.1704 8.14708V9.58283"
stroke="white"
stroke="currentColor"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.36719 1.8125H13.1704V3.39705C13.1704 3.71756 12.9106 3.97737 12.5901 3.97737H7.94751C7.62701 3.97737 7.36719 3.71756 7.36719 3.39705V1.8125Z"
stroke="white"
stroke="currentColor"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.0844 8.36719L11.8615 13.5901L9.25 10.9786"
stroke="white"
stroke="currentColor"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"

@ -17,7 +17,7 @@ export const SmallSwitchQuestionListGraph = ({
width: "77px",
height: "51px",
position: "fixed",
zIndex: "1111",
zIndex: "999",
right: "0",
top: "200px",
background: "#333647",

@ -10557,7 +10557,16 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -10653,7 +10662,14 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -10806,6 +10822,11 @@ svgo@^2.7.0:
picocolors "^1.0.0"
stable "^0.1.8"
swiper@^11.1.4:
version "11.1.4"
resolved "https://registry.yarnpkg.com/swiper/-/swiper-11.1.4.tgz#2f8e303e8bf9e5bc40a3885fc637ae60ff27996c"
integrity sha512-1n7kbYJB2dFEpUHRFszq7gys/ofIBrMNibwTiMvPHwneKND/t9kImnHt6CfGPScMHgI+dWMbGTycCKGMoOO1KA==
swr@^2.2.4:
version "2.2.5"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b"
@ -11835,7 +11856,7 @@ workbox-window@6.6.1:
"@types/trusted-types" "^2.0.2"
workbox-core "6.6.1"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -11853,6 +11874,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"