Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
77def75671 | |||
c61aa5a5a7 | |||
e5610b3346 | |||
ad3929dd9f | |||
0c13ccf313 | |||
c97085b394 | |||
63fd61537c |
@ -3,12 +3,9 @@ include:
|
|||||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
file: "/templates/docker/build-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"
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/templates/docker/service-discovery.gitlab-ci.yml"
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
- service-discovery
|
|
||||||
|
|
||||||
build-app:
|
build-app:
|
||||||
tags:
|
tags:
|
||||||
@ -17,7 +14,7 @@ build-app:
|
|||||||
variables:
|
variables:
|
||||||
DOCKER_BUILD_PATH: "./Dockerfile"
|
DOCKER_BUILD_PATH: "./Dockerfile"
|
||||||
STAGING_BRANCH: "staging"
|
STAGING_BRANCH: "staging"
|
||||||
PRODUCTION_BRANCH: "main"
|
PRODUCTION_BRANCH: "eng"
|
||||||
|
|
||||||
deploy-to-staging:
|
deploy-to-staging:
|
||||||
extends: .deploy_template
|
extends: .deploy_template
|
||||||
@ -29,11 +26,12 @@ deploy-to-staging:
|
|||||||
|
|
||||||
deploy-to-prod:
|
deploy-to-prod:
|
||||||
extends: .deploy_template
|
extends: .deploy_template
|
||||||
|
variables:
|
||||||
|
DOCKER_BUILD_PATH: "./Dockerfile"
|
||||||
|
STAGING_BRANCH: "staging"
|
||||||
|
PRODUCTION_BRANCH: "eng"
|
||||||
rules:
|
rules:
|
||||||
- if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
- if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
||||||
tags:
|
tags:
|
||||||
- front
|
- front
|
||||||
- prod
|
- prod
|
||||||
|
|
||||||
service-discovery:
|
|
||||||
extends: .sd_artefacts_template
|
|
||||||
|
13
deployments/eng/docker-compose.yaml
Normal file
13
deployments/eng/docker-compose.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
respen:
|
||||||
|
container_name: respen
|
||||||
|
restart: unless-stopped
|
||||||
|
image: $CI_REGISTRY_IMAGE/eng:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
hostname: respen
|
||||||
|
tty: true
|
||||||
|
networks:
|
||||||
|
- main_default
|
||||||
|
networks:
|
||||||
|
main_default:
|
||||||
|
external: true
|
@ -1,9 +1,8 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
respondent:
|
respondent_en:
|
||||||
container_name: respondent
|
container_name: respondent_en
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
image: $CI_REGISTRY_IMAGE/eng:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
hostname: respondent
|
hostname: respondent_en
|
||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
|
BIN
dist-package.zip
Normal file
BIN
dist-package.zip
Normal file
Binary file not shown.
@ -20,8 +20,8 @@ import { ApologyPage } from "./ViewPublicationPage/ApologyPage";
|
|||||||
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage";
|
||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
|
|
||||||
import "moment/dist/locale/ru";
|
import "moment/dist/locale/en-ca";
|
||||||
moment.locale("ru");
|
moment.locale("en");
|
||||||
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -4,15 +4,7 @@ import { FallbackProps } from "react-error-boundary";
|
|||||||
type Props = Partial<FallbackProps>;
|
type Props = Partial<FallbackProps>;
|
||||||
|
|
||||||
export const ApologyPage = ({ error }: Props) => {
|
export const ApologyPage = ({ error }: Props) => {
|
||||||
let message = "Что-то пошло не так";
|
let message = error?.message ?? error.response?.data ?? "Something went wrong";
|
||||||
|
|
||||||
if (error.response?.data === "quiz is inactive") message = "Квиз не активирован";
|
|
||||||
if (error.message === "No questions found") message = "Нет созданных вопросов";
|
|
||||||
if (error.message === "Quiz is empty") message = "Квиз пуст";
|
|
||||||
if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос";
|
|
||||||
if (error.message === "No quiz id") message = "Отсутствует id квиза";
|
|
||||||
if (error.message === "Quiz data is null") message = "Не были переданы параметры квиза";
|
|
||||||
if (error.response?.data === "Invalid request data") message = "Такого квиза не существует";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
@ -85,7 +85,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
if (email.length > 0) body.email = email;
|
if (email.length > 0) body.email = email;
|
||||||
if (phone.length > 0) body.phone = phone;
|
if (phone.length > 0) body.phone = phone;
|
||||||
if (adress.length > 0) body.address = adress;
|
if (adress.length > 0) body.address = adress;
|
||||||
if (text.length > 0) body.customs = { [FC.text.text || "Фамилия"]: text };
|
if (text.length > 0) body.customs = { [FC.text.text || "Surname"]: text };
|
||||||
|
|
||||||
if (Object.keys(body).length > 0) {
|
if (Object.keys(body).length > 0) {
|
||||||
try {
|
try {
|
||||||
@ -99,7 +99,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
enqueueSnackbar("The answer was not counted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -119,12 +119,12 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
const FC = settings.cfg.formContact.fields;
|
const FC = settings.cfg.formContact.fields;
|
||||||
|
|
||||||
if (!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)) {
|
if (!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)) {
|
||||||
return enqueueSnackbar("введена некорректная почта");
|
return enqueueSnackbar("Incorrect email entered");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fireOnce.current) {
|
if (fireOnce.current) {
|
||||||
if (name.length === 0 && email.length === 0 && phone.length === 0 && text.length === 0 && adress.length === 0)
|
if (name.length === 0 && email.length === 0 && phone.length === 0 && text.length === 0 && adress.length === 0)
|
||||||
return enqueueSnackbar("Пожалуйста, заполните поля");
|
return enqueueSnackbar("Please fill in the fields");
|
||||||
|
|
||||||
//почта валидна, хоть одно поле заполнено
|
//почта валидна, хоть одно поле заполнено
|
||||||
setFire(true);
|
setFire(true);
|
||||||
@ -159,7 +159,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
yandexMetrics.contactsFormField("address");
|
yandexMetrics.contactsFormField("address");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("повторите попытку позже");
|
enqueueSnackbar("please try again later");
|
||||||
}
|
}
|
||||||
if (settings.cfg.resultInfo.showResultForm === "after") {
|
if (settings.cfg.resultInfo.showResultForm === "after") {
|
||||||
onShowResult();
|
onShowResult();
|
||||||
@ -281,22 +281,22 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
}}
|
}}
|
||||||
fontSize={"16px"}
|
fontSize={"16px"}
|
||||||
>
|
>
|
||||||
С 
|
I agree with the 
|
||||||
<Link
|
<Link
|
||||||
href={"https://shub.pena.digital/ppdd"}
|
href={"https://shub.pena.digital/ppdd"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Положением об обработке персональных данных{" "}
|
Regulation on the processing of personal data{" "}
|
||||||
</Link>
|
</Link>
|
||||||
 и 
|
 and the 
|
||||||
<Link
|
<Link
|
||||||
href={"https://shub.pena.digital/docs/privacy"}
|
href={"https://shub.pena.digital/docs/privacy"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
Политикой конфиденциальности{" "}
|
Privacy Policy{" "}
|
||||||
</Link>
|
</Link>
|
||||||
 ознакомлен
|
 agree
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings.cfg.formContact?.button || "Получить результаты"}
|
{settings.cfg.formContact?.button || "Get results"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{show_badge && (
|
{show_badge && (
|
||||||
|
@ -45,7 +45,7 @@ export const ContactTextBlock: FC<ContactTextBlockProps> = ({ settings }) => {
|
|||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings.cfg.formContact.title || "Заполните форму, чтобы получить результаты теста"}
|
{settings.cfg.formContact.title || "Fill out the form to receive your test results"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{settings.cfg.formContact.desc && (
|
{settings.cfg.formContact.desc && (
|
||||||
<Typography
|
<Typography
|
||||||
|
@ -45,8 +45,8 @@ export const Inputs = ({
|
|||||||
<CustomInput
|
<CustomInput
|
||||||
onChange={({ target }) => setName(target.value)}
|
onChange={({ target }) => setName(target.value)}
|
||||||
id={name}
|
id={name}
|
||||||
title={FC["name"].innerText || "Введите имя"}
|
title={FC["name"].innerText || "Enter your name"}
|
||||||
desc={FC["name"].text || "Имя"}
|
desc={FC["name"].text || "Name"}
|
||||||
Icon={NameIcon}
|
Icon={NameIcon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -56,7 +56,7 @@ export const Inputs = ({
|
|||||||
setEmail(target.value.replaceAll(/\s/g, ""));
|
setEmail(target.value.replaceAll(/\s/g, ""));
|
||||||
}}
|
}}
|
||||||
id={email}
|
id={email}
|
||||||
title={FC["email"].innerText || "Введите Email"}
|
title={FC["email"].innerText || "Enter your Email"}
|
||||||
desc={FC["email"].text || "Email"}
|
desc={FC["email"].text || "Email"}
|
||||||
Icon={EmailIcon}
|
Icon={EmailIcon}
|
||||||
type="email"
|
type="email"
|
||||||
@ -70,8 +70,8 @@ export const Inputs = ({
|
|||||||
}}
|
}}
|
||||||
value={phone}
|
value={phone}
|
||||||
id={phone}
|
id={phone}
|
||||||
title={FC["phone"].innerText || "Введите номер телефона"}
|
title={FC["phone"].innerText || "Enter your phone number"}
|
||||||
desc={FC["phone"].text || "Номер телефона"}
|
desc={FC["phone"].text || "Phone number"}
|
||||||
Icon={PhoneIcon}
|
Icon={PhoneIcon}
|
||||||
isPhone={true}
|
isPhone={true}
|
||||||
/>
|
/>
|
||||||
@ -80,8 +80,8 @@ export const Inputs = ({
|
|||||||
<CustomInput
|
<CustomInput
|
||||||
onChange={({ target }) => setText(target.value)}
|
onChange={({ target }) => setText(target.value)}
|
||||||
id={text}
|
id={text}
|
||||||
title={FC["text"].text || "Введите фамилию"}
|
title={FC["text"].text || "Enter your surname"}
|
||||||
desc={FC["text"].innerText || "Фамилия"}
|
desc={FC["text"].innerText || "Surname"}
|
||||||
Icon={TextIcon}
|
Icon={TextIcon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -89,8 +89,8 @@ export const Inputs = ({
|
|||||||
<CustomInput
|
<CustomInput
|
||||||
onChange={({ target }) => setAdress(target.value)}
|
onChange={({ target }) => setAdress(target.value)}
|
||||||
id={adress}
|
id={adress}
|
||||||
title={FC["address"].innerText || "Введите адрес"}
|
title={FC["address"].innerText || "Enter your address"}
|
||||||
desc={FC["address"].text || "Адрес"}
|
desc={FC["address"].text || "Address"}
|
||||||
Icon={AddressIcon}
|
Icon={AddressIcon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
|||||||
{stepNumber !== null && (
|
{stepNumber !== null && (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
<Typography sx={{ color: theme.palette.text.primary }}>
|
||||||
Вопрос {stepNumber} из {questionsAmount}
|
Question {stepNumber} of {questionsAmount}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stepper
|
<Stepper
|
||||||
activeStep={stepNumber}
|
activeStep={stepNumber}
|
||||||
|
@ -58,7 +58,7 @@ export const PointSystemResultList = () => {
|
|||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentQuestion.title || "Вопрос без названия"}
|
{currentQuestion.title || "Question without a title"}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
@ -81,7 +81,7 @@ export const PointSystemResultList = () => {
|
|||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ваш ответ:
|
Your answer:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -135,7 +135,7 @@ const Line = ({ checkTrue, text }: LineProps) => {
|
|||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{text || "не выбрано"}
|
{text || "not selected"}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -35,7 +35,7 @@ export default function QuestionSelect({ selectedQuestion, setQuestion }: Props)
|
|||||||
id="category-select"
|
id="category-select"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={selectedQuestion.id}
|
value={selectedQuestion.id}
|
||||||
placeholder="Заголовок вопроса"
|
placeholder="Question title"
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
setQuestion(target.value);
|
setQuestion(target.value);
|
||||||
}}
|
}}
|
||||||
|
@ -55,7 +55,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("Заявка не может быть отправлена");
|
enqueueSnackbar("The request could not be sent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Boolean(settings.cfg.score)) {
|
if (Boolean(settings.cfg.score)) {
|
||||||
@ -70,7 +70,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
localStorage.setItem("sessions", JSON.stringify({ ...sessions, [quizId]: new Date().getTime() }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("Количество баллов не может быть отправлено");
|
enqueueSnackbar("The number of points could not be sent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@ -154,7 +154,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
wordBreak: "break-word",
|
wordBreak: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ваш результат:
|
Your result:
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
@ -248,7 +248,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ваши баллы
|
Your points
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
@ -269,7 +269,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Посмотреть ответы
|
View answers
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
sx={{
|
sx={{
|
||||||
@ -339,7 +339,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
height: "50px",
|
height: "50px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resultQuestion.content.hint.text || "Узнать подробнее"}
|
{resultQuestion.content.hint.text || "More information"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{settings.cfg.resultInfo.showResultForm === "after" && resultQuestion.content.redirect && (
|
{settings.cfg.resultInfo.showResultForm === "after" && resultQuestion.content.redirect && (
|
||||||
@ -361,7 +361,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
|||||||
width: "auto",
|
width: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resultQuestion.content.hint.text || "Перейти на сайт"}
|
{resultQuestion.content.hint.text || "Go to website"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -302,7 +302,7 @@ export const StartPageViewPublication = () => {
|
|||||||
}}
|
}}
|
||||||
onClick={onQuizStart}
|
onClick={onQuizStart}
|
||||||
>
|
>
|
||||||
{settings.cfg.startpage.button.trim() ? settings.cfg.startpage.button : "Пройти тест"}
|
{settings.cfg.startpage.button.trim() ? settings.cfg.startpage.button : "Take the test"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -74,7 +74,7 @@ export default function ViewPublicationPage() {
|
|||||||
textAlign={"center"}
|
textAlign={"center"}
|
||||||
mt="50px"
|
mt="50px"
|
||||||
>
|
>
|
||||||
Вопрос не выбран
|
Question not selected
|
||||||
</Typography>
|
</Typography>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
@ -113,7 +113,7 @@ export default function ViewPublicationPage() {
|
|||||||
if (preview) return;
|
if (preview) return;
|
||||||
|
|
||||||
sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => {
|
sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => {
|
||||||
enqueueSnackbar("Ошибка при отправке ответа");
|
enqueueSnackbar("Error sending answer");
|
||||||
console.error("Error sending answer", e);
|
console.error("Error sending answer", e);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -50,7 +50,7 @@ export default ({ currentQuestion }: DateProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>От</span>
|
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>From</span>
|
||||||
<DateCalendar
|
<DateCalendar
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiInputBase-root": {
|
"& .MuiInputBase-root": {
|
||||||
@ -73,7 +73,7 @@ export default ({ currentQuestion }: DateProps) => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>До</span>
|
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>To</span>
|
||||||
<DateCalendar
|
<DateCalendar
|
||||||
sx={{
|
sx={{
|
||||||
"& .MuiInputBase-root": {
|
"& .MuiInputBase-root": {
|
||||||
|
@ -181,7 +181,7 @@ export const EmojiVariant = ({
|
|||||||
pl: "15px",
|
pl: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Введите свой ответ
|
Enter your answer
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -68,7 +68,7 @@ export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, se
|
|||||||
updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0);
|
updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
enqueueSnackbar("the answer was not counted");
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSending(false);
|
setIsSending(false);
|
||||||
|
@ -39,7 +39,7 @@ export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProp
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||||
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
<Typography color={theme.palette.text.primary}>You have uploaded:</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
padding: "5px 5px 5px 16px",
|
padding: "5px 5px 5px 16px",
|
||||||
|
@ -105,14 +105,14 @@ const CurrentModal = ({ status }: { status: ModalWarningType }) => {
|
|||||||
case null:
|
case null:
|
||||||
return null;
|
return null;
|
||||||
case "errorType":
|
case "errorType":
|
||||||
return <Typography>Выбран некорректный тип файла</Typography>;
|
return <Typography>Incorrect file type selected</Typography>;
|
||||||
case "errorSize":
|
case "errorSize":
|
||||||
return <Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>;
|
return <Typography>File is too big. Maximum size is 50 MB</Typography>;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>Допустимые расширения файлов:</Typography>
|
<Typography>Acceptable file extensions:</Typography>
|
||||||
<Typography>{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
<Typography>{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -204,7 +204,7 @@ export const ImageVariant = ({
|
|||||||
pl: "15px",
|
pl: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Введите свой ответ
|
Enter your answer
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -368,7 +368,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography color={theme.palette.text.primary}>до</Typography>
|
<Typography color={theme.palette.text.primary}>to</Typography>
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
value={reversed ? String(reversedMaxRange) : maxRange}
|
value={reversed ? String(reversedMaxRange) : maxRange}
|
||||||
|
@ -195,7 +195,7 @@ export const VariantItem = ({
|
|||||||
top: "-23px",
|
top: "-23px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Введите свой ответ
|
Enter your answer
|
||||||
</Typography>
|
</Typography>
|
||||||
<OwnInput
|
<OwnInput
|
||||||
questionId={questionId}
|
questionId={questionId}
|
||||||
|
@ -127,7 +127,7 @@ export const VarimgVariant = ({
|
|||||||
pl: "15px",
|
pl: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Введите свой ответ
|
Enter your answer
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -156,9 +156,9 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
) : currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0 ? (
|
) : currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0 ? (
|
||||||
currentQuestion.content.replText
|
currentQuestion.content.replText
|
||||||
) : variant?.extendedText || isMobile ? (
|
) : variant?.extendedText || isMobile ? (
|
||||||
"Выберите вариант ответа ниже"
|
"Select an answer option below"
|
||||||
) : (
|
) : (
|
||||||
"Выберите вариант ответа слева"
|
"Select an answer option on the left"
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -23,7 +23,7 @@ export default function NextButton({ isNextButtonEnabled, moveToNextQuestion }:
|
|||||||
}}
|
}}
|
||||||
onClick={moveToNextQuestion}
|
onClick={moveToNextQuestion}
|
||||||
>
|
>
|
||||||
Далее →
|
Next →
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ export default function PrevButton({ isPreviousButtonEnabled, moveToPrevQuestion
|
|||||||
}}
|
}}
|
||||||
onClick={moveToPrevQuestion}
|
onClick={moveToPrevQuestion}
|
||||||
>
|
>
|
||||||
{isMobileMini ? "←" : "← Назад"}
|
{isMobileMini ? "←" : "← Back"}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,15 @@ export const MAX_FILE_SIZE = 419430400;
|
|||||||
|
|
||||||
export const UPLOAD_FILE_DESCRIPTIONS_MAP = {
|
export const UPLOAD_FILE_DESCRIPTIONS_MAP = {
|
||||||
picture: {
|
picture: {
|
||||||
title: "Добавить изображение",
|
title: "Add image",
|
||||||
description: "Принимает изображения",
|
description: "Accepts images",
|
||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
title: "Добавить видео",
|
title: "Add video",
|
||||||
description: "Принимает .mp4 и .mov формат — максимум 50мб",
|
description: "Accepts .mp4 and .mov format - maximum 50mb",
|
||||||
},
|
},
|
||||||
audio: { title: "Добавить аудиофайл", description: "Принимает аудиофайлы" },
|
audio: { title: "Add audio file", description: "Accepts audio files" },
|
||||||
document: { title: "Добавить документ", description: "Принимает документы" },
|
document: { title: "Add document", description: "Accepts documents" },
|
||||||
} as const satisfies Record<UploadFileType, { title: string; description: string }>;
|
} as const satisfies Record<UploadFileType, { title: string; description: string }>;
|
||||||
|
|
||||||
export const ACCEPT_SEND_FILE_TYPES_MAP = {
|
export const ACCEPT_SEND_FILE_TYPES_MAP = {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
|
import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared";
|
||||||
|
|
||||||
export const UPLOAD_FILE_TYPES_MAP = {
|
export const UPLOAD_FILE_TYPES_MAP = {
|
||||||
picture: "Изображения",
|
picture: "Image",
|
||||||
video: "Видео",
|
video: "Video",
|
||||||
audio: "Аудио",
|
audio: "Audio",
|
||||||
document: "Документ",
|
document: "Document",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type UploadFileType = keyof typeof UPLOAD_FILE_TYPES_MAP;
|
export type UploadFileType = keyof typeof UPLOAD_FILE_TYPES_MAP;
|
||||||
|
@ -6,44 +6,33 @@ export type ServerError = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const translateMessage: Record<string, string> = {
|
|
||||||
"user not found": "Пользователь не найден",
|
|
||||||
"invalid password": "Неправильный пароль",
|
|
||||||
"field <password> is empty": 'Поле "Пароль" не заполнено',
|
|
||||||
"field <login> is empty": 'Поле "Логин" не заполнено',
|
|
||||||
"field <email> is empty": 'Поле "E-mail" не заполнено',
|
|
||||||
"field <phoneNumber> is empty": 'Поле "Номер телефона" не заполнено',
|
|
||||||
"user with this email or login is exist": "Пользователь уже существует",
|
|
||||||
"user with this login is exist": "Пользователь с таким логином уже существует",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parseAxiosError = (nativeError: unknown): [string, number?] => {
|
export const parseAxiosError = (nativeError: unknown): [string, number?] => {
|
||||||
const error = nativeError as AxiosError;
|
const error = nativeError as AxiosError;
|
||||||
|
|
||||||
if (error.response?.data && "statusCode" in (error.response.data as ServerError)) {
|
if (error.response?.data && "statusCode" in (error.response.data as ServerError)) {
|
||||||
const serverError = error.response.data as ServerError;
|
const serverError = error.response.data as ServerError;
|
||||||
const translatedMessage = translateMessage[serverError.message];
|
const translatedMessage = serverError.message;
|
||||||
if (translatedMessage !== undefined) serverError.message = translatedMessage;
|
if (translatedMessage !== undefined) serverError.message = translatedMessage;
|
||||||
return [serverError.message, serverError.statusCode];
|
return [serverError.message, serverError.statusCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (error.status) {
|
switch (error.status) {
|
||||||
case 404:
|
case 404:
|
||||||
return ["Не найдено.", error.status];
|
return ["Not found.", error.status];
|
||||||
|
|
||||||
case 403:
|
case 403:
|
||||||
return ["Доступ ограничен.", error.status];
|
return ["Access is restricted.", error.status];
|
||||||
|
|
||||||
case 401:
|
case 401:
|
||||||
return ["Ошибка авторизации.", error.status];
|
return ["Authorization error.", error.status];
|
||||||
|
|
||||||
case 500:
|
case 500:
|
||||||
return ["Внутренняя ошибка сервера.", error.status];
|
return ["Internal Server Error.", error.status];
|
||||||
|
|
||||||
case 503:
|
case 503:
|
||||||
return ["Сервис недоступен.", error.status];
|
return ["Service unavailable.", error.status];
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ["Неизвестная ошибка сервера."];
|
return ["Unknown server error."];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,14 +5,14 @@ import { StrictMode, lazy } from "react";
|
|||||||
|
|
||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/en/",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
element: <App />,
|
element: <App />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ":quizId",
|
path: "/en/:quizId",
|
||||||
element: <App />,
|
element: <App />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -16,8 +16,8 @@ export default function QuizBanner({
|
|||||||
quizId,
|
quizId,
|
||||||
position,
|
position,
|
||||||
onWidgetClose,
|
onWidgetClose,
|
||||||
appealText = "Пройти тест",
|
appealText = "Take the test",
|
||||||
quizHeaderText = "Заголовок квиза",
|
quizHeaderText = "Quiz Title",
|
||||||
buttonTextColor,
|
buttonTextColor,
|
||||||
buttonBackgroundColor,
|
buttonBackgroundColor,
|
||||||
autoShowQuizTime = null,
|
autoShowQuizTime = null,
|
||||||
@ -178,10 +178,16 @@ export default function QuizBanner({
|
|||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography fontSize="24px" lineHeight="120%">
|
<Typography
|
||||||
|
fontSize="24px"
|
||||||
|
lineHeight="120%"
|
||||||
|
>
|
||||||
{appealText}
|
{appealText}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography fontSize="44px" lineHeight="120%">
|
<Typography
|
||||||
|
fontSize="44px"
|
||||||
|
lineHeight="120%"
|
||||||
|
>
|
||||||
{quizHeaderText}
|
{quizHeaderText}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@ -202,7 +208,11 @@ export default function QuizBanner({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 7 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
viewBox="0 0 7 7"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M1.00391 0.757812L6.67266 6.42656M1.00391 6.42656L6.67266 0.757812"
|
d="M1.00391 0.757812L6.67266 6.42656M1.00391 6.42656L6.67266 0.757812"
|
||||||
stroke="white"
|
stroke="white"
|
||||||
|
@ -17,7 +17,7 @@ export default function OpenQuizButton({
|
|||||||
buttonFlash = false,
|
buttonFlash = false,
|
||||||
withShadow = false,
|
withShadow = false,
|
||||||
rounded = false,
|
rounded = false,
|
||||||
buttonText = "Пройти квиз",
|
buttonText = "Take the quiz",
|
||||||
buttonTextColor,
|
buttonTextColor,
|
||||||
buttonBackgroundColor,
|
buttonBackgroundColor,
|
||||||
fullScreen = false,
|
fullScreen = false,
|
||||||
|
@ -120,7 +120,7 @@ export default function QuizSideButton({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{buttonText || "Пройти квиз"}
|
{buttonText || "Take the quiz"}
|
||||||
{showButtonFlash && <RunningStripe />}
|
{showButtonFlash && <RunningStripe />}
|
||||||
</Button>
|
</Button>
|
||||||
</Fade>
|
</Fade>
|
||||||
|
@ -16,6 +16,9 @@ export const alias = {
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
build: {
|
||||||
|
assetsDir: "en/assets/",
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias,
|
alias,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user