Skip to content

Instantly share code, notes, and snippets.

@crutch12
Created May 13, 2022 13:42
Show Gist options
  • Save crutch12/088fbbe99f45919543e0d6cc2d7dc757 to your computer and use it in GitHub Desktop.
Save crutch12/088fbbe99f45919543e0d6cc2d7dc757 to your computer and use it in GitHub Desktop.
Setup React Casl ability (src/components/ability)
import React from 'react';
import { defineAbilitiesFor } from '~/lib/ability';
export const AbilityContext = React.createContext(defineAbilitiesFor(null));
import { useNotifications } from '@d11t/ui';
import { useRootAppContext } from '@vtb-curs/ui-config';
import React from 'react';
import useSWR from 'swr';
import { AbilityContext } from './AbilityContext';
import { useServices } from '~/components/services-context';
import { PageLoading } from '~/components/widgets';
import { defineAbilitiesFor } from '~/lib/ability';
import { useAppDispatch, useAppSelector } from '~/store/hooks';
import { setSession } from '~/store/slices/session';
import { Session } from '~/types/auth';
const AbilityProvider: React.FC = ({ children }) => {
const { addNotification } = useNotifications();
const dispatch = useAppDispatch();
const { user: providedUser, setUser } = useRootAppContext();
const { roleModelService } = useServices();
const sessionUser = useAppSelector((state) => state.session.session?.userInfo);
const getUser = React.useCallback(
async (_key: string, user: typeof providedUser) => {
const userInfo = user ?? (await roleModelService.getUserInfo());
if (!user) {
setUser(userInfo);
}
const newSession: Session = { userInfo };
dispatch(setSession(newSession));
return userInfo;
},
[roleModelService, dispatch, setUser],
);
const onError = React.useCallback(() => {
addNotification({
icon: 'attention',
title: 'Не удалось загрузить информацию о пользователе',
});
}, [addNotification]);
// @NOTE: Скипаем запрос, если session уже лежит в store
const { data: userInfo, isValidating } = useSWR(sessionUser ? undefined : ['/getUser', providedUser], getUser, {
onError,
fallbackData: providedUser,
});
const ability = React.useMemo(() => defineAbilitiesFor(userInfo ?? null), [userInfo]);
if (isValidating) {
return <PageLoading label="Загрузка данных о пользователе" style={{ padding: '20px 32px' }} />;
}
return <AbilityContext.Provider value={ability}>{children}</AbilityContext.Provider>;
};
export default AbilityProvider;
import { createContextualCan } from '@casl/react';
import { AbilityContext } from './AbilityContext';
export const CanI = createContextualCan(AbilityContext.Consumer);
import React from 'react';
import { AbilityContext } from './AbilityContext';
export function useCan() {
return React.useContext(AbilityContext);
}
export { AbilityContext } from './AbilityContext';
export { default as AbilityProvider } from './AbilityProvider';
export { CanI } from './CanI';
export { useCan } from './hooks';
export { default as withCan } from './withCan';
import React from 'react';
import { useCan } from './hooks';
import PageError from '~/components/PageError';
import { Actions, Subjects } from '~/lib/ability';
const withCan =
<P extends object>(Component: React.ComponentType<P>, action: Actions | Actions[], subject: Subjects): React.FC<P> =>
// eslint-disable-next-line react/display-name
({ ...props }: P) => {
const { can } = useCan();
const actions = Array.isArray(action) ? action : [action];
const allowed = actions.some((_action) => can(_action, subject));
if (allowed) {
return <Component {...(props as P)} />;
}
return <PageError statusCode={403} message={'Недостаточно прав'} />;
};
export default withCan;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment