mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor router and add unsaved changes prompt
This commit is contained in:
parent
0134af6b57
commit
0fb9ea932d
|
@ -1,4 +1,4 @@
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import Navigation from './components/Navigation/Navigation';
|
import Navigation from './components/Navigation/Navigation';
|
||||||
|
@ -15,7 +15,7 @@ import RestorePasswordPage from './pages/RestorePasswordPage';
|
||||||
import RSFormPage from './pages/RSFormPage';
|
import RSFormPage from './pages/RSFormPage';
|
||||||
import UserProfilePage from './pages/UserProfilePage';
|
import UserProfilePage from './pages/UserProfilePage';
|
||||||
|
|
||||||
function App () {
|
function Root() {
|
||||||
const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
||||||
return (
|
return (
|
||||||
<div className='antialiased clr-app'>
|
<div className='antialiased clr-app'>
|
||||||
|
@ -26,24 +26,9 @@ function App () {
|
||||||
draggable={false}
|
draggable={false}
|
||||||
pauseOnFocusLoss={false}
|
pauseOnFocusLoss={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='overflow-auto' style={{maxHeight: viewportHeight}}>
|
<div className='overflow-auto' style={{maxHeight: viewportHeight}}>
|
||||||
<main className='h-full' style={{minHeight: mainHeight}}>
|
<main className='h-full' style={{minHeight: mainHeight}}>
|
||||||
<Routes>
|
<Outlet />
|
||||||
<Route path='/' element={ <HomePage/>} />
|
|
||||||
|
|
||||||
<Route path='login' element={ <LoginPage/>} />
|
|
||||||
<Route path='signup' element={<RegisterPage/>} />
|
|
||||||
<Route path='restore-password' element={ <RestorePasswordPage/>} />
|
|
||||||
<Route path='profile' element={<UserProfilePage/>} />
|
|
||||||
|
|
||||||
<Route path='manuals' element={<ManualsPage/>} />
|
|
||||||
|
|
||||||
<Route path='library' element={<LibraryPage/>} />
|
|
||||||
<Route path='rsforms/:id' element={ <RSFormPage/>} />
|
|
||||||
<Route path='rsform-create' element={ <CreateRSFormPage/>} />
|
|
||||||
<Route path='*' element={ <NotFoundPage/>} />
|
|
||||||
</Routes>
|
|
||||||
</main>
|
</main>
|
||||||
{!noNavigation && !noFooter && <Footer />}
|
{!noNavigation && !noFooter && <Footer />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,4 +36,57 @@ function App () {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Root />,
|
||||||
|
errorElement: <NotFoundPage />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
element: <HomePage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
element: <LoginPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'signup',
|
||||||
|
element: <RegisterPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'restore-password',
|
||||||
|
element: <RestorePasswordPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
element: <UserProfilePage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'manuals',
|
||||||
|
element: <ManualsPage />,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'library',
|
||||||
|
element: <LibraryPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'rsforms/:id',
|
||||||
|
element: <RSFormPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'rsform-create',
|
||||||
|
element: <CreateRSFormPage />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function App () {
|
||||||
|
return (
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
14
rsconcept/frontend/src/hooks/useModificationPrompt.ts
Normal file
14
rsconcept/frontend/src/hooks/useModificationPrompt.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { unstable_usePrompt } from 'react-router-dom';
|
||||||
|
|
||||||
|
function usePromptUnsaved() {
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
unstable_usePrompt({
|
||||||
|
when: isModified,
|
||||||
|
message: 'Изменения не сохранены. Вы уверены что хотите совершить переход?'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {isModified, setIsModified};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default usePromptUnsaved;
|
|
@ -4,7 +4,6 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import ErrorFallback from './components/ErrorFallback.tsx';
|
import ErrorFallback from './components/ErrorFallback.tsx';
|
||||||
|
@ -27,7 +26,6 @@ const logError = (error: Error, info: { componentStack: string }) => {
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={ErrorFallback}
|
FallbackComponent={ErrorFallback}
|
||||||
onReset={resetState}
|
onReset={resetState}
|
||||||
|
@ -47,6 +45,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
</ThemeState>
|
</ThemeState>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</BrowserRouter>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,7 +21,7 @@ function HomePage() {
|
||||||
}, [navigate, user])
|
}, [navigate, user])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center w-full py-2'>
|
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
|
||||||
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
|
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import TextURL from '../components/Common/TextURL';
|
||||||
|
|
||||||
export function NotFoundPage() {
|
export function NotFoundPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='flex flex-col px-4 py-2'>
|
||||||
<h1 className='text-xl font-semibold'>Error 404 - Not Found</h1>
|
<h1 className='text-xl font-semibold'>Ошибка 404 - Страница не найдена</h1>
|
||||||
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базы данных</p>
|
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базе данных</p>
|
||||||
|
<TextURL href='/' text='Вернуться на Портал' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import TextArea from '../../components/Common/TextArea';
|
||||||
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
||||||
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||||
import { getCstTypificationLabel } from '../../utils/staticUI';
|
import { getCstTypificationLabel } from '../../utils/staticUI';
|
||||||
import EditorRSExpression from './EditorRSExpression';
|
import EditorRSExpression from './EditorRSExpression';
|
||||||
|
@ -33,7 +34,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
return schema?.items?.find((cst) => cst.id === activeID);
|
return schema?.items?.find((cst) => cst.id === activeID);
|
||||||
}, [schema?.items, activeID]);
|
}, [schema?.items, activeID]);
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(false);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
const [editMode, setEditMode] = useState(EditMode.TEXT);
|
||||||
|
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
|
@ -59,7 +61,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
);
|
);
|
||||||
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
||||||
activeCst?.definition.text.raw, activeCst?.convention,
|
activeCst?.definition.text.raw, activeCst?.convention,
|
||||||
term, textDefinition, expression, convention]);
|
term, textDefinition, expression, convention, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, HelpIcon, SaveIcon, ShareIcon } f
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { IRSFormCreateData, LibraryItemType } from '../../utils/models';
|
import { IRSFormCreateData, LibraryItemType } from '../../utils/models';
|
||||||
|
|
||||||
interface EditorRSFormProps {
|
interface EditorRSFormProps {
|
||||||
|
@ -37,7 +38,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
const [canonical, setCanonical] = useState(false);
|
const [canonical, setCanonical] = useState(false);
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(true);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
|
@ -53,7 +54,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
);
|
);
|
||||||
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
||||||
schema?.is_common, schema?.is_canonical,
|
schema?.is_common, schema?.is_canonical,
|
||||||
title, alias, comment, common, canonical]);
|
title, alias, comment, common, canonical, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema) {
|
if (schema) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { toast } from 'react-toastify';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../components/Common/TextInput';
|
||||||
import { useUserProfile } from '../../context/UserProfileContext';
|
import { useUserProfile } from '../../context/UserProfileContext';
|
||||||
|
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||||
import { IUserUpdateData } from '../../utils/models';
|
import { IUserUpdateData } from '../../utils/models';
|
||||||
|
|
||||||
function EditorProfile() {
|
function EditorProfile() {
|
||||||
|
@ -14,7 +15,7 @@ function EditorProfile() {
|
||||||
const [first_name, setFirstName] = useState('');
|
const [first_name, setFirstName] = useState('');
|
||||||
const [last_name, setLastName] = useState('');
|
const [last_name, setLastName] = useState('');
|
||||||
|
|
||||||
const [isModified, setIsModified] = useState(true);
|
const { isModified, setIsModified } = useModificationPrompt();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -26,7 +27,7 @@ function EditorProfile() {
|
||||||
user.first_name !== first_name ||
|
user.first_name !== first_name ||
|
||||||
user.last_name !== last_name
|
user.last_name !== last_name
|
||||||
);
|
);
|
||||||
}, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name]);
|
}, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name, setIsModified]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user