Refactor router and add unsaved changes prompt

This commit is contained in:
IRBorisov 2023-08-27 22:08:18 +03:00
parent 0134af6b57
commit 0fb9ea932d
8 changed files with 88 additions and 32 deletions

View File

@ -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;

View 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;

View File

@ -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>,
) )

View File

@ -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>
); );

View File

@ -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>
); );
} }

View File

@ -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(
() => { () => {

View File

@ -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) {

View File

@ -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) {