Skip to content

Commit

Permalink
Signup: Properly suggest non-taken usernames when signing up - Redux (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bisko authored Jan 6, 2017
1 parent 96efd04 commit eaa6ea0
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 20 deletions.
28 changes: 10 additions & 18 deletions client/components/signup-form/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import React from 'react';
import { map, forEach, head, includes, keys, find } from 'lodash';
import { map, forEach, head, includes, keys } from 'lodash';
import debugModule from 'debug';
import classNames from 'classnames';
import i18n from 'i18n-calypso';
Expand All @@ -26,7 +26,7 @@ import formState from 'lib/form-state';
import LoggedOutFormLinks from 'components/logged-out-form/links';
import LoggedOutFormLinkItem from 'components/logged-out-form/link-item';
import LoggedOutFormFooter from 'components/logged-out-form/footer';
import { getValueFromProgressStore, mergeFormWithValue, getFlowSteps } from 'signup/utils';
import { mergeFormWithValue } from 'signup/utils';

const VALIDATION_DELAY_AFTER_FIELD_CHANGES = 1500,
debug = debugModule( 'calypso:signup-form:form' );
Expand All @@ -45,6 +45,10 @@ export default React.createClass( {

displayName: 'SignupForm',

contextTypes: {
store: React.PropTypes.object
},

getInitialState() {
return {
notice: null,
Expand All @@ -64,25 +68,13 @@ export default React.createClass( {
},

autoFillUsername( form ) {
const steps = getFlowSteps( this.props.flowName );
const domainStep = find( steps, step => step.match( /^domain/ ) );
let domainName = getValueFromProgressStore( {
stepName: domainStep || null,
fieldName: 'siteUrl',
signupProgressStore: this.props.signupProgressStore
} );
const siteName = getValueFromProgressStore( {
stepName: 'site',
fieldName: 'site',
signupProgressStore: this.props.signupProgressStore
} );
if ( domainName ) {
domainName = domainName.split( '.' )[ 0 ];
}
// Fetch the suggested username from local storage
const suggestedUsername = this.context.store.getState().signup.optionalDependencies.suggestedUsername;

return mergeFormWithValue( {
form,
fieldName: 'username',
fieldValue: siteName || domainName || null
fieldValue: suggestedUsername || undefined
} );
},

Expand Down
70 changes: 69 additions & 1 deletion client/lib/signup/step-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { startFreeTrial } from 'lib/upgrades/actions';
import { PLAN_PREMIUM } from 'lib/plans/constants';
import analytics from 'lib/analytics';

import {
SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET,
} from 'state/action-types';

import { getSiteTitle } from 'state/signup/steps/site-title/selectors';
import { getSurveyVertical, getSurveySiteType } from 'state/signup/steps/survey/selectors';

Expand Down Expand Up @@ -152,6 +156,68 @@ function setThemeOnSite( callback, { siteSlug }, { themeSlug } ) {
} );
}

/**
* Gets username suggestions from the API.
*
* Ask the API to validate a username.
*
* If the API returns a suggestion, then the username is already taken.
* If there is no error from the API, then the username is free.
*
* @param {string} username The username to get suggestions for.
* @param {object} reduxState The Redux state object
*/
function getUsernameSuggestion( username, reduxState ) {
const fields = {
givesuggestions: 1,
username: username
};

// Clear out the local storage variable before sending the call.
reduxState.dispatch( {
type: SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET,
data: ''
} );

wpcom.undocumented().validateNewUser( fields, ( error, response ) => {
if ( error || ! response ) {
return null;
}

/**
* Default the suggested username to `username` because if the validation succeeds would mean
* that the username is free
*/
let resultingUsername = username;

/**
* Only start checking for suggested username if the API returns an error for the validation.
*/
if ( ! response.success ) {
const { messages } = response;

/**
* The only case we want to update username field is when the username is already taken.
*
* This ensures that the validation is done
*
* Check for:
* - username taken error -
* - a valid suggested username
*/
if ( messages.username && messages.username.taken && messages.suggested_username ) {
resultingUsername = messages.suggested_username.data;
}
}

// Save the suggested username for later use
reduxState.dispatch( {
type: SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET,
data: resultingUsername
} );
} );
}

module.exports = {
createSiteWithCart: createSiteWithCart,

Expand Down Expand Up @@ -233,5 +299,7 @@ module.exports = {
} );
},

setThemeOnSite: setThemeOnSite
setThemeOnSite: setThemeOnSite,

getUsernameSuggestion: getUsernameSuggestion
};
10 changes: 9 additions & 1 deletion client/signup/steps/domains/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ var StepWrapper = require( 'signup/step-wrapper' ),
{ DOMAINS_WITH_PLANS_ONLY } = require( 'state/current-user/constants' ),
{ getSurveyVertical } = require( 'state/signup/steps/survey/selectors.js' ),
analyticsMixin = require( 'lib/mixins/analytics' ),
signupUtils = require( 'signup/utils' );
signupUtils = require( 'signup/utils' ),
getUsernameSuggestion = require( 'lib/signup/step-actions' ).getUsernameSuggestion;

import { getCurrentUser, currentUserHasFlag } from 'state/current-user/selectors';
import Notice from 'components/notice';
Expand All @@ -28,6 +29,10 @@ const registerDomainAnalytics = analyticsMixin( 'registerDomain' ),
mapDomainAnalytics = analyticsMixin( 'mapDomain' );

const DomainsStep = React.createClass( {
contextTypes: {
store: React.PropTypes.object
},

showDomainSearch: function() {
page( signupUtils.getStepUrl( this.props.flowName, this.props.stepName, this.props.locale ) );
},
Expand Down Expand Up @@ -120,6 +125,9 @@ const DomainsStep = React.createClass( {
}, this.getThemeArgs() ), [], { domainItem } );

this.props.goToNextStep();

// Start the username suggestion process.
getUsernameSuggestion( siteUrl.split( '.' )[ 0 ], this.context.store );
},

handleAddMapping: function( sectionName, domain, state ) {
Expand Down
1 change: 1 addition & 0 deletions client/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export const SHORTCODE_REQUEST_FAILURE = 'SHORTCODE_REQUEST_FAILURE';
export const SHORTCODE_REQUEST_SUCCESS = 'SHORTCODE_REQUEST_SUCCESS';
export const SIGNUP_COMPLETE_RESET = 'SIGNUP_COMPLETE_RESET';
export const SIGNUP_DEPENDENCY_STORE_UPDATE = 'SIGNUP_DEPENDENCY_STORE_UPDATE';
export const SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET = 'SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET';
export const SIGNUP_STEPS_SITE_TITLE_SET = 'SIGNUP_STEPS_SITE_TITLE_SET';
export const SIGNUP_STEPS_SURVEY_SET = 'SIGNUP_STEPS_SURVEY_SET';
export const SITE_DOMAINS_RECEIVE = 'SITE_DOMAINS_RECEIVE';
Expand Down
7 changes: 7 additions & 0 deletions client/state/signup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ It has only two reducers, because these are the only actions that actually happe
* `SIGNUP_DEPENDENCY_STORE_UPDATE` - update store data with new values
* `SIGNUP_COMPLETE_RESET` - clear out the store

### `optional-dependencies`

Holds optional data that is used between the steps, outside of the defined `dependencies`, which are stored inside `SignupDependencyStore`.

Each piece of data is defined as a separate reducer.

* `SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET` - Username suggestion based on the chosen domain name during Signup. [PR for more information](https://github.com/Automattic/wp-calypso/pull/6596)
28 changes: 28 additions & 0 deletions client/state/signup/optional-dependencies/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { combineReducers } from 'redux';

/**
* Internal dependencies
*/
import {
SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET,
} from 'state/action-types';

import { createReducer } from 'state/utils';
import { suggestedUsernameSchema } from './schema';

const suggestedUsername = createReducer( '',
{
[ SIGNUP_OPTIONAL_DEPENDENCY_SUGGESTED_USERNAME_SET ]: ( state = null, action ) => {
return action.data;
}
},
suggestedUsernameSchema
);

export default combineReducers( {
suggestedUsername
} );

3 changes: 3 additions & 0 deletions client/state/signup/optional-dependencies/schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const suggestedUsername = {
type: [ 'string', 'null' ]
};
2 changes: 2 additions & 0 deletions client/state/signup/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { combineReducers } from 'redux';
* Internal dependencies
*/
import dependencyStore from './dependency-store/reducer';
import optionalDependencies from './optional-dependencies/reducer';
import steps from './steps/reducer';

export default combineReducers( {
dependencyStore,
optionalDependencies,
steps,
} );

0 comments on commit eaa6ea0

Please sign in to comment.