-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invites: Add invites create validation methods #3037
Changes from all commits
693801a
c94ed4b
c98b696
d9ba6b2
946a37b
fd8fcdc
6e8b04b
c2ce2ff
df920c7
1f9de80
bb77af5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { fromJS } from 'immutable'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { action as ActionTypes } from 'lib/invites/constants'; | ||
|
||
const initialState = fromJS( { | ||
success: {}, | ||
errors: {} | ||
} ); | ||
|
||
const reducer = ( state = initialState, payload ) => { | ||
const { action } = payload; | ||
switch ( action.type ) { | ||
case ActionTypes.RECEIVE_CREATE_INVITE_VALIDATION_SUCCESS: | ||
return state.setIn( [ 'success', action.siteId ], action.data.success ).setIn( [ 'errors', action.siteId ], action.data.errors ); | ||
} | ||
return state; | ||
} | ||
|
||
export { initialState, reducer }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { createReducerStore } from 'lib/store'; | ||
import { reducer, initialState } from 'lib/invites/reducers/invites-accept-validation'; | ||
|
||
const InvitesAcceptValidationStore = createReducerStore( reducer, initialState ); | ||
|
||
InvitesAcceptValidationStore.getInvite = ( siteId, inviteKey ) => InvitesAcceptValidationStore.get().getIn( [ 'list', siteId, inviteKey ] ); | ||
InvitesAcceptValidationStore.getInviteError = ( siteId, inviteKey ) => InvitesAcceptValidationStore.get().getIn( [ 'errors', siteId, inviteKey ] ); | ||
|
||
export default InvitesAcceptValidationStore; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { createReducerStore } from 'lib/store'; | ||
import { reducer, initialState } from 'lib/invites/reducers/invites-create-validation'; | ||
|
||
const InvitesCreateValidationStore = createReducerStore( reducer, initialState ); | ||
|
||
InvitesCreateValidationStore.getSuccess = ( siteId ) => InvitesCreateValidationStore.get().getIn( [ 'success', siteId ] ); | ||
InvitesCreateValidationStore.getErrors = ( siteId ) => InvitesCreateValidationStore.get().getIn( [ 'errors', siteId ] ); | ||
|
||
export default InvitesCreateValidationStore; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
require( 'lib/react-test-env-setup' )(); | ||
|
||
const assert = require( 'chai' ).assert; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
const Dispatcher = require( 'dispatcher' ), | ||
constants = require( 'lib/invites/constants' ); | ||
|
||
describe( 'Invites Create Validation Store', () => { | ||
let InvitesCreateValidationStore; | ||
const siteId = 123; | ||
|
||
const validationData = { | ||
errors: { | ||
'test@gmail.com': { | ||
errors: { | ||
'form-error-username-or-email': [ 'User already has a role on your site.' ] | ||
}, | ||
error_data: [] | ||
} | ||
}, | ||
success: [ 'testuser', 'test2@gmail.com' ] | ||
} | ||
|
||
const actions = { | ||
receiveValidaton: { | ||
type: constants.action.RECEIVE_CREATE_INVITE_VALIDATION_SUCCESS, | ||
siteId: siteId, | ||
data: validationData | ||
}, | ||
}; | ||
|
||
beforeEach( () => { | ||
InvitesCreateValidationStore = require( 'lib/invites/stores/invites-create-validation' ); | ||
} ); | ||
|
||
describe( 'Validating invite creation', () => { | ||
beforeEach( () => { | ||
Dispatcher.handleServerAction( actions.receiveValidaton ); | ||
} ); | ||
|
||
it( 'Validation is not empty', () => { | ||
const success = InvitesCreateValidationStore.getSuccess( siteId ); | ||
assert.lengthOf( success, 2 ); | ||
const errors = InvitesCreateValidationStore.getErrors( siteId ); | ||
assert.equal( errors, validationData.errors ); | ||
} ); | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,8 @@ import Card from 'components/card'; | |
import Main from 'components/main'; | ||
import HeaderCake from 'components/header-cake'; | ||
import CountedTextarea from 'components/forms/counted-textarea'; | ||
import { createInviteValidation } from 'lib/invites/actions'; | ||
import InvitesCreateValidationStore from 'lib/invites/stores/invites-create-validation'; | ||
|
||
/** | ||
* Module variables | ||
|
@@ -32,6 +34,14 @@ export default React.createClass( { | |
|
||
mixins: [ LinkedStateMixin ], | ||
|
||
componentDidMount() { | ||
InvitesCreateValidationStore.on( 'change', this.refreshValidation ); | ||
}, | ||
|
||
componentWillUnmount() { | ||
InvitesCreateValidationStore.off( 'change', this.refreshValidation ); | ||
}, | ||
|
||
componentWillReceiveProps() { | ||
this.setState( this.resetState() ); | ||
}, | ||
|
@@ -46,18 +56,41 @@ export default React.createClass( { | |
role: 'follower', | ||
message: '', | ||
response: false, | ||
sendingInvites: false | ||
sendingInvites: false, | ||
getTokenStatus: () => {} | ||
} ); | ||
}, | ||
|
||
onTokensChange( tokens ) { | ||
this.setState( { usernamesOrEmails: tokens } ); | ||
const { role } = this.state; | ||
createInviteValidation( this.props.site.ID, tokens, role ); | ||
}, | ||
|
||
onMessageChange( event ) { | ||
this.setState( { message: event.target.value } ); | ||
}, | ||
|
||
refreshValidation() { | ||
const errors = InvitesCreateValidationStore.getErrors( this.props.site.ID ) || []; | ||
let success = InvitesCreateValidationStore.getSuccess( this.props.site.ID ) || []; | ||
if ( ! success.indexOf ) { | ||
success = Object.keys( success ).map( key => success[ key ] ); | ||
} | ||
this.setState( { | ||
getTokenStatus: ( value ) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we storing this function in state? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we pass the same function to tokens they don't rerender, we need to create a new function every time the validation results change to see them change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be about the same, besides, the function behaviour really changes, I like that we are creating a new instance. |
||
if ( 'string' === typeof value ) { | ||
if ( errors[ value ] ) { | ||
return 'is-error'; | ||
} | ||
if ( success.indexOf( value ) > -1 ) { | ||
return 'is-success'; | ||
} | ||
} | ||
} | ||
} ); | ||
}, | ||
|
||
submitForm( event ) { | ||
event.preventDefault(); | ||
debug( 'Submitting invite form. State: ' + JSON.stringify( this.state ) ); | ||
|
@@ -79,19 +112,6 @@ export default React.createClass( { | |
page.back( fallback ); | ||
}, | ||
|
||
getTokenStatus( value ) { | ||
let status; | ||
if ( 'string' === typeof value ) { | ||
if ( -1 < value.indexOf( 'error' ) ) { | ||
status = 'is-error'; | ||
} else if ( -1 < value.indexOf( 'success' ) ) { | ||
status = 'is-success'; | ||
} | ||
} | ||
|
||
return status; | ||
}, | ||
|
||
renderRoleExplanation() { | ||
return ( | ||
<a target="_blank" href="http://en.support.wordpress.com/user-roles/"> | ||
|
@@ -123,7 +143,7 @@ export default React.createClass( { | |
<FormLabel>{ this.translate( 'Usernames or Emails' ) }</FormLabel> | ||
<TokenField | ||
isBorderless | ||
tokenStatus={ this.getTokenStatus } | ||
tokenStatus={ this.state.getTokenStatus } | ||
value={ this.state.usernamesOrEmails } | ||
onChange={ this.onTokensChange } /> | ||
<FormSettingExplanation> | ||
|
@@ -142,7 +162,8 @@ export default React.createClass( { | |
siteId={ this.props.site.ID } | ||
valueLink={ this.linkState( 'role' ) } | ||
disabled={ this.state.sendingInvites } | ||
explanation={ this.renderRoleExplanation() }/> | ||
explanation={ this.renderRoleExplanation() } | ||
/> | ||
|
||
<FormFieldset> | ||
<FormLabel htmlFor="message">{ this.translate( 'Custom Message' ) }</FormLabel> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this matches with the API. The validation endpoint doesn't return errors for usernames or emails that don't validate. The endpoint returns an object that looks a bit like: