2
0

🚸 (phone) Improve phone input behavior and validation

Now accepting landline phone numbers. Consistent select UI on every browser. Auto focus on country select.
This commit is contained in:
Baptiste Arnaud
2023-03-09 14:19:39 +01:00
parent bf1fbf2c53
commit 6b08df71ba
10 changed files with 70 additions and 41 deletions

View File

@ -37,7 +37,7 @@ test.describe('Phone input block', () => {
await page.click('text=Restart') await page.click('text=Restart')
await page.locator(`input[placeholder="+33 XX XX XX XX"]`).type('+33 6 73') await page.locator(`input[placeholder="+33 XX XX XX XX"]`).type('+33 6 73')
await expect(page.getByRole('combobox')).toHaveText(/🇫🇷.+/) await expect(page.getByText('🇫🇷')).toBeVisible()
await page.locator('button >> text="Go"').click() await page.locator('button >> text="Go"').click()
await expect(page.locator('text=Try again bro')).toBeVisible() await expect(page.locator('text=Try again bro')).toBeVisible()
await page await page

View File

@ -23,11 +23,11 @@
"db": "workspace:*", "db": "workspace:*",
"google-spreadsheet": "3.3.0", "google-spreadsheet": "3.3.0",
"got": "12.5.3", "got": "12.5.3",
"libphonenumber-js": "^1.10.21",
"next": "13.1.6", "next": "13.1.6",
"nextjs-cors": "^2.1.2", "nextjs-cors": "^2.1.2",
"nodemailer": "6.9.1", "nodemailer": "6.9.1",
"openai": "^3.2.1", "openai": "^3.2.1",
"phone": "^3.1.34",
"qs": "6.11.0", "qs": "6.11.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",

View File

@ -1,4 +1,4 @@
import phone from 'phone' import { parsePhoneNumber } from 'libphonenumber-js'
export const formatPhoneNumber = (phoneNumber: string) => export const formatPhoneNumber = (phoneNumber: string) =>
phone(phoneNumber).phoneNumber parsePhoneNumber(phoneNumber).formatInternational()

View File

@ -1,4 +1,4 @@
import { phone } from 'phone' import { isValidPhoneNumber } from 'libphonenumber-js'
export const validatePhoneNumber = (phoneNumber: string) => export const validatePhoneNumber = (phoneNumber: string) =>
phone(phoneNumber).isValid isValidPhoneNumber(phoneNumber)

View File

@ -50,13 +50,13 @@ export const continueBotFlow =
message: 'Current block is not an input block', message: 'Current block is not an input block',
}) })
if (reply && !isReplyValid(reply, block)) return parseRetryMessage(block)
const formattedReply = formatReply(reply, block.type) const formattedReply = formatReply(reply, block.type)
if (!formattedReply && !canSkip(block.type)) { if (!formattedReply && !canSkip(block.type)) {
return parseRetryMessage(block) return parseRetryMessage(block)
} }
if (formattedReply && !isReplyValid(formattedReply, block))
return parseRetryMessage(block)
const newSessionState = await processAndSaveAnswer( const newSessionState = await processAndSaveAnswer(
state, state,

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/js", "name": "@typebot.io/js",
"version": "0.0.21", "version": "0.0.22",
"description": "Javascript library to display typebots on your website", "description": "Javascript library to display typebots on your website",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@ -0,0 +1,16 @@
import { JSX } from 'solid-js/jsx-runtime'
export const ChevronDownIcon = (props: JSX.SvgSVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2px"
stroke-linecap="round"
stroke-linejoin="round"
{...props}
>
<polyline points="6 9 12 15 18 9" />
</svg>
)

View File

@ -1,4 +1,5 @@
import { ShortTextInput } from '@/components' import { ShortTextInput } from '@/components'
import { ChevronDownIcon } from '@/components/icons/ChevronDownIcon'
import { SendButton } from '@/components/SendButton' import { SendButton } from '@/components/SendButton'
import { InputSubmitContent } from '@/types' import { InputSubmitContent } from '@/types'
import { isMobile } from '@/utils/isMobileSignal' import { isMobile } from '@/utils/isMobileSignal'
@ -75,7 +76,13 @@ export const PhoneInput = (props: PhoneInputProps) => {
const selectNewCountryCode = ( const selectNewCountryCode = (
event: Event & { currentTarget: { value: string } } event: Event & { currentTarget: { value: string } }
) => { ) => {
setSelectedCountryCode(event.currentTarget.value) const code = event.currentTarget.value
setSelectedCountryCode(code)
const dial_code = phoneCountries.find(
(country) => country.code === code
)?.dial_code
if (inputValue() === '' && dial_code) setInputValue(dial_code)
inputRef?.focus()
} }
onMount(() => { onMount(() => {
@ -91,30 +98,37 @@ export const PhoneInput = (props: PhoneInputProps) => {
}} }}
onKeyDown={submitWhenEnter} onKeyDown={submitWhenEnter}
> >
<div class="flex flex-1"> <div class="flex">
<select <div class="relative typebot-country-select flex justify-center items-center rounded-md">
onChange={selectNewCountryCode} <div class="pl-2 pr-1 flex items-center gap-2">
class="w-12 pl-2 focus:outline-none rounded-lg typebot-country-select" <span>
>
<option selected>
{ {
phoneCountries.find( phoneCountries.find(
(country) => selectedCountryCode() === country.code (country) => selectedCountryCode() === country.code
)?.flag )?.flag
} }
</option> </span>
<For <ChevronDownIcon class="w-3" />
each={phoneCountries.filter( </div>
(country) => country.code !== selectedCountryCode()
)} <select
onChange={selectNewCountryCode}
class="absolute top-0 left-0 w-full h-full cursor-pointer opacity-0"
> >
<For each={phoneCountries}>
{(country) => ( {(country) => (
<option value={country.code}> <option
{country.name} ({country.dial_code}) value={country.code}
selected={country.code === selectedCountryCode()}
>
{country.name}{' '}
{country.dial_code ? `(${country.dial_code})` : ''}
</option> </option>
)} )}
</For> </For>
</select> </select>
</div>
<ShortTextInput <ShortTextInput
type="tel" type="tel"
ref={inputRef} ref={inputRef}

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/react", "name": "@typebot.io/react",
"version": "0.0.21", "version": "0.0.22",
"description": "React library to display typebots on your website", "description": "React library to display typebots on your website",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

13
pnpm-lock.yaml generated
View File

@ -370,6 +370,7 @@ importers:
google-auth-library: 8.7.0 google-auth-library: 8.7.0
google-spreadsheet: 3.3.0 google-spreadsheet: 3.3.0
got: 12.5.3 got: 12.5.3
libphonenumber-js: ^1.10.21
models: workspace:* models: workspace:*
next: 13.1.6 next: 13.1.6
next-transpile-modules: 10.0.0 next-transpile-modules: 10.0.0
@ -378,7 +379,6 @@ importers:
nodemailer: 6.9.1 nodemailer: 6.9.1
openai: ^3.2.1 openai: ^3.2.1
papaparse: 5.3.2 papaparse: 5.3.2
phone: ^3.1.34
qs: 6.11.0 qs: 6.11.0
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0 react-dom: 18.2.0
@ -400,11 +400,11 @@ importers:
db: link:../../packages/db db: link:../../packages/db
google-spreadsheet: 3.3.0 google-spreadsheet: 3.3.0
got: 12.5.3 got: 12.5.3
libphonenumber-js: 1.10.21
next: 13.1.6_6m24vuloj5ihw4zc5lbsktc4fu next: 13.1.6_6m24vuloj5ihw4zc5lbsktc4fu
nextjs-cors: 2.1.2_next@13.1.6 nextjs-cors: 2.1.2_next@13.1.6
nodemailer: 6.9.1 nodemailer: 6.9.1
openai: 3.2.1 openai: 3.2.1
phone: 3.1.34
qs: 6.11.0 qs: 6.11.0
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
@ -14284,6 +14284,10 @@ packages:
resolution: {integrity: sha512-MDZ1zLIkfSDZV5xBta3nuvbEOlsnKCPe4z5r3hyup/AXveevkl9A1eSWmLhd2FX4k7pJDe4MrLeQsux0HI/VWg==} resolution: {integrity: sha512-MDZ1zLIkfSDZV5xBta3nuvbEOlsnKCPe4z5r3hyup/AXveevkl9A1eSWmLhd2FX4k7pJDe4MrLeQsux0HI/VWg==}
dev: false dev: false
/libphonenumber-js/1.10.21:
resolution: {integrity: sha512-/udZhx49av2r2gZR/+xXSrwcR8smX/sDNrVpOFrvW+CA26TfYTVZfwb3MIDvmwAYMLs7pXuJjZX0VxxGpqPhsA==}
dev: false
/lie/3.1.1: /lie/3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies: dependencies:
@ -15983,11 +15987,6 @@ packages:
sha.js: 2.4.11 sha.js: 2.4.11
dev: false dev: false
/phone/3.1.34:
resolution: {integrity: sha512-h+nJqLyzA4vbPlD9poMS6fqeW4Dz1lRNPK9qHmrM7Pqkac/0RdiFZrbSJTVxE5xj/HFAaggQTZyfj6XmSjootA==}
engines: {node: '>=12'}
dev: false
/php-parser/3.1.3: /php-parser/3.1.3:
resolution: {integrity: sha512-hPvBmnRYPqWEtMfIFOlyjQv1q75UUtxt4U+YscKIQViGmEE2Xa4BuS1B1/cZdjy7MVcwtnr0WkEsr915LgRKOw==} resolution: {integrity: sha512-hPvBmnRYPqWEtMfIFOlyjQv1q75UUtxt4U+YscKIQViGmEE2Xa4BuS1B1/cZdjy7MVcwtnr0WkEsr915LgRKOw==}
dev: true dev: true