Skip to content
This repository has been archived by the owner on Jan 28, 2022. It is now read-only.

WIP: Convert the UI to be React based #1

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e094bd1
Adding react_rails
wbushey Jul 15, 2016
0f70e8f
First React component
wbushey Jul 15, 2016
f32d4b7
Added Jasmine, and first React spec
wbushey Jul 16, 2016
c454904
Using jquery-rails version of jQuery
wbushey Jul 17, 2016
0cf5d40
Upgrading bootstrap.js to 2.3.2
wbushey Jul 17, 2016
63a4c3b
jQuery 1.8 -> 1.11: live -> on
wbushey Jul 17, 2016
34a554e
More development of the Field component
wbushey Jul 18, 2016
59c6ee4
Text fields update their values
wbushey Jul 26, 2016
41b11a6
Refactored Field into several focused components
wbushey Jul 27, 2016
9e61111
All components can receive input
wbushey Jul 28, 2016
244c7ae
Fields can have initial values set
wbushey Jul 31, 2016
fd3b154
Fields can display errors
wbushey Aug 1, 2016
d608018
Starting the Edit Profile view in React
wbushey Aug 1, 2016
3b9b322
Working shipping options
wbushey Aug 6, 2016
e17776d
Created Survey form partial
wbushey Aug 7, 2016
b54c1a0
Removing unneeded server side partials
wbushey Aug 8, 2016
a4061cf
Replaced display of location search with React component
wbushey Aug 10, 2016
dc7f124
Implemented LocationSearchView
wbushey Aug 13, 2016
d2b22d1
Old work in some state, including some failing tests
wbushey Sep 27, 2016
7818b2f
Updating .gitignore and setting up working environment
Oct 25, 2016
8047543
Fixes #23
Oct 25, 2016
a16324f
Fixed one thing about a still failing spec
wbushey Oct 26, 2016
2a27441
Component Fields can take onStateChange callbacks (#35)
wbushey Oct 29, 2016
69904e1
Finishing update of gems from master
wbushey Dec 11, 2016
f96ff50
Merge branch 'master' into react
wbushey Mar 29, 2017
eb041cf
Fixing Gemfile
wbushey Mar 29, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Old work in some state, including some failing tests
wbushey committed Dec 11, 2016
commit d2b22d1c2cc2600bbe87a10c3bf3e7349cf4828b
9 changes: 8 additions & 1 deletion app/assets/javascripts/components/fields/radio_field.es6.jsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,12 @@ class RadioField extends React.Component {
this.handleChange = this.handleChange.bind(this);
}

componentWillUpdate(){
if (this.props.onStateChange){
this.props.onStateChange();
}
}

value(){
return this.state.value;
}
@@ -42,5 +48,6 @@ RadioField.propTypes = {
required: React.PropTypes.bool,
private: React.PropTypes.bool,
options: React.PropTypes.array,
errors: React.PropTypes.array
errors: React.PropTypes.array,
onStateChange: React.PropTypes.func
};
75 changes: 75 additions & 0 deletions app/assets/javascripts/components/views/combo_form_view.es6.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
class ComboFormView extends Form{

constructor(props){
super(props);
var errors = this.props.errors;
if (errors === undefined){
errors = {};
}
['email', 'username', 'password'].forEach(function(attr){
if (!(attr in errors)){
errors[attr] = null;
}
});
var user_status = AdoptAUtils.fetch(this.props.value, 'user_status', 'new');
this.state = {
errors: errors,
user_status: user_status
}
}

componentDidMount(){
this.refs.email.refs.input.focus();
}

isNew(){
return (this.state.user_status === 'new');
}

renderSignUpForm(){
var fetch = AdoptAUtils.fetch;
return(
<fieldset id='user_sign_up_fields'>
<TextField rel='username' name='username' label={I18n.t('labels.username')} value={fetch(this.props.value, 'username')} errors={this.state.errors.username}/>
<PasswordField rel='password' name='password' label={I18n.t('labels.password')} value={fetch(this.props.value, 'password')} errors={this.state.errors.password}/>
<ShippingInformationPartial value={this.props.value} errors={this.props.errors}/>
</fieldset>
);
}

renderSignInForm(){
var fetch = AdoptAUtils.fetch;
return (
<fieldset id='user_sign_in_fields'>
<PasswordField rel='password' name='password' label={I18n.t('labels.password')} value={fetch(this.props.value, 'password')} errors={this.state.errors.password}/>
<CheckboxField rel='remember' name='remember' options={[{value: 'me', label: I18n.t('labels.remember_me')}]} value={fetch(this.props.value, 'remember')} errors={this.state.errors.remember}/>
</fieldset>
);
}

renderRemainder(){
if (this.isNew()){
return this.renderSignUpForm();
} else {
return this.renderSignInForm();
}
}

render(){
var fetch = AdoptAUtils.fetch;
return (
<form id='combo-form' className='form-vertical'>
<fieldset id='common_fields'>
<TextField ref='email' name='email' label={I18n.t('labels.email')} private={true} value={fetch(this.props.value, 'email')} errors={this.state.errors.email}/>
<RadioField ref='user_status' name='user_status' options={ComboFormView.userStatusOptions} value={this.state.user_status}/>
</fieldset>
{this.renderRemainder()}
</form>
);
}
}

ComboFormView.userStatusOptions = [
{value: 'new', label: I18n.t('labels.user_new')},
{value: 'existing', label: I18n.t('labels.user_existing')}
]
4 changes: 2 additions & 2 deletions app/assets/javascripts/components/views/edit_profile.es6.jsx
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@ class EditProfile extends React.Component{
<form>
<TextField name='email' label="Email Address" private={true}/>
<TextField name='username' label="Username" private={false}/>
<ShippingInformation/>
<Survey/>
<ShippingInformationPartial/>
<SurveyPartial/>
<h2>Change Password</h2>
<PasswordField name='password' label='New Password' private={true}/>
<PasswordField name='current_password' label='Current Password' private={true}/>
Original file line number Diff line number Diff line change
@@ -77,9 +77,8 @@ class LocationSearchView extends Form{
<a id='sign_out_link' className='btn btn-danger'>{I18n.t('buttons.sign_out')}</a>
</fieldset>
</form>
)
);
}

}

LocationSearchView.cityStateOptions = [
42 changes: 0 additions & 42 deletions app/assets/javascripts/main.js.erb
Original file line number Diff line number Diff line change
@@ -22,16 +22,6 @@ $(function() {
//errors[0].focus();
}

// Focus on the first non-empty text input or password field
function setComboFormFocus() {
$('#combo-form input[type="email"], #combo-form input[type="text"]:visible, #combo-form input[type="password"]:visible, #combo-form input[type="submit"]:visible, #combo-form input[type="tel"]:visible, #combo-form button:visible').each(function(index) {
if($(this).val() === "" || $(this).attr('type') === 'submit' || this.tagName.toLowerCase() === 'button') {
$(this).focus();
return false;
}
});
}

function congratulateAdopter(args){
$.ajax({
type: 'GET',
@@ -71,38 +61,6 @@ $(function() {

}

$('#combo-form input[type="radio"]').on('click', function() {
var radioInput = $(this);
if('new' === radioInput.val()) {
$('#combo-form').data('state', 'user_sign_up');
$('#user_forgot_password_fields').slideUp();
$('#user_sign_in_fields').slideUp();
$('#user_sign_up_fields').slideDown(function() {
setComboFormFocus();
});
} else if('existing' === radioInput.val()) {
$('#user_sign_up_fields').slideUp();
$('#user_sign_in_fields').slideDown(function() {
$('#combo-form').data('state', 'user_sign_in');
setComboFormFocus();
$('#user_forgot_password_link').click(function() {
$('#combo-form').data('state', 'user_forgot_password');
$('#user_sign_in_fields').slideUp();
$('#user_forgot_password_fields').slideDown(function() {
setComboFormFocus();
$('#user_remembered_password_link').click(function() {
$('#combo-form').data('state', 'user_sign_in');
$('#user_forgot_password_fields').slideUp();
$('#user_sign_in_fields').slideDown(function() {
setComboFormFocus();
});
});
});
});
});
}
});

$('#combo-form').on('submit', function() {
var submitButton = $("#combo-form input[type='submit']");
$(submitButton).attr("disabled", true);
51 changes: 1 addition & 50 deletions app/views/sidebar/_combo_form.html.haml
Original file line number Diff line number Diff line change
@@ -1,51 +1,2 @@
= form_for :user, :html => {:id => "combo-form", :class => "form-vertical"} do |f|
%fieldset#common_fields
.control-group
%label{:for => "user_email", :id => "user_email_label"}
= t("labels.email")
%small
= image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
= f.email_field "email", :value => params[:user] ? params[:user][:email] : nil
.control-group.radio
= f.label "new" , radio_button_tag("user", "new", true).html_safe + t("labels.user_new")
= f.label "existing", radio_button_tag("user", "existing").html_safe + t("labels.user_existing")
%fieldset#user_sign_up_fields
= render :partial => "users/user_username", locals: {f: f}
.control-group
%label{:for => "user_password_confirmation", :id => "user_password_confirmation_label"}
= t("labels.password_choose")
%small
= image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
= f.password_field "password_confirmation"
%h2
=t("labels.shipping_info_heading")
= render :partial => "users/user_realnames", locals: {f: f}
= render :partial => "users/user_address", locals: {f: f}

.form-actions
= f.submit t("buttons.sign_up"), :class => "btn btn-primary"
%p
= t("defaults.tos", :tos => link_to(t("titles.tos"), "#tos", :id => "tos_link", :"data-toggle" => "modal")).html_safe
%fieldset#user_sign_in_fields{:style => "display: none;"}
.control-group
%label{:for => "user_password", :id => "user_password_label"}
= t("labels.password")
%small
= image_tag "lock.png", :class => "lock", :alt => t("captions.private"), :title => t("captions.private")
= f.password_field "password"
.control-group
= f.label "remember_me" , f.check_box("remember_me", :checked => true).html_safe + t("labels.remember_me")
.form-actions
= f.submit t("buttons.sign_in"), :class => "btn btn-primary"
%p
= link_to t("links.forgot_password"), "#", :id => "user_forgot_password_link"
%fieldset#user_forgot_password_fields{:style => "display: none;"}
.form-actions
= f.submit t("buttons.email_password"), :class => "btn btn-primary"
%p
= link_to t("links.remembered_password"), "#", :id => "user_remembered_password_link"
= react_component 'ComboFormView'
= render :partial => "sidebar/tos"
:javascript
$(function() {
$('#user_email').focus();
});
19 changes: 19 additions & 0 deletions spec/javascripts/components/fields/radio_field_spec.js.jsx
Original file line number Diff line number Diff line change
@@ -91,4 +91,23 @@ describe('RadioField', function(){
expect($(fieldNode).text()).toContain('error messages');
});
});

describe('with onStateChange', function(){
var stateChangeSypy, input, $input;

beforeEach(function(){
stateChangeSpy = jasmine.createSpy('stateChangeCallback');
field = TestUtils.renderIntoDocument(
<CheckboxField name="myField" options={options} onStateChange={stateChangeSpy}/>
);
fieldNode = ReactDOM.findDOMNode(field);
var $input = $(fieldNode).find('input[value="three"]').first();
var input = $input[0];
});

it('calls the provided onStateChange', function(){
TestUtils.Simulate.change(input, {target: {checked: true, value: $input.val()}});
expect(stateChangeSpy).toHaveBeenCalled();
});
});
});
61 changes: 61 additions & 0 deletions spec/javascripts/components/views/combo_form_view_spec.js.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
describe('ComboFormView', function(){
var field, fieldNode, $field;

var values = {
email: '[email protected]',
user_status: 'new'
};

var errors = {
email: ['bad email']
};

itBehavesLikeAForm(ComboFormView, values, errors);

beforeEach(function(){
field = TestUtils.renderIntoDocument(
<ComboFormView/>
);
fieldNode = ReactDOM.findDOMNode(field);
$field = $(fieldNode);
});

it('Has Combo Form fields', function(){
expect($field.find('input#email').length).toEqual(1);
expect($field.find('input#user_status_new').length).toEqual(1);
expect($field.find('input#user_status_existing').length).toEqual(1);
});

describe('New user', function(){
beforeEach(function(){
values.user_status = 'new'
field = TestUtils.renderIntoDocument(
<ComboFormView value={values}/>
);
fieldNode = ReactDOM.findDOMNode(field);
$field = $(fieldNode);
});

it('Displays user signup fields', function(){
expect($field.find('input#username').length).toEqual(1);
expect($field.find('input#password').length).toEqual(1);
expect(TestUtils.findRenderedComponentWithType(field, ShippingInformationPartial)).toBeDefined();
});
});

describe('Existing user', function(){
beforeEach(function(){
values.user_status = 'existing';
field = TestUtils.renderIntoDocument(
<ComboFormView value={values}/>
);
fieldNode = ReactDOM.findDOMNode(field);
$field = $(fieldNode);
});

it('Displays user sign in fields', function(){
expect($field.find('input#password').length).toEqual(1, 'Password field not found');
expect($field.find('input#remember_me').length).toEqual(1, 'Remember Me checkbox not found');
});
});
});
4 changes: 3 additions & 1 deletion spec/javascripts/helpers/shared_examples.js
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@ function itBehavesLikeAForm(view_component, values, errors){

it('Applies errors to fields', function(){
$.each(errors, function(key, message){
expect($field.find('.error:contains("'+ message +'")').length).toEqual(1, 'error message "' + message + '" could not be found');
if (message !== null){
expect($field.find('.error:contains("'+ message +'")').length).toEqual(1, 'error message "' + message + '" could not be found');
}
});
});
});