From 3490f20a469bd0cea2bcd5938ea9a5b5deefeb3e Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 1 Jun 2016 16:47:36 -0400 Subject: [PATCH 01/18] add react_on_rails to gemfile --- Gemfile | 1 + Gemfile.lock | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Gemfile b/Gemfile index 2f88b0ec6..f44d2695d 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,7 @@ gem 'select2-rails', '~> 4.0.2' gem 'kaminari', '~> 0.16.3' gem 'draper', '~> 2.1.0' gem 'inline_svg', '~> 0.8.0' +gem 'react_on_rails', '~> 6.0.1' # forms / formatting gem 'simple_form', '~> 3.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index a422d2ce5..eeeb0f977 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -104,6 +104,7 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.2) + connection_pool (2.2.0) daemons (1.2.3) database_cleaner (1.5.3) debug_inspector (0.0.2) @@ -144,6 +145,8 @@ GEM ffi (1.9.10) font-awesome-rails (4.6.3.0) railties (>= 3.2, < 5.1) + foreman (0.82.0) + thor (~> 0.19.1) formatador (0.2.5) fullcalendar-rails (2.6.1.0) jquery-rails (>= 4.0.5, < 5.0.0) @@ -339,6 +342,13 @@ GEM rb-fsevent (0.9.7) rb-inotify (0.9.7) ffi (>= 0.5.0) + react_on_rails (6.0.1) + addressable + connection_pool + execjs (~> 2.5) + foreman + rails (>= 3.2) + rainbow (~> 2.1) redcarpet (3.3.4) ref (2.0.0) remotipart (1.2.1) @@ -501,6 +511,7 @@ DEPENDENCIES rails_12factor (~> 0.0.3) rails_admin (~> 0.8.1) rake (~> 11.1.2) + react_on_rails (~> 6.0.1) redcarpet (~> 3.3.4) rspec-rails (~> 3.4.2) rubocop (~> 0.40.0) From 1bd70a014f69bd43cacd36e465a0f8d318b44fec Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Fri, 3 Jun 2016 11:52:36 -0400 Subject: [PATCH 02/18] Initial install of react-on-rails; many modifications. DELETE hello world later --- .gitignore | 7 +- Procfile.dev | 2 + app/assets/javascripts/application.js | 2 + app/controllers/hello_world_controller.rb | 5 ++ app/views/hello_world/index.html.erb | 3 + client/.babelrc | 3 + client/REACT_ON_RAILS_CLIENT_README.md | 9 +++ .../actions/helloWorldActionCreators.jsx | 8 ++ .../components/HelloWorldWidget.jsx | 42 ++++++++++ .../constants/helloWorldConstants.jsx | 13 +++ .../HelloWorld/containers/HelloWorld.jsx | 42 ++++++++++ .../HelloWorld/reducers/helloWorldReducer.jsx | 19 +++++ .../app/bundles/HelloWorld/reducers/index.jsx | 13 +++ .../HelloWorld/startup/HelloWorldApp.jsx | 24 ++++++ .../HelloWorld/store/helloWorldStore.jsx | 32 ++++++++ client/package.json | 40 ++++++++++ client/webpack.config.js | 58 ++++++++++++++ config/initializers/assets.rb | 10 +++ config/initializers/react_on_rails.rb | 80 +++++++++++++++++++ config/routes.rb | 1 + package.json | 13 +++ spec/spec_helper.rb | 4 + 22 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 Procfile.dev create mode 100644 app/controllers/hello_world_controller.rb create mode 100644 app/views/hello_world/index.html.erb create mode 100644 client/.babelrc create mode 100644 client/REACT_ON_RAILS_CLIENT_README.md create mode 100644 client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx create mode 100644 client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx create mode 100644 client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx create mode 100644 client/app/bundles/HelloWorld/containers/HelloWorld.jsx create mode 100644 client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx create mode 100644 client/app/bundles/HelloWorld/reducers/index.jsx create mode 100644 client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx create mode 100644 client/app/bundles/HelloWorld/store/helloWorldStore.jsx create mode 100644 client/package.json create mode 100644 client/webpack.config.js create mode 100644 config/initializers/react_on_rails.rb create mode 100644 package.json diff --git a/.gitignore b/.gitignore index a359e8362..7168f97c4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ diff.py Vagrantfile *.sublime* coverage/ -.env \ No newline at end of file +.env# React on Rails +npm-debug.log +node_modules + +# Generated js bundles +/app/assets/webpack/* diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 000000000..8ac16351b --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: rails s +client: sh -c 'rm app/assets/webpack/* || true && cd client && npm run build:development' diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 924cb380d..282d82fe7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,3 +1,5 @@ +//= require webpack-bundle + //= require jquery //= require jquery_ujs //= require datatables.min.js diff --git a/app/controllers/hello_world_controller.rb b/app/controllers/hello_world_controller.rb new file mode 100644 index 000000000..a8f38ef25 --- /dev/null +++ b/app/controllers/hello_world_controller.rb @@ -0,0 +1,5 @@ +class HelloWorldController < ApplicationController + def index + @hello_world_props = { name: "Stranger" } + end +end diff --git a/app/views/hello_world/index.html.erb b/app/views/hello_world/index.html.erb new file mode 100644 index 000000000..7f8ef5be9 --- /dev/null +++ b/app/views/hello_world/index.html.erb @@ -0,0 +1,3 @@ +

Hello World

+<%= react_component("HelloWorldApp", props: @hello_world_props, prerender: false) %> + diff --git a/client/.babelrc b/client/.babelrc new file mode 100644 index 000000000..9b7d435ad --- /dev/null +++ b/client/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "stage-0", "react"] +} diff --git a/client/REACT_ON_RAILS_CLIENT_README.md b/client/REACT_ON_RAILS_CLIENT_README.md new file mode 100644 index 000000000..642244d66 --- /dev/null +++ b/client/REACT_ON_RAILS_CLIENT_README.md @@ -0,0 +1,9 @@ +Client folder generated by the React on Rails gem. + +See documentation [at github.com/shakacode/react_on_rails](https://github.com/shakacode/react_on_rails) for details on how it is organized. + +If you need additional help, please consider: + +* [Our ShakaCode Forum for React on Rails](https://forum.shakacode.com/c/rails/reactonrails). +* Joining our Slack discussion room by [email us a bit about you and your project](mailto:contact@shakacode.com). +* [Hiring us](https://forum.shakacode.com/c/rails/reactonrails) for coaching and custom web application development for your project. diff --git a/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx b/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx new file mode 100644 index 000000000..78f92150d --- /dev/null +++ b/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx @@ -0,0 +1,8 @@ +import actionTypes from '../constants/helloWorldConstants'; + +export function updateName(name) { + return { + type: actionTypes.HELLO_WORLD_NAME_UPDATE, + name, + }; +} diff --git a/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx b/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx new file mode 100644 index 000000000..606609f1c --- /dev/null +++ b/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx @@ -0,0 +1,42 @@ +// HelloWorldWidget is an arbitrary name for any "dumb" component. We do not recommend suffixing +// all your dump component names with Widget. + +import React, { PropTypes } from 'react'; + +// Simple example of a React "dumb" component +export default class HelloWorldWidget extends React.Component { + static propTypes = { + // If you have lots of data or action properties, you should consider grouping them by + // passing two properties: "data" and "actions". + updateName: PropTypes.func.isRequired, + name: PropTypes.string.isRequired, + }; + + // React will automatically provide us with the event `e` + handleChange(e) { + const name = e.target.value; + this.props.updateName(name); + } + + render() { + const { name } = this.props; + return ( +
+

+ Hello, {name}! Welcome to Reservations! +

+
+
+ + this.handleChange(e)} + /> +
+
+ ); + } +} diff --git a/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx b/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx new file mode 100644 index 000000000..f34393c25 --- /dev/null +++ b/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx @@ -0,0 +1,13 @@ +// See https://www.npmjs.com/package/mirror-creator +// Allows us to set up constants in a slightly more concise syntax. See: +// client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx +import mirrorCreator from 'mirror-creator'; + +const actionTypes = mirrorCreator([ + 'HELLO_WORLD_NAME_UPDATE', +]); + +// actionTypes = {HELLO_WORLD_NAME_UPDATE: "HELLO_WORLD_NAME_UPDATE"} +// Notice how we don't have to duplicate HELLO_WORLD_NAME_UPDATE twice +// thanks to mirror-creator. +export default actionTypes; diff --git a/client/app/bundles/HelloWorld/containers/HelloWorld.jsx b/client/app/bundles/HelloWorld/containers/HelloWorld.jsx new file mode 100644 index 000000000..fe1cb2872 --- /dev/null +++ b/client/app/bundles/HelloWorld/containers/HelloWorld.jsx @@ -0,0 +1,42 @@ +import React, { PropTypes } from 'react'; +import HelloWorldWidget from '../components/HelloWorldWidget'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import Immutable from 'immutable'; +import * as helloWorldActionCreators from '../actions/helloWorldActionCreators'; + +function select(state) { + // Which part of the Redux global state does our component want to receive as props? + // Note the use of `$$` to prefix the property name because the value is of type Immutable.js + return { $$helloWorldStore: state.$$helloWorldStore }; +} + +// Simple example of a React "smart" component +const HelloWorld = (props) => { + const { dispatch, $$helloWorldStore } = props; + const actions = bindActionCreators(helloWorldActionCreators, dispatch); + const { updateName } = actions; + const name = $$helloWorldStore.get('name'); + + // This uses the ES2015 spread operator to pass properties as it is more DRY + // This is equivalent to: + // + return ( + + ); +}; + +HelloWorld.propTypes = { + dispatch: PropTypes.func.isRequired, + + // This corresponds to the value used in function select above. + // We prefix all property and variable names pointing to Immutable.js objects with '$$'. + // This allows us to immediately know we don't call $$helloWorldStore['someProperty'], but + // instead use the Immutable.js `get` API for Immutable.Map + $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired, +}; + +// Don't forget to actually use connect! +// Note that we don't export HelloWorld, but the redux "connected" version of it. +// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples +export default connect(select)(HelloWorld); diff --git a/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx b/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx new file mode 100644 index 000000000..3ca225cc7 --- /dev/null +++ b/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx @@ -0,0 +1,19 @@ +import Immutable from 'immutable'; + +import actionTypes from '../constants/helloWorldConstants'; + +export const $$initialState = Immutable.fromJS({ + name: '', // this is the default state that would be used if one were not passed into the store +}); + +export default function helloWorldReducer($$state = $$initialState, action) { + const { type, name } = action; + + switch (type) { + case actionTypes.HELLO_WORLD_NAME_UPDATE: + return $$state.set('name', name); + + default: + return $$state; + } +} diff --git a/client/app/bundles/HelloWorld/reducers/index.jsx b/client/app/bundles/HelloWorld/reducers/index.jsx new file mode 100644 index 000000000..1615975c7 --- /dev/null +++ b/client/app/bundles/HelloWorld/reducers/index.jsx @@ -0,0 +1,13 @@ +// This file is our manifest of all reducers for the app. +// See also /client/app/bundles/HelloWorld/store/helloWorldStore.jsx +// A real world app will likely have many reducers and it helps to organize them in one file. +import helloWorldReducer from './helloWorldReducer'; +import { $$initialState as $$helloWorldState } from './helloWorldReducer'; + +export default { + $$helloWorldStore: helloWorldReducer, +}; + +export const initialStates = { + $$helloWorldState, +}; diff --git a/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx b/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx new file mode 100644 index 000000000..603620c04 --- /dev/null +++ b/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import ReactOnRails from 'react-on-rails'; +import { Provider } from 'react-redux'; + +import createStore from '../store/helloWorldStore'; +import HelloWorld from '../containers/HelloWorld'; + +// See documentation for https://github.com/reactjs/react-redux. +// This is how you get props from the Rails view into the redux store. +// This code here binds your smart component to the redux store. +// railsContext provides contextual information especially useful for server rendering, such as +// knowing the locale. See the React on Rails documentation for more info on the railsContext +const HelloWorldApp = (props, _railsContext) => { + const store = createStore(props); + const reactComponent = ( + + + + ); + return reactComponent; +}; + +// This is how react_on_rails can see the HelloWorldApp in the browser. +ReactOnRails.register({ HelloWorldApp }); diff --git a/client/app/bundles/HelloWorld/store/helloWorldStore.jsx b/client/app/bundles/HelloWorld/store/helloWorldStore.jsx new file mode 100644 index 000000000..85d21edd5 --- /dev/null +++ b/client/app/bundles/HelloWorld/store/helloWorldStore.jsx @@ -0,0 +1,32 @@ +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; + +// See +// https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html +// This is not actually used for this simple example, but you'd probably want to use this +// once your app has asynchronous actions. +import thunkMiddleware from 'redux-thunk'; + +import reducers from '../reducers'; +import { initialStates } from '../reducers'; + +export default props => { + // This is how we get initial props Rails into redux. + const { name } = props; + const { $$helloWorldState } = initialStates; + + // Redux expects to initialize the store using an Object, not an Immutable.Map + const initialState = { + $$helloWorldStore: $$helloWorldState.merge({ + name, + }), + }; + + const reducer = combineReducers(reducers); + const composedStore = compose( + applyMiddleware(thunkMiddleware) + ); + const storeCreator = composedStore(createStore); + const store = storeCreator(reducer, initialState); + + return store; +}; diff --git a/client/package.json b/client/package.json new file mode 100644 index 000000000..75693d887 --- /dev/null +++ b/client/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "0.0.1", + "engines": { + "node": "5.10.0", + "npm": "3.5.0" + }, + "scripts": { + "build:test": "webpack --config webpack.config.js", + "build:production": "NODE_ENV=production webpack --config webpack.config.js", + "build:development": "webpack -w --config webpack.config.js" + }, + "cacheDirectories": ["node_modules", "client/node_modules"], + "dependencies": { + "babel": "^6.5.2", + "babel-cli": "^6.6.5", + "babel-core": "^6.7.4", + "babel-loader": "^6.2.4", + "babel-runtime": "^6.6.1", + "babel-polyfill": "^6.7.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0", + "es5-shim": "^4.5.7", + "expose-loader": "^0.7.1", + "immutable": "^3.7.6", + "imports-loader": "^0.6.5", + "mirror-creator": "1.1.0", + "react": "^0.14.8 || ^15.0.0", + "react-dom": "^0.14.8 || ^15.0.0", + "react-on-rails": "6.0.1", + "react-redux": "^4.4.1", + "redux": "^3.3.1", + "redux-promise": "^0.5.3", + "redux-thunk": "^2.0.1", + "webpack": "^1.12.14" + }, + "devDependencies": { + } +} diff --git a/client/webpack.config.js b/client/webpack.config.js new file mode 100644 index 000000000..54d2dd570 --- /dev/null +++ b/client/webpack.config.js @@ -0,0 +1,58 @@ +const webpack = require('webpack'); +const path = require('path'); + +const devBuild = process.env.NODE_ENV !== 'production'; +const nodeEnv = devBuild ? 'development' : 'production'; + +config = { + entry: [ + 'es5-shim/es5-shim', + 'es5-shim/es5-sham', + 'babel-polyfill', + './app/bundles/HelloWorld/startup/HelloWorldApp', + ], + + output: { + filename: 'webpack-bundle.js', + path: '../app/assets/webpack', + }, + + resolve: { + extensions: ['', '.js', '.jsx'], + alias: { + react: path.resolve('./node_modules/react'), + 'react-dom': path.resolve('./node_modules/react-dom'), + }, + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(nodeEnv), + }, + }), + ], + module: { + loaders: [ + { + test: require.resolve('react'), + loader: 'imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham', + }, + { + test: /\.jsx?$/, loader: 'babel-loader', + exclude: /node_modules/, + }, + ], + }, +}; + +module.exports = config; + +if (devBuild) { + console.log('Webpack dev build for Rails'); // eslint-disable-line no-console + module.exports.devtool = 'eval-source-map'; +} else { + config.plugins.push( + new webpack.optimize.DedupePlugin() + ); + console.log('Webpack production build for Rails'); // eslint-disable-line no-console +} diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 68e54f5d5..630c87165 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -10,3 +10,13 @@ # application.js, application.css, and all non-JS/CSS in app/assets folder are # already added. Rails.application.config.assets.precompile += %w( print.css ) +# Add client/assets/ folders to asset pipeline's search path. +# If you do not want to move existing images and fonts from your Rails app +# you could also consider creating symlinks there that point to the original +# rails directories. In that case, you would not add these paths here. +# If you have a different server bundle file than your client bundle, you'll +# need to add it here, like this: +# Rails.application.config.assets.precompile += %w( server-bundle.js ) + +# Add folder with webpack generated assets to assets.paths +Rails.application.config.assets.paths << Rails.root.join("app", "assets", "webpack") diff --git a/config/initializers/react_on_rails.rb b/config/initializers/react_on_rails.rb new file mode 100644 index 000000000..1619a278a --- /dev/null +++ b/config/initializers/react_on_rails.rb @@ -0,0 +1,80 @@ +# Shown below are the defaults for configuration +ReactOnRails.configure do |config| + # Client bundles are configured in application.js + + # Directory where your generated assets go. All generated assets must go to the same directory. + # Configure this in your webpack config files. This relative to your Rails root directory. + config.generated_assets_dir = File.join(%w(app assets webpack)) + + # Define the files we need to check for webpack compilation when running tests. + config.webpack_generated_files = %w( webpack-bundle.js ) + + # This is the file used for server rendering of React when using `(prerender: true)` + # If you are never using server rendering, you may set this to "". + # If you are using the same file for client and server rendering, having this set probably does + # not affect performance. + config.server_bundle_js_file = "webpack-bundle.js" + + # If you are using the ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + # with rspec then this controls what npm command is run + # to automatically refresh your webpack assets on every test run. + config.npm_build_test_command = "npm run build:test" + + # This configures the script to run to build the production assets by webpack. Set this to nil + # if you don't want react_on_rails building this file for you. + config.npm_build_production_command = "npm run build:production" + + ################################################################################ + # CLIENT RENDERING OPTIONS + # Below options can be overriden by passing options to the react_on_rails + # `render_component` view helper method. + ################################################################################ + # default is false + config.prerender = false + + # default is true for development, off otherwise + config.trace = Rails.env.development? + + ################################################################################ + # SERVER RENDERING OPTIONS + ################################################################################ + # If set to true, this forces Rails to reload the server bundle if it is modified + config.development_mode = Rails.env.development? + + # For server rendering. This can be set to false so that server side messages are discarded. + # Default is true. Be cautious about turning this off. + config.replay_console = true + + # Default is true. Logs server rendering messages to Rails.logger.info + config.logging_on_server = true + + config.raise_on_prerender_error = false # change to true to raise exception on server if the JS code throws + + # Server rendering only (not for render_component helper) + # You can configure your pool of JS virtual machines and specify where it should load code: + # On MRI, use `therubyracer` for the best performance + # (see [discussion](https://github.com/reactjs/react-rails/pull/290)) + # On MRI, you'll get a deadlock with `pool_size` > 1 + # If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering. + config.server_renderer_pool_size = 1 # increase if you're on JRuby + config.server_renderer_timeout = 20 # seconds + + ################################################################################ + # MISCELLANEOUS OPTIONS + ################################################################################ + + # Default is false, enable if your content security policy doesn't include `style-src: 'unsafe-inline'` + config.skip_display_none = false + + # The server render method - either ExecJS or NodeJS + config.server_render_method = "ExecJS" + + # Client js uses assets not digested by rails. + # For any asset matching this regex, non-digested symlink will be created + # To disable symlinks set this parameter to nil. + config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/ + + ActiveSupport.on_load(:action_view) do + include ReactOnRailsHelper + end +end diff --git a/config/routes.rb b/config/routes.rb index fd73aaffe..544714e6b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Reservations::Application.routes.draw do + get 'hello_world', to: 'hello_world#index' root to: 'catalog#index' # routes for Devise diff --git a/package.json b/package.json new file mode 100644 index 000000000..fff2d91c8 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "0.0.1", + "engines": { + "node": "5.10.0", + "npm": "3.5.0" + }, + "scripts": { + "postinstall": "cd client && npm install", + "rails-server": "echo 'visit http://localhost:3000/hello_world' && foreman start -f Procfile.dev", + "test": "rspec" + } +} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b7930bbf..e43a6330e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,10 @@ ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| + # Ensure that if we are running js tests, we are using latest webpack assets + # This will use the defaults of :js and :server_rendering meta tags + ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + # ## Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: From da93054ddd05d554286cfcfe26edd05bf00fa989 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Fri, 3 Jun 2016 12:21:01 -0400 Subject: [PATCH 03/18] add ActiveRecordSerializers --- Gemfile | 1 + Gemfile.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Gemfile b/Gemfile index f44d2695d..323ba593f 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,7 @@ gem 'kaminari', '~> 0.16.3' gem 'draper', '~> 2.1.0' gem 'inline_svg', '~> 0.8.0' gem 'react_on_rails', '~> 6.0.1' +gem 'active_model_serializers', '~> 0.10.0' # forms / formatting gem 'simple_form', '~> 3.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index eeeb0f977..8961831ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,6 +20,10 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_model_serializers (0.10.0) + actionpack (>= 4.0) + activemodel (>= 4.0) + railties (>= 4.0) activejob (4.2.6) activesupport (= 4.2.6) globalid (>= 0.3.0) @@ -455,6 +459,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers (~> 0.10.0) awesome_print (~> 1.6.1) aws-sdk (< 2.0) bootstrap-sass (~> 3.3.6) From c1c4f4e2a2732637322863e5d3a1f2440cac5e32 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Mon, 6 Jun 2016 11:41:43 -0400 Subject: [PATCH 04/18] init of user profile react; does not work yet --- app/controllers/users_controller.rb | 14 ++++++ app/serializers/user_serializer.rb | 4 ++ app/views/users/show.html.erb | 2 + client/app/bundles/user/UserApp.jsx | 25 +++++++++++ .../app/bundles/user/userActionCreators.jsx | 8 ++++ client/app/bundles/user/userConstants.jsx | 8 ++++ client/app/bundles/user/userContainer.jsx | 31 +++++++++++++ client/app/bundles/user/userInfoTable.jsx | 45 +++++++++++++++++++ client/app/bundles/user/userReducer.jsx | 21 +++++++++ client/app/bundles/user/userStore.jsx | 30 +++++++++++++ client/webpack.config.js | 1 + 11 files changed, 189 insertions(+) create mode 100644 app/serializers/user_serializer.rb create mode 100644 client/app/bundles/user/UserApp.jsx create mode 100644 client/app/bundles/user/userActionCreators.jsx create mode 100644 client/app/bundles/user/userConstants.jsx create mode 100644 client/app/bundles/user/userContainer.jsx create mode 100644 client/app/bundles/user/userInfoTable.jsx create mode 100644 client/app/bundles/user/userReducer.jsx create mode 100644 client/app/bundles/user/userStore.jsx diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 69bb2dacf..534c4f659 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,7 @@ # rubocop:disable ClassLength class UsersController < ApplicationController + include ReactOnRails::Controller + load_and_authorize_resource layout 'application_with_sidebar', only: [:show, :edit] @@ -12,6 +14,8 @@ class UsersController < ApplicationController only: [:show, :edit, :update, :destroy, :ban, :unban] before_action :check_cas_auth, only: [:show, :new, :create, :quick_create, :edit, :update] + before_action :props, only: [:show, :edit] + before_action :init_store, only: [:show, :edit] include Autocomplete include Calendarable @@ -273,4 +277,14 @@ def generate_calendar_resource def calendar_name_method :equipment_model end + + # React on Rails / Redux methods + + def init_store + redux_store("UserStore", props: @props) + end + + def props + @props = ActiveModelSerializers::SerializableResource.new(@user) + end end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 000000000..28691b725 --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,4 @@ +class UserSerializer < ActiveModel::Serializer + attributes :id, :email, :first_name, :last_name, :nickname, :phone, + :affiliation, :terms_of_service_accepted, :role +end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 5ad8d466c..be6558432 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -27,6 +27,8 @@
+ <%= react_component('UserApp', props: @props) %> +
<% if @cas_auth %> diff --git a/client/app/bundles/user/UserApp.jsx b/client/app/bundles/user/UserApp.jsx new file mode 100644 index 000000000..bd4191888 --- /dev/null +++ b/client/app/bundles/user/UserApp.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactOnRails from 'react-on-rails'; +import { Provider } from 'react-redux'; + +import createStore from './userStore'; +import User from './userContainer'; + +// See documentation for https://github.com/reactjs/react-redux. +// This is how you get props from the Rails view into the redux store. +// This code here binds your smart component to the redux store. +// railsContext provides contextual information especially useful for server rendering, such as +// knowing the locale. See the React on Rails documentation for more info on the railsContext +const UserApp = (props, _railsContext) => { + const store = createStore(props); + const reactComponent = ( + + + + ); + return reactComponent; +}; + +// This is how react_on_rails can see the HelloWorldApp in the browser. +ReactOnRails.register({ UserApp }); + diff --git a/client/app/bundles/user/userActionCreators.jsx b/client/app/bundles/user/userActionCreators.jsx new file mode 100644 index 000000000..3c13ace6d --- /dev/null +++ b/client/app/bundles/user/userActionCreators.jsx @@ -0,0 +1,8 @@ +import actionTypes from './userConstants'; + +export function toggleEditMode() { + return { + type: actionTypes.USER_EDIT_MODE_TOGGLE, + }; +} + diff --git a/client/app/bundles/user/userConstants.jsx b/client/app/bundles/user/userConstants.jsx new file mode 100644 index 000000000..3c0de2a25 --- /dev/null +++ b/client/app/bundles/user/userConstants.jsx @@ -0,0 +1,8 @@ +import mirrorCreator from 'mirror-creator'; + +const actionTypes = mirrorCreator([ + 'USER_EDIT_MODE_TOGGLE', +]); + +export default actionTypes; + diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/bundles/user/userContainer.jsx new file mode 100644 index 000000000..9c9ebe1c9 --- /dev/null +++ b/client/app/bundles/user/userContainer.jsx @@ -0,0 +1,31 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import Immutable from 'immutable'; +import UserInfoTable from './userInfoTable'; +import * as userActionCreators from './userActionCreators'; + +function select(state) { + return { $$userStore: state.$$userStore }; +} + +const User = (props) => { + const { dispatch, $$userStore } = props; + const actions = bindActionCreators(userActionCreators, dispatch); + const { toggleEditMode } = actions; + const editMode = $$userStore.get('editMode'); + + return ( + + ); +}; + +User.propTypes = { + dispatch: PropTypes.func.isRequired, + + $$userStore: PropTypes.instanceOf(Immutable.Map).isRequired, +}; + +// Don't forget to actually use connect! +export default connect(select)(User); + diff --git a/client/app/bundles/user/userInfoTable.jsx b/client/app/bundles/user/userInfoTable.jsx new file mode 100644 index 000000000..e35cb82b3 --- /dev/null +++ b/client/app/bundles/user/userInfoTable.jsx @@ -0,0 +1,45 @@ +import React, { PropTypes } from 'react'; + +export default class UserInfoTable extends React.Component { + static propTypes = { + // If you have lots of data or action properties, you should consider grouping them by + // passing two properties: "data" and "actions". + toggleEditMode: PropTypes.func.isRequired, + editMode: PropTypes.bool.isRequired, + user: PropTypes.object.isRequired, + }; + + // React will automatically provide us with the event `e` + handleChange(e) { + const editMode = e.target.value; + this.props.toggleEditMode(editMode); + } + + render() { + const user = this.state.user + return ( +
+
+
First Name
+
user.first_name
+ +
Last Name
+
user.last_name
+ +
Nickname
+
user.nickname
+ +
Phone
+
user.phone
+ +
Email
+
mail_to user.email, user.email
+ +
Affiliation
+
user.affiliation
+
+
+ ); + } +} + diff --git a/client/app/bundles/user/userReducer.jsx b/client/app/bundles/user/userReducer.jsx new file mode 100644 index 000000000..d38f5b746 --- /dev/null +++ b/client/app/bundles/user/userReducer.jsx @@ -0,0 +1,21 @@ +import Immutable from 'immutable'; + +import actionTypes from './userConstants'; + +// this is the default state that would be used if one were not passed into the store +export const $$initialState = Immutable.fromJS({ + editMode: false, +}); + +export default function userReducer($$state = $$initialState, action) { + const { type } = action; + + switch (type) { + case actionTypes.USER_EDIT_MODE_TOGGLE: + return $$state.set('editMode', !$$state.editMode); + + default: + return $$state; + } +} + diff --git a/client/app/bundles/user/userStore.jsx b/client/app/bundles/user/userStore.jsx new file mode 100644 index 000000000..ecd8eb34a --- /dev/null +++ b/client/app/bundles/user/userStore.jsx @@ -0,0 +1,30 @@ +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; + +// See +// https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html +// This is not actually used for this simple example, but you'd probably want to use this +// once your app has asynchronous actions. +// import thunkMiddleware from 'redux-thunk'; + +import reducers from './userReducer'; +import { initialState } from './userReducer'; + +export default props => { + // This is how we get initial props Rails into redux. + const { $$userState } = initialState; + + // Redux expects to initialize the store using an Object, not an Immutable.Map + const initialState = { + $$userStore: $$userState.merge({ ...props }), + }; + + const reducer = combineReducers(reducers); + const composedStore = compose( + applyMiddleware(thunkMiddleware) + ); + const storeCreator = composedStore(createStore); + const store = storeCreator(reducer, initialState); + + return store; +}; + diff --git a/client/webpack.config.js b/client/webpack.config.js index 54d2dd570..113f24cf6 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -10,6 +10,7 @@ config = { 'es5-shim/es5-sham', 'babel-polyfill', './app/bundles/HelloWorld/startup/HelloWorldApp', + './app/bundles/user/UserApp', ], output: { From 9c8149050c634569c4b4e1d90e9e9483a96fa4f5 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Mon, 6 Jun 2016 16:02:53 -0400 Subject: [PATCH 05/18] working but rough --- app/controllers/users_controller.rb | 8 +- app/views/users/show.html.erb | 29 +------ client/app/bundles/reducers.jsx | 12 +++ client/app/bundles/user/UserProfile.jsx | 101 ++++++++++++++++++++++ client/app/bundles/user/userContainer.jsx | 3 +- client/app/bundles/user/userStore.jsx | 6 +- client/webpack.config.js | 1 + 7 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 client/app/bundles/reducers.jsx create mode 100644 client/app/bundles/user/UserProfile.jsx diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 534c4f659..ec3ec1792 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -15,7 +15,7 @@ class UsersController < ApplicationController before_action :check_cas_auth, only: [:show, :new, :create, :quick_create, :edit, :update] before_action :props, only: [:show, :edit] - before_action :init_store, only: [:show, :edit] + #before_action :init_store, only: [:show, :edit] include Autocomplete include Calendarable @@ -280,9 +280,9 @@ def calendar_name_method # React on Rails / Redux methods - def init_store - redux_store("UserStore", props: @props) - end + #def init_store + # redux_store("UserStore", props: @props) + #end def props @props = ActiveModelSerializers::SerializableResource.new(@user) diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index be6558432..e2c1c55aa 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -27,34 +27,7 @@
- <%= react_component('UserApp', props: @props) %> - -
-
- <% if @cas_auth %> -
Username
-
<%= @user.username %>
- <% end %> - -
First Name
-
<%= @user.first_name %>
- -
Last Name
-
<%= @user.last_name %>
- -
Nickname
-
<%= !@user.nickname.blank? ? @user.nickname : "(none)" %>
- -
Phone
-
<%= !@user.phone.blank? ? @user.phone : "(none)" %>
- -
Email
-
<%= mail_to @user.email, @user.email %>
- -
Affiliation
-
<%= @user.affiliation %>
-
-
+ <%= react_component('UserProfile', props: @props) %>
diff --git a/client/app/bundles/reducers.jsx b/client/app/bundles/reducers.jsx new file mode 100644 index 000000000..e0190045c --- /dev/null +++ b/client/app/bundles/reducers.jsx @@ -0,0 +1,12 @@ +// This file is our manifest of all reducers for the app. +import userReducer from './user/userReducer'; +import { $$initialState as $$userState } from './user/userReducer'; + +export default { + $$userStore: userReducer, +}; + +export const initialStates = { + $$userState, +}; + diff --git a/client/app/bundles/user/UserProfile.jsx b/client/app/bundles/user/UserProfile.jsx new file mode 100644 index 000000000..057de3774 --- /dev/null +++ b/client/app/bundles/user/UserProfile.jsx @@ -0,0 +1,101 @@ +import React, { PropTypes } from 'react'; +import ReactOnRails from 'react-on-rails'; +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; +import { bindActionCreators } from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import { Provider } from 'react-redux'; +import { connect } from 'react-redux'; +import mirrorCreator from 'mirror-creator'; +import Immutable from 'immutable'; + +const initialState = { + editMode: false, + user: null +} + +const userReducer = (state = initialState, action) => { + switch (action.type) { + case 'SET_USER': + return { + ...state, + user: action.user + }; + case 'TOGGLE_EDIT_MODE': + return { + ...state, + editMode: !state.editMode + }; + default: + return state; + } +}; + +const store = createStore(userReducer); + +class UserInfo extends React.Component { + render() { + let editing = this.props.editMode; + let color = editing ? 'red' : ''; + return ( +
+ + {EditableTable(this.props.user, this.props.editMode)} +
+ ); + } +} + +const EditableTable = (user, editMode) => { + return ( +
+
+
First Name
+
{user.first_name}
+ +
Last Name
+
{user.last_name}
+ +
Nickname
+
{user.nickname.blank}
+ +
Phone
+
{user.phone}
+ +
Email
+
{user.email}
+ +
Affiliation
+
{user.affiliation}
+
+
+ ); +} + +const UserProfile = (props, _railsContext) => { + if (store.getState().user === null) { + store.dispatch({ + type: 'SET_USER', + user: props + }); + } + + const reactComponent = ( + + ); + return reactComponent; +}; + +store.subscribe(UserProfile); + +// This is how react_on_rails can see the HelloWorldApp in the browser. +ReactOnRails.register({ UserProfile }); + diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/bundles/user/userContainer.jsx index 9c9ebe1c9..784299ab0 100644 --- a/client/app/bundles/user/userContainer.jsx +++ b/client/app/bundles/user/userContainer.jsx @@ -14,9 +14,10 @@ const User = (props) => { const actions = bindActionCreators(userActionCreators, dispatch); const { toggleEditMode } = actions; const editMode = $$userStore.get('editMode'); + const user = $$userStore.get('user'); return ( - + ); }; diff --git a/client/app/bundles/user/userStore.jsx b/client/app/bundles/user/userStore.jsx index ecd8eb34a..372d88c8c 100644 --- a/client/app/bundles/user/userStore.jsx +++ b/client/app/bundles/user/userStore.jsx @@ -1,4 +1,5 @@ import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; +import thunkMiddleware from 'redux-thunk'; // See // https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html @@ -6,12 +7,11 @@ import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; // once your app has asynchronous actions. // import thunkMiddleware from 'redux-thunk'; -import reducers from './userReducer'; -import { initialState } from './userReducer'; +import reducers, { initialStates } from '../reducers'; export default props => { // This is how we get initial props Rails into redux. - const { $$userState } = initialState; + const { $$userState } = initialStates; // Redux expects to initialize the store using an Object, not an Immutable.Map const initialState = { diff --git a/client/webpack.config.js b/client/webpack.config.js index 113f24cf6..1ec542a42 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -11,6 +11,7 @@ config = { 'babel-polyfill', './app/bundles/HelloWorld/startup/HelloWorldApp', './app/bundles/user/UserApp', + './app/bundles/user/UserProfile', ], output: { From 7f77adbdffa0c850e2a56937ebce6628b519db13 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Tue, 7 Jun 2016 16:07:48 -0400 Subject: [PATCH 06/18] works a bit better, does not save updates yet, but things are more organized --- app/controllers/users_controller.rb | 4 + client/.bootstraprc | 104 +++++++++++++++++++ client/app/bundles/user/Profile.jsx | 98 +++++++++++++++++ client/app/bundles/user/UserProfile.jsx | 84 ++------------- client/app/bundles/user/editableTable.jsx | 49 +++++++++ client/app/bundles/user/editableTextItem.jsx | 21 ++++ client/app/bundles/user/registration.jsx | 7 ++ client/app/bundles/user/table.jsx | 31 ++++++ client/app/bundles/user/userContainer.jsx | 56 ++++++---- client/app/bundles/user/userReducer.jsx | 47 ++++++--- client/webpack.config.js | 5 +- 11 files changed, 390 insertions(+), 116 deletions(-) create mode 100644 client/.bootstraprc create mode 100644 client/app/bundles/user/Profile.jsx create mode 100644 client/app/bundles/user/editableTable.jsx create mode 100644 client/app/bundles/user/editableTextItem.jsx create mode 100644 client/app/bundles/user/registration.jsx create mode 100644 client/app/bundles/user/table.jsx diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ec3ec1792..b7b617051 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -245,6 +245,10 @@ def find # rubocop:disable CyclomaticComplexity, PerceivedComplexity end end + def js_update + flash[:error] unless @user.update(user_params) + end + private def user_params diff --git a/client/.bootstraprc b/client/.bootstraprc new file mode 100644 index 000000000..7b41542cc --- /dev/null +++ b/client/.bootstraprc @@ -0,0 +1,104 @@ +--- +# Output debugging info +# loglevel: debug + +# Major version of Bootstrap: 3 or 4 +bootstrapVersion: 3 + +# If Bootstrap version 3 is used - turn on/off custom icon font path +useCustomIconFontPath: false + +# Webpack loaders, order matters +styleLoaders: + - style + - css + - sass + +# Extract styles to stand-alone css file +# Different settings for different environments can be used, +# It depends on value of NODE_ENV environment variable +# This param can also be set in webpack config: +# entry: 'bootstrap-loader/extractStyles' +extractStyles: false +# env: +# development: +# extractStyles: false +# production: +# extractStyles: true + + +# Customize Bootstrap variables that get imported before the original Bootstrap variables. +# Thus, derived Bootstrap variables can depend on values from here. +# See the Bootstrap _variables.scss file for examples of derived Bootstrap variables. +# +# preBootstrapCustomizations: ./path/to/bootstrap/pre-customizations.scss + + +# This gets loaded after bootstrap/variables is loaded +# Thus, you may customize Bootstrap variables +# based on the values established in the Bootstrap _variables.scss file +# +# bootstrapCustomizations: ./path/to/bootstrap/customizations.scss + + +# Import your custom styles here +# Usually this endpoint-file contains list of @imports of your application styles +# +# appStyles: ./path/to/your/app/styles/endpoint.scss + + +### Bootstrap styles +styles: + + # Mixins + mixins: true + + # Reset and dependencies + normalize: true + print: true + glyphicons: true + + # Core CSS + scaffolding: true + type: true + code: true + grid: true + tables: true + forms: true + buttons: true + + # Components + component-animations: true + dropdowns: true + button-groups: true + input-groups: true + navs: true + navbar: true + breadcrumbs: true + pagination: true + pager: true + labels: true + badges: true + jumbotron: true + thumbnails: true + alerts: true + progress-bars: true + media: true + list-group: true + panels: true + wells: true + responsive-embed: true + close: true + + # Components w/ JavaScript + modals: true + tooltip: true + popovers: true + carousel: true + + # Utility classes + utilities: true + responsive-utilities: true + +### Bootstrap scripts +scripts: false diff --git a/client/app/bundles/user/Profile.jsx b/client/app/bundles/user/Profile.jsx new file mode 100644 index 000000000..493a017ea --- /dev/null +++ b/client/app/bundles/user/Profile.jsx @@ -0,0 +1,98 @@ +import React, { PropTypes } from 'react'; +import ReactOnRails from 'react-on-rails'; +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; +import { bindActionCreators } from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import { Provider } from 'react-redux'; +import { connect } from 'react-redux'; +import mirrorCreator from 'mirror-creator'; +import Immutable from 'immutable'; + +const initialState = { + editMode: false, + user: null +} + +const userReducer = (state = initialState, action) => { + switch (action.type) { + case 'SET_USER': + return { + ...state, + user: action.user + }; + case 'TOGGLE_EDIT_MODE': + return { + ...state, + editMode: !state.editMode + }; + default: + return state; + } +}; + +const store = createStore(userReducer); + +class UserInfo extends React.Component { + render() { + let editing = this.props.editMode; + let color = editing ? 'red' : ''; + return ( +
+ + {EditableTable(this.props.user, this.props.editMode)} +
+ ); + } +} + +const EditableTable = (user, editMode) => { + return ( +
+
+
First Name
+
{user.first_name}
+ +
Last Name
+
{user.last_name}
+ +
Nickname
+
{user.nickname.blank}
+ +
Phone
+
{user.phone}
+ +
Email
+
{user.email}
+ +
Affiliation
+
{user.affiliation}
+
+
+ ); +} + +export default (props, _railsContext) => { + if (store.getState().user === null) { + store.dispatch({ + type: 'SET_USER', + user: props + }); + } + + const reactComponent = ( +
+

OLD CODE

+ +
+ ); + return reactComponent; +}; diff --git a/client/app/bundles/user/UserProfile.jsx b/client/app/bundles/user/UserProfile.jsx index 057de3774..afbf12331 100644 --- a/client/app/bundles/user/UserProfile.jsx +++ b/client/app/bundles/user/UserProfile.jsx @@ -8,76 +8,12 @@ import { connect } from 'react-redux'; import mirrorCreator from 'mirror-creator'; import Immutable from 'immutable'; -const initialState = { - editMode: false, - user: null -} +import User from './userContainer'; +import userReducer from './userReducer'; -const userReducer = (state = initialState, action) => { - switch (action.type) { - case 'SET_USER': - return { - ...state, - user: action.user - }; - case 'TOGGLE_EDIT_MODE': - return { - ...state, - editMode: !state.editMode - }; - default: - return state; - } -}; - -const store = createStore(userReducer); - -class UserInfo extends React.Component { - render() { - let editing = this.props.editMode; - let color = editing ? 'red' : ''; - return ( -
- - {EditableTable(this.props.user, this.props.editMode)} -
- ); - } -} - -const EditableTable = (user, editMode) => { - return ( -
-
-
First Name
-
{user.first_name}
- -
Last Name
-
{user.last_name}
-
Nickname
-
{user.nickname.blank}
- -
Phone
-
{user.phone}
- -
Email
-
{user.email}
- -
Affiliation
-
{user.affiliation}
-
-
- ); -} - -const UserProfile = (props, _railsContext) => { +export default (props, _railsContext) => { + const store = createStore(userReducer); if (store.getState().user === null) { store.dispatch({ type: 'SET_USER', @@ -86,16 +22,10 @@ const UserProfile = (props, _railsContext) => { } const reactComponent = ( - + + + ); return reactComponent; }; -store.subscribe(UserProfile); - -// This is how react_on_rails can see the HelloWorldApp in the browser. -ReactOnRails.register({ UserProfile }); - diff --git a/client/app/bundles/user/editableTable.jsx b/client/app/bundles/user/editableTable.jsx new file mode 100644 index 000000000..3dff616cc --- /dev/null +++ b/client/app/bundles/user/editableTable.jsx @@ -0,0 +1,49 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +const Item = ({ title, text }) => { + return ( +
+
{title}
+
{text}
+
+ ); +} + +const EditableItem = ({ title, text }) => { + return ( +
+
{title}
+
+
+ ); +} + +const EditableTable = ({ user, editing }) => { + const LocalItem = editing ? EditableItem : Item; + const nickname = user.nickname === '' ? '(none)' : `${user.nickname}`; + const phone = user.phone === '' ? '(none)' : `${user.phone}`; + + return ( +
+
+
+ + + + + +
+
+
+ ); +} + +const mapStateToProps = (state) => { + return { + user: state.user, + editing: state.editMode, + } +} + +export default connect(mapStateToProps)(EditableTable) diff --git a/client/app/bundles/user/editableTextItem.jsx b/client/app/bundles/user/editableTextItem.jsx new file mode 100644 index 000000000..90908ea01 --- /dev/null +++ b/client/app/bundles/user/editableTextItem.jsx @@ -0,0 +1,21 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +const EditableItem = ({ editing, title, text }) => { + const item = editing ? : text + return ( +
+
{title}
+
{item}
+
+ ); +} + +const mapStateToProps = (state) => { + return { + editing: state.editing, + } +} + +export default connect(mapStateToProps)(EditableItem) + diff --git a/client/app/bundles/user/registration.jsx b/client/app/bundles/user/registration.jsx new file mode 100644 index 000000000..ba4e1a03d --- /dev/null +++ b/client/app/bundles/user/registration.jsx @@ -0,0 +1,7 @@ +import ReactOnRails from 'react-on-rails'; + +import UserProfile from './UserProfile'; + +ReactOnRails.register({ + UserProfile, +}); diff --git a/client/app/bundles/user/table.jsx b/client/app/bundles/user/table.jsx new file mode 100644 index 000000000..ba5b42724 --- /dev/null +++ b/client/app/bundles/user/table.jsx @@ -0,0 +1,31 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +const Item = ({ title, text }) => { + return ( +
+
{title}
+
{text}
+
+ ); +} + +const Table = ({ user }) => { + return ( +
+
+ + +
+
+ ); +} + +const mapStateToProps = (state) => { + return { + user: state.user + } +} + +export default connect(mapStateToProps)(Table) + diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/bundles/user/userContainer.jsx index 784299ab0..b1430195c 100644 --- a/client/app/bundles/user/userContainer.jsx +++ b/client/app/bundles/user/userContainer.jsx @@ -1,32 +1,46 @@ import React, { PropTypes } from 'react'; -import { connect } from 'react-redux'; +import ReactOnRails from 'react-on-rails'; +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; import { bindActionCreators } from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import { Provider } from 'react-redux'; +import { connect } from 'react-redux'; +import mirrorCreator from 'mirror-creator'; import Immutable from 'immutable'; -import UserInfoTable from './userInfoTable'; -import * as userActionCreators from './userActionCreators'; -function select(state) { - return { $$userStore: state.$$userStore }; -} +import EditableTable from './editableTable' -const User = (props) => { - const { dispatch, $$userStore } = props; - const actions = bindActionCreators(userActionCreators, dispatch); - const { toggleEditMode } = actions; - const editMode = $$userStore.get('editMode'); - const user = $$userStore.get('user'); +const UserInfo = ({ user, editing, onEditClick, onCancelClick }) => { + const color = editing ? 'success' : 'primary'; + const text = editing ? 'Save' : 'Edit User'; + const cancel = editing + ? + : null; return ( - +
+ +
+ + {cancel} +
+
); -}; - -User.propTypes = { - dispatch: PropTypes.func.isRequired, +} - $$userStore: PropTypes.instanceOf(Immutable.Map).isRequired, -}; +const mapStateToProps = (state) => { + return { + editing: state.editMode, + user: state.user, + } +} -// Don't forget to actually use connect! -export default connect(select)(User); +const mapDispatchToProps = (dispatch) => { + return { + onEditClick: (user) => { + dispatch({ type: 'TOGGLE_EDIT_MODE', user: user }) }, + onCancelClick: () => { dispatch({ type: 'CANCEL_EDIT_MODE' }) }, + } +} +export default connect(mapStateToProps, mapDispatchToProps)(UserInfo) diff --git a/client/app/bundles/user/userReducer.jsx b/client/app/bundles/user/userReducer.jsx index d38f5b746..60f8f0be1 100644 --- a/client/app/bundles/user/userReducer.jsx +++ b/client/app/bundles/user/userReducer.jsx @@ -1,21 +1,38 @@ -import Immutable from 'immutable'; +import React, { PropTypes } from 'react'; -import actionTypes from './userConstants'; - -// this is the default state that would be used if one were not passed into the store -export const $$initialState = Immutable.fromJS({ +const initialState = { editMode: false, -}); - -export default function userReducer($$state = $$initialState, action) { - const { type } = action; - - switch (type) { - case actionTypes.USER_EDIT_MODE_TOGGLE: - return $$state.set('editMode', !$$state.editMode); + user: null +} +export default function userReducer(state = initialState, action) { + switch (action.type) { + case 'SET_USER': + return { + ...state, + user: action.user + }; + case 'TOGGLE_EDIT_MODE': + const user = action.user; + if (state.editMode) { + $.ajax({ + type: 'PUT', + url: `/users/${user.id}`, + data: { user: user }, + dataType: 'json', + }); + } + return { + ...state, + editMode: !state.editMode, + user: user + }; + case 'CANCEL_EDIT_MODE': + return { + ...state, + editMode: false + }; default: - return $$state; + return state; } } - diff --git a/client/webpack.config.js b/client/webpack.config.js index 1ec542a42..edee57309 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -9,9 +9,8 @@ config = { 'es5-shim/es5-shim', 'es5-shim/es5-sham', 'babel-polyfill', - './app/bundles/HelloWorld/startup/HelloWorldApp', - './app/bundles/user/UserApp', - './app/bundles/user/UserProfile', + './app/bundles/user/registration', + './app/bundles/user/Profile', ], output: { From 62135ab7ea8827c68b976247bacdde84c680d6df Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 12:32:59 -0400 Subject: [PATCH 07/18] working form --- app/controllers/users_controller.rb | 55 +++++++----- client/app/bundles/user/Profile.jsx | 98 -------------------- client/app/bundles/user/editableTable.jsx | 104 +++++++++++++++------- client/app/bundles/user/table.jsx | 23 +++-- client/app/bundles/user/userContainer.jsx | 37 ++++---- client/app/bundles/user/userReducer.jsx | 21 ++--- client/webpack.config.js | 1 - 7 files changed, 149 insertions(+), 190 deletions(-) delete mode 100644 client/app/bundles/user/Profile.jsx diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b7b617051..4eca511f2 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -173,28 +173,9 @@ def edit end def update # rubocop:disable CyclomaticComplexity, PerceivedComplexity - @edit_title_text = (current_user == @user) ? 'Profile' : 'User' - par = user_params - # use :update_with_password when we're not using CAS and you're editing - # your own profile - if @cas_auth || ((can? :manage, User) && (@user.id != current_user.id)) - method = :update_attributes - # delete the current_password key from the params hash just in case it's - # present (and :update_attributes will throw an error) - par.delete('current_password') - else - method = :update_with_password - # make sure we update the username as well - par[:username] = par[:email] - end - if @user.send(method, par) - # sign in the user if you've edited yourself since you have a new - # password, otherwise don't - sign_in @user, bypass: true if @user.id == current_user.id - flash[:notice] = 'Successfully updated user.' - redirect_to user_path(@user) - else - render :edit + respond_to do |format| + format.html { html_update } + format.json { js_update } end end @@ -245,12 +226,38 @@ def find # rubocop:disable CyclomaticComplexity, PerceivedComplexity end end + private + + def html_update + @edit_title_text = (current_user == @user) ? 'Profile' : 'User' + par = user_params + # use :update_with_password when we're not using CAS and you're editing + # your own profile + if @cas_auth || ((can? :manage, User) && (@user.id != current_user.id)) + method = :update_attributes + # delete the current_password key from the params hash just in case it's + # present (and :update_attributes will throw an error) + par.delete('current_password') + else + method = :update_with_password + # make sure we update the username as well + par[:username] = par[:email] + end + if @user.send(method, par) + # sign in the user if you've edited yourself since you have a new + # password, otherwise don't + sign_in @user, bypass: true if @user.id == current_user.id + flash[:notice] = 'Successfully updated user.' + redirect_to user_path(@user) + else + render :edit + end + end + def js_update flash[:error] unless @user.update(user_params) end - private - def user_params permitted_attributes = [:first_name, :last_name, :nickname, :phone, :email, :affiliation, :terms_of_service_accepted, diff --git a/client/app/bundles/user/Profile.jsx b/client/app/bundles/user/Profile.jsx deleted file mode 100644 index 493a017ea..000000000 --- a/client/app/bundles/user/Profile.jsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { PropTypes } from 'react'; -import ReactOnRails from 'react-on-rails'; -import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; -import { bindActionCreators } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { Provider } from 'react-redux'; -import { connect } from 'react-redux'; -import mirrorCreator from 'mirror-creator'; -import Immutable from 'immutable'; - -const initialState = { - editMode: false, - user: null -} - -const userReducer = (state = initialState, action) => { - switch (action.type) { - case 'SET_USER': - return { - ...state, - user: action.user - }; - case 'TOGGLE_EDIT_MODE': - return { - ...state, - editMode: !state.editMode - }; - default: - return state; - } -}; - -const store = createStore(userReducer); - -class UserInfo extends React.Component { - render() { - let editing = this.props.editMode; - let color = editing ? 'red' : ''; - return ( -
- - {EditableTable(this.props.user, this.props.editMode)} -
- ); - } -} - -const EditableTable = (user, editMode) => { - return ( -
-
-
First Name
-
{user.first_name}
- -
Last Name
-
{user.last_name}
- -
Nickname
-
{user.nickname.blank}
- -
Phone
-
{user.phone}
- -
Email
-
{user.email}
- -
Affiliation
-
{user.affiliation}
-
-
- ); -} - -export default (props, _railsContext) => { - if (store.getState().user === null) { - store.dispatch({ - type: 'SET_USER', - user: props - }); - } - - const reactComponent = ( -
-

OLD CODE

- -
- ); - return reactComponent; -}; diff --git a/client/app/bundles/user/editableTable.jsx b/client/app/bundles/user/editableTable.jsx index 3dff616cc..667332879 100644 --- a/client/app/bundles/user/editableTable.jsx +++ b/client/app/bundles/user/editableTable.jsx @@ -1,49 +1,87 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; -const Item = ({ title, text }) => { - return ( -
-
{title}
-
{text}
-
- ); -} +const Form = ({ user, onSave, id }) => { + let firstName, lastName, nickname, phone, email, affiliation; -const EditableItem = ({ title, text }) => { return (
-
{title}
-
-
- ); -} - -const EditableTable = ({ user, editing }) => { - const LocalItem = editing ? EditableItem : Item; - const nickname = user.nickname === '' ? '(none)' : `${user.nickname}`; - const phone = user.phone === '' ? '(none)' : `${user.phone}`; - - return ( -
-
-
- - - - - +
{ + e.preventDefault(); + onSave({ + first_name: firstName.value, + last_name: lastName.value, + nickname: nickname.value, + phone: phone.value, + email: email.value, + affiliation: affiliation.value, + }) + }}> +
+ +
+ { firstName = node } }/>
-
-
+
+
+ +
+ { lastName = node } }/> +
+
+
+ +
+ { nickname = node } }/> +
+
+
+ +
+ { phone = node } }/> +
+
+
+ +
+ { email = node } }/> +
+
+
+ +
+ { affiliation = node } }/> +
+
+ +
); } const mapStateToProps = (state) => { return { user: state.user, - editing: state.editMode, } } -export default connect(mapStateToProps)(EditableTable) +const mapDispatchToProps = (dispatch) => { + return { + onSave: (changes) => { + dispatch({ type: 'UPDATE_USER', changes: changes }) + }, + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Form) diff --git a/client/app/bundles/user/table.jsx b/client/app/bundles/user/table.jsx index ba5b42724..f5ce9e15d 100644 --- a/client/app/bundles/user/table.jsx +++ b/client/app/bundles/user/table.jsx @@ -3,21 +3,26 @@ import { connect } from 'react-redux'; const Item = ({ title, text }) => { return ( -
-
{title}
-
{text}
+
+ +
+

{text}

+
); } const Table = ({ user }) => { + const nickname = user.nickname === '' ? '(none)' : `${user.nickname}`; + const phone = user.phone === '' ? '(none)' : `${user.phone}`; return ( -
-
- - -
-
+
+ + + + + + ); } diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/bundles/user/userContainer.jsx index b1430195c..788ba03f0 100644 --- a/client/app/bundles/user/userContainer.jsx +++ b/client/app/bundles/user/userContainer.jsx @@ -9,20 +9,31 @@ import mirrorCreator from 'mirror-creator'; import Immutable from 'immutable'; import EditableTable from './editableTable' +import Table from './table' -const UserInfo = ({ user, editing, onEditClick, onCancelClick }) => { - const color = editing ? 'success' : 'primary'; - const text = editing ? 'Save' : 'Edit User'; - const cancel = editing - ? +const UserInfo = ({ user, editing, onEditClick }) => { + const table = editing ? : ; + const save = editing + ? : null; - + const color = editing ? 'default' : 'primary'; + const text = editing ? 'Cancel' : 'Edit User'; return (
- -
- - {cancel} +
+
+ {table} +
+
+
+ {save} + +
+
); @@ -36,11 +47,7 @@ const mapStateToProps = (state) => { } const mapDispatchToProps = (dispatch) => { - return { - onEditClick: (user) => { - dispatch({ type: 'TOGGLE_EDIT_MODE', user: user }) }, - onCancelClick: () => { dispatch({ type: 'CANCEL_EDIT_MODE' }) }, - } + return { onEditClick: () => { dispatch({ type: 'TOGGLE_EDIT_MODE' }) }, } } export default connect(mapStateToProps, mapDispatchToProps)(UserInfo) diff --git a/client/app/bundles/user/userReducer.jsx b/client/app/bundles/user/userReducer.jsx index 60f8f0be1..6581e95fd 100644 --- a/client/app/bundles/user/userReducer.jsx +++ b/client/app/bundles/user/userReducer.jsx @@ -2,7 +2,7 @@ import React, { PropTypes } from 'react'; const initialState = { editMode: false, - user: null + user: null, } export default function userReducer(state = initialState, action) { @@ -10,27 +10,28 @@ export default function userReducer(state = initialState, action) { case 'SET_USER': return { ...state, - user: action.user + user: action.user, }; case 'TOGGLE_EDIT_MODE': - const user = action.user; + return { + ...state, + editMode: !state.editMode, + }; + case 'UPDATE_USER': + const user = state.user; + const changes = action.changes if (state.editMode) { $.ajax({ type: 'PUT', url: `/users/${user.id}`, - data: { user: user }, + data: { user: changes }, dataType: 'json', }); } return { ...state, editMode: !state.editMode, - user: user - }; - case 'CANCEL_EDIT_MODE': - return { - ...state, - editMode: false + user: { ...user, ...changes } }; default: return state; diff --git a/client/webpack.config.js b/client/webpack.config.js index edee57309..db155380d 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -10,7 +10,6 @@ config = { 'es5-shim/es5-sham', 'babel-polyfill', './app/bundles/user/registration', - './app/bundles/user/Profile', ], output: { From 518afaabc06fd3bcd1651b06d367f968674bb647 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 15:58:39 -0400 Subject: [PATCH 08/18] appearance improvements + TOS --- app/controllers/users_controller.rb | 4 +++- client/app/bundles/user/editableTable.jsx | 16 +++++++++++++++- client/app/bundles/user/userContainer.jsx | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4eca511f2..666a3d1d8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -256,6 +256,7 @@ def html_update def js_update flash[:error] unless @user.update(user_params) + render nothing: true end def user_params @@ -296,6 +297,7 @@ def calendar_name_method #end def props - @props = ActiveModelSerializers::SerializableResource.new(@user) + @props = ActiveModelSerializers::SerializableResource + .new(@user, scope: current_user, scope_name: :current_user) end end diff --git a/client/app/bundles/user/editableTable.jsx b/client/app/bundles/user/editableTable.jsx index 667332879..b83039590 100644 --- a/client/app/bundles/user/editableTable.jsx +++ b/client/app/bundles/user/editableTable.jsx @@ -2,7 +2,7 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; const Form = ({ user, onSave, id }) => { - let firstName, lastName, nickname, phone, email, affiliation; + let firstName, lastName, nickname, phone, email, affiliation, tos; return (
@@ -15,6 +15,7 @@ const Form = ({ user, onSave, id }) => { phone: phone.value, email: email.value, affiliation: affiliation.value, + terms_of_service_accepted: tos.value, }) }}>
@@ -65,6 +66,19 @@ const Form = ({ user, onSave, id }) => { ref={node => { affiliation = node } }/>
+
+
+
+ +
+
+
); diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/bundles/user/userContainer.jsx index 788ba03f0..8f3103333 100644 --- a/client/app/bundles/user/userContainer.jsx +++ b/client/app/bundles/user/userContainer.jsx @@ -11,13 +11,13 @@ import Immutable from 'immutable'; import EditableTable from './editableTable' import Table from './table' -const UserInfo = ({ user, editing, onEditClick }) => { +const UserInfo = ({ user, canEdit, editing, onEditClick }) => { const table = editing ? :
; const save = editing ? : null; const color = editing ? 'default' : 'primary'; - const text = editing ? 'Cancel' : 'Edit User'; + const text = editing ? 'Cancel' : 'Edit'; return (
From 9771f36c77ce0238d796cc4d1c21e25509833a20 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 16:00:51 -0400 Subject: [PATCH 09/18] delete helloworld --- client/.bootstraprc | 104 ------------------ .../actions/helloWorldActionCreators.jsx | 8 -- .../components/HelloWorldWidget.jsx | 42 ------- .../constants/helloWorldConstants.jsx | 13 --- .../HelloWorld/containers/HelloWorld.jsx | 42 ------- .../HelloWorld/reducers/helloWorldReducer.jsx | 19 ---- .../app/bundles/HelloWorld/reducers/index.jsx | 13 --- .../HelloWorld/startup/HelloWorldApp.jsx | 24 ---- .../HelloWorld/store/helloWorldStore.jsx | 32 ------ 9 files changed, 297 deletions(-) delete mode 100644 client/.bootstraprc delete mode 100644 client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx delete mode 100644 client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx delete mode 100644 client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx delete mode 100644 client/app/bundles/HelloWorld/containers/HelloWorld.jsx delete mode 100644 client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx delete mode 100644 client/app/bundles/HelloWorld/reducers/index.jsx delete mode 100644 client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx delete mode 100644 client/app/bundles/HelloWorld/store/helloWorldStore.jsx diff --git a/client/.bootstraprc b/client/.bootstraprc deleted file mode 100644 index 7b41542cc..000000000 --- a/client/.bootstraprc +++ /dev/null @@ -1,104 +0,0 @@ ---- -# Output debugging info -# loglevel: debug - -# Major version of Bootstrap: 3 or 4 -bootstrapVersion: 3 - -# If Bootstrap version 3 is used - turn on/off custom icon font path -useCustomIconFontPath: false - -# Webpack loaders, order matters -styleLoaders: - - style - - css - - sass - -# Extract styles to stand-alone css file -# Different settings for different environments can be used, -# It depends on value of NODE_ENV environment variable -# This param can also be set in webpack config: -# entry: 'bootstrap-loader/extractStyles' -extractStyles: false -# env: -# development: -# extractStyles: false -# production: -# extractStyles: true - - -# Customize Bootstrap variables that get imported before the original Bootstrap variables. -# Thus, derived Bootstrap variables can depend on values from here. -# See the Bootstrap _variables.scss file for examples of derived Bootstrap variables. -# -# preBootstrapCustomizations: ./path/to/bootstrap/pre-customizations.scss - - -# This gets loaded after bootstrap/variables is loaded -# Thus, you may customize Bootstrap variables -# based on the values established in the Bootstrap _variables.scss file -# -# bootstrapCustomizations: ./path/to/bootstrap/customizations.scss - - -# Import your custom styles here -# Usually this endpoint-file contains list of @imports of your application styles -# -# appStyles: ./path/to/your/app/styles/endpoint.scss - - -### Bootstrap styles -styles: - - # Mixins - mixins: true - - # Reset and dependencies - normalize: true - print: true - glyphicons: true - - # Core CSS - scaffolding: true - type: true - code: true - grid: true - tables: true - forms: true - buttons: true - - # Components - component-animations: true - dropdowns: true - button-groups: true - input-groups: true - navs: true - navbar: true - breadcrumbs: true - pagination: true - pager: true - labels: true - badges: true - jumbotron: true - thumbnails: true - alerts: true - progress-bars: true - media: true - list-group: true - panels: true - wells: true - responsive-embed: true - close: true - - # Components w/ JavaScript - modals: true - tooltip: true - popovers: true - carousel: true - - # Utility classes - utilities: true - responsive-utilities: true - -### Bootstrap scripts -scripts: false diff --git a/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx b/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx deleted file mode 100644 index 78f92150d..000000000 --- a/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import actionTypes from '../constants/helloWorldConstants'; - -export function updateName(name) { - return { - type: actionTypes.HELLO_WORLD_NAME_UPDATE, - name, - }; -} diff --git a/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx b/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx deleted file mode 100644 index 606609f1c..000000000 --- a/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx +++ /dev/null @@ -1,42 +0,0 @@ -// HelloWorldWidget is an arbitrary name for any "dumb" component. We do not recommend suffixing -// all your dump component names with Widget. - -import React, { PropTypes } from 'react'; - -// Simple example of a React "dumb" component -export default class HelloWorldWidget extends React.Component { - static propTypes = { - // If you have lots of data or action properties, you should consider grouping them by - // passing two properties: "data" and "actions". - updateName: PropTypes.func.isRequired, - name: PropTypes.string.isRequired, - }; - - // React will automatically provide us with the event `e` - handleChange(e) { - const name = e.target.value; - this.props.updateName(name); - } - - render() { - const { name } = this.props; - return ( -
-

- Hello, {name}! Welcome to Reservations! -

-
-
- - this.handleChange(e)} - /> - -
- ); - } -} diff --git a/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx b/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx deleted file mode 100644 index f34393c25..000000000 --- a/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx +++ /dev/null @@ -1,13 +0,0 @@ -// See https://www.npmjs.com/package/mirror-creator -// Allows us to set up constants in a slightly more concise syntax. See: -// client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx -import mirrorCreator from 'mirror-creator'; - -const actionTypes = mirrorCreator([ - 'HELLO_WORLD_NAME_UPDATE', -]); - -// actionTypes = {HELLO_WORLD_NAME_UPDATE: "HELLO_WORLD_NAME_UPDATE"} -// Notice how we don't have to duplicate HELLO_WORLD_NAME_UPDATE twice -// thanks to mirror-creator. -export default actionTypes; diff --git a/client/app/bundles/HelloWorld/containers/HelloWorld.jsx b/client/app/bundles/HelloWorld/containers/HelloWorld.jsx deleted file mode 100644 index fe1cb2872..000000000 --- a/client/app/bundles/HelloWorld/containers/HelloWorld.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { PropTypes } from 'react'; -import HelloWorldWidget from '../components/HelloWorldWidget'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import Immutable from 'immutable'; -import * as helloWorldActionCreators from '../actions/helloWorldActionCreators'; - -function select(state) { - // Which part of the Redux global state does our component want to receive as props? - // Note the use of `$$` to prefix the property name because the value is of type Immutable.js - return { $$helloWorldStore: state.$$helloWorldStore }; -} - -// Simple example of a React "smart" component -const HelloWorld = (props) => { - const { dispatch, $$helloWorldStore } = props; - const actions = bindActionCreators(helloWorldActionCreators, dispatch); - const { updateName } = actions; - const name = $$helloWorldStore.get('name'); - - // This uses the ES2015 spread operator to pass properties as it is more DRY - // This is equivalent to: - // - return ( - - ); -}; - -HelloWorld.propTypes = { - dispatch: PropTypes.func.isRequired, - - // This corresponds to the value used in function select above. - // We prefix all property and variable names pointing to Immutable.js objects with '$$'. - // This allows us to immediately know we don't call $$helloWorldStore['someProperty'], but - // instead use the Immutable.js `get` API for Immutable.Map - $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired, -}; - -// Don't forget to actually use connect! -// Note that we don't export HelloWorld, but the redux "connected" version of it. -// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples -export default connect(select)(HelloWorld); diff --git a/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx b/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx deleted file mode 100644 index 3ca225cc7..000000000 --- a/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import Immutable from 'immutable'; - -import actionTypes from '../constants/helloWorldConstants'; - -export const $$initialState = Immutable.fromJS({ - name: '', // this is the default state that would be used if one were not passed into the store -}); - -export default function helloWorldReducer($$state = $$initialState, action) { - const { type, name } = action; - - switch (type) { - case actionTypes.HELLO_WORLD_NAME_UPDATE: - return $$state.set('name', name); - - default: - return $$state; - } -} diff --git a/client/app/bundles/HelloWorld/reducers/index.jsx b/client/app/bundles/HelloWorld/reducers/index.jsx deleted file mode 100644 index 1615975c7..000000000 --- a/client/app/bundles/HelloWorld/reducers/index.jsx +++ /dev/null @@ -1,13 +0,0 @@ -// This file is our manifest of all reducers for the app. -// See also /client/app/bundles/HelloWorld/store/helloWorldStore.jsx -// A real world app will likely have many reducers and it helps to organize them in one file. -import helloWorldReducer from './helloWorldReducer'; -import { $$initialState as $$helloWorldState } from './helloWorldReducer'; - -export default { - $$helloWorldStore: helloWorldReducer, -}; - -export const initialStates = { - $$helloWorldState, -}; diff --git a/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx b/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx deleted file mode 100644 index 603620c04..000000000 --- a/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import ReactOnRails from 'react-on-rails'; -import { Provider } from 'react-redux'; - -import createStore from '../store/helloWorldStore'; -import HelloWorld from '../containers/HelloWorld'; - -// See documentation for https://github.com/reactjs/react-redux. -// This is how you get props from the Rails view into the redux store. -// This code here binds your smart component to the redux store. -// railsContext provides contextual information especially useful for server rendering, such as -// knowing the locale. See the React on Rails documentation for more info on the railsContext -const HelloWorldApp = (props, _railsContext) => { - const store = createStore(props); - const reactComponent = ( - - - - ); - return reactComponent; -}; - -// This is how react_on_rails can see the HelloWorldApp in the browser. -ReactOnRails.register({ HelloWorldApp }); diff --git a/client/app/bundles/HelloWorld/store/helloWorldStore.jsx b/client/app/bundles/HelloWorld/store/helloWorldStore.jsx deleted file mode 100644 index 85d21edd5..000000000 --- a/client/app/bundles/HelloWorld/store/helloWorldStore.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; - -// See -// https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html -// This is not actually used for this simple example, but you'd probably want to use this -// once your app has asynchronous actions. -import thunkMiddleware from 'redux-thunk'; - -import reducers from '../reducers'; -import { initialStates } from '../reducers'; - -export default props => { - // This is how we get initial props Rails into redux. - const { name } = props; - const { $$helloWorldState } = initialStates; - - // Redux expects to initialize the store using an Object, not an Immutable.Map - const initialState = { - $$helloWorldStore: $$helloWorldState.merge({ - name, - }), - }; - - const reducer = combineReducers(reducers); - const composedStore = compose( - applyMiddleware(thunkMiddleware) - ); - const storeCreator = composedStore(createStore); - const store = storeCreator(reducer, initialState); - - return store; -}; From 74ad9224b1689f244f60baa3059e298903ac813c Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 16:04:03 -0400 Subject: [PATCH 10/18] remove the rest of the hello world --- app/controllers/hello_world_controller.rb | 5 ----- app/views/hello_world/index.html.erb | 3 --- client/app/bundles/user/editableTextItem.jsx | 21 -------------------- config/routes.rb | 1 - 4 files changed, 30 deletions(-) delete mode 100644 app/controllers/hello_world_controller.rb delete mode 100644 app/views/hello_world/index.html.erb delete mode 100644 client/app/bundles/user/editableTextItem.jsx diff --git a/app/controllers/hello_world_controller.rb b/app/controllers/hello_world_controller.rb deleted file mode 100644 index a8f38ef25..000000000 --- a/app/controllers/hello_world_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class HelloWorldController < ApplicationController - def index - @hello_world_props = { name: "Stranger" } - end -end diff --git a/app/views/hello_world/index.html.erb b/app/views/hello_world/index.html.erb deleted file mode 100644 index 7f8ef5be9..000000000 --- a/app/views/hello_world/index.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -

Hello World

-<%= react_component("HelloWorldApp", props: @hello_world_props, prerender: false) %> - diff --git a/client/app/bundles/user/editableTextItem.jsx b/client/app/bundles/user/editableTextItem.jsx deleted file mode 100644 index 90908ea01..000000000 --- a/client/app/bundles/user/editableTextItem.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { PropTypes } from 'react'; -import { connect } from 'react-redux'; - -const EditableItem = ({ editing, title, text }) => { - const item = editing ? : text - return ( -
-
{title}
-
{item}
-
- ); -} - -const mapStateToProps = (state) => { - return { - editing: state.editing, - } -} - -export default connect(mapStateToProps)(EditableItem) - diff --git a/config/routes.rb b/config/routes.rb index 544714e6b..fd73aaffe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Reservations::Application.routes.draw do - get 'hello_world', to: 'hello_world#index' root to: 'catalog#index' # routes for Devise From d158cbf1182c3e44280c268b3ba892fbf77fd75f Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 16:13:07 -0400 Subject: [PATCH 11/18] cleanup --- client/app/bundles/user/UserApp.jsx | 25 ---------------- .../app/bundles/user/userActionCreators.jsx | 8 ----- client/app/bundles/user/userConstants.jsx | 8 ----- client/app/bundles/user/userStore.jsx | 30 ------------------- client/app/{bundles => }/reducers.jsx | 0 .../app/{bundles/user => }/registration.jsx | 2 +- client/app/{bundles => }/user/UserProfile.jsx | 0 .../app/{bundles => }/user/editableTable.jsx | 0 client/app/{bundles => }/user/table.jsx | 0 .../app/{bundles => }/user/userContainer.jsx | 0 .../app/{bundles => }/user/userInfoTable.jsx | 0 client/app/{bundles => }/user/userReducer.jsx | 0 client/webpack.config.js | 2 +- package.json | 2 +- 14 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 client/app/bundles/user/UserApp.jsx delete mode 100644 client/app/bundles/user/userActionCreators.jsx delete mode 100644 client/app/bundles/user/userConstants.jsx delete mode 100644 client/app/bundles/user/userStore.jsx rename client/app/{bundles => }/reducers.jsx (100%) rename client/app/{bundles/user => }/registration.jsx (65%) rename client/app/{bundles => }/user/UserProfile.jsx (100%) rename client/app/{bundles => }/user/editableTable.jsx (100%) rename client/app/{bundles => }/user/table.jsx (100%) rename client/app/{bundles => }/user/userContainer.jsx (100%) rename client/app/{bundles => }/user/userInfoTable.jsx (100%) rename client/app/{bundles => }/user/userReducer.jsx (100%) diff --git a/client/app/bundles/user/UserApp.jsx b/client/app/bundles/user/UserApp.jsx deleted file mode 100644 index bd4191888..000000000 --- a/client/app/bundles/user/UserApp.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import ReactOnRails from 'react-on-rails'; -import { Provider } from 'react-redux'; - -import createStore from './userStore'; -import User from './userContainer'; - -// See documentation for https://github.com/reactjs/react-redux. -// This is how you get props from the Rails view into the redux store. -// This code here binds your smart component to the redux store. -// railsContext provides contextual information especially useful for server rendering, such as -// knowing the locale. See the React on Rails documentation for more info on the railsContext -const UserApp = (props, _railsContext) => { - const store = createStore(props); - const reactComponent = ( - - - - ); - return reactComponent; -}; - -// This is how react_on_rails can see the HelloWorldApp in the browser. -ReactOnRails.register({ UserApp }); - diff --git a/client/app/bundles/user/userActionCreators.jsx b/client/app/bundles/user/userActionCreators.jsx deleted file mode 100644 index 3c13ace6d..000000000 --- a/client/app/bundles/user/userActionCreators.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import actionTypes from './userConstants'; - -export function toggleEditMode() { - return { - type: actionTypes.USER_EDIT_MODE_TOGGLE, - }; -} - diff --git a/client/app/bundles/user/userConstants.jsx b/client/app/bundles/user/userConstants.jsx deleted file mode 100644 index 3c0de2a25..000000000 --- a/client/app/bundles/user/userConstants.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import mirrorCreator from 'mirror-creator'; - -const actionTypes = mirrorCreator([ - 'USER_EDIT_MODE_TOGGLE', -]); - -export default actionTypes; - diff --git a/client/app/bundles/user/userStore.jsx b/client/app/bundles/user/userStore.jsx deleted file mode 100644 index 372d88c8c..000000000 --- a/client/app/bundles/user/userStore.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; -import thunkMiddleware from 'redux-thunk'; - -// See -// https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html -// This is not actually used for this simple example, but you'd probably want to use this -// once your app has asynchronous actions. -// import thunkMiddleware from 'redux-thunk'; - -import reducers, { initialStates } from '../reducers'; - -export default props => { - // This is how we get initial props Rails into redux. - const { $$userState } = initialStates; - - // Redux expects to initialize the store using an Object, not an Immutable.Map - const initialState = { - $$userStore: $$userState.merge({ ...props }), - }; - - const reducer = combineReducers(reducers); - const composedStore = compose( - applyMiddleware(thunkMiddleware) - ); - const storeCreator = composedStore(createStore); - const store = storeCreator(reducer, initialState); - - return store; -}; - diff --git a/client/app/bundles/reducers.jsx b/client/app/reducers.jsx similarity index 100% rename from client/app/bundles/reducers.jsx rename to client/app/reducers.jsx diff --git a/client/app/bundles/user/registration.jsx b/client/app/registration.jsx similarity index 65% rename from client/app/bundles/user/registration.jsx rename to client/app/registration.jsx index ba4e1a03d..e5ac4fcd0 100644 --- a/client/app/bundles/user/registration.jsx +++ b/client/app/registration.jsx @@ -1,6 +1,6 @@ import ReactOnRails from 'react-on-rails'; -import UserProfile from './UserProfile'; +import UserProfile from './user/UserProfile'; ReactOnRails.register({ UserProfile, diff --git a/client/app/bundles/user/UserProfile.jsx b/client/app/user/UserProfile.jsx similarity index 100% rename from client/app/bundles/user/UserProfile.jsx rename to client/app/user/UserProfile.jsx diff --git a/client/app/bundles/user/editableTable.jsx b/client/app/user/editableTable.jsx similarity index 100% rename from client/app/bundles/user/editableTable.jsx rename to client/app/user/editableTable.jsx diff --git a/client/app/bundles/user/table.jsx b/client/app/user/table.jsx similarity index 100% rename from client/app/bundles/user/table.jsx rename to client/app/user/table.jsx diff --git a/client/app/bundles/user/userContainer.jsx b/client/app/user/userContainer.jsx similarity index 100% rename from client/app/bundles/user/userContainer.jsx rename to client/app/user/userContainer.jsx diff --git a/client/app/bundles/user/userInfoTable.jsx b/client/app/user/userInfoTable.jsx similarity index 100% rename from client/app/bundles/user/userInfoTable.jsx rename to client/app/user/userInfoTable.jsx diff --git a/client/app/bundles/user/userReducer.jsx b/client/app/user/userReducer.jsx similarity index 100% rename from client/app/bundles/user/userReducer.jsx rename to client/app/user/userReducer.jsx diff --git a/client/webpack.config.js b/client/webpack.config.js index db155380d..57ec939c3 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -9,7 +9,7 @@ config = { 'es5-shim/es5-shim', 'es5-shim/es5-sham', 'babel-polyfill', - './app/bundles/user/registration', + './app/registration', ], output: { diff --git a/package.json b/package.json index fff2d91c8..a07191333 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "scripts": { "postinstall": "cd client && npm install", - "rails-server": "echo 'visit http://localhost:3000/hello_world' && foreman start -f Procfile.dev", + "rails-server": "foreman start -f Procfile.dev", "test": "rspec" } } From 99c22ef73f3898ae716462426ce0fffa1603d3e1 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 16:26:46 -0400 Subject: [PATCH 12/18] clean up + rubocop and hound config --- .hound.yml | 4 ++++ .rubocop.yml | 2 ++ app/controllers/users_controller.rb | 13 ++++--------- app/serializers/user_serializer.rb | 2 +- client/app/user/UserProfile.jsx | 11 ++--------- client/app/user/userContainer.jsx | 10 +--------- config/initializers/assets.rb | 3 ++- 7 files changed, 16 insertions(+), 29 deletions(-) create mode 100644 .hound.yml diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 000000000..f20616ed2 --- /dev/null +++ b/.hound.yml @@ -0,0 +1,4 @@ +javascript: + enabled: false +eslint: + enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index fba1c4a28..9be356cf2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,9 +14,11 @@ AllCops: # 2015/01/04 - skipping Capistrano deploy.rb file for now, remove the # following line once #1074 is resolved - 'config/deploy.rb' + - 'config/initializers/react_on_rails.rb' - 'lib/**/*.erb' - 'script/**/*' - 'vendor/**/*' + - 'node_modules/**/*' # disable the documentation check at the start of classes / modules Documentation: Enabled: false diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 666a3d1d8..a562bb44a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -14,8 +14,7 @@ class UsersController < ApplicationController only: [:show, :edit, :update, :destroy, :ban, :unban] before_action :check_cas_auth, only: [:show, :new, :create, :quick_create, :edit, :update] - before_action :props, only: [:show, :edit] - #before_action :init_store, only: [:show, :edit] + before_action :props, only: [:show] include Autocomplete include Calendarable @@ -172,7 +171,7 @@ def edit @can_edit_username = can? :edit_username, User end - def update # rubocop:disable CyclomaticComplexity, PerceivedComplexity + def update respond_to do |format| format.html { html_update } format.json { js_update } @@ -228,7 +227,7 @@ def find # rubocop:disable CyclomaticComplexity, PerceivedComplexity private - def html_update + def html_update # rubocop:disable CyclomaticComplexity, PerceivedComplexity @edit_title_text = (current_user == @user) ? 'Profile' : 'User' par = user_params # use :update_with_password when we're not using CAS and you're editing @@ -292,12 +291,8 @@ def calendar_name_method # React on Rails / Redux methods - #def init_store - # redux_store("UserStore", props: @props) - #end - def props @props = ActiveModelSerializers::SerializableResource - .new(@user, scope: current_user, scope_name: :current_user) + .new(@user, scope: current_user, scope_name: :current_user) end end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 28691b725..a6f464d20 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,4 +1,4 @@ class UserSerializer < ActiveModel::Serializer attributes :id, :email, :first_name, :last_name, :nickname, :phone, - :affiliation, :terms_of_service_accepted, :role + :affiliation, :terms_of_service_accepted, :role end diff --git a/client/app/user/UserProfile.jsx b/client/app/user/UserProfile.jsx index afbf12331..23d223a6a 100644 --- a/client/app/user/UserProfile.jsx +++ b/client/app/user/UserProfile.jsx @@ -1,13 +1,6 @@ -import React, { PropTypes } from 'react'; -import ReactOnRails from 'react-on-rails'; -import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; -import { bindActionCreators } from 'redux'; -import thunkMiddleware from 'redux-thunk'; +import React from 'react'; +import { createStore } from 'redux'; import { Provider } from 'react-redux'; -import { connect } from 'react-redux'; -import mirrorCreator from 'mirror-creator'; -import Immutable from 'immutable'; - import User from './userContainer'; import userReducer from './userReducer'; diff --git a/client/app/user/userContainer.jsx b/client/app/user/userContainer.jsx index 8f3103333..7ba06c78f 100644 --- a/client/app/user/userContainer.jsx +++ b/client/app/user/userContainer.jsx @@ -1,13 +1,5 @@ -import React, { PropTypes } from 'react'; -import ReactOnRails from 'react-on-rails'; -import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; -import { bindActionCreators } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { Provider } from 'react-redux'; +import React from 'react'; import { connect } from 'react-redux'; -import mirrorCreator from 'mirror-creator'; -import Immutable from 'immutable'; - import EditableTable from './editableTable' import Table from './table' diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 630c87165..4d2da8831 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -19,4 +19,5 @@ # Rails.application.config.assets.precompile += %w( server-bundle.js ) # Add folder with webpack generated assets to assets.paths -Rails.application.config.assets.paths << Rails.root.join("app", "assets", "webpack") +Rails.application.config.assets.paths << Rails.root.join('app', 'assets', + 'webpack') From ca64cfde918cc919c85394abd01fd9202547b7c8 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Wed, 8 Jun 2016 16:27:44 -0400 Subject: [PATCH 13/18] remove old table --- client/app/user/userInfoTable.jsx | 45 ------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 client/app/user/userInfoTable.jsx diff --git a/client/app/user/userInfoTable.jsx b/client/app/user/userInfoTable.jsx deleted file mode 100644 index e35cb82b3..000000000 --- a/client/app/user/userInfoTable.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { PropTypes } from 'react'; - -export default class UserInfoTable extends React.Component { - static propTypes = { - // If you have lots of data or action properties, you should consider grouping them by - // passing two properties: "data" and "actions". - toggleEditMode: PropTypes.func.isRequired, - editMode: PropTypes.bool.isRequired, - user: PropTypes.object.isRequired, - }; - - // React will automatically provide us with the event `e` - handleChange(e) { - const editMode = e.target.value; - this.props.toggleEditMode(editMode); - } - - render() { - const user = this.state.user - return ( -
-
-
First Name
-
user.first_name
- -
Last Name
-
user.last_name
- -
Nickname
-
user.nickname
- -
Phone
-
user.phone
- -
Email
-
mail_to user.email, user.email
- -
Affiliation
-
user.affiliation
-
-
- ); - } -} - From 9672dd7e37ab941c7cc1fd8d1d57b418514a1cba Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Thu, 9 Jun 2016 09:59:40 -0400 Subject: [PATCH 14/18] move user reservation counts to react --- app/serializers/user_serializer.rb | 14 +++++++++++- app/views/users/show.html.erb | 20 +---------------- client/app/user/reservations.jsx | 30 +++++++++++++++++++++++++ client/app/user/userContainer.jsx | 36 +++++++++++++++++------------- 4 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 client/app/user/reservations.jsx diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index a6f464d20..2bcb6a8c7 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,4 +1,16 @@ class UserSerializer < ActiveModel::Serializer attributes :id, :email, :first_name, :last_name, :nickname, :phone, - :affiliation, :terms_of_service_accepted, :role + :affiliation, :terms_of_service_accepted, :role, + :reservation_counts + + def reservation_counts + res = object.reservations + counts = { checked_out: res.checked_out.count, + overdue: res.overdue.count, + future: res.reserved.count, + past: res.returned.count, + past_overdue: res.returned_overdue.count } + counts[:missed] = res.missed.count unless AppConfig.check(:res_exp_time) + counts + end end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index e2c1c55aa..1efff0e63 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -25,25 +25,7 @@
-
- - <%= react_component('UserProfile', props: @props) %> - -
- - <%# @show_equipment has reservation status (:overdue, ...) as key, full - reservations in question as value %> - <% @show_equipment.each do |status, reservations| %> - <%= content_tag :div, :id => "#{status}_count", :class => 'col-md-6' do %> - <%= content_tag :h4, status.to_s.titleize %> - <%= content_tag :div, :class => "#{status}_num" do %> - <%= content_tag :i, nil, :class => stats_icons(status) %> - <%= reservations.size %> - <% end %> - <% end %> - <% end %> -
-
+<%= react_component('UserProfile', props: @props) %> <%# TODO: Make this prettier %> <% unless @user.requirements.empty? %> diff --git a/client/app/user/reservations.jsx b/client/app/user/reservations.jsx new file mode 100644 index 000000000..49ea28386 --- /dev/null +++ b/client/app/user/reservations.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +const Item = ({ title, num, icon }) => { + return ( +
+

{title}

+
+ {num} +
+
+ ); +} + +const Reservations = ({ counts }) => { + const missed = counts.hasOwnProperty('missed') + ? + : null; + return ( +
+ + + + + + {missed} +
+ ); +} + +export default Reservations diff --git a/client/app/user/userContainer.jsx b/client/app/user/userContainer.jsx index 7ba06c78f..c8a495ccd 100644 --- a/client/app/user/userContainer.jsx +++ b/client/app/user/userContainer.jsx @@ -1,7 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import EditableTable from './editableTable' -import Table from './table' +import EditableTable from './editableTable'; +import Table from './table'; +import Reservations from './reservations'; const UserInfo = ({ user, canEdit, editing, onEditClick }) => { const table = editing ? :
; @@ -11,22 +12,27 @@ const UserInfo = ({ user, canEdit, editing, onEditClick }) => { const color = editing ? 'default' : 'primary'; const text = editing ? 'Cancel' : 'Edit'; return ( -
-
-
- {table} -
-
-
- {save} - +
+
+
+
+ {table} +
+
+
+ {save} + +
+
+ +
); } From 5f35b5b7e1451f8d9a92ceb0069270ca369ba56e Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Thu, 9 Jun 2016 13:11:32 -0400 Subject: [PATCH 15/18] remove hound config --- .hound.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .hound.yml diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index f20616ed2..000000000 --- a/.hound.yml +++ /dev/null @@ -1,4 +0,0 @@ -javascript: - enabled: false -eslint: - enabled: true From 880b6ebc7555aff56ec83421940e9305a25ba08c Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Thu, 9 Jun 2016 13:17:47 -0400 Subject: [PATCH 16/18] attempt to fix travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 501b6a199..a39bb8df1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ before_script: - cp config/secrets.yml.example config/secrets.yml - bundle exec rake db:create - bundle exec rake db:schema:load + - npm run build:test script: # JS linters, uncomment once code passes From 1377f77a64b9501c7dca2168b58f81fa36d7c72a Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Mon, 13 Jun 2016 15:48:02 -0400 Subject: [PATCH 17/18] minor refactoring --- client/app/user/UserProfile.jsx | 9 +-------- client/app/user/table.jsx | 8 +++----- client/app/user/userContainer.jsx | 16 +++++++--------- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/client/app/user/UserProfile.jsx b/client/app/user/UserProfile.jsx index 23d223a6a..0f204d8f2 100644 --- a/client/app/user/UserProfile.jsx +++ b/client/app/user/UserProfile.jsx @@ -6,14 +6,7 @@ import userReducer from './userReducer'; export default (props, _railsContext) => { - const store = createStore(userReducer); - if (store.getState().user === null) { - store.dispatch({ - type: 'SET_USER', - user: props - }); - } - + const store = createStore(userReducer, { user: props }); const reactComponent = ( diff --git a/client/app/user/table.jsx b/client/app/user/table.jsx index f5ce9e15d..237e1cd56 100644 --- a/client/app/user/table.jsx +++ b/client/app/user/table.jsx @@ -26,11 +26,9 @@ const Table = ({ user }) => { ); } -const mapStateToProps = (state) => { - return { - user: state.user - } -} +const mapStateToProps = (state) => ({ + user: state.user +}); export default connect(mapStateToProps)(Table) diff --git a/client/app/user/userContainer.jsx b/client/app/user/userContainer.jsx index c8a495ccd..a3613d23c 100644 --- a/client/app/user/userContainer.jsx +++ b/client/app/user/userContainer.jsx @@ -37,15 +37,13 @@ const UserInfo = ({ user, canEdit, editing, onEditClick }) => { ); } -const mapStateToProps = (state) => { - return { - editing: state.editMode, - user: state.user, - } -} +const mapStateToProps = (state) => ({ + editing: state.editMode, + user: state.user, +}); -const mapDispatchToProps = (dispatch) => { - return { onEditClick: () => { dispatch({ type: 'TOGGLE_EDIT_MODE' }) }, } -} +const mapDispatchToProps = (dispatch) => ({ + onEditClick: () => { dispatch({ type: 'TOGGLE_EDIT_MODE' }) }, +}); export default connect(mapStateToProps, mapDispatchToProps)(UserInfo) From 3bda0b779af62a3f0f0160b950c428573d6a00f2 Mon Sep 17 00:00:00 2001 From: Sydney Young Date: Mon, 20 Jun 2016 12:27:13 -0400 Subject: [PATCH 18/18] more refactoring --- client/app/user/editableTable.jsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/app/user/editableTable.jsx b/client/app/user/editableTable.jsx index b83039590..ae51c338a 100644 --- a/client/app/user/editableTable.jsx +++ b/client/app/user/editableTable.jsx @@ -84,17 +84,13 @@ const Form = ({ user, onSave, id }) => { ); } -const mapStateToProps = (state) => { - return { - user: state.user, - } -} +const mapStateToProps = (state) => ({ + user: state.user, +}); const mapDispatchToProps = (dispatch) => { return { - onSave: (changes) => { - dispatch({ type: 'UPDATE_USER', changes: changes }) - }, + onSave(changes) { dispatch({ type: 'UPDATE_USER', changes: changes }) }, } }