From c15516344a2ac50f2e9998f479954f5a2467c9c3 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Mon, 6 May 2024 16:47:19 +0300 Subject: [PATCH 1/7] fix: metrics using --- lib/components/QuizAnswerer.tsx | 280 +++--- .../ContactForm/ContactForm.tsx | 143 ++- .../ViewPublicationPage/ResultForm.tsx | 56 +- .../StartPageViewPublication/index.tsx | 942 ++++++++++-------- .../ViewPublicationPage.tsx | 37 +- lib/model/metrics.ts | 8 + lib/model/settingsData.ts | 4 +- lib/utils/{emailRegexp.tsx => emailRegexp.ts} | 0 lib/utils/hooks/{ => metrics}/useVKMetrics.ts | 14 +- .../useVkMetricsGoals.ts} | 26 +- .../hooks/{ => metrics}/useYandexMetrics.ts | 14 +- .../hooks/metrics/useYandexMetricsGoals.ts | 47 + lib/utils/hooks/useQuestionFlowControl.ts | 409 ++++---- 13 files changed, 1067 insertions(+), 913 deletions(-) create mode 100644 lib/model/metrics.ts rename lib/utils/{emailRegexp.tsx => emailRegexp.ts} (100%) rename lib/utils/hooks/{ => metrics}/useVKMetrics.ts (70%) rename lib/utils/hooks/{useVkMetricGoals.ts => metrics/useVkMetricsGoals.ts} (76%) rename lib/utils/hooks/{ => metrics}/useYandexMetrics.ts (72%) create mode 100644 lib/utils/hooks/metrics/useYandexMetricsGoals.ts diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx index 0a98d9a..185d39e 100644 --- a/lib/components/QuizAnswerer.tsx +++ b/lib/components/QuizAnswerer.tsx @@ -1,154 +1,180 @@ -import { getQuizData } from "@/api/quizRelase"; -import { QuizViewContext, createQuizViewStore } from "@/stores/quizView"; -import LoadingSkeleton from "@/ui_kit/LoadingSkeleton"; -import { QuizDataContext } from "@contexts/QuizDataContext"; -import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext"; -import { QuizSettings } from "@model/settingsData"; -import { Box, CssBaseline, ThemeProvider } from "@mui/material"; -import ScopedCssBaseline from "@mui/material/ScopedCssBaseline"; +import { + startTransition, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; +import { ErrorBoundary } from "react-error-boundary"; +import useSWR from "swr"; +import { SnackbarProvider } from "notistack"; +import moment from "moment"; +import { + Box, + ScopedCssBaseline, + CssBaseline, + ThemeProvider, +} from "@mui/material"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; import { ruRU } from "@mui/x-date-pickers/locales"; + +import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage"; +import { ApologyPage } from "./ViewPublicationPage/ApologyPage"; + +import { QuizViewContext, createQuizViewStore } from "@/stores/quizView"; + +import { getQuizData } from "@/api/quizRelase"; + +import { QuizDataContext } from "@contexts/QuizDataContext"; +import { RootContainerWidthContext } from "@contexts/RootContainerWidthContext"; +import LoadingSkeleton from "@/ui_kit/LoadingSkeleton"; + +import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; +import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; import { handleComponentError } from "@utils/handleComponentError"; import lightTheme from "@utils/themes/light"; -import moment from "moment"; -import { SnackbarProvider } from "notistack"; -import { startTransition, useEffect, useLayoutEffect, useRef, useState } from "react"; -import { ErrorBoundary } from "react-error-boundary"; -import useSWR from "swr"; -import { ApologyPage } from "./ViewPublicationPage/ApologyPage"; -import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage"; +import type { QuizSettings } from "@model/settingsData"; moment.locale("ru"); -const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; +const localeText = + ruRU.components.MuiLocalizationProvider.defaultProps.localeText; type Props = { - quizSettings?: QuizSettings; - quizId: string; - preview?: boolean; - changeFaviconAndTitle?: boolean; - className?: string; - disableGlobalCss?: boolean; + quizSettings?: QuizSettings; + quizId: string; + preview?: boolean; + changeFaviconAndTitle?: boolean; + className?: string; + disableGlobalCss?: boolean; }; -function QuizAnswererInner({ quizSettings, quizId, preview = false, changeFaviconAndTitle = true, className, disableGlobalCss = false }: Props) { - const [quizViewStore] = useState(createQuizViewStore); - const [rootContainerWidth, setRootContainerWidth] = useState(() => window.innerWidth); - const rootContainerRef = useRef(null); - const { data, error, isLoading } = useSWR(quizSettings ? null : ["quizData", quizId], params => getQuizData(params[1]), { - revalidateOnFocus: false, - revalidateOnReconnect: false, - shouldRetryOnError: false, - refreshInterval: 0, - }); +function QuizAnswererInner({ + quizSettings, + quizId, + preview = false, + changeFaviconAndTitle = true, + className, + disableGlobalCss = false, +}: Props) { + const [quizViewStore] = useState(createQuizViewStore); + const [rootContainerWidth, setRootContainerWidth] = useState( + () => window.innerWidth + ); + const rootContainerRef = useRef(null); + const { data, error, isLoading } = useSWR( + quizSettings ? null : ["quizData", quizId], + (params) => getQuizData(params[1]), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + shouldRetryOnError: false, + refreshInterval: 0, + } + ); + const vkMetrics = useVkMetricsGoals( + quizSettings?.settings.cfg.vkMetricsNumber + ); + const yandexMetrics = useYandexMetricsGoals( + quizSettings?.settings.cfg.yandexMetricsNumber + ); + useEffect(() => { + setTimeout(() => { + vkMetrics.quizOpened(); + yandexMetrics.quizOpened(); + }, 4000); + }, []); - useEffect(() => { - setTimeout(() => { - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && quizSettings?.settings.cfg.yandexMetricNumber !== undefined) { - YM( - quizSettings.settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-start" - ); - }; - if (VP !== undefined && quizSettings?.settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: quizSettings.settings.cfg.vkMetricNumber, - goal: "penaquiz-start" - }); - }; - }, 4000) - }, []) + useLayoutEffect(() => { + if (rootContainerRef.current) + setRootContainerWidth(rootContainerRef.current.clientWidth); + }, []); - useLayoutEffect(() => { - if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); - }, []); + useEffect(() => { + const handleWindowResize = () => { + startTransition(() => { + if (rootContainerRef.current) + setRootContainerWidth(rootContainerRef.current.clientWidth); + }); + }; + window.addEventListener("resize", handleWindowResize); - useEffect(() => { - const handleWindowResize = () => { - startTransition(() => { - if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); - }); - }; - window.addEventListener("resize", handleWindowResize); + return () => { + window.removeEventListener("resize", handleWindowResize); + }; + }, []); - return () => { - window.removeEventListener("resize", handleWindowResize); - }; - }, []); + if (isLoading) return ; + if (error) return ; - if (isLoading) return ; - if (error) return ; + quizSettings ??= data; + if (!quizSettings) throw new Error("Quiz data is null"); - quizSettings ??= data; - if (!quizSettings) throw new Error("Quiz data is null"); + if (quizSettings.questions.length === 0) + return ; + if (!quizId) return ; - if (quizSettings.questions.length === 0) return ; - if (!quizId) return ; + const quizContainer = ( + + + + + + ); - const quizContainer = ( - + + - - - - - ); - - return ( - - - - {disableGlobalCss ? ( - - {quizContainer} - - ) : ( - - {quizContainer} - - )} - - - - ); + {quizContainer} + + ) : ( + {quizContainer} + )} + + + + ); } export default function QuizAnswerer(props: Props) { - - return ( - - - - - - - - ); + return ( + + + + + + + + ); } diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx index 2344a62..861021a 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx @@ -1,26 +1,31 @@ -import { useEffect, useRef, useState, } from "react"; -import { Box, Button, Link, Typography, useTheme, } from "@mui/material"; - -import CustomCheckbox from "@ui_kit/CustomCheckbox.tsx"; - -import { DESIGN_LIST } from "@utils/designList.ts"; -import { sendFC, SendFCParams } from "@api/quizRelase.ts"; -import { useQuizData } from "@contexts/QuizDataContext.ts"; -import { NameplateLogo } from "@icons/NameplateLogo.tsx"; -import { QuizQuestionResult } from "@model/questionTypes/result.ts"; -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared.ts"; -import { quizThemes } from "@utils/themes/Publication/themePublication.ts"; +import { useEffect, useRef, useState } from "react"; +import { Box, Button, Link, Typography, useTheme } from "@mui/material"; import { enqueueSnackbar } from "notistack"; -import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts"; -import { + +import CustomCheckbox from "@ui_kit/CustomCheckbox"; + +import { Inputs } from "@/components/ViewPublicationPage/ContactForm/Inputs/Inputs"; + +import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; +import { useQuizData } from "@contexts/QuizDataContext"; + +import { sendFC, SendFCParams } from "@api/quizRelase"; + +import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; +import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; + +import { EMAIL_REGEXP } from "@utils/emailRegexp"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import { DESIGN_LIST } from "@utils/designList"; + +import { NameplateLogo } from "@icons/NameplateLogo"; + +import type { FormContactFieldData, FormContactFieldName, -} from "@model/settingsData.ts"; -import { - Inputs -} from "@/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx"; -import { EMAIL_REGEXP } from "@utils/emailRegexp.tsx"; - +} from "@model/settingsData"; +import type { QuizQuestionResult } from "@model/questionTypes/result"; +import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; type Props = { currentQuestion: AnyTypedQuizQuestion; @@ -44,6 +49,9 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { const isMobile = useRootContainerSize() < 850; const isTablet = useRootContainerSize() < 1000; + const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); + const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); + useEffect(() => { function handleResize() { setScreenHeight(window.innerHeight); @@ -60,18 +68,18 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { currentQuestion.type === "result" ? currentQuestion : questions.find((question): question is QuizQuestionResult => { - if (settings?.cfg.haveRoot) { - return ( - question.type === "result" && - question.content.rule.parentId === currentQuestion.content.id - ); - } else { - return ( - question.type === "result" && - question.content.rule.parentId === "line" - ); - } - }); + if (settings?.cfg.haveRoot) { + return ( + question.type === "result" && + question.content.rule.parentId === currentQuestion.content.id + ); + } else { + return ( + question.type === "result" && + question.content.rule.parentId === "line" + ); + } + }); if (!resultQuestion) throw new Error("Result question not found"); @@ -142,24 +150,8 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { sessions[quizId] = Date.now(); localStorage.setItem("sessions", JSON.stringify(sessions)); - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-contacts" - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: "penaquiz-contacts" - }); - }; + vkMetrics.contactsFormFilled(); + yandexMetrics.contactsFormFilled(); } catch (e) { enqueueSnackbar("повторите попытку позже"); } @@ -171,25 +163,9 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { setFire(false); } useEffect(() => { - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-form" - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: "penaquiz-form" - }); - }; - }, []) + vkMetrics.contactsFormOpened(); + yandexMetrics.contactsFormOpened(); + }, []); return ( { settings.cfg.design && !isMobile ? quizThemes[settings.cfg.theme].isLight ? `url(${DESIGN_LIST[settings.cfg.theme]})` - : `linear-gradient(90deg, #272626, transparent), url(${DESIGN_LIST[settings.cfg.theme] - })` + : `linear-gradient(90deg, #272626, transparent), url(${ + DESIGN_LIST[settings.cfg.theme] + })` : null, }} > @@ -240,7 +217,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { justifyContent: "center", borderRight: isMobile ? undefined : "1px solid #9A9AAF80", margin: isMobile ? 0 : "40px 0", - padding: isMobile ? "0" : "0 40px" + padding: isMobile ? "0" : "0 40px", }} > { justifyContent: "center", flexDirection: "column", backgroundColor: theme.palette.background.default, - p: isMobile ? "0 20px" : isTablet ? "0px 40px 30px 60px" : "125px 60px 30px 60px", + p: isMobile + ? "0 20px" + : isTablet + ? "0px 40px 30px 60px" + : "125px 60px 30px 60px", }} > { display: "flex", flexDirection: "column", mt: isMobile ? "10px" : "20px", - mb: "20px" + mb: "20px", }} > { colorIcon={theme.palette.primary.main} sx={{ marginRight: "0" }} /> - + С  Положением об обработке персональных данных{" "} @@ -361,7 +345,6 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { border: "1px solid #9A9AAF", color: "#9A9AAF", }, - }} > {settings.cfg.formContact?.button || "Получить результаты"} @@ -371,8 +354,9 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { { textDecoration: "none", position: "absolute", bottom: 0, - left: isMobile ? "28%" : undefined + left: isMobile ? "28%" : undefined, }} > { ); }; - diff --git a/lib/components/ViewPublicationPage/ResultForm.tsx b/lib/components/ViewPublicationPage/ResultForm.tsx index 61b672d..a3bbf6e 100644 --- a/lib/components/ViewPublicationPage/ResultForm.tsx +++ b/lib/components/ViewPublicationPage/ResultForm.tsx @@ -1,15 +1,21 @@ +import { useEffect } from "react"; import { Box, Button, Link, Typography, useTheme } from "@mui/material"; -import { NameplateLogo } from "@icons/NameplateLogo"; +import { useQuizViewStore } from "@/stores/quizView"; + +import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; +import { useQuizData } from "@contexts/QuizDataContext"; + +import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; +import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; +import { DESIGN_LIST } from "@/utils/designList"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; + import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe"; -import { useQuizData } from "@contexts/QuizDataContext"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; -import { useRootContainerSize } from "../../contexts/RootContainerWidthContext"; -import type { QuizQuestionResult } from "../../model/questionTypes/result"; -import { useQuizViewStore } from "@/stores/quizView"; -import { DESIGN_LIST } from "@/utils/designList"; -import { useEffect } from "react"; +import { NameplateLogo } from "@icons/NameplateLogo"; + +import type { QuizQuestionResult } from "@/model/questionTypes/result"; type ResultFormProps = { resultQuestion: QuizQuestionResult; @@ -24,27 +30,13 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { (state) => state.setCurrentQuizStep ); const spec = settings.cfg.spec; + const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); + const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); useEffect(() => { - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - `penaquiz-result-{${resultQuestion.id}}` - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: `penaquiz-result-{${resultQuestion.id}}` - }); - }; - }, []) + vkMetrics.resultShown(resultQuestion.id); + yandexMetrics.resultShown(resultQuestion.id); + }, []); return ( { { : "#F5F7FF", }} /> - )} @@ -233,8 +225,8 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { p: (settings.cfg.resultInfo.showResultForm === "before" && !settings.cfg.score) || - (settings.cfg.resultInfo.showResultForm === "after" && - resultQuestion.content.redirect) + (settings.cfg.resultInfo.showResultForm === "after" && + resultQuestion.content.redirect) ? "20px" : "0", }} diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx index 0b1a9b2..b2a10e8 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx @@ -1,15 +1,14 @@ import { - Box, - Button, - ButtonBase, - Link, - Paper, - Typography, - useTheme, + Box, + Button, + ButtonBase, + Link, + Paper, + Typography, + useTheme, } from "@mui/material"; import { QuizPreviewLayoutByType } from "./QuizPreviewLayoutByType"; -import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe"; import { useQuizData } from "@contexts/QuizDataContext"; import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; @@ -21,450 +20,509 @@ import { NameplateLogo } from "@icons/NameplateLogo"; import { useQuizViewStore } from "@/stores/quizView"; import { DESIGN_LIST } from "@/utils/designList"; +import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; +import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; + +import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe"; + export const StartPageViewPublication = () => { - const theme = useTheme(); - const { settings, show_badge, quizId, questions } = useQuizData(); - const { isMobileDevice } = useUADevice(); - const setCurrentQuizStep = useQuizViewStore(state => state.setCurrentQuizStep); + const theme = useTheme(); + const { settings, show_badge, quizId, questions } = useQuizData(); + const { isMobileDevice } = useUADevice(); + const setCurrentQuizStep = useQuizViewStore( + (state) => state.setCurrentQuizStep + ); - const size = useRootContainerSize(); - const isMobile = size < 700; - const isTablet = size >= 700 && size < 1100; - console.log(settings) - const handleCopyNumber = () => { - navigator.clipboard.writeText(settings.cfg.info.phonenumber); - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-phone" - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: "penaquiz-phone" - }); - }; - }; + const size = useRootContainerSize(); + const isMobile = size < 700; + const isTablet = size >= 700 && size < 1100; - const background = - settings.cfg.startpage.background.type === "image" ? ( - - ) : settings.cfg.startpage.background.type === "video" ? ( - settings.cfg.startpage.background.video ? ( - - ) : null - ) : null; + const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); + const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); - const quizHeaderBlock = ( { + navigator.clipboard.writeText(settings.cfg.info.phonenumber); + + vkMetrics.phoneNumberOpened(); + yandexMetrics.phoneNumberOpened(); + }; + + const background = + settings.cfg.startpage.background.type === "image" ? ( + + ) : settings.cfg.startpage.background.type === "video" ? ( + settings.cfg.startpage.background.video ? ( + + ) : null + ) : null; + + const quizHeaderBlock = ( + - + {settings.cfg.startpage.logo && ( + + )} + - {settings.cfg.startpage.logo && - - } - + + + ); + + const PenaBadge = ( + + + {/**/} + {/* Сделано на PenaQuiz*/} + {/**/} + + ); + + const realQuestionsCount = questions.filter( + (question) => question.type !== null && question.type !== "result" + ).length; + + const onQuizStart = () => { + setCurrentQuizStep("question"); + + vkMetrics.firstPageOpened(); + yandexMetrics.firstPageOpened(); + }; + + const onSiteClick = () => { + vkMetrics.emailOpened(); + yandexMetrics.emailOpened(); + + location.href = ( + settings.cfg.info.site.includes("https") + ? settings.cfg.info.site + : `https://${settings.cfg.info.site}` + ).replace(/\s+/g, ""); + }; + + return ( + + + - {settings.cfg.info.orgname} - - - ) - - const PenaBadge = ( - - - {/**/} - {/* Сделано на PenaQuiz*/} - {/**/} - ) - - const realQuestionsCount = questions.filter((question) => question.type !== null && question.type !== "result").length; - - return ( - - + {settings.name} + + + {settings.cfg.startpage.description} + + - - - {settings.name} - - - {settings.cfg.startpage.description} - - - + + + + {settings.cfg.startpageType === "expanded" && + settings.cfg.startpage.position === "center" && + !isMobile && + quizHeaderBlock} + + {settings.cfg.info.site && ( + + + {settings.cfg.info.site} + + + )} + {settings.cfg.info.clickable ? ( + isMobileDevice ? ( + + + {settings.cfg.info.phonenumber} + + + ) : ( + + + {settings.cfg.info.phonenumber} + + + ) + ) : ( + + {settings.cfg.info.phonenumber} + + )} + + {settings.cfg.info.law} + + - - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-startquiz" - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: "penaquiz-startquiz" - }); - }; - }} - > - {settings.cfg.startpage.button.trim() - ? settings.cfg.startpage.button - : "Пройти тест"} - - - - - - {settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && !isMobile && quizHeaderBlock} - - {settings.cfg.info.site && ( - { - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - await YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - "penaquiz-email" - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - await VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: "penaquiz-email" - }); - }; - location.href = ( - settings.cfg.info.site.includes("https") - ? settings.cfg.info.site - : `https://${settings.cfg.info.site}` - ).replace(/\s+/g, '') - }} - > - - {settings.cfg.info.site} - - - - )} - {settings.cfg.info.clickable ? ( - isMobileDevice ? ( - - - {settings.cfg.info.phonenumber} - - - ) : ( - - - {settings.cfg.info.phonenumber} - - - ) - ) : ( - - {settings.cfg.info.phonenumber} - - )} - - {settings.cfg.info.law} - - - - {show_badge && PenaBadge} - - - } - backgroundBlock={background} - startpageType={settings.cfg.startpageType} - alignType={settings.cfg.startpage.position} - /> - - ); + {show_badge && PenaBadge} + + + } + backgroundBlock={background} + startpageType={settings.cfg.startpageType} + alignType={settings.cfg.startpage.position} + /> + + ); }; diff --git a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx index 8e3716f..663611b 100644 --- a/lib/components/ViewPublicationPage/ViewPublicationPage.tsx +++ b/lib/components/ViewPublicationPage/ViewPublicationPage.tsx @@ -1,23 +1,21 @@ -import {sendAnswer} from "@api/quizRelase"; -import {useQuizData} from "@contexts/QuizDataContext"; -import {ThemeProvider, Typography} from "@mui/material"; -import {useQuizViewStore} from "@stores/quizView"; -import {useQuestionFlowControl} from "@utils/hooks/useQuestionFlowControl"; -import {notReachable} from "@utils/notReachable"; -import {quizThemes} from "@utils/themes/Publication/themePublication"; -import {enqueueSnackbar} from "notistack"; -import {ReactElement, useEffect} from "react"; -import {Question} from "./Question"; -import {ResultForm} from "./ResultForm"; -import {StartPageViewPublication} from "./StartPageViewPublication"; +import { sendAnswer } from "@api/quizRelase"; +import { useQuizData } from "@contexts/QuizDataContext"; +import { ThemeProvider, Typography } from "@mui/material"; +import { useQuizViewStore } from "@stores/quizView"; +import { useQuestionFlowControl } from "@utils/hooks/useQuestionFlowControl"; +import { notReachable } from "@utils/notReachable"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import { enqueueSnackbar } from "notistack"; +import { ReactElement, useEffect } from "react"; +import { Question } from "./Question"; +import { ResultForm } from "./ResultForm"; +import { StartPageViewPublication } from "./StartPageViewPublication"; import NextButton from "./tools/NextButton"; import PrevButton from "./tools/PrevButton"; import QuestionSelect from "./QuestionSelect"; -import {useYandexMetrics} from "@/utils/hooks/useYandexMetrics"; -import {useVKMetrics} from "@/utils/hooks/useVKMetrics"; -import { - ContactForm -} from "@/components/ViewPublicationPage/ContactForm/ContactForm.tsx"; +import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics"; +import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics"; +import { ContactForm } from "@/components/ViewPublicationPage/ContactForm/ContactForm.tsx"; export default function ViewPublicationPage() { const { @@ -39,8 +37,8 @@ export default function ViewPublicationPage() { showResultAfterContactForm, setQuestion, } = useQuestionFlowControl(); - useYandexMetrics(settings?.cfg?.yandexMetricNumber); - useVKMetrics(settings?.cfg?.vkMetricNumber); + useYandexMetrics(settings?.cfg?.yandexMetricsNumber); + useVKMetrics(settings?.cfg?.vkMetricsNumber); const isAnswer = answers.some( (ans) => ans.questionId === currentQuestion?.id @@ -75,7 +73,6 @@ export default function ViewPublicationPage() { ); - let quizStepElement: ReactElement; switch (currentQuizStep) { case "startpage": { diff --git a/lib/model/metrics.ts b/lib/model/metrics.ts new file mode 100644 index 0000000..ce8260d --- /dev/null +++ b/lib/model/metrics.ts @@ -0,0 +1,8 @@ +export type MetricsMessengers = + | "telegram" + | "viber" + | "whatsapp" + | "vkontakte" + | "messenger" + | "skype" + | "instagram"; diff --git a/lib/model/settingsData.ts b/lib/model/settingsData.ts index 4a0985c..5f84fa0 100644 --- a/lib/model/settingsData.ts +++ b/lib/model/settingsData.ts @@ -108,8 +108,8 @@ export interface QuizConfig { law?: string; }; meta: string; - yandexMetricNumber: number | undefined; - vkMetricNumber: number | undefined; + yandexMetricsNumber: number | undefined; + vkMetricsNumber: number | undefined; } export type FormContactFieldName = diff --git a/lib/utils/emailRegexp.tsx b/lib/utils/emailRegexp.ts similarity index 100% rename from lib/utils/emailRegexp.tsx rename to lib/utils/emailRegexp.ts diff --git a/lib/utils/hooks/useVKMetrics.ts b/lib/utils/hooks/metrics/useVKMetrics.ts similarity index 70% rename from lib/utils/hooks/useVKMetrics.ts rename to lib/utils/hooks/metrics/useVKMetrics.ts index bea48ee..fb31f0e 100644 --- a/lib/utils/hooks/useVKMetrics.ts +++ b/lib/utils/hooks/metrics/useVKMetrics.ts @@ -1,17 +1,17 @@ import { useEffect } from "react"; -export const useVKMetrics = (vkMetricNumber: number | undefined) => { +export const useVKMetrics = (vkMetricsNumber: number | undefined) => { useEffect(() => { if ( - vkMetricNumber && - typeof vkMetricNumber === "number" && - !Number.isNaN(vkMetricNumber) + vkMetricsNumber && + typeof vkMetricsNumber === "number" && + !Number.isNaN(vkMetricsNumber) ) { const script = document.createElement("script"); script.type = "text/javascript"; script.innerHTML = ` var _tmr = window._tmr || (window._tmr = []); - _tmr.push({id: "${vkMetricNumber}", type: "pageView", start: (new Date()).getTime()}); + _tmr.push({id: "${vkMetricsNumber}", type: "pageView", start: (new Date()).getTime()}); (function (d, w, id) { if (d.getElementById(id)) return; var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id; @@ -23,8 +23,8 @@ export const useVKMetrics = (vkMetricNumber: number | undefined) => { document.body.appendChild(script); const noscript = document.createElement("noscript"); - noscript.innerHTML = `
Top.Mail.Ru
`; + noscript.innerHTML = `
Top.Mail.Ru
`; document.body.appendChild(noscript); } - }, [vkMetricNumber]); + }, [vkMetricsNumber]); }; diff --git a/lib/utils/hooks/useVkMetricGoals.ts b/lib/utils/hooks/metrics/useVkMetricsGoals.ts similarity index 76% rename from lib/utils/hooks/useVkMetricGoals.ts rename to lib/utils/hooks/metrics/useVkMetricsGoals.ts index 41c3b89..36231af 100644 --- a/lib/utils/hooks/useVkMetricGoals.ts +++ b/lib/utils/hooks/metrics/useVkMetricsGoals.ts @@ -1,21 +1,14 @@ import { useEffect, useState } from "react"; -type VkMetric = { +import type { MetricsMessengers } from "@model/metrics"; + +type MetricsGoal = { type: "reachGoal"; id: number; goal: string; }; -type ExtendedWindow = Window & { _tmp?: VkMetric[] }; - -type Messenger = - | "telegram" - | "viber" - | "whatsapp" - | "vkontakte" - | "messenger" - | "skype" - | "instagram"; +type ExtendedWindow = Window & { _tmp?: MetricsGoal[] }; const sendMetrics = (vkPixelId: number | undefined, goal: string) => { if (vkPixelId) { @@ -27,7 +20,7 @@ const sendMetrics = (vkPixelId: number | undefined, goal: string) => { } }; -export const useVkMetricGoals = (vkPixelId: number | undefined) => { +export const useVkMetricsGoals = (vkPixelId: number | undefined) => { const [vkId, setVkId] = useState(undefined); useEffect(() => { @@ -46,16 +39,17 @@ export const useVkMetricGoals = (vkPixelId: number | undefined) => { // Посетитель кликнул по email на стартовой странице emailOpened: () => sendMetrics(vkId, "penaquiz-email"), // Посетитель увидел определенный результат (id - айдишник вопроса с типом result) - resultShown: (resultId: string) => sendMetrics(vkId, `penaquiz-result-${resultId}`), + resultShown: (resultId: string) => + sendMetrics(vkId, `penaquiz-result-${resultId}`), // Посетитель дошёл до формы контактов contactsFormOpened: () => sendMetrics(vkId, "penaquiz-form"), // Посетитель заполнил форму контактов contactsFormFilled: () => sendMetrics(vkId, "penaquiz-contacts"), // Посетитель отправил заявку с мессенджером - messengerRequestSended: (messenger: Messenger) => - sendMetrics(vkId, `marquiz-messengers-${messenger}`), + messengerRequestSended: (messenger: MetricsMessengers) => + sendMetrics(vkId, `penaquiz-messengers-${messenger}`), // Посетитель прошёл вопрос questionPassed: (questionId: string) => - sendMetrics(vkId, `marquiz-step${questionId}`), + sendMetrics(vkId, `penaquiz-step${questionId}`), }; }; diff --git a/lib/utils/hooks/useYandexMetrics.ts b/lib/utils/hooks/metrics/useYandexMetrics.ts similarity index 72% rename from lib/utils/hooks/useYandexMetrics.ts rename to lib/utils/hooks/metrics/useYandexMetrics.ts index ba747a8..b481661 100644 --- a/lib/utils/hooks/useYandexMetrics.ts +++ b/lib/utils/hooks/metrics/useYandexMetrics.ts @@ -1,11 +1,11 @@ import { useEffect } from "react"; -export const useYandexMetrics = (yandexMetricNumber: number | undefined) => { +export const useYandexMetrics = (yandexMetricsNumber: number | undefined) => { useEffect(() => { if ( - yandexMetricNumber && - typeof yandexMetricNumber === "number" && - !Number.isNaN(yandexMetricNumber) + yandexMetricsNumber && + typeof yandexMetricsNumber === "number" && + !Number.isNaN(yandexMetricsNumber) ) { const script = document.createElement("script"); script.type = "text/javascript"; @@ -16,7 +16,7 @@ export const useYandexMetrics = (yandexMetricNumber: number | undefined) => { k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); - ym(${yandexMetricNumber}, "init", { + ym(${yandexMetricsNumber}, "init", { clickmap:true, trackLinks:true, accurateTrackBounce:true, @@ -27,8 +27,8 @@ export const useYandexMetrics = (yandexMetricNumber: number | undefined) => { document.body.appendChild(script); const noscript = document.createElement("noscript"); - noscript.innerHTML = `
`; + noscript.innerHTML = `
`; document.body.appendChild(noscript); } - }, [yandexMetricNumber]); + }, [yandexMetricsNumber]); }; diff --git a/lib/utils/hooks/metrics/useYandexMetricsGoals.ts b/lib/utils/hooks/metrics/useYandexMetricsGoals.ts new file mode 100644 index 0000000..fb4ccaf --- /dev/null +++ b/lib/utils/hooks/metrics/useYandexMetricsGoals.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from "react"; + +import type { MetricsMessengers } from "@model/metrics"; + +type ExtendedWindow = Window & { + ym?: (id: number, type: string, goal: string) => void; +}; + +const sendMetrics = (yandexMetricsId: number | undefined, goal: string) => { + if (yandexMetricsId) { + (window as ExtendedWindow).ym?.(yandexMetricsId, "reachGoal", goal); + } +}; + +export const useYandexMetricsGoals = (yandexMetricsId: number | undefined) => { + const [id, setId] = useState(undefined); + + useEffect(() => { + if (yandexMetricsId) { + setId(yandexMetricsId); + } + }, [yandexMetricsId]); + + return { + // Посетитель открыл квиз + quizOpened: () => sendMetrics(id, "penaquiz-start"), + // Посетитель нажал на кнопку стартовой страницы + firstPageOpened: () => sendMetrics(id, "penaquiz-startquiz"), + // Посетитель кликнул по номеру телефона на стартовой странице + phoneNumberOpened: () => sendMetrics(id, "penaquiz-phone"), + // Посетитель кликнул по email на стартовой странице + emailOpened: () => sendMetrics(id, "penaquiz-email"), + // Посетитель увидел определенный результат (id - айдишник вопроса с типом result) + resultShown: (resultId: string) => + sendMetrics(id, `penaquiz-result-${resultId}`), + // Посетитель дошёл до формы контактов + contactsFormOpened: () => sendMetrics(id, "penaquiz-form"), + // Посетитель заполнил форму контактов + contactsFormFilled: () => sendMetrics(id, "penaquiz-contacts"), + // Посетитель отправил заявку с мессенджером + messengerRequestSended: (messenger: MetricsMessengers) => + sendMetrics(id, `penaquiz-messengers-${messenger}`), + // Посетитель прошёл вопрос + questionPassed: (questionId: string) => + sendMetrics(id, `penaquiz-step${questionId}`), + }; +}; diff --git a/lib/utils/hooks/useQuestionFlowControl.ts b/lib/utils/hooks/useQuestionFlowControl.ts index 472d385..eecd1c0 100644 --- a/lib/utils/hooks/useQuestionFlowControl.ts +++ b/lib/utils/hooks/useQuestionFlowControl.ts @@ -1,213 +1,262 @@ -import { useQuizViewStore } from "@stores/quizView"; import { useCallback, useDebugValue, useMemo, useState } from "react"; -import { isResultQuestionEmpty } from "../../components/ViewPublicationPage/tools/checkEmptyData"; -import moment from "moment"; -import { useQuizData } from "@contexts/QuizDataContext"; import { enqueueSnackbar } from "notistack"; +import moment from "moment"; +import { isResultQuestionEmpty } from "@/components/ViewPublicationPage/tools/checkEmptyData"; +import { useQuizData } from "@contexts/QuizDataContext"; + +import { useQuizViewStore } from "@stores/quizView"; + +import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; +import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; export function useQuestionFlowControl() { - const { settings, questions } = useQuizData(); - const sortedQuestions = useMemo(() => { - return [...questions].sort((a, b) => a.page - b.page); - }, [questions]); - const [currentQuestionId, setCurrentQuestionId] = useState(getFirstQuestionId); - const answers = useQuizViewStore(state => state.answers); - const pointsSum = useQuizViewStore(state => state.pointsSum); - const setCurrentQuizStep = useQuizViewStore(state => state.setCurrentQuizStep); + const { settings, questions } = useQuizData(); + const sortedQuestions = useMemo(() => { + return [...questions].sort((a, b) => a.page - b.page); + }, [questions]); + const [currentQuestionId, setCurrentQuestionId] = useState( + getFirstQuestionId + ); + const answers = useQuizViewStore((state) => state.answers); + const pointsSum = useQuizViewStore((state) => state.pointsSum); + const setCurrentQuizStep = useQuizViewStore( + (state) => state.setCurrentQuizStep + ); + const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); + const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); - const currentQuestion = sortedQuestions.find(question => question.id === currentQuestionId) ?? sortedQuestions[0]; - console.log("currentQuestion", currentQuestion) + const currentQuestion = + sortedQuestions.find((question) => question.id === currentQuestionId) ?? + sortedQuestions[0]; + console.log("currentQuestion", currentQuestion); - const linearQuestionIndex = currentQuestion && sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled - ? sortedQuestions.indexOf(currentQuestion) - : null; + const linearQuestionIndex = + currentQuestion && + sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled + ? sortedQuestions.indexOf(currentQuestion) + : null; - function getFirstQuestionId() { - if (sortedQuestions.length === 0) return null; + function getFirstQuestionId() { + if (sortedQuestions.length === 0) return null; - if (settings.cfg.haveRoot) { - const nextQuestion = sortedQuestions.find( - question => question.id === settings.cfg.haveRoot || question.content.id === settings.cfg.haveRoot - ); - if (!nextQuestion) return null; + if (settings.cfg.haveRoot) { + const nextQuestion = sortedQuestions.find( + (question) => + question.id === settings.cfg.haveRoot || + question.content.id === settings.cfg.haveRoot + ); + if (!nextQuestion) return null; - return nextQuestion.id; - } - - return sortedQuestions[0].id; + return nextQuestion.id; } - const nextQuestionIdPointsLogic = useCallback(() => { - return sortedQuestions.find(question => - question.type === "result" && question.content.rule.parentId === "line" - ); - }, [sortedQuestions]); + return sortedQuestions[0].id; + } - const nextQuestionIdMainLogic = useCallback(() => { - const questionAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id); + const nextQuestionIdPointsLogic = useCallback(() => { + return sortedQuestions.find( + (question) => + question.type === "result" && question.content.rule.parentId === "line" + ); + }, [sortedQuestions]); - if (questionAnswer && !moment.isMoment(questionAnswer.answer)) { - const userAnswers = Array.isArray(questionAnswer.answer) ? questionAnswer.answer : [questionAnswer.answer]; + const nextQuestionIdMainLogic = useCallback(() => { + const questionAnswer = answers.find( + ({ questionId }) => questionId === currentQuestion.id + ); - for (const branchingRule of currentQuestion.content.rule.main) { - if (userAnswers.some(answer => branchingRule.rules[0].answers.includes(answer))) { - return branchingRule.next; - } - } - } + if (questionAnswer && !moment.isMoment(questionAnswer.answer)) { + const userAnswers = Array.isArray(questionAnswer.answer) + ? questionAnswer.answer + : [questionAnswer.answer]; - if (!currentQuestion.required) { - const defaultNextQuestionId = currentQuestion.content.rule.default; - if (defaultNextQuestionId.length > 1 && defaultNextQuestionId !== " ") return defaultNextQuestionId; - //Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт - //Кинуть на ребёнка надо даже если там нет дефолта - if ( - ["date", "page", "text", "number"].includes(currentQuestion.type) - && currentQuestion.content.rule.children.length === 1 - ) return currentQuestion.content.rule.children[0]; - } - - //ничё не нашли, ищем резулт - return sortedQuestions.find(q => { - return q.type === "result" && q.content.rule.parentId === currentQuestion.content.id; - })?.id; - }, [answers, currentQuestion, sortedQuestions]); - - const nextQuestionId = useMemo(() => { - if (settings.cfg.score) { - return nextQuestionIdPointsLogic(); - } - return nextQuestionIdMainLogic(); - }, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score]); - - const prevQuestion = linearQuestionIndex !== null - ? sortedQuestions[linearQuestionIndex - 1] - : sortedQuestions.find(q => - q.id === currentQuestion?.content.rule.parentId - || q.content.id === currentQuestion?.content.rule.parentId - ); - - const findResultPointsLogic = useCallback(() => { - const results = sortedQuestions.filter( - e => e.type === "result" && e.content.rule.minScore !== undefined && e.content.rule.minScore <= pointsSum - ); - const numbers = results.map( - e => e.type === "result" && e.content.rule.minScore !== undefined ? e.content.rule.minScore : 0 - ); - const indexOfNext = Math.max(...numbers); - - return results[numbers.indexOf(indexOfNext)]; - }, [pointsSum, sortedQuestions]); - - const nextQuestion = useMemo(() => { - let next; - if (settings.cfg.score) { - if (linearQuestionIndex !== null) { - next = sortedQuestions[linearQuestionIndex + 1]; - if (next?.type === "result" || next == undefined) next = findResultPointsLogic(); - } - } else { - if (linearQuestionIndex !== null) { - next = sortedQuestions[linearQuestionIndex + 1] ?? sortedQuestions.find(question => - question.type === "result" && question.content.rule.parentId === "line" - ); - } else { - next = sortedQuestions.find(q => q.id === nextQuestionId || q.content.id === nextQuestionId); - } - } - - return next; - }, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]); - - const showResult = useCallback(() => { - if (nextQuestion?.type !== "result") throw new Error("Current question is not result"); - - setCurrentQuestionId(nextQuestion.id); + for (const branchingRule of currentQuestion.content.rule.main) { if ( - settings.cfg.resultInfo.showResultForm === "after" - || isResultQuestionEmpty(nextQuestion) - ) setCurrentQuizStep("contactform"); - }, [nextQuestion, setCurrentQuizStep, settings.cfg.resultInfo.showResultForm]); - - const showResultAfterContactForm = useCallback(() => { - if (currentQuestion?.type !== "result") throw new Error("Current question is not result"); - if (isResultQuestionEmpty(currentQuestion)) { - enqueueSnackbar("Данные отправлены"); - return; + userAnswers.some((answer) => + branchingRule.rules[0].answers.includes(answer) + ) + ) { + return branchingRule.next; } + } + } - setCurrentQuizStep("question"); - }, [currentQuestion, setCurrentQuizStep]); + if (!currentQuestion.required) { + const defaultNextQuestionId = currentQuestion.content.rule.default; + if (defaultNextQuestionId.length > 1 && defaultNextQuestionId !== " ") + return defaultNextQuestionId; + //Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт + //Кинуть на ребёнка надо даже если там нет дефолта + if ( + ["date", "page", "text", "number"].includes(currentQuestion.type) && + currentQuestion.content.rule.children.length === 1 + ) + return currentQuestion.content.rule.children[0]; + } - const moveToPrevQuestion = useCallback(() => { - if (!prevQuestion) throw new Error("Previous question not found"); + //ничё не нашли, ищем резулт + return sortedQuestions.find((q) => { + return ( + q.type === "result" && + q.content.rule.parentId === currentQuestion.content.id + ); + })?.id; + }, [answers, currentQuestion, sortedQuestions]); - setCurrentQuestionId(prevQuestion.id); - }, [prevQuestion]); + const nextQuestionId = useMemo(() => { + if (settings.cfg.score) { + return nextQuestionIdPointsLogic(); + } + return nextQuestionIdMainLogic(); + }, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score]); - const moveToNextQuestion = useCallback(() => { - if (!nextQuestion) throw new Error("Next question not found"); + const prevQuestion = + linearQuestionIndex !== null + ? sortedQuestions[linearQuestionIndex - 1] + : sortedQuestions.find( + (q) => + q.id === currentQuestion?.content.rule.parentId || + q.content.id === currentQuestion?.content.rule.parentId + ); - if (nextQuestion.type === "result") return showResult(); - //засчитываем переход с вопроса дальше + const findResultPointsLogic = useCallback(() => { + const results = sortedQuestions.filter( + (e) => + e.type === "result" && + e.content.rule.minScore !== undefined && + e.content.rule.minScore <= pointsSum + ); + const numbers = results.map((e) => + e.type === "result" && e.content.rule.minScore !== undefined + ? e.content.rule.minScore + : 0 + ); + const indexOfNext = Math.max(...numbers); - //@ts-ignore - let YM = window?.ym; - //@ts-ignore - let VP = window?._tmr; - if (YM !== undefined && settings.cfg.yandexMetricNumber !== undefined) { - YM( - settings.cfg.yandexMetricNumber, - "reachGoal", - `penaquiz-step{${currentQuestion.id}}` - ); - }; - if (VP !== undefined && settings.cfg.vkMetricNumber !== undefined) { - VP.push({ - type: "reachGoal", - id: settings.cfg.vkMetricNumber, - goal: `penaquiz-step{${currentQuestion.id}}` - }); - }; + return results[numbers.indexOf(indexOfNext)]; + }, [pointsSum, sortedQuestions]); - setCurrentQuestionId(nextQuestion.id); - }, [nextQuestion, showResult]); + const nextQuestion = useMemo(() => { + let next; + if (settings.cfg.score) { + if (linearQuestionIndex !== null) { + next = sortedQuestions[linearQuestionIndex + 1]; + if (next?.type === "result" || next == undefined) + next = findResultPointsLogic(); + } + } else { + if (linearQuestionIndex !== null) { + next = + sortedQuestions[linearQuestionIndex + 1] ?? + sortedQuestions.find( + (question) => + question.type === "result" && + question.content.rule.parentId === "line" + ); + } else { + next = sortedQuestions.find( + (q) => q.id === nextQuestionId || q.content.id === nextQuestionId + ); + } + } - const setQuestion = useCallback((questionId: string) => { - const question = sortedQuestions.find(q => q.id === questionId); - if (!question) return; + return next; + }, [ + nextQuestionId, + findResultPointsLogic, + linearQuestionIndex, + sortedQuestions, + settings.cfg.score, + ]); - setCurrentQuestionId(question.id); - }, [sortedQuestions]); + const showResult = useCallback(() => { + if (nextQuestion?.type !== "result") + throw new Error("Current question is not result"); - const isPreviousButtonEnabled = Boolean(prevQuestion); + setCurrentQuestionId(nextQuestion.id); + if ( + settings.cfg.resultInfo.showResultForm === "after" || + isResultQuestionEmpty(nextQuestion) + ) + setCurrentQuizStep("contactform"); + }, [ + nextQuestion, + setCurrentQuizStep, + settings.cfg.resultInfo.showResultForm, + ]); - const isNextButtonEnabled = useMemo(() => { - const hasAnswer = answers.some(({ questionId }) => questionId === currentQuestion.id); + const showResultAfterContactForm = useCallback(() => { + if (currentQuestion?.type !== "result") + throw new Error("Current question is not result"); + if (isResultQuestionEmpty(currentQuestion)) { + enqueueSnackbar("Данные отправлены"); + return; + } - if ("required" in currentQuestion.content && currentQuestion.content.required) { - return hasAnswer; - } + setCurrentQuizStep("question"); + }, [currentQuestion, setCurrentQuizStep]); - return Boolean(nextQuestion); - }, [answers, currentQuestion, nextQuestion]); + const moveToPrevQuestion = useCallback(() => { + if (!prevQuestion) throw new Error("Previous question not found"); - useDebugValue({ - linearQuestionIndex, - currentQuestion: currentQuestion, - prevQuestion: prevQuestion, - nextQuestion: nextQuestion, - }); + setCurrentQuestionId(prevQuestion.id); + }, [prevQuestion]); - return { - currentQuestion, - currentQuestionStepNumber: linearQuestionIndex === null ? null : linearQuestionIndex + 1, - isNextButtonEnabled, - isPreviousButtonEnabled, - moveToPrevQuestion, - moveToNextQuestion, - showResultAfterContactForm, - setQuestion, - }; -} + const moveToNextQuestion = useCallback(() => { + if (!nextQuestion) throw new Error("Next question not found"); + + if (nextQuestion.type === "result") return showResult(); + // Засчитываем переход с вопроса дальше + vkMetrics.questionPassed(currentQuestion.id); + yandexMetrics.questionPassed(currentQuestion.id); + + setCurrentQuestionId(nextQuestion.id); + }, [nextQuestion, showResult]); + + const setQuestion = useCallback( + (questionId: string) => { + const question = sortedQuestions.find((q) => q.id === questionId); + if (!question) return; + + setCurrentQuestionId(question.id); + }, + [sortedQuestions] + ); + + const isPreviousButtonEnabled = Boolean(prevQuestion); + + const isNextButtonEnabled = useMemo(() => { + const hasAnswer = answers.some( + ({ questionId }) => questionId === currentQuestion.id + ); + + if ( + "required" in currentQuestion.content && + currentQuestion.content.required + ) { + return hasAnswer; + } + + return Boolean(nextQuestion); + }, [answers, currentQuestion, nextQuestion]); + + useDebugValue({ + linearQuestionIndex, + currentQuestion: currentQuestion, + prevQuestion: prevQuestion, + nextQuestion: nextQuestion, + }); + + return { + currentQuestion, + currentQuestionStepNumber: + linearQuestionIndex === null ? null : linearQuestionIndex + 1, + isNextButtonEnabled, + isPreviousButtonEnabled, + moveToPrevQuestion, + moveToNextQuestion, + showResultAfterContactForm, + setQuestion, + }; +} From 9fbd5b8eeca9a690dba75b65ded4920a651d360a Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 8 May 2024 01:19:08 +0300 Subject: [PATCH 2/7] v1.0.38 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b06e24..f3fdc17 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@frontend/squzanswerer", - "version": "1.0.36", + "version": "1.0.38", "type": "module", "main": "./dist-package/index.js", "module": "./dist-package/index.js", From b32b371a10620a5fce116f0da2f653b09b69591f Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Fri, 10 May 2024 15:02:01 +0300 Subject: [PATCH 3/7] fix: bug --- lib/utils/hooks/metrics/useVkMetricsGoals.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/hooks/metrics/useVkMetricsGoals.ts b/lib/utils/hooks/metrics/useVkMetricsGoals.ts index 36231af..d7c6437 100644 --- a/lib/utils/hooks/metrics/useVkMetricsGoals.ts +++ b/lib/utils/hooks/metrics/useVkMetricsGoals.ts @@ -8,11 +8,11 @@ type MetricsGoal = { goal: string; }; -type ExtendedWindow = Window & { _tmp?: MetricsGoal[] }; +type ExtendedWindow = Window & { _tmr?: MetricsGoal[] }; const sendMetrics = (vkPixelId: number | undefined, goal: string) => { if (vkPixelId) { - (window as ExtendedWindow)._tmp?.push({ + (window as ExtendedWindow)._tmr?.push({ type: "reachGoal", id: vkPixelId, goal, From 09978ab7cc50c06035078fda6fd1e517a180692e Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Fri, 10 May 2024 15:06:12 +0300 Subject: [PATCH 4/7] fix: vk pixel --- lib/utils/hooks/metrics/useVkMetricsGoals.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/hooks/metrics/useVkMetricsGoals.ts b/lib/utils/hooks/metrics/useVkMetricsGoals.ts index 36231af..d7c6437 100644 --- a/lib/utils/hooks/metrics/useVkMetricsGoals.ts +++ b/lib/utils/hooks/metrics/useVkMetricsGoals.ts @@ -8,11 +8,11 @@ type MetricsGoal = { goal: string; }; -type ExtendedWindow = Window & { _tmp?: MetricsGoal[] }; +type ExtendedWindow = Window & { _tmr?: MetricsGoal[] }; const sendMetrics = (vkPixelId: number | undefined, goal: string) => { if (vkPixelId) { - (window as ExtendedWindow)._tmp?.push({ + (window as ExtendedWindow)._tmr?.push({ type: "reachGoal", id: vkPixelId, goal, From 6e6df2114e3ddde26e0b2b2d1c9fc7441dfdc481 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Fri, 10 May 2024 15:48:47 +0300 Subject: [PATCH 5/7] fix: vk pixel bug --- lib/utils/hooks/metrics/useVkMetricsGoals.ts | 12 +----------- lib/utils/hooks/metrics/useYandexMetricsGoals.ts | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/utils/hooks/metrics/useVkMetricsGoals.ts b/lib/utils/hooks/metrics/useVkMetricsGoals.ts index d7c6437..53c34fa 100644 --- a/lib/utils/hooks/metrics/useVkMetricsGoals.ts +++ b/lib/utils/hooks/metrics/useVkMetricsGoals.ts @@ -1,5 +1,3 @@ -import { useEffect, useState } from "react"; - import type { MetricsMessengers } from "@model/metrics"; type MetricsGoal = { @@ -20,15 +18,7 @@ const sendMetrics = (vkPixelId: number | undefined, goal: string) => { } }; -export const useVkMetricsGoals = (vkPixelId: number | undefined) => { - const [vkId, setVkId] = useState(undefined); - - useEffect(() => { - if (vkPixelId) { - setVkId(vkPixelId); - } - }, [vkPixelId]); - +export const useVkMetricsGoals = (vkId: number | undefined) => { return { // Посетитель открыл квиз quizOpened: () => sendMetrics(vkId, "penaquiz-start"), diff --git a/lib/utils/hooks/metrics/useYandexMetricsGoals.ts b/lib/utils/hooks/metrics/useYandexMetricsGoals.ts index fb4ccaf..6ec5a35 100644 --- a/lib/utils/hooks/metrics/useYandexMetricsGoals.ts +++ b/lib/utils/hooks/metrics/useYandexMetricsGoals.ts @@ -1,5 +1,3 @@ -import { useEffect, useState } from "react"; - import type { MetricsMessengers } from "@model/metrics"; type ExtendedWindow = Window & { @@ -12,15 +10,7 @@ const sendMetrics = (yandexMetricsId: number | undefined, goal: string) => { } }; -export const useYandexMetricsGoals = (yandexMetricsId: number | undefined) => { - const [id, setId] = useState(undefined); - - useEffect(() => { - if (yandexMetricsId) { - setId(yandexMetricsId); - } - }, [yandexMetricsId]); - +export const useYandexMetricsGoals = (id: number | undefined) => { return { // Посетитель открыл квиз quizOpened: () => sendMetrics(id, "penaquiz-start"), From 7a7dd22302d77fb925fa561d7d196971d85c03b7 Mon Sep 17 00:00:00 2001 From: aleksandr-raw <104529174+aleksandr-raw@users.noreply.github.com> Date: Fri, 10 May 2024 17:38:01 +0400 Subject: [PATCH 6/7] improved padding on tablet, fixed gradient --- .../StartPageDesktop.tsx | 116 +++--- .../StartPageMobile.tsx | 382 +++++++++--------- 2 files changed, 255 insertions(+), 243 deletions(-) diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx index 317a73f..332340e 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageDesktop.tsx @@ -23,14 +23,14 @@ type StartPageDesktopProps = { type LayoutProps = Omit; const StandartLayout = ({ - alignType, - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: LayoutProps) => { + alignType, + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: LayoutProps) => { const size = useRootContainerSize(); const isTablet = size >= 700 && size < 1100; - const { settings } = useQuizData(); + const {settings} = useQuizData(); return ( - {quizHeaderBlock} - {quizMainBlock} - - {settings.cfg.startpage.background.desktop && ( - img": { width: "100%", borderRadius: "12px" }, + flexDirection: "column", + justifyContent: "space-between", + alignItems: "flex-start", + p: isTablet ? "25px" : alignType === 'left' ? "25px 25px 25px 35px" : "25px 35px 25px 25px", + overflowY: "auto", + scrollbarWidth: "none", + "&::-webkit-scrollbar": { + width: 0, + }, }} - >{backgroundBlock} - )} + > + {quizHeaderBlock} + {quizMainBlock} + + {settings.cfg.startpage.background.desktop && ( + img": {width: "100%", borderRadius: "12px"}, + }} + >{backgroundBlock} + )} + ); }; const ExpandedLayout = ({ - alignType, - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: LayoutProps) => { + alignType, + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: LayoutProps) => { const size = useRootContainerSize(); const isTablet = size >= 700 && size < 1100; return ( @@ -171,12 +182,12 @@ const ExpandedLayout = ({ } const CenteredLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: LayoutProps) => { + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: LayoutProps) => { const isTablet = useRootContainerSize() < 1100; - const { settings } = useQuizData(); + const {settings} = useQuizData(); return ( img": { width: "100%", borderRadius: "12px" }, + "& > img": {width: "100%", borderRadius: "12px"}, }} > {backgroundBlock} @@ -219,12 +229,12 @@ const CenteredLayout = ({ }; export const StartPageDesktop = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - startpageType, - alignType, -}: StartPageDesktopProps) => { + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + startpageType, + alignType, + }: StartPageDesktopProps) => { switch (startpageType) { case null: case "standard": { diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx index 797bced..7b11f30 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/StartPageMobile.tsx @@ -9,171 +9,174 @@ import type {QuizStartpageType} from "@model/settingsData"; import {DESIGN_LIST} from "@/utils/designList"; type StartPageMobileProps = { - quizHeaderBlock: JSX.Element; - quizMainBlock: JSX.Element; - backgroundBlock: JSX.Element | null; - startpageType: QuizStartpageType; + quizHeaderBlock: JSX.Element; + quizMainBlock: JSX.Element; + backgroundBlock: JSX.Element | null; + startpageType: QuizStartpageType; }; type MobileLayoutProps = Omit; const StandartMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: MobileLayoutProps) => { - const { settings } = useQuizData(); + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: MobileLayoutProps) => { + const {settings} = useQuizData(); - return ( - - - - {quizHeaderBlock} - - {settings.cfg.startpage.background.desktop && ( - - img": { width: "100%", borderRadius: "12px" }, - }} - > - {backgroundBlock} - - - )} + return ( - {quizMainBlock} + + + {quizHeaderBlock} + + {settings.cfg.startpage.background.desktop && ( + + img": { + width: "100%", + borderRadius: "12px" + }, + }} + > + {backgroundBlock} + + + )} + + {quizMainBlock} + + - - - ); + ); }; const ExpandedMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: MobileLayoutProps) => ( - + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: MobileLayoutProps) => ( - - {quizHeaderBlock} - {quizMainBlock} - - - img": { - display: "block", - minHeight: "100%", - }, - }} > - {backgroundBlock} + + + {quizHeaderBlock} + {quizMainBlock} + + + img": { + display: "block", + minHeight: "100%", + }, + }} + > + {backgroundBlock} + - ); const CenteredMobileLayout = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, -}: MobileLayoutProps) => { + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + }: MobileLayoutProps) => { const {settings} = useQuizData(); return ( @@ -211,11 +213,11 @@ const CenteredMobileLayout = ({ {quizHeaderBlock} {settings.cfg.startpage.background.desktop && ( img": {width: "100%", borderRadius: "12px"}, - }} + sx={{ + width: "100%", + overflow: "hidden", + "& > img": {width: "100%", borderRadius: "12px"}, + }} > {backgroundBlock} @@ -238,44 +240,44 @@ const CenteredMobileLayout = ({ } export const StartPageMobile = ({ - quizHeaderBlock, - quizMainBlock, - backgroundBlock, - startpageType, -}: StartPageMobileProps) => { - switch (startpageType) { - case null: - case "standard": { - return ( - - ); - } + quizHeaderBlock, + quizMainBlock, + backgroundBlock, + startpageType, + }: StartPageMobileProps) => { + switch (startpageType) { + case null: + case "standard": { + return ( + + ); + } - case "expanded": { - return ( - - ); - } + case "expanded": { + return ( + + ); + } - case "centered": { - return ( - - ); - } + case "centered": { + return ( + + ); + } - default: - notReachable(startpageType); - } + default: + notReachable(startpageType); + } }; From f7dcf5d16f7a185fd92227cd956465895f8b2899 Mon Sep 17 00:00:00 2001 From: Tamara Date: Sat, 11 May 2024 00:31:39 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StartPageViewPublication/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx index cb686e0..0d5c966 100644 --- a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx +++ b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx @@ -264,6 +264,7 @@ export const StartPageViewPublication = () => { ? "start" : "center", flexGrow: settings.cfg.startpageType === "centered" ? 0 : 1, + wordBreak: "break-word", alignItems: settings.cfg.startpageType === "centered" ? "center" @@ -411,6 +412,7 @@ export const StartPageViewPublication = () => { onClick={onSiteClick} sx={{ display: "block", + width: "100%", marginTop: "10px", marginLeft: settings.cfg.startpageType === "expanded" && @@ -428,8 +430,11 @@ export const StartPageViewPublication = () => { settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position === "center" && !isMobile - ? "end" - : "none", + ? "end" : settings.cfg.startpageType === "expanded" && + settings.cfg.startpage.position === "center" && + isMobile || settings.cfg.startpageType === "centered" && + isMobile ? "center" + : "start", color: theme.palette.primary.main, overflow: "hidden", textOverflow: "ellipsis",