Improve UI forms

This commit is contained in:
IRBorisov 2023-12-08 00:33:47 +03:00
parent b4c1fd97fb
commit 286bb4f29d
11 changed files with 151 additions and 141 deletions

View File

@ -45,7 +45,7 @@ function Root() {
overflowY: showScroll ? 'scroll': 'auto'
}}
>
<main className='w-full h-full min-w-fit' style={{minHeight: mainHeight}}>
<main className='w-full h-full min-w-fit flex flex-col items-center' style={{minHeight: mainHeight}}>
<Outlet />
</main>

View File

@ -1,24 +0,0 @@
interface FormProps {
title?: string
className?: string
dimensions?: string
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
children: React.ReactNode
}
function Form({
title, className, onSubmit,
dimensions='max-w-xs',
children
}: FormProps) {
return (
<form
className={`border shadow-md py-2 clr-app px-6 flex flex-col gap-3 ${dimensions} ${className}`}
onSubmit={onSubmit}
>
{ title ? <h1 className='text-xl whitespace-nowrap'>{title}</h1> : null }
{children}
</form>);
}
export default Form;

View File

@ -1,4 +1,5 @@
interface OverlayProps {
id?: string
children: React.ReactNode
position?: string
className?: string
@ -6,13 +7,13 @@ interface OverlayProps {
}
function Overlay({
children, className,
id, children, className,
position='top-0 right-0',
layer='z-pop'
}: OverlayProps) {
return (
<div className='relative'>
<div className={`absolute ${className} ${position} ${layer}`}>
<div id={id} className={`absolute ${className} ${position} ${layer}`}>
{children}
</div>
</div>);

View File

@ -36,7 +36,9 @@ function PDFViewer({
<Document
file={file}
onLoadSuccess={onDocumentLoadSuccess}
className='mx-3 w-fit'
className='px-3 w-fit'
loading='Загрузка PDF файла...'
error='Не удалось загрузить файл.'
>
<Overlay position='top-6 left-1/2 -translate-x-1/2' className='flex select-none'>
<PageControls

View File

@ -0,0 +1,33 @@
import { useAuth } from '../context/AuthContext';
import { useConceptNavigation } from '../context/NagivationContext';
import TextURL from './Common/TextURL';
function ExpectedAnonymous() {
const { user, logout } = useAuth();
const { navigateTo } = useConceptNavigation();
function logoutAndRedirect() {
logout(() => navigateTo('/login/'));
}
return (
<div className='flex flex-col items-center gap-3 py-6'>
<p className='font-semibold'>{`Вы вошли в систему как ${user?.username ?? ''}`}</p>
<div className='flex gap-3'>
<TextURL text='Новая схема' href='/rsform-create'/>
<span> | </span>
<TextURL text='Библиотека' href='/library'/>
<span> | </span>
<TextURL text='Справка' href='/manuals'/>
<span> | </span>
<span
className='cursor-pointer hover:underline text-url'
onClick={logoutAndRedirect}
>
Выйти
</span>
</div>
</div>);
}
export default ExpectedAnonymous;

View File

@ -5,7 +5,6 @@ import { toast } from 'react-toastify';
import BackendError from '../components/BackendError';
import Button from '../components/Common/Button';
import Checkbox from '../components/Common/Checkbox';
import Form from '../components/Common/Form';
import Label from '../components/Common/Label';
import MiniButton from '../components/Common/MiniButton';
import Overlay from '../components/Common/Overlay';
@ -79,11 +78,11 @@ function CreateRSFormPage() {
return (
<RequireAuth>
<div className='flex justify-center'>
<Form title='Создание концептуальной схемы'
<form
className='max-w-lg w-full py-3 px-6 flex flex-col gap-3'
onSubmit={handleSubmit}
dimensions='max-w-lg w-full mt-4'
>
<h1>Создание концептуальной схемы</h1>
<Overlay position='top-[-2.4rem] right-[-1rem]'>
<input ref={inputRef} type='file'
style={{ display: 'none' }}
@ -123,7 +122,7 @@ function CreateRSFormPage() {
value={common}
setValue={value => setCommon(value ?? false)}
/>
<div className='flex items-center justify-center gap-4 py-2'>
<div className='flex items-center justify-around py-2'>
<SubmitButton
text='Создать схему'
loading={processing}
@ -136,8 +135,7 @@ function CreateRSFormPage() {
/>
</div>
{error ? <BackendError error={error} /> : null}
</Form>
</div>
</form>
</RequireAuth>);
}

View File

@ -3,14 +3,14 @@ import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import BackendError, { ErrorInfo } from '../components/BackendError';
import Form from '../components/Common/Form';
import SubmitButton from '../components/Common/SubmitButton';
import TextInput from '../components/Common/TextInput';
import TextURL from '../components/Common/TextURL';
import ExpectedAnonymous from '../components/ExpectedAnonymous';
import { useAuth } from '../context/AuthContext';
import { useConceptNavigation } from '../context/NagivationContext';
import { useConceptTheme } from '../context/ThemeContext';
import { IUserLoginData } from '../models/library';
import { resources } from '../utils/constants';
function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
@ -25,11 +25,10 @@ function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
}
function LoginPage() {
const {mainHeight} = useConceptTheme();
const location = useLocation();
const { navigateTo, navigateHistory } = useConceptNavigation();
const search = useLocation().search;
const { user, login, logout, loading, error, setError } = useAuth();
const { user, login, loading, error, setError } = useAuth();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
@ -61,41 +60,17 @@ function LoginPage() {
}
}
function logoutAndRedirect() {
logout(() => navigateTo('/login/'));
if (user) {
return (<ExpectedAnonymous />);
}
return (
<div
className='flex items-start justify-center w-full pt-8 select-none'
style={{minHeight: mainHeight}}
>
{user ?
<div className='flex flex-col items-center gap-2'>
<p className='font-semibold'>{`Вы вошли в систему как ${user.username}`}</p>
<p>
<TextURL text='Создать схему' href='/rsform-create'/>
<span> | </span>
<TextURL text='Библиотека' href='/library'/>
<span> | </span>
<TextURL text='Справка' href='/manuals'/>
<span> | </span>
<span
className='cursor-pointer hover:underline text-url'
onClick={logoutAndRedirect}
>
Выйти
</span>
</p>
</div> : null}
{!user ?
<Form
<form
className='pt-12 pb-6 px-6 flex flex-col gap-3 w-[24rem]'
onSubmit={handleSubmit}
dimensions='w-[24rem]'
>
<img alt='Концепт Портал'
src='/logo_full.svg'
className='max-h-[2.5rem] min-w-[2.5rem] mt-2 mb-4'
src={resources.logo}
className='max-h-[2.5rem] min-w-[2.5rem] mb-3'
/>
<TextInput id='username' autoFocus required allowEnter
label='Имя пользователя'
@ -113,6 +88,7 @@ function LoginPage() {
text='Войти'
dimensions='w-[12rem]'
loading={loading}
disabled={!username || !password}
/>
</div>
<div className='flex flex-col text-sm'>
@ -120,8 +96,7 @@ function LoginPage() {
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{error ? <ProcessError error={error} /> : null}
</Form> : null}
</div>);
</form>);
}
export default LoginPage;

View File

@ -7,7 +7,7 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) {
return (
<div className='w-full px-2 py-2'>
<div className='w-full px-2 py-2 max-w-[80rem]'>
<InfoTopic topic={topic}/>
</div>);
}

View File

@ -2,7 +2,7 @@ import TextURL from '../components/Common/TextURL';
export function NotFoundPage() {
return (
<div className='flex flex-col px-4 py-2'>
<div className='px-6 py-2'>
<h1 className='text-xl font-semibold'>Ошибка 404 - Страница не найдена</h1>
<p className='mt-2'>Данная страница не существует или запрашиваемый объект отсутствует в базе данных</p>
<TextURL href='/' text='Вернуться на Портал' />

View File

@ -5,13 +5,17 @@ import { toast } from 'react-toastify';
import BackendError from '../components/BackendError';
import Button from '../components/Common/Button';
import Checkbox from '../components/Common/Checkbox';
import Form from '../components/Common/Form';
import ConceptTooltip from '../components/Common/ConceptTooltip';
import Overlay from '../components/Common/Overlay';
import SubmitButton from '../components/Common/SubmitButton';
import TextInput from '../components/Common/TextInput';
import TextURL from '../components/Common/TextURL';
import ExpectedAnonymous from '../components/ExpectedAnonymous';
import { HelpIcon } from '../components/Icons';
import { useAuth } from '../context/AuthContext';
import { useConceptNavigation } from '../context/NagivationContext';
import { type IUserSignupData } from '../models/library';
import { globalIDs } from '../utils/constants';
function RegisterPage() {
const location = useLocation();
@ -57,80 +61,99 @@ function RegisterPage() {
}
}
if (user) {
return (<ExpectedAnonymous />);
}
return (
<div className='flex justify-center w-full py-2'>
{user ? <b>{`Вы вошли в систему как ${user.username}`}</b> : null}
{!user ?
<Form
title='Регистрация'
onSubmit={handleSubmit}
dimensions='w-[30rem] mt-3'
>
<TextInput id='username' required
label='Имя пользователя'
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password' type='password' required
label='Пароль'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<TextInput id='password2' required type='password'
label='Повторите пароль'
value={password2}
onChange={event => setPassword2(event.target.value)}
/>
<div className='text-sm'>
<p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p>
{/* <p>- минимум 8 символов</p>
<p>- большие, маленькие буквы, цифры</p>
<p>- минимум 1 спец. символ</p> */}
</div>
<TextInput id='email' required
label='email'
value={email}
onChange={event => setEmail(event.target.value)}
/>
<TextInput id='first_name'
label='Имя'
value={firstName}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name'
label='Фамилия'
value={lastName}
onChange={event => setLastName(event.target.value)}
/>
<div className='flex text-sm'>
<Checkbox
label='Принимаю условия'
value={acceptPrivacy}
setValue={setAcceptPrivacy}
<form
className='py-3 px-6 flex flex-col gap-3 h-fit'
onSubmit={handleSubmit}
>
<h1>Новый пользователь</h1>
<div className='flex gap-12'>
<div className='flex flex-col gap-3'>
<div className='absolute'>
<Overlay
id={globalIDs.password_tooltip}
position='top-[4.8rem] left-[3.4rem] absolute'
>
<HelpIcon color='text-primary' size={5} />
</Overlay>
<ConceptTooltip
anchorSelect={`#${globalIDs.password_tooltip}`}
offset={6}
>
<p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p>
</ConceptTooltip>
</div>
<TextInput id='username' required
label='Имя пользователя (логин)'
value={username}
dimensions='w-[12rem]'
onChange={event => setUsername(event.target.value)}
/>
<TextURL
text='обработки персональных данных...'
href={'/manuals?topic=privacy'}
<TextInput id='password' type='password' required
label='Пароль'
dimensions='w-[15rem]'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<TextInput id='password2' required type='password'
label='Повторите пароль'
dimensions='w-[15rem]'
value={password2}
onChange={event => setPassword2(event.target.value)}
/>
</div>
<div className='flex items-center justify-center w-full gap-4 my-4'>
<SubmitButton
text='Регистрировать'
dimensions='min-w-[10rem]'
loading={loading}
disabled={!acceptPrivacy}
<div className='flex flex-col gap-3 w-[15rem]'>
<TextInput id='email' required
label='Электронная почта (email)'
value={email}
onChange={event => setEmail(event.target.value)}
/>
<Button
text='Отмена'
dimensions='min-w-[10rem]'
onClick={() => handleCancel()}
<TextInput id='first_name'
label='Отображаемое имя'
value={firstName}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name'
label='Отображаемая фамилия'
value={lastName}
onChange={event => setLastName(event.target.value)}
/>
</div>
{error ? <BackendError error={error} /> : null}
</Form> : null}
</div>);
</div>
<div className='flex text-sm'>
<Checkbox
label='Принимаю условия'
value={acceptPrivacy}
setValue={setAcceptPrivacy}
/>
<TextURL
text='обработки персональных данных...'
href={'/manuals?topic=privacy'}
/>
</div>
<div className='flex items-center justify-around w-full my-3'>
<SubmitButton
text='Регистрировать'
dimensions='min-w-[10rem]'
loading={loading}
disabled={!acceptPrivacy}
/>
<Button
text='Назад'
dimensions='min-w-[10rem]'
onClick={() => handleCancel()}
/>
</div>
{error ? <BackendError error={error} /> : null}
</form>);
}
export default RegisterPage;

View File

@ -29,7 +29,8 @@ export const EXTEOR_TRS_FILE = '.trs';
*/
export const resources = {
graph_font: '/DejaVu.ttf',
privacy_policy: '/privacy.pdf'
privacy_policy: '/privacy.pdf',
logo: '/logo_full.svg'
};
/**
@ -59,6 +60,7 @@ export const urls = {
* Global unique IDs.
*/
export const globalIDs = {
password_tooltip: 'password-tooltip',
main_scroll: 'main-scroll',
library_item_editor: 'library-item-editor',
constituenta_editor: 'constituenta-editor'