DEV Community

Cover image for Admiral. Roles and access division in the menu
Max Bantsevich for dev.family

Posted on

Admiral. Roles and access division in the menu

Hi everyone, just a reminder that we are developing Admiral - an open source solution on React, which allows you to quickly develop beautiful CRUDs in admin panels, but create fully custom interfaces if you want. From developers for developers with love, as the saying goes.

admiral interface

Our project is available here - https://github.com/dev-family/admiral

Today we want to share small tricks on how to create a menu in the control panel that will be generated depending on the user's role. This is quite a popular use case.

alerts

Static Menu

From the documentation you can see that out of the box comes an example of building a static menu. Nothing unusual - a menu component with Item (menu items) and SubItem (menu sub-items) with a title, a link to go to and an icon inside it.

import { Menu, SubMenu, MenuItemLink } from '../../admiral'

const CustomMenu = () => {
    return (
        <Menu>
            <MenuItemLink icon="FiCircle" name="First Menu Item" to="/first" />
            <SubMenu icon="FiCircle" name="Second Menu Item">
                <MenuItemLink icon="FiCircle" name="Sub Menu Item" to="/second" />
            </SubMenu>
        </Menu>
    )
}

export default CustomMenu
Enter fullscreen mode Exit fullscreen mode

What if we want to manage this menu? Separate the menu by role? Make it available to guests? That's where creating a Dynamic Menu can help.

Dynamic Menu

In order to convert the menu from static to dynamic, we have to get it from somewhere. Let's turn to the documentation, more precisely to the section on interacting with API. Let's use the getIdentity method, which gives information about the authorized user, in this case each user will receive the menu available for him along with information about him. Let's go to the file menu.tsx.

Let's first define the basic structure of the menu.

interface ISubMenuItemChildren {  
    name: string  
    to: string  
    badge?: number  
    icon?: keyof typeof Icons  
}  

interface ISubMenuItem {  
    to?: string  
    name: string  
    badge?: number  
    icon?: keyof typeof Icons  
    children?: Array<ISubMenuItemChildren>  
}
Enter fullscreen mode Exit fullscreen mode

The fields from the name are self-explanatory, but let's break it down just in case:

  • name - name of the menu item
  • to - jump link
  • badge - optional field that will display a number (total number of records in the menu item, it depends on what number you want to display).
  • icon - icon
  • children - array with sub-items

In the end, we plan to get such data using the getIdentity method:

name: "Dev Family",
user: {id: 4, name: "Dev Family", email: "info@dev.family",…},
email: "info@dev.family",
id: 4,
menu: [
    {name: "Users", icon: "FiUsers", badge: null, to: "/users"},
    {name: "Channels", icon: "FiPlayCircle", badge: null, to: "/channels"}
],
Enter fullscreen mode Exit fullscreen mode

Let's try to parse the array of menu objects in the menu.tsx file, the resulting file looks like this:

import React, { useCallback, useEffect, useState } from 'react'  
import { Menu, MenuItemLink, SubMenu, useGetIdentity } from '@devfamily/admiral'  
import * as Icons from 'react-icons/fi'  

interface ISubMenuItemChildren {  
    name: string  
    to: string  
    badge?: number  
    icon?: keyof typeof Icons  
}  

interface ISubMenuItem {  
    to?: string  
    name: string  
    badge?: number  
    icon?: keyof typeof Icons  
    children?: Array<ISubMenuItemChildren>  
}  

type SubMenuItemsType = Array<ISubMenuItem>  

const CustomMenu = () => {  
    const { identity } = useGetIdentity()  
    const [filteredSubMenuItems, setFilteredSubMenuItems] = useState<SubMenuItemsType>([])  

    useEffect(() => {  
        if (identity?.menu && Array.isArray(identity?.menu)) {  
            setFilteredSubMenuItems(identity?.menu)  
        }  
    }, [identity?.menu])  

    const renderSubMenuItems = useCallback(  
        function () {  
            return (  
                <>  
                    {filteredSubMenuItems.map(function (submenu, i) {  
                        if (submenu.to) {  
                            return (  
                                <MenuItemLink  
                                    key={`${i}`}  
                                    icon={submenu.icon}  
                                    name={submenu.name}  
                                    to={submenu.to}  
                                    badge={{ count: submenu.badge, status: 'error' }}  
                                />  
                            )  
                        }  

                        if (submenu.children) {  
                            return (  
                                <SubMenu  
                                    key={i}  
                                    icon={submenu.icon}  
                                    name={submenu.name}  
                                    badge={{ count: submenu.badge, status: 'error' }}  
                                >  
                                    {submenu.children.map(function (contentItem, j) {  
                                        return (  
                                            <MenuItemLink  
                                                badge={{ count: contentItem.badge, status: 'warning' }}  
                                                key={`${i}_${j}`}  
                                                icon={contentItem.icon}  
                                                name={contentItem.name}  
                                                to={contentItem.to}  
                                            />  
                                        )  
                                    })}  
                                </SubMenu>  
                            )  
                        }  
                    })}  
                </>  
            )  
        },  
        [filteredSubMenuItems],  
    )  


    return <Menu>{renderSubMenuItems()}</Menu>  
}  

export default CustomMenu
Enter fullscreen mode Exit fullscreen mode

Now our menu is fetched from the API method getIdentity and generated dynamically. If you have other use cases we can break down for you, share ideas in the comments!

dev.family team

Top comments (0)