简体   繁体   中英

How can I show a different sidebar based on user information? After logged in. ReactJS

I have a react js application which authenticates with azure active directory and then it shows this menu:

在此输入图像描述

However I want that after logged in, based on information received from Azure AD Like Roles Or groups, then a different side bar with different options is shown, so I can have the same APP with different groups of users and the menu will depend on the role or group.

This is my app structure:

在此输入图像描述

And here is the relevant files:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';

const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
  ReactDOM.render(<DashApp />, document.getElementById('root'));
  // Hot Module Replacement API
  if (module.hot) {
    module.hot.accept('./dashApp.js', () => {
      const NextApp = require('./dashApp').default;
      ReactDOM.render(<NextApp />, document.getElementById('root'));
    });
  }

},DO_NOT_LOGIN);

registerServiceWorker();

Sidebebar.js

import React, { Component } from "react";
import { connect } from "react-redux";
import clone from "clone";
import { Link } from "react-router-dom";
import { Layout } from "antd";
import options from "./options";
import Scrollbars from "../../components/utility/customScrollBar.js";
import Menu from "../../components/uielements/menu";
import IntlMessages from "../../components/utility/intlMessages";
import SidebarWrapper from "./sidebar.style";
import appActions from "../../redux/app/actions";
import Logo from "../../components/utility/logo";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";

const SubMenu = Menu.SubMenu;
const { Sider } = Layout;

const {
  toggleOpenDrawer,
  changeOpenKeys,
  changeCurrent,
  toggleCollapsed
} = appActions;
const stripTrailingSlash = str => {
  if (str.substr(-1) === "/") {
    return str.substr(0, str.length - 1);
  }
  return str;
};

class Sidebar extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.onOpenChange = this.onOpenChange.bind(this);
  }
  handleClick(e) {
    this.props.changeCurrent([e.key]);
    if (this.props.app.view === "MobileView") {
      setTimeout(() => {
        this.props.toggleCollapsed();
        this.props.toggleOpenDrawer();
      }, 100);
    }
  }
  onOpenChange(newOpenKeys) {
    const { app, changeOpenKeys } = this.props;
    const latestOpenKey = newOpenKeys.find(
      key => !(app.openKeys.indexOf(key) > -1)
    );
    const latestCloseKey = app.openKeys.find(
      key => !(newOpenKeys.indexOf(key) > -1)
    );
    let nextOpenKeys = [];
    if (latestOpenKey) {
      nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
    }
    if (latestCloseKey) {
      nextOpenKeys = this.getAncestorKeys(latestCloseKey);
    }
    changeOpenKeys(nextOpenKeys);
  }
  getAncestorKeys = key => {
    const map = {
      sub3: ["sub2"]
    };
    return map[key] || [];
  };
  getMenuItem = ({ singleOption, submenuStyle, submenuColor }) => {
    const { key, label, leftIcon, children } = singleOption;
    const url = stripTrailingSlash(this.props.url);
    if (children) {
      return (
        <SubMenu
          key={key}
          title={
            <span className="isoMenuHolder" style={submenuColor}>
              <i className={leftIcon} />
              <span className="nav-text">
                <IntlMessages id={label} />
              </span>
            </span>
          }
        >
          {children.map(child => {
            const linkTo = child.withoutDashboard
              ? `/${child.key}`
              : `${url}/${child.key}`;
            return (
              <Menu.Item style={submenuStyle} key={child.key}>
                <Link style={submenuColor} to={linkTo}>
                  <IntlMessages id={child.label} />
                </Link>
              </Menu.Item>
            );
          })}
        </SubMenu>
      );
    }
    return (
      <Menu.Item key={key}>
        <Link to={`${url}/${key}`}>
          <span className="isoMenuHolder" style={submenuColor}>
            <i className={leftIcon} />
            <span className="nav-text">
              <IntlMessages id={label} />
            </span>
          </span>
        </Link>
      </Menu.Item>
    );
  };
  render() {
    const { app, toggleOpenDrawer, height } = this.props;
    const collapsed = clone(app.collapsed) && !clone(app.openDrawer);
    const { openDrawer } = app;
    const mode = collapsed === true ? "vertical" : "inline";
    const onMouseEnter = event => {
      if (openDrawer === false) {
        toggleOpenDrawer();
      }
      return;
    };
    const onMouseLeave = () => {
      if (openDrawer === true) {
        toggleOpenDrawer();
      }
      return;
    };
    const customizedTheme = themes[themeConfig.theme];
    const styling = {
      backgroundColor: customizedTheme.backgroundColor
    };
    const submenuStyle = {
      backgroundColor: "rgba(0,0,0,0.3)",
      color: customizedTheme.textColor
    };
    const submenuColor = {
      color: customizedTheme.textColor
    };
    return (
      <SidebarWrapper>
        <Sider
          trigger={null}
          collapsible={true}
          collapsed={collapsed}
          width="240"
          className="isomorphicSidebar"
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
          style={styling}
        >
          <Logo collapsed={collapsed} />
          <Scrollbars style={{ height: height - 70 }}>
            <Menu
              onClick={this.handleClick}
              theme="dark"
              className="isoDashboardMenu"
              mode={mode}
              openKeys={collapsed ? [] : app.openKeys}
              selectedKeys={app.current}
              onOpenChange={this.onOpenChange}
            >
              {options.map(singleOption =>
                this.getMenuItem({ submenuStyle, submenuColor, singleOption })
              )}
            </Menu>
          </Scrollbars>
        </Sider>
      </SidebarWrapper>
    );
  }
}

export default connect(
  state => ({
    app: state.App.toJS(),
    height: state.App.toJS().height
  }),
  { toggleOpenDrawer, changeOpenKeys, changeCurrent, toggleCollapsed }
)(Sidebar);

dashapp.js

import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
  getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";

const currentAppLocale =
  AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];


const DashApp = () => (
  <LocaleProvider locale={currentAppLocale.antd}>
    <IntlProvider
      locale={currentAppLocale.locale}
      messages={currentAppLocale.messages}
    >
      <ThemeProvider theme={themes[themeConfig.theme]}>
        <DashAppHolder>
          <Provider store={store}>
            <PublicRoutes history={history} />
          </Provider>
        </DashAppHolder>
      </ThemeProvider>
    </IntlProvider>
  </LocaleProvider>
);
Boot()
  .then(() => DashApp())
  .catch(error => console.error(error));

export default DashApp;
export { AppLocale };

Question:

How do I modify this code to render a different sidebar depending on the logged in user?

The first step highly relies of how your authentication is configured. Once you logged in with azure you will want to store the retrieved user profile inside your redux data storage.

If you are using react redux with adal, you should have a login action dispatched. On success of this action, inside your login reducer, pick what you need from the information retrieved.

The following code is only a sample of what you could be left with after implementing such a reducer. AD will certainly give you a group ID instead of the 'admin' role shown in this example, simply modify the check accordingly. You may have to set groupMembershipClaims to "SecurityGroup" or "All" in your app's manifest in AAD in order to add this piece of information inside the authentication response.

case Action.LOGIN_SUCCESS:
  return {
    ...state,
    username: action.username,
    isAdmin: action.role === 'admin'
  }

You may need to customize what informations AD sends you from the AD dashboard.

What's left is trivial :

  • Connect the store to the components where user permissions are needed

     connect(state => ({ user: state.loginReducer, })) 
  • Customize the render conditionally inside your component

     render() { const { user } = this.props; return ( <div className={classNames.navbar}> { user.isAdmin && <Link to="adminpanel" label="Admin Panel" /> } <Link to="about" label="About" /> </div> ) } 

By the way, you should set the locale inside the redux storage aswell if you need to handle dynamic language switching.

In the presented code I did not found a way to figure out if user is authenticated or not. I think that's the main point here.

The easiest way to do that, is to check for a cookie. That of course depends on how your authentication is working. You need to have a function that will check for auth cookie. Something like this. I'm using universal-cookie packages, seems like your app is isomorphic, so you need to pass ssrCookie on server, you will get that from res.headers.cookies . If the app is client-side only you can drop ssrCookie :

 import Cookies from 'universal-cookie'

 const authCookieName = 'awesome_app__token'

 function getCookies(isServer, ssrCookie) {
   if (isServer) {
     return new Cookies(ssrCookie || '')
   } else {
     return new Cookies()
   }
 }

 export function isLoggedIn(isServer, ssrCookie) {
   return !!getToken(getCookies(isServer, ssrCookie), ssrCookie)
 }

 function getToken(isServer, ssrCookie) {
   return getCookies(isServer, ssrCookie).get(authCookieName)
 }

So, now, in you react code you check if user is logged in:

import { isLoggedIn } from './thePathToFileWhereThisFunctionsWasSet'

class Sidebar extends Component {
   render() {
     return (
       {isLoggedIn() ? (<div>For logged in user</div>) : (<div>For not logged in users</div>)}
     )
   }
}

After digging a bit in react-adal code, I saw that the logged state is not passed through the children's context. so as far as I see, the only way to get the current user is :

import { authContext } from './adalConfig';
authContext.getCachedUser();

This only after you have wrapped your main component with runWithAdal as you did.

With that you may create a new component SidebarOptions which will decide upon the prop user ( user = authContext.getCachedUser(); ) which options to render.

Another option is to create a PR to react-adal and make sure to pass these values through the wrapped component or all the children s components (using react-context`)

Also, as I can see you are using redux, so a it's better to save the user info with dispatching an action upon first rendered component with the authContext.getCachedUser() data and set it on your store for easy access across the app. a good place to do this is in the success callback of withAdalLoginApi .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM