Разработка личного кабинета клиента на 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. В следующей части публикации рассмотрим добавление поиска и фильтров, кастомизацию меню и домашней страницы, а так же поддержки на мобильных устройствах.