не ветвиться отсюда, залив без тестового просмотра
This commit is contained in:
parent
7c898d7e83
commit
6302afa071
@ -7,7 +7,6 @@
|
|||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@frontend/kitui": "^1.0.63",
|
"@frontend/kitui": "^1.0.63",
|
||||||
"@frontend/squzanswerer": "^1.0.0",
|
|
||||||
"@mui/icons-material": "^5.10.14",
|
"@mui/icons-material": "^5.10.14",
|
||||||
"@mui/material": "^5.10.14",
|
"@mui/material": "^5.10.14",
|
||||||
"@mui/x-date-pickers": "^6.16.1",
|
"@mui/x-date-pickers": "^6.16.1",
|
||||||
|
@ -236,7 +236,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/list" element={<MyQuizzesFull />} />
|
<Route path="/list" element={<MyQuizzesFull />} />
|
||||||
<Route path={"/view/:quizId"} element={<ViewPage />} />
|
{/* <Route path={"/view/:quizId"} element={<ViewPage />} /> */}
|
||||||
<Route path={"/tariffs"} element={<Tariffs />} />
|
<Route path={"/tariffs"} element={<Tariffs />} />
|
||||||
<Route path={"/results/:quizId"} element={<QuizAnswersPage />} />
|
<Route path={"/results/:quizId"} element={<QuizAnswersPage />} />
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
|
273
src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx
Normal file
273
src/pages/Questions/ButtonsOptions/ButtonsOptions.tsx
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
||||||
|
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||||
|
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
||||||
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
|
import type { SxProps } from "@mui/material";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
copyQuestion,
|
||||||
|
deleteQuestion,
|
||||||
|
deleteQuestionWithTimeout,
|
||||||
|
clearRuleForAll,
|
||||||
|
updateQuestion,
|
||||||
|
getQuestionByContentId,
|
||||||
|
} from "@root/questions/actions";
|
||||||
|
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||||
|
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||||
|
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||||
|
import Branching from "../../assets/icons/questionsPage/branching";
|
||||||
|
import Clue from "../../assets/icons/questionsPage/clue";
|
||||||
|
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||||
|
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||||
|
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||||
|
import { updateRootContentId } from "@root/quizes/actions";
|
||||||
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { updateSomeWorkBackend } from "@root/uiTools/actions";
|
||||||
|
import { DeleteFunction } from "@utils/deleteFunc";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
switchState: string;
|
||||||
|
SSHC: (data: string) => void;
|
||||||
|
question: AnyTypedQuizQuestion;
|
||||||
|
sx?: SxProps;
|
||||||
|
openBranchingPage: boolean;
|
||||||
|
setOpenBranchingPage: (a: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ButtonsOptions({
|
||||||
|
SSHC,
|
||||||
|
switchState,
|
||||||
|
question,
|
||||||
|
openBranchingPage,
|
||||||
|
setOpenBranchingPage,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const { questions } = useQuestionsStore.getState();
|
||||||
|
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const openedModal = () => {
|
||||||
|
setOpenBranchingPage(true);
|
||||||
|
updateDesireToOpenABranchingModal(question.content.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonSetting: {
|
||||||
|
icon: JSX.Element;
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
myFunc?: any;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<SettingIcon
|
||||||
|
color={
|
||||||
|
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
title: "Настройки",
|
||||||
|
value: "setting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<Branching
|
||||||
|
color={
|
||||||
|
switchState === "branching" ? "#ffffff" : theme.palette.grey3.main
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
title: "Ветвление",
|
||||||
|
value: "branching",
|
||||||
|
myFunc: (question) => {
|
||||||
|
setOpenBranchingPage(true);
|
||||||
|
updateDesireToOpenABranchingModal(question.content.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
background: "#f2f3f7",
|
||||||
|
height: isMobile ? "92px" : "70px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{buttonSetting.map(({ icon, title, value, myFunc }) => (
|
||||||
|
<Box key={value}>
|
||||||
|
{value === "branching" ? (
|
||||||
|
question.type === "page" ||
|
||||||
|
question.type === "text" ||
|
||||||
|
question.type === "date" ||
|
||||||
|
question.type === "number" ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
<MiniButtonSetting
|
||||||
|
key={title}
|
||||||
|
onClick={() => {
|
||||||
|
openedModal();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
display: quiz.config.type === "form" ? "none" : "flex",
|
||||||
|
backgroundColor:
|
||||||
|
switchState === value
|
||||||
|
? theme.palette.brightPurple.main
|
||||||
|
: "transparent",
|
||||||
|
color:
|
||||||
|
switchState === value
|
||||||
|
? "#ffffff"
|
||||||
|
: theme.palette.grey3.main,
|
||||||
|
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||||
|
height: "30px",
|
||||||
|
"&:hover": {
|
||||||
|
color: theme.palette.grey3.main,
|
||||||
|
"& path": { stroke: theme.palette.grey3.main },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{isWrappMiniButtonSetting ? null : title}
|
||||||
|
</MiniButtonSetting>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<MiniButtonSetting
|
||||||
|
key={title}
|
||||||
|
onClick={() => {
|
||||||
|
SSHC(value);
|
||||||
|
myFunc();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
backgroundColor:
|
||||||
|
switchState === value
|
||||||
|
? theme.palette.brightPurple.main
|
||||||
|
: "transparent",
|
||||||
|
color:
|
||||||
|
switchState === value
|
||||||
|
? "#ffffff"
|
||||||
|
: theme.palette.grey3.main,
|
||||||
|
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||||
|
height: "30px",
|
||||||
|
"&:hover": {
|
||||||
|
color: theme.palette.grey3.main,
|
||||||
|
"& path": { stroke: theme.palette.grey3.main },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{isWrappMiniButtonSetting ? null : title}
|
||||||
|
</MiniButtonSetting>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "20px",
|
||||||
|
display: "flex",
|
||||||
|
gap: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
|
onClick={() => copyQuestion(question.id, question.quizId)}
|
||||||
|
>
|
||||||
|
<CopyIcon color={"#4D4D4D"} />
|
||||||
|
</IconButton>
|
||||||
|
{(quiz?.config.type !== "form" || question.id !== questions[0].id) && (
|
||||||
|
<IconButton
|
||||||
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (question.type === null) {
|
||||||
|
deleteQuestion(question.id);
|
||||||
|
}
|
||||||
|
if (question.content.rule.parentId.length !== 0) {
|
||||||
|
setOpenDelete(true);
|
||||||
|
} else {
|
||||||
|
deleteQuestionWithTimeout(question.id, () =>
|
||||||
|
DeleteFunction(questions, question, quiz),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
|
>
|
||||||
|
<DeleteIcon color={"#4D4D4D"} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
padding: "30px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||||
|
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||||
|
потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: "30px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => setOpenDelete(false)}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ minWidth: "150px" }}
|
||||||
|
onClick={() => {
|
||||||
|
deleteQuestionWithTimeout(question.id, () =>
|
||||||
|
DeleteFunction(questions, question, quiz),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подтвердить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
25
src/pages/Questions/ButtonsOptions/switchOptionsAndPict.tsx
Normal file
25
src/pages/Questions/ButtonsOptions/switchOptionsAndPict.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||||
|
import UploadImage from "../UploadImage";
|
||||||
|
import HelpQuestions from "../helpQuestions";
|
||||||
|
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
switchState: string;
|
||||||
|
question: QuizQuestionVarImg;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SwitchOptionsAndPict({
|
||||||
|
switchState = "setting",
|
||||||
|
question,
|
||||||
|
}: Props) {
|
||||||
|
switch (switchState) {
|
||||||
|
case "setting":
|
||||||
|
return <SettingOptionsAndPict question={question} />;
|
||||||
|
case "help":
|
||||||
|
return <HelpQuestions question={question} />;
|
||||||
|
case "image":
|
||||||
|
return <UploadImage question={question} />;
|
||||||
|
default:
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
}
|
@ -205,43 +205,46 @@ export default function ButtonsOptionsAndPict({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
> */}
|
> */}
|
||||||
<MiniButtonSetting
|
{question.type !== "text"
|
||||||
onMouseEnter={() => setButtonHover("branching")}
|
&&
|
||||||
onMouseLeave={() => setButtonHover("")}
|
<MiniButtonSetting
|
||||||
onClick={() => {
|
onMouseEnter={() => setButtonHover("branching")}
|
||||||
setOpenBranchingPage(true);
|
onMouseLeave={() => setButtonHover("")}
|
||||||
updateDesireToOpenABranchingModal(question.content.id);
|
onClick={() => {
|
||||||
}}
|
setOpenBranchingPage(true);
|
||||||
sx={{
|
updateDesireToOpenABranchingModal(question.content.id);
|
||||||
display: quiz.config.type === "form" ? "none" : "flex",
|
}}
|
||||||
height: "30px",
|
sx={{
|
||||||
maxWidth: "103px",
|
display: quiz.config.type === "form" ? "none" : "flex",
|
||||||
minWidth: isIconMobile ? "30px" : "64px",
|
height: "30px",
|
||||||
backgroundColor:
|
maxWidth: "103px",
|
||||||
switchState === "branching"
|
minWidth: isIconMobile ? "30px" : "64px",
|
||||||
? theme.palette.brightPurple.main
|
backgroundColor:
|
||||||
: "transparent",
|
switchState === "branching"
|
||||||
color:
|
? theme.palette.brightPurple.main
|
||||||
switchState === "branching"
|
: "transparent",
|
||||||
? "#ffffff"
|
|
||||||
: theme.palette.grey3.main,
|
|
||||||
"&:hover": {
|
|
||||||
color:
|
color:
|
||||||
switchState === "branching" ? theme.palette.grey3.main : null,
|
switchState === "branching"
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Branching
|
|
||||||
color={
|
|
||||||
buttonHover === "branching"
|
|
||||||
? theme.palette.grey3.main
|
|
||||||
: switchState === "branching"
|
|
||||||
? "#ffffff"
|
? "#ffffff"
|
||||||
: theme.palette.grey3.main
|
: theme.palette.grey3.main,
|
||||||
}
|
"&:hover": {
|
||||||
/>
|
color:
|
||||||
{isIconMobile ? null : "Ветвление"}
|
switchState === "branching" ? theme.palette.grey3.main : null,
|
||||||
</MiniButtonSetting>
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Branching
|
||||||
|
color={
|
||||||
|
buttonHover === "branching"
|
||||||
|
? theme.palette.grey3.main
|
||||||
|
: switchState === "branching"
|
||||||
|
? "#ffffff"
|
||||||
|
: theme.palette.grey3.main
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{isIconMobile ? null : "Ветвление"}
|
||||||
|
</MiniButtonSetting>
|
||||||
|
}
|
||||||
{/* </Tooltip> */}
|
{/* </Tooltip> */}
|
||||||
<MiniButtonSetting
|
<MiniButtonSetting
|
||||||
onMouseEnter={() => setButtonHover("image")}
|
onMouseEnter={() => setButtonHover("image")}
|
||||||
|
@ -51,7 +51,7 @@ export default function Emoji({
|
|||||||
<AnswerDraggableList
|
<AnswerDraggableList
|
||||||
question={question}
|
question={question}
|
||||||
additionalContent={(variant) => (
|
additionalContent={(variant) => (
|
||||||
<>
|
<>Оооооо, тестовая публикация получа
|
||||||
{!isTablet && (
|
{!isTablet && (
|
||||||
<Box sx={{ cursor: "pointer" }}>
|
<Box sx={{ cursor: "pointer" }}>
|
||||||
<Box
|
<Box
|
||||||
|
@ -24,6 +24,7 @@ export default function SettingOptionsAndPict({
|
|||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
||||||
|
|
||||||
|
console.log("question.content.replText ", question.content.replText)
|
||||||
const setReplText = useDebouncedCallback((replText) => {
|
const setReplText = useDebouncedCallback((replText) => {
|
||||||
updateQuestion(question.id, (question) => {
|
updateQuestion(question.id, (question) => {
|
||||||
if (question.type !== "varimg") return;
|
if (question.type !== "varimg") return;
|
||||||
@ -100,7 +101,7 @@ export default function SettingOptionsAndPict({
|
|||||||
}}
|
}}
|
||||||
maxLength={60}
|
maxLength={60}
|
||||||
placeholder={"Пример текста"}
|
placeholder={"Пример текста"}
|
||||||
text={question.content.replText}
|
value={question.content.replText}
|
||||||
onChange={({ target }) => setReplText(target.value)}
|
onChange={({ target }) => setReplText(target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -182,7 +183,7 @@ export default function SettingOptionsAndPict({
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
sx={{ maxWidth: "360px", width: "100%" }}
|
sx={{ maxWidth: "360px", width: "100%" }}
|
||||||
placeholder={"Пример текста"}
|
placeholder={"Пример текста"}
|
||||||
text={question.content.replText}
|
value={question.content.replText}
|
||||||
maxLength={60}
|
maxLength={60}
|
||||||
onChange={({ target }) => setReplText(target.value)}
|
onChange={({ target }) => setReplText(target.value)}
|
||||||
/>
|
/>
|
||||||
|
@ -14,6 +14,7 @@ import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
|||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import SwitchTextField from "./switchTextField";
|
import SwitchTextField from "./switchTextField";
|
||||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||||
|
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: QuizQuestionText;
|
question: QuizQuestionText;
|
||||||
@ -100,7 +101,7 @@ export default function OwnTextField({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions
|
<ButtonsOptionsAndPict
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
question={question}
|
question={question}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { QuizQuestionText } from "@model/questionTypes/text";
|
import { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingTextField from "./settingTextField";
|
import SettingTextField from "./settingTextField";
|
||||||
|
import UploadImage from "../UploadImage";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
@ -14,6 +15,8 @@ export default function SwitchTextField({
|
|||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingTextField question={question} />;
|
return <SettingTextField question={question} />;
|
||||||
|
case "image":
|
||||||
|
return <UploadImage question={question} />;
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions question={question} />;
|
return <HelpQuestions question={question} />;
|
||||||
default:
|
default:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { QuizView } from "@frontend/squzanswerer";
|
// import { QuizView } from "@frontend/squzanswerer";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ const validationSchema = object({
|
|||||||
password: string()
|
password: string()
|
||||||
.min(8, "Минимум 8 символов")
|
.min(8, "Минимум 8 символов")
|
||||||
.matches(
|
.matches(
|
||||||
/^[.,:;\-_+!&()<>\[\]{}№`@"~*|#$%^=?\d\w]+$/,
|
/^[.,:;\-_+!&()<>\[\]{}№`@"'~*|#$₽%^=?\d\w]+$/,
|
||||||
"Некорректные символы",
|
"Некорректные символы",
|
||||||
)
|
)
|
||||||
.required("Поле обязательно"),
|
.required("Поле обязательно"),
|
||||||
|
Loading…
Reference in New Issue
Block a user