Merge branch 'dev' of penahub.gitlab.yandexcloud.net:frontend/squiz into dev
This commit is contained in:
commit
1ec03d04d7
@ -1,2 +1 @@
|
||||
REACT_APP_DOMAIN="https://squiz.pena.digital"
|
||||
REACT_APP_HUB_DOMAIN="https://shub.pena.digital"
|
||||
@ -1,2 +1 @@
|
||||
REACT_APP_DOMAIN="https://squiz.pena.digital"
|
||||
REACT_APP_HUB_DOMAIN="https://shub.pena.digital"
|
||||
REACT_APP_DOMAIN="https://squiz.pena.digital"
|
||||
@ -6,7 +6,7 @@
|
||||
"@craco/craco": "^7.0.0",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.55",
|
||||
"@frontend/kitui": "^1.0.62",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-date-pickers": "^6.16.1",
|
||||
|
||||
34
src/App.tsx
34
src/App.tsx
@ -43,10 +43,12 @@ import {
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import PrivateRoute from "@ui_kit/PrivateRoute";
|
||||
|
||||
import { Restore } from "./pages/startPage/Restore";
|
||||
import { Restore } from "./pages/auth/Restore";
|
||||
|
||||
import { isAxiosError } from "axios";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import RecoverPassword from "./pages/auth/RecoverPassword";
|
||||
import OutdatedLink from "./pages/auth/OutdatedLink";
|
||||
export function useUserAccountFetcher({
|
||||
onError,
|
||||
onNewUserAccount,
|
||||
@ -183,7 +185,9 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/signin" element={<SigninDialog />} />
|
||||
<Route path="/signup" element={<SignupDialog />} />
|
||||
<Route path="/restore" element={<Restore />} />
|
||||
<Route path="//recover" element={<Restore />} />
|
||||
<Route path="/changepwd" element={<RecoverPassword />} />
|
||||
<Route path="/changepwd/expired" element={<OutdatedLink />} />
|
||||
</Routes>
|
||||
)}
|
||||
<Routes location={location.state?.backgroundLocation || location}>
|
||||
@ -201,9 +205,31 @@ export default function App() {
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/restore"
|
||||
path="/recover"
|
||||
element={
|
||||
<Navigate to="/" replace state={{ redirectTo: "/restore" }} />
|
||||
<Navigate to="/" replace state={{ redirectTo: "/recover" }} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/changepwd"
|
||||
element={
|
||||
<Navigate
|
||||
to="/"
|
||||
replace
|
||||
state={{
|
||||
redirectTo: window.location.pathname + window.location.search,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/changepwd/expired"
|
||||
element={
|
||||
<Navigate
|
||||
to="/"
|
||||
replace
|
||||
state={{ redirectTo: "/changepwd/expired" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="/list" element={<MyQuizzesFull />} />
|
||||
|
||||
@ -8,7 +8,7 @@ import type {
|
||||
} from "@frontend/kitui";
|
||||
import { parseAxiosError } from "../utils/parse-error";
|
||||
|
||||
const apiUrl =process.env.REACT_APP_DOMAIN + "/auth";
|
||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/auth";
|
||||
|
||||
export async function register(
|
||||
login: string,
|
||||
@ -70,3 +70,29 @@ export async function logout(): Promise<[unknown, string?]> {
|
||||
return [null, `Не удалось выйти. ${error}`];
|
||||
}
|
||||
}
|
||||
|
||||
export async function recover(
|
||||
email: string,
|
||||
): Promise<[unknown | null, string?]> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("email", email);
|
||||
formData.append(
|
||||
"RedirectionURL",
|
||||
process.env.REACT_APP_DOMAIN + "/changepwd",
|
||||
);
|
||||
const recoverResponse = await makeRequest<unknown, unknown>({
|
||||
url: process.env.REACT_APP_DOMAIN + "/codeword/recover",
|
||||
body: formData,
|
||||
useToken: false,
|
||||
withCredentials: true,
|
||||
});
|
||||
console.log(recoverResponse);
|
||||
return [recoverResponse];
|
||||
} catch (nativeError) {
|
||||
console.log(nativeError);
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось восстановить пароль. ${error}`];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
|
||||
const domen = process.env.REACT_APP_HUB_DOMAIN
|
||||
const domen = process.env.REACT_APP_DOMAIN;
|
||||
|
||||
export function sendContactFormRequest(body: {
|
||||
contact: string;
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
CopyQuestionResponse,
|
||||
} from "@model/question/copy";
|
||||
|
||||
const baseUrl = process.env.REACT_APP_DOMAIN ;
|
||||
const baseUrl = process.env.REACT_APP_DOMAIN;
|
||||
|
||||
function get(quizId: string) {
|
||||
return makeRequest<any>({
|
||||
|
||||
@ -50,7 +50,7 @@ export interface Quiz {
|
||||
question_cnt: number;
|
||||
/** count passings */
|
||||
passed_count: number;
|
||||
sessions_count: number;
|
||||
session_count: number;
|
||||
/** average time of passing */
|
||||
average_time: number;
|
||||
/** set true if squiz realize group functionality */
|
||||
@ -109,7 +109,7 @@ export interface RawQuiz {
|
||||
question_cnt: number;
|
||||
/** count passings */
|
||||
passed_count: number;
|
||||
sessions_count: number;
|
||||
session_count: number;
|
||||
/** average time of passing */
|
||||
average_time: number;
|
||||
/** set true if squiz realize group functionality */
|
||||
|
||||
@ -127,13 +127,13 @@ export default function Component() {
|
||||
>
|
||||
Пользовательское соглашение
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
variant="text"
|
||||
sx={{ fontSize: "16px", fontWeight: 500, color: "white" }}
|
||||
href={"https://hub.pena.digital/docs/privacy"}
|
||||
target="_blank"
|
||||
>
|
||||
Политика конфиденциальности
|
||||
Политика конфиденциальности
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -67,18 +67,18 @@ export default function SettingOptionsAndPict({
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (question.type !== "varimg") return;
|
||||
{/*<CustomCheckbox*/}
|
||||
{/* sx={{ mr: isMobile ? "0px" : "16px" }}*/}
|
||||
{/* label={'Вариант "свой ответ"'}*/}
|
||||
{/* checked={question.content.own}*/}
|
||||
{/* handleChange={({ target }) =>*/}
|
||||
{/* updateQuestion(question.id, (question) => {*/}
|
||||
{/* if (question.type !== "varimg") return;*/}
|
||||
|
||||
question.content.own = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
{/* question.content.own = target.checked;*/}
|
||||
{/* })*/}
|
||||
{/* }*/}
|
||||
{/*/>*/}
|
||||
{!isWrappColumn && (
|
||||
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
|
||||
@ -90,7 +90,6 @@ export const ResultSettings = () => {
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">Настройки результатов</Typography>
|
||||
<Info />
|
||||
<Button
|
||||
disableRipple
|
||||
sx={{
|
||||
|
||||
@ -65,7 +65,9 @@ function TariffPage() {
|
||||
for (let page = 2; page <= totalPages; page += 1) {
|
||||
const tariffsResult = await makeRequest<never, GetTariffsResponse>({
|
||||
method: "GET",
|
||||
url: process.env.REACT_APP_DOMAIN + `/strator/tariff?page=${page}&limit=100`,
|
||||
url:
|
||||
process.env.REACT_APP_DOMAIN +
|
||||
`/strator/tariff?page=${page}&limit=100`,
|
||||
});
|
||||
console.log(page);
|
||||
|
||||
@ -109,7 +111,7 @@ function TariffPage() {
|
||||
//Добавляем желаемый тариф в корзину
|
||||
await makeRequest({
|
||||
method: "PATCH",
|
||||
url: process.env.REACT_APP_HUB_DOMAIN + `/customer/cart?id=${id}`,
|
||||
url: process.env.REACT_APP_DOMAIN + `/customer/cart?id=${id}`,
|
||||
});
|
||||
//Если нам хватает денежек - покупаем тариф
|
||||
if (price <= user.wallet.cash) {
|
||||
@ -130,9 +132,11 @@ function TariffPage() {
|
||||
// history.pushState({}, null, "https://hub.pena.digital/wallet?action=squizpay");
|
||||
|
||||
var link = document.createElement("a");
|
||||
link.href = process.env.REACT_APP_HUB_DOMAIN + `/payment?action=squizpay&dif=${
|
||||
(price - Number(user.wallet.cash)) * 100
|
||||
}`;
|
||||
link.href =
|
||||
process.env.REACT_APP_DOMAIN +
|
||||
`/payment?action=squizpay&dif=${
|
||||
(price - Number(user.wallet.cash)) * 100
|
||||
}`;
|
||||
document.body.appendChild(link);
|
||||
// link.click();
|
||||
}
|
||||
@ -300,7 +304,7 @@ export const inCart = () => {
|
||||
try {
|
||||
await makeRequest({
|
||||
method: "PATCH",
|
||||
url: process.env.REACT_APP_HUB_DOMAIN + `/customer/cart?id=${id}`,
|
||||
url: process.env.REACT_APP_DOMAIN + `/customer/cart?id=${id}`,
|
||||
});
|
||||
|
||||
let index = saveCart.indexOf("green");
|
||||
|
||||
137
src/pages/auth/OutdatedLink.tsx
Normal file
137
src/pages/auth/OutdatedLink.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
Box,
|
||||
Dialog,
|
||||
IconButton,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import PenaLogo from "../Landing/images/icons/QuizLogo";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUserStore } from "../../stores/user";
|
||||
// import { cardShadow } from "@root/utils/theme";
|
||||
|
||||
export default function OutdatedLink() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||
const user = useUserStore((state) => state.user);
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(
|
||||
function redirectIfSignedIn() {
|
||||
if (user) navigate("/tariffs", { replace: true });
|
||||
},
|
||||
[navigate, user],
|
||||
);
|
||||
|
||||
function handleClose() {
|
||||
setIsDialogOpen(false);
|
||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
width: "600px",
|
||||
maxWidth: "600px",
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
backdrop: {
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{
|
||||
position: "relative",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
// boxShadow: cardShadow,
|
||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
||||
{
|
||||
position: "absolute",
|
||||
top: "46px",
|
||||
margin: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box>
|
||||
<PenaLogo width={upMd ? 233 : 196} color="black" />
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
mt: "5px",
|
||||
mb: upMd ? "10px" : "33px",
|
||||
}}
|
||||
>
|
||||
Внимание! Ссылка устарела!
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
mt: "5px",
|
||||
mb: upMd ? "10px" : "33px",
|
||||
}}
|
||||
>
|
||||
Срок действия ссылки истёк, пожалуйста повторите попытку
|
||||
восстановления пароля
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() => navigate("/")}
|
||||
sx={{
|
||||
py: "12px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#581CA7",
|
||||
},
|
||||
"&:active": {
|
||||
color: "white",
|
||||
backgroundColor: "black",
|
||||
},
|
||||
}}
|
||||
>
|
||||
На главную
|
||||
</Button>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
mt: "auto",
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
198
src/pages/auth/RecoverPassword.tsx
Normal file
198
src/pages/auth/RecoverPassword.tsx
Normal file
@ -0,0 +1,198 @@
|
||||
import {
|
||||
Box,
|
||||
Dialog,
|
||||
IconButton,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useFormik } from "formik";
|
||||
import InputTextfield from "@ui_kit/InputTextfield";
|
||||
import PenaLogo from "../Landing/images/icons/QuizLogo";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { object, string } from "yup";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUserStore } from "@root/user";
|
||||
|
||||
import { getAuthToken, makeRequest, setAuthToken } from "@frontend/kitui";
|
||||
import { FormContactFieldName } from "@model/quizSettings";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
interface Values {
|
||||
password: string;
|
||||
}
|
||||
|
||||
const initialValues: Values = {
|
||||
password: "",
|
||||
};
|
||||
|
||||
const validationSchema = object({
|
||||
password: string()
|
||||
.min(8, "Минимум 8 символов")
|
||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||
.required("Поле обязательно"),
|
||||
});
|
||||
|
||||
export default function RecoverPassword() {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||
const [tokenUser, setTokenUser] = useState<string>("");
|
||||
const user = useUserStore((state) => state.user);
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues,
|
||||
validationSchema,
|
||||
onSubmit: async (values, formikHelpers) => {
|
||||
if (tokenUser) {
|
||||
setAuthToken(tokenUser || "");
|
||||
try {
|
||||
const response = await makeRequest<unknown, unknown>({
|
||||
url: process.env.REACT_APP_DOMAIN + "/user/",
|
||||
method: "PATCH",
|
||||
body: { password: values.password },
|
||||
});
|
||||
setIsDialogOpen(false);
|
||||
navigate("/");
|
||||
enqueueSnackbar("Пароль успешно сменён");
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
setAuthToken("");
|
||||
enqueueSnackbar(
|
||||
`Извините, произошла ошибка, попробуйте повторить позже. ${error}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Неверный url-адрес");
|
||||
}
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const authToken = params.get("auth");
|
||||
setTokenUser(authToken);
|
||||
|
||||
history.pushState(null, document.title, "/changepwd");
|
||||
return () => {
|
||||
setAuthToken("");
|
||||
};
|
||||
}, []);
|
||||
console.log(tokenUser);
|
||||
|
||||
function handleClose() {
|
||||
setIsDialogOpen(false);
|
||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
width: "600px",
|
||||
maxWidth: "600px",
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
backdrop: {
|
||||
style: {
|
||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={formik.handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
position: "relative",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
// boxShadow: cardShadow,
|
||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
||||
{
|
||||
position: "absolute",
|
||||
top: "46px",
|
||||
margin: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box>
|
||||
<PenaLogo width={upMd ? 233 : 196} color="black" />
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
mt: "5px",
|
||||
mb: upMd ? "10px" : "33px",
|
||||
}}
|
||||
>
|
||||
Введите новый пароль
|
||||
</Typography>
|
||||
<InputTextfield
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "введите пароль",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
color="#F2F3F7"
|
||||
id="password"
|
||||
label="Новый пароль"
|
||||
gap={upMd ? "10px" : "10px"}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
type="submit"
|
||||
disabled={formik.isSubmitting}
|
||||
sx={{
|
||||
py: "12px",
|
||||
"&:hover": {
|
||||
backgroundColor: "#581CA7",
|
||||
},
|
||||
"&:active": {
|
||||
color: "white",
|
||||
backgroundColor: "black",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Восстановить
|
||||
</Button>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
mt: "auto",
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -11,35 +11,25 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import InputTextfield from "@ui_kit/InputTextfield";
|
||||
import PasswordInput from "@ui_kit/passwordInput";
|
||||
import { useFormik } from "formik";
|
||||
import { object, ref, string } from "yup";
|
||||
import Logotip from "../Landing/images/icons/QuizLogo";
|
||||
import { useNavigate, Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { recover } from "@api/auth";
|
||||
|
||||
interface Values {
|
||||
email: string;
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
}
|
||||
|
||||
const initialValues: Values = {
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
};
|
||||
|
||||
const validationSchema = object({
|
||||
email: string()
|
||||
.required("Поле обязательно")
|
||||
.email("Введите корректный email"),
|
||||
password: string()
|
||||
.min(8, "Минимум 8 символов")
|
||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||
.required("Поле обязательно"),
|
||||
repeatPassword: string()
|
||||
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
||||
.required("Повторите пароль"),
|
||||
});
|
||||
|
||||
export const Restore: FC = () => {
|
||||
@ -52,7 +42,19 @@ export const Restore: FC = () => {
|
||||
const formik = useFormik<Values>({
|
||||
initialValues,
|
||||
validationSchema,
|
||||
onSubmit: (values) => {},
|
||||
onSubmit: async (values, formikHelpers) => {
|
||||
const [recoverResponse, recoverError] = await recover(
|
||||
values.email.trim(),
|
||||
);
|
||||
|
||||
formikHelpers.setSubmitting(false);
|
||||
if (recoverError) {
|
||||
enqueueSnackbar(recoverError);
|
||||
return;
|
||||
}
|
||||
navigate("/");
|
||||
enqueueSnackbar("Письмо прийдёт Вам на почту");
|
||||
},
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
@ -138,41 +140,6 @@ export const Restore: FC = () => {
|
||||
label="Email"
|
||||
gap={upMd ? "10px" : "10px"}
|
||||
/>
|
||||
<PasswordInput
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error: formik.touched.password && Boolean(formik.errors.password),
|
||||
helperText: formik.touched.password && formik.errors.password,
|
||||
autoComplete: "new-password",
|
||||
"data-cy": "password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
color="#F2F3F7"
|
||||
id="password"
|
||||
label="Пароль"
|
||||
gap={upMd ? "10px" : "10px"}
|
||||
/>
|
||||
<PasswordInput
|
||||
TextfieldProps={{
|
||||
value: formik.values.repeatPassword,
|
||||
placeholder: "Не менее 8 символов",
|
||||
onBlur: formik.handleBlur,
|
||||
error:
|
||||
formik.touched.repeatPassword &&
|
||||
Boolean(formik.errors.repeatPassword),
|
||||
helperText:
|
||||
formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||
autoComplete: "new-password",
|
||||
"data-cy": "repeat-password",
|
||||
}}
|
||||
onChange={formik.handleChange}
|
||||
color="#F2F3F7"
|
||||
id="repeatPassword"
|
||||
label="Повторить пароль"
|
||||
gap={upMd ? "10px" : "10px"}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
@ -235,7 +235,7 @@ export default function SigninDialog() {
|
||||
</Link>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/restore"
|
||||
to="/recover"
|
||||
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||
sx={{
|
||||
color: "#7E2AEA",
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { createQuiz } from "@root/quizes/actions";
|
||||
import { createQuiz, updateQuiz } from "@root/quizes/actions";
|
||||
import { useQuizes } from "@root/quizes/hooks";
|
||||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||||
import React from "react";
|
||||
@ -16,6 +16,7 @@ import { resetEditConfig } from "@root/quizes/actions";
|
||||
import FirstQuiz from "./FirstQuiz";
|
||||
import QuizCard from "./QuizCard";
|
||||
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||
import QuizgenegationName from "@utils/quizgenegationName";
|
||||
|
||||
interface Props {
|
||||
outerContainerSx?: SxProps<Theme>;
|
||||
@ -76,16 +77,22 @@ export default function MyQuizzesFull({
|
||||
}}
|
||||
>
|
||||
{quizes.map((quiz) => {
|
||||
if (quiz.name.length === 0 || quiz.name === " ") {
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.name = QuizgenegationName({ quiz });
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<QuizCard
|
||||
key={quiz.id}
|
||||
quiz={quiz}
|
||||
openCount={quiz.sessions_count}
|
||||
openCount={quiz.session_count}
|
||||
applicationCount={quiz.passed_count}
|
||||
conversionPercent={
|
||||
quiz.sessions_count
|
||||
quiz.session_count
|
||||
? (
|
||||
(quiz.passed_count / quiz.sessions_count) *
|
||||
(quiz.passed_count / quiz.session_count) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: 0
|
||||
|
||||
@ -97,11 +97,7 @@ export default function QuizCard({
|
||||
}}
|
||||
variant="h5"
|
||||
>
|
||||
{quiz.name.length === 0 || quiz.name === " "
|
||||
? quiz.config.type === "form"
|
||||
? "Form " + quiz.backendId.toString().slice(-4).replace(/^0/, "1")
|
||||
: "Quiz " + quiz.backendId.toString().slice(-4).replace(/^0/, "1")
|
||||
: quiz.name}
|
||||
{quiz.name}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@ -6,11 +6,8 @@ import ArrowLeft from "@icons/ArrowLeftSP";
|
||||
import LayoutCenteredIcon from "@icons/LayoutCenteredIcon";
|
||||
import LayoutExpandedIcon from "@icons/LayoutExpandedIcon";
|
||||
import LayoutStandartIcon from "@icons/LayoutStandartIcon";
|
||||
|
||||
import MobilePhoneIcon from "@icons/MobilePhoneIcon";
|
||||
import { QuizStartpageType } from "@model/quizSettings";
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import UploadIcon from "../../assets/icons/UploadIcon";
|
||||
|
||||
@ -740,7 +737,7 @@ export default function StartPageSettings() {
|
||||
quiz.config.info.site = e.target.value;
|
||||
})
|
||||
}
|
||||
maxLength={25}
|
||||
maxLength={100}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
@ -761,7 +758,7 @@ export default function StartPageSettings() {
|
||||
quiz.config.info.law = e.target.value;
|
||||
})
|
||||
}
|
||||
maxLength={45}
|
||||
maxLength={1000}
|
||||
/>
|
||||
<Extra />
|
||||
</>
|
||||
|
||||
@ -2,12 +2,13 @@ import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CreationCard from "@ui_kit/CreationCard";
|
||||
import quizCreationImage1 from "../../assets/quiz-creation-1.png";
|
||||
import quizCreationImage2 from "../../assets/quiz-creation-2.png";
|
||||
import { setQuizType } from "@root/quizes/actions";
|
||||
import { setQuizType, updateQuiz } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import arrowLeftIcon from "../../assets/icons/arrow_left.svg";
|
||||
import arrowRightIcon from "../../assets/icons/arrow_right.svg";
|
||||
import QuizgenegationName from "@utils/quizgenegationName";
|
||||
|
||||
export default function StepOne() {
|
||||
const quiz = useCurrentQuiz();
|
||||
@ -69,6 +70,9 @@ export default function StepOne() {
|
||||
data-cy="create-quiz-card"
|
||||
onClick={() => {
|
||||
setQuizType(quiz.id, "quiz");
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.name = QuizgenegationName({ quiz });
|
||||
});
|
||||
}}
|
||||
>
|
||||
<CreationCard
|
||||
@ -84,6 +88,9 @@ export default function StepOne() {
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
setQuizType(quiz.id, "form");
|
||||
updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.name = QuizgenegationName({ quiz });
|
||||
});
|
||||
}}
|
||||
>
|
||||
<CreationCard
|
||||
|
||||
@ -413,7 +413,8 @@ export const uploadQuestionImage = async (
|
||||
}
|
||||
|
||||
const imageId = values[0];
|
||||
const imageUrl = process.env.REACT_APP_DOMAIN + `/squizimages/${quizQid}/${imageId}`;
|
||||
const imageUrl =
|
||||
process.env.REACT_APP_DOMAIN + `/squizimages/${quizQid}/${imageId}`;
|
||||
|
||||
updateQuestion(questionId, (question) => {
|
||||
updateFn(question, imageUrl);
|
||||
|
||||
@ -129,7 +129,7 @@ export const quizStore = create<QuizStore>()(
|
||||
config: {
|
||||
noStartPage: false,
|
||||
type: "", // quiz или form
|
||||
logo: process.env.REACT_APP_HUB_DOMAIN + "/img/logo",
|
||||
logo: process.env.REACT_APP_DOMAIN + "/img/logo",
|
||||
startpage: {
|
||||
description: "", // приветственный текст опроса
|
||||
button: "", // текст на кнопке начала опроса
|
||||
|
||||
@ -269,7 +269,7 @@ export const uploadQuizImage = async (
|
||||
updateQuiz(quizId, (quiz) => {
|
||||
updateFn(
|
||||
quiz,
|
||||
process.env.REACT_APP_DOMAIN + `/squizimages/${quiz.qid}/${imageId}`,
|
||||
process.env.REACT_APP_DOMAIN + `/squizimages/${quiz.qid}/${imageId}`,
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -16,20 +16,27 @@ const translateMessage: Record<string, string> = {
|
||||
"user with this email or login is exist": "Пользователь уже существует",
|
||||
"user with this login is exist":
|
||||
"Пользователь с таким логином уже существует",
|
||||
unauthorized: "Ссылка просрочена",
|
||||
};
|
||||
|
||||
export const parseAxiosError = (nativeError: unknown): [string, number?] => {
|
||||
console.log(nativeError);
|
||||
const error = nativeError as AxiosError;
|
||||
|
||||
if (
|
||||
error.response?.data &&
|
||||
"statusCode" in (error.response.data as ServerError)
|
||||
) {
|
||||
console.log(error);
|
||||
console.log(error.response?.data);
|
||||
if (error.response?.data) {
|
||||
const serverError = error.response.data as ServerError;
|
||||
const translatedMessage = translateMessage[serverError.message];
|
||||
if (translatedMessage !== undefined)
|
||||
serverError.message = translatedMessage;
|
||||
return [serverError.message, serverError.statusCode];
|
||||
let SEMessage;
|
||||
if ("statusCode" in (error.response?.data as ServerError)) {
|
||||
SEMessage = serverError?.message.toLowerCase() || "";
|
||||
}
|
||||
if ("error" in (error.response?.data as ServerError)) {
|
||||
SEMessage = serverError?.error.toLowerCase() || "";
|
||||
}
|
||||
console.log(serverError);
|
||||
const translatedMessage = translateMessage[SEMessage || ""];
|
||||
if (translatedMessage !== undefined) SEMessage = translatedMessage;
|
||||
return [SEMessage || "", serverError.statusCode];
|
||||
}
|
||||
|
||||
switch (error.status) {
|
||||
|
||||
16
src/utils/quizgenegationName.ts
Normal file
16
src/utils/quizgenegationName.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Quiz } from "@model/quiz/quiz";
|
||||
interface Props {
|
||||
quiz: Quiz;
|
||||
}
|
||||
export default function QuizgenegationName({ quiz }: Props) {
|
||||
let QuizgenegationName = "";
|
||||
quiz.config.type === "form"
|
||||
? (QuizgenegationName =
|
||||
"Form " + quiz.backendId.toString().slice(-4).replace(/^0/, "1"))
|
||||
: quiz.config.type === "quiz"
|
||||
? (QuizgenegationName =
|
||||
"Quiz " + quiz.backendId.toString().slice(-4).replace(/^0/, "1"))
|
||||
: (QuizgenegationName =
|
||||
"No type " + quiz.backendId.toString().slice(-4).replace(/^0/, "1"));
|
||||
return QuizgenegationName;
|
||||
}
|
||||
@ -1407,10 +1407,10 @@
|
||||
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz"
|
||||
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
|
||||
|
||||
"@frontend/kitui@^1.0.55":
|
||||
version "1.0.55"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.55.tgz#6e02f80b2c13828142ffeacaf9704e50e54324d0"
|
||||
integrity sha1-bgL4CywTgoFC/+rK+XBOUOVDJNA=
|
||||
"@frontend/kitui@^1.0.62":
|
||||
version "1.0.62"
|
||||
resolved "https://penahub.gitlab.yandexcloud.net/api/v4/projects/21/packages/npm/@frontend/kitui/-/@frontend/kitui-1.0.62.tgz#212185a0a19a9b9948a85e9d4c71e002ad7f8b27"
|
||||
integrity sha1-ISGFoKGam5lIqF6dTHHgAq1/iyc=
|
||||
dependencies:
|
||||
immer "^10.0.2"
|
||||
reconnecting-eventsource "^1.6.2"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user