- );
-};
-
-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}
-
-
-
-
-
Monthly
-
1 Year
-
3 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;