Загрузка данных
import { Provider } from 'react-redux';
import { Navigate, Route, Routes } from 'react-router-dom';
import {
AccessProvider,
Can,
Permissions,
ProtectedRoute,
useAccess,
} from '@/shared/permissions';
import { store } from '@/shared/store';
import { useGetEquipmentQuery } from '@/shared/api/equipment/client';
type LocationRow = {
id: string;
name: string;
};
const locationRows: LocationRow[] = [
{
id: '1',
name: 'Переговорная 1',
},
{
id: '2',
name: 'Переговорная 2',
},
];
/**
* Пример подключения AccessProvider.
*
* Важно:
* AccessProvider должен быть внутри Redux Provider,
* потому что он берет роли пользователя из store.
*/
export const ExampleAppProviders = () => {
return (
<Provider store={store}>
<AccessProvider>
<ExampleApp />
</AccessProvider>
</Provider>
);
};
/**
* Пример защиты страниц через ProtectedRoute.
*/
const ExampleApp = () => {
return (
<Routes>
<Route path="/" element={<ExamplePage />} />
<Route
path="/audit"
element={
<ProtectedRoute permissions={Permissions.auditRead}>
<AuditPage />
</ProtectedRoute>
}
/>
<Route
path="/settings"
element={
<ProtectedRoute
permissions={[
Permissions.controllerSettingsRead,
Permissions.deviceSettingsRead,
Permissions.padletSettingsRead,
]}
checkMode="some"
>
<SettingsPage />
</ProtectedRoute>
}
/>
<Route path="/forbidden" element={<ForbiddenPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
};
const ExamplePage = () => {
return (
<div>
<h1>Примеры использования permissions</h1>
<OnePermissionExample />
<EveryPermissionsExample />
<SomePermissionsExample />
<FallbackExample />
<TableExample />
<RtkQuerySkipExample />
</div>
);
};
/**
* Пример 1.
*
* Проверка одного permission.
* Кнопка будет отображена только если у пользователя есть право location:create.
*/
const OnePermissionExample = () => {
return (
<Can permissions={Permissions.locationCreate}>
<button onClick={handleCreateLocation}>
Добавить локацию
</button>
</Can>
);
};
/**
* Пример 2.
*
* Проверка нескольких permissions.
* По умолчанию checkMode = "every", то есть нужны все permissions.
*/
const EveryPermissionsExample = () => {
return (
<Can
permissions={[
Permissions.locationRead,
Permissions.locationUpdate,
]}
>
<button onClick={handleUpdateLocation}>
Редактировать локацию
</button>
</Can>
);
};
/**
* Пример 3.
*
* Проверка нескольких permissions.
* checkMode = "some" означает, что достаточно хотя бы одного permission.
*/
const SomePermissionsExample = () => {
return (
<Can
permissions={[
Permissions.locationUpdate,
Permissions.locationDelete,
]}
checkMode="some"
>
<div>
<button onClick={handleUpdateLocation}>
Редактировать
</button>
<button onClick={handleDeleteLocation}>
Удалить
</button>
</div>
</Can>
);
};
/**
* Пример 4.
*
* fallback показывается, если доступа нет.
*/
const FallbackExample = () => {
return (
<Can
permissions={Permissions.equipmentTokensCreate}
fallback={<span>Нет доступа к созданию токена</span>}
>
<button onClick={handleCreateEquipmentToken}>
Создать токен
</button>
</Can>
);
};
/**
* Пример 5.
*
* Для таблиц лучше не оборачивать каждую кнопку в Can.
* Иначе на больших списках будет много лишних компонентов.
*
* Лучше один раз получить canUpdateLocation / canDeleteLocation
* и использовать обычные условия.
*/
const TableExample = () => {
const { can } = useAccess();
const canUpdateLocation = can(Permissions.locationUpdate);
const canDeleteLocation = can(Permissions.locationDelete);
const hasActions = canUpdateLocation || canDeleteLocation;
return (
<table>
<thead>
<tr>
<th>Название</th>
{hasActions && <th>Действия</th>}
</tr>
</thead>
<tbody>
{locationRows.map((location) => {
return (
<tr key={location.id}>
<td>{location.name}</td>
{hasActions && (
<td>
{canUpdateLocation && (
<button onClick={() => handleUpdateLocationById(location.id)}>
Редактировать
</button>
)}
{canDeleteLocation && (
<button onClick={() => handleDeleteLocationById(location.id)}>
Удалить
</button>
)}
</td>
)}
</tr>
);
})}
</tbody>
</table>
);
};
/**
* Пример 6.
*
* RTK Query запрос лучше не дергать, если у пользователя нет READ-доступа.
*/
const RtkQuerySkipExample = () => {
const { can } = useAccess();
const canReadEquipment = can(Permissions.equipmentRead);
const {
data,
isLoading,
isError,
} = useGetEquipmentQuery(undefined, {
skip: !canReadEquipment,
});
if (!canReadEquipment) {
return <div>Нет доступа к оборудованию</div>;
}
if (isLoading) {
return <div>Загрузка оборудования...</div>;
}
if (isError) {
return <div>Ошибка загрузки оборудования</div>;
}
return (
<div>
<h2>Оборудование</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
const AuditPage = () => {
return <div>Страница аудита</div>;
};
const SettingsPage = () => {
return <div>Страница настроек</div>;
};
const ForbiddenPage = () => {
return <div>Нет доступа</div>;
};
const handleCreateLocation = () => {
console.log('create location');
};
const handleUpdateLocation = () => {
console.log('update location');
};
const handleDeleteLocation = () => {
console.log('delete location');
};
const handleCreateEquipmentToken = () => {
console.log('create equipment token');
};
const handleUpdateLocationById = (locationId: string) => {
console.log('update location', locationId);
};
const handleDeleteLocationById = (locationId: string) => {
console.log('delete location', locationId);
};