Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squzanswerer!176
This commit is contained in:
Nastya 2024-10-26 17:25:54 +00:00
commit c8d157e9c4
12 changed files with 227 additions and 91 deletions

@ -252,3 +252,40 @@ export function sendFC({ questionId, body, qid, preview }: SendFCParams) {
method: "POST", method: "POST",
}); });
} }
//форма контактов
export type SendResultParams = {
questionId: string;
pointsSum: number;
qid: string;
preview: boolean;
};
export function sendResult({ questionId, pointsSum, qid, preview }: SendResultParams) {
if (preview) return;
const formData = new FormData();
// const keysBody = Object.keys(body)
// const content:any = {}
// fields.forEach((key) => {
// if (keysBody.includes(key)) content[key] = body.key
// })
const answers = [
{
question_id: questionId,
content: pointsSum.toString(),
result: false,
qid,
},
];
formData.append("answers", JSON.stringify(answers));
formData.append("qid", qid);
return publicationMakeRequest({
url: domain + `/answer/v1.0.0/answer`,
body: formData,
method: "POST",
});
}

@ -20,9 +20,13 @@ import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage"; import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import "moment/dist/locale/ru";
moment.locale("ru"); moment.locale("ru");
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
console.log(localeText);
console.log(moment);
type Props = { type Props = {
quizSettings?: QuizSettings; quizSettings?: QuizSettings;
quizId: string; quizId: string;

@ -24,11 +24,14 @@ import { NameplateLogo } from "@icons/NameplateLogo";
import type { FormContactFieldData, FormContactFieldName } from "@model/settingsData"; import type { FormContactFieldData, FormContactFieldName } from "@model/settingsData";
import type { QuizQuestionResult } from "@model/questionTypes/result"; import type { QuizQuestionResult } from "@model/questionTypes/result";
import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { isProduction } from "@/utils/defineDomain";
type Props = { type Props = {
currentQuestion: AnyTypedQuizQuestion; currentQuestion: AnyTypedQuizQuestion;
onShowResult: () => void; onShowResult: () => void;
}; };
//Костыль для особого квиза. Для него не нужно показывать email адрес
const isDisableEmail = window.location.pathname.includes("/377c7570-1bee-4320-ac1e-d731b6223ce8");
export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
const theme = useTheme(); const theme = useTheme();
@ -114,7 +117,8 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
async function handleShowResultsClick() { async function handleShowResultsClick() {
const FC = settings.cfg.formContact.fields; const FC = settings.cfg.formContact.fields;
if (FC["email"].used !== EMAIL_REGEXP.test(email)) {
if (!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)) {
return enqueueSnackbar("введена некорректная почта"); return enqueueSnackbar("введена некорректная почта");
} }
@ -250,6 +254,9 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
setText={setText} setText={setText}
adress={adress} adress={adress}
setAdress={setAdress} setAdress={setAdress}
crutch={{
disableEmail: isDisableEmail,
}}
/> />
</Box> </Box>
<Box <Box
@ -315,9 +322,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
<Box <Box
component={Link} component={Link}
target={"_blank"} target={"_blank"}
href={`https://${ href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
window.location.hostname[0] === "s" ? "s" : ""
}quiz.pena.digital/squiz/quiz/logo?q=${quizId}`}
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",

@ -19,6 +19,9 @@ type InputsProps = {
setText: Dispatch<SetStateAction<string>>; setText: Dispatch<SetStateAction<string>>;
adress: string; adress: string;
setAdress: Dispatch<SetStateAction<string>>; setAdress: Dispatch<SetStateAction<string>>;
crutch: {
disableEmail: boolean;
};
}; };
export const Inputs = ({ export const Inputs = ({
@ -32,6 +35,7 @@ export const Inputs = ({
setText, setText,
adress, adress,
setAdress, setAdress,
crutch,
}: InputsProps) => { }: InputsProps) => {
const { settings } = useQuizSettings(); const { settings } = useQuizSettings();
const FC = settings.cfg.formContact.fields; const FC = settings.cfg.formContact.fields;
@ -95,7 +99,7 @@ export const Inputs = ({
return ( return (
<> <>
{FC["name"].used ? Name : <></>} {FC["name"].used ? Name : <></>}
{FC["email"].used ? Email : <></>} {FC["email"].used && !crutch.disableEmail ? Email : <></>}
{FC["phone"].used ? Phone : <></>} {FC["phone"].used ? Phone : <></>}
{FC["text"].used ? Text : <></>} {FC["text"].used ? Text : <></>}
{FC["address"].used ? Adress : <></>} {FC["address"].used ? Adress : <></>}

@ -7,7 +7,6 @@ import { AnyTypedQuizQuestion, QuizQuestionVariant } from "@/index";
export const PointSystemResultList = () => { export const PointSystemResultList = () => {
const theme = useTheme(); const theme = useTheme();
const { questions } = useQuizSettings(); const { questions } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers); const answers = useQuizViewStore((state) => state.answers);
@ -20,7 +19,6 @@ export const PointSystemResultList = () => {
let currentVariants = currentQuestion.content.variants; let currentVariants = currentQuestion.content.variants;
const currentAnswer = answers.find((a) => a.questionId === currentQuestion.id); const currentAnswer = answers.find((a) => a.questionId === currentQuestion.id);
const answeredVariant = currentVariants.find((v, i) => { const answeredVariant = currentVariants.find((v, i) => {
if (v.id === currentAnswer?.answer) { if (v.id === currentAnswer?.answer) {
answerIndex = i; answerIndex = i;
@ -55,14 +53,20 @@ export const PointSystemResultList = () => {
> >
{currentQuestion.page + 1}. {currentQuestion.page + 1}.
</Typography> </Typography>
<Typography>{currentQuestion.title || "Вопрос без названия"}</Typography> <Typography
sx={{
color: theme.palette.text.primary,
}}
>
{currentQuestion.title || "Вопрос без названия"}
</Typography>
</Box> </Box>
<Typography <Typography
sx={{ sx={{
color: answeredVariant?.points ? "inherit" : theme.palette.grey[500], color: answeredVariant?.points ? theme.palette.primary.main : theme.palette.grey[500],
}} }}
> >
{answeredVariant?.points || "0"}/1 {answeredVariant?.points || "0"}
</Typography> </Typography>
</Box> </Box>
<Box <Box

@ -22,7 +22,8 @@ import { notReachable } from "@utils/notReachable";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import { DESIGN_LIST } from "@/utils/designList"; import { DESIGN_LIST } from "@/utils/designList";
import type { ReactNode } from "react"; import { type ReactNode } from "react";
import { isProduction } from "@/utils/defineDomain";
type Props = { type Props = {
currentQuestion: RealTypedQuizQuestion; currentQuestion: RealTypedQuizQuestion;
@ -95,7 +96,7 @@ export const Question = ({
{show_badge && ( {show_badge && (
<Link <Link
target="_blank" target="_blank"
href={`https://${window.location.hostname.includes("s") ? "s" : ""}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`} href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
sx={{ sx={{
mt: "20px", mt: "20px",
alignSelf: "end", alignSelf: "end",
@ -123,7 +124,11 @@ export const Question = ({
</Box> </Box>
</Box> </Box>
{questionSelect} {questionSelect}
<Footer stepNumber={currentQuestionStepNumber} prevButton={prevButton} nextButton={nextButton} /> <Footer
stepNumber={currentQuestionStepNumber}
prevButton={prevButton}
nextButton={nextButton}
/>
</Box> </Box>
</Box> </Box>
); );
@ -140,7 +145,12 @@ function QuestionByType({ question, stepNumber }: { question: RealTypedQuizQuest
case "emoji": case "emoji":
return <Emoji currentQuestion={question} />; return <Emoji currentQuestion={question} />;
case "text": case "text":
return <Text currentQuestion={question} stepNumber={stepNumber} />; return (
<Text
currentQuestion={question}
stepNumber={stepNumber}
/>
);
case "select": case "select":
return <Select currentQuestion={question} />; return <Select currentQuestion={question} />;
case "date": case "date":

@ -16,6 +16,9 @@ import type { QuizQuestionResult } from "@/model/questionTypes/result";
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe"; import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
import { TextAccordion } from "./tools/TextAccordion"; import { TextAccordion } from "./tools/TextAccordion";
import { PointSystemResultList } from "./PointSystemResultList"; import { PointSystemResultList } from "./PointSystemResultList";
import { enqueueSnackbar } from "notistack";
import { sendFC, sendResult } from "@/api/quizRelase";
import { isProduction } from "@/utils/defineDomain";
type ResultFormProps = { type ResultFormProps = {
resultQuestion: QuizQuestionResult; resultQuestion: QuizQuestionResult;
@ -25,8 +28,10 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const isTablet = useRootContainerSize() < 1000; const isTablet = useRootContainerSize() < 1000;
const { settings, show_badge, quizId } = useQuizSettings(); const { settings, show_badge, quizId, questions, preview } = useQuizSettings();
const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep); const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
//Список засчитанных баллов для балловых квизов
const pointsSum = useQuizViewStore((state) => state.pointsSum);
const spec = settings.cfg.spec; const spec = settings.cfg.spec;
const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber); const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber);
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber); const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
@ -36,6 +41,41 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
yandexMetrics.resultIdShown(resultQuestion.id); yandexMetrics.resultIdShown(resultQuestion.id);
}, [resultQuestion.id, vkMetrics, yandexMetrics]); }, [resultQuestion.id, vkMetrics, yandexMetrics]);
useEffect(() => {
(async () => {
if (!settings.cfg.showfc) {
try {
await sendFC({
questionId: resultQuestion.id,
body: {},
qid: quizId,
preview,
});
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
} catch (e) {
enqueueSnackbar("Заявка не может быть отправлена");
}
}
if (Boolean(settings.cfg.score)) {
try {
await sendResult({
questionId: resultQuestion.id,
pointsSum,
qid: quizId,
preview,
});
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
} catch (e) {
enqueueSnackbar("Количество баллов не может быть отправлено");
}
}
})();
}, []);
return ( return (
<Box <Box
sx={{ sx={{
@ -188,32 +228,53 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
</Typography> </Typography>
)} )}
{settings.cfg?.score && ( {settings.cfg?.score && (
<TextAccordion <>
headerText={ <Typography
<Typography
sx={{
color: theme.palette.primary.main,
"&:hover": {
color: theme.palette.primary.dark,
},
}}
>
Посмотреть ответы
</Typography>
}
sx={{
mt: "60px",
width: "100%",
}}
>
<Box
sx={{ sx={{
mt: "25px", color: theme.palette.primary.main,
fontSize: "30px",
m: "30px 0",
fontWeight: 600,
}} }}
> >
<PointSystemResultList /> Ваши баллы
</Box> </Typography>
</TextAccordion> <Typography
sx={{
color: theme.palette.primary.main,
fontSize: "30px",
fontWeight: 600,
}}
>
{pointsSum} из {questions.filter((e) => e.type != "result").length}
</Typography>
<TextAccordion
headerText={
<Typography
sx={{
color: theme.palette.primary.main,
"&:hover": {
color: theme.palette.primary.dark,
},
}}
>
Посмотреть ответы
</Typography>
}
sx={{
mt: "60px",
width: "100%",
}}
>
<Box
sx={{
mt: "25px",
}}
>
<PointSystemResultList />
</Box>
</TextAccordion>
</>
)} )}
</Box> </Box>
</Box> </Box>
@ -221,9 +282,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
<Box <Box
component={Link} component={Link}
target={"_blank"} target={"_blank"}
href={`https://${ href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
window.location.hostname.includes("s") ? "s" : ""
}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",

@ -16,6 +16,8 @@ import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe"; import QuizVideo from "@/ui_kit/VideoIframe/VideoIframe";
import { isProduction } from "@/utils/defineDomain";
export const StartPageViewPublication = () => { export const StartPageViewPublication = () => {
const theme = useTheme(); const theme = useTheme();
const { settings, show_badge, quizId, questions } = useQuizSettings(); const { settings, show_badge, quizId, questions } = useQuizSettings();
@ -135,8 +137,7 @@ export const StartPageViewPublication = () => {
<Box <Box
component={Link} component={Link}
target={"_blank"} target={"_blank"}
href={`https://${window.location.hostname[0] === "s" ? "s" : "" href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}
}quiz.pena.digital/squiz/quiz/logo?q=${quizId}`}
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",

@ -5,13 +5,13 @@ import { DateCalendar } from "@mui/x-date-pickers";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import type { Moment } from "moment"; import type { Moment } from "moment";
import moment from "moment"; import moment from "moment";
import { Paper, useTheme } from "@mui/material"; import { Box, Paper, TextField, useTheme } from "@mui/material";
import { useRootContainerSize } from "@/contexts/RootContainerWidthContext"; import { useRootContainerSize } from "@/contexts/RootContainerWidthContext";
type DateProps = { type DateProps = {
currentQuestion: QuizQuestionDate; currentQuestion: QuizQuestionDate;
}; };
console.log(moment.locale());
export default ({ currentQuestion }: DateProps) => { export default ({ currentQuestion }: DateProps) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 690; const isMobile = useRootContainerSize() < 690;
@ -46,48 +46,55 @@ export default ({ currentQuestion }: DateProps) => {
display: "inline-flex", display: "inline-flex",
flexWrap: "wrap", flexWrap: "wrap",
marginTop: "20px", marginTop: "20px",
p: "20px",
}} }}
> >
<DateCalendar <Box>
sx={{ <span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>От</span>
"& .MuiInputBase-root": { <DateCalendar
backgroundColor: settings.cfg.design sx={{
? quizThemes[settings.cfg.theme].isLight "& .MuiInputBase-root": {
? "#F2F3F7" backgroundColor: settings.cfg.design
: "rgba(154,154,175, 0.2)" ? quizThemes[settings.cfg.theme].isLight
: quizThemes[settings.cfg.theme].isLight ? "#F2F3F7"
? "white" : "rgba(154,154,175, 0.2)"
: theme.palette.background.default, : quizThemes[settings.cfg.theme].isLight
borderRadius: "10px", ? "white"
maxWidth: "250px", : theme.palette.background.default,
pr: "30px", borderRadius: "10px",
"& input": { py: "11px", pl: "20px", lineHeight: "19px" }, maxWidth: "250px",
"& fieldset": { borderColor: "#9A9AAF" }, pr: "30px",
}, "& input": { py: "11px", pl: "20px", lineHeight: "19px" },
}} "& fieldset": { borderColor: "#9A9AAF" },
value={currentFrom} },
onChange={(data) => onDateChange(data, 0)} }}
/> value={currentFrom}
<DateCalendar onChange={(data) => onDateChange(data, 0)}
sx={{ />
"& .MuiInputBase-root": { </Box>
backgroundColor: settings.cfg.design <Box>
? quizThemes[settings.cfg.theme].isLight <span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>До</span>
? "#F2F3F7" <DateCalendar
: "rgba(154,154,175, 0.2)" sx={{
: quizThemes[settings.cfg.theme].isLight "& .MuiInputBase-root": {
? "white" backgroundColor: settings.cfg.design
: theme.palette.background.default, ? quizThemes[settings.cfg.theme].isLight
borderRadius: "10px", ? "#F2F3F7"
maxWidth: "250px", : "rgba(154,154,175, 0.2)"
pr: "30px", : quizThemes[settings.cfg.theme].isLight
"& input": { py: "11px", pl: "20px", lineHeight: "19px" }, ? "white"
"& fieldset": { borderColor: "#9A9AAF" }, : theme.palette.background.default,
}, borderRadius: "10px",
}} maxWidth: "250px",
value={currentTo} pr: "30px",
onChange={(data) => onDateChange(data, 1)} "& input": { py: "11px", pl: "20px", lineHeight: "19px" },
/> "& fieldset": { borderColor: "#9A9AAF" },
},
}}
value={currentTo}
onChange={(data) => onDateChange(data, 1)}
/>
</Box>
</Paper> </Paper>
); );
}; };

@ -28,7 +28,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
{currentQuestion.title} {currentQuestion.title}
</Typography> </Typography>
<RadioGroup <RadioGroup
name={currentQuestion.id} name={currentQuestion.id.toString()}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
sx={{ sx={{
display: "flex", display: "flex",

@ -5,8 +5,13 @@
let domain = "https://hbpn.link"; let domain = "https://hbpn.link";
const currentDomain = location.hostname; const currentDomain = location.hostname;
//туризм больше не в исключениях const isProduction = !(
if (currentDomain === "s.hbpn.link" || currentDomain.includes("localhost") || currentDomain.includes("127.0.0.1")) currentDomain === "s.hbpn.link" ||
domain = "https://s.hbpn.link"; currentDomain.includes("localhost") ||
currentDomain.includes("127.0.0.1")
);
export { domain }; //туризм больше не в исключениях
if (!isProduction) domain = "https://s.hbpn.link";
export { domain, isProduction };

@ -127,6 +127,7 @@ export function useQuestionFlowControl() {
//Анализ результата по количеству баллов //Анализ результата по количеству баллов
const findResultPointsLogic = useCallback(() => { const findResultPointsLogic = useCallback(() => {
//Отбираем из массива только тип резулт И результы с информацией о ожидаемых баллах И те результы, чьи суммы баллов меньше или равны насчитанным баллам юзера //Отбираем из массива только тип резулт И результы с информацией о ожидаемых баллах И те результы, чьи суммы баллов меньше или равны насчитанным баллам юзера
const results = sortedQuestions.filter( const results = sortedQuestions.filter(
(e) => e.type === "result" && e.content.rule.minScore !== undefined && e.content.rule.minScore <= pointsSum (e) => e.type === "result" && e.content.rule.minScore !== undefined && e.content.rule.minScore <= pointsSum
); );
@ -136,7 +137,6 @@ export function useQuestionFlowControl() {
); );
//Извлекаем самое большое число //Извлекаем самое большое число
const indexOfNext = Math.max(...numbers); const indexOfNext = Math.max(...numbers);
//Отдаём индекс нужного нам результата //Отдаём индекс нужного нам результата
return results[numbers.indexOf(indexOfNext)]; return results[numbers.indexOf(indexOfNext)];
}, [pointsSum, sortedQuestions]); }, [pointsSum, sortedQuestions]);