Merge branch 'dev' into refactor
This commit is contained in:
commit
1e3fc3ae26
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,4 +22,4 @@ widget
|
|||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
@ -1,38 +1,32 @@
|
|||||||
include:
|
include:
|
||||||
- project: "devops/pena-continuous-integration"
|
- project: "devops/pena-continuous-integration"
|
||||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
file: "/templates/docker/build-template.gitlab-ci.yml"
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/templates/docker/clean-template.gitlab-ci.yml"
|
|
||||||
- project: "devops/pena-continuous-integration"
|
- project: "devops/pena-continuous-integration"
|
||||||
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
||||||
stages:
|
stages:
|
||||||
- clean
|
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
clear-old-images:
|
|
||||||
extends: .clean_template
|
|
||||||
variables:
|
|
||||||
STAGING_BRANCH: "main"
|
|
||||||
PRODUCTION_BRANCH: "main"
|
|
||||||
image:
|
|
||||||
name: docker/compose:1.28.0
|
|
||||||
entrypoint: [""]
|
|
||||||
before_script:
|
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
||||||
- docker images
|
|
||||||
script:
|
|
||||||
- docker system prune -af
|
|
||||||
build-app:
|
build-app:
|
||||||
|
tags:
|
||||||
|
- frontbuild
|
||||||
extends: .build_template
|
extends: .build_template
|
||||||
variables:
|
variables:
|
||||||
DOCKER_BUILD_PATH: "./Dockerfile"
|
DOCKER_BUILD_PATH: "./Dockerfile"
|
||||||
STAGING_BRANCH: "main"
|
STAGING_BRANCH: "staging"
|
||||||
PRODUCTION_BRANCH: "main"
|
PRODUCTION_BRANCH: "main"
|
||||||
|
|
||||||
deploy-to-staging:
|
deploy-to-staging:
|
||||||
extends: .deploy_template
|
extends: .deploy_template
|
||||||
variables:
|
rules:
|
||||||
DEPLOY_TO: "staging"
|
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
||||||
BRANCH: "main"
|
tags:
|
||||||
|
- front
|
||||||
|
- staging
|
||||||
|
|
||||||
|
deploy-to-prod:
|
||||||
|
extends: .deploy_template
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
||||||
|
tags:
|
||||||
|
- front
|
||||||
|
- prod
|
||||||
|
@ -10,5 +10,6 @@ RUN yarn build
|
|||||||
|
|
||||||
FROM nginx:latest as result
|
FROM nginx:latest as result
|
||||||
WORKDIR /usr/share/nginx/html
|
WORKDIR /usr/share/nginx/html
|
||||||
COPY --from=build /usr/app/build/ /usr/share/nginx/html
|
COPY --from=build /usr/app/dist/ /usr/share/nginx/html
|
||||||
|
COPY pub.js /usr/share/nginx/html/export/pub.js
|
||||||
COPY hub.conf /etc/nginx/conf.d/default.conf
|
COPY hub.conf /etc/nginx/conf.d/default.conf
|
||||||
|
8
deployments/main/docker-compose.yaml
Normal file
8
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
respondent:
|
||||||
|
container_name: respondent
|
||||||
|
restart: unless-stopped
|
||||||
|
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
hostname: respondent
|
||||||
|
tty: true
|
||||||
|
|
@ -2,12 +2,7 @@ services:
|
|||||||
respondent:
|
respondent:
|
||||||
container_name: respondent
|
container_name: respondent
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
networks:
|
|
||||||
- marketplace_penahub_frontend
|
|
||||||
hostname: respondent
|
hostname: respondent
|
||||||
tty: true
|
tty: true
|
||||||
networks:
|
|
||||||
marketplace_penahub_frontend:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Quiz</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
"@mui/x-date-pickers": "^6.16.1",
|
"@mui/x-date-pickers": "^6.16.1",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^16.7.13",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"dayjs": "^1.11.10",
|
|
||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.5.2",
|
||||||
"immer": "^10.0.3",
|
"immer": "^10.0.3",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"nanoid": "^5.0.3",
|
"nanoid": "^5.0.3",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
1
pub.js
Normal file
1
pub.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
console.log("PEHA NUB")
|
@ -1,16 +1,15 @@
|
|||||||
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
|
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
import { ruRU } from '@mui/x-date-pickers/locales';
|
||||||
import dayjs from "dayjs";
|
import moment from "moment";
|
||||||
import "dayjs/locale/ru";
|
|
||||||
import { SnackbarProvider } from 'notistack';
|
import { SnackbarProvider } from 'notistack';
|
||||||
import { SWRConfig } from "swr";
|
import { SWRConfig } from "swr";
|
||||||
import { ViewPage } from "./pages/ViewPublicationPage";
|
import { ViewPage } from "./pages/ViewPublicationPage";
|
||||||
import lightTheme from "./utils/themes/light";
|
import lightTheme from "./utils/themes/light";
|
||||||
|
|
||||||
|
|
||||||
dayjs.locale("ru");
|
moment.locale("ru");
|
||||||
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -24,7 +23,7 @@ export default function App({ widget = false }: Props) {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
}}>
|
}}>
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="ru" localeText={localeText}>
|
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<SnackbarProvider
|
<SnackbarProvider
|
||||||
preventDuplicate={true}
|
preventDuplicate={true}
|
||||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@ -5,10 +5,11 @@ type InfoProps = {
|
|||||||
height?: number;
|
height?: number;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
onClick?: any;
|
onClick?: any;
|
||||||
className?: string
|
className?: string;
|
||||||
|
color?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Info({ width = 20, height = 20, sx, onClick, className }: InfoProps) {
|
export default function Info({ width = 20, height = 20, sx, onClick, className, color = "#7e2aea" }: InfoProps) {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={sx}
|
sx={sx}
|
||||||
@ -24,21 +25,21 @@ export default function Info({ width = 20, height = 20, sx, onClick, className }
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
||||||
stroke="#7E2AEA"
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9.25 9.25H10V14.5H10.75"
|
d="M9.25 9.25H10V14.5H10.75"
|
||||||
stroke="#7E2AEA"
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
||||||
fill="#7E2AEA"
|
fill={color}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "dayjs/locale/ru";
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import type {
|
|||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export const UPLOAD_FILE_TYPES_MAP = {
|
export const UPLOAD_FILE_TYPES_MAP = {
|
||||||
all: "Все типы файлов",
|
|
||||||
picture: "Изображения",
|
picture: "Изображения",
|
||||||
video: "Видео",
|
video: "Видео",
|
||||||
audio: "Аудио",
|
audio: "Аудио",
|
||||||
|
@ -87,6 +87,7 @@ export interface QuizConfig {
|
|||||||
text: FCField;
|
text: FCField;
|
||||||
address: FCField;
|
address: FCField;
|
||||||
button: string;
|
button: string;
|
||||||
|
fields: Record<FormContactFieldName, FormContactFieldData>;
|
||||||
};
|
};
|
||||||
info: {
|
info: {
|
||||||
phonenumber: string;
|
phonenumber: string;
|
||||||
@ -98,6 +99,21 @@ export interface QuizConfig {
|
|||||||
meta: string;
|
meta: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FormContactFieldName =
|
||||||
|
| "name"
|
||||||
|
| "email"
|
||||||
|
| "phone"
|
||||||
|
| "text"
|
||||||
|
| "address";
|
||||||
|
|
||||||
|
type FormContactFieldData = {
|
||||||
|
text: string;
|
||||||
|
innerText: string;
|
||||||
|
key: string;
|
||||||
|
required: boolean;
|
||||||
|
used: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export interface QuizItems {
|
export interface QuizItems {
|
||||||
description: string;
|
description: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||||
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
|
||||||
import NameIcon from "@icons/ContactFormIcon/NameIcon";
|
import NameIcon from "@icons/ContactFormIcon/NameIcon";
|
||||||
|
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
||||||
import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon";
|
import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon";
|
||||||
import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
||||||
import { Box, Button, InputAdornment, Link, TextField as MuiTextField, TextFieldProps, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Button, InputAdornment, Link, TextField as MuiTextField, TextFieldProps, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
@ -20,16 +20,8 @@ import { useQuestionsStore } from "@stores/quizData/store";
|
|||||||
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||||
|
|
||||||
const EMAIL_REGEXP = /^(([^<>()[\].,:\s@"]+(\.[^<>()[\].,:\s@"]+)*)|(".+"))@(([^<>()[\].,:\s@"]+\.)+[^<>()[\].,:\s@"]{2,})$/iu;
|
const EMAIL_REGEXP = /^(([^<>()[\].,:\s@"]+(\.[^<>()[\].,:\s@"]+)*)|(".+"))@(([^<>()[\].,:\s@"]+\.)+[^<>()[\].,:\s@"]{2,})$/iu;
|
||||||
|
|
||||||
type ContactType =
|
|
||||||
| "name"
|
|
||||||
| "email"
|
|
||||||
| "phone"
|
|
||||||
| "text"
|
|
||||||
| "adress";
|
|
||||||
|
|
||||||
type ContactFormProps = {
|
type ContactFormProps = {
|
||||||
currentQuestion: any;
|
currentQuestion: any;
|
||||||
showResultForm: boolean;
|
showResultForm: boolean;
|
||||||
@ -37,6 +29,44 @@ type ContactFormProps = {
|
|||||||
setShowResultForm: (show: boolean) => void;
|
setShowResultForm: (show: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const icons = [
|
||||||
|
{
|
||||||
|
type: "name",
|
||||||
|
icon: NameIcon,
|
||||||
|
defaultText: "Введите имя",
|
||||||
|
defaultTitle: "имя",
|
||||||
|
backendName: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "email",
|
||||||
|
icon: EmailIcon,
|
||||||
|
defaultText: "Введите Email",
|
||||||
|
defaultTitle: "Email",
|
||||||
|
backendName: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "phone",
|
||||||
|
icon: PhoneIcon,
|
||||||
|
defaultText: "Введите номер телефона",
|
||||||
|
defaultTitle: "номер телефона",
|
||||||
|
backendName: "phone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
icon: TextIcon,
|
||||||
|
defaultText: "Введите фамилию",
|
||||||
|
defaultTitle: "фамилию",
|
||||||
|
backendName: "adress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "address",
|
||||||
|
icon: AddressIcon,
|
||||||
|
defaultText: "Введите адрес",
|
||||||
|
defaultTitle: "адрес",
|
||||||
|
backendName: "adress",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const ContactForm = ({
|
export const ContactForm = ({
|
||||||
currentQuestion,
|
currentQuestion,
|
||||||
showResultForm,
|
showResultForm,
|
||||||
@ -44,73 +74,94 @@ export const ContactForm = ({
|
|||||||
setShowResultForm,
|
setShowResultForm,
|
||||||
}: ContactFormProps) => {
|
}: ContactFormProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings, items } = useQuestionsStore()
|
const { settings, items } = useQuestionsStore();
|
||||||
|
|
||||||
const [ready, setReady] = useState(false)
|
const [ready, setReady] = useState(false);
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("");
|
||||||
const [email, setEmail] = useState("")
|
const [email, setEmail] = useState("");
|
||||||
const [phone, setPhone] = useState("")
|
const [phone, setPhone] = useState("");
|
||||||
const [text, setText] = useState("")
|
const [text, setText] = useState("");
|
||||||
const [adress, setAdress] = useState("")
|
const [adress, setAdress] = useState("");
|
||||||
|
|
||||||
const fireOnce = useRef(true)
|
const fireOnce = useRef(true);
|
||||||
const [fire, setFire] = useState(false)
|
const [fire, setFire] = useState(false);
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||||
|
|
||||||
const resultQuestion: QuizQuestionResult = items.find((question): question is QuizQuestionResult => {
|
const followNextForm = () => {
|
||||||
if (settings?.cfg.haveRoot) { //ветвимся
|
setShowContactForm(false);
|
||||||
|
setShowResultForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
const resultQuestion: QuizQuestionResult = items.find((question) => {
|
||||||
|
if (settings?.cfg.haveRoot) {
|
||||||
|
//ветвимся
|
||||||
return (
|
return (
|
||||||
question.type === "result" &&
|
question.type === "result" &&
|
||||||
|
//@ts-ignore
|
||||||
question.content.rule.parentId === currentQuestion.content.id
|
question.content.rule.parentId === currentQuestion.content.id
|
||||||
);
|
);
|
||||||
} else {// не ветвимся
|
} else {
|
||||||
|
// не ветвимся
|
||||||
return (
|
return (
|
||||||
question.type === "result" &&
|
question.type === "result" && question.content.rule.parentId === "line"
|
||||||
question.content.rule.parentId === "line"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})!;
|
});
|
||||||
|
|
||||||
const inputHC = async () => {
|
const inputHC = async () => {
|
||||||
if (!settings) return;
|
if (!settings) return;
|
||||||
|
|
||||||
const body: Partial<Record<ContactType, string>> = {};
|
//@ts-ignore
|
||||||
|
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||||
|
const body = {};
|
||||||
|
//@ts-ignore
|
||||||
if (name.length > 0) body.name = name;
|
if (name.length > 0) body.name = name;
|
||||||
|
//@ts-ignore
|
||||||
if (email.length > 0) body.email = email;
|
if (email.length > 0) body.email = email;
|
||||||
|
//@ts-ignore
|
||||||
if (phone.length > 0) body.phone = phone;
|
if (phone.length > 0) body.phone = phone;
|
||||||
if (text.length > 0) body.text = text;
|
//@ts-ignore
|
||||||
if (adress.length > 0) body.adress = adress;
|
if (adress.length > 0) body.address = adress;
|
||||||
|
//@ts-ignore
|
||||||
|
if (text.length > 0) body.customs = { [FC.text.text || "Фамилия"]: text };
|
||||||
|
|
||||||
if (Object.keys(body).length > 0) {
|
if (Object.keys(body).length > 0) {
|
||||||
try {
|
try {
|
||||||
await sendFC({
|
await sendFC({
|
||||||
questionId: resultQuestion?.id,
|
questionId: resultQuestion?.id,
|
||||||
body: body,
|
body: body,
|
||||||
qid: settings.qid
|
qid: settings.qid,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
|
localStorage.setItem(
|
||||||
|
"sessions",
|
||||||
|
JSON.stringify({ ...sessions, [settings.qid]: new Date().getTime() })
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("ответ не был засчитан")
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const FCcopy: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
let FCcopy: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||||
|
|
||||||
const filteredFC: any = {};
|
let filteredFC: any = {};
|
||||||
for (const i in FCcopy) {
|
for (let i in FCcopy) {
|
||||||
const field = FCcopy[i];
|
let field = FCcopy[i];
|
||||||
console.log(filteredFC);
|
console.log(filteredFC);
|
||||||
if (field.used) {
|
if (field.used) {
|
||||||
filteredFC[i] = field;
|
filteredFC[i] = field;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const isWide = Object.keys(filteredFC).length > 2;
|
let isWide = Object.keys(filteredFC).length > 2;
|
||||||
|
|
||||||
if (!settings) throw new Error("settings is null");
|
if (!settings) throw new Error("settings is null");
|
||||||
if (!resultQuestion) return <ApologyPage message="не получилось найти результат для этой ветки :(" />
|
if (!resultQuestion)
|
||||||
|
return (
|
||||||
|
<ApologyPage message="не получилось найти результат для этой ветки :(" />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -121,17 +172,21 @@ export const ContactForm = ({
|
|||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
"&::-webkit-scrollbar": { width: "0", display: "none", msOverflowStyle: "none" },
|
"&::-webkit-scrollbar": {
|
||||||
|
width: "0",
|
||||||
|
display: "none",
|
||||||
|
msOverflowStyle: "none",
|
||||||
|
},
|
||||||
scrollbarWidth: "none",
|
scrollbarWidth: "none",
|
||||||
msOverflowStyle: "none"
|
msOverflowStyle: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: isWide && !isMobile ? "100%" : (isMobile ? undefined : "530px"),
|
width: isWide && !isMobile ? "100%" : isMobile ? undefined : "530px",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
height: "90vh",
|
height: "90vh",
|
||||||
display: isWide && !isMobile ? "flex" : undefined
|
display: isWide && !isMobile ? "flex" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -141,7 +196,7 @@ export const ContactForm = ({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
borderRight: isWide && !isMobile ? "1px solid gray" : undefined
|
borderRight: isWide && !isMobile ? "1px solid gray" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
@ -149,28 +204,26 @@ export const ContactForm = ({
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
m: "20px 0",
|
m: "20px 0",
|
||||||
fontSize: "28px",
|
fontSize: "28px",
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings?.cfg.formContact.title || "Заполните форму, чтобы получить результаты теста"}
|
{settings?.cfg.formContact.title ||
|
||||||
|
"Заполните форму, чтобы получить результаты теста"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{
|
{settings?.cfg.formContact.desc && (
|
||||||
settings?.cfg.formContact.desc &&
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
m: "20px 0",
|
m: "20px 0",
|
||||||
fontSize: "18px"
|
fontSize: "18px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings?.cfg.formContact.desc}
|
{settings?.cfg.formContact.desc}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -178,77 +231,92 @@ export const ContactForm = ({
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
p: "30px"
|
p: "30px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
my: "20px"
|
my: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Inputs
|
<Inputs
|
||||||
name={name} setName={setName}
|
name={name}
|
||||||
email={email} setEmail={setEmail}
|
setName={setName}
|
||||||
phone={phone} setPhone={setPhone}
|
email={email}
|
||||||
text={text} setText={setText}
|
setEmail={setEmail}
|
||||||
adress={adress} setAdress={setAdress}
|
phone={phone}
|
||||||
|
setPhone={setPhone}
|
||||||
|
text={text}
|
||||||
|
setText={setText}
|
||||||
|
adress={adress}
|
||||||
|
setAdress={setAdress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{
|
{
|
||||||
// resultQuestion &&
|
// resultQuestion &&
|
||||||
// settings?.cfg.resultInfo.when === "after" &&
|
// settings?.cfg.resultInfo.when === "after" &&
|
||||||
(
|
<Button
|
||||||
<Button
|
disabled={!(ready && !fire)}
|
||||||
disabled={!(ready && !fire)}
|
variant="contained"
|
||||||
variant="contained"
|
onClick={async () => {
|
||||||
onClick={async () => {
|
//@ts-ignore
|
||||||
//@ts-ignore
|
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact
|
if (FC["email"].used === EMAIL_REGEXP.test(email)) {
|
||||||
if (FC["email"].used === EMAIL_REGEXP.test(email)) {//почта валидна
|
//почта валидна
|
||||||
setFire(true)
|
setFire(true);
|
||||||
|
|
||||||
|
if (fireOnce.current) {
|
||||||
if (fireOnce.current) {
|
if (
|
||||||
if (
|
name.length > 0 ||
|
||||||
name.length > 0 ||
|
email.length > 0 ||
|
||||||
email.length > 0 ||
|
phone.length > 0 ||
|
||||||
phone.length > 0 ||
|
text.length > 0 ||
|
||||||
text.length > 0 ||
|
adress.length > 0
|
||||||
adress.length > 0
|
) {
|
||||||
) {
|
try {
|
||||||
|
await inputHC();
|
||||||
try {
|
fireOnce.current = false;
|
||||||
await inputHC()
|
const QID =
|
||||||
fireOnce.current = false
|
process.env.NODE_ENV === "production"
|
||||||
enqueueSnackbar("Данные успешно отправлены")
|
? window.location.pathname.replace(/\//g, "")
|
||||||
} catch (e) {
|
: "ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
||||||
enqueueSnackbar("повторите попытку позже")
|
const sessions: any = JSON.parse(
|
||||||
}
|
localStorage.getItem("sessions") || "{}"
|
||||||
|
);
|
||||||
if ((settings?.cfg.resultInfo.showResultForm === "after" || settings?.cfg.resultInfo.when === "email") && !checkEmptyData({ resultData: resultQuestion })) {
|
sessions[QID] = Date.now();
|
||||||
setShowContactForm(false)
|
localStorage.setItem(
|
||||||
setShowResultForm(true)
|
"sessions",
|
||||||
}
|
JSON.stringify(sessions)
|
||||||
} else {
|
);
|
||||||
enqueueSnackbar("Пожалуйста, заполните поля")
|
enqueueSnackbar("Данные успешно отправлены");
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar("повторите попытку позже");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setFire(false)
|
if (
|
||||||
} else {
|
settings?.cfg.resultInfo.showResultForm === "after" &&
|
||||||
enqueueSnackbar("введена некорректная почта")
|
!checkEmptyData({ resultData: resultQuestion })
|
||||||
|
) {
|
||||||
|
setShowContactForm(false);
|
||||||
|
setShowResultForm(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enqueueSnackbar("Пожалуйста, заполните поля");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
setFire(false);
|
||||||
>
|
} else {
|
||||||
{settings?.cfg.formContact?.button || "Получить результаты"}
|
enqueueSnackbar("введена некорректная почта");
|
||||||
</Button>
|
}
|
||||||
)}
|
}}
|
||||||
|
>
|
||||||
|
{settings?.cfg.formContact?.button || "Получить результаты"}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -257,96 +325,169 @@ export const ContactForm = ({
|
|||||||
width: isMobile ? "300px" : "450px",
|
width: isMobile ? "300px" : "450px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomCheckbox label="" handleChange={({ target }) => { setReady(target.checked) }} checked={ready} colorIcon={theme.palette.primary.main} />
|
<CustomCheckbox
|
||||||
|
label=""
|
||||||
|
handleChange={({ target }) => {
|
||||||
|
setReady(target.checked);
|
||||||
|
}}
|
||||||
|
checked={ready}
|
||||||
|
colorIcon={theme.palette.primary.main}
|
||||||
|
/>
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
<Typography sx={{ color: theme.palette.text.primary }}>
|
||||||
С 
|
С 
|
||||||
<Link href={"https://shub.pena.digital/ppdd"} target="_blank">
|
<Link href={"https://shub.pena.digital/ppdd"} target="_blank">
|
||||||
Положением об обработке персональных данных </Link>
|
Положением об обработке персональных данных{" "}
|
||||||
|
</Link>
|
||||||
 и 
|
 и 
|
||||||
<Link href={"https://shub.pena.digital/docs/privacy"} target="_blank"> Политикой конфиденциальности </Link>
|
<Link
|
||||||
|
href={"https://shub.pena.digital/docs/privacy"}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Политикой конфиденциальности{" "}
|
||||||
|
</Link>
|
||||||
 ознакомлен
|
 ознакомлен
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
mt: "20px",
|
mt: "20px",
|
||||||
gap: "15px"
|
gap: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NameplateLogo style={{
|
<NameplateLogo
|
||||||
fontSize: "34px",
|
style={{
|
||||||
color: quizThemes[settings.cfg.theme].isLight ? "#151515" : "#FFFFFF"
|
fontSize: "34px",
|
||||||
}} />
|
//@ts-ignore
|
||||||
<Typography sx={{
|
color: mode[settings.cfg.theme] ? "#151515" : "#FFFFFF",
|
||||||
fontSize: "20px",
|
}}
|
||||||
color: quizThemes[settings.cfg.theme].isLight ? "#4D4D4D" : "#F5F7FF", whiteSpace: "nowrap"
|
/>
|
||||||
}}>
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "20px",
|
||||||
|
//@ts-ignore
|
||||||
|
color: mode[settings.cfg.theme] ? "#4D4D4D" : "#F5F7FF",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
Сделано на PenaQuiz
|
Сделано на PenaQuiz
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box >
|
</Box>
|
||||||
</Box >
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Inputs = ({
|
const Inputs = ({
|
||||||
name, setName,
|
name,
|
||||||
email, setEmail,
|
setName,
|
||||||
phone, setPhone,
|
email,
|
||||||
text, setText,
|
setEmail,
|
||||||
adress, setAdress
|
phone,
|
||||||
|
setPhone,
|
||||||
|
text,
|
||||||
|
setText,
|
||||||
|
adress,
|
||||||
|
setAdress,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const { settings } = useQuestionsStore()
|
const { settings } = useQuestionsStore();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||||
|
|
||||||
|
if (!FC) return null;
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact
|
const Name = (
|
||||||
|
<CustomInput
|
||||||
|
//@ts-ignore
|
||||||
|
onChange={({ target }) => setName(target.value)}
|
||||||
|
id={name}
|
||||||
|
title={FC["name"].innerText || "Введите имя"}
|
||||||
|
desc={FC["name"].text || "имя"}
|
||||||
|
Icon={NameIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const Name = <CustomInput onChange={({ target }) => setName(target.value)} id={name} title={FC["name"].innerText || "Введите имя"} desc={FC["name"].text || "имя"} Icon={NameIcon} />
|
const Email = (
|
||||||
|
<CustomInput
|
||||||
|
error={!EMAIL_REGEXP.test(email)}
|
||||||
|
label={!EMAIL_REGEXP.test(email) ? "" : "Некорректная почта"}
|
||||||
|
//@ts-ignore
|
||||||
|
onChange={({ target }) => setEmail(target.value)}
|
||||||
|
id={email}
|
||||||
|
title={FC["email"].innerText || "Введите Email"}
|
||||||
|
desc={FC["email"].text || "Email"}
|
||||||
|
Icon={EmailIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const Phone = (
|
||||||
|
<CustomInput
|
||||||
|
//@ts-ignore
|
||||||
|
onChange={({ target }) => setPhone(target.value)}
|
||||||
|
id={phone}
|
||||||
|
title={FC["phone"].innerText || "Введите номер телефона"}
|
||||||
|
desc={FC["phone"].text || "номер телефона"}
|
||||||
|
Icon={PhoneIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const Email = <CustomInput
|
const Text = (
|
||||||
|
<CustomInput
|
||||||
error = {!EMAIL_REGEXP.test(email)}
|
//@ts-ignore
|
||||||
label={!EMAIL_REGEXP.test(email) ? "" : "Некорректная почта"}
|
onChange={({ target }) => setText(target.value)}
|
||||||
//@ts-ignore
|
id={text}
|
||||||
onChange={({ target }) => setEmail(target.value)} id={email} title={FC["email"].innerText || "Введите Email"} desc={FC["email"].text || "Email"} Icon={EmailIcon} />
|
title={FC["text"].text || "Введите фамилию"}
|
||||||
|
desc={FC["text"].innerText || "фамилию"}
|
||||||
|
Icon={TextIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const Phone = <CustomInput onChange={({ target }) => setPhone(target.value)} id={phone} title={FC["phone"].innerText || "Введите номер телефона"} desc={FC["phone"].text || "номер телефона"} Icon={PhoneIcon} />
|
const Adress = (
|
||||||
//@ts-ignore
|
<CustomInput
|
||||||
const Text = <CustomInput onChange={({ target }) => setText(target.value)} id={text} title={FC["text"].innerText || "Введите фамилию"} desc={FC["text"].text || "фамилию"} Icon={TextIcon} />
|
//@ts-ignore
|
||||||
//@ts-ignore
|
onChange={({ target }) => setAdress(target.value)}
|
||||||
const Adress = <CustomInput onChange={({ target }) => setAdress(target.value)} id={adress} title={FC["address"].innerText || "Введите адрес"} desc={FC["address"].text || "адрес"} Icon={AddressIcon} />
|
id={adress}
|
||||||
|
title={FC["address"].innerText || "Введите адрес"}
|
||||||
|
desc={FC["address"].text || "адрес"}
|
||||||
|
Icon={AddressIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (Object.values(FC).some((data) => data.used)) {
|
if (Object.values(FC).some((data) => data.used)) {
|
||||||
return <>
|
return (
|
||||||
{FC["name"].used ? Name : <></>}
|
<>
|
||||||
{FC["email"].used ? Email : <></>}
|
{FC["name"].used ? Name : <></>}
|
||||||
{FC["phone"].used ? Phone : <></>}
|
{FC["email"].used ? Email : <></>}
|
||||||
{FC["text"].used ? Text : <></>}
|
{FC["phone"].used ? Phone : <></>}
|
||||||
{FC["address"].used ? Adress : <></>}
|
{FC["text"].used ? Text : <></>}
|
||||||
</>
|
{FC["address"].used ? Adress : <></>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <>
|
return (
|
||||||
{Name}
|
<>
|
||||||
{Email}
|
{Name}
|
||||||
{Phone}
|
{Email}
|
||||||
</>
|
{Phone}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const CustomInput = ({ title, desc, Icon, onChange }: any) => {
|
const CustomInput = ({ title, desc, Icon, onChange }: any) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
|
//@ts-ignore
|
||||||
return (
|
return (
|
||||||
<Box m="15px 0">
|
<Box m="15px 0">
|
||||||
<Typography mb="7px" color={theme.palette.text.primary}>{title}</Typography>
|
<Typography mb="7px" color={theme.palette.text.primary}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@ -355,9 +496,13 @@ const CustomInput = ({ title, desc, Icon, onChange }: any) => {
|
|||||||
}}
|
}}
|
||||||
placeholder={desc}
|
placeholder={desc}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <InputAdornment position="start"><Icon color="gray" /></InputAdornment>,
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<Icon color="gray" />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
@ -11,15 +11,16 @@ import useSWR from "swr";
|
|||||||
import { ApologyPage } from "./ApologyPage";
|
import { ApologyPage } from "./ApologyPage";
|
||||||
import { Question } from "./Question";
|
import { Question } from "./Question";
|
||||||
import { StartPageViewPublication } from "./StartPageViewPublication";
|
import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||||
import { replaceSpacesToEmptyLines } from "./tools/replaceSpacesToEmptyLines";
|
|
||||||
import { parseQuizData } from "@model/api/getQuizData";
|
|
||||||
|
|
||||||
|
|
||||||
|
import { parseQuizData } from "@model/api/getQuizData";
|
||||||
|
import { replaceSpacesToEmptyLines } from "./tools/replaceSpacesToEmptyLines";
|
||||||
|
|
||||||
const QID =
|
const QID =
|
||||||
import.meta.env.PROD ?
|
import.meta.env.PROD ?
|
||||||
window.location.pathname.replace(/\//g, '')
|
window.location.pathname.replace(/\//g, '')
|
||||||
:
|
:
|
||||||
"0bed8483-3016-4bca-b8e0-a72c3146f18b";
|
"ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
||||||
|
|
||||||
|
|
||||||
export const ViewPage = () => {
|
export const ViewPage = () => {
|
||||||
@ -36,6 +37,8 @@ export const ViewPage = () => {
|
|||||||
if (link && settings.cfg.startpage.favIcon) {
|
if (link && settings.cfg.startpage.favIcon) {
|
||||||
link.setAttribute("href", settings?.cfg.startpage.favIcon);
|
link.setAttribute("href", settings?.cfg.startpage.favIcon);
|
||||||
}
|
}
|
||||||
|
//установка заголовка страницы
|
||||||
|
document.title = settings.name;
|
||||||
|
|
||||||
setVisualStartPage(!settings.cfg.noStartPage);
|
setVisualStartPage(!settings.cfg.noStartPage);
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import dayjs from "dayjs";
|
import moment from "moment";
|
||||||
import { DatePicker } from "@mui/x-date-pickers";
|
import { DatePicker } from "@mui/x-date-pickers";
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
@ -24,8 +24,8 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
const answer = answers.find(
|
const answer = answers.find(
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
)?.answer as string;
|
)?.answer as string;
|
||||||
const [day, month, year] = answer?.split(".") || [];
|
const currentAnswer = moment(answer) || moment();
|
||||||
|
|
||||||
if (!settings) throw new Error("settings is null");
|
if (!settings) throw new Error("settings is null");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,12 +52,9 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
value={dayjs(
|
value={ currentAnswer }
|
||||||
month && day && year
|
|
||||||
? new window.Date(`${month}.${day}.${year}`)
|
|
||||||
: new window.Date()
|
|
||||||
)}
|
|
||||||
onChange={async (date) => {
|
onChange={async (date) => {
|
||||||
|
console.log(date)
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -65,26 +62,13 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
try {
|
try {
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: new window.Date(date.toDate()).toLocaleDateString(
|
body: moment(date).format("YYYY.MM.DD"),
|
||||||
"ru-RU",
|
|
||||||
{
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
qid: settings.qid,
|
qid: settings.qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
currentQuestion.id,
|
currentQuestion.id,
|
||||||
String(
|
date
|
||||||
new window.Date(date.toDate()).toLocaleDateString("ru-RU", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
@ -111,7 +111,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentQuestion.content.variants[index].answer,
|
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer,
|
||||||
qid: settings.qid
|
qid: settings.qid
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
ButtonBase,
|
ButtonBase,
|
||||||
useTheme,
|
useTheme,
|
||||||
IconButton, useMediaQuery,
|
IconButton, useMediaQuery, Modal,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
||||||
import { UPLOAD_FILE_TYPES_MAP } from "../tools/File";
|
import { UPLOAD_FILE_TYPES_MAP } from "../tools/File";
|
||||||
@ -11,23 +11,97 @@ import { UPLOAD_FILE_TYPES_MAP } from "../tools/File";
|
|||||||
import UploadIcon from "@icons/UploadIcon";
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
import CloseBold from "@icons/CloseBold";
|
import CloseBold from "@icons/CloseBold";
|
||||||
|
|
||||||
import type { ChangeEvent } from "react";
|
import { useState, type ChangeEvent } from "react";
|
||||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||||
import type { DragEvent } from "react";
|
import type { DragEvent } from "react";
|
||||||
import type { UploadFileType } from "@model/questionTypes/file";
|
import type { UploadFileType } from "@model/questionTypes/file";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { sendFile } from "@api/quizRelase";
|
import { sendAnswer, sendFile } from "@api/quizRelase";
|
||||||
import { useQuestionsStore } from "@stores/quizData/store"
|
import { useQuestionsStore } from "@stores/quizData/store"
|
||||||
|
import Info from "@icons/Info";
|
||||||
|
|
||||||
type FileProps = {
|
type FileProps = {
|
||||||
currentQuestion: QuizQuestionFile;
|
currentQuestion: QuizQuestionFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CurrentModal = ({ status }: { status: "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "" }) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'errorType':
|
||||||
|
return (<>
|
||||||
|
<Typography>Выбран некорректный тип файла</Typography>
|
||||||
|
</>)
|
||||||
|
case 'errorSize':
|
||||||
|
return (<>
|
||||||
|
<Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>
|
||||||
|
</>)
|
||||||
|
default:
|
||||||
|
return (<>
|
||||||
|
<Typography>Допустимые расширения файлов:</Typography>
|
||||||
|
<Typography>{
|
||||||
|
//@ts-ignore
|
||||||
|
ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
||||||
|
</>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ACCEPT_SEND_FILE_TYPES_MAP = {
|
||||||
|
picture: [
|
||||||
|
".jpeg",
|
||||||
|
".jpg",
|
||||||
|
".png",
|
||||||
|
".ico",
|
||||||
|
".gif",
|
||||||
|
".tiff",
|
||||||
|
".webp",
|
||||||
|
".eps",
|
||||||
|
".svg"
|
||||||
|
],
|
||||||
|
video: [
|
||||||
|
".mp4",
|
||||||
|
".mov",
|
||||||
|
".wmv",
|
||||||
|
".avi",
|
||||||
|
".avchd",
|
||||||
|
".flv",
|
||||||
|
".f4v",
|
||||||
|
".swf",
|
||||||
|
".mkv",
|
||||||
|
".webm",
|
||||||
|
".mpeg-2"
|
||||||
|
],
|
||||||
|
audio: [
|
||||||
|
".aac",
|
||||||
|
".aiff",
|
||||||
|
".dsd",
|
||||||
|
".flac",
|
||||||
|
".mp3",
|
||||||
|
".mqa",
|
||||||
|
".ogg",
|
||||||
|
".wav",
|
||||||
|
".wma"
|
||||||
|
],
|
||||||
|
document: [
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
|
".dotx",
|
||||||
|
".rtf",
|
||||||
|
".odt",
|
||||||
|
".pdf",
|
||||||
|
".txt",
|
||||||
|
".xls",
|
||||||
|
".ppt",
|
||||||
|
".xlsx",
|
||||||
|
".pptx",
|
||||||
|
".pages",
|
||||||
|
],
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
||||||
UploadFileType,
|
UploadFileType,
|
||||||
{ title: string; description: string }
|
{ title: string; description: string }
|
||||||
> = {
|
> = {
|
||||||
all: { title: "Добавить файл", description: "Принимает любые файлы" },
|
|
||||||
picture: {
|
picture: {
|
||||||
title: "Добавить изображение",
|
title: "Добавить изображение",
|
||||||
description: "Принимает изображения",
|
description: "Принимает изображения",
|
||||||
@ -41,171 +115,230 @@ const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const File = ({ currentQuestion }: FileProps) => {
|
export const File = ({ currentQuestion }: FileProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
const { settings } = useQuestionsStore()
|
const { settings } = useQuestionsStore()
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
|
|
||||||
|
const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">("")
|
||||||
|
|
||||||
const answer = answers.find(
|
const answer = answers.find(
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
)?.answer as string;
|
)?.answer as string;
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||||
const uploadFile = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
const uploadFile = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!settings) return;
|
if (!settings) return;
|
||||||
|
|
||||||
const file = target.files?.[0];
|
const file = target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
|
if (file.size <= 52428800) {
|
||||||
|
//проверяем на соответствие
|
||||||
|
console.log(file.name.toLowerCase())
|
||||||
|
if (ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].find((ednding => {
|
||||||
|
console.log(ednding)
|
||||||
|
console.log(file.name.toLowerCase().endsWith(ednding))
|
||||||
|
return file.name.toLowerCase().endsWith(ednding)
|
||||||
|
}))) {
|
||||||
|
|
||||||
try {
|
//Нужный формат
|
||||||
|
console.log(file)
|
||||||
|
try {
|
||||||
|
|
||||||
await sendFile({
|
const data = await sendFile({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: {
|
body: {
|
||||||
file: `${file.name}|${URL.createObjectURL(file)}`,
|
file: file,
|
||||||
name: file.name
|
name: file.name
|
||||||
},
|
},
|
||||||
qid: settings.qid
|
qid: settings.qid
|
||||||
})
|
})
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
updateAnswer(
|
await sendAnswer({
|
||||||
currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
`${file.name}|${URL.createObjectURL(file)}`
|
//@ts-ignore
|
||||||
);
|
body: `https://storage.yandexcloud.net/squizanswer/${settings.qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`,
|
||||||
|
//@ts-ignore
|
||||||
|
qid: settings.qid
|
||||||
|
})
|
||||||
|
|
||||||
} catch (e) {
|
updateAnswer(
|
||||||
enqueueSnackbar("ответ не был засчитан")
|
currentQuestion.id,
|
||||||
}
|
`${file.name}|${URL.createObjectURL(file)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
enqueueSnackbar("ответ не был засчитан")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//неподходящий формат
|
||||||
|
setStatusModal("errorType")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
setStatusModal("errorSize")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<>
|
||||||
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
<Box>
|
||||||
<Box
|
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
||||||
sx={{
|
<Box
|
||||||
display: "flex",
|
sx={{
|
||||||
flexDirection: "column",
|
display: "flex",
|
||||||
width: "100%",
|
flexDirection: "column",
|
||||||
marginTop: "20px",
|
width: "100%",
|
||||||
maxWidth: answer?.split("|")[0] ? "640px" : "550px",
|
marginTop: "20px",
|
||||||
}}
|
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
|
||||||
>
|
}}
|
||||||
{answer?.split("|")[0] && (
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
{answer?.split("|")[0] && (
|
||||||
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||||
<Box
|
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
||||||
sx={{
|
<Box
|
||||||
padding: "5px 5px 5px 16px",
|
sx={{
|
||||||
backgroundColor: theme.palette.primary.main,
|
padding: "5px 5px 5px 16px",
|
||||||
borderRadius: "8px",
|
backgroundColor: theme.palette.primary.main,
|
||||||
color: "#FFFFFF",
|
borderRadius: "8px",
|
||||||
display: "flex",
|
color: "#FFFFFF",
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
overflow: "hidden",
|
alignItems: "center",
|
||||||
gap: "15px",
|
overflow: "hidden",
|
||||||
}}
|
gap: "15px",
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer?.split("|")[0]}</Typography>
|
|
||||||
<IconButton
|
|
||||||
sx={{ p: 0 }}
|
|
||||||
onClick={() => {
|
|
||||||
updateAnswer(currentQuestion.id, "");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseBold />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!answer?.split("|")[0] && (
|
|
||||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
|
||||||
<input
|
|
||||||
onChange={uploadFile}
|
|
||||||
hidden
|
|
||||||
accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]}
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: isMobile ? undefined : "120px",
|
|
||||||
display: "flex",
|
|
||||||
gap: "50px",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "33px 44px 33px 55px",
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
border: `1px solid #9A9AAF`,
|
|
||||||
// border: `1px solid ${theme.palette.grey2.main}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Box>
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: "#9A9AAF",
|
whiteSpace: "nowrap",
|
||||||
// color: theme.palette.grey2.main,
|
textOverflow: "ellipsis",
|
||||||
fontWeight: 500,
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{answer?.split("|")[0]}</Typography>
|
||||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
<IconButton
|
||||||
.title
|
sx={{ p: 0 }}
|
||||||
}
|
onClick={() => {
|
||||||
</Typography>
|
updateAnswer(currentQuestion.id, "");
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#9A9AAF",
|
|
||||||
// color: theme.palette.grey2.main,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
<CloseBold />
|
||||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
</IconButton>
|
||||||
.description
|
|
||||||
}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</ButtonBase>
|
)}
|
||||||
)}
|
|
||||||
{answer && currentQuestion.content.type === "picture" && (
|
{!answer?.split("|")[0] && (
|
||||||
<img
|
<Box sx={{
|
||||||
src={answer.split("|")[1]}
|
display: "flex",
|
||||||
alt=""
|
alignItems: "center"
|
||||||
style={{
|
}}>
|
||||||
marginTop: "15px",
|
<ButtonBase component="label" sx={{ justifyContent: "flex-start", width: "100%" }}>
|
||||||
maxWidth: "300px",
|
<input
|
||||||
maxHeight: "300px",
|
onChange={uploadFile}
|
||||||
}}
|
hidden
|
||||||
/>
|
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
|
||||||
)}
|
multiple
|
||||||
{answer && currentQuestion.content.type === "video" && (
|
type="file"
|
||||||
<video
|
/>
|
||||||
src={answer.split("|")[1]}
|
<Box
|
||||||
style={{
|
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
||||||
marginTop: "15px",
|
event.preventDefault()
|
||||||
maxWidth: "300px",
|
}
|
||||||
maxHeight: "300px",
|
sx={{
|
||||||
objectFit: "cover",
|
width: "100%",
|
||||||
}}
|
height: isMobile ? undefined : "120px",
|
||||||
/>
|
display: "flex",
|
||||||
)}
|
gap: "50px",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "33px 44px 33px 55px",
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
border: `1px solid #9A9AAF`,
|
||||||
|
// border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UploadIcon />
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#9A9AAF",
|
||||||
|
// color: theme.palette.grey2.main,
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||||
|
.title
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#9A9AAF",
|
||||||
|
// color: theme.palette.grey2.main,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||||
|
.description
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</ButtonBase>
|
||||||
|
<Info sx={{ width: "40px", height: "40px" }} color={theme.palette.primary.main} onClick={() => setStatusModal(currentQuestion.content.type)} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{answer && currentQuestion.content.type === "picture" && (
|
||||||
|
<img
|
||||||
|
src={answer.split("|")[1]}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
marginTop: "15px",
|
||||||
|
maxWidth: "300px",
|
||||||
|
maxHeight: "300px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{answer && currentQuestion.content.type === "video" && (
|
||||||
|
<video
|
||||||
|
src={answer.split("|")[1]}
|
||||||
|
style={{
|
||||||
|
marginTop: "15px",
|
||||||
|
maxWidth: "300px",
|
||||||
|
maxHeight: "300px",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
<Modal
|
||||||
|
open={Boolean(statusModal)}
|
||||||
|
onClose={() => setStatusModal("")}
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: isMobile ? 300 : 400,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 3,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
}}>
|
||||||
|
<CurrentModal status={statusModal} />
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -78,7 +78,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
|||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentQuestion.content.variants[index].answer,
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
qid: settings.qid
|
qid: settings.qid
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
|||||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useQuestionsStore } from "@stores/quizData/store";
|
import { useQuestionsStore } from "@stores/quizData/store";
|
||||||
|
|
||||||
@ -19,74 +19,89 @@ type NumberProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Number = ({ currentQuestion }: NumberProps) => {
|
export const Number = ({ currentQuestion }: NumberProps) => {
|
||||||
const { settings } = useQuestionsStore()
|
const { settings } = useQuestionsStore();
|
||||||
|
const [inputValue, setInputValue] = useState<string>("0");
|
||||||
const [minRange, setMinRange] = useState<string>("0");
|
const [minRange, setMinRange] = useState<string>("0");
|
||||||
const [maxRange, setMaxRange] = useState<string>("100000000000");
|
const [maxRange, setMaxRange] = useState<string>("100000000000");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||||
|
|
||||||
|
|
||||||
const updateMinRangeDebounced = useDebouncedCallback(async (value, crowded = false) => {
|
|
||||||
if (!settings) return null;
|
|
||||||
|
|
||||||
if (crowded) {
|
|
||||||
setMinRange(maxRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: value,
|
|
||||||
qid: settings.qid
|
|
||||||
})
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, value);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}, 1000);
|
|
||||||
const updateMaxRangeDebounced = useDebouncedCallback(async (value, crowded = false) => {
|
|
||||||
if (!settings) return null;
|
|
||||||
|
|
||||||
if (crowded) {
|
|
||||||
setMaxRange(minRange);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: value,
|
|
||||||
qid: settings.qid
|
|
||||||
})
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, value);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан")
|
|
||||||
}
|
|
||||||
|
|
||||||
}, 1000);
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
|
||||||
|
|
||||||
const min = window.Number(currentQuestion.content.range.split("—")[0]);
|
const min = window.Number(currentQuestion.content.range.split("—")[0]);
|
||||||
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
||||||
|
|
||||||
|
const sendAnswerToBackend = async (value: string) => {
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: value,
|
||||||
|
//@ts-ignore
|
||||||
|
qid: settings.qid,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, value);
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
||||||
|
const newValue =
|
||||||
|
window.Number(value) < window.Number(minRange)
|
||||||
|
? minRange
|
||||||
|
: window.Number(value) > window.Number(maxRange)
|
||||||
|
? maxRange
|
||||||
|
: value;
|
||||||
|
|
||||||
|
setInputValue(newValue);
|
||||||
|
await sendAnswerToBackend(newValue);
|
||||||
|
}, 1000);
|
||||||
|
const updateMinRangeDebounced = useDebouncedCallback(
|
||||||
|
async (value: string, crowded = false) => {
|
||||||
|
const newMinValue = crowded
|
||||||
|
? maxRange
|
||||||
|
: window.Number(value.split("—")[0]) < min
|
||||||
|
? String(min)
|
||||||
|
: value.split("—")[0];
|
||||||
|
|
||||||
|
setMinRange(newMinValue);
|
||||||
|
await sendAnswerToBackend(`${newMinValue}—${value.split("—")[1]}`);
|
||||||
|
},
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
const updateMaxRangeDebounced = useDebouncedCallback(
|
||||||
|
async (value: string, crowded = false) => {
|
||||||
|
const newMaxValue = crowded
|
||||||
|
? minRange
|
||||||
|
: window.Number(value.split("—")[1]) > max
|
||||||
|
? String(max)
|
||||||
|
: value.split("—")[1];
|
||||||
|
|
||||||
|
setMaxRange(newMaxValue);
|
||||||
|
await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`);
|
||||||
|
},
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
const sliderValue = answer || currentQuestion.content.start + "—" + max;
|
const sliderValue = answer || currentQuestion.content.start + "—" + max;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (answer) {
|
if (answer) {
|
||||||
setMinRange(answer.split("—")[0]);
|
if (answer.includes("—")) {
|
||||||
setMaxRange(answer.split("—")[1]);
|
setMinRange(answer.split("—")[0]);
|
||||||
|
setMaxRange(answer.split("—")[1]);
|
||||||
|
} else {
|
||||||
|
setInputValue(answer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!answer) {
|
if (!answer) {
|
||||||
setMinRange(String(currentQuestion.content.start));
|
setMinRange(String(currentQuestion.content.start));
|
||||||
setMaxRange(String(max));
|
setMaxRange(String(max));
|
||||||
|
setInputValue(String(currentQuestion.content.start));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -94,7 +109,9 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
<Typography variant="h5" color={theme.palette.text.primary}>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -102,7 +119,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
gap: "30px",
|
gap: "30px",
|
||||||
paddingRight: isMobile ? "10px" : undefined
|
paddingRight: isMobile ? "10px" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomSlider
|
<CustomSlider
|
||||||
@ -116,50 +133,51 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
step={currentQuestion.content.step || 1}
|
step={currentQuestion.content.step || 1}
|
||||||
onChange={async (_, value) => {
|
onChange={(_, value) => {
|
||||||
|
const range = Array.isArray(value)
|
||||||
|
? `${value[0]}—${value[1]}`
|
||||||
|
: String(value);
|
||||||
const range = String(value).replace(",", "—").replace (/\D/, '');
|
|
||||||
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, range);
|
updateAnswer(currentQuestion.id, range);
|
||||||
updateMinRangeDebounced(range, true);
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
onChangeCommitted={(_, value) => {
|
onChangeCommitted={async (_, value) => {
|
||||||
if (currentQuestion.content.chooseRange) {
|
if (currentQuestion.content.chooseRange && Array.isArray(value)) {
|
||||||
const range = value as number[];
|
setMinRange(String(value[0]));
|
||||||
|
setMaxRange(String(value[1]));
|
||||||
|
await sendAnswerToBackend(`${value[0]}—${value[1]}`);
|
||||||
|
|
||||||
setMinRange(String(range[0]));
|
return;
|
||||||
setMaxRange(String(range[1]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInputValue(String(value));
|
||||||
|
await sendAnswerToBackend(String(value));
|
||||||
}}
|
}}
|
||||||
|
//@ts-ignore
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
"& .MuiSlider-valueLabel": {
|
"& .MuiSlider-valueLabel": {
|
||||||
background: theme.palette.primary.main,
|
background: theme.palette.primary.main,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!currentQuestion.content.chooseRange && (
|
{!currentQuestion.content.chooseRange && (
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
value={answer}
|
value={inputValue}
|
||||||
onChange={async ({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateMinRangeDebounced(window.Number(target.value.replace (/\D/, '')) > max
|
const value = target.value.replace(/\D/g, "");
|
||||||
? String(max)
|
setInputValue(value);
|
||||||
: window.Number(target.value) < min
|
updateValueDebounced(value);
|
||||||
? String(min)
|
|
||||||
: target.value, true);
|
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "80px",
|
maxWidth: "80px",
|
||||||
borderColor: theme.palette.text.primary,
|
borderColor: theme.palette.text.primary,
|
||||||
"& .MuiInputBase-input": {
|
"& .MuiInputBase-input": {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -178,22 +196,25 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
placeholder="0"
|
placeholder="0"
|
||||||
value={minRange}
|
value={minRange}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
setMinRange(target.value.replace (/\D/, ''));
|
const newValue = target.value.replace(/\D/g, "");
|
||||||
|
setMinRange(newValue);
|
||||||
|
|
||||||
if (window.Number(target.value) >= window.Number(maxRange)) {
|
if (window.Number(newValue) >= window.Number(maxRange)) {
|
||||||
updateMinRangeDebounced(`${maxRange}—${maxRange}`, true);
|
updateMinRangeDebounced(`${maxRange}—${maxRange}`, true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMinRangeDebounced(`${target.value}—${maxRange}`);
|
updateMinRangeDebounced(`${newValue}—${maxRange}`);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "80px",
|
maxWidth: "80px",
|
||||||
borderColor: theme.palette.text.primary,
|
borderColor: theme.palette.text.primary,
|
||||||
"& .MuiInputBase-input": {
|
"& .MuiInputBase-input": {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -202,22 +223,25 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
placeholder="0"
|
placeholder="0"
|
||||||
value={maxRange}
|
value={maxRange}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
setMaxRange(target.value.replace (/\D/, ''));
|
const newValue = target.value.replace(/\D/g, "");
|
||||||
|
setMaxRange(newValue);
|
||||||
|
|
||||||
if (window.Number(target.value) <= window.Number(minRange)) {
|
if (window.Number(newValue) <= window.Number(minRange)) {
|
||||||
updateMaxRangeDebounced(`${minRange}—${minRange}`, true);
|
updateMaxRangeDebounced(`${minRange}—${minRange}`, true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMaxRangeDebounced(`${minRange}—${target.value}`);
|
updateMaxRangeDebounced(`${minRange}—${newValue}`);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: "80px",
|
maxWidth: "80px",
|
||||||
borderColor: theme.palette.text.primary,
|
borderColor: theme.palette.text.primary,
|
||||||
"& .MuiInputBase-input": {
|
"& .MuiInputBase-input": {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -226,5 +250,4 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -99,7 +99,7 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
|
|||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: String(value),
|
body: String(value) + " из " + currentQuestion.content.steps,
|
||||||
qid: settings.qid
|
qid: settings.qid
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentQuestion.content.variants[index].answer,
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
qid: settings.qid
|
qid: settings.qid
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import type {
|
|||||||
} from "model/questionTypes/file";
|
} from "model/questionTypes/file";
|
||||||
|
|
||||||
export const UPLOAD_FILE_TYPES_MAP: Record<UploadFileType, string> = {
|
export const UPLOAD_FILE_TYPES_MAP: Record<UploadFileType, string> = {
|
||||||
all: "file",
|
|
||||||
picture: "image/*",
|
picture: "image/*",
|
||||||
video: "video/*",
|
video: "video/*",
|
||||||
audio: "audio/*",
|
audio: "audio/*",
|
||||||
@ -23,7 +22,7 @@ export default function File({ question }: Props) {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [acceptedType, setAcceptedType] = useState<any>(
|
const [acceptedType, setAcceptedType] = useState<any>(
|
||||||
UPLOAD_FILE_TYPES_MAP.all
|
UPLOAD_FILE_TYPES_MAP.picture
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -3,10 +3,11 @@ import { produce } from "immer";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
|
import type { Moment } from "moment";
|
||||||
|
|
||||||
type Answer = {
|
type Answer = {
|
||||||
questionId: string;
|
questionId: string;
|
||||||
answer: string | string[];
|
answer: string | string[] | Moment;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OwnVariant = {
|
type OwnVariant = {
|
||||||
@ -40,7 +41,10 @@ function setProducedState<A extends string | { type: string; }>(
|
|||||||
useQuizViewStore.setState(state => produce(state, recipe), false, action);
|
useQuizViewStore.setState(state => produce(state, recipe), false, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAnswer = (questionId: string, answer: string | string[]) => setProducedState(state => {
|
export const updateAnswer = (
|
||||||
|
questionId: string,
|
||||||
|
answer: string | string[] | Moment
|
||||||
|
) => setProducedState(state => {
|
||||||
const index = state.answers.findIndex(answer => questionId === answer.questionId);
|
const index = state.answers.findIndex(answer => questionId === answer.questionId);
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
|
69
src/ui_kit/LabeledDatePicker.tsx
Normal file
69
src/ui_kit/LabeledDatePicker.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import CalendarIcon from "@icons/CalendarIcon";
|
||||||
|
import { Typography, Box, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import { DatePicker } from "@mui/x-date-pickers";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label?: string;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
value?: moment.Moment;
|
||||||
|
onChange?: (value: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LabeledDatePicker({ label, value = moment(), onChange, sx }: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upLg = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label && (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "20px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
mb: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
<DatePicker
|
||||||
|
//@ts-ignore
|
||||||
|
value={value._d}
|
||||||
|
onChange={onChange}
|
||||||
|
slots={{
|
||||||
|
openPickerIcon: () => <CalendarIcon />,
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
openPickerButton: {
|
||||||
|
sx: {
|
||||||
|
p: 0,
|
||||||
|
},
|
||||||
|
"data-cy": "open-datepicker",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
"& .MuiInputBase-root": {
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
borderRadius: "10px",
|
||||||
|
pr: "22px",
|
||||||
|
"& input": {
|
||||||
|
py: "11px",
|
||||||
|
pl: upLg ? "20px" : "13px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
},
|
||||||
|
"& fieldset": {
|
||||||
|
borderColor: "#9A9AAF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
56
src/utils/hooks/useGetSettings.ts
Normal file
56
src/utils/hooks/useGetSettings.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||||
|
import { useQuestionsStore } from "@stores/quizData/store";
|
||||||
|
|
||||||
|
import { getData } from "@api/quizRelase"
|
||||||
|
|
||||||
|
interface SettingsGetter {
|
||||||
|
quizId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGetSettings(quizId: string) {
|
||||||
|
|
||||||
|
const [fetchState, setFetchState] = useState<"fetching" | "idle" | "all fetched">("idle")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function get() {
|
||||||
|
const data = await getData(quizId)
|
||||||
|
//@ts-ignore
|
||||||
|
const settings = data.settings
|
||||||
|
const parseData = {
|
||||||
|
settings: {
|
||||||
|
fp: settings.fp,
|
||||||
|
rep: settings.rep,
|
||||||
|
name: settings.name,
|
||||||
|
cfg: JSON.parse(settings?.cfg),
|
||||||
|
lim: settings.lim,
|
||||||
|
due: settings.due,
|
||||||
|
delay: settings.delay,
|
||||||
|
pausable: settings.pausable
|
||||||
|
},
|
||||||
|
//@ts-ignore
|
||||||
|
items: data.items.map((item) => {
|
||||||
|
const content = JSON.parse(item.c)
|
||||||
|
return {
|
||||||
|
description: item.desc,
|
||||||
|
id: item.id,
|
||||||
|
page: item.p,
|
||||||
|
required: item.req,
|
||||||
|
title: item.title,
|
||||||
|
type: item.typ,
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
//@ts-ignore
|
||||||
|
cnt: data.cnt
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
useQuestionsStore.setState(parseData)
|
||||||
|
}
|
||||||
|
get()
|
||||||
|
// const controller = new AbortController()
|
||||||
|
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
return
|
||||||
|
// return
|
||||||
|
}
|
@ -1471,7 +1471,7 @@ dashdash@^1.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
dayjs@^1.10.4, dayjs@^1.11.10:
|
dayjs@^1.10.4:
|
||||||
version "1.11.10"
|
version "1.11.10"
|
||||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
|
||||||
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
|
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
|
||||||
@ -2444,6 +2444,11 @@ minimist@^1.2.8:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
|
|
||||||
|
moment@^2.30.1:
|
||||||
|
version "2.30.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
|
||||||
|
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
|
||||||
|
|
||||||
ms@2.1.2:
|
ms@2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
Loading…
Reference in New Issue
Block a user