diff --git a/Gemfile b/Gemfile index 25a0e7f..69a2ade 100644 --- a/Gemfile +++ b/Gemfile @@ -95,3 +95,4 @@ gem 'high_voltage', '~> 2.4.0' gem 'airbrake' gem 'bootstrap-sass', '~> 3.3.5' +gem 'react-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 0fe9c09..c2323aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,6 +46,10 @@ GEM autoprefixer-rails (6.0.2) execjs json + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) @@ -79,6 +83,7 @@ GEM execjs coffee-script-source (1.9.1.1) colorize (0.7.7) + connection_pool (2.2.1) coveralls (0.8.2) json (~> 1.8) rest-client (>= 1.6.8, < 2) @@ -205,6 +210,13 @@ GEM thor (>= 0.18.1, < 2.0) rake (10.4.2) rdoc (4.2.0) + react-rails (1.10.0) + babel-transpiler (>= 0.7.0) + coffee-script-source (~> 1.8) + connection_pool + execjs + railties (>= 3.2) + tilt responders (2.1.0) railties (>= 4.2.0, < 5) rest-client (1.8.0) @@ -313,6 +325,7 @@ DEPENDENCIES omniauth-oauth2 purecss-rails rails (= 4.2.4) + react-rails rspec-rails sass-rails (~> 5.0) sdoc (~> 0.4.0) @@ -324,4 +337,4 @@ DEPENDENCIES web-console (~> 2.0) BUNDLED WITH - 1.10.6 + 1.13.7 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index bc4b5a6..bacf92d 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,6 +18,9 @@ //= require select2 //= require jquery-dateFormat //= require turbolinks +//= require react +//= require react_ujs +//= require components //= require_tree . ready = function() { diff --git a/app/assets/javascripts/components.js b/app/assets/javascripts/components.js new file mode 100644 index 0000000..9ce7a4f --- /dev/null +++ b/app/assets/javascripts/components.js @@ -0,0 +1 @@ +//= require_tree ./components diff --git a/app/assets/javascripts/components/.gitkeep b/app/assets/javascripts/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/components/transaction_form.jsx.coffee b/app/assets/javascripts/components/transaction_form.jsx.coffee new file mode 100644 index 0000000..1a54e1c --- /dev/null +++ b/app/assets/javascripts/components/transaction_form.jsx.coffee @@ -0,0 +1,127 @@ +{ button, div, h3, input, option, select } = React.DOM + +Action = React.createFactory React.createClass + buttonClass: (b) -> + { giving } = @props + c = ['btn', 'btn-default'] + c.push 'active' if b == giving + c.join ' ' + onClick: (b) -> + => + @props.setAction b + render: -> + { giving } = @props + div className: 'btn-group btn-group-lg', + button type: 'button', className: @buttonClass(true), onClick: @onClick(true), + 'Give Money' + button type: 'button', className: @buttonClass(false), onClick: @onClick(false), + 'Request Money' + +Amount = React.createFactory React.createClass + onChange: (ref) -> + @props.setAmount ref.target.value + format: (ref) -> + t = ref.target + t.value = parseFloat(t.value).toFixed(2) + render: -> + div className: 'row', + div className: 'col-xs-4', + div className: 'input-group', + div className: 'input-group-addon', '€' + input { + type: 'number', + className: 'form-control input-lg', + placeholder: '0.00', + onChange: @onChange, + onBlur: @format + } + +Suggestions = React.createFactory React.createClass + render: -> + +Peer = React.createFactory React.createClass + onChange: (ref) -> + @props.setPeer ref.target.value + options: -> + { peer, peers } = @props + if peer == '' or peers.includes(peer) + [] + else + re = new RegExp peer + peers.filter (s) -> + s.match(re) != null + inputClass: (n) -> + c = ['form-control', 'input-lg'] + c.push 'active' if n > 0 + c.join ' ' + setPeer: (p) -> + => + @props.setPeer p + render: -> + options = @options() + div className: 'row', + div className: 'col-xs-4', + div className: 'suggestions-wrapper', + input type: 'text', className: @inputClass(options.length), onChange: @onChange, placeholder: 'Zeus member', value: (@props.peer || '') + if options.length != 0 + div className: 'suggestions', + @options().map (s, i) => + div className: 'suggestion', key: i, onClick: @setPeer(s), + s + +Message = React.createFactory React.createClass + onChange: (ref) -> + @props.setMessage ref.target.value + render: -> + div className: 'row', + div className: 'col-xs-8', + input type: 'text', className: 'form-control input-lg', onChange: @onChange, placeholder: 'Message' + +Submit = React.createFactory React.createClass + render: -> + div className: 'row', + div className: 'col-xs-4 col-xs-offset-4', + button type: 'submit', onClick: @props.onClick, className: 'btn btn-default btn-lg form-control', 'Confirm' + +Step = React.createFactory React.createClass + render: -> + div className: 'form-step', + div className: 'form-step-counter', @props.step + div className: 'form-step-content', + div className: 'form-step-title', @props.title + div className: 'clear-both' + @props.children + div className: 'clear-both' + +@TransactionForm = React.createClass + getInitialState: -> + giving: null, amount: null, peer: null, message: null + setAction: (b) -> + @setState giving: b + setAmount: (a) -> + @setState amount: a + setPeer: (p) -> + @setState peer: p + setMessage: (m) -> + @setState message: m + submit: (e) -> + console.log 'submit' + render: -> + { amount, giving, message, peer } = @state + { peers } = @props + div id: 'transaction-form', + h3 null, 'Transfer some money' + Step step: 1, title: 'What do you want to do?', + Action giving: giving, setAction: @setAction + if giving != null + Step step: 2, title: 'How much do you want to give?', + Amount setAmount: @setAmount + if amount != null + Step step: 3, title: 'Who do you want to give it to?', + Peer peer: peer, peers: peers, setPeer: @setPeer + if peer != null + Step step: 4, title: 'Why do you want to give this?', + Message setMessage: @setMessage + if message != null + Submit null + div className: 'clear-both' diff --git a/app/assets/stylesheets/card.scss b/app/assets/stylesheets/card.scss index 3de7a88..c345473 100644 --- a/app/assets/stylesheets/card.scss +++ b/app/assets/stylesheets/card.scss @@ -5,5 +5,9 @@ .card { box-shadow: 0 1px 3px rgba(0,0,0, 0.12), 0 1px 2px rgba(0,0,0, 0.24); border-radius: 2px; + + &.padded { + padding: 10px; + } } } diff --git a/app/assets/stylesheets/transaction_form.scss b/app/assets/stylesheets/transaction_form.scss new file mode 100644 index 0000000..3617b15 --- /dev/null +++ b/app/assets/stylesheets/transaction_form.scss @@ -0,0 +1,87 @@ +$step-color: #b5b5b5;; +$step-padding-top: 3px; +$step-width: 26px; + +#transaction-form { + .form-step { + margin-bottom: 10px; + + .form-step-counter { + float: left; + font-size: 16px; + font-weight: bold; + vertical-align: middle; + white-space: nowrap; + text-align: center; + display: inline-block; + min-width: 10px; + padding: $step-padding-top 7px; + height: $step-width; + width: $step-width; + background: $step-color; + color: #fff; + position: relative; + border-radius: 50%; + } + + .form-step-content { + margin-left: $step-width + 10px; + width: 100%; + + .form-step-title { + display: inline-block; + font-weight: bold; + margin-bottom: 10px; + } + + .form-options { + & > div { + border: 1px solid #000; + display: inline-block; + width: 40%; + margin-right: 10px; + padding: 20px; + text-align: center; + } + } + + .suggestions-wrapper { + position: relative; + + input.active { + border-radius: 6px 6px 0 0; + } + + .suggestions { + position: absolute; + top: 100%; + width: 100%; + border: 1px solid #ccc; + border-top: 0; + border-radius: 0 0 6px 6px; + background: #fff; + z-index: 2; + + .suggestion { + cursor: pointer; + padding: 5px 8px; + + &:hover { + background-color: #f7f7f7; + } + + &:last-child { + border-radius: 0 0 6px 6px; + } + } + } + } + + .btn { + &:focus, &:active { + outline: 0; + } + } + } + } +} diff --git a/app/views/pages/landing.html.haml b/app/views/pages/landing.html.haml index e6408f6..34ca1c9 100644 --- a/app/views/pages/landing.html.haml +++ b/app/views/pages/landing.html.haml @@ -3,8 +3,8 @@ = render 'transactions' .pure-u-5-12 .card-wrapper - .card - nieuwe transactie + .card.padded + = react_component 'TransactionForm', user: current_user, peers: User.all.order(:name).pluck(:name) .card-wrapper .card requests