diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx
index 19dc1e9..8a8c040 100644
--- a/lib/components/QuizAnswerer.tsx
+++ b/lib/components/QuizAnswerer.tsx
@@ -13,7 +13,7 @@ import { handleComponentError } from "@utils/handleComponentError";
import lightTheme from "@utils/themes/light";
import moment from "moment";
import { SnackbarProvider } from "notistack";
-import { startTransition, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
+import React, { startTransition, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
@@ -51,6 +51,16 @@ function QuizAnswererInner({
const yandexMetrics = useYandexMetricsGoals(quizSettings?.settings.cfg.yandexMetricsNumber);
const r = useQuizStore();
const { settings, questions } = useQuizStore();
+ const [currentTime, setCurrentTime] = React.useState(moment());
+
+ // Реактивный таймер для отслеживания времени
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setCurrentTime(moment());
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
useEffect(() => {
addquizid(quizId);
@@ -114,6 +124,19 @@ function QuizAnswererInner({
return ;
}
+ // Проверяем, истекло ли время для overTime
+ const overTimeConfig = settings?.cfg?.overTime;
+ const isOverTimeEnabled = overTimeConfig?.enabled;
+ const isTimeExpired =
+ isOverTimeEnabled && overTimeConfig?.endsAt
+ ? currentTime.isAfter(moment(overTimeConfig.endsAt)) || currentTime.isSame(moment(overTimeConfig.endsAt))
+ : false;
+
+ // Если время истекло и нет стартовой страницы, показываем ApologyPage
+ if (isTimeExpired && settings?.cfg?.noStartPage) {
+ return ;
+ }
+
const quizContainer = (
;
+};
+
+export const OverTime = ({ sx }: OverTimeProps) => {
+ const theme = useTheme();
+ const { settings } = useQuizStore();
+ const { t } = useTranslation();
+ const [currentTime, setCurrentTime] = React.useState(moment());
+
+ // Реактивный таймер с useEffect
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setCurrentTime(moment());
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ // Проверяем, включен ли overTime
+ const overTimeConfig = settings?.cfg?.overTime;
+ const isEnabled = overTimeConfig?.enabled;
+
+ // Если не включен, не показываем карточку
+ if (!isEnabled) {
+ return null;
+ }
+
+ // Функция для расчета времени до окончания
+ const calculateTimeLeft = (now: moment.Moment) => {
+ // Для тестирования: добавляем 2 часа к текущему времени
+ const testEndsAt = moment().add(2, "hours");
+ const endsAt = overTimeConfig?.endsAt ? moment(overTimeConfig.endsAt) : testEndsAt;
+
+ if (endsAt.isBefore(now) || endsAt.isSame(now)) {
+ return { days: 0, hours: 0, minutes: 0, seconds: 0 };
+ }
+
+ const duration = moment.duration(endsAt.diff(now));
+
+ return {
+ days: Math.floor(duration.asDays()),
+ hours: duration.hours(),
+ minutes: duration.minutes(),
+ seconds: duration.seconds(),
+ };
+ };
+
+ const { days, hours, minutes, seconds } = calculateTimeLeft(currentTime);
+
+ return (
+
+
+ {overTimeConfig?.description || t("Quiz will become unavailable in")}
+
+
+
+
+ {days}
+
+
+ {t("days")}
+
+
+ :
+
+
+ {hours}
+
+
+ {t("hours")}
+
+
+ :
+
+
+ {minutes}
+
+
+ {t("minutes")}
+
+
+ :
+
+
+ {seconds}
+
+
+ {t("seconds")}
+
+
+
+
+ );
+};
diff --git a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx
index 5985654..5c5b127 100644
--- a/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx
+++ b/lib/components/ViewPublicationPage/StartPageViewPublication/index.tsx
@@ -1,6 +1,9 @@
import { Box, Button, ButtonBase, Link, Paper, Typography, useTheme } from "@mui/material";
+import moment from "moment";
+import React from "react";
import { QuizPreviewLayoutByType } from "./QuizPreviewLayoutByType";
+import { OverTime } from "./OverTime";
import { useQuizStore } from "@/stores/useQuizStore";
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
@@ -24,6 +27,16 @@ export const StartPageViewPublication = () => {
const { settings, show_badge, quizId, questions } = useQuizStore();
const { isMobileDevice } = useUADevice();
const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
+ const [currentTime, setCurrentTime] = React.useState(moment());
+
+ // Реактивный таймер для обновления времени
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setCurrentTime(moment());
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
const size = useRootContainerSize();
const isMobile = size < 700;
@@ -39,15 +52,6 @@ export const StartPageViewPublication = () => {
yandexMetrics.phoneNumberOpened();
};
- console.log("------------------------------------------------");
- console.log("Background type:", settings.cfg.startpage.background.type);
- console.log("Is image type:", settings.cfg.startpage.background.type === "image");
- console.log("Is video type:", settings.cfg.startpage.background.type === "video");
- console.log("Video URL:", settings.cfg.startpage.background.video);
- console.log("Desktop background:", settings.cfg.startpage.background.desktop);
- console.log("Startpage type:", settings.cfg.startpageType);
- console.log("------------------------------------------------");
-
const background =
settings.cfg.startpage.background.type === "image" ? (
{
) : settings.cfg.startpage.background.type === "video" ? (
settings.cfg.startpage.background.video ? (
(() => {
- console.log("Rendering QuizVideo with URL:", settings.cfg.startpage.background.video);
return (
{
{
{settings.cfg.info.orgname}
+ {((settings.cfg.startpageType === "expanded" && settings.cfg.startpage.position !== "center") ||
+ (isMobile && settings.cfg.startpageType === "expanded")) && }
);
@@ -159,12 +170,12 @@ export const StartPageViewPublication = () => {
alignItems: "center",
gap: "7px",
textDecoration: "none",
- marginLeft:
+ mr:
settings.cfg.startpageType === "expanded" &&
settings.cfg.startpage.position === "center" &&
!isTablet &&
!isMobile
- ? "61px"
+ ? "27px"
: undefined,
}}
>
@@ -182,6 +193,14 @@ export const StartPageViewPublication = () => {
(question) => question.type !== null && question.type !== "result"
).length;
+ // Проверяем, истекло ли время для overTime
+ const overTimeConfig = settings?.cfg?.overTime;
+ const isOverTimeEnabled = overTimeConfig?.enabled;
+ const isTimeExpired =
+ isOverTimeEnabled && overTimeConfig?.endsAt
+ ? currentTime.isAfter(moment(overTimeConfig.endsAt)) || currentTime.isSame(moment(overTimeConfig.endsAt))
+ : false;
+
const onQuizStart = () => {
setCurrentQuizStep("question");
@@ -289,7 +308,7 @@ export const StartPageViewPublication = () => {
+
+ {((!isMobile && settings.cfg.startpageType === "centered") ||
+ (!isMobile &&
+ settings.cfg.startpageType === "expanded" &&
+ settings.cfg.startpage.position === "center")) && (
+
+ )}
- {show_badge && PenaBadge}
+ {show_badge && PenaBadge}
+
>
}
diff --git a/lib/model/settingsData.ts b/lib/model/settingsData.ts
index 47d354c..bbfc431 100644
--- a/lib/model/settingsData.ts
+++ b/lib/model/settingsData.ts
@@ -123,6 +123,11 @@ export interface QuizConfig {
yandexMetricsNumber?: number;
vkMetricsNumber?: number;
backBlocked?: boolean;
+ overTime?: {
+ enabled: boolean;
+ endsAt: number;
+ description: string;
+ };
}
export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address";
diff --git a/lib/utils/themes/Publication/genericPublication.ts b/lib/utils/themes/Publication/genericPublication.ts
index 2cf5050..1635bfd 100644
--- a/lib/utils/themes/Publication/genericPublication.ts
+++ b/lib/utils/themes/Publication/genericPublication.ts
@@ -3,6 +3,14 @@ import theme from "../generic";
const themePublic = createTheme({
...theme,
+ palette: {
+ ...theme.palette,
+ // Переопределяем цвета для публичных тем
+ paperBackground: "#F2F3F7",
+ paperText: "#333647",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#FFFFFF",
+ },
components: {
...theme.components,
MuiButton: {
diff --git a/lib/utils/themes/Publication/themePublication.ts b/lib/utils/themes/Publication/themePublication.ts
index d3e91a7..8ace83c 100644
--- a/lib/utils/themes/Publication/themePublication.ts
+++ b/lib/utils/themes/Publication/themePublication.ts
@@ -7,6 +7,7 @@ import type { QuizTheme } from "@model/settingsData";
const StandardTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#7E2AEA",
dark: "#581CA7",
@@ -28,6 +29,7 @@ const StandardTheme = createTheme({
const StandardDarkTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#7E2AEA",
dark: "#581CA7",
@@ -43,12 +45,18 @@ const StandardDarkTheme = createTheme({
background: {
default: "#333647",
},
- },
+ // Темные цвета для OverTime
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
+ } satisfies any,
});
const PinkTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#D34085",
dark: "#AD376E",
@@ -70,6 +78,7 @@ const PinkTheme = createTheme({
const PinkDarkTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#D34085",
dark: "#AD376E",
@@ -85,12 +94,17 @@ const PinkDarkTheme = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const BlackWhiteTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#4E4D51",
dark: "#323232",
@@ -112,6 +126,7 @@ const BlackWhiteTheme = createTheme({
const OliveTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#758E4F",
dark: "#4A6324",
@@ -133,6 +148,7 @@ const OliveTheme = createTheme({
const PurpleTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#7E2AEA",
dark: "#581CA7",
@@ -154,6 +170,7 @@ const PurpleTheme = createTheme({
const YellowTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#F2B133",
dark: "#E6A11C",
@@ -175,6 +192,7 @@ const YellowTheme = createTheme({
const GoldDarkTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#E6AA37",
dark: "#E19A13",
@@ -190,12 +208,17 @@ const GoldDarkTheme = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const BlueTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#4964ED",
dark: "#354DC8",
@@ -217,6 +240,7 @@ const BlueTheme = createTheme({
const BlueDarkTheme = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#07A0C3",
dark: "#0A819C",
@@ -232,12 +256,17 @@ const BlueDarkTheme = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const crutch_FurnitureABC = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#F2B133",
dark: "#E6A11C",
@@ -253,12 +282,17 @@ const crutch_FurnitureABC = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design1 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#F2B133",
dark: "#E6A11C",
@@ -274,12 +308,17 @@ const Design1 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design2 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#3D9A63",
dark: "#247746",
@@ -295,12 +334,17 @@ const Design2 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design3 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#4B6A99",
dark: "#32507D",
@@ -322,6 +366,7 @@ const Design3 = createTheme({
const Design4 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#FF9431",
dark: "#EF8624",
@@ -337,12 +382,17 @@ const Design4 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design5 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#2D99BA",
dark: "#1A84A6",
@@ -358,12 +408,17 @@ const Design5 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design6 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#D34085",
dark: "#AD376E",
@@ -379,12 +434,17 @@ const Design6 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design7 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#B47C3B",
dark: "#9C6524",
@@ -400,12 +460,17 @@ const Design7 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design8 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#F0B136",
dark: "#E19F1D",
@@ -421,12 +486,17 @@ const Design8 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design9 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#678F48",
dark: "#527933",
@@ -442,12 +512,17 @@ const Design9 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
const Design10 = createTheme({
...themePublic,
palette: {
+ ...themePublic.palette,
primary: {
main: "#3666AF",
dark: "#1B478A",
@@ -463,6 +538,10 @@ const Design10 = createTheme({
background: {
default: "#333647",
},
+ paperBackground: "#262835",
+ paperText: "#ffffff",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#31333f",
},
});
diff --git a/lib/utils/themes/generic.ts b/lib/utils/themes/generic.ts
index e030d71..5016652 100644
--- a/lib/utils/themes/generic.ts
+++ b/lib/utils/themes/generic.ts
@@ -18,6 +18,13 @@ const theme = createTheme({
xl: 1536,
},
},
+ palette: {
+ // Базовые цвета для OverTime компонента
+ paperBackground: "#F2F3F7",
+ paperText: "#333647",
+ paperSecondaryText: "#9A9AAF",
+ paperBlockBackground: "#FFFFFF",
+ },
components: {
MuiCssBaseline: {
styleOverrides: {
diff --git a/lib/utils/themes/mui.d.ts b/lib/utils/themes/mui.d.ts
index 150b8d5..2ca764d 100644
--- a/lib/utils/themes/mui.d.ts
+++ b/lib/utils/themes/mui.d.ts
@@ -1,5 +1,3 @@
-import "@material-ui/styles";
-
declare module "@mui/material/styles" {
interface Palette {
lightPurple: Palette["primary"];
@@ -13,6 +11,10 @@ declare module "@mui/material/styles" {
orange: Palette["primary"];
navbarbg: Palette["primary"];
ownPlaceholder: Palette["primary"];
+ paperBackground: string;
+ paperText: string;
+ paperSecondaryText: string;
+ paperBlockBackground: string;
}
interface PaletteOptions {
lightPurple?: PaletteOptions["primary"];
@@ -26,6 +28,10 @@ declare module "@mui/material/styles" {
orange?: PaletteOptions["primary"];
navbarbg?: PaletteOptions["primary"];
ownPlaceholder?: PaletteOptions["primary"];
+ paperBackground?: string;
+ paperText?: string;
+ paperSecondaryText?: string;
+ paperBlockBackground?: string;
}
interface TypographyVariants {
infographic: React.CSSProperties;
diff --git a/public/locales/en.json b/public/locales/en.json
index b6d0001..1fe038b 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -56,5 +56,11 @@
"Step": "Step",
"questions are not ready yet": "There are no questions for the audience yet. Please wait",
"Add your image": "Add your image",
- "select emoji": "select emoji"
+ "select emoji": "select emoji",
+ "quiz time expired": "Quiz time has expired",
+ "Quiz will become unavailable in": "Quiz will become unavailable in",
+ "days": "days",
+ "hours": "hours",
+ "minutes": "min.",
+ "seconds": "sec."
}
diff --git a/public/locales/ru.json b/public/locales/ru.json
index d346874..f17f6b6 100644
--- a/public/locales/ru.json
+++ b/public/locales/ru.json
@@ -59,5 +59,11 @@
"select emoji": "выберите смайлик",
"Please complete the phone number": "Пожалуйста, завершите номер телефона",
"Please enter a valid email": "Пожалуйста, введите корректную почту",
- "Please enter a valid phone number": "Пожалуйста, введите корректный номер телефона"
+ "Please enter a valid phone number": "Пожалуйста, введите корректный номер телефона",
+ "quiz time expired": "Время квиза истекло",
+ "Quiz will become unavailable in": "Квиз станет недоступен через",
+ "days": "дней",
+ "hours": "часов",
+ "minutes": "мин.",
+ "seconds": "сек."
}
diff --git a/public/locales/uz.json b/public/locales/uz.json
index a272df0..222c163 100644
--- a/public/locales/uz.json
+++ b/public/locales/uz.json
@@ -56,5 +56,11 @@
"Step": "Qadam",
"questions are not ready yet": "Tomoshabinlar uchun hozircha savollar yo'q. Iltimos kuting",
"Add your image": "Rasmingizni qo'shing",
- "select emoji": "emoji tanlang"
+ "select emoji": "emoji tanlang",
+ "quiz time expired": "Test vaqti tugadi",
+ "Quiz will become unavailable in": "Test quyidagi vaqtda mavjud bo'lmaydi",
+ "days": "kun",
+ "hours": "soat",
+ "minutes": "daq.",
+ "seconds": "son."
}
diff --git a/src/i18n/i18nWidget.ts b/src/i18n/i18nWidget.ts
index 50f581e..3289860 100644
--- a/src/i18n/i18nWidget.ts
+++ b/src/i18n/i18nWidget.ts
@@ -73,6 +73,12 @@ const r = {
"Add your image": "Добавьте своё изображение",
"select emoji": "выберите смайлик",
"Please complete the phone number": "Пожалуйста, заполните номер телефона до конца",
+ "quiz time expired": "Время квиза истекло",
+ "Quiz will become unavailable in": "Квиз станет недоступен через",
+ days: "дней",
+ hours: "часов",
+ minutes: "мин.",
+ seconds: "сек.",
"": "", // Пустой ключ для fallback
},
},
@@ -137,6 +143,12 @@ const r = {
"Add your image": "Add your image",
"select emoji": "select emoji",
"Please complete the phone number": "Please complete the phone number",
+ "quiz time expired": "Quiz time has expired",
+ "Quiz will become unavailable in": "Quiz will become unavailable in",
+ days: "days",
+ hours: "hours",
+ minutes: "min.",
+ seconds: "sec.",
"": "", // Пустой ключ для fallback
},
},
@@ -201,6 +213,12 @@ const r = {
"Add your image": "Rasmingizni qo'shing",
"select emoji": "emoji tanlang",
"Please complete the phone number": "Iltimos, telefon raqamini to'liq kiriting",
+ "quiz time expired": "Test vaqti tugadi",
+ "Quiz will become unavailable in": "Test quyidagi vaqtda mavjud bo'lmaydi",
+ days: "kun",
+ hours: "soat",
+ minutes: "daq.",
+ seconds: "son.",
"": "", // Пустой ключ для fallback
},
},
diff --git a/src/types/mui-palette.d.ts b/src/types/mui-palette.d.ts
new file mode 100644
index 0000000..1fbab6b
--- /dev/null
+++ b/src/types/mui-palette.d.ts
@@ -0,0 +1,14 @@
+declare module "@mui/material/styles" {
+ interface Palette {
+ paperBackground: string;
+ paperText: string;
+ paperSecondaryText: string;
+ paperBlockBackground: string;
+ }
+ interface PaletteOptions {
+ paperBackground?: string;
+ paperText?: string;
+ paperSecondaryText?: string;
+ paperBlockBackground?: string;
+ }
+}