added api, validation and protected routing
This commit is contained in:
parent
7b1dab7002
commit
6817f5bf9b
@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect, useLayoutEffect } from "react";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import { FormsData, UserData, TypeAnswerData } from "./context";
|
||||
import { globalRender } from "./router/protectedRouting.js";
|
||||
import classes from "./assets/styles/app.module.scss"
|
||||
import NavBar from "./components/NavBar.jsx";
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
@ -28,6 +29,8 @@ const App = () => {
|
||||
{id: 8, text: 'Дата', typeTag: InputDate}
|
||||
]);
|
||||
|
||||
// useEffect(() => globalRender(window.location.pathname, user, navigate));
|
||||
|
||||
return (
|
||||
<UserData.Provider value={{ user, setUser }}>
|
||||
<FormsData.Provider value={{ forms, setForms }}>
|
||||
|
@ -26,8 +26,9 @@
|
||||
width: 100%;
|
||||
&__columns {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
padding: 0 2%;
|
||||
height: 15%;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid rgb(220, 220, 220);
|
||||
@ -48,8 +49,9 @@
|
||||
}
|
||||
&__item {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
padding: 0 2%;
|
||||
height: 25%;
|
||||
width: 100%;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
@ -58,7 +60,7 @@
|
||||
background-color: rgba(240, 240, 240, 0.8);
|
||||
}
|
||||
&__title {
|
||||
width: 33.3%;
|
||||
// width: 33.3%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
@ -80,6 +82,11 @@
|
||||
top: calc(50% - 7px);
|
||||
cursor: pointer;
|
||||
}
|
||||
ul {
|
||||
li {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,13 @@ import classes from "../assets/styles/components/myInput.module.scss"
|
||||
const MyInput = (props) => {
|
||||
return (
|
||||
<div className={classes.main} style={{...props.otherMainStyle}}>
|
||||
<input type={props.type} placeholder={props.placeholder} style={{...props.otherInputStyle}} onChange={(e) => props.change(e.target.value)} value={props.value}/>
|
||||
<input
|
||||
type={props.type}
|
||||
placeholder={props.placeholder}
|
||||
style={{...props.otherInputStyle}}
|
||||
onChange={(e) => props.change(e.target.value)}
|
||||
value={props.value}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ const NavBar = ({navigate, auth, setAuth}) => {
|
||||
<div className={classes.main}>
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.menu}>
|
||||
{auth ?
|
||||
<div className={classes.menu__authorized}>
|
||||
<span onClick={() => navigate("/")}>Главная</span>
|
||||
<span onClick={() => navigate("/forms")}>Мои формы</span>
|
||||
</div> :
|
||||
<div></div>}
|
||||
{auth ?
|
||||
<span onClick={() => navigate("/forms")}>Мои формы</span> :
|
||||
<span></span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.profile}>
|
||||
{auth ?
|
||||
|
43
src/hooks/api/enterAccountApi.js
Normal file
43
src/hooks/api/enterAccountApi.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { totalRegisterValidate } from "../validation/enterAccountValidate.js";
|
||||
|
||||
async function logIn(email, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
resolve({
|
||||
email: "senya.bogachev@mail.ru",
|
||||
phone: "89110128244",
|
||||
name: "Арсений",
|
||||
surname: "Богачев",
|
||||
patronymic: "Валерьевич"
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject("Error")
|
||||
}
|
||||
}, 1000)
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
};
|
||||
|
||||
async function completeRegistration(data) {
|
||||
const validate = totalRegisterValidate(data)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (validate.status) {
|
||||
console.log("Отправляем данные на бэк ->", data)
|
||||
resolve({
|
||||
status: 201,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject(validate.message)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
};
|
||||
|
||||
export { logIn, completeRegistration };
|
65
src/hooks/api/formApi.js
Normal file
65
src/hooks/api/formApi.js
Normal file
@ -0,0 +1,65 @@
|
||||
function saveAnswersApi(id, answers) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
resolve({
|
||||
id: id,
|
||||
answers: answers
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject("Error")
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
function removeFormApi(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
resolve({
|
||||
id: id
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject("Error")
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
|
||||
function updateFormByFormsApi(id, name, questions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
resolve({
|
||||
id: id,
|
||||
name: name,
|
||||
questions: questions
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject("Error")
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
function saveFormApi(name, questions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
resolve({
|
||||
name: name,
|
||||
questions: questions
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject("Error")
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
export { saveAnswersApi, saveFormApi, updateFormByFormsApi, removeFormApi }
|
31
src/hooks/validation/enterAccountValidate.js
Normal file
31
src/hooks/validation/enterAccountValidate.js
Normal file
@ -0,0 +1,31 @@
|
||||
const constructorAnswerValidate = (state, messageReject = "Ошибка", messageResolve = undefined) => {
|
||||
return state ?
|
||||
{
|
||||
status: true,
|
||||
message: messageResolve
|
||||
} :
|
||||
{
|
||||
status: false,
|
||||
message: messageReject
|
||||
}
|
||||
}
|
||||
|
||||
const totalRegisterValidate = (data) => {
|
||||
const listValidation = [
|
||||
constructorAnswerValidate(data.name.length, "Обязательное поле."),
|
||||
constructorAnswerValidate(data.surname.length, "Обязательное поле."),
|
||||
constructorAnswerValidate(data.email.length, "Обязательное поле."),
|
||||
constructorAnswerValidate(data.phone.length, "Обязательное поле."),
|
||||
constructorAnswerValidate(data.password === data.repiedPassword, "Введенные пароли не совпадают."),
|
||||
constructorAnswerValidate(data.password < 8, "Пароль должен иметь более 8 символов.")
|
||||
]
|
||||
|
||||
for (let value of listValidation) {
|
||||
if (!value.status) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return { status: true }
|
||||
}
|
||||
|
||||
export { totalRegisterValidate, constructorAnswerValidate }
|
@ -4,6 +4,7 @@ import classes from "../assets/styles/enterAccount.module.scss";
|
||||
import MyInput from "../components/MyInput.jsx";
|
||||
import MyButton from "../components/MyButton.jsx";
|
||||
import { UserData } from "../context";
|
||||
import { logIn, completeRegistration } from "../hooks/api/enterAccountApi.js";
|
||||
|
||||
const EnterAccount = () => {
|
||||
const [enter, setEnter] = useState("login");
|
||||
@ -20,31 +21,38 @@ const EnterAccount = () => {
|
||||
const {user, setUser} = useContext(UserData);
|
||||
|
||||
function cleanState() {
|
||||
setEmail();
|
||||
setPhone();
|
||||
setName();
|
||||
setSurname();
|
||||
setPatronymic();
|
||||
setPassword();
|
||||
setRepiedPassword();
|
||||
}
|
||||
setEmail("");
|
||||
setPhone("");
|
||||
setName("");
|
||||
setSurname("");
|
||||
setPatronymic("");
|
||||
setPassword("");
|
||||
setRepiedPassword("");
|
||||
};
|
||||
|
||||
function selectTag(tag) {
|
||||
setEnter(tag);
|
||||
cleanState();
|
||||
};
|
||||
|
||||
function createUser() {
|
||||
if (password === repiedPassword) {
|
||||
setUser({
|
||||
completeRegistration({
|
||||
email: email,
|
||||
phone: phone,
|
||||
name: name,
|
||||
surname: surname,
|
||||
patronymic: patronymic,
|
||||
password: password
|
||||
})
|
||||
password: password,
|
||||
repiedPassword: repiedPassword
|
||||
}).then((resolve, reject) => {
|
||||
if (resolve.status === 201) {
|
||||
setUser(resolve.data)
|
||||
cleanState();
|
||||
navigate("/");
|
||||
}
|
||||
else {
|
||||
console.log('Error')
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
@ -52,10 +60,10 @@ const EnterAccount = () => {
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.tabs}>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item" onClick={() => setEnter("login")}>
|
||||
<li class="nav-item" onClick={() => selectTag("login")}>
|
||||
<a className={enter === "login" ? "nav-link active" : "nav-link"} aria-current="page">Авторизация</a>
|
||||
</li>
|
||||
<li class="nav-item" onClick={() => setEnter("register")}>
|
||||
<li class="nav-item" onClick={() => selectTag("register")}>
|
||||
<a className={enter === "register" ? "nav-link active" : "nav-link"}>Регитрация</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -67,11 +75,15 @@ const EnterAccount = () => {
|
||||
<h3>Войти в аккаунт</h3>
|
||||
</div>
|
||||
<div className={classes.content__wrapper__login__body}>
|
||||
<MyInput placeholder={"Email"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}}/>
|
||||
<MyInput type={"password"} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}}/>
|
||||
<MyInput placeholder={"Email"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}} value={email} change={setEmail}/>
|
||||
<MyInput type={"password"} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}} value={password} change={setPassword}/>
|
||||
</div>
|
||||
<div className={classes.content__wrapper__login__footer}>
|
||||
<MyButton text={"Войти"} otherStyle={{height: "50%", width: "20%"}}/>
|
||||
<MyButton
|
||||
text={"Войти"}
|
||||
otherStyle={{height: "50%", width: "20%"}}
|
||||
click={() => logIn(email, password).then((resolve, reject) => setUser(resolve))}
|
||||
/>
|
||||
</div>
|
||||
</div> :
|
||||
<div className={classes.content__wrapper__register}>
|
||||
|
@ -1,22 +1,23 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import classes from "../assets/styles/forms.module.scss"
|
||||
import MyButton from "../components/MyButton.jsx";
|
||||
import MyInput from "../components/MyInput.jsx";
|
||||
import { FormsData } from "../context";
|
||||
import { FormsData, UserData } from "../context";
|
||||
import { removeFormApi } from "../hooks/api/formApi.js";
|
||||
|
||||
const Forms = () => {
|
||||
const navigate = useNavigate()
|
||||
const navigate = useNavigate();
|
||||
const {forms, setForms} = useContext(FormsData);
|
||||
const {user, setUser} = useContext(UserData);
|
||||
const [stateLoading, setStateLoading] = useState(false);
|
||||
|
||||
const response = ms => {
|
||||
return new Promise(r => setTimeout(() => r('response end'), ms))
|
||||
}
|
||||
};
|
||||
|
||||
function createForm() {
|
||||
setStateLoading(true);
|
||||
// Эмуляция пост запроса
|
||||
response(1000)
|
||||
.then((r) => {
|
||||
console.log(r);
|
||||
@ -24,16 +25,27 @@ const Forms = () => {
|
||||
navigate("/forms/edit");
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
function editForm(item) {
|
||||
navigate("/forms/edit", {
|
||||
state: {
|
||||
id: item.id,
|
||||
data: item.listAnswer
|
||||
data: item.questions
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function removeForm(id) {
|
||||
removeFormApi(id)
|
||||
.then((resolve, _) => {
|
||||
console.log(resolve);
|
||||
setForms([...forms.filter(item => {
|
||||
item.id !== id
|
||||
})]);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.main}>
|
||||
@ -49,19 +61,20 @@ const Forms = () => {
|
||||
<div className={classes.listForms}>
|
||||
<div className={classes.listForms__columns}>
|
||||
<div className={classes.listForms__columns__item}>Название</div>
|
||||
<div className={classes.listForms__columns__item}>Ответы</div>
|
||||
<div className={classes.listForms__columns__item}>Изменения</div>
|
||||
{/* <div className={classes.listForms__columns__item}>Ответы</div>
|
||||
<div className={classes.listForms__columns__item}>Изменения</div> */}
|
||||
</div>
|
||||
<div className={classes.listForms__forms}>
|
||||
{forms.map((item, i) =>
|
||||
<div className={classes.listForms__forms__item} key={i}>
|
||||
<div className={classes.listForms__forms__item__title} onClick={() => editForm(item)}>{item.title}</div>
|
||||
<div className={classes.listForms__forms__item__answers}>{item.datetime}</div>
|
||||
<div className={classes.listForms__forms__item__update}>{item.update}</div>
|
||||
{/* <div className={classes.listForms__forms__item__answers}>{item.datetime}</div>
|
||||
<div className={classes.listForms__forms__item__update}>{item.update}</div> */}
|
||||
<i class="fa-solid fa-ellipsis-vertical" id="action" data-bs-toggle="dropdown"></i>
|
||||
<ul class="dropdown-menu" aria-labelledby="action">
|
||||
<li><a class="dropdown-item" onClick={() => navigate(`/forms/${item.id}/`)}>Открыть</a></li>
|
||||
<li><a class="dropdown-item" onClick={() => navigator.clipboard.writeText(`http://localhost:3000/forms/${item.id}/`)}>Скопировать ссылку</a></li>
|
||||
<li><a class="dropdown-item" onClick={() => removeForm(item.id)}>Удалить</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
@ -5,6 +5,7 @@ import MyButton from "../components/MyButton.jsx";
|
||||
import AnswerModal from "../components/AnswerModal.jsx";
|
||||
import PreviewModal from "../components/PreviewModal.jsx"
|
||||
import { FormsData, TypeAnswerData } from "../context";
|
||||
import { saveFormApi, updateFormByFormsApi } from "../hooks/api/formApi.js";
|
||||
|
||||
const NewForm = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -29,11 +30,11 @@ const NewForm = () => {
|
||||
|
||||
const [newForm, setNewForm] = useState(location.state ? location.state.data : []);
|
||||
|
||||
const [stateModal, setStateModal] = useState(false)
|
||||
const [stateModal, setStateModal] = useState(false);
|
||||
|
||||
function removeAnswerByForm(id) {
|
||||
setNewForm([...newForm.filter(item => item.id !== id)]);
|
||||
}
|
||||
};
|
||||
|
||||
function cleanStates() {
|
||||
setStateModal(false)
|
||||
@ -45,7 +46,7 @@ const NewForm = () => {
|
||||
setCurrentTypeAnswer("");
|
||||
setCurrentOptionAnswer("")
|
||||
setMandatory(false);
|
||||
}
|
||||
};
|
||||
|
||||
function addOptionAnswer(text) {
|
||||
setOptionAnswer([...optionAnswer, {
|
||||
@ -53,7 +54,7 @@ const NewForm = () => {
|
||||
text: currentOptionAnswer
|
||||
}]);
|
||||
setCurrentOptionAnswer("");
|
||||
}
|
||||
};
|
||||
|
||||
function editAnswerByForm(id) {
|
||||
const obj = newForm.find(item => item.id === id);
|
||||
@ -65,7 +66,7 @@ const NewForm = () => {
|
||||
setOptionAnswer(obj.optionAnswer);
|
||||
setMandatory(obj.mandatory);
|
||||
setStateModal(id);
|
||||
}
|
||||
};
|
||||
|
||||
function updateAnswerByForm() {
|
||||
setNewForm(newForm.map(item => {
|
||||
@ -81,7 +82,7 @@ const NewForm = () => {
|
||||
return item
|
||||
}))
|
||||
cleanStates()
|
||||
}
|
||||
};
|
||||
|
||||
function saveStates() {
|
||||
setNewForm([...newForm, {
|
||||
@ -95,38 +96,43 @@ const NewForm = () => {
|
||||
file: file
|
||||
}]);
|
||||
cleanStates();
|
||||
}
|
||||
};
|
||||
|
||||
function updateFormByForms() {
|
||||
updateFormByFormsApi(location.state.id, "Новая форма", newForm)
|
||||
.then((resolve, _) => {
|
||||
console.log(resolve);
|
||||
setForms(
|
||||
forms.map(item => {
|
||||
if (item.id === location.state.id) {
|
||||
item.title = 'Новая форма',
|
||||
item.datetime = 'Без изменений',
|
||||
item.update = '01/01/24',
|
||||
item.listAnswer = newForm
|
||||
item.title = "Новая форма",
|
||||
item.questions = newForm
|
||||
}
|
||||
return item
|
||||
})
|
||||
)
|
||||
);
|
||||
cleanStates();
|
||||
navigate("/forms");
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function saveForm() {
|
||||
saveFormApi("Новая форма", newForm)
|
||||
.then((resolve, reject) => {
|
||||
console.log(resolve);
|
||||
setForms(
|
||||
[...forms, {
|
||||
id: nextID(forms),
|
||||
title: 'Новая форма',
|
||||
datetime: 'Без изменений',
|
||||
update: '01/01/24',
|
||||
listAnswer: newForm,
|
||||
title: "Новая форма",
|
||||
questions: newForm,
|
||||
answers: []
|
||||
}]
|
||||
);
|
||||
cleanStates();
|
||||
navigate("/forms");
|
||||
}
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.main}>
|
||||
|
@ -4,6 +4,7 @@ import classes from "../assets/styles/viewForm.module.scss";
|
||||
import { FormsData, TypeAnswerData } from "../context";
|
||||
import GeneratingFormFields from "../components/GeneratingFormFields.jsx";
|
||||
import MyButton from "../components/MyButton.jsx";
|
||||
import { saveAnswersApi } from "../hooks/api/formApi.js";
|
||||
|
||||
const ViewForm = () => {
|
||||
const navigate = useNavigate();
|
||||
@ -16,9 +17,9 @@ const ViewForm = () => {
|
||||
};
|
||||
|
||||
const [answers, setAnswers] = useState(
|
||||
newForm() ? newForm().listAnswer.map(item => (
|
||||
newForm() ? newForm().questions.map(item => (
|
||||
{id: item.id, answer: []}
|
||||
)) : ""
|
||||
)) : []
|
||||
);
|
||||
|
||||
function updateAnswersForm(value, id) {
|
||||
@ -32,6 +33,16 @@ const ViewForm = () => {
|
||||
)
|
||||
};
|
||||
|
||||
function saveAnswers() {
|
||||
saveAnswersApi(formId, answers)
|
||||
.then((resolve, _) => {
|
||||
console.log(resolve)
|
||||
setAnswers([]);
|
||||
navigate("/");
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.main}>
|
||||
{newForm() ?
|
||||
@ -46,12 +57,15 @@ const ViewForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.form__content}>
|
||||
<GeneratingFormFields newForm={newForm().listAnswer} listTypeAnswer={listTypeAnswer} answers={answers} updateAnswersForm={updateAnswersForm}/>
|
||||
<GeneratingFormFields newForm={newForm().questions} listTypeAnswer={listTypeAnswer} answers={answers} updateAnswersForm={updateAnswersForm}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.footer}>
|
||||
<MyButton text={"Отправить"}/>
|
||||
<MyButton text={"Отмена"} backgroundColor={"rgb(180, 180, 180)"} click={() => {}}/>
|
||||
<MyButton text={"Отправить"} click={saveAnswers}/>
|
||||
<MyButton text={"Отмена"} backgroundColor={"rgb(180, 180, 180)"} click={() => {
|
||||
setAnswers([]);
|
||||
navigate("/");
|
||||
}}/>
|
||||
</div>
|
||||
</div> :
|
||||
<div className={classes.wrapper}>
|
||||
|
18
src/router/protectedRouting.js
Normal file
18
src/router/protectedRouting.js
Normal file
@ -0,0 +1,18 @@
|
||||
const protectedUrl = {
|
||||
notAuthorized: [
|
||||
"/forms",
|
||||
"/forms/edit",
|
||||
"/profile"
|
||||
],
|
||||
notRights: [
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
function globalRender(url, user, navigate) {
|
||||
if (!user && protectedUrl.notAuthorized.some(item => item === url)) {
|
||||
navigate("/enter")
|
||||
}
|
||||
}
|
||||
|
||||
export { globalRender }
|
@ -21,19 +21,19 @@ const router = createBrowserRouter([
|
||||
element: <Forms/>,
|
||||
},
|
||||
{
|
||||
path: '/forms/edit/',
|
||||
path: "/forms/edit",
|
||||
element: <NewForm/>
|
||||
},
|
||||
{
|
||||
path: '/enter',
|
||||
path: "/enter",
|
||||
element: <EnterAccount/>
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
path: "/profile",
|
||||
element: <Profile/>
|
||||
},
|
||||
{
|
||||
path: '/forms/:formId',
|
||||
path: "/forms/:formId",
|
||||
element: <ViewForm/>
|
||||
}
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user