# Chartbrew Frontend

# Structure

|-- .eslintrc.json
|-- package.json
|-- semantic.json                   # semantic build settings
|-- config                          # Webpack, jest, etc config files
|-- public                          # Contains the entry index.html file
|-- scripts                         # React scripts (build, run, etc)
|-- src
    |-- actions                     # Redux actions
    |-- assets                      # Any assets (images and stuff)
    |-- components                  # React (dumb) components that should be re-usable
    |-- config                      # various config files with globals, colors, misc functions
    |-- containers                  # React smart components (usually pages that contain multiple components)
    |-- reducers                    # Redux reducers
    |-- semantic                    # semantic files
        |-- dist                    # The distribution generated by the semantic build
        |-- src                     # Currently only the globals are tracked by Git - this sets certain styles for the semantic distribution
        |   |-- site
        |   |   |-- globals
        |   |       |-- reset.overrides
        |   |       |-- reset.variables
        |   |       |-- site.overrides
        |   |       |-- site.variables

# How this works

If you never used React Redux (opens new window) before, it's strongly recommended to run a quick-start (opens new window) before attempting to modify anything in the front-end.

The main point on future developments will be to always develop with the props down, events up (opens new window) mentality. In Chartbrew this means that a container should send the props to a component and the component can call any events that were passed down by the container. The events will run in the parent component.

The new components should be functional and use React Hooks. (opens new window)

WARNING

Currently, the containers are quite huge and some components are not as dumb as they should be. This will be improved with future updates.

# Example

The following example will create a dummy container with a component, reducer and action.

# Actions

The actions are placed in the actions/ folder.

// actions/brew.js

import { API_HOST } from "../config/settings";

export const FETCHING_BREW = "FETCHING_BREW"; // this is the type of action, used for identification
export const FETCHED_BREW = "FETCHED_BREW";

export function getBrew(id) {
  return (dispatch) => { // dispatch is used to send the action to the reducer
    const url = `${API_HOST}/brew/${id}`;
    const method = "GET";
    const headers = new Headers({
      "Accept": "application/json",
    });

    dispatch({ type: FETCHING_BREW });
    return fetch(url, { method, headers }) // like in the backend code, Promises are preferred
      .then((response) => {
        if (!response.ok) {
          return new Promise((resolve, reject) => reject(response.statusText));
        }

        return response.json();
      })
      .then((brew) => {
        dispatch({ type: FETCHED_BREW, brew }); // dispatching the action together with the payload
        return new Promise((resolve) => resolve(brew));
      })
      .catch((error) => {
        return new Promise((resolve, reject) => reject(error));
      });
  };
}

# Reducer

The reducers are placed in the reducers/ folder and registered in the index file there.

// reducers/brew.js

import {
  FETCHING_BREW,
  FETCHED_BREW,
} from "../actions/brew";

export default function brew((state) = {
  loading: false,
  data: {},
}, action) {
  switch (action.type) {
    case FETCHING_BREW:
      return { ...state, loading: true };
    case FETCHED_BREW:
      // remember the "brew" payload dispatched from the action
      return { ...state, loading: false, data: action.brew };
    default:
      return state;
  }
}

Don't forget to register this reducer in the index.js file in the same folder.

# Component

This will be an example component to show the name and flavour of the brew and a simple button with an event attached.

// components/BrewCard.js

import React from "react";
import PropTypes from "prop-types";
import { Header } from "semantic-ui-react";

function BrewCard(props) {
  const { brew, mixBrew } = props;

  return (
    <div style={styles.container}>
      <Header>{{brew.name}}</Header>
      <p>{{brew.flavour}}</p>
      <Button primary onClick={mixBrew}>
        Mix the Brew
      </Button>
    </div>
  );
}

const styles = {
  container: {
    flex: 1,
  },
};

BrewCard.defaultProps = {
  mixBrew: () => {},
};

BrewCard.propTypes = {
  brew: PropTypes.object.isRequired,
  mixBrew: PropTypes.func,
};

export default BrewCard;

# Container

This will be a page showing the BrewCard component after getting calling the getBrew action.

import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
  Segment, Header, Dimmer, Loader,
} from "semantic-ui-react";

import { getBrew as getBrewAction } from "../actions/brew";

function BrewPage(props) {
  const { getBrew, brew, loading } = props;
  const [mixed, setMixed] = useState(false);

  useEffect(() => {
    getBrew(1);
  }, []);

  // container functionality should be prepended with a '_'
  const _onMixBrew = () => {
    setMixed(true);
  };

  if (loading) {
    return (
      <div style={styles.container}>
        <Dimmer active>
          <Loader />
        </Dimmer>
      </div>
    );
  }

  return (
    <div style={styles.container}>
      <Header attached="top" as="h2">The Brew</Header>
      <Segment raised attached>
        <BrewCard brew={brew} mixBrew={_onMixBrew} />

        {mixed && (
          <Message positive>
            <p>The brew is mixed</p>
          </Message>
        )}
      </Segment>
    </div>
  );
}

const styles = {
  container: {
    flex: 1,
  },
};

BrewPage.propTypes = {
  getBrew: PropTypes.func.isRequired,
  brew: PropTypes.object.isRequired,
};

const mapStateToProps = (state) => {
  return {
    brew: state.brew.data,
    loading: state.brew.loading,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    getBrew: (id) => dispatch(getBrewAction(id)),
  };
};

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

# Styling

Chartbrew is using Fomantic UI as the CSS framework. This is a community maintained version of Semantic UI which is not really maintained anymore.

The react components usable within Chartbrew can be found here. (opens new window)

For any changes to the general feel of the site you can modify Sematic's global variables in src/semantic/src/site/globals. You can check out the theming docs on Fomantic's website (opens new window).