Skip to content
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

Domains: checkout/whois form consolidation #19889

Merged
merged 8 commits into from
Feb 26, 2018

Conversation

ramonjd
Copy link
Member

@ramonjd ramonjd commented Nov 16, 2017

This PR addresses issue #18808

Goals

The core objective is to consolidate the domain checkout and whois contact details form, which share common fields and functionality.

Because of the varying internal business logic of each form, there's no way to unify them completely, however we can attempt to reduce some of the repetition in the contact form itself.

First steps

  • identify common functionality
  • create a <ContactDetailsFormFields /> common component
  • migrate formState controller into <ContactDetailsFormFields />
  • integration with domain details form
  • integrate with WHOIS contact details form

TODO

Update the button class attribute value for the wp-e2e-test.

Screenshots

Domain checkout form

checkout-after

WHOIS form

whois-after

Afterthoughts

This was a worthy exercise, not only because it exposed the idiosyncratic logic and dependencies of both form, but also highlighted areas of complexity, not limited to the following:

  • Dealing with the specific logic of each journey, for example, disabling form fields and rendering a 'GApps' state, led to a component that only really shifts the work to another place.
  • form-state triggers a great deal of rendering and is a little wily

Furthermore, the effort required to consolidate the forms has been quite high, and might turn out to be disproportionate to the need for simplifying two contact forms.

As mentioned, the two forms perform separate tasks, though they share the same contact details requirements. Creating a common dependency could create more work should their internal logic diverge further later. Rather than instructing a swiss-army-like <ContactDetailsFormFields /> to render a form permutations only it knows about, we might come to the conclusion that it would be better to instruct a form-builder component to construct a form using fields controls we specify.

@ramonjd ramonjd added [Feature] Checkout The checkout screen and process for purchases made on WordPress.com. [Feature Group] Emails & Domains Features related to email integrations and domain management. [Status] In Progress labels Nov 16, 2017
@matticbot
Copy link
Contributor

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 50bbc39 to 20145dd Compare November 16, 2017 03:56
<div className="checkout__domain-details-contact-details-fields">
{ this.createField( 'organization', HiddenInput, {
label: translate( 'Organization' ),
text: translate( "+ Add your organization's name" ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Add Organization Name' ) ES Score: 7

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch 2 times, most recently from 5183f6e to b93ea94 Compare November 23, 2017 03:09
<div className="contact-details-form-fields__container">
{ this.createField( 'organization', HiddenInput, {
label: translate( 'Organization' ),
text: translate( "+ Add your organization's name" ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Add Organization Name' ) ES Score: 7

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from b93ea94 to 613f135 Compare November 28, 2017 03:35
HiddenInput,
{
label: translate( 'Organization' ),
text: labelTexts.organization || translate( '+ Add organization name' ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Add Organization Name' ) ES Score: 17
See 1 additional suggestion in the PR translation status page

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 613f135 to e0e8e6b Compare November 28, 2017 04:50
@ramonjd ramonjd added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Nov 28, 2017
@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 200b5b2 to 123a844 Compare November 29, 2017 04:54
HiddenInput,
{
label: translate( 'Organization' ),
text: labelTexts.organization || translate( '+ Add organization name' ),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! I've found a possible matching string that has already been translated 15 times:
translate( 'Add Organization Name' ) ES Score: 17
See 1 additional suggestion in the PR translation status page

Help me improve these suggestions: react with 👎 if the suggestion doesn't make any sense, or with 👍 if it's a particularly good one (even if not implemented).

@ramonjd ramonjd changed the title [WIP] Checkout: Domains checkout/whois form consolidation Domains: checkout/whois form consolidation Nov 29, 2017
@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from a0faa3c to 93c813c Compare November 30, 2017 05:33
Copy link
Contributor

@klimeryk klimeryk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pure awesomeness 👍
Very well done - these two forms have gathered a lot of cruft and it's great to a major cleanup like this 🙇

I've noticed just a few regressions, but given the scope of this PR (and the vast responsibilities of both forms) it was expected. I've tested a few scenarios: registering a new domain, a new domain that does not support privacy, just G Suite, editing WHOIS for an HRS domain, editing WHOIS for a .nl domain (mandatory fax field, weeeee), editing WHOIS for a WWD domain.

It seems it's not possible to update WHOIS info for a WWD domain. The reason is that fax is not passed to the backend, so it defaults to null when sent to WWD... and WWD fails with an error:

{
  "raw_response": null,
  "response": {
    "code": "INVALID_BODY",
    "message": "Request body doesn't fulfill schema, see details in `fields`",
    "fields": [
      {
        "message": "is not a string",
        "path": "contacts.contactRegistrant.fax",
        "code": "UNEXPECTED_TYPE"
      },
...

Either we should send empty fax or fix the backend.

Additionally, when saving WWD info if you change the email, a confirmation dialog is shown (to inform the user what will happen - that they need verify the change from both emails, etc.). However, when you hit Request Confirmation button, the main area of Calypso goes blank and there's this confusing error in the console:
screen shot 2017-12-03 at 11 36 46
I'm guessing something is undefined.

Apart from what I've mentioned in the comments, I've noticed that the check introduced in #16240 was undone by this - we should not allow the user to re-save the same WHOIS info. They often did this thinking that it will allow them to lift the 60-day lock :/

Also worth noting: the PR needs a rebase 😁

};
```

### onContactDetailsChange {Func} (required)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant onSubmit :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hawkeye!
Now that I'm reviewing it, I've actually noticed several mistakes in there ;)
Thanks!

@@ -243,7 +243,8 @@ class PhoneInput extends React.PureComponent {
name={ this.props.name }
ref={ this.setNumberInputRef }
type="tel"
className={ classnames( { 'is-error': this.props.isError } ) }
disabled={ this.props.disabled }
className={ this.props.isError && 'is-error' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this line was added before #19940 😀

Edit: hah, it's a warning even:
screen shot 2017-12-03 at 10 57 30

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase fixed it! :)

export class ContactDetailsFormFields extends Component {
static propTypes = {
eventFormName: PropTypes.string,
contactDetails: PropTypes.shape( {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great usage of shape! 👍
But the list of field names seems unnecessarily duplicated. Maybe it could be generated based on this.fieldNames with some map calls and the spread operator?

contactDetails: Object.assign( {}, ...this.fieldNames.map( ( field ) => ( { [field]: PropTypes.string } ) ) )

Similarly, defaultProps can be written as:

contactDetails: Object.assign( {}, ...this.fieldNames.map( ( field ) => ( { [field]: '' } ) ) )

Or is that too magical? 😁

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like less repetition :) It makes sense, since they are, and must be, equal.

We'd have to pull this.fieldNames out of the constructor. Making it another static var would work (and we could access it through ContactDetailsFormFields.fieldNames).

But now I reflect on it, this.fieldNames is a definitive and immutable list of form fields. It might work better as a constant. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, I did not think about the static/instance problem, but yup, you're right - it's a constant/immutable list anyway.

translate: identity,
};

constructor() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should pass props through the constructor to super - otherwise this.props will be undefined. See https://reactjs.org/docs/react-component.html#constructor

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still no props? 😢

} );
}

loadFormState = fn => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you something more descriptive than fn? 🙏 Initially I thought it was something from lodash or something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's legacy from the previous form-state hydration. You're right, it's a little opaque. Will change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, should have noticed that, sorry about that. But, yeah, let's rename it to something more descriptive 🙇

@@ -243,7 +243,8 @@ class PhoneInput extends React.PureComponent {
name={ this.props.name }
ref={ this.setNumberInputRef }
type="tel"
className={ classnames( { 'is-error': this.props.isError } ) }
disabled={ this.props.disabled }
className={ this.props.isError && 'is-error' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that once there's a validation error in the phone field, it stays in the is-error state 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely 🤔 ... Haven't been able to reproduce this yet. But I'm going to try!

@@ -344,117 +156,46 @@ export class DomainDetailsForm extends PureComponent {
renderPrivacySection() {
return (
<PrivacyProtection
checkPrivacyRadio={ this.allDomainItemsHavePrivacy() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the PrivacyProtection component was not updated after this rename and it's still relying on this.props.checkPrivacyRadio, which leads to this interesting state:
screen shot 2017-12-03 at 11 09 36

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I reproduce this?

FYI, I ditched most of the props on this component since countriesList, disabled, fields, and onCheckboxChange weren't being used by <PrivacyProtection /> at all.

And this.handleCheckboxChange didn't even exist in the domain details component :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, I did not do anything special - just try to register a new domain that supports privacy. And try to check a different radio button - both will be checked as a result.

cartItems.getDomainRegistrationsWithoutPrivacy( this.props.cart ).length === 0 &&
cartItems.getDomainTransfersWithoutPrivacy( this.props.cart ).length === 0
);
allDomainRegistrationsHavePrivacy() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only domain registrations? Domain transfers are domains too! (or was this discussed with @aidvu?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably why it needs a rebase. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaah, damn GitHub, confusing me! 🤜 :octocat: 🤛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to rebase as the first task 👍

@ramonjd
Copy link
Member Author

ramonjd commented Dec 4, 2017

@klimeryk Huge thanks for your review so far. Really helpful.

Apart from what I've mentioned in the comments, I've noticed that the check introduced in #16240 was undone by this - we should not allow the user to re-save the same WHOIS info.

I had this on my radar, and I thought it was working. I must have broken my fix. :)

Edit:

You did, perhaps without knowing it, find the solution to the above for me:

Either we should send empty fax or fix the backend.

I was deleting the fax (as we were doing in the original domain-details-form.jsx) and therefore the comparison check failed. 👍

@ramonjd
Copy link
Member Author

ramonjd commented Dec 5, 2017

However, when you hit Request Confirmation button, the main area of Calypso goes blank and there's this confusing error in the console

My mistake. I wasn't caching the form values. Also noticed the effect of #16239 Domains: After updating, the WHOIS store is not automatically updated, whereby the button isn't disabled. I might take a look at that later, but I think we just need to do another upgradesActions.fetchWhois to update the store after submit, or something... :)

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 93c813c to 4ced986 Compare December 5, 2017 02:14
@klimeryk
Copy link
Contributor

klimeryk commented Dec 5, 2017

I was deleting the fax (as we were doing in the original domain-details-form.jsx) and therefore the comparison check failed. 👍

That probably worked because we use Tucows for new domains, while this empty value seems to be required by WWD only 🤷‍♂️

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 4ced986 to f545c20 Compare December 7, 2017 07:18
@deBhal
Copy link
Contributor

deBhal commented Feb 1, 2018

We're looking at addressing the conflict in #21781, and then we can circle back to this.

@aidvu
Copy link
Contributor

aidvu commented Feb 1, 2018

Thanks for the update @deBhal.

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 30d97c2 to 42d9d95 Compare February 7, 2018 06:31
@ramonjd
Copy link
Member Author

ramonjd commented Feb 7, 2018

We're looking at addressing the conflict in #21781, and then we can circle back to this.

Fixed in #22156. Full steam ahead 🚂 !!

@ramonjd
Copy link
Member Author

ramonjd commented Feb 8, 2018

Here are some bundle stats for this branch.

TL;DR This PR adds circa 20k to the gzipped domains chunk mainly due to phone number validation code.


Looks like it's adding about 20k to the gzipped payload in domains/components. From what I can tell, more than 3/4 of this comes down to <FormPhoneMediaInput /> and its dependency data.js, which contains the rules for validating phone numbers. Both comprise a fairly hefty slab of code that's only used in this form.

phone-input screen shot 2018-02-08 at 12 13 46 pm

Previously we were only pulling this component in the checkout chunk, but with the consolidated form it's now in domains.

I experimented with loading the shared <ContactDetailsFormFields /> using <AsyncLoad />. This reduces the payload overall, only slightly, but creates a 25k chunk that has to be downloaded. Given that checkout is one of our most important flows, I'd argue we wouldn't want to risk keeping the user waiting for a sequential HTTP call.

with async screen shot 2018-02-08 at 12 11 37 pm

Instead, I'm inclined to see if we can load data.js asynchronously, and refactor the phone input to handle the case when no validation is present. I guess this depends on how crucial phone validation is to WordPress / the domain registrar.

Overall, it's a slight step backwards in the quest to lower our bundle, but we might be able to claw back some of those bytes later. (?)

@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from 42d9d95 to d906e24 Compare February 15, 2018 01:04
@ramonjd ramonjd dismissed klimeryk’s stale review February 15, 2018 01:07

Review changes implemented some time ago

Copy link
Contributor

@klimeryk klimeryk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing job, @ramonjd. Tested all the cases I could think of:

  • Registering a gTLD
  • Registering .ca
  • Registering .fr
  • Editing WHOIS for Tucows gTLD
  • Editing WHOIS for WWD gTLD
  • Editing WHOIS for .fr
  • Purchasing a domain by a brand-new user.
  • Purchasing G Suite for a mapped domain.
  • Purchasing a domain in the domain-only flow.

Everything looks solid - really stellar job on this, @ramonjd 💖
One small request: please merge this when there are many HEs around, just in case there's a regression, so that it will be quickly reported and handled appropriately 🙇

translate: identity,
};

constructor() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still no props? 😢

@klimeryk klimeryk added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Feb 18, 2018
@ramonjd
Copy link
Member Author

ramonjd commented Feb 20, 2018

Thanks @klimeryk !
Wouldn't have got this one even near the goal posts without you and everyone's reviews! I'm going to look at how to reduce the phone data.js when I get time.

Still no props?

Do you mean the props arg in the constructor? We don't need to since we're not accessing this.props in the constructor, so it's fine to leave out.

please merge this when there are many HEs around, just in case there's a regression, so that it will be quickly reported and handled appropriately

Will do. 🙇

@klimeryk
Copy link
Contributor

Do you mean the props arg in the constructor? We don't need to since we're not accessing this.props in the constructor, so it's fine to leave out.

That might be true, but in the future it might change and someone will get unpleasantly surprised ;) You're calling the parent constructor either way, might as well pass the props to it ¯\_(ツ)_/¯

@klimeryk
Copy link
Contributor

Please merge this, @ramonjd, before it grows any more merge conflicts 😅 🌵

Fixed focus events and state field refresh
Hooked in contact details cache to form card
Focussing on address field when country select changed
Removing useless props from Privacy Protection

Added README.md and moved component under component/domains

Passing props to super :)
Not deleting the fax field in the state so that we pass an empty value to the backend
Creating a constant out of this.fieldNames and deconstructing into proptypes
Fixed some some strange reoccurring formatting in the tracksData object

Updated disable button check from PR #20549
…acy selection radio boxes, because the first name field stole the focus using the autofocus attribute
@ramonjd ramonjd force-pushed the try/checkout-whois-form-consolidation branch from d906e24 to cccafa3 Compare February 26, 2018 21:33
@ramonjd ramonjd merged commit 1cc89e2 into master Feb 26, 2018
@ramonjd ramonjd deleted the try/checkout-whois-form-consolidation branch February 26, 2018 22:25
@alisterscott
Copy link
Contributor

alisterscott commented Feb 26, 2018

Hi 👋

I noticed a couple of potential issues with this:

Firstly, at ~900px or narrower the state and postcode fields merge into one. Also, the "Continue to Checkout" label has been renamed to a less descriptive "Continue" - was this intentional? I didn't see this mentioned in the PR.

I'll raise the fields issue as a separate bug

Now

now

Previously

previous

@ramonjd
Copy link
Member Author

ramonjd commented Feb 26, 2018

@alisterscott Thanks for testing! Appreciate it. And for catching the classname change in the e2e tests. Totally slipped past me. (Even though I'd made a note of it in the PR :( )

I'll look into those issues today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Checkout The checkout screen and process for purchases made on WordPress.com. [Feature Group] Emails & Domains Features related to email integrations and domain management. [Pri] Low
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants