Используем Material Design.

Установка библиотеки.

install @material-ui/core --save

Установка кнопки

ссылка на документацию

import Button from '@material-ui/core/Button';

...

return (
  <>
    <Button variant="contained" color="secondary">
      Вход
    </Button>
  
)
...

Всплывающие панели

Имортируем дополнительные компоненты.

import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Drawer from '@material-ui/core/Drawer';
import TextField from '@material-ui/core/TextField';

Определим состояние компонента с двумя флагами открытия форм логина и регистрации.

  const [state, setState] = React.useState({
    login: false,
    registration: false
  });

После этого у нас появляется переменная sate

  {
    login: false,
    registration: false
  }

Мы можем изменять состояние (объект state) при помощи функции setState примерно так:

setState({ ...state, login: false })

Определим панель с вормой логина.

    <Drawer anchor="right" open={state.login} onClose={() => {
        setState({ ...state, login: false })
    }}>
      <List>

        <ListItem>
          <ListItemText primary="Вход на сайт" />
        </ListItem>

        <ListItem>
            <TextField label="Email" variant="outlined" />
        </ListItem>

        <ListItem>
            <TextField label="Пароль" variant="outlined" />
        </ListItem>

        <ListItem>
          <Button variant="contained" color="secondary">
              Войти
          </Button>
        </ListItem>

      </List>
    </Drawer>

anchor=”right” - указывает на позицию панели

Добавим кнопку для показа панели где установим состояние панели login.

    <Button variant="contained" onClick={ () => {setState({ ...state, login: true })}} color="secondary">
      Вход
    </Button>

setState({ …state, login: true })} - мы используем три точки для того, чтобы сделать клонирование переменной, и сделать ее копию т.к. хотим поменять только ее часть.

Результат.

start page

Набросим на кнопку функцию логина.

  <Button 
    onClick={ () => login() }
    variant="contained" 
    color="secondary">
      Войти
  </Button>

Получаем значения полей и передаем в запрос.

Для начала определим переменные состояния.

  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');

Привяжем изменения полей к состоянию.

        <ListItem>
            <TextField 
            onChange={event => setEmail(event.target.value)}
            label="Email" 
            variant="outlined" />
        </ListItem>

        <ListItem>
            <TextField 
            onChange={event => setPassword(event.target.value)} 
            label="Пароль" 
            variant="outlined" />
        </ListItem>

Совершим запрос на сервер и передадим данные.

  const login = () => {
    req.post('account/login',{email,password})
    .then((payload) => {
      console.log(payload);
    });
  }

Выведем ошибку авторизации.

Создадим под ошибку state и установим его в true при статусе = 1.

const [error, setError] = React.useState(false);

  const req = new Request();
  const login = () => {
    req.post('account/login',{email,password})
    .then((payload) => {
      if(payload.status === 1) {
        setError(true);
      }
    });
  }

Подкрасим красным поля, вставив error={error}.

        <ListItem>
            <TextField 
            error={error}
            onChange={event => setEmail(event.target.value)}
            label="Email" 
            variant="outlined" />
        </ListItem>

        <ListItem>
            <TextField 
            error={error}
            onChange={event => setPassword(event.target.value)} 
            label="Пароль" 
            variant="outlined" />
        </ListItem>

Можем показать ошибку отдельным компонентом.

        <ListItem
        style={error ? {} : { display: 'none' }}
        >
          <TextField
            error
            id="filled-error"
            label="Ошибка входа!"
            variant="filled"
          />
        </ListItem>

start page

Далее при успешном входе скроем панель и установим переменную токена в localStorage браузера.

  const login = () => {
    req.post('account/login',{email,password})
    .then((payload) => {
      if(payload.status === 1) {
        setError(true);
      } else {
        window.localStorage.setItem('token', payload.token);
        setState({...state, login: false});
      }
    });
  }

start page

Создадим функцию разлогина.

  const logout = () => {
    window.localStorage.removeItem('token');
    location.reload();
  }

Где удалим токен из хранилища и перегрузим страницу.

Компонент формы регистрации.

Для удобства разделим форму регистрации и авторизации на два компонента.

Компонент регистрации RegForm.tsx.

import * as React from 'react';
import { Request } from '../../Request';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Drawer from '@material-ui/core/Drawer';
import TextField from '@material-ui/core/TextField';


export default function RegForm() {


  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [error, setError] = React.useState(false);

  const req = new Request();
  const register = () => {
    req.post('account/registration',{email,password})
    .then((payload) => {
      if(payload.status === 1) {
        setError(true);
      } else {
        window.localStorage.setItem('token', payload.token);
      }
    });
  }

    return (
      <>
        <List>

          <ListItem>
              <TextField 
              error={error}
              onChange={event => setEmail(event.target.value)}
              label="Email" 
              variant="outlined" />
          </ListItem>

          <ListItem>
              <TextField 
              error={error}
              onChange={event => setPassword(event.target.value)} 
              label="Пароль" 
              variant="outlined" />
          </ListItem>

          <ListItem
          style={error ? {} : { display: 'none' }}
          >
            <TextField
              error
              id="filled-error"
              label="Ошибка входа!"
              variant="filled"
            />
          </ListItem>

          <ListItem>
            <Button 
              onClick={ () => register() }
              variant="contained" 
              color="secondary">
                Зарегистрироваться
            </Button>
          </ListItem>
        </List>
      
    )
}

Компонент авторизации LoginForm.tsx.

import * as React from 'react';
import { Request } from '../../Request';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Drawer from '@material-ui/core/Drawer';
import TextField from '@material-ui/core/TextField';


export default function LoginForm() {


  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [error, setError] = React.useState(false);

  const req = new Request();
  const login = () => {
    req.post('account/login',{email,password})
    .then((payload) => {
      if(payload.status === 1) {
        setError(true);
      } else {
        window.localStorage.setItem('token', payload.token);
      }
    });
  }


    return (
      <>
          <List>

          <ListItem>
              <TextField 
              error={error}
              onChange={event => setEmail(event.target.value)}
              label="Email" 
              variant="outlined" />
          </ListItem>

          <ListItem>
              <TextField 
              error={error}
              onChange={event => setPassword(event.target.value)} 
              label="Пароль" 
              variant="outlined" />
          </ListItem>

          <ListItem
          style={error ? {} : { display: 'none' }}
          >
            <TextField
              error
              id="filled-error"
              label="Ошибка входа!"
              variant="filled"
            />
          </ListItem>

          <ListItem>
            <Button 
              onClick={ () => login() }
              variant="contained" 
              color="secondary">
                Войти
            </Button>
            <a className="btn btn-primary" href="/login/google-oauth2/">
              <img src="/static/images/google.png" />
            </a>
          </ListItem>

        </List>
      
    )
}

Теперь сократим компонент UserMenu.tsx и додавим табы.

import * as React from 'react';
import Button from '@material-ui/core/Button';
import Drawer from '@material-ui/core/Drawer';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import RegForm from './RegForm';
import LoginForm from './LoginForm';


export default function UserMenu() {

  const [value, setValue] = React.useState(0);
  const [showPanel, setShowPanel] = React.useState(false);

  const logout = () => {
    window.localStorage.removeItem('token');
    location.reload();
  }

  if (window.localStorage.getItem('token')) {
    return (
      <>
        <Button 
        onClick={ () => {logout()}}
        variant="contained" 
        color="secondary">
          Выход
        </Button>
      
    );
  } else {
    const handleChange = (event, newValue) => {
      setValue(newValue);
      console.log(newValue);
    };
    return (
      <>
        <Button 
        variant="contained" 
        onClick={ () => {setShowPanel(true)}} 
        color="secondary">
          Вход
        </Button>

        <Drawer anchor="right" open={showPanel} onClose={() => {
            setShowPanel(false)
        }}>

          <Tabs 
          value={value}
          onChange={handleChange} 
          >
            <Tab label="Вход"  />
            <Tab label="Регистрация" />
          </Tabs>

          <div hidden={value !== 1} >
            <RegForm  />
          </div>

          <div hidden={value !== 0} >
            <LoginForm />
          </div>

        </Drawer>
      
    )
  }

}

Так как мы вынесли формы в отдельные компоненты, то чтобы отреагировать родительским компонентом UserMenu на события внутри дочерних, нужно пробросить эти собития изнутри дочерних наружу.

В нашем случае это событие авторизации, после него необходимо закрыть панель из родительского компонента.

Определим колбек в родительском компоненте.

export default function UserMenu() {

  .....
  const doLogin = () => {
    setShowPanel(false);
  }

в нем же передадим дочернему связку этого колбека с колбеком внутри дочернего компонента.

  <div hidden={value !== 0} >
    <LoginForm clickCallback={doLogin} />
  </div>

В дочернем определим приход колбека в определении функции самого компонента.

export default function LoginForm({clickCallback}) {

  ...

И вызовем этот колбек.

req.post('account/login',{email,password})
.then((payload) => {
  if(payload.status === 1) {
   ...
  } else {
    ...
    clickCallback();
  }
});

Мы може передать токен пользователя через колбек и установить его как состояние компонента, избегая перегрузки страницы после логина.

export default function UserMenu() {

  const [token, setToken] = React.useState(window.localStorage.getItem('token'));

  ...

  const doLogin = (token) => {
    setShowPanel(false);
    setToken(token);
  }

Передача токена из дочернего компонента.

req.post('account/login',{email,password})
.then((payload) => {
  if(payload.status === 1) {
    setError(true);
  } else {
    clickCallback(payload.token);
  }
});
Задать вопрос, прокомментировать.