diff --git a/src/actions/fuelSavingsActions.js b/src/actions/fuelSavingsActions.js deleted file mode 100644 index 9401470..0000000 --- a/src/actions/fuelSavingsActions.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as types from '../constants/ActionTypes'; - -export function saveFuelSavings(settings) { - return { type: types.SAVE_FUEL_SAVINGS, settings }; -} - -export function calculateFuelSavings(settings, fieldName, value) { - return { type: types.CALCULATE_FUEL_SAVINGS, settings, fieldName, value }; -} \ No newline at end of file diff --git a/src/actions/socketActionsFactory.js b/src/actions/socketActionsFactory.js new file mode 100644 index 0000000..8a8932e --- /dev/null +++ b/src/actions/socketActionsFactory.js @@ -0,0 +1,38 @@ +import { + SOCKET_SET_OPENING, SOCKET_SET_CLOSING, SOCKET_SEND_MESSAGE +} from "../constants/ActionTypes"; + +export default (socketClient) => ({ + open: url => { + const action = { type: SOCKET_SET_OPENING }; + try { + socketClient.open(url); + } catch (err) { + action.error = true; + action.payload = err; + } + return action; + }, + + close: () => { + const action = { type: SOCKET_SET_CLOSING }; + try { + socketClient.close(); + } catch (err) { + action.error = true; + action.payload = err; + } + return action; + }, + + send: message => { + const action = { type: SOCKET_SEND_MESSAGE }; + try { + socketClient.send(JSON.stringify(message)); + } catch (err) { + action.error = true; + action.payload = err; + } + return action; + } +}); diff --git a/src/actions/socketHandlerActions.js b/src/actions/socketHandlerActions.js new file mode 100644 index 0000000..005f442 --- /dev/null +++ b/src/actions/socketHandlerActions.js @@ -0,0 +1,34 @@ +import { + SOCKET_SET_CLOSED, + SOCKET_SET_ERROR, + SOCKET_SET_OPEN, + SOCKET_RECEIVE_MESSAGE +} from "../constants/ActionTypes"; + +export default { + onclose: event => ({ + type: SOCKET_SET_CLOSED, + payload: { + code: event.code + } + }), + + onerror: event => ({ + type: SOCKET_SET_ERROR + }), + + onopen: event => ({ + type: SOCKET_SET_OPEN + }), + + onmessage: event => { + const action = { type: SOCKET_RECEIVE_MESSAGE }; + try { + action.payload = JSON.parse(event.data); + } catch (err) { + action.error = true; + action.payload = err; + } + return action; + } +}; diff --git a/src/businessLogic/dateHelper.js b/src/businessLogic/dateHelper.js deleted file mode 100644 index b5e1f19..0000000 --- a/src/businessLogic/dateHelper.js +++ /dev/null @@ -1,10 +0,0 @@ -export default class DateHelper { - //See tests for desired format. - static getFormattedDateTime(date) { - return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${this.padLeadingZero(date.getMinutes())}:${this.padLeadingZero(date.getSeconds())}`; - } - - static padLeadingZero(value) { - return value > 9 ? value : '0' + value; - } -} diff --git a/src/businessLogic/dateHelper.spec.js b/src/businessLogic/dateHelper.spec.js deleted file mode 100644 index e818288..0000000 --- a/src/businessLogic/dateHelper.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import chai from 'chai'; -import DateHelper from './dateHelper'; - -chai.should(); - -describe('Date Helper', () => { - describe('getFormattedDateTime', () => { - it('returns mm/dd hh:mm:ss formatted time when passed a date', () => { - //arrange - //The 7 numbers specify the year, month, day, hour, minute, second, and millisecond, in that order - let date = new Date(99,0,24,11,33,30,0); - - //assert - DateHelper.getFormattedDateTime(date).should.equal('1/24 11:33:30'); - }); - - it('pads single digit minute and second values with leading zeros', () => { - //arrange - //The 7 numbers specify the year, month, day, hour, minute, second, and millisecond, in that order - let date = new Date(99,0,4,11,3,2,0); - - //assert - DateHelper.getFormattedDateTime(date).should.equal('1/4 11:03:02'); - }); - }); -}); diff --git a/src/businessLogic/fuelSavingsCalculator.js b/src/businessLogic/fuelSavingsCalculator.js deleted file mode 100644 index 643a6d3..0000000 --- a/src/businessLogic/fuelSavingsCalculator.js +++ /dev/null @@ -1,67 +0,0 @@ -import mathHelper from './mathHelper'; -import NumberFormatter from './numberFormatter'; - -//This file uses the factory function pattern instead of a class. -//Just showing an alternative to using a class. -//This declares a function with a private method. -//The public function returns an object literal. -//Could arguably be called FuelSavingCalculatorFactory. -let fuelSavingsCalculator = function() { - //private - let calculateMonthlyCost = function(milesDrivenPerMonth, ppg, mpg) { - let gallonsUsedPerMonth = milesDrivenPerMonth / mpg; - return gallonsUsedPerMonth * ppg; - }; - - //public - return { - calculateMilesDrivenPerMonth: function(milesDriven, milesDrivenTimeframe) { - const monthsPerYear = 12; - const weeksPerYear = 52; - - switch (milesDrivenTimeframe) { - case 'week': - return (milesDriven * weeksPerYear) / monthsPerYear; - case 'month': - return milesDriven; - case 'year': - return milesDriven / monthsPerYear; - default: - throw 'Unknown milesDrivenTimeframe passed: ' + milesDrivenTimeframe; - } - }, - - calculateSavingsPerMonth: function(settings) { - if (!settings.milesDriven) { - return 0; - } - - let milesDrivenPerMonth = this.calculateMilesDrivenPerMonth(settings.milesDriven, settings.milesDrivenTimeframe); - let tradeFuelCostPerMonth = calculateMonthlyCost(milesDrivenPerMonth, settings.tradePpg, settings.tradeMpg); - let newFuelCostPerMonth = calculateMonthlyCost(milesDrivenPerMonth, settings.newPpg, settings.newMpg); - let savingsPerMonth = tradeFuelCostPerMonth - newFuelCostPerMonth; - - return mathHelper.roundNumber(savingsPerMonth, 2); - }, - - - necessaryDataIsProvidedToCalculateSavings: function(settings) { - return settings.newMpg > 0 - && settings.tradeMpg > 0 - && settings.newPpg > 0 - && settings.tradePpg > 0 - && settings.milesDriven > 0; - }, - - calculateSavings: function(settings) { - let monthlySavings = this.calculateSavingsPerMonth(settings); - return { - monthly: NumberFormatter.getCurrencyFormattedNumber(monthlySavings), - annual: NumberFormatter.getCurrencyFormattedNumber(monthlySavings * 12), - threeYear: NumberFormatter.getCurrencyFormattedNumber(monthlySavings * 12 * 3) - }; - } - }; -}; - -export default fuelSavingsCalculator; diff --git a/src/businessLogic/fuelSavingsCalculator.spec.js b/src/businessLogic/fuelSavingsCalculator.spec.js deleted file mode 100644 index f86a13b..0000000 --- a/src/businessLogic/fuelSavingsCalculator.spec.js +++ /dev/null @@ -1,123 +0,0 @@ -import chai from 'chai'; -import Calculator from './fuelSavingsCalculator'; - -chai.should(); - -describe('Fuel Savings Calculator', () => { - describe('necessaryDataIsProvidedToCalculateSavings', () => { - it('returns false when necessary data isn\'t provided', () => { - //arrange - let settings = { - newMpg: 20 - }; - - //assert - Calculator().necessaryDataIsProvidedToCalculateSavings(settings).should.equal(false); - }); - - it('returns true when necessary data is provided', () => { - //arrange - let settings = { - newMpg: 20, - tradeMpg: 10, - newPpg: 1.50, - tradePpg: 1.50, - milesDriven: 100 - }; - - //assert - Calculator().necessaryDataIsProvidedToCalculateSavings(settings).should.equal(true); - }); - }); - - describe("milesPerMonth", () => { - it("converts a weekly timeframe to a monthly timeframe", () => { - //arrange - var milesPerWeek = 100; - - //act - var milesPerMonth = Calculator().calculateMilesDrivenPerMonth(milesPerWeek, 'week'); - - //assert - milesPerMonth.should.equal(433.3333333333333); - }); - - it("returns a monthly timeframe untouched", () => { - //arrange - var milesPerMonth = 300; - - //act - var milesPerMonthCalculated = Calculator().calculateMilesDrivenPerMonth(milesPerMonth, 'month'); - - //assert - milesPerMonthCalculated.should.equal(milesPerMonth); - }); - - it("converts a yearly timeframe to a monthly timeframe", () => { - //arrange - var milesPerYear = 1200; - - //act - var milesPerMonth = Calculator().calculateMilesDrivenPerMonth(milesPerYear, 'year'); - - //assert - milesPerMonth.should.equal(100); - }); - }); - - describe("calculateSavingsPerMonth", () => { - it("returns 29.93 in savings per month with these settings", () => { - //arrange - var settings = { - tradePpg: 3.75, - tradeMpg: 24, - newPpg: 3.75, - newMpg: 38, - milesDriven: 120, - milesDrivenTimeframe: 'week' - }; - - //act - var savingsPerMonth = Calculator().calculateSavingsPerMonth(settings); - - //assert - savingsPerMonth.should.equal(29.93); - }); - - it("returns 40.83 in savings per month with these settings", () => { - //arrange - var settings = { - tradePpg: 4.15, - tradeMpg: 24, - newPpg: 3.75, - newMpg: 38, - milesDriven: 550, - milesDrivenTimeframe: 'month' - }; - - //act - var savingsPerMonth = Calculator().calculateSavingsPerMonth(settings); - - //assert - savingsPerMonth.should.equal(40.83); - }); - - it("returns -157.12 in loss per month with these settings", () => { - //arrange - var settings = { - tradePpg: 3.15, - tradeMpg: 40, - newPpg: 3.75, - newMpg: 18, - milesDriven: 14550, - milesDrivenTimeframe: 'year' - }; - - //act - var savingsPerMonth = Calculator().calculateSavingsPerMonth(settings); - - //assert - savingsPerMonth.should.equal(-157.12); - }); - }); -}); \ No newline at end of file diff --git a/src/businessLogic/mathHelper.js b/src/businessLogic/mathHelper.js deleted file mode 100644 index 52b7c69..0000000 --- a/src/businessLogic/mathHelper.js +++ /dev/null @@ -1,40 +0,0 @@ -class MathHelper { - static roundNumber(numberToRound, numberOfDecimalPlaces) { - if (numberToRound === 0) { - return 0; - } - - if (!numberToRound) { - return ''; - } - - const scrubbedNumber = numberToRound.toString().replace('$', '').replace(',', ''); - return Math.round(scrubbedNumber * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces); - } - - static addArray(values) { //adds array of values passed. - if (values == null) { - return null; - } - - let total = 0; - for (let i in values) { - total += parseInt(this.convertToPennies(values[i])); //do math in pennies to assure accuracy. - } - - return total / 100; //convert back into dollars - } - - static convertToPennies(dollarValue) { - if (dollarValue === 0) { - return 0; - } - - dollarValue = parseFloat(dollarValue); - dollarValue = this.roundNumber(dollarValue, 2); //round to 2 decimal places. - const dollarValueContainsDecimal = (dollarValue.toString().indexOf(".") !== -1); - return (dollarValueContainsDecimal) ? parseInt(dollarValue.toString().replace('.', '')) : parseInt(dollarValue) * 100; - } -} - -export default MathHelper; diff --git a/src/businessLogic/mathHelper.spec.js b/src/businessLogic/mathHelper.spec.js deleted file mode 100644 index c567a21..0000000 --- a/src/businessLogic/mathHelper.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -import chai from 'chai'; -import MathHelper from './mathHelper'; - -chai.should(); - -describe('Math Helper', () => { - describe('roundNumber', () => { - it('returns 0 when passed null', () => { - MathHelper.roundNumber(null).should.equal(''); - }); - - it('returns 0 when passed 0', () => { - MathHelper.roundNumber(0).should.equal(0); - }); - - it('rounds up to 1.56 when passed 1.55555 rounded to 2 digits', () => { - MathHelper.roundNumber(1.55555, 2).should.equal(1.56); - }); - - it('rounds up to -1.56 when passed -1.55555 rounded to 2 digits', () => { - MathHelper.roundNumber(-1.55555, 2).should.equal(-1.56); - }); - }); - - describe('addArray', () => { - it('returns 0 when passed empty array', () => { - MathHelper.addArray([]).should.equal(0); - }); - - // it('returns null when passed null', () => { - // should.not.exist(MathHelper.addArray(null)); - // }); - - it('returns 6 when passed [2,4]', () => { - MathHelper.addArray([2,4]).should.equal(6); - }); - - it('returns 7 when passed [-6, 11, 2]', () => { - MathHelper.addArray([-6, 11, 2]).should.equal(7); - }); - }); - - describe('convertToPennies', () => { - it('returns 142 when passed 1.42', () => { - MathHelper.convertToPennies(1.42).should.equal(142); - }); - - it('returns 0 when passed 0', () => { - MathHelper.convertToPennies(0).should.equal(0); - }); - - it('rounds down to 132 when passed 1.3242', () => { - MathHelper.convertToPennies(1.3242).should.equal(132); - }); - - it('rounds up to 133 when passed 1.325', () => { - MathHelper.convertToPennies(1.325).should.equal(133); - }); - }); -}); diff --git a/src/businessLogic/numberFormatter.js b/src/businessLogic/numberFormatter.js deleted file mode 100644 index ddd73a8..0000000 --- a/src/businessLogic/numberFormatter.js +++ /dev/null @@ -1,58 +0,0 @@ -import MathHelper from './mathHelper'; - -class NumberFormatter { - static getCurrencyFormattedNumber(value) { - if (value === null) { - return ''; - } - - value = this.getFormattedNumber(value); - return '$' + value; - } - - static getFormattedNumber(value) { - if (value === 0) { - return 0; - } - - if (!value) { - return ''; - } - - if (!this.isInt(this.scrubFormatting(value))) { - return ''; //if it's not a number after scrubbing formatting, just return empty. - } - - let roundedValue = MathHelper.roundNumber(value, 2); //round if more than 2 decimal points - roundedValue = roundedValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); //add commas for 1,000's. RegEx from http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript - const roundedValueContainsDecimalPlace = (roundedValue.indexOf('.') !== -1); - - if (roundedValueContainsDecimalPlace) { - const numbersToTheRightOfDecimal = roundedValue.split('.')[1]; - - switch (numbersToTheRightOfDecimal.length) { - case 0: - return roundedValue.replace('.', ''); //no decimal necessary since no numbers after decimal - case 1: - return roundedValue + '0'; - default: - return roundedValue; - } - } - return roundedValue; - } - - static isInt(n) { - if (n === '' || n === null) { - return false; - } - - return n % 1 === 0; - } - - static scrubFormatting(value) { - return value.toString().replace('$', '').replace(',', '').replace('.', ''); - } -} - -export default NumberFormatter; diff --git a/src/businessLogic/numberFormatter.spec.js b/src/businessLogic/numberFormatter.spec.js deleted file mode 100644 index aca61e4..0000000 --- a/src/businessLogic/numberFormatter.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import NumberFormatter from './numberFormatter'; -import chai from 'chai'; - -chai.should(); - -describe('Number Formatter', () => { - describe('getCurrencyFormattedNumber', () => { - it('returns $5.50 when passed 5.5', () => { - NumberFormatter.getCurrencyFormattedNumber(5.5).should.equal("$5.50"); - }); - }); - - describe('isInt', () => { - it('returns true when passed 0', () => { - NumberFormatter.isInt(0).should.equal(true); - }); - - it('returns false when passed an empty string', () => { - NumberFormatter.isInt('').should.equal(false); - }); - - it('returns true when passed int as a string', () => { - NumberFormatter.isInt('5').should.equal(true); - }); - }); - - describe('scrubFormatting', () => { - it('strips commas and decimals', () => { - NumberFormatter.scrubFormatting('1,234.56').should.equal('123456'); - }); - }); - - describe('getFormattedNumber', () => { - it('returns 0 if passed 0', () => { - NumberFormatter.getFormattedNumber(0).should.equal(0); - }); - }); -}); diff --git a/src/components/FuelSavingsApp.js b/src/components/FuelSavingsApp.js deleted file mode 100644 index 069d65a..0000000 --- a/src/components/FuelSavingsApp.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, {PropTypes} from 'react'; -import FuelSavingsResults from './FuelSavingsResults'; -import FuelSavingsTextInput from './FuelSavingsTextInput'; - -const FuelSavingsApp = (props) => { - const save = function () { - props.actions.saveFuelSavings(props.appState); - }; - - const onTimeframeChange = function (e) { - props.actions.calculateFuelSavings(props, 'milesDrivenTimeframe', e.target.value); - }; - - const fuelSavingsKeypress = function (name, value) { - props.actions.calculateFuelSavings(props, name, value); - }; - - const settings = props.appState; - - return ( -
-

Fuel Savings Analysis

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- miles per - -
{settings.dateModified}
- -
- - {settings.necessaryDataIsProvidedToCalculateSavings ? : null} - -
- ); -}; - -FuelSavingsApp.propTypes = { - actions: PropTypes.object.isRequired, - appState: PropTypes.object.isRequired -}; - -export default FuelSavingsApp; diff --git a/src/components/FuelSavingsApp.spec.js b/src/components/FuelSavingsApp.spec.js deleted file mode 100644 index f3dcc12..0000000 --- a/src/components/FuelSavingsApp.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -import chai from 'chai'; -import FuelSavingsCalculatorForm from './FuelSavingsApp'; - -chai.should(); - -describe('Fuel Savings Calculator Component', () => { - -}); diff --git a/src/components/FuelSavingsResults.js b/src/components/FuelSavingsResults.js deleted file mode 100644 index 1042ebb..0000000 --- a/src/components/FuelSavingsResults.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, {PropTypes} from 'react'; -import NumberFormatter from '../businessLogic/numberFormatter'; - -//This is a stateless functional component. (Also known as pure or dumb component) -//More info: https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components -//And https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d -//And starter kit with more examples here: https://github.com/ericelliott/react-pure-component-starter -const FuelSavingsResults = (props) => { - const savingsExist = NumberFormatter.scrubFormatting(props.savings.monthly) > 0; - const savingsClass = savingsExist ? 'savings' : 'loss'; - const resultLabel = savingsExist ? 'Savings' : 'Loss'; - - //You can even exclude the return statement below if the entire component is - //composed within the parentheses. Return is necessary here because some - //variables are set above. - return ( - - - - - - - -
{resultLabel} - - - - - - - - - - - - - -
Monthly1 Year3 Year
{props.savings.monthly}{props.savings.annual}{props.savings.threeYear}
-
- ); -}; - -//Note that this odd style is utilized for propType validation for now. Must be defined *after* -//the component is defined, which is why it's separate and down here. -FuelSavingsResults.propTypes = { - savings: PropTypes.object.isRequired -}; - -export default FuelSavingsResults; diff --git a/src/components/FuelSavingsResults.spec.js b/src/components/FuelSavingsResults.spec.js deleted file mode 100644 index 52803ef..0000000 --- a/src/components/FuelSavingsResults.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import chai from 'chai'; -import cheerio from 'cheerio'; -import FuelSavingsResults from './FuelSavingsResults'; -import React from 'react'; -import ReactDOMServer from 'react/lib/ReactDOMServer'; - -chai.should(); - -/*This test file displays how to test a React component's HTML - outside of the browser. It uses Cheerio, which is a handy - server-side library that mimics jQuery. So to test a React - components HTML for a given state we do the following: - 1. Instantiate the component and pass the desired prop values - 2. Use ReactDOMServer to generate the resulting HTML - 3. Use Cheerio to load the HTML into a fake DOM - 4. Use Cheerio to query the DOM using jQuery style selectors - 5. Assert that certain DOM elements exist with expected values. - */ -describe('Fuel Savings Calculator Results Component', () => { - describe('Savings label', () => { - it('displays as savings when savings exist', () => { - //arrange - var props = { - savings: { - monthly: '10', - annual: '120', - threeYear: '360' - } - }; - - var sut = React.createElement(FuelSavingsResults, props); - - //act - var html = ReactDOMServer.renderToStaticMarkup(sut); - let $ = cheerio.load(html); - var fuelSavingsLabel = $('.fuel-savings-label').html(); - - //assert - fuelSavingsLabel.should.equal('Savings'); - }); - - it('display as loss when savings don\'t exist', () => { - //arrange - var props = { - savings: { - monthly: '-10', - annual: '-120', - threeYear: '-360' - } - }; - - var sut = React.createElement(FuelSavingsResults, props); - - //act - var html = ReactDOMServer.renderToStaticMarkup(sut); - let $ = cheerio.load(html); - var fuelSavingsLabel = $('.fuel-savings-label').html(); - - //assert - fuelSavingsLabel.should.equal('Loss'); - }); - }); -}); diff --git a/src/components/FuelSavingsTextInput.js b/src/components/FuelSavingsTextInput.js deleted file mode 100644 index 696916d..0000000 --- a/src/components/FuelSavingsTextInput.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { Component, PropTypes } from 'react'; - -function buildHandleChange(props) { - return function handleChange(e) { - props.onChange(props.name, e.target.value); - }; -} - -const FuelSavingsTextInput = (props) => { - const handleChange = buildHandleChange(props); - - return ( - - ); -}; - -FuelSavingsTextInput.propTypes = { - name: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.string, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]) -}; - -export default FuelSavingsTextInput; diff --git a/src/components/SolsticeApp.js b/src/components/SolsticeApp.js new file mode 100644 index 0000000..a510802 --- /dev/null +++ b/src/components/SolsticeApp.js @@ -0,0 +1,21 @@ +import React, {PropTypes} from 'react'; + +const App = (props) => { + const { onClick, socket } = props; + return ( +
+

Solstice web UI

+
Socket state: {socket.state}
+ +
+ ); +}; + +App.propTypes = { + onClick: PropTypes.func.isRequired, + socket: PropTypes.object.isRequired +}; + +export default App; diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js index 55abcf1..b859da8 100644 --- a/src/constants/ActionTypes.js +++ b/src/constants/ActionTypes.js @@ -1,2 +1,7 @@ -export const SAVE_FUEL_SAVINGS = 'SAVE_FUEL_SAVINGS'; -export const CALCULATE_FUEL_SAVINGS = 'CALCULATE_FUEL_SAVINGS'; \ No newline at end of file +export const SOCKET_SET_OPEN = Symbol("SOCKET_SET_OPEN"); +export const SOCKET_SET_OPENING = Symbol("SOCKET_SET_OPENING"); +export const SOCKET_SET_CLOSED = Symbol("SOCKET_SET_CLOSED"); +export const SOCKET_SET_CLOSING = Symbol("SOCKET_SET_CLOSING"); +export const SOCKET_SET_ERROR = Symbol("SOCKET_SET_ERROR"); +export const SOCKET_RECEIVE_MESSAGE = Symbol("SOCKET_RECEIVE_MESSAGE"); +export const SOCKET_SEND_MESSAGE = Symbol("SOCKET_SEND_MESSAGE"); diff --git a/src/containers/App.js b/src/containers/App.js index 5ac4831..80735b3 100644 --- a/src/containers/App.js +++ b/src/containers/App.js @@ -1,37 +1,47 @@ // This file bootstraps the app with the boilerplate necessary // to support hot reloading in Redux -import React, {PropTypes} from 'react'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import FuelSavingsApp from '../components/FuelSavingsApp'; -import * as actions from '../actions/fuelSavingsActions'; +import React, {PropTypes} from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; -class App extends React.Component { - render() { +import SolsticeApp from "../components/SolsticeApp"; +import socketActionsFactory from "../actions/socketActionsFactory"; +import socketHandlerActions from "../actions/socketHandlerActions"; +import SocketClient from "../utils/SocketClient"; + +const App = (props) => { + const onClick = (event) => { + const url = "ws://localhost:2244"; + props.actions.socketActions.open(url); + }; return ( - + ); - } -} +}; App.propTypes = { - actions: PropTypes.object.isRequired, - appState: PropTypes.object.isRequired + actions: PropTypes.object.isRequired, + socket: PropTypes.object.isRequired }; function mapStateToProps(state) { - return { - appState: state.fuelSavingsAppState - }; + return { + socket: state.socket + }; } function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators(actions, dispatch) - }; + const callbacks = bindActionCreators(socketHandlerActions, dispatch); + const socketClient = new SocketClient(callbacks); + const socketActions = socketActionsFactory(socketClient); + return { + actions: { + socketActions: bindActionCreators(socketActions, dispatch) + } + }; } export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps, + mapDispatchToProps )(App); diff --git a/src/index.html b/src/index.html index d1eb595..6296701 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - React Slingshot + Solstice Web UI
diff --git a/src/index.js b/src/index.js index bc95648..e9d7607 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import { Provider } from 'react-redux'; import App from './containers/App'; import configureStore from './store/configureStore'; import './styles/styles.scss'; //Yep, that's right. You can import SASS/CSS files too! Webpack will run the associated loader and plug this into the page. +import SocketClient from "./utils/SocketClient"; const store = configureStore(); diff --git a/src/reducers/fuelSavings.js b/src/reducers/fuelSavings.js deleted file mode 100644 index d15d5a6..0000000 --- a/src/reducers/fuelSavings.js +++ /dev/null @@ -1,53 +0,0 @@ -import {SAVE_FUEL_SAVINGS, CALCULATE_FUEL_SAVINGS} from '../constants/ActionTypes'; -import calculator from '../businessLogic/fuelSavingsCalculator'; -import dateHelper from '../businessLogic/dateHelper'; -import objectAssign from 'object-assign'; - -const initialState = { - newMpg: null, - tradeMpg: null, - newPpg: null, - tradePpg: null, - milesDriven: null, - milesDrivenTimeframe: 'week', - displayResults: false, - dateModified: null, - necessaryDataIsProvidedToCalculateSavings: false, - savings: { - monthly: 0, - annual: 0, - threeYear: 0 - } -}; - -//IMPORTANT: Note that with Redux, state should NEVER be changed. -//State is considered immutable. Instead, -//create a copy of the state passed and set new values on the copy. -//Note that I'm using Object.assign to create a copy of current state -//and update values on the copy. -export default function fuelSavingsAppState(state = initialState, action) { - switch (action.type) { - case SAVE_FUEL_SAVINGS: - // For this example, just simulating a save by changing date modified. - // In a real app using Redux, you might use redux-thunk and handle the async call in fuelSavingsActions.js - return objectAssign({}, state, { dateModified: dateHelper.getFormattedDateTime(new Date()) }); - - case CALCULATE_FUEL_SAVINGS: - { // limit scope with this code block, to satisfy eslint no-case-declarations rule. - let newState = objectAssign({}, state); - newState[action.fieldName] = action.value; - let calc = calculator(); - newState.necessaryDataIsProvidedToCalculateSavings = calc.necessaryDataIsProvidedToCalculateSavings(newState); - newState.dateModified = dateHelper.getFormattedDateTime(new Date()); - - if (newState.necessaryDataIsProvidedToCalculateSavings) { - newState.savings = calc.calculateSavings(newState); - } - - return newState; - } - - default: - return state; - } -} diff --git a/src/reducers/index.js b/src/reducers/index.js index cecd810..41356b0 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,8 +1,8 @@ import { combineReducers } from 'redux'; -import fuelSavingsAppState from './fuelSavings'; +import socket from "./socket"; const rootReducer = combineReducers({ - fuelSavingsAppState + socket }); export default rootReducer; diff --git a/src/reducers/socket.js b/src/reducers/socket.js new file mode 100644 index 0000000..0986956 --- /dev/null +++ b/src/reducers/socket.js @@ -0,0 +1,53 @@ +import objectAssign from "object-assign"; + +import * as types from "../constants/ActionTypes"; + +const STATE_OPENING = 0; +const STATE_OPEN = 1; +const STATE_CLOSING = 2; +const STATE_CLOSED = 3; + +const initialState = { + state: STATE_CLOSED, + url: null +}; + +export default function socket(state = initialState, action) { + switch (action.type) { + case types.SOCKET_SET_OPENING: + if (action.error) { + return state; + } + return objectAssign({}, state, { state: STATE_OPENING }); + + case types.SOCKET_SET_OPEN: + return objectAssign({}, state, { state: STATE_OPEN }); + + case types.SOCKET_SET_CLOSING: + if (action.error) { + return state; + } + return objectAssign({}, state, { state: STATE_CLOSING }); + + case types.SOCKET_SET_CLOSED: + return objectAssign({}, state, { state: STATE_CLOSED }); + + case types.SOCKET_SET_ERROR: + if (state.state === STATE_OPENING) { + return objectAssign({}, state, { state: STATE_CLOSED }); + } + return state; + + case types.SOCKET_RECEIVE_MESSAGE: + console.log(`Socket received message: ${action.payload}`); + return state; + + case types.SOCKET_SEND_MESSAGE: + console.log("Sending message"); + return state; + + default: + return state; + } +} + diff --git a/src/reducers/solstice.js b/src/reducers/solstice.js new file mode 100644 index 0000000..fc3f277 --- /dev/null +++ b/src/reducers/solstice.js @@ -0,0 +1,11 @@ +import objectAssign from 'object-assign'; + +const initialState = { + socketConnected: false, + clientConnected: false, + loggedIn: false +}; + +export default function solsticeAppState(state = initialState, action) { + return state; +} diff --git a/src/store/configureStore.js b/src/store/configureStore.js index a4c9e7a..78c9ea1 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -1,5 +1,5 @@ if (process.env.NODE_ENV === 'production') { - module.exports = require('./configureStore.prod') + module.exports = require('./configureStore.prod'); } else { - module.exports = require('./configureStore.dev') + module.exports = require('./configureStore.dev'); } diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 16389fe..95df057 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -1,31 +1,9 @@ -/* Variables */ -$vin-blue: #5bb7db; -$vin-green: #60b044; -$vin-red: #ff0000; - /* Styles */ body { - font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1.4em; - color: #4d4d4d; - min-width: 230px; - max-width: 550px; - margin: 0 auto; - -webkit-font-smoothing: antialiased; - -moz-font-smoothing: antialiased; - font-smoothing: antialiased; - font-weight: 300; + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; + font-weight: 300; } - -td { - padding: 12px; -} - -h2 { - color: $vin-blue; -} - -.savings { color: $vin-green; } -.loss { color: $vin-red; } -input.small { width: 46px; } -td.fuel-savings-label { width: 175px; } diff --git a/src/utils/SocketClient.js b/src/utils/SocketClient.js new file mode 100644 index 0000000..df6ea2c --- /dev/null +++ b/src/utils/SocketClient.js @@ -0,0 +1,31 @@ +import { bindActionCreators } from "redux"; +import objectAssign from "object-assign"; + +const STATE_CONNECTING = 0; +const STATE_OPEN = 1; +const STATE_CLOSING = 2; +const STATE_CLOSED = 3; + +class SocketClient { + constructor(callbacks) { + this.callbacks = callbacks; + } + + open(url) { + if (this.socket && this.socket.readyState !== STATE_CLOSED) { + throw new Error("SocketClient: socket already open"); + } + this.socket = new WebSocket(url); + objectAssign(this.socket, this.callbacks); + } + + close() { + this.socket.close(); + } + + send(message) { + this.socket.send(message); + } +} + +export default SocketClient;