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

Redesigns autocomplete component and adds metric tracking #11

Merged
merged 7 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lob/react-address-autocomplete",
"version": "1.1.5",
"version": "1.1.7",
"description": "A collection of components and utility functions for verifying and suggesting addresses using Lob",
"author": "Lob",
"license": "MIT",
Expand Down
36 changes: 36 additions & 0 deletions src/Autocomplete.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.lob-gray-text {
color: #888;
text-decoration: inherit;
}

.lob-label {
align-items: center;
border-bottom: 1px solid #DDDDDD;
cursor: pointer;
display: flex;
font-size: 17px;
padding: 16px;
pointer-events: none;
}

.lob-label > a {
font-weight: 600;
color: #0699D6;
text-decoration: inherit;
}

.lob-label > span {
flex: 1;
font-weight: 400;
margin-left: 12px;
}

.lob-logo {
height: .9em;
margin-left: 1px;
margin-top: 3px;
}

.logo-large {
height: 21px;
}
115 changes: 101 additions & 14 deletions src/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,88 @@
import React, { useEffect, useState, useRef } from 'react'
import Select, { components } from 'react-select'
import throttle from 'lodash.throttle'
import './Autocomplete.css'

// Internal Dependencies
import { postAutocompleteAddress } from './api'

const getLobLabel = () => (
<a
href='https://www.lob.com/address-verification'
style={{ color: 'hsl(0, 0%, 50%)', textDecoration: 'inherit' }}
>
<span style={{ verticalAlign: 'top' }}>Powered by </span>
const LOB_LABEL = 'lob-label'
const LOB_URL =
'https://www.lob.com/address-verification?utm_source=autocomplete&utm_medium=react'

const LobLogo = ({ className }) => {
return (
<svg
style={{ height: '.9em', marginLeft: '1px', marginTop: '3px' }}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 1259 602'
className={className}
>
<path
fill='#0099d7'
/* eslint-ignore-next-line */
d='M1063,141c-47.06,0-89,18.33-121,50.78V0H780V338.74C765,222.53,666.88,138,540,138c-137,0-242,101-242,232a235,235,0,0,0,7.7,60H164V0H0V585H307l14.54-112.68C359.94,550,441.74,602,540,602c127.75,0,225.08-83.62,240-200.41V585H930V540.27c31.8,37,77.27,56.73,133,56.73,103,0,196-109,196-228C1259,239,1175,141,1063,141ZM540,450c-45,0-81-36-81-80s36-80,81-80c46,0,81,35,81,80S585,450,540,450Zm475-1c-46,0-83-36-83-80a82.8,82.8,0,0,1,82.6-83h.4c47,0,85,37,85,83C1100,413,1062,449,1015,449Z'
/>
</svg>
)
}

const poweredByLob = () => (
<a href={LOB_URL} className='lob-gray-text'>
<span style={{ verticalAlign: 'top' }}>Powered by </span>
<LobLogo className='lob-logo' />
</a>
)

const getLobLabel = () => (
<div className={LOB_LABEL}>
<LobLogo className='logo-large' />
<span className='lob-gray-text'>Deliverable addresses</span>
<a href={LOB_URL}>Learn more</a>
</div>
)

// Highlight the users input in the primary line by comparing char by char. We only check the
// primary line for simplicity sake
const getOptionElement = (suggestion, inputValue) => {
/* eslint-disable camelcase */
const { primary_line, city, state, zip_code } = suggestion

let boldStopIndex = 0

inputValue.split('').forEach((inputChar) => {
if (
inputChar.toLowerCase() ===
primary_line.charAt(boldStopIndex).toLowerCase()
) {
boldStopIndex += 1
}
})

const primaryLineElement =
boldStopIndex === 0 ? (
<span>{primary_line}, </span>
) : boldStopIndex === primary_line.length ? (
<span>
<strong>{primary_line}, </strong>
</span>
) : (
<span>
<strong>{primary_line.substring(0, boldStopIndex)}</strong>
{primary_line.substring(boldStopIndex)},{' '}
</span>
)

return (
<span>
{primaryLineElement}
<span className='lob-gray-text'>
{city}, {state.toUpperCase()}, {zip_code}
</span>
</span>
)
/* eslint-enable camelcase */
}

/**
* Part of Lob's response body schema for US autocompletions
* https://docs.lob.com/#section/Autocompletion-Test-Env
Expand Down Expand Up @@ -115,15 +173,15 @@ const Autocomplete = ({

const newSuggestions = suggestions.map((x) => ({
value: x,
label: `${x.primary_line} ${x.city} ${x.state}`
label: getOptionElement(x, inputValue)
}))

setAutocompleteResults([
...newSuggestions,
{
value: 'none',
value: LOB_LABEL,
label: getLobLabel()
}
},
...newSuggestions
])
})
.catch((err) => {
Expand All @@ -143,7 +201,7 @@ const Autocomplete = ({
fetchData(inputValue, addressComponentValues)
}
}
}, [inputValue])
}, [inputValue, delaySearch])

/** Event handlers */

Expand All @@ -163,6 +221,11 @@ const Autocomplete = ({

// Fires when the select component has changed (as opposed to the input inside the select)
const handleChange = (option) => {
if (option.value === LOB_LABEL) {
window.location.href = LOB_URL
return
}

// User has pasted an address directly into input, let's call the API
if (typeof option === 'string') {
setInputValue(option)
Expand All @@ -181,25 +244,49 @@ const Autocomplete = ({
onSelection(option)
}

const handleSelect = (option) => {
if (option.value !== LOB_LABEL) {
reactSelectProps.onSelect(option)
}
}

const customFilter = (candidate, input) => {
return candidate
}

// Remove padding from first option which is our Lob label
const customStyles = {
option: (styles, { data }) => {
if (data.value === LOB_LABEL) {
return {
...styles,
background: 'none',
cursor: 'pointer',
padding: '0'
}
}
return styles
},
...reactSelectProps.styles
}

return (
<Select
components={{ Input }}
inputValue={inputValue}
options={autocompleteResults}
controlShouldRenderValue={false}
noOptionsMessage={getLobLabel}
noOptionsMessage={poweredByLob}
placeholder='Start typing an address...'
value={selectValue}
{...reactSelectProps}
// We don't let user completely override onChange and onInputChange and risk them breaking
// the behavior of our input component.
filterOption={customFilter}
onChange={handleChange}
onInputChange={handleInputChange}
filterOption={customFilter}
onSelect={handleSelect}
styles={customStyles}
/>
)
}
Expand Down
15 changes: 11 additions & 4 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ export const postAutocompleteAddress = (
addressPrefix,
additionalAddressData
) => {
const url =
'https://api.lob.com/v1/us_autocompletions?valid_addresses=true&case=proper'
const url = new URL('https://api.lob.com/v1/us_autocompletions')
url.searchParams.append('av_integration_origin', window.location.href)
url.searchParams.append('integration', 'react-address-autocomplete')
url.searchParams.append('valid_addresses', 'true')
url.searchParams.append('case', 'proper')
const init = {
method: 'POST',
headers: {
Expand All @@ -25,7 +28,9 @@ export const postAutocompleteAddress = (

export const postVerifyAddress = (apiKey, address) => {
const payload = typeof address === 'string' ? { address } : address
const url = 'https://api.lob.com/v1/us_verifications'
const url = new URL('https://api.lob.com/v1/us_verifications')
url.searchParams.append('av_integration_origin', window.location.href)
url.searchParams.append('integration', 'react-address-autocomplete')
const init = {
method: 'POST',
headers: {
Expand All @@ -43,7 +48,9 @@ export const postVerifyInternationalAddress = (
address,
countryCode
) => {
const url = 'https://api.lob.com/v1/intl_verifications'
const url = new URL('https://api.lob.com/v1/intl_verifications')
url.searchParams.append('av_integration_origin', window.location.href)
url.searchParams.append('integration', 'react-address-autocomplete')
const init = {
method: 'POST',
headers: {
Expand Down