Search code examples
reactjsreact-admin

React Admin render resources based on permissions


I'm building an admin interface with react admin on top of firestore.

Everything works great with the firestore dataProvider, but now I would like to handle permissions and, based on permissions, show a list of experts.

To achieve this I decided to implement a custom Admin with AdminContext and AdminUI.

The expected behaviour of my current implementation: if I'm not logged in, I'm first redirected to the login page and then based on permissions (usePermission), I decide what to render.

The real behaviour: AsyncResources gets the render even if not logged in and it will crash on the permissions check because is null.

function App() {
  return (
    <AdminContext authProvider={authProvider} dataProvider={dataProvider}>
      <AsyncResources />
    </AdminContext>
  );
}

Resources based on auth user permissions

function AsyncResources() {
  const [emails, setEmails] = useState<string[]>([]);
  const { loading, permissions } = usePermissions();

  useEffect(() => {
    (async () => {
      const experts = await firebase.firestore().collection("experts").get();
      const docIds: string[] = experts.docs.map((doc) => doc.id);
      setEmails(docIds);
    })();
  }, []);

  if (loading) {
    return (
      <AdminUI layout={CustomLayout} theme={theme}>
        <Loading
          loadingPrimary="WePractice Admin UI"
          loadingSecondary="Loading..."
        />
      </AdminUI>
    );
  }

  if (permissions.admin) {
    return (
      <AdminUI layout={CustomLayout} theme={theme}>
        {emails.map((email) => (
          <Resource
            key={email}
            options={{ label: `${email}` }}
            name={`experts/${email}/requests`}
            list={RequestList}
            edit={RequestEdit}
          />
        ))}
        <Resource name={`experts`} list={ExpertList} />
      </AdminUI>
    );
  }

  return (
    <AdminUI layout={CustomLayout} theme={theme}>
      <Resource
        options={{ label: `${permissions.email}` }}
        name={`experts/${permissions.email}/requests`}
        list={RequestList}
        edit={RequestEdit}
      />
    </AdminUI>
  );
}

A solution to fix this is probably to to the following:

    <AdminContext authProvider={authProvider} dataProvider={dataProvider}>
        <AdminUI layout={CustomLayout} theme={theme}>
            <AsyncResources />
        </AdminUI>
    </AdminContext>

In this case seems the AdminUI to do the authentication check and redirect to login.

The problem with this solution is that Resources cannot be wrapped by div, fragments or other components (I return an array), so in this case anything would be rendered.

Anyone has a solution to this?

Thanks


Solution

  • I solved my problem by moving the permissions logic at the upper level and using the Admin component.

    New implementation

    function App() {
      const [emails, setEmails] = useState<string[]>([]);
      useEffect(() => {
        (async () => {
          const experts = await firebase.firestore().collection("experts").get();
          const docIds: string[] = experts.docs.map((doc) => doc.id);
    
          setEmails(docIds);
        })();
      }, []);
    
      return (
        <Admin
          catchAll={NotFound}
          authProvider={authProvider}
          dataProvider={dataProvider}
          layout={CustomLayout}
          theme={theme}
        >
          {(props) => {
            if (props.admin) {
              return [
                emails.map((email) => (
                  <Resource
                    key={email}
                    options={{ label: `${email}` }}
                    name={`experts/${email}/requests`}
                    list={RequestList}
                    edit={RequestEdit}
                  />
                )),
                <Resource name={`experts`} list={ExpertList} />,
              ];
            }
            return [
              <Resource
                options={{ label: `${props.email}` }}
                name={`experts/${props.email}/requests`}
                list={RequestList}
                edit={RequestEdit}
              />,
            ];
          }}
        </Admin>
      );
    }