Разработка личного кабинета клиента на React: авторизация и стайлинг

В предыдущем посте мы начали разработку личного кабинета на React и узнали как этот процесс можно ускорить при помощи фреймворка React Admin. Были рассмотрены следующие моменты: установка и первичная настройка, вывод перечня данных полученных по API, реализовали редактирование и добавление записей. Пусть пока и на основе фиктивного API, но мы уже разобрались как происходит процесс разработки.

Личный кабинет не был бы личным, если бы не давал возможность ограничить доступ к данным по логину и паролю. В этой публикации как раз и займемся реализацией процесса аутентификации пользователей. Так же немного коснемся вопроса стилизации нашей административной панели на React.

Аутентификация

react-admin позволяет достаточно гибко реализовывать аутентификацию. JSONPlaceholder не имеет модели аутентификации, поэтому мы собираемся создать фиктивный процесс аутентификации, который будет принимать любые значения в качестве имени пользователя и пароля и сохранять эти значения в localStorage. В папке src создайте файл с именем authProvider.js:

// in src/authProvider.js
export default {
    // called when the user attempts to log in
    login: ({ username }) => {
        localStorage.setItem('username', username);
        // accept all username/password combinations
        return Promise.resolve();
    },
    // called when the user clicks on the logout button
    logout: () => {
        localStorage.removeItem('username');
        return Promise.resolve();
    },
    // called when the API returns an error
    checkError: ({ status }) => {
        if (status === 401 || status === 403) {
            localStorage.removeItem('username');
            return Promise.reject();
        }
        return Promise.resolve();
    },
    // called when the user navigates to a new location, to check for authentication
    checkAuth: () => {
        return localStorage.getItem('username')
            ? Promise.resolve()
            : Promise.reject();
    },
    // called when the user navigates to a new location, to check for permissions / roles
    getPermissions: () => Promise.resolve(),
};

AuthProvider должен предоставить 5 методов, каждый из которых возвращает Promise. Поскольку вызовы authProvider являются асинхронными, вы можете c легкостью запросить сервер аутентификации.

Чтобы включить эту стратегию аутентификации, передайте клиенту в качестве свойства authProvider в компоненте <Admin>:

<Admin dashboard={Dashboard} authProvider={authProvider} dataProvider={dataProvider}>
// ...
</Admin>

После перезагрузки приложения, выведется форма входа в систему, которая на данный момент принимает любые данные:

Немного о типах полей

В предыдущей статье мы коснулись вопроса типов полей когда создавали пользовательские компоненты на основе дынных, генерируемых гессером. React-admin дает возможность использвоать разные типы компонентов Field для отображения данных: число, дата, изображение, HTML, массив, ссылка и другие.

Например, поле с адресом веб-сайта выглядит как URL. Вместо того, чтобы отображать его в виде текста, почему бы не отобразить его с помощью кликабельной ссылки? Это именно то, что делает <UrlField>.

В react-admin поля являются простыми компонентами React. При выполнении, они получают record из API (например, { "id": 2, "name": "Ervin Howell", "website": "anastasia.net", ... }), и source поле которое должны отобразить (например, website). Это означает, что мы можем написать пользовательский компонент достаточно просто. Например, вот упрощенная версия UrlField:

// in src/MyUrlField.js
import React from 'react';

const MyUrlField = ({ record = {}, source }) =>
  <a href={record[source]}>
    {record[source]}
  </a>;

export default MyUrlField;

Вы можете использовать этот компонент в <UserList> вместо компонента <UrlField> и он будет работать аналогично.

// in src/users.js
import React from 'react';
import { List, Datagrid, TextField, EmailField } from 'react-admin';
import MyUrlField from './MyUrlField';

export const UserList = props => (
    <List {...props}>
        <Datagrid rowClick="edit">
            ...
            <MyUrlField source="website" />
            ...
        </Datagrid>
    </List>
);

Вы можете заменить любой из компонентов react-admin своим собственным, если встроенный компонент не отвечает вашим потребностям.

Настройка стилей

Компонент MyUrlField – прекрасная возможность проиллюстрировать, как настраивать стили. React-admin опирается на material-ui, набор компонентов React, созданный по образцу Руководства Google по UI-разработке. Material-ui использует JSS, решение CSS-in-JS, для стилизации компонентов. Давайте воспользуемся возможностями JSS, чтобы удалить подчеркивание из ссылки и добавить значок:

// in src/MyUrlField.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import LaunchIcon from '@material-ui/icons/Launch';

const useStyles = makeStyles({
  link: {
    textDecoration: 'none'
  },
  icon: {
    width: '0.5em',
    paddingLeft: 2
  }
});

const MyUrlField = ({ record = {}, source }) => {
  const classes = useStyles();
  return (
    <a href={record[source]} className={classes.link}>
      {record[source]}
      <LaunchIcon className={classes.icon} />
    </a>
  );
};

export default MyUrlField;

В JSS стили определяются как объекты JavaScript, именую свойства CSS в стиле JS (например, textDecoration вместо text-decoration). Чтобы передать эти стили компоненту, используется makeStyles для генерации React-хука. Хук создаст новые имена классов для этих стилей и вернет новые имена классов в объекте классов. Затем вы можете использовать эти имена className prop, как если бы вы использовали обычный класс CSS.

В JSS есть гораздо больше возможностей, о которых можно прочитать в документации по Material-UI. Так же Material-UI поддерживает другие решения CSS-in-JS, включая Styled Components.

Обработка взаимосвязей

В нашем фиктивном API от JSONPlaceholder содержатся записи публикаций post которые содержат поле userId указывающее на пользователя. Это позволяет продемонстрировать работу со взаимосвязями в данных.

React-admin знает, как использовать эти внешние ключи для получения ссылок. Давайте проверим, как ListGuesser управляет ими, создав новый <Resource> для конечной точки API /posts:

...
import { Admin, Resource, ListGuesser } from 'react-admin';
...
const App = () => (
  <Admin dataProvider={dataProvider} authProvider={authProvider}>
    <Resource name='posts' list={ListGuesser} />
    ...
</Admin>
);
...

ListGuesser предлагает использовать для поля userId. Давайте поиграем с этим новым полем, создав компонент PostList на основе кода, предложенного гэссером:

// in src/posts.js
import React from 'react';
import { List, Datagrid, TextField, ReferenceField } from 'react-admin';

export const PostList = props => (
  <List {...props}>
    <Datagrid rowClick='edit'>
      <ReferenceField source='userId' reference='users'>
        <TextField source='id' />
      </ReferenceField>
      <TextField source='id' />
      <TextField source='title' />
      <TextField source='body' />
    </Datagrid>
  </List>
);
// in src/App.js
...
import { Admin, Resource } from 'react-admin';
import { PostList } from './posts';
...
<Resource name="posts" list={PostList} />
...

При отображении списка сообщений приложение отображает идентификатор автора сообщения в виде <TextField>. Это поле id не имеет большого значения, вместо этого давайте используем имя пользователя. Для этого в src/posts.js вместо <TextField source="id" /> поставим <TextField source="name" />.

// in src/posts.js
...
<ReferenceField source='userId' reference='users'>
    <TextField source='name' />
</ReferenceField>
...

Список сообщений теперь отображает имена пользователей в каждой строке.

Для справки: самостоятельно компонент <ReferenceField> ничего не отображает. Он просто выбирает связанные данные и передает их в виде  record  своему дочернему компоненту (в нашем случае это <TextField>). Как и компонент <List>, все компоненты <Reference> отвечают только за выборку и подготовку данных и делегируют рендеринг своим дочерним элементам.

Обратите внимание: если вы посмотрите на вкладку network вашего браузера, то заметите, что react-admin объединяет запросы к /user и выполняет один HTTP-запрос для получения всей таблицы данных. Эта оптимизация делает интерфейс более быстрым и отзывчивым.

Чтобы завершить список публикаций, поместите поле идентификатора записи id в качестве первого столбца и удалите поле body. С точки зрения UX поля, большим частям текста не стоит появляться в таблице данных, а только в подробных представлениях. Кроме того, чтобы выделить действие «Правка» (Edit), давайте заменим rowClick кнопкой явного действия:

// in src/posts.js
import React from 'react';
import { List, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin';

export const PostList = props => (
  <List {...props}>
    <Datagrid>
      <TextField source='id' />
      <ReferenceField source='userId' reference='users'>
        <TextField source='name' />
      </ReferenceField>
      <TextField source='title' />
      <EditButton />
    </Datagrid>
  </List>
);

Добавление возможностей создания и редактирования публикацию

Подробно рассмотренному ранее редактированию пользователей, можно создать и функциональность добавления и редактирования публикаций. Для это применим знакомый <EditGuesser>, чтобы получить структуру компонента, а затем на её основе создадим пользовательский компонент и, при необходимости, внесем в него коррективы.

export const PostEdit = props => (
  <Edit {...props}>
    <SimpleForm>
      <TextInput disabled source='id' />
      <ReferenceInput source='userId' reference='users'>
        <SelectInput optionText='name' />
      </ReferenceInput>
      <TextInput source='title' />
      <TextInput multiline source='body' />
    </SimpleForm>
  </Edit>
);

Для реализации функции создания реализуем PostCreate:

export const PostCreate = props => (
  <Create {...props}>
    <SimpleForm>
      <ReferenceInput source='userId' reference='users'>
        <SelectInput optionText='name' />
      </ReferenceInput>
      <TextInput source='title' />
      <TextInput multiline source='body' />
    </SimpleForm>
  </Create>
);

Регистрируем компоненты в App.js и получаем рабочий функционал редактирования и добавления.

import React from 'react';
import { Admin, Resource } from 'react-admin';
import authProvider from './authProvider';
import { PostList, PostEdit, PostCreate } from './posts';
import { UserList, UserEdit, UserCreate } from './users';
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');

const App = () => (
  <Admin dataProvider={dataProvider} authProvider={authProvider}>
    <Resource name='posts' list={PostList} edit={PostEdit} create={PostCreate} />
    <Resource name='users' list={UserList} edit={UserEdit} create={UserCreate} />
  </Admin>
);

export default App;

Страница редактирования имеет небольшую проблему: она использует идентификатор сообщения в качестве основного заголовка (текст, отображаемый в верхней панели). Давайте настроим заголовок представления с помощью компонента title:

// in src/posts.js
...
const PostTitle = ({ record }) => {
  return <span>Post {record ? `"${record.title}"` : ''}</span>;
};

export const PostEdit = props => (
  <Edit title={<PostTitle />} {...props}>
...
  </Edit>
);

На этом этапе мы уже имеем достаточно функциональное приложение, пусть хоть пока и с подключением к фиктивному API. В следующей части публикации рассмотрим добавление поиска и фильтров, кастомизацию меню и домашней страницы, а так же поддержки на мобильных устройствах.

Добавить комментарий

Ваш адрес email не будет опубликован.

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.