add banner widget installation setup

This commit is contained in:
nflnkr 2024-05-24 16:44:16 +03:00
parent 55c414d7bd
commit 85b825d546
11 changed files with 670 additions and 32 deletions

@ -7,7 +7,7 @@
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@frontend/kitui": "^1.0.82",
"@frontend/squzanswerer": "^1.0.41",
"@frontend/squzanswerer": "^1.0.42",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@mui/x-charts": "^6.19.5",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

@ -0,0 +1,56 @@
import { Box, SxProps, Theme } from "@mui/material";
interface Props {
sx?: SxProps<Theme>;
}
export default function BannerWidgetPreviewIcon({ sx = [] }: Props) {
return (
<Box
sx={[
{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
"& svg": {
width: "100%",
height: "100%",
},
},
...(Array.isArray(sx) ? sx : [sx]),
]}
>
<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"
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"
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"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.0844 8.36719L11.8615 13.5901L9.25 10.9786"
stroke="white"
strokeWidth="0.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
}

@ -8,15 +8,16 @@ import {
} from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useState } from "react";
import ContainerWidgetSetup from "./WidgetSetupByType/ContainerWidgetSetup";
import InstallationStepButton from "./InstallationStepButton";
import BannerWidgetSetup from "./WidgetSetupByType/BannerWidgetSetup";
import ButtonWidgetSetup from "./WidgetSetupByType/ButtonWidgetSetup";
import ContainerWidgetSetup from "./WidgetSetupByType/ContainerWidgetSetup";
import WidgetTypeButton from "./WidgetTypeButton";
import BannerWidgetPreview from "./previewIcons/BannerWidgetPreview";
import ButtonWidgetPreview from "./previewIcons/ButtonWidgetPreview";
import ContainerWidgetPreview from "./previewIcons/ContainerWidgetPreview";
import PopupWidgetPreview from "./previewIcons/PopupWidgetPreview";
import SideWidgetPreview from "./previewIcons/SideWidgetPreview";
import ButtonWidgetSetup from "./WidgetSetupByType/ButtonWidgetSetup";
type WidgetSetupSettings = {
step: 1 | 2 | 3;
@ -195,6 +196,12 @@ export default function QuizInstallationCard() {
nextButton={nextButton}
/>
)}
{widgetSetupSettings.widgetType === "banner" && (
<BannerWidgetSetup
step={widgetSetupSettings.step}
nextButton={nextButton}
/>
)}
</>
)}
</Box>

@ -0,0 +1,561 @@
import { BannerWidgetParams } from "@frontend/squzanswerer";
import {
Box,
Button,
Collapse,
Divider,
FormControl,
FormControlLabel,
Radio,
RadioGroup,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useCurrentQuiz } from "@root/quizes/hooks";
import CircleColorPicker from "@ui_kit/CircleColorPicker";
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 { ReactNode, useState } from "react";
import fixedButtonWidgetPreview from "../../../../assets/banner-widget-preview.png";
import WidgetScript from "../WidgetScript";
import { createBannerWidgetScriptText } from "../createWidgetScriptText";
import BannerWidgetPreviewIcon from "@icons/BannerWidgetPreviewIcon";
import CloseIcon from "@mui/icons-material/Close";
import RunningStripe from "@ui_kit/RunningStripe";
interface Props {
step: 2 | 3;
nextButton: ReactNode;
}
export default function BannerWidgetSetup({ step, nextButton }: Props) {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
const quiz = useCurrentQuiz();
const [widgetWidth, setWidgetWidth] = useState<string>("");
const [widgetHeight, setWidgetHeight] = useState<string>("");
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 [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 [position, setPosition] =
useState<BannerWidgetParams["position"]>("bottomleft");
const [bannerFullWidth, setBannerFullWidth] = useState<boolean>(false);
const [autoShowWidgetTime, setAutoShowWidgetTime] = useState<number>(10);
const [appealText, setAppealText] = useState<string>("");
const [quizHeaderText, setQuizHeaderText] = useState<string>("");
const [pulsation, setPulsation] = useState<boolean>(false);
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,
});
return <WidgetScript scriptText={scriptText} />;
}
return (
<Box
sx={{
display: "flex",
flexDirection: isSmallMonitor ? "column" : "row",
gap: "40px",
p: "20px",
}}
>
<Box
sx={{
flex: "1 1 0",
p: "20px",
pt: "40px",
}}
>
<Box
sx={{
position: "relative",
}}
>
<img
src={fixedButtonWidgetPreview}
style={{
display: "block",
width: "100%",
height: "100%",
}}
/>
<Box
sx={[
{
position: "absolute",
top: "calc(12.5 / 262 * 100%)",
left: "calc(50.6 / 520 * 100%)",
width: "calc(196 / 520 * 100%)",
height: "calc(30 / 262 * 100%)",
},
bannerFullWidth && {
top: "calc(7 / 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 ? "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>
</Box>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "15px",
flex: "1 1 0",
}}
>
<Box
sx={{
display: "flex",
gap: "40px",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Ширина окна (px)
</Typography>
<PenaTextField
type="number"
value={widgetWidth}
onChange={(e) => setWidgetWidth(e.target.value)}
FormControlSx={{ maxWidth: "160px" }}
placeholder="auto"
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "20px",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Высота окна (px)
</Typography>
<PenaTextField
type="number"
value={widgetHeight}
onChange={(e) => setWidgetHeight(e.target.value)}
FormControlSx={{ maxWidth: "160px" }}
placeholder="auto"
/>
</Box>
</Box>
<Typography sx={{ color: theme.palette.grey2.main }}>
Текст-призыв
</Typography>
<PenaTextField
placeholder="Пройти тест"
value={appealText}
onChange={(e) => setAppealText(e.target.value)}
FormControlSx={{ maxWidth: "360px" }}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Заголовок quiz
</Typography>
<PenaTextField
placeholder="Заголовок quiz"
value={quizHeaderText}
onChange={(e) => setQuizHeaderText(e.target.value)}
FormControlSx={{ maxWidth: "360px" }}
/>
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<PenaTextField
type="number"
value={autoShowWidgetTime}
onChange={(e) => setAutoShowWidgetTime(parseInt(e.target.value))}
FormControlSx={{
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
</Box>
<Box
sx={{
display: "flex",
gap: "40px",
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)}
/>
</Box>
<CustomCheckbox
label="Баннер на всю ширину экрана"
checked={bannerFullWidth}
handleChange={(e) => {
setBannerFullWidth(e.target.checked);
if (e.target.checked) setPosition("topleft");
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
Расположение
</Typography>
<FormControl>
<RadioGroup
value={position}
onChange={(e) => {
setPosition(e.target.value as BannerWidgetParams["position"]);
}}
sx={{
display: "flex",
flexWrap: "wrap",
flexDirection: "row",
paddingLeft: "5px",
justifyContent: "space-between",
}}
>
{bannerFullWidth ? (
<Box
sx={{
display: "flex",
flexDirection: "row",
gap: "24px",
}}
>
<FormControlLabel
label="Сверху страницы"
value="topleft"
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
sx={{ color: theme.palette.grey2.main }}
/>
<FormControlLabel
label="Снизу страницы"
value="bottomleft"
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
sx={{ color: theme.palette.grey2.main }}
/>
</Box>
) : (
<>
<Box
sx={{
display: "flex",
flexDirection: "column",
}}
>
<FormControlLabel
label="Слева сверху"
value="topleft"
control={
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }}
/>
<FormControlLabel
label="Справа сверху"
value="topright"
control={
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
}}
>
<FormControlLabel
label="Слева снизу"
value="bottomleft"
control={
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }}
/>
<FormControlLabel
label="Справа снизу"
value="bottomright"
control={
<Radio
checkedIcon={<RadioCheck />}
icon={<RadioIcon />}
/>
}
sx={{ color: theme.palette.grey2.main }}
/>
</Box>
</>
)}
</RadioGroup>
</FormControl>
<Typography sx={{ color: theme.palette.grey2.main }}>
Параметры
</Typography>
{!bannerFullWidth && (
<CustomCheckbox
label="Закругленная"
checked={rounded}
handleChange={(e) => setRounded(e.target.checked)}
/>
)}
<CustomCheckbox
label="С тенью"
checked={withShadow}
handleChange={(e) => setWithShadow(e.target.checked)}
/>
<CustomCheckbox
label="С бликом"
checked={buttonFlash}
handleChange={(e) => setButtonFlash(e.target.checked)}
/>
<CustomCheckbox
label={'Эффект "пульсация"'}
checked={pulsation}
handleChange={(e) => setPulsation(e.target.checked)}
/>
<CustomCheckbox
label="Отключить на мобильных устройствах"
checked={hideOnMobile}
handleChange={(e) => setHideOnMobile(e.target.checked)}
/>
<Button
variant="text"
onClick={() => setIsAutoopenQuizSettingsOpen((p) => !p)}
sx={{
fontSize: "16px",
lineHeight: "19px",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
textDecoration: "underline",
textAlign: "left",
alignSelf: "start",
px: 0,
}}
>
+ Автооткрытие quiz
</Button>
<Collapse in={isAutoopenQuizSettingsOpen}>
<Box
sx={{
p: "20px",
backgroundColor: "#F2F3F7",
borderRadius: "8px",
}}
>
<CustomCheckbox
label="Автооткрытие квиза"
checked={autoShowQuiz}
handleChange={(e) => setAutoShowQuiz(e.target.checked)}
/>
<CustomCheckbox
label="Открывать квиз при попытке уйти с сайта"
checked={openOnLeaveAttempt}
handleChange={(e) => setOpenOnLeaveAttempt(e.target.checked)}
sx={{
mt: "15px",
}}
/>
<Collapse in={autoShowQuiz}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: "15px",
pt: "15px",
}}
>
<Divider color="#9A9AAF" />
<Box
sx={{
display: "flex",
gap: "10px",
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>
Показывать через
</Typography>
<PenaTextField
type="number"
value={autoShowQuizTime}
onChange={(e) =>
setAutoShowQuizTime(parseInt(e.target.value))
}
FormControlSx={{
width: "90px",
}}
/>
<Typography sx={{ color: theme.palette.grey2.main }}>
секунд
</Typography>
</Box>
</Box>
</Collapse>
</Box>
</Collapse>
<Box
sx={{
display: "flex",
justifyContent: "end",
pt: "10px",
}}
>
{nextButton}
</Box>
</Box>
</Box>
);
}

@ -27,6 +27,7 @@ import Dots from "../../../../assets/dots.png";
import fixedButtonWidgetPreview from "../../../../assets/fixed-button-widget-preview.png";
import WidgetScript from "../WidgetScript";
import { createButtonWidgetScriptText } from "../createWidgetScriptText";
import RunningStripe from "@ui_kit/RunningStripe";
interface Props {
step: 2 | 3;
@ -163,30 +164,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
}}
>
{buttonText || "Пройти тест"}
{buttonFlash && (
<Box
component="span"
sx={{
position: "absolute",
height: "70px",
width: "140px",
background:
"linear-gradient(0deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.1) 100%)",
animation: "runningStripe linear 3s infinite",
transform: "rotate(-60deg)",
"@keyframes runningStripe": {
"0%": {
left: "-150px",
opacity: 1,
},
"25%, 100%": {
left: "100%",
opacity: 0,
},
},
}}
/>
)}
{buttonFlash && <RunningStripe />}
</Button>
</Box>
</Box>
@ -260,6 +238,7 @@ export default function ButtonWidgetSetup({ step, nextButton }: Props) {
sx={{
display: "flex",
gap: "40px",
alignItems: "center",
}}
>
<Typography sx={{ color: theme.palette.grey2.main }}>

@ -16,7 +16,7 @@ export default function CircleColorPicker({ color, onChange }: Props) {
aspectRatio: 1,
height: "22px",
width: "22px",
minWidth: "20px",
minWidth: "22px",
borderRadius: "50%",
backgroundColor: color,
border: "1px solid #4D4D4D",

@ -0,0 +1,35 @@
import { Box, SxProps, Theme } from "@mui/material";
interface Props {
sx?: SxProps<Theme>;
}
export default function RunningStripe({ sx = [] }: Props) {
return (
<Box
component="span"
sx={[
{
position: "absolute",
height: "70px",
width: "140px",
background:
"linear-gradient(0deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.1) 100%)",
animation: "runningStripe linear 3s infinite",
transform: "rotate(-60deg)",
"@keyframes runningStripe": {
"0%": {
left: "-150px",
opacity: 1,
},
"25%, 100%": {
left: "100%",
opacity: 0,
},
},
},
...(Array.isArray(sx) ? sx : [sx]),
]}
/>
);
}

@ -1527,10 +1527,10 @@
immer "^10.0.2"
reconnecting-eventsource "^1.6.2"
"@frontend/squzanswerer@^1.0.41":
version "1.0.41"
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/43/packages/npm/@frontend/squzanswerer/-/@frontend/squzanswerer-1.0.41.tgz#d535b03c0a432a2c427c99083a6ec9b1e64e060e"
integrity sha1-1TWwPApDKixCfJkIOm7JseZOBg4=
"@frontend/squzanswerer@^1.0.42":
version "1.0.42"
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/43/packages/npm/@frontend/squzanswerer/-/@frontend/squzanswerer-1.0.42.tgz#9c904cd034c5aece72f150e8bfe717edcc8f1d53"
integrity sha1-nJBM0DTFrs5y8VDov+cX7cyPHVM=
dependencies:
bowser "1.9.4"
country-flag-emoji-polyfill "^0.1.8"