Refactoring: Context structure and error processing

This commit is contained in:
IRBorisov 2024-06-05 12:28:08 +03:00
parent 3f2db1e7bc
commit 89c2e704a8
11 changed files with 175 additions and 127 deletions

View File

@ -17,18 +17,6 @@ import useQueryStrings from '@/hooks/useQueryStrings';
import { IUserLoginData } from '@/models/user'; import { IUserLoginData } from '@/models/user';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='text-sm select-text clr-text-red'>
На Портале отсутствует такое сочетание имени пользователя и пароля
</div>
);
} else {
return <InfoError error={error} />;
}
}
function LoginPage() { function LoginPage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const query = useQueryStrings(); const query = useQueryStrings();
@ -105,3 +93,16 @@ function LoginPage() {
} }
export default LoginPage; export default LoginPage;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='text-sm select-text clr-text-red'>
На Портале отсутствует такое сочетание имени пользователя и пароля
</div>
);
} else {
return <InfoError error={error} />;
}
}

View File

@ -1,13 +1,9 @@
'use client'; 'use client';
import axios from 'axios';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
import TextURL from '@/components/ui/TextURL';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
@ -174,25 +170,7 @@ export const OssEditState = ({ children }: OssEditStateProps) => {
</AnimatePresence> </AnimatePresence>
) : null} ) : null}
{model.loading ? <Loader /> : null} {children}
{model.errorLoading ? <ProcessError error={model.errorLoading} /> : null}
{model.schema && !model.loading ? children : null}
</OssEditContext.Provider> </OssEditContext.Provider>
); );
}; };
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>{`Схема с указанным идентификатором отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
</div>
</div>
);
} else {
return <InfoError error={error} />;
}
}

View File

@ -1,12 +1,16 @@
'use client'; 'use client';
import axios from 'axios';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext'; import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
@ -30,7 +34,7 @@ function OssTabs() {
const activeTab = (Number(query.get('tab')) ?? OssTabID.CARD) as OssTabID; const activeTab = (Number(query.get('tab')) ?? OssTabID.CARD) as OssTabID;
const { calculateHeight } = useConceptOptions(); const { calculateHeight } = useConceptOptions();
const { schema, loading } = useOSS(); const { schema, loading, errorLoading } = useOSS();
const { destroyItem } = useLibrary(); const { destroyItem } = useLibrary();
const [isModified, setIsModified] = useState(false); const [isModified, setIsModified] = useState(false);
@ -115,6 +119,8 @@ function OssTabs() {
return ( return (
<OssEditState> <OssEditState>
{loading ? <Loader /> : null}
{errorLoading ? <ProcessError error={errorLoading} /> : null}
{schema && !loading ? ( {schema && !loading ? (
<Tabs <Tabs
selectedIndex={activeTab} selectedIndex={activeTab}
@ -141,3 +147,27 @@ function OssTabs() {
} }
export default OssTabs; export default OssTabs;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response) {
if (error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>{`Схема с указанным идентификатором отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
</div>
</div>
);
} else if (error.response.status === 403) {
return (
<div className='p-2 text-center'>
<p>Владелец ограничил доступ к данной схеме</p>
<TextURL text='Библиотека' href='/library' />
</div>
);
}
}
return <InfoError error={error} />;
}

View File

@ -0,0 +1,27 @@
'use client';
import axios from 'axios';
import { ErrorData } from '@/components/info/InfoError';
import TextURL from '@/components/ui/TextURL';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response) {
if (error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>{`Схема с указанным идентификатором отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
</div>
</div>
);
} else if (error.response.status === 403) {
return (
<div className='p-2 text-center'>
<p>Владелец ограничил доступ к данной схеме</p>
<TextURL text='Библиотека' href='/library' />
</div>
);
}
}
return <InfoError error={error} />;
}

View File

@ -14,14 +14,6 @@ import { useConceptNavigation } from '@/context/NavigationContext';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
import { IPasswordTokenData, IResetPasswordData } from '@/models/user'; import { IPasswordTokenData, IResetPasswordData } from '@/models/user';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return <div className='mt-6 text-sm select-text clr-text-red'>Данная ссылка не действительна</div>;
} else {
return <InfoError error={error} />;
}
}
function PasswordChangePage() { function PasswordChangePage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const token = useQueryStrings().get('token'); const token = useQueryStrings().get('token');
@ -117,3 +109,12 @@ function PasswordChangePage() {
} }
export default PasswordChangePage; export default PasswordChangePage;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return <div className='mt-6 text-sm select-text clr-text-red'>Данная ссылка не действительна</div>;
} else {
return <InfoError error={error} />;
}
}

View File

@ -1,16 +1,11 @@
'use client'; 'use client';
import axios from 'axios';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import fileDownload from 'js-file-download'; import fileDownload from 'js-file-download';
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Divider from '@/components/ui/Divider';
import Loader from '@/components/ui/Loader';
import TextURL from '@/components/ui/TextURL';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
@ -719,41 +714,12 @@ export const RSEditState = ({
</AnimatePresence> </AnimatePresence>
) : null} ) : null}
{model.loading ? <Loader /> : null} {children}
{model.errorLoading ? (
<ProcessError error={model.errorLoading} isArchive={model.isArchive} itemID={model.itemID} />
) : null}
{model.schema && !model.loading ? children : null}
</RSEditContext.Provider> </RSEditContext.Provider>
); );
}; };
// ====== Internals ========= // ====== Internals =========
function ProcessError({
error,
isArchive,
itemID
}: {
error: ErrorData;
isArchive: boolean;
itemID: string;
}): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>{`Схема с указанным идентификатором ${isArchive ? 'и версией ' : ''}отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
{isArchive ? <Divider vertical margins='mx-3' /> : null}
{isArchive ? <TextURL text='Актуальная версия' href={`/rsforms/${itemID}`} /> : null}
</div>
</div>
);
} else {
return <InfoError error={error} />;
}
}
function getNextActiveOnDelete( function getNextActiveOnDelete(
activeID: ConstituentaID | undefined, activeID: ConstituentaID | undefined,
items: IConstituenta[], items: IConstituenta[],

View File

@ -1,12 +1,17 @@
'use client'; 'use client';
import axios from 'axios';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Divider from '@/components/ui/Divider';
import Loader from '@/components/ui/Loader';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext'; import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
@ -39,7 +44,7 @@ function RSTabs() {
const cstQuery = query.get('active'); const cstQuery = query.get('active');
const { setNoFooter, calculateHeight } = useConceptOptions(); const { setNoFooter, calculateHeight } = useConceptOptions();
const { schema, loading } = useRSForm(); const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
const { destroyItem } = useLibrary(); const { destroyItem } = useLibrary();
const [isModified, setIsModified] = useState(false); const [isModified, setIsModified] = useState(false);
@ -233,13 +238,15 @@ function RSTabs() {
onCreateCst={onCreateCst} onCreateCst={onCreateCst}
onDeleteCst={onDeleteCst} onDeleteCst={onDeleteCst}
> >
{loading ? <Loader /> : null}
{errorLoading ? <ProcessError error={errorLoading} isArchive={isArchive} itemID={itemID} /> : null}
{schema && !loading ? ( {schema && !loading ? (
<Tabs <Tabs
selectedIndex={activeTab} selectedIndex={activeTab}
onSelect={onSelectTab} onSelect={onSelectTab}
defaultFocus defaultFocus
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col min-w-fit mx-auto' className='flex flex-col mx-auto min-w-fit'
> >
<TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}> <TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<RSTabsMenu onDestroy={onDestroySchema} /> <RSTabsMenu onDestroy={onDestroySchema} />
@ -269,3 +276,37 @@ function RSTabs() {
} }
export default RSTabs; export default RSTabs;
// ====== Internals =========
function ProcessError({
error,
isArchive,
itemID
}: {
error: ErrorData;
isArchive: boolean;
itemID: string;
}): React.ReactElement {
if (axios.isAxiosError(error) && error.response) {
if (error.response.status === 404) {
return (
<div className='p-2 text-center'>
<p>{`Схема с указанным идентификатором ${isArchive ? 'и версией ' : ''}отсутствует`}</p>
<div className='flex justify-center'>
<TextURL text='Библиотека' href='/library' />
{isArchive ? <Divider vertical margins='mx-3' /> : null}
{isArchive ? <TextURL text='Актуальная версия' href={`/rsforms/${itemID}`} /> : null}
</div>
</div>
);
} else if (error.response.status === 403) {
return (
<div className='p-2 text-center'>
<p>Владелец ограничил доступ к данной схеме</p>
<TextURL text='Библиотека' href='/library' />
</div>
);
}
}
return <InfoError error={error} />;
}

View File

@ -25,24 +25,6 @@ import { HelpTopic } from '@/models/miscellaneous';
import { IUserSignupData } from '@/models/user'; import { IUserSignupData } from '@/models/user';
import { globals, patterns } from '@/utils/constants'; import { globals, patterns } from '@/utils/constants';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
if ('email' in error.response.data) {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
<div className='w-full text-sm text-center select-text clr-text-red'>{error.response.data.email}.</div>
);
} else {
return (
<div className='text-sm select-text clr-text-red'>
<PrettyJson data={error.response} />
</div>
);
}
}
return <InfoError error={error} />;
}
function RegisterPage() { function RegisterPage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user, signup, loading, error, setError } = useAuth(); const { user, signup, loading, error, setError } = useAuth();
@ -203,3 +185,22 @@ function RegisterPage() {
} }
export default RegisterPage; export default RegisterPage;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
if ('email' in error.response.data) {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
<div className='w-full text-sm text-center select-text clr-text-red'>{error.response.data.email}.</div>
);
} else {
return (
<div className='text-sm select-text clr-text-red'>
<PrettyJson data={error.response} />
</div>
);
}
}
return <InfoError error={error} />;
}

View File

@ -12,14 +12,6 @@ import AnimateFade from '@/components/wrap/AnimateFade';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { IRequestPasswordData } from '@/models/user'; import { IRequestPasswordData } from '@/models/user';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return <div className='mt-6 text-sm select-text clr-text-red'>Данный email не используется на Портале.</div>;
} else {
return <InfoError error={error} />;
}
}
function RestorePasswordPage() { function RestorePasswordPage() {
const { requestPasswordReset, loading, error, setError } = useAuth(); const { requestPasswordReset, loading, error, setError } = useAuth();
@ -73,3 +65,12 @@ function RestorePasswordPage() {
} }
export default RestorePasswordPage; export default RestorePasswordPage;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return <div className='mt-6 text-sm select-text clr-text-red'>Данный email не используется на Портале.</div>;
} else {
return <InfoError error={error} />;
}
}

View File

@ -14,14 +14,6 @@ import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { IUserUpdatePassword } from '@/models/user'; import { IUserUpdatePassword } from '@/models/user';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return <div className='text-sm select-text clr-text-red'>Неверно введен старый пароль</div>;
} else {
return <InfoError error={error} />;
}
}
function EditorPassword() { function EditorPassword() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { updatePassword, error, setError, loading } = useAuth(); const { updatePassword, error, setError, loading } = useAuth();
@ -109,3 +101,12 @@ function EditorPassword() {
} }
export default EditorPassword; export default EditorPassword;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return <div className='text-sm select-text clr-text-red'>Неверно введен старый пароль</div>;
} else {
return <InfoError error={error} />;
}
}

View File

@ -12,18 +12,6 @@ import { useBlockNavigation } from '@/context/NavigationContext';
import { useUserProfile } from '@/context/UserProfileContext'; import { useUserProfile } from '@/context/UserProfileContext';
import { IUserUpdateData } from '@/models/user'; import { IUserUpdateData } from '@/models/user';
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
if ('email' in error.response.data) {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
<div className='text-sm select-text clr-text-red'>{error.response.data.email}.</div>
);
}
}
return <InfoError error={error} />;
}
function EditorProfile() { function EditorProfile() {
const { updateUser, user, errorProcessing, processing } = useUserProfile(); const { updateUser, user, errorProcessing, processing } = useUserProfile();
@ -106,3 +94,16 @@ function EditorProfile() {
} }
export default EditorProfile; export default EditorProfile;
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
if ('email' in error.response.data) {
return (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
<div className='text-sm select-text clr-text-red'>{error.response.data.email}.</div>
);
}
}
return <InfoError error={error} />;
}