The Problem:
You are in the kitchen sitting next to Alyssa, our friendly kitchen expediter. Her role is to take packed orders and make sure they get picked up by the right delivery courier. She’s currently got 3 screens she’s using to see all the information she needs to do her job. Your job is to help make her life easier!
Using React, create a simple Node.js prototype app that serves a single page with cards representing orders. The card should have:
- The order number (like ‘0001’, ‘0002’, etc)
- The name of the customer
- The address of the customer
- The name of the courier
- The pickup ETA
The card should also have a button to mark it as “picked up”, at which point it should exit the screen in a graceful way. Cards should be autogenerated every 15 seconds and appear on the page. Generate them however you’d like!
There should be a “Help!” button at the top of the screen that can be pressed to bring up a confirmation modal that will say, “Are you sure you want to close the kitchen?”, at which point orders will no longer appear automatically.
How you setup your project is completely up to you, though we recommend using gulp to preprocess your React JSX files to include on the page.
Specifically we are looking for:
- Easy to read code
- Good modularization of different UI elements
- Knowledge of React and (minor) knowledge of Node.js
Good luck and let us know if you have any questions!
- A header with a Help button, that when clicked reveals a modal.
- In the future I would refactor Modal into a reusable component. Set up props to pass h1, p and the reducers for Yes & No buttons.
- In the future I would set up Help Modal to change when state.session.off is true to provide the option to open the kitchen again, thus re-enabling newTicket.
- Expo to hold the "tickets".
- To emulate an expo's expectations for the layout of tickets, tickets should be presented in a row. The queue will begin on the left hand side of the screen and come in on the right hand side of the screen.
- The Expo should display a partial ticket on the far end so that the user develops an expectation that there are more tickets out of the view and that to access them they can scroll horizontally.
- In the future, I think it would be interesting to a/b test a layout where the second to last ticket becomes a "stack" of tickets when there are more tickets than the screen has space to display. When an expediter would like to view the full set of tickets, they can tap on the bottom edge of the stack of tickets and the tickets will spread out and the screen will be scrollable.
- I am also concerned about what kind of cue can be set up to alert the expediter that there is a new ticket. If i were to use a visual cue, perhaps having new tickets glow a certain color and then fade into the main color for a span of 2 seconds. If I were to use an audible cue, a notification ping would be most effective.
- Cards to hold the order details.
- Cards should have a button to mark as completed/picked up. In future iterations I would like to have a button in the nav bar or in the footer that takes you to a display of completed orders.
- It would be user friendly to provide the pickup ETA with a timer that counts down to that time next to it. This is something I would definitely want to implement in the future.
- Additionally, it would be clever to change the background color of tickets to red to indicate when they are overdue.
- It would be cool to use CSS Transitions or some animation to flip the cards to reveal the contents of the order.
- Gulp, writing a gulpfile and properly concatenating my files together.
- npm scripts to run my build.
- Plugging in Babel.
- Setting up Redux. My experience with Redux has been adding connect to components and accessing state to populate a UI, but i've never built the state or plugged in Redux before.
- Auto generating cards every 15 seconds
setInterval(function, timeInMillisecondsInteger)
.
- Node v5.10.1
- Hapi
- React
- Babel Preset React
- Babel Preset ES2015
- Babel Plugin "transform-class-properties" (for static)
- Redux
- Babel Plugin "transform-object-rest-spread" (for spread operators)
- redux-logger
- redux-thunk
- Babel
- Browserify
- Babelify
- Gulp
- Normalize.css
- gulp-sass
- Radium
- Babel plugin "transform-decorators-legacy" (for decorators)
- Mocha
- Chai
- Babel-Register (Dev Dependency)
- ESLint
- eslint-plugin-babel
- babel-eslint
- eslint-plugin-react
- Watchify
- Chokidar
- npm run all
- Faker
- Moment
Because I haven't put together my own build before (but i've been learning about how to do it and excited i get to finally do it) I decided to put together a boilerplate build alongside my work for this project. This way I can implement this work in this future on my other personal projects 🎉.
- Create raw index.html viewable in the browser.
- Set up Hapi to serve
index.html
at the request of a browser. - Set up Hapi to serve
index.css
at the request of a browser. - Set up Gulp to concat the
index.css
, normalize.css and any other css files. - Make Hapi point to the Gulp output in
tmp/
instead ofui/index.css
I had been encouraged to set up this project to build by sourcing CDN from the index.html, however I really wanted to learn how to get a project up and running on it's own, so at this point I undid my initial set up and opted to write my own builds.
- Install browserify & babelify
- Set up browserify with an npm script to build
- Set up babelify with browserify
Reached a point in my iteration where it was appropriate to start de-structuring my components and using Gulp to concatenate my work for me.
- Make Gulp copy over
ui/index.html
totmp/index.html
. - Make Hapi point to the Gulp output in temp/ instead of
ui/index.html
. - Install gulp-sass
- Add sass file to
ui/
so i can make sure gulp-sass is parsing the sass into css. - Make Gulp concat sass and css files and run through gulp-sass into
tmp/index.css
. Checktmp/index.css
. - Create react components for the UI in
ui/components/*.jsx
Ran into an error in my server/index.js
file. ES2015 was throwing an error upon npm start because my node version reverted to v0.12.x. To rectify I ran nvm install stable
& npm rebuild
. I could then npm start without a problem.
While introducing modules, I ran into a pair of error messages. React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).
& Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
. This blog post helped me find the solution.
Now I'm getting a new error message (yay!). RangeError: Maximum call stack size exceeded
. Not sure why i'm getting an infinite loop error message but the problem was solved by cleaning up the imports and exports which did not properly call modules from their respective homes. (Learned later about cyclical dependency calling).
- Create a
Header
module & import theModal
module into the Header. - Create a
Expo
module & import theTickets
module into the Expo.
Having trouble with styling. I didn't want to complicate things further and thought i could simply set classes on my jsx html tags. I think I'm going to look into wiring up Radium so i can do inline styles. iteration is 🆒.
- Install Radium
Getting unexpected token
error for the use of a decorator with Radium. Apparently Babel 6 took out ES7 decorators. Considering transform-decorators-legacy.
- Install babel-plugin-transform-decorators-legacy so i can use decorators with Radium.
- Install Mocha Chai
While working on the Ticket component, in order use static
for PropTypes I need to install babel-plugin-transform-class-properties
.
- Provide ticket with a UI that lists the information.
- Provide Ticket with props from Expo.
- Generate a number of tickets from a list in the Expo component.
- Set up redux in the root javascript file with Providers and createStore.
- Replace array data source with a redux source
- Insert new data to the redux source
Got an unexpected token error because I used a spread operator in reducers. Install babel-plugin-transform-object-rest-spread
in order to have access to spread operators.
I know I should have installed ESLint sooner but I put it off because I wanted to actually start working on the UI. But I'm doing it now (please don't hurt me 😬).
- Install ESLint & babel plugins.
- Write a script for eslint so i can just run
npm run lint
. - Turn tickets into a nested object where the keys are IDs, instead of an array of objects.
- Install ESLint plugin for React & JSX.
npm install eslint-plugin-react --save-dev
In order to map our tickets state object in the Expo component, it needs to be inside of an array (which was how it was structured to begin with however turning tickets into a nested object means we can sort the object by it's keys.) I was advised that the values
functions in Ramda will solve this problem
- Install Ramda.
- Import
values
from Ramda
Ok, if i'm going to install all these dependencies, then i might as well learn how to set up a server that will watch for changes in the build. Watchify was recommended to me. Honestly this is the one dependency I wish I had set up from the onset.
- Install watchify, chokidar & npm run all
- Set up a new set of scripts for watching builds
- Create a watch task in gulp
Moving tickets that are in the queue, into a completed list. This collection of completed tickets is a result of a computed function, filtering out tickets where completed is false. Even though memoizing would reduce the frequency of computation, the downside is becomes stale and therefore I would also have to manage the freshness with more code. Additionally if I needed a third or fourth or fifth subcollection (i.e. a list of tickets that have a key of ASAP and a value of true.) the object would be repetitively stored across many many lists. Computing the subcollections (in this case on a state change) allows me to keep the subcollections organized without having the manage the subcollections manually.
- Change connector to give access to two collections: queue & completed.
- Use Ramda's filter function to create the queue & completed values.
- Display the completed values in a new component.
When I passed dispatch through Expo to Tickets, got the error Invalid prop 'dispatch' of type 'number' supplied to 'Ticket', expected 'function'. Check the render method of 'Expo'.
because i forgot the second position of the map function is the index.
- Curry the renderTicket & renderTickets functions so that dispatch can be passed along to Tickets component without messing up the call for the map function.
- Build the UI to mark change the state of a ticket from in the queue to completed.
If I were to iterate through and create a more production ready application, I would create a function that moves tickets out of state if their date is older than 24 hours.
- Refactor Expo to only display tickets that are in the subcollection onlyQueue, if session view is 'queue'.
- Add if condition to display tickets that are in the subcollection onlyCompleted, if session view is 'completed'.
- Set default session view to 'queue'.
- Create an action that signals the intent to view 'completed'.
- Create an action that signals the intent to view 'queue'.
- Create a new reducer that listens for this intent & changes session to 'completed'.
- Add to the new reducer, a case that listens for the intent to view 'queue' & changes session to 'queue'.
- Write a function
toggleView
to set the button in the header and toggle it's contents based on the current view. - Write a function
onClickChangeView
to trigger the state change and toggle the button.
Ran into a problem with the toggleView button. To figure out what's happening I need to install redux-logger.
- Install redux-logger
npm i --save-dev redux-logger
in order to view state in the console. - Add some flair & finally style a layout
- Flexbox the header & buttons
- Set global styles in the index.scss file so that I can control the general design with a single global attribute & so that my jsx file won't be too style heavy.
- Layout the ticket information.
- Create a simple jagged edge on the tickets so that they have a torn receipt paper nostalgia to them.
- Add white borders to the ticket info so that it is easier to visually locate the necessary information
- Create a new set of actions to openModal & closeModal.
- Create a new reducer to listen for openModal & closeModal and change state.help.open based on the action.
- Create a new connect switch statement to handle changing state.help.open from false to true when clicking the Help button in the Header.
- Set up the No button in the modal to handle changing state.help.open to false
- Display Modal when
open: true
- Style Modal
Now in order to auto-generate tickets – as much as I would like to continue using Chefs & their famous restaurant addresses as Customers information & NYT food critics as Couriers – I need to utilize a random data generator to auto-generate tickets. While I'm working with the Ticket object, I might as well format & generate the ETA data using moment.js.
- npm install faker & moment for randomly generated data for the tickets.
- New redux action NEW_TICKET.
- New redux reducer in tickets function that appends a new ticket with random data to state.
To mimic a client to API relationship, i decided to set up the new ticket generation in the action creator, using redux-thunk to get state.
- npm install redux-thunk to access state in the action creator.
- Write newTicket action to provide a new key and object with Ticket values.
- Write new case in tickets reducer to append the new key and Ticket object to state.
- Write noTicket action
- Refactor newTicket logic into fetchTicket and set up with an if statement to determine whether fetchTicket should return noTicket or a newTicket.
- Refactor newTicket reducer. There is no need for a noTicket reducer since it returns no UI.
- Add updatedAt to Ticket's props & values.
- Refactor Ticket to only re-render UI shouldComponentUpdate.
- Fix order numbers so they render the order number preceded by enough 0's to have 4 digits
- Write tests for reducers.
- Write tests for actions.
- Write tests for the Ticket component.
- Write tests for the Expo component.
- Write tests for the Modal component.
- Just more tests. Always more tests.
- Add React CSSTransitionGroup to components so that changes in state can be smooth. (on change to
state.session.view
)