From c15516344a2ac50f2e9998f479954f5a2467c9c3 Mon Sep 17 00:00:00 2001 From: IlyaDoronin Date: Mon, 6 May 2024 16:47:19 +0300 Subject: [PATCH] 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, + }; +}