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

refactor: phone number field #10796

Merged
merged 2 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1,212 changes: 0 additions & 1,212 deletions packages/mask/dashboard/assets/region.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
PhoneNumberField,
SendingCodeField,
useCustomSnackbar,
type PhoneNumberFieldValue as PhoneConfig,
} from '@masknet/theme'
import { SendingCodeField, useCustomSnackbar } from '@masknet/theme'
import { Box } from '@mui/material'
import guessCallingCode from 'guess-calling-code'
import { pick } from 'lodash-es'
Expand All @@ -17,6 +12,7 @@ import { phoneRegexp } from '../../../utils/regexp.js'
import { AccountType, Locale, Scenario } from '../../../type.js'
import { PrimaryButton } from '../../PrimaryButton/index.js'
import { RestoreContext } from './RestoreProvider.js'
import { PhoneNumberField } from '@masknet/shared'

export const PhoneField = memo(function PhoneField() {
const language = useLanguage()
Expand All @@ -28,8 +24,20 @@ export const PhoneField = memo(function PhoneField() {
const { state, dispatch, downloadBackupInfo } = RestoreContext.useContainer()
const { loading, phoneForm } = state
const { account, code, dialingCode } = phoneForm
const phoneConfig: PhoneConfig = useMemo(() => pick(phoneForm, 'dialingCode', 'phone', 'country'), [phoneForm])
const setPhoneConfig = useCallback((newConfig: PhoneConfig) => dispatch({ type: 'SET_PHONE', form: newConfig }), [])
const phoneConfig = useMemo(() => pick(phoneForm, 'dialingCode', 'phone'), [phoneForm])
const onPhoneNumberChange = useCallback(
(phoneNumber: string) => {
dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, phone: phoneNumber } })
},
[phoneConfig],
)
const onCountryCodeChange = useCallback(
(code: string) => {
dispatch({ type: 'SET_PHONE', form: { ...phoneConfig, dialingCode: code } })
},
[phoneConfig],
)

useEffect(() => {
if (dialingCode) return
dispatch({ type: 'SET_PHONE', form: { dialingCode: guessCallingCode() } })
Expand Down Expand Up @@ -81,14 +89,18 @@ export const PhoneField = memo(function PhoneField() {
)
}, [account, code, disabled, loading])

console.log(phoneForm)
return (
<>
<PhoneNumberField
fullWidth
code={phoneConfig.dialingCode}
onCodeChange={onCountryCodeChange}
onBlur={validCheck}
onChange={setPhoneConfig}
error={invalidPhone ? t.data_recovery_invalid_mobile() : error || ''}
value={phoneConfig}
placeholder={t.mobile_number()}
onChange={(event) => onPhoneNumberChange(event.target.value)}
error={invalidPhone}
helperText={invalidPhone ? t.data_recovery_invalid_mobile() : error || ''}
value={phoneForm.phone}
/>
<Box mt={1.5}>
<SendingCodeField
Expand Down
1 change: 0 additions & 1 deletion packages/mask/dashboard/constants.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/mask/dashboard/contexts/CloudBackupFormContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useTabs } from '@masknet/theme'
import { phoneRegexp } from '../utils/regexp.js'
import guessCallingCode from 'guess-calling-code'

export interface CloudBackupFormInputs {
email: string
Expand All @@ -28,7 +29,7 @@ function useCloudBackupFormContext() {
email: '',
phone: '',
code: '',
countryCode: '+93',
countryCode: guessCallingCode(),
},
resolver: zodResolver(
z
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { memo, useCallback, useMemo, useState } from 'react'
import { memo, useCallback } from 'react'
import { useDashboardI18N } from '../../../locales/i18n_generated.js'
import { UserContext, useLanguage } from '../../../../shared-ui/index.js'
import { CloudBackupFormContext } from '../../../contexts/CloudBackupFormContext.js'
import { Box, TextField, Typography } from '@mui/material'
import { Box, TextField } from '@mui/material'
import { Controller } from 'react-hook-form'
import { Icons } from '@masknet/icons'
import { CountryCodePicker } from '../../../components/CountryCodePicker/index.js'
import REGIONS from '../../../assets/region.json'

import { CountdownButton, makeStyles, useCustomSnackbar } from '@masknet/theme'
import { AccountType, Scenario, Locale } from '../../../type.js'
import { sendCode } from '../../../utils/api.js'
import { COUNTRY_ICON_URL } from '../../../constants.js'
import { PhoneNumberField } from '@masknet/shared'

const useStyles = makeStyles()((theme) => ({
send: {
Expand All @@ -26,9 +24,6 @@ export const PhoneForm = memo(function PhoneForm() {
const lang = useLanguage()
const { showSnackbar } = useCustomSnackbar()

const [open, setOpen] = useState(false)
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)

const {
formState: {
control,
Expand All @@ -41,13 +36,6 @@ export const PhoneForm = memo(function PhoneForm() {

const [countryCode, phone] = watch(['countryCode', 'phone'])

const countryIcon = useMemo(() => {
if (!countryCode) return
const target = REGIONS.find((x) => x.dial_code === countryCode)
if (!target) return
return `${COUNTRY_ICON_URL}${target.code.toLowerCase()}.svg`
}, [countryCode])

const handleSendVerificationCode = useCallback(async () => {
const response = await sendCode({
account: phone,
Expand All @@ -69,36 +57,15 @@ export const PhoneForm = memo(function PhoneForm() {
control={control}
name="phone"
render={({ field }) => (
<TextField
<PhoneNumberField
{...field}
code={countryCode}
onCodeChange={(code) => setValue('countryCode', code)}
onFocus={() => clearErrors('phone')}
fullWidth
placeholder={t.mobile_number()}
type="tel"
error={!!errors.phone?.message}
helperText={errors.phone?.message}
InputProps={{
disableUnderline: true,
startAdornment: (
<Typography
display="flex"
alignItems="center"
columnGap="4px"
style={{ cursor: 'pointer' }}
onClick={(event) => {
setAnchorEl(event.currentTarget)
setOpen(true)
}}>
<img src={countryIcon} style={{ width: 16, height: 12 }} />
<Box
component="span"
sx={{ minWidth: 32, textAlign: 'right', whiteSpace: 'nowrap' }}>
{countryCode}
</Box>
<Icons.ArrowDrop size={16} />
</Typography>
),
}}
/>
)}
/>
Expand Down Expand Up @@ -134,15 +101,6 @@ export const PhoneForm = memo(function PhoneForm() {
/>
)}
/>
<CountryCodePicker
open={open}
code={countryCode}
anchorEl={anchorEl}
onClose={(code) => {
if (code) setValue('countryCode', code)
setOpen(false)
}}
/>
</Box>
)
})
3 changes: 3 additions & 0 deletions packages/shared-base-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ export * from './bom/index.js'
export * from './data/index.js'
export * from './components/index.js'
export * from './hooks/index.js'

export { addShareBaseI18N, languages } from './locales/languages.js'

export { default as COUNTRIES } from './country-data.json'
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"bignumber.js": "9.1.1",
"d3": "^5.16.0",
"date-fns": "^2.30.0",
"fuse.js": "6.6.2",
"iframe-resizer-react": "^1.1.0",
"immer": "^10.0.2",
"json-stable-stringify": "^1.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { List, ListItemButton, ListItemIcon, ListItemText, Popover, TextField, Typography } from '@mui/material'
import { memo, useDeferredValue, useMemo, useState } from 'react'
import { useDashboardI18N } from '../../locales/i18n_generated.js'

import { Icons } from '@masknet/icons'
import { makeStyles } from '@masknet/theme'
import Fuse from 'fuse.js'

import REGIONS from '../../assets/region.json'
import { COUNTRY_ICON_URL } from '../../constants.js'
import { useSharedI18N } from '../../../index.js'
import { COUNTRIES } from '@masknet/shared-base-ui'
import { getCountryFlag } from '../../../utils/getCountryFlag.js'

const useStyles = makeStyles()((theme) => ({
paper: {
Expand Down Expand Up @@ -62,20 +62,20 @@ export interface CountryCodePickerProps {
}

export const CountryCodePicker = memo<CountryCodePickerProps>(({ open, anchorEl, onClose, code }) => {
const t = useDashboardI18N()
const t = useSharedI18N()
const { classes } = useStyles()
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)

const regions = useMemo(() => {
if (!deferredQuery) return REGIONS
const fuse = new Fuse(REGIONS, {
if (!deferredQuery) return COUNTRIES
const fuse = new Fuse(COUNTRIES, {
shouldSort: false,
isCaseSensitive: false,
threshold: 0.45,
minMatchCharLength: 1,
findAllMatches: true,
keys: ['name'],
keys: ['country_region', 'iso_code', 'dialing_code'],
})

const filtered = fuse.search(deferredQuery)
Expand Down Expand Up @@ -106,26 +106,27 @@ export const CountryCodePicker = memo<CountryCodePickerProps>(({ open, anchorEl,
/>
<List className={classes.list} data-hide-scrollbar>
{regions.map((data) => {
const selected = data.dial_code === code
const icon = `${COUNTRY_ICON_URL}${code.toLowerCase()}.svg`
const selected = data.dialing_code === code
const icon = getCountryFlag(data.iso_code)

return (
<ListItemButton
onClick={() => {
onClose(data.dial_code)
onClose(data.dialing_code)
}}
key={data.code}
key={data.iso_code}
className={classes.listItem}
autoFocus={selected}
selected={selected}>
<ListItemIcon className={classes.listItemIcon}>
<img src={icon} className={classes.icon} />
</ListItemIcon>
<ListItemText
primary={data.name}
primary={data.country_region}
className={classes.text}
classes={{ primary: classes.primaryText }}
/>
<Typography className={classes.primaryText}>{data.dial_code}</Typography>
<Typography className={classes.primaryText}>+{data.dialing_code}</Typography>
</ListItemButton>
)
})}
Expand Down
55 changes: 55 additions & 0 deletions packages/shared/src/UI/components/PhoneNumberField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useMemo, useState } from 'react'
import { Button, TextField, type FilledTextFieldProps, Typography, ClickAwayListener } from '@mui/material'
import { COUNTRIES } from '@masknet/shared-base-ui'
import { getCountryFlag, useSharedI18N } from '../../../index.js'
import { Icons } from '@masknet/icons'
import { CountryCodePicker } from '../CountryCodePicker/index.js'

export interface PhoneNumberFieldProps extends Omit<FilledTextFieldProps, 'variant'> {
code: string
onCodeChange: (code: string) => void
}

export function PhoneNumberField({ code, onCodeChange, ...rest }: PhoneNumberFieldProps) {
const t = useSharedI18N()
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)

const countryIcon = useMemo(() => {
if (!code) return
const country = COUNTRIES.find((x) => x.dialing_code === code)
if (!country) return
return getCountryFlag(country.iso_code)
}, [code])

return (
<>
<TextField
placeholder={t.mobile_number()}
type="tel"
{...rest}
InputProps={{
...rest.InputProps,
disableUnderline: true,
startAdornment: (
<Button variant="text" onClick={(event) => setAnchorEl(event.currentTarget)}>
<img src={countryIcon} style={{ width: 16, height: 12 }} />
<Typography component="span">+{code}</Typography>
<Icons.ArrowDrop size={16} />
</Button>
),
}}
/>
<ClickAwayListener onClickAway={() => setAnchorEl(null)}>
<CountryCodePicker
open={!!anchorEl}
anchorEl={anchorEl}
code={code}
onClose={(code) => {
if (code) onCodeChange(code)
setAnchorEl(null)
}}
/>
</ClickAwayListener>
</>
)
}
2 changes: 2 additions & 0 deletions packages/shared/src/UI/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,5 @@ export * from './WalletStatusBox/index.js'
export * from './SearchResultInspector/index.js'
export * from './ClickableChip/index.js'
export * from './CompositionDialog/index.js'
export * from './PhoneNumberField/index.js'
export * from './CountryCodePicker/index.js'
2 changes: 2 additions & 0 deletions packages/shared/src/locales/en-US.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"send": "Send",
"search_area": "Search Area",
"approve": "Approve",
"operation": "Operation",
"approve_mask": "Approve MASK?",
Expand Down Expand Up @@ -32,6 +33,7 @@
"select_collectibles": "Select Collectibles",
"no_collectible_found": "No collectible found.",
"manage_token_list": "Manage Token List",
"mobile_number": "Mobile Number",
"token_list_loading": "Loading",
"erc20_token_list_placeholder": "Name or Contract address e.g. USDC or 0x234...",
"erc20_token_list_loading": "Loading token lists...",
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/utils/getCountryFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const getCountryFlag = (code: string) => `http://purecatamphetamine.github.io/country-flag-icons/3x2/${code}.svg`
1 change: 1 addition & 0 deletions packages/shared/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './addressSorter.js'
export * from './resolveNextIDPlatform.js'
export * from './resolveValueToSearch.js'
export * from './identifierSelector.js'
export * from './getCountryFlag.js'
Loading