R: Improve suspesce and loading behavior

This commit is contained in:
Ivan 2025-01-29 16:17:50 +03:00
parent 283edcce86
commit 242d98abdc
19 changed files with 163 additions and 191 deletions

View File

@ -23,7 +23,7 @@ export const useResetPassword = () => {
data: IResetPasswordDTO, // data: IResetPasswordDTO, //
onSuccess?: () => void onSuccess?: () => void
) => resetMutation.mutate(data, { onSuccess }), ) => resetMutation.mutate(data, { onSuccess }),
isPending: resetMutation.isPending, isPending: resetMutation.isPending || validateMutation.isPending,
error: resetMutation.error, error: resetMutation.error,
reset: resetMutation.reset reset: resetMutation.reset
}; };

View File

@ -1,4 +1,4 @@
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI'; import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
import { ILibraryFilter } from '@/models/miscellaneous'; import { ILibraryFilter } from '@/models/miscellaneous';
@ -6,7 +6,7 @@ import { useLibrary } from './useLibrary';
export function useApplyLibraryFilter(filter: ILibraryFilter) { export function useApplyLibraryFilter(filter: ILibraryFilter) {
const { items } = useLibrary(); const { items } = useLibrary();
const { user } = useAuth(); const { user } = useAuthSuspense();
let result = items; let result = items;
if (!filter.folderMode && filter.head) { if (!filter.folderMode && filter.head) {
@ -28,10 +28,10 @@ export function useApplyLibraryFilter(filter: ILibraryFilter) {
result = result.filter(item => filter.isVisible === item.visible); result = result.filter(item => filter.isVisible === item.visible);
} }
if (filter.isOwned !== undefined) { if (filter.isOwned !== undefined) {
result = result.filter(item => filter.isOwned === (item.owner === user?.id)); result = result.filter(item => filter.isOwned === (item.owner === user.id));
} }
if (filter.isEditor !== undefined) { if (filter.isEditor !== undefined) {
result = result.filter(item => filter.isEditor == user?.editor.includes(item.id)); result = result.filter(item => filter.isEditor == user.editor.includes(item.id));
} }
if (filter.filterUser !== undefined) { if (filter.filterUser !== undefined) {
result = result.filter(item => filter.filterUser === item.owner); result = result.filter(item => filter.filterUser === item.owner);

View File

@ -1,11 +1,11 @@
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useLogout } from '@/backend/auth/useLogout'; import { useLogout } from '@/backend/auth/useLogout';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
function ExpectedAnonymous() { function ExpectedAnonymous() {
const { user } = useAuth(); const { user } = useAuthSuspense();
const { logout } = useLogout(); const { logout } = useLogout();
const router = useConceptNavigation(); const router = useConceptNavigation();
@ -15,7 +15,7 @@ function ExpectedAnonymous() {
return ( return (
<div className='cc-fade-in flex flex-col items-center gap-3 py-6'> <div className='cc-fade-in flex flex-col items-center gap-3 py-6'>
<p className='font-semibold'>{`Вы вошли в систему как ${user?.username ?? ''}`}</p> <p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
<div className='flex gap-3'> <div className='flex gap-3'>
<TextURL text='Новая схема' href='/library/create' /> <TextURL text='Новая схема' href='/library/create' />
<span> | </span> <span> | </span>

View File

@ -1,25 +0,0 @@
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
interface DataLoaderProps {
isLoading?: boolean;
error?: ErrorData;
hasNoData?: boolean;
}
function DataLoader({ isLoading, hasNoData, error, children }: React.PropsWithChildren<DataLoaderProps>) {
if (isLoading) {
return <Loader />;
}
if (error) {
return <InfoError error={error} />;
}
if (hasNoData) {
return <div className='cc-fade-in w-full text-center p-1'>Данные не загружены</div>;
} else {
return <>{children}</>;
}
}
export default DataLoader;

View File

@ -5,7 +5,7 @@ import { useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { IRSFormCloneDTO } from '@/backend/library/api'; import { IRSFormCloneDTO } from '@/backend/library/api';
import { useCloneItem } from '@/backend/library/useCloneItem'; import { useCloneItem } from '@/backend/library/useCloneItem';
import { VisibilityIcon } from '@/components/DomainIcons'; import { VisibilityIcon } from '@/components/DomainIcons';
@ -35,7 +35,7 @@ function DlgCloneLibraryItem() {
state => state.props as DlgCloneLibraryItemProps state => state.props as DlgCloneLibraryItemProps
); );
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuthSuspense();
const [title, setTitle] = useState(cloneTitle(base)); const [title, setTitle] = useState(cloneTitle(base));
const [alias, setAlias] = useState(base.alias); const [alias, setAlias] = useState(base.alias);

View File

@ -1,11 +1,12 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useState } from 'react'; import { Suspense, useEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import { IInlineSynthesisDTO } from '@/backend/rsform/api'; import { IInlineSynthesisDTO } from '@/backend/rsform/api';
import { useRSForm } from '@/backend/rsform/useRSForm'; import { useRSForm } from '@/backend/rsform/useRSForm';
import Loader from '@/components/ui/Loader';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
@ -14,7 +15,7 @@ import { ConstituentaID, IRSForm } from '@/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import TabConstituents from './TabConstituents'; import TabConstituents from './TabConstituents';
import TabSchema from './TabSchema'; import TabSource from './TabSource';
import TabSubstitutions from './TabSubstitutions'; import TabSubstitutions from './TabSubstitutions';
export interface DlgInlineSynthesisProps { export interface DlgInlineSynthesisProps {
@ -32,20 +33,20 @@ function DlgInlineSynthesis() {
const { receiver, onInlineSynthesis } = useDialogsStore(state => state.props as DlgInlineSynthesisProps); const { receiver, onInlineSynthesis } = useDialogsStore(state => state.props as DlgInlineSynthesisProps);
const [activeTab, setActiveTab] = useState(TabID.SCHEMA); const [activeTab, setActiveTab] = useState(TabID.SCHEMA);
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined); const [sourceID, setSourceID] = useState<LibraryItemID | undefined>(undefined);
const [selected, setSelected] = useState<ConstituentaID[]>([]); const [selected, setSelected] = useState<ConstituentaID[]>([]);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]); const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]);
const source = useRSForm({ itemID: donorID }); const { schema } = useRSForm({ itemID: sourceID });
const validated = !!source.schema && selected.length > 0; const validated = selected.length > 0;
function handleSubmit() { function handleSubmit() {
if (!source.schema) { if (!sourceID || selected.length === 0) {
return; return;
} }
onInlineSynthesis({ onInlineSynthesis({
source: source.schema.id, source: sourceID,
receiver: receiver.id, receiver: receiver.id,
items: selected, items: selected,
substitutions: substitutions substitutions: substitutions
@ -53,9 +54,16 @@ function DlgInlineSynthesis() {
} }
useEffect(() => { useEffect(() => {
setSelected(source.schema ? source.schema.items.map(cst => cst.id) : []); if (schema) {
setSelected(schema.items.map(cst => cst.id));
}
}, [schema, setSelected]);
function handleSetSource(schemaID: LibraryItemID) {
setSourceID(schemaID);
setSelected([]);
setSubstitutions([]); setSubstitutions([]);
}, [source.schema]); }
return ( return (
<Modal <Modal
@ -73,32 +81,44 @@ function DlgInlineSynthesis() {
> >
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none', 'bg-prim-200')}> <TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none', 'bg-prim-200')}>
<TabLabel label='Схема' title='Источник конституент' className='w-[8rem]' /> <TabLabel label='Схема' title='Источник конституент' className='w-[8rem]' />
<TabLabel label='Содержание' title='Перечень конституент' className='w-[8rem]' /> <TabLabel
<TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]' /> label='Содержание'
title={!sourceID ? 'Выберите схему' : 'Перечень конституент'}
className='w-[8rem]'
disabled={!sourceID}
/>
<TabLabel
label='Отождествления'
title={!sourceID ? 'Выберите схему' : 'Таблица отождествлений'}
className='w-[8rem]'
disabled={!sourceID}
/>
</TabList> </TabList>
<TabPanel> <TabPanel>
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} /> <TabSource selected={sourceID} setSelected={handleSetSource} receiver={receiver} />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<TabConstituents {!!sourceID ? (
schema={source.schema} <Suspense fallback={<Loader />}>
loading={source.isLoading} <TabConstituents itemID={sourceID} selected={selected} setSelected={setSelected} />
selected={selected} </Suspense>
setSelected={setSelected} ) : null}
/>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<TabSubstitutions {!!sourceID ? (
receiver={receiver} <Suspense fallback={<Loader />}>
source={source.schema} <TabSubstitutions
selected={selected} sourceID={sourceID}
loading={source.isLoading} receiver={receiver}
substitutions={substitutions} selected={selected}
setSubstitutions={setSubstitutions} substitutions={substitutions}
/> setSubstitutions={setSubstitutions}
/>
</Suspense>
) : null}
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>

View File

@ -1,33 +1,29 @@
'use client'; 'use client';
import { ErrorData } from '@/components/info/InfoError'; import { useRSFormSuspense } from '@/backend/rsform/useRSForm';
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta'; import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
import DataLoader from '@/components/wrap/DataLoader'; import { LibraryItemID } from '@/models/library';
import { ConstituentaID, IRSForm } from '@/models/rsform'; import { ConstituentaID } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface TabConstituentsProps { interface TabConstituentsProps {
schema?: IRSForm; itemID: LibraryItemID;
loading?: boolean;
error?: ErrorData;
selected: ConstituentaID[]; selected: ConstituentaID[];
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>; setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
} }
function TabConstituents({ schema, error, loading, selected, setSelected }: TabConstituentsProps) { function TabConstituents({ itemID, selected, setSelected }: TabConstituentsProps) {
const { schema } = useRSFormSuspense({ itemID });
return ( return (
<DataLoader isLoading={loading} error={error} hasNoData={!schema}> <PickMultiConstituenta
{schema ? ( schema={schema}
<PickMultiConstituenta data={schema.items}
schema={schema} rows={13}
data={schema.items} prefixID={prefixes.cst_inline_synth_list}
rows={13} selected={selected}
prefixID={prefixes.cst_inline_synth_list} setSelected={setSelected}
selected={selected} />
setSelected={setSelected}
/>
) : null}
</DataLoader>
); );
} }

View File

@ -7,13 +7,13 @@ import { LibraryItemID, LibraryItemType } from '@/models/library';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { sortItemsForInlineSynthesis } from '@/models/rsformAPI'; import { sortItemsForInlineSynthesis } from '@/models/rsformAPI';
interface TabSchemaProps { interface TabSourceProps {
selected?: LibraryItemID; selected?: LibraryItemID;
setSelected: (newValue: LibraryItemID) => void; setSelected: (newValue: LibraryItemID) => void;
receiver: IRSForm; receiver: IRSForm;
} }
function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) { function TabSource({ selected, receiver, setSelected }: TabSourceProps) {
const { items: libraryItems } = useLibrary(); const { items: libraryItems } = useLibrary();
const selectedInfo = libraryItems.find(item => item.id === selected); const selectedInfo = libraryItems.find(item => item.id === selected);
const sortedItems = sortItemsForInlineSynthesis(receiver, libraryItems); const sortedItems = sortItemsForInlineSynthesis(receiver, libraryItems);
@ -43,4 +43,4 @@ function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
); );
} }
export default TabSchema; export default TabSource;

View File

@ -1,48 +1,34 @@
'use client'; 'use client';
import { ErrorData } from '@/components/info/InfoError'; import { useRSFormSuspense } from '@/backend/rsform/useRSForm';
import PickSubstitutions from '@/components/select/PickSubstitutions'; import PickSubstitutions from '@/components/select/PickSubstitutions';
import DataLoader from '@/components/wrap/DataLoader'; import { LibraryItemID } from '@/models/library';
import { ICstSubstitute } from '@/models/oss'; import { ICstSubstitute } from '@/models/oss';
import { ConstituentaID, IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface TabSubstitutionsProps { interface TabSubstitutionsProps {
receiver?: IRSForm; receiver: IRSForm;
source?: IRSForm; sourceID: LibraryItemID;
selected: ConstituentaID[]; selected: ConstituentaID[];
loading?: boolean;
error?: ErrorData;
substitutions: ICstSubstitute[]; substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>; setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
} }
function TabSubstitutions({ function TabSubstitutions({ sourceID, receiver, selected, substitutions, setSubstitutions }: TabSubstitutionsProps) {
source, const { schema: source } = useRSFormSuspense({ itemID: sourceID });
receiver,
selected,
error,
loading,
substitutions,
setSubstitutions
}: TabSubstitutionsProps) {
const schemas = [...(source ? [source] : []), ...(receiver ? [receiver] : [])]; const schemas = [...(source ? [source] : []), ...(receiver ? [receiver] : [])];
return ( return (
<DataLoader isLoading={loading} error={error} hasNoData={!source}> <PickSubstitutions
<PickSubstitutions substitutions={substitutions}
substitutions={substitutions} setSubstitutions={setSubstitutions}
setSubstitutions={setSubstitutions} rows={10}
rows={10} prefixID={prefixes.cst_inline_synth_substitutes}
prefixID={prefixes.cst_inline_synth_substitutes} schemas={schemas}
schemas={schemas} filter={cst => cst.id !== source?.id || selected.includes(cst.id)}
filter={cst => cst.id !== source?.id || selected.includes(cst.id)} />
/>
</DataLoader>
); );
} }

View File

@ -9,9 +9,9 @@ import { useRSForm } from '@/backend/rsform/useRSForm';
import { RelocateUpIcon } from '@/components/DomainIcons'; import { RelocateUpIcon } from '@/components/DomainIcons';
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta'; import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
import SelectLibraryItem from '@/components/select/SelectLibraryItem'; import SelectLibraryItem from '@/components/select/SelectLibraryItem';
import Loader from '@/components/ui/Loader';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import Modal from '@/components/ui/Modal';
import DataLoader from '@/components/wrap/DataLoader';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { ILibraryItem, LibraryItemID } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IOperation, IOperationSchema } from '@/models/oss'; import { IOperation, IOperationSchema } from '@/models/oss';
@ -119,19 +119,18 @@ function DlgRelocateConstituents() {
onSelectValue={handleSelectDestination} onSelectValue={handleSelectDestination}
/> />
</div> </div>
<DataLoader isLoading={sourceData.isLoading} error={sourceData.error}> {sourceData.isLoading ? <Loader /> : null}
{sourceData.schema ? ( {!sourceData.isLoading && sourceData.schema ? (
<PickMultiConstituenta <PickMultiConstituenta
noBorder noBorder
schema={sourceData.schema} schema={sourceData.schema}
data={filteredConstituents} data={filteredConstituents}
rows={12} rows={12}
prefixID={prefixes.dlg_cst_constituents_list} prefixID={prefixes.dlg_cst_constituents_list}
selected={selected} selected={selected}
setSelected={setSelected} setSelected={setSelected}
/> />
) : null} ) : null}
</DataLoader>
</div> </div>
</Modal> </Modal>
); );

View File

@ -1,4 +1,4 @@
import RequireAuth from '@/components/wrap/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
import FormCreateItem from './FormCreateItem'; import FormCreateItem from './FormCreateItem';

View File

@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { ILibraryCreateDTO } from '@/backend/library/api'; import { ILibraryCreateDTO } from '@/backend/library/api';
import { useCreateItem } from '@/backend/library/useCreateItem'; import { useCreateItem } from '@/backend/library/useCreateItem';
import { VisibilityIcon } from '@/components/DomainIcons'; import { VisibilityIcon } from '@/components/DomainIcons';
@ -29,7 +29,7 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
function FormCreateItem() { function FormCreateItem() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuthSuspense();
const { createItem, isPending, error, reset } = useCreateItem(); const { createItem, isPending, error, reset } = useCreateItem();
const searchLocation = useLibrarySearchStore(state => state.location); const searchLocation = useLibrarySearchStore(state => state.location);

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useLibrary } from '@/backend/library/useLibrary'; import { useLibrary } from '@/backend/library/useLibrary';
import { SubfoldersIcon } from '@/components/DomainIcons'; import { SubfoldersIcon } from '@/components/DomainIcons';
import { IconFolderEdit, IconFolderTree } from '@/components/Icons'; import { IconFolderEdit, IconFolderTree } from '@/components/Icons';
@ -23,7 +23,7 @@ interface ViewSideLocationProps {
} }
function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps) { function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps) {
const { user, isAnonymous } = useAuth(); const { user, isAnonymous } = useAuthSuspense();
const { items } = useLibrary(); const { items } = useLibrary();
const windowSize = useWindowSize(); const windowSize = useWindowSize();

View File

@ -6,13 +6,13 @@ import { useEffect, useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext'; import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useLogin } from '@/backend/auth/useLogin'; import { useLogin } from '@/backend/auth/useLogin';
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
import InfoError, { ErrorData } from '@/components/info/InfoError'; import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
@ -21,7 +21,7 @@ function LoginPage() {
const query = useQueryStrings(); const query = useQueryStrings();
const userQuery = query.get('username'); const userQuery = query.get('username');
const { isAnonymous } = useAuth(); const { isAnonymous } = useAuthSuspense();
const { login, isPending, error, reset } = useLogin(); const { login, isPending, error, reset } = useLogin();
const [username, setUsername] = useState(userQuery || ''); const [username, setUsername] = useState(userQuery || '');

View File

@ -3,7 +3,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import SelectLocationContext from '@/components/select/SelectLocationContext'; import SelectLocationContext from '@/components/select/SelectLocationContext';
import SelectLocationHead from '@/components/select/SelectLocationHead'; import SelectLocationHead from '@/components/select/SelectLocationHead';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
@ -21,7 +21,7 @@ export interface DlgChangeLocationProps {
function DlgChangeLocation() { function DlgChangeLocation() {
const { initial, onChangeLocation } = useDialogsStore(state => state.props as DlgChangeLocationProps); const { initial, onChangeLocation } = useDialogsStore(state => state.props as DlgChangeLocationProps);
const { user } = useAuth(); const { user } = useAuthSuspense();
const [head, setHead] = useState<LocationHead>(initial.substring(0, 2) as LocationHead); const [head, setHead] = useState<LocationHead>(initial.substring(0, 2) as LocationHead);
const [body, setBody] = useState<string>(initial.substring(3)); const [body, setBody] = useState<string>(initial.substring(3));

View File

@ -9,9 +9,9 @@ import { urls } from '@/app/urls';
import { IResetPasswordDTO } from '@/backend/auth/api'; import { IResetPasswordDTO } from '@/backend/auth/api';
import { useResetPassword } from '@/backend/auth/useResetPassword'; import { useResetPassword } from '@/backend/auth/useResetPassword';
import InfoError, { ErrorData } from '@/components/info/InfoError'; import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import DataLoader from '@/components/wrap/DataLoader';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
function PasswordChangePage() { function PasswordChangePage() {
@ -51,43 +51,48 @@ function PasswordChangePage() {
validateToken({ token: token ?? '' }, () => setIsTokenValid(true)); validateToken({ token: token ?? '' }, () => setIsTokenValid(true));
}, [token, validateToken]); }, [token, validateToken]);
return ( if (error) {
<DataLoader isLoading={isPending} hasNoData={!isTokenValid}> return <ProcessError error={error} />;
<form className={clsx('cc-fade-in cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}> }
<TextInput
id='new_password'
type='password'
label='Новый пароль'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
}}
/>
<TextInput
id='new_password_repeat'
type='password'
label='Повторите новый'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);
}}
/>
<SubmitButton if (isPending || !isTokenValid) {
text='Установить пароль' return <Loader />;
className='self-center w-[12rem] mt-3' }
loading={isPending}
disabled={!canSubmit} return (
/> <form className={clsx('cc-fade-in cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}>
{error ? <ProcessError error={error} /> : null} <TextInput
</form> id='new_password'
</DataLoader> type='password'
label='Новый пароль'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
}}
/>
<TextInput
id='new_password_repeat'
type='password'
label='Повторите новый'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);
}}
/>
<SubmitButton
text='Установить пароль'
className='self-center w-[12rem] mt-3'
loading={isPending}
disabled={!canSubmit}
/>
</form>
); );
} }

View File

@ -1,15 +1,11 @@
import { useAuth } from '@/backend/auth/useAuth'; import { useAuthSuspense } from '@/backend/auth/useAuth';
import Loader from '@/components/ui/Loader'; import ExpectedAnonymous from '@/components/ExpectedAnonymous';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
import FormSignup from './FormSignup'; import FormSignup from './FormSignup';
function RegisterPage() { function RegisterPage() {
const { user, isLoading } = useAuth(); const { user } = useAuthSuspense();
if (isLoading) {
return <Loader />;
}
if (user) { if (user) {
return <ExpectedAnonymous />; return <ExpectedAnonymous />;
} else { } else {

View File

@ -1,7 +1,4 @@
import { Suspense } from 'react'; import RequireAuth from '@/components/RequireAuth';
import Loader from '@/components/ui/Loader';
import RequireAuth from '@/components/wrap/RequireAuth';
import EditorPassword from './EditorPassword'; import EditorPassword from './EditorPassword';
import EditorProfile from './EditorProfile'; import EditorProfile from './EditorProfile';
@ -9,15 +6,13 @@ import EditorProfile from './EditorProfile';
function UserProfilePage() { function UserProfilePage() {
return ( return (
<RequireAuth> <RequireAuth>
<Suspense fallback={<Loader />}> <div className='cc-fade-in flex flex-col py-2 mx-auto w-fit'>
<div className='cc-fade-in flex flex-col py-2 mx-auto w-fit'> <h1 className='mb-2 select-none'>Учетные данные пользователя</h1>
<h1 className='mb-2 select-none'>Учетные данные пользователя</h1> <div className='flex py-2'>
<div className='flex py-2'> <EditorProfile />
<EditorProfile /> <EditorPassword />
<EditorPassword />
</div>
</div> </div>
</Suspense> </div>
</RequireAuth> </RequireAuth>
); );
} }