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

Update Address search - show all inputs #6569

Merged
merged 24 commits into from
Dec 23, 2021
Merged

Conversation

aldo-expensify
Copy link
Contributor

@aldo-expensify aldo-expensify commented Dec 2, 2021

Added reviewers from previous PR because you already have some context and saw most of these changes :)

Similar to the previous PR, but without moving the error handling to the form's state. I did a work around to batch clearing errors and kept the errors in Onyx.

Details

Allow the user to manually complete address information and all fields are visible:

  • Added state, city and zip code fields back in the following steps: "Company Step", "Personal information (requestor)" and "Additional information (beneficial owners)"
  • Added state and zip code to AddDebitCardPage
  • When we select a result from the address autocomplete, we use the information that is available. If some part is missing (i.e. city), we just ignore that.
  • Deleted unused validation method for Google places address components

Fixed Issues

$ #6309

Tests / QA steps

  1. Create NewDot account, verify it.
  2. Create workspace and start the add VBA flow
  3. In "Company information" step:
    1. You should see the inputs: Company address, City, Zip Code, and State:
    2. Click "Save & Continue", they should get inline errors
    3. Typing in any of the field should clear the error.
    4. Type and address in Company address and select a result. The other address fields (City, Zip Code, and State) should get updated with the information from the selected result.
    5. Focus on the Company address field and type #12334 at the end (adding the apt number).
    6. Click "Save & Continue", you should be able to proceed to the next step.
  4. Do the same steps (sub-steps in step (3)) for "Personal information"
  5. Do the same steps (sub-steps in step (3)) for "Additional information" adding beneficial owners
  6. Navigate to settings/payments/add-debit-card and do the same steps (sub-steps in step (3)). The only difference in this view is that the City field is not available.

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

Web

Mobile Web

Desktop

iOS

Android

onChange callback gets called with an object, and only once when
a result is selected
Had to add again `skippedFirstOnChangeText` state to work around the
library's feature that is clearing the input at the beginning.
Update IndentityForm input names, now it reads and writes using the same
input names for address related fields.
@@ -206,7 +206,7 @@ function isValidURL(url) {
* @returns {Object}
*/
function validateIdentity(identity) {
const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob'];
const requiredFields = ['firstName', 'lastName', 'addressStreet', 'addressCity', 'addressZipCode', 'addressState', 'ssnLast4', 'dob'];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is because I changed these names in the IdentityForm.js for consistency, see lines:

value={props.values.state}
onChange={value => props.onFieldChange('addressState', value)}

IdentityForm is reading values using one key (i.e. state) and then calling onChangeText with a different key (i.e. addressState). This just makes it harder to follow and update the logic where we already have some key renaming (i.e. requestor step, beneficial owners).

Copy link
Contributor

Choose a reason for hiding this comment

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

This just makes it harder to follow and update the logic where we already have some key renaming (i.e. requestor step, beneficial owners).

Unsure what is hard to follow or update, but won't we have to "rename" again if something else decides to use this method which is meant to be generic? Could be better to just leave it generic and then pass in fields we want to validate regardless of what they are named.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, looking at it again, this function could really stay as it is. The thing that was confusing is the IdentityForm reading an input with one key and then calling "onFieldChange" with another key.

@aldo-expensify
Copy link
Contributor Author

aldo-expensify commented Dec 2, 2021

Two more things:

  • @luacmartins commented that typing the Apt # in the street field (autocomplete) causing the autocomplete to show results is not the best. I agree, but I would say lets do that in a separate PR.
  • Since there is not "ref" in the AddressSearch, I think it can be converted to class component. Let me know if I should do that here.

@aldo-expensify aldo-expensify marked this pull request as ready for review December 2, 2021 23:08
@aldo-expensify aldo-expensify requested a review from a team as a code owner December 2, 2021 23:08
@MelvinBot MelvinBot requested review from deetergp and removed request for a team December 2, 2021 23:09
@aldo-expensify aldo-expensify removed the request for review from deetergp December 2, 2021 23:09
Copy link
Contributor

@luacmartins luacmartins left a comment

Choose a reason for hiding this comment

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

While testing, I've seen the following behavior:

  1. Selecting an address from the dropdown does not clear City and Zip Code errors (see video below).
  2. Just leaving this one here for the record, as we might want to fix it in a different PR. When the dropdown is open, tabbing out will cycle through all dropdown options (without highlighting them) until we finally land on the City field after 6 tabs. The only way to close the dropdown then is to focus on it again and click away or selecting one of the values. Maybe we should close the dropdown on tab out?
error.mov

src/components/AddressSearch.js Outdated Show resolved Hide resolved
src/components/AddressSearch.js Outdated Show resolved Hide resolved
@aldo-expensify
Copy link
Contributor Author

  1. Selecting an address from the dropdown does not clear City and Zip Code errors (see video below).

Thanks 😬 , will have a look at it!

  1. Just leaving this one here for the record, as we might want to fix it in a different PR. When the dropdown is open, tabbing out will cycle through all dropdown options (without highlighting them) until we finally land on the City field after 6 tabs. The only way to close the dropdown then is to focus on it again and click away or selecting one of the values. Maybe we should close the dropdown on tab out?

There is this separate GH issue for that: #6235

@aldo-expensify
Copy link
Contributor Author

Selecting an address from the dropdown does not clear City and Zip Code errors (see video below).

Fixed!

@aldo-expensify
Copy link
Contributor Author

I'm going on vacations for two weeks starting on Monday, so I won't be able to continue and finish this during that time.

If anyone wants to use this PR and finish it, feel free to do it. Also feel free to pick the original issue and do a new PR if this doesn't seem to be the right path :)

If the issue is still there when I come back, I can start working on it. I don't think it is of high priority because the user currently can enter any address manually.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Left a few more comments and ideas for how to improve a few things.

I think it would be good to maybe try and just wrap this one up and really try to do the bare minimum simplest things we can to make this work - then revisit things like perfecting errors and validation once the forms doc refactors start.

src/components/AddressSearch.js Show resolved Hide resolved
src/libs/ReimbursementAccountUtils.js Outdated Show resolved Hide resolved
@@ -206,7 +206,7 @@ function isValidURL(url) {
* @returns {Object}
*/
function validateIdentity(identity) {
const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob'];
const requiredFields = ['firstName', 'lastName', 'addressStreet', 'addressCity', 'addressZipCode', 'addressState', 'ssnLast4', 'dob'];
Copy link
Contributor

Choose a reason for hiding this comment

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

This just makes it harder to follow and update the logic where we already have some key renaming (i.e. requestor step, beneficial owners).

Unsure what is hard to follow or update, but won't we have to "rename" again if something else decides to use this method which is meant to be generic? Could be better to just leave it generic and then pass in fields we want to validate regardless of what they are named.

// We use `skippedFirstOnChangeText` to work around a feature of the library:
// The library is calling onChangeText with '' at the start and we don't need this
// https://github.com/FaridSafi/react-native-google-places-autocomplete/blob/47d7223dd48f85da97e80a0729a985bbbcee353f/GooglePlacesAutocomplete.js#L148
const [skippedFirstOnChangeText, setSkippedFirstOnChangeText] = useState(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not saying this is likely but example of how this could go wrong in the future...

  • Someone updates this library and the bug gets fixed
  • They are now stuck having to figure out why the first onChangeText never fires
  • Maybe they find this code eventually 😂

Copy link
Contributor

Choose a reason for hiding this comment

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

src/components/AddressSearch.js Outdated Show resolved Hide resolved
src/components/AddressSearch.js Outdated Show resolved Hide resolved
src/pages/ReimbursementAccount/ACHContractStep.js Outdated Show resolved Hide resolved
@@ -233,14 +252,20 @@ class ACHContractStep extends React.Component {
</ExpensifyText>
<IdentityForm
style={[styles.mb2]}
onFieldChange={(inputKey, value) => this.clearErrorAndSetBeneficialOwnerValue(index, inputKey, value)}
onFieldChange={(inputKeyOrValues, value) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Having onFieldChange return one of two possible things is a bit strange.

What if we just return an object and have the validation and error clearing handled all at the same time for the various fields?

Sorry to sound like a broken record, but the way we validate these will be changing soon in pretty drastic ways so maybe we should not spend much time perfecting this.

Copy link
Contributor Author

@aldo-expensify aldo-expensify Dec 23, 2021

Choose a reason for hiding this comment

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

Having onFieldChange return one of two possible things is a bit strange.

Agreed, didn't liked it, but it was what I came up with to solve the clearing multiple errors in a batch.

What if we just return an object and have the validation and error clearing handled all at the same time for the various fields?

the idea is to ALWAYS pass an object to the onFieldChange callback, even if we are just changing a single field like firstName, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: onFieldChange gets called with an object every time.

src/pages/ReimbursementAccount/ACHContractStep.js Outdated Show resolved Hide resolved
/>
<ExpensifyText style={[styles.mutedTextLabel, styles.mt1]}>{props.translate('common.noPO')}</ExpensifyText>
<View style={[styles.flexRow, styles.mt4]}>
<View style={[styles.flex2, styles.mr2]}>
Copy link
Contributor

Choose a reason for hiding this comment

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

opportunity to DRY up the code perhaps?

Looks very similar to the lines here:

<ExpensifyText style={[styles.mutedTextLabel, styles.mt1]}>{this.props.translate('common.noPO')}</ExpensifyText>
<View style={[styles.flexRow, styles.mt4]}>
<View style={[styles.flex2, styles.mr2]}>
<ExpensiTextInput
label={this.props.translate('common.city')}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did some refactoring for this. Added AddressForm component, the name was based on the name for IndentityForm, which is also a "partial" form.

@marcaaron
Copy link
Contributor

@aldo-expensify just checking on this one. Are you actively working on it and if not should we close it?

@aldo-expensify
Copy link
Contributor Author

@aldo-expensify just checking on this one. Are you actively working on it and if not should we close it?

I'll try to revive this PR and address your comments, there are a bunch of conflicts now :P

@aldo-expensify
Copy link
Contributor Author

aldo-expensify commented Dec 23, 2021

Update:

  • (AddressSearch) Using ref instead of state to keep track of skipping the first time the library calls onTextChange with ''
  • Use street, city, zipCode and state without "address" prefix in IdentityForm and ValidationUtils. This removed a few input key renaming (maps) but added new ones in other places. Overall, I think it reduced the number of changes and I personally prefer it without the "address" prefix everywhere.

@aldo-expensify
Copy link
Contributor Author

Update: IdentityForm now always calls onFieldChange passing an object with shape {[inputName]: value}. The callbacks look less ugly now.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Tests well, nice job and thanks for sticking with it!


const values = {};
if (street && street.length > props.value.length) {
// Don't replace if the user has typed something longer. I.e. maybe the user entered the Apt #
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the change!

NAB, this comment does help explain why we might not want to use the street number and street name provided by Google Places. I do have a lingering concern that we are not quite painting the exact picture of why we need this logic. Here's a suggestion that is more verbose, but explains the situation in greater detail

We are only passing the street number and name if the combined length is longer than the value that was initially passed to the autocomplete component. Google Places can truncate details like Apt # and this is the best way we have to tell that the new value it's giving us is less specific than the one the user entered manually.

const state = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name');

const values = {};
if (street && street.length > props.value.length) {
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, should we trim props.value.length in case there is whitespace that makes it longer but less accurate?

Also, I wonder if we need to worry about a case like this where someone enters some very long address into the street input. I feel like in trying to optimize for the Apartment # case we are maybe introducing the possibility of bad street info instead of trusting the correctness of what Google Places returns.

Here's an example of what I mean:

2021-12-23_10-35-56

2021-12-23_10-36-01

I think we can maybe wait and see if this is a problem...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NAB, should we trim props.value.length in case there is whitespace that makes it longer but less accurate?

Also, I wonder if we need to worry about a case like this where someone enters some very long address into the street input. I feel like in trying to optimize for the Apartment # case we are maybe introducing the possibility of bad street info instead of trusting the correctness of what Google Places returns.

I think we can maybe wait and see if this is a problem...

I honestly don't like it either, I think the ideal case is to have the Apt number in a separate field. As @luacmartins , this also causes the autocomplete to start showing results while the user may be only trying to input the apt number and it looks weird :P. Having said that, if we wanted to do a separate input for it, I think that should be done in a separate pr/issue.

src/components/AddressSearch.js Show resolved Hide resolved
errors: {},
};

const AddressForm = props => (
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for refactoring this!

this.setValue(renamedValues);
this.clearErrors(_.keys(renamedValues));
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

So much cleaner than what we had before 👏

onFieldChange={(values) => {
const renamedValues = {};
_.each(values, (value, inputKey) => {
const renamedInputKey = lodashGet(this.renamedFields, inputKey, inputKey);
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, if we are not using this.renamedFields again we could just declare them in this method. Only thought to mention it because we are doing that in the RequestorStep and could keep it consistent.

@marcaaron marcaaron merged commit 4b7f78a into main Dec 23, 2021
@marcaaron marcaaron deleted the aldo_vba-address-all-inputs2 branch December 23, 2021 20:40
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by @marcaaron in version: 1.1.23-2 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 failure ❌
🍎 iOS 🍎 failure ❌
🕸 web 🕸 success ✅

_.each(values, (value, inputKey) => {
if (inputKey === 'city') {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Just noticed this, but don't we want to update the city field?

Copy link
Contributor Author

@aldo-expensify aldo-expensify Jan 3, 2022

Choose a reason for hiding this comment

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

I saw that the city field wasn't properly added in any of these places in the component: state, requiredFields and errorTranslationKeys.

And, most importantly, we didn't really use it when sending the information to the backend here:

function addBillingCard(params) {
const cardMonth = CardUtils.getMonthFromExpirationDateString(params.expirationDate);
const cardYear = CardUtils.getYearFromExpirationDateString(params.expirationDate);
Onyx.merge(ONYXKEYS.ADD_DEBIT_CARD_FORM, {submitting: true});
API.AddBillingCard({
cardNumber: params.cardNumber,
cardYear,
cardMonth,
cardCVV: params.securityCode,
addressName: params.nameOnCard,
addressZip: params.addressZipCode,
currency: CONST.CURRENCY.USD,
}).then(((response) => {
let errorMessage = '';
if (response.jsonCode === 200) {
const cardObject = {
additionalData: {
isBillingCard: false,
isP2PDebitCard: true,
},
addressName: params.nameOnCard,
addressState: params.addressState,
addressStreet: params.addressStreet,
addressZip: params.addressZipCode,
cardMonth,
cardNumber: CardUtils.maskCardNumber(params.cardNumber),
cardYear,
currency: 'USD',

I thought that it was added by mistake when the "manual" switch was added. It made sense to me that it wasn't being sent to the back because I have seen many card forms that don't have a "City" field.

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, not used by the backend hmm OK then thanks!

@OSBotify
Copy link
Contributor

OSBotify commented Jan 4, 2022

🚀 Deployed to production by @francoisl in version: 1.1.24-8 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants