dopry/netlify-cms

View on GitHub
src/containers/App.js

Summary

Maintainability
C
7 hrs
Test Coverage
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { IndexLink } from "react-router";
import FontIcon from 'react-toolbox/lib/font_icon';
import { Layout, Panel } from 'react-toolbox/lib/layout';
import { Navigation } from 'react-toolbox/lib/navigation';
import { Notifs } from 'redux-notifications';
import TopBarProgress from 'react-topbar-progress-indicator';
import Sidebar from './Sidebar';
import { loadConfig as actionLoadConfig } from '../actions/config';
import { loginUser as actionLoginUser, logoutUser as actionLogoutUser } from '../actions/auth';
import { toggleSidebar as actionToggleSidebar } from '../actions/globalUI';
import { currentBackend } from '../backends/backend';
import {
  runCommand as actionRunCommand,
  navigateToCollection as actionNavigateToCollection,
  createNewEntryInCollection as actionCreateNewEntryInCollection,
} from '../actions/findbar';
import AppHeader from '../components/AppHeader/AppHeader';
import { Loader, Toast } from '../components/UI/index';
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
import { SIMPLE, EDITORIAL_WORKFLOW } from '../constants/publishModes';
import styles from './App.css';
import sidebarStyles from './Sidebar.css';

TopBarProgress.config({
  barColors: {
    "0": '#3ab7a5',
    '1.0': '#3ab7a5',
  },
  shadowBlur: 5,
  shadowColor: '#3ab7a5',
  barThickness: 2,
});

class App extends React.Component {

  static propTypes = {
    auth: ImmutablePropTypes.map,
    children: PropTypes.node,
    config: ImmutablePropTypes.map,
    collections: ImmutablePropTypes.orderedMap,
    createNewEntryInCollection: PropTypes.func.isRequired,
    logoutUser: PropTypes.func.isRequired,
    dispatch: PropTypes.func.isRequired,
    toggleSidebar: PropTypes.func.isRequired,
    navigateToCollection: PropTypes.func.isRequired,
    user: ImmutablePropTypes.map,
    runCommand: PropTypes.func.isRequired,
    isFetching: PropTypes.bool.isRequired,
    publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
    siteId: PropTypes.string,
  };

  static configError(config) {
    return (<div>
      <h1>Error loading the CMS configuration</h1>

      <div>
        <p>The <code>config.yml</code> file could not be loaded or failed to parse properly.</p>
        <p><strong>Error message:</strong> {config.get('error')}</p>
      </div>
    </div>);
  }

  componentDidMount() {
    this.props.dispatch(actionLoadConfig());
  }

  handleLogin(credentials) {
    this.props.dispatch(actionLoginUser(credentials));
  }

  authenticating() {
    const { auth } = this.props;
    const backend = currentBackend(this.props.config);

    if (backend == null) {
      return <div><h1>Waiting for backend...</h1></div>;
    }

    return (
      <div>
        {
          React.createElement(backend.authComponent(), {
            onLogin: this.handleLogin.bind(this),
            error: auth && auth.get('error'),
            isFetching: auth && auth.get('isFetching'),
            siteId: this.props.config.getIn(["backend", "site_domain"]),
            base_url: this.props.config.getIn(["backend", "base_url"], null)
          })
        }
      </div>
    );
  }

  handleLinkClick(event, handler, ...args) {
    event.preventDefault();
    handler(...args);
  }

  render() {
    const {
      user,
      config,
      children,
      collections,
      toggleSidebar,
      runCommand,
      navigateToCollection,
      createNewEntryInCollection,
      logoutUser,
      isFetching,
      publishMode,
    } = this.props;


    if (config === null) {
      return null;
    }

    if (config.get('error')) {
      return App.configError(config);
    }

    if (config.get('isFetching')) {
      return <Loader active>Loading configuration...</Loader>;
    }

    if (user == null) {
      return this.authenticating();
    }

    const sidebarContent = (
      <div>
        <Navigation type="vertical" className={sidebarStyles.nav}>
          {
            publishMode === SIMPLE ? null :
            <section>
              <h1 className={sidebarStyles.heading}>Publishing</h1>
              <div className={sidebarStyles.linkWrapper}>
                <IndexLink to="/" className={sidebarStyles.viewEntriesLink}>Editorial Workflow</IndexLink>
              </div>
            </section>
          }
          <section>
            <h1 className={sidebarStyles.heading}>Collections</h1>
            {
              collections.valueSeq().map((collection) => {
                const collectionName = collection.get('name');
                return (
                  <div key={collectionName} className={sidebarStyles.linkWrapper}>
                    <a
                      href={getCollectionUrl(collectionName, true)}
                      className={sidebarStyles.viewEntriesLink}
                      onClick={e => this.handleLinkClick(e, navigateToCollection, collectionName)}
                    >
                      {collection.get('label')}
                    </a>
                    {
                      collection.get('create') ? (
                        <a
                          href={getNewEntryUrl(collectionName, true)}
                          className={sidebarStyles.createEntryLink}
                          onClick={e => this.handleLinkClick(e, createNewEntryInCollection, collectionName)}
                        >
                          <FontIcon value="add_circle_outline" />
                        </a>
                      ) : null
                    }
                  </div>
                );
              })
            }
          </section>
        </Navigation>
      </div>
    );

    return (
      <Sidebar content={sidebarContent}>
        <Layout>
          <Notifs CustomComponent={Toast} />
          <AppHeader
            user={user}
            collections={collections}
            runCommand={runCommand}
            onCreateEntryClick={createNewEntryInCollection}
            onLogoutClick={logoutUser}
            toggleDrawer={toggleSidebar}
          />
          <Panel scrollY className={styles.entriesPanel}>
            { isFetching && <TopBarProgress /> }
            <div>
              {children}
            </div>
          </Panel>

        </Layout>
      </Sidebar>
    );
  }
}

function mapStateToProps(state) {
  const { auth, config, collections, globalUI } = state;
  const user = auth && auth.get('user');
  const isFetching = globalUI.get('isFetching');
  const publishMode = config && config.get('publish_mode');
  return { auth, config, collections, user, isFetching, publishMode };
}

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    toggleSidebar: () => dispatch(actionToggleSidebar()),
    runCommand: (type, payload) => {
      dispatch(actionRunCommand(type, payload));
    },
    navigateToCollection: (collection) => {
      dispatch(actionNavigateToCollection(collection));
    },
    createNewEntryInCollection: (collectionName) => {
      dispatch(actionCreateNewEntryInCollection(collectionName));
    },
    logoutUser: () => {
      dispatch(actionLogoutUser());
    },
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(App);