From df4970c4c36e11d5b2dc1dc29362650cc23ec6f7 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Thu, 22 Dec 2016 21:22:47 -0500 Subject: [PATCH] Initial commit --- js/actions/ErrorActions.js | 34 +++ js/actions/UserActions.js | 204 ++++++++++++++++++ js/components/AccountSettingsModal.js | 158 ++++++++++++++ js/components/LunchApp.js | 95 ++++++++ js/components/NewUserForm.js | 165 ++++++++++++++ js/components/TopBar.js | 123 +++++++++++ js/constants/ErrorConstants.js | 9 + js/constants/UserConstants.js | 12 ++ .../AccountSettingsModalContainer.js | 21 ++ js/containers/LunchAppContainer.js | 22 ++ js/containers/TopBarContainer.js | 27 +++ js/main.js | 40 ++++ js/models.js | 115 ++++++++++ js/reducers/ErrorReducer.js | 17 ++ js/reducers/LunchReducer.js | 11 + js/reducers/SessionReducer.js | 14 ++ js/reducers/UserReducer.js | 15 ++ static/index.html | 18 ++ static/stylesheet.css | 16 ++ 19 files changed, 1116 insertions(+) create mode 100644 js/actions/ErrorActions.js create mode 100644 js/actions/UserActions.js create mode 100644 js/components/AccountSettingsModal.js create mode 100644 js/components/LunchApp.js create mode 100644 js/components/NewUserForm.js create mode 100644 js/components/TopBar.js create mode 100644 js/constants/ErrorConstants.js create mode 100644 js/constants/UserConstants.js create mode 100644 js/containers/AccountSettingsModalContainer.js create mode 100644 js/containers/LunchAppContainer.js create mode 100644 js/containers/TopBarContainer.js create mode 100644 js/main.js create mode 100644 js/models.js create mode 100644 js/reducers/ErrorReducer.js create mode 100644 js/reducers/LunchReducer.js create mode 100644 js/reducers/SessionReducer.js create mode 100644 js/reducers/UserReducer.js create mode 100644 static/index.html create mode 100644 static/stylesheet.css diff --git a/js/actions/ErrorActions.js b/js/actions/ErrorActions.js new file mode 100644 index 0000000..38479b4 --- /dev/null +++ b/js/actions/ErrorActions.js @@ -0,0 +1,34 @@ +var ErrorConstants = require('../constants/ErrorConstants'); + +var models = require('../models.js'); +var Error = models.Error; + +function serverError(error) { + return { + type: ErrorConstants.ERROR_SERVER, + error: error + }; +} + +function ajaxError(error) { + var e = new Error(); + e.ErrorId = 5; + e.ErrorString = "Request Failed: " + status + error; + + return { + type: ErrorConstants.ERROR_AJAX, + error: e + }; +} + +function clearError() { + return { + type: ErrorConstants.CLEAR_ERROR, + }; +} + +module.exports = { + serverError: serverError, + ajaxError: ajaxError, + clearError: clearError +}; diff --git a/js/actions/UserActions.js b/js/actions/UserActions.js new file mode 100644 index 0000000..81ed4ac --- /dev/null +++ b/js/actions/UserActions.js @@ -0,0 +1,204 @@ +var UserConstants = require('../constants/UserConstants'); + +var ErrorActions = require('./ErrorActions'); + +var models = require('../models.js'); +var User = models.User; +var Session = models.Session; +var Error = models.Error; + +function loginUser() { + return { + type: UserConstants.LOGIN_USER + } +} + +function userLoggedIn(session) { + return { + type: UserConstants.USER_LOGGEDIN, + session: session + } +} + +function logoutUser() { + return { + type: UserConstants.LOGOUT_USER + } +} + +function userLoggedOut() { + return { + type: UserConstants.USER_LOGGEDOUT + } +} + +function fetchUser(userId) { + return { + type: UserConstants.FETCH_USER, + userId: userId + } +} + +function userFetched(user) { + return { + type: UserConstants.USER_FETCHED, + user: user + } +} + +function updateUser(user) { + return { + type: UserConstants.UPDATE_USER, + user: user + } +} + +function userUpdated(user) { + return { + type: UserConstants.USER_UPDATED, + user: user + } +} + +function fetch(userId) { + return function (dispatch) { + dispatch(fetchUser()); + + $.ajax({ + type: "GET", + dataType: "json", + url: "user/"+userId+"/", + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + ErrorActions.serverError(e); + } else { + var u = new User(); + u.fromJSON(data); + dispatch(userFetched(u)); + } + }, + error: function(jqXHR, status, error) { + ErrorActions.ajaxError(e); + } + }); + }; +} + +function initializeSession(dispatch, session) { + dispatch(userLoggedIn(session)); + dispatch(fetch(session.UserId)); +} + +function login(user) { + return function (dispatch) { + dispatch(loginUser()); + + $.ajax({ + type: "POST", + dataType: "json", + url: "session/", + data: {user: user.toJSON()}, + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + ErrorActions.serverError(e); + } else { + var s = new Session(); + s.fromJSON(data); + initializeSession(dispatch, s); + } + }, + error: function(jqXHR, status, error) { + ErrorActions.ajaxError(e); + } + }); + }; +} + +function tryResumingSession() { + return function (dispatch) { + $.ajax({ + type: "GET", + dataType: "json", + url: "session/", + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + if (e.ErrorId != 1 /* Not Signed In*/) + ErrorActions.serverError(e); + } else { + var s = new Session(); + s.fromJSON(data); + dispatch(loginUser()); + initializeSession(dispatch, s); + } + }, + error: function(jqXHR, status, error) { + ErrorActions.ajaxError(e); + } + }); + }; +} + +function logout() { + return function (dispatch) { + dispatch(logoutUser()); + + $.ajax({ + type: "DELETE", + dataType: "json", + url: "session/", + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + ErrorActions.serverError(e); + } else { + dispatch(userLoggedOut()); + } + }, + error: function(jqXHR, status, error) { + ErrorActions.ajaxError(e); + } + }); + }; +} + +function update(user) { + return function (dispatch) { + dispatch(updateUser()); + + $.ajax({ + type: "PUT", + dataType: "json", + url: "user/"+user.UserId+"/", + data: {user: user.toJSON()}, + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + ErrorActions.serverError(e); + } else { + var u = new User(); + u.fromJSON(data); + dispatch(userUpdated(u)); + } + }, + error: function(jqXHR, status, error) { + ErrorActions.ajaxError(e); + } + }); + }; +} + +module.exports = { + fetch: fetch, + login: login, + logout: logout, + update: update, + tryResumingSession: tryResumingSession +}; diff --git a/js/components/AccountSettingsModal.js b/js/components/AccountSettingsModal.js new file mode 100644 index 0000000..64b9700 --- /dev/null +++ b/js/components/AccountSettingsModal.js @@ -0,0 +1,158 @@ +var React = require('react'); + +var ReactDOM = require('react-dom'); + +var ReactBootstrap = require('react-bootstrap'); +var Modal = ReactBootstrap.Modal; +var Button = ReactBootstrap.Button; +var ButtonGroup = ReactBootstrap.ButtonGroup; +var Form = ReactBootstrap.Form; +var FormGroup = ReactBootstrap.FormGroup; +var FormControl = ReactBootstrap.FormControl; +var ControlLabel = ReactBootstrap.ControlLabel; +var Col = ReactBootstrap.Col; + +var User = require('../models').User; + +module.exports = React.createClass({ + displayName: "AccountSettingsModal", + _getInitialState: function(props) { + return {error: "", + name: props.user.Name, + username: props.user.Username, + email: props.user.Email, + password: models.BogusPassword, + confirm_password: models.BogusPassword, + passwordChanged: false, + initial_password: models.BogusPassword}; + }, + getInitialState: function() { + return this._getInitialState(this.props); + }, + componentWillReceiveProps: function(nextProps) { + if (nextProps.show && !this.props.show) { + this.setState(this._getInitialState(nextProps)); + } + }, + passwordValidationState: function() { + if (this.state.passwordChanged) { + if (this.state.password.length >= 10) + return "success"; + else if (this.state.password.length >= 6) + return "warning"; + else + return "error"; + } + }, + confirmPasswordValidationState: function() { + if (this.state.confirm_password.length > 0) { + if (this.state.confirm_password == this.state.password) + return "success"; + else + return "error"; + } + }, + handleCancel: function() { + if (this.props.onCancel != null) + this.props.onCancel(); + }, + handleChange: function() { + if (ReactDOM.findDOMNode(this.refs.password).value != this.state.initial_password) + this.setState({passwordChanged: true}); + this.setState({ + name: ReactDOM.findDOMNode(this.refs.name).value, + username: ReactDOM.findDOMNode(this.refs.username).value, + email: ReactDOM.findDOMNode(this.refs.email).value, + password: ReactDOM.findDOMNode(this.refs.password).value, + confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value + }); + }, + handleSubmit: function(e) { + var u = new User(); + e.preventDefault(); + + u.UserId = this.props.user.UserId; + u.Name = this.state.name; + u.Username = this.state.username; + u.Email = this.state.email; + if (this.state.passwordChanged) { + u.Password = this.state.password; + if (u.Password != this.state.confirm_password) { + this.setState({error: "Error: password do not match"}); + return; + } + } else { + u.Password = models.BogusPassword; + } + + this.props.onUpdateUser(u); + this.props.onSubmit(); + }, + render: function() { + return ( + + + Edit Account Settings + + + {this.state.error} +
+ + Name + + + + + + Username + + + + + + Email + + + + + + Password + + + + + + + Confirm Password + + + + + +
+
+ + + + + + +
+ ); + } +}); diff --git a/js/components/LunchApp.js b/js/components/LunchApp.js new file mode 100644 index 0000000..6af2049 --- /dev/null +++ b/js/components/LunchApp.js @@ -0,0 +1,95 @@ +var React = require('react'); + +var ReactBootstrap = require('react-bootstrap'); +var Jumbotron = ReactBootstrap.Jumbotron; +var Tabs = ReactBootstrap.Tabs; +var Tab = ReactBootstrap.Tab; +var Modal = ReactBootstrap.Modal; + +var TopBarContainer = require('../containers/TopBarContainer'); +var NewUserForm = require('./NewUserForm'); +var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer'); + +module.exports = React.createClass({ + displayName: "LunchApp", + getInitialState: function() { + return { + hash: "home", + showAccountSettingsModal: false + }; + }, + componentDidMount: function() { + this.props.tryResumingSession(); + this.handleHashChange(); + if ("onhashchange" in window) { + window.onhashchange = this.handleHashChange; + } + }, + handleHashChange: function() { + var hash = location.hash.replace(/^#/, ''); + if (hash.length == 0) + hash = "home"; + if (hash != this.state.hash) + this.setHash(hash); + }, + setHash: function(hash) { + location.hash = hash; + if (this.state.hash != hash) + this.setState({hash: hash}); + }, + handleAccountSettings: function() { + this.setState({showAccountSettingsModal: true}); + }, + handleSettingsSubmitted: function(user) { + this.setState({ + showAccountSettingsModal: false + }); + }, + handleSettingsCanceled: function() { + this.setState({showAccountSettingsModal: false}); + }, + handleCreateNewUser: function() { + this.setHash("new_user"); + }, + handleGoHome: function() { + this.setHash("home"); + }, + render: function() { + var mainContent; + if (this.state.hash == "new_user") { + mainContent = + } else { + if (this.props.user.isUser()) + mainContent = ( + + accounts + + securities + + Scheduled transactions go here... + Budgets go here... + Reports go here... + ); + else + mainContent = ( + +
+

Lunch App

+
+
); + } + + return ( +
+ + {mainContent} + +
+ ); + } +}); diff --git a/js/components/NewUserForm.js b/js/components/NewUserForm.js new file mode 100644 index 0000000..9a0145c --- /dev/null +++ b/js/components/NewUserForm.js @@ -0,0 +1,165 @@ +var React = require('react'); +var ReactDOM = require('react-dom'); + +var ReactBootstrap = require('react-bootstrap'); +var Panel = ReactBootstrap.Panel; +var Form = ReactBootstrap.Form; +var FormGroup = ReactBootstrap.FormGroup; +var FormControl = ReactBootstrap.FormControl; +var ControlLabel = ReactBootstrap.ControlLabel; +var Col = ReactBootstrap.Col; +var Button = ReactBootstrap.Button; +var ButtonGroup = ReactBootstrap.ButtonGroup; + +var models = require('../models'); +var User = models.User; +var Error = models.Error; + +module.exports = React.createClass({ + getInitialState: function() { + return {error: "", + name: "", + username: "", + email: "", + password: "", + confirm_password: "", + passwordChanged: false, + initial_password: ""}; + }, + passwordValidationState: function() { + if (this.state.passwordChanged) { + if (this.state.password.length >= 10) + return "success"; + else if (this.state.password.length >= 6) + return "warning"; + else + return "error"; + } + }, + confirmPasswordValidationState: function() { + if (this.state.confirm_password.length > 0) { + if (this.state.confirm_password == this.state.password) + return "success"; + else + return "error"; + } + }, + handleCancel: function() { + if (this.props.onCancel != null) + this.props.onCancel(); + }, + handleChange: function() { + if (ReactDOM.findDOMNode(this.refs.password).value != this.state.initial_password) + this.setState({passwordChanged: true}); + this.setState({ + name: ReactDOM.findDOMNode(this.refs.name).value, + username: ReactDOM.findDOMNode(this.refs.username).value, + email: ReactDOM.findDOMNode(this.refs.email).value, + password: ReactDOM.findDOMNode(this.refs.password).value, + confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value + }); + }, + handleSubmit: function(e) { + var u = new User(); + var error = ""; + e.preventDefault(); + + u.Name = this.state.name; + u.Username = this.state.username; + u.Email = this.state.email; + u.Password = this.state.password; + if (u.Password != this.state.confirm_password) { + this.setState({error: "Error: password do not match"}); + return; + } + + this.handleCreateNewUser(u); + }, + handleCreateNewUser: function(user) { + $.ajax({ + type: "POST", + dataType: "json", + url: "user/", + data: {user: user.toJSON()}, + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + this.setState({error: e}); + } else { + this.props.onNewUser(); + } + }.bind(this), + error: function(jqXHR, status, error) { + var e = new Error(); + e.ErrorId = 5; + e.ErrorString = "Request Failed: " + status + error; + this.setState({error: e}); + }.bind(this), + }); + }, + render: function() { + var title =

Create New User

; + return ( + + {this.state.error} +
+ + Name + + + + + + Username + + + + + + Email + + + + + + Password + + + + + + + Confirm Password + + + + + + + + + + +
+
+ ); + } +}); diff --git a/js/components/TopBar.js b/js/components/TopBar.js new file mode 100644 index 0000000..3255133 --- /dev/null +++ b/js/components/TopBar.js @@ -0,0 +1,123 @@ +var React = require('react'); + +var ReactBootstrap = require('react-bootstrap'); +var Alert = ReactBootstrap.Alert; +var FormGroup = ReactBootstrap.FormGroup; +var FormControl = ReactBootstrap.FormControl; +var Button = ReactBootstrap.Button; +var DropdownButton = ReactBootstrap.DropdownButton; +var MenuItem = ReactBootstrap.MenuItem; +var Row = ReactBootstrap.Row; +var Col = ReactBootstrap.Col; + +var ReactDOM = require('react-dom'); + +var User = require('../models').User; + +const LoginBar = React.createClass({ + getInitialState: function() { + return {username: '', password: ''}; + }, + onUsernameChange: function(e) { + this.setState({username: e.target.value}); + }, + onPasswordChange: function(e) { + this.setState({password: e.target.value}); + }, + handleSubmit: function(e) { + var user = new User(); + e.preventDefault(); + user.Username = ReactDOM.findDOMNode(this.refs.username).value; + user.Password = ReactDOM.findDOMNode(this.refs.password).value; + this.props.onLogin(user); + }, + handleNewUserSubmit: function(e) { + e.preventDefault(); + this.props.onCreateNewUser(); + }, + render: function() { + return ( +
+ + + + + + + + + + + + + + + + + +
+ ); + } +}); + +const LogoutBar = React.createClass({ + handleOnSelect: function(key) { + if (key == 1) { + if (this.props.onAccountSettings != null) + this.props.onAccountSettings(); + } else if (key == 2) { + this.props.onLogout(); + } + }, + render: function() { + var signedInString = "Signed in as "+this.props.user.Name; + return ( + + + + + +
+ + Account Settings + Logout + +
+ +
+
+ ); + } +}); + +module.exports = React.createClass({ + displayName: "TopBar", + render: function() { + var barContents; + var errorAlert; + if (!this.props.user.isUser()) + barContents = ; + else + barContents = ; + if (this.props.error.isError()) + errorAlert = + +

Error!

+

Error {this.props.error.ErrorId}: {this.props.error.ErrorString}

+ +
; + + return ( +
+ {barContents} + {errorAlert} +
+ ); + } +}); diff --git a/js/constants/ErrorConstants.js b/js/constants/ErrorConstants.js new file mode 100644 index 0000000..d3a0d1c --- /dev/null +++ b/js/constants/ErrorConstants.js @@ -0,0 +1,9 @@ +var keyMirror = require('keymirror'); + +module.exports = keyMirror({ + ERROR_AJAX: null, + ERROR_SERVER: null, + ERROR_CLIENT: null, + ERROR_USER: null, + CLEAR_ERROR: null +}); diff --git a/js/constants/UserConstants.js b/js/constants/UserConstants.js new file mode 100644 index 0000000..f090be4 --- /dev/null +++ b/js/constants/UserConstants.js @@ -0,0 +1,12 @@ +var keyMirror = require('keymirror'); + +module.exports = keyMirror({ + LOGIN_USER: null, + USER_LOGGEDIN: null, + LOGOUT_USER: null, + USER_LOGGEDOUT: null, + FETCH_USER: null, + USER_FETCHED: null, + UPDATE_USER: null, + USER_UPDATED: null +}); diff --git a/js/containers/AccountSettingsModalContainer.js b/js/containers/AccountSettingsModalContainer.js new file mode 100644 index 0000000..f653083 --- /dev/null +++ b/js/containers/AccountSettingsModalContainer.js @@ -0,0 +1,21 @@ +var connect = require('react-redux').connect; + +var UserActions = require('../actions/UserActions'); +var AccountSettingsModal = require('../components/AccountSettingsModal'); + +function mapStateToProps(state) { + return { + user: state.user + } +} + +function mapDispatchToProps(dispatch) { + return { + onUpdateUser: function(user) {dispatch(UserActions.update(user))} + } +} + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(AccountSettingsModal) diff --git a/js/containers/LunchAppContainer.js b/js/containers/LunchAppContainer.js new file mode 100644 index 0000000..a16d63d --- /dev/null +++ b/js/containers/LunchAppContainer.js @@ -0,0 +1,22 @@ +var connect = require('react-redux').connect; + +var UserActions = require('../actions/UserActions'); + +var LunchApp = require('../components/LunchApp'); + +function mapStateToProps(state) { + return { + user: state.user + } +} + +function mapDispatchToProps(dispatch) { + return { + tryResumingSession: function() {dispatch(UserActions.tryResumingSession())}, + } +} + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(LunchApp) diff --git a/js/containers/TopBarContainer.js b/js/containers/TopBarContainer.js new file mode 100644 index 0000000..2f0cf03 --- /dev/null +++ b/js/containers/TopBarContainer.js @@ -0,0 +1,27 @@ +var connect = require('react-redux').connect; + +var UserActions = require('../actions/UserActions'); +var ErrorActions = require('../actions/ErrorActions'); + +var TopBar = require('../components/TopBar'); + +function mapStateToProps(state) { + return { + user: state.user, + error: state.error + } +} + +function mapDispatchToProps(dispatch) { + return { + onLogin: function(user) {dispatch(UserActions.login(user))}, + onLogout: function() {dispatch(UserActions.logout())}, + onUpdateUser: function(user) {dispatch(UserActions.update(user))}, + onClearError: function() {dispatch(ErrorActions.clearError())} + } +} + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(TopBar) diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..72e30a1 --- /dev/null +++ b/js/main.js @@ -0,0 +1,40 @@ +var React = require('react'); +var ReactDOM = require('react-dom'); + +var Provider = require('react-redux').Provider; +var Redux = require('redux'); +var ReduxThunk = require('redux-thunk').default; + +var Globalize = require('globalize'); +var globalizeLocalizer = require('react-widgets/lib/localizers/globalize'); + +var LunchAppContainer = require('./containers/LunchAppContainer'); +var LunchReducer = require('./reducers/LunchReducer'); + +// Setup globalization for react-widgets +//Globalize.load(require("cldr-data").entireSupplemental()); +Globalize.load( + require("cldr-data/main/en/ca-gregorian"), + require("cldr-data/main/en/numbers"), + require("cldr-data/supplemental/likelySubtags"), + require("cldr-data/supplemental/timeData"), + require("cldr-data/supplemental/weekData") +); +Globalize.locale('en'); +globalizeLocalizer(Globalize); + +$(document).ready(function() { + var store = Redux.createStore( + LunchReducer, + Redux.applyMiddleware( + ReduxThunk + ) + ); + + ReactDOM.render( + + + , + document.getElementById("content") + ); +}); diff --git a/js/models.js b/js/models.js new file mode 100644 index 0000000..a7cea52 --- /dev/null +++ b/js/models.js @@ -0,0 +1,115 @@ +var Big = require('big.js'); + +function getJSONObj(json_input) { + if (typeof json_input == "string") + return $.parseJSON(json_input) + else if (typeof json_input == "object") + return json_input; + + console.error("Unable to parse json:", json_input); + return null +} + +function User() { + this.UserId = -1; + this.Name = ""; + this.Username = ""; + this.Password = ""; + this.Email = ""; +} + +User.prototype.toJSON = function() { + var json_obj = {}; + json_obj.UserId = this.UserId; + json_obj.Name = this.Name; + json_obj.Username = this.Username; + json_obj.Password = this.Password; + json_obj.Email = this.Email; + return JSON.stringify(json_obj); +} + +User.prototype.fromJSON = function(json_input) { + var json_obj = getJSONObj(json_input); + + if (json_obj.hasOwnProperty("UserId")) + this.UserId = json_obj.UserId; + if (json_obj.hasOwnProperty("Name")) + this.Name = json_obj.Name; + if (json_obj.hasOwnProperty("Username")) + this.Username = json_obj.Username; + if (json_obj.hasOwnProperty("Password")) + this.Password = json_obj.Password; + if (json_obj.hasOwnProperty("Email")) + this.Email = json_obj.Email; +} + +User.prototype.isUser = function() { + var empty_user = new User(); + return this.UserId != empty_user.UserId || + this.Username != empty_user.Username; +} + +function Session() { + this.SessionId = -1; + this.UserId = -1; +} + +Session.prototype.toJSON = function() { + var json_obj = {}; + json_obj.SessionId = this.SessionId; + json_obj.UserId = this.UserId; + return JSON.stringify(json_obj); +} + +Session.prototype.fromJSON = function(json_input) { + var json_obj = getJSONObj(json_input); + + if (json_obj.hasOwnProperty("SessionId")) + this.SessionId = json_obj.SessionId; + if (json_obj.hasOwnProperty("UserId")) + this.UserId = json_obj.UserId; +} + +Session.prototype.isSession = function() { + var empty_session = new Session(); + return this.SessionId != empty_session.SessionId || + this.UserId != empty_session.UserId; +} + +function Error() { + this.ErrorId = -1; + this.ErrorString = ""; +} + +Error.prototype.toJSON = function() { + var json_obj = {}; + json_obj.ErrorId = this.ErrorId; + json_obj.ErrorString = this.ErrorString; + return JSON.stringify(json_obj); +} + +Error.prototype.fromJSON = function(json_input) { + var json_obj = getJSONObj(json_input); + + if (json_obj.hasOwnProperty("ErrorId")) + this.ErrorId = json_obj.ErrorId; + if (json_obj.hasOwnProperty("ErrorString")) + this.ErrorString = json_obj.ErrorString; +} + +Error.prototype.isError = function() { + var empty_error = new Error(); + return this.ErrorId != empty_error.ErrorId || + this.ErrorString != empty_error.ErrorString; +} + +module.exports = models = { + + // Classes + User: User, + Session: Session, + Error: Error, + + // Constants + BogusPassword: "password" +}; diff --git a/js/reducers/ErrorReducer.js b/js/reducers/ErrorReducer.js new file mode 100644 index 0000000..7d93f41 --- /dev/null +++ b/js/reducers/ErrorReducer.js @@ -0,0 +1,17 @@ +var ErrorConstants = require('../constants/ErrorConstants'); + +var Error = require('../models').Error; + +module.exports = function(state = new Error(), action) { + switch (action.type) { + case ErrorConstants.ERROR_AJAX: + case ErrorConstants.ERROR_SERVER: + case ErrorConstants.ERROR_CLIENT: + case ErrorConstants.ERROR_USER: + return action.error; + case ErrorConstants.CLEAR_ERROR: + return new Error(); + default: + return state; + } +}; diff --git a/js/reducers/LunchReducer.js b/js/reducers/LunchReducer.js new file mode 100644 index 0000000..27f1d22 --- /dev/null +++ b/js/reducers/LunchReducer.js @@ -0,0 +1,11 @@ +var Redux = require('redux'); + +var UserReducer = require('./UserReducer'); +var SessionReducer = require('./SessionReducer'); +var ErrorReducer = require('./ErrorReducer'); + +module.exports = Redux.combineReducers({ + user: UserReducer, + session: SessionReducer, + error: ErrorReducer +}); diff --git a/js/reducers/SessionReducer.js b/js/reducers/SessionReducer.js new file mode 100644 index 0000000..1ce65b6 --- /dev/null +++ b/js/reducers/SessionReducer.js @@ -0,0 +1,14 @@ +var UserConstants = require('../constants/UserConstants'); + +var Session = require('../models').Session; + +module.exports = function(state = new Session(), action) { + switch (action.type) { + case UserConstants.USER_LOGGEDIN: + return action.session; + case UserConstants.USER_LOGGEDOUT: + return new Session(); + default: + return state; + } +}; diff --git a/js/reducers/UserReducer.js b/js/reducers/UserReducer.js new file mode 100644 index 0000000..21102dc --- /dev/null +++ b/js/reducers/UserReducer.js @@ -0,0 +1,15 @@ +var UserConstants = require('../constants/UserConstants'); + +var User = require('../models').User; + +module.exports = function(state = new User(), action) { + switch (action.type) { + case UserConstants.USER_FETCHED: + case UserConstants.USER_UPDATED: + return action.user; + case UserConstants.USER_LOGGEDOUT: + return new User(); + default: + return state; + } +}; diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..078bb96 --- /dev/null +++ b/static/index.html @@ -0,0 +1,18 @@ + + +Lunch + + + + + + + + + + + + +
+ + diff --git a/static/stylesheet.css b/static/stylesheet.css new file mode 100644 index 0000000..d8ac3e6 --- /dev/null +++ b/static/stylesheet.css @@ -0,0 +1,16 @@ +html, body { + height: 100%; +} +div#content { + display: block; + width: 95%; + height: 100%; + min-width: 75em; + max-width: 100em; + margin: auto; +} + +/* Keep the main windows sized to the full viewable height */ +.fullheight { + height: 100%; +}