Skip to content

Commit

Permalink
Merge pull request #106 from CityOfZion/CU-86dty6d93
Browse files Browse the repository at this point in the history
CU-86dty6d93 - NEON3 - Contact Screen - Name Service delayed resolution
  • Loading branch information
yumiuehara authored Aug 1, 2024
2 parents 87bb029 + 7d323d8 commit 7bc90a4
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 49 deletions.
8 changes: 7 additions & 1 deletion src/renderer/src/components/Contact/AddressCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { useNameService } from '@renderer/hooks/useNameService'
import { TContactAddress } from '@shared/@types/store'

import { IconButton } from '../IconButton'
import { Loader } from '../Loader'

export const AddressCell = ({ address, blockchain }: TContactAddress) => {
const { validatedAddress, isNameService, validateAddressOrNS } = useNameService()
const { validatedAddress, isNameService, validateAddressOrNS, isValidatingAddressOrDomainAddress } = useNameService(0)

useEffect(() => {
validateAddressOrNS(address, blockchain)
Expand All @@ -17,6 +18,11 @@ export const AddressCell = ({ address, blockchain }: TContactAddress) => {
<div className="flex flex-col">
<div className="flex items-center gap-x-1">
{address}

{isValidatingAddressOrDomainAddress && (
<Loader containerClassName="justify-start w-min ml-1" className="w-4 h-4" />
)}

<IconButton
icon={<MdOutlineContentCopy className="text-neon" />}
size="sm"
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/src/components/Contact/ContactAddressTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ export const ContactAddressTable = ({ contactAddresses }: TProps) => {
header: t('components:contactAddressTable.blockchain'),
}),
columnHelper.accessor('address', {
cell: info => <AddressCell address={info.row.original.address} blockchain={info.row.original.blockchain} />,
cell: info => (
<AddressCell
key={info.row.original.address}
address={info.row.original.address}
blockchain={info.row.original.blockchain}
/>
),
enableSorting: false,
header: t('components:contactAddressTable.address'),
}),
Expand Down
36 changes: 29 additions & 7 deletions src/renderer/src/hooks/useNameService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,55 @@ import { useCallback, useState } from 'react'
import { hasNameService } from '@cityofzion/blockchain-service'
import { bsAggregator } from '@renderer/libs/blockchainService'
import { TBlockchainServiceKey } from '@shared/@types/blockchain'
import { Query, QueryClient, useQueryClient } from '@tanstack/react-query'
import { debounce } from 'lodash'

export const useNameService = () => {
function buildQueryKey(blockchain: TBlockchainServiceKey, domain: string) {
return ['nameService', blockchain, domain]
}

const STALE_TIME = 1000 * 60 // 1 minute

export const useNameService = (debounceTime = 1000) => {
const [isValidatingAddressOrDomainAddress, setIsValidatingAddressOrDomainAddress] = useState(false)
const [validatedAddress, setValidatedAddress] = useState<string>()
const [isNameService, setIsNameService] = useState(false)
const [isValidAddressOrDomainAddress, setIsValidAddressOrDomainAddress] = useState<boolean>()
const queryClient = useQueryClient()

// eslint-disable-next-line react-hooks/exhaustive-deps
const validateAddressOrNSDebounce = useCallback(
debounce(async (domainOrAddress: string, blockchain: TBlockchainServiceKey) => {
debounce(async (domainOrAddress: string, blockchain: TBlockchainServiceKey, queryClient: QueryClient) => {
let isValid = false
let address: string | undefined
let isNS = false

setIsValidatingAddressOrDomainAddress(true)
const queryCache = queryClient.getQueryCache()
const queryKey = buildQueryKey(blockchain, domainOrAddress)
const defaultedOptions = queryClient.defaultQueryOptions({ queryKey, staleTime: STALE_TIME })
const query = queryCache.get(defaultedOptions.queryHash) as Query<string> | undefined

const shouldFetch = !query || query.isStaleByTime(defaultedOptions.staleTime)
if (shouldFetch) {
setIsValidatingAddressOrDomainAddress(true)
} else {
address = query?.state.data
isValid = true
isNS = true
}

try {
const service = bsAggregator.blockchainServicesByName[blockchain]

if (service.validateAddress(domainOrAddress)) {
address = domainOrAddress
isValid = true
} else if (hasNameService(service) && service.validateNameServiceDomainFormat(domainOrAddress)) {
} else if (hasNameService(service) && service.validateNameServiceDomainFormat(domainOrAddress) && shouldFetch) {
address = await service.resolveNameServiceDomain(domainOrAddress)
isValid = true
isNS = true

queryCache.build(queryClient, defaultedOptions).setData(address, { manual: true })
}
} catch {
isValid = false
Expand All @@ -39,7 +61,7 @@ export const useNameService = () => {
setValidatedAddress(address)
setIsValidAddressOrDomainAddress(isValid)
setIsNameService(isNS)
}, 1000),
}, debounceTime),
[]
)

Expand All @@ -54,9 +76,9 @@ export const useNameService = () => {
return
}

validateAddressOrNSDebounce(domainOrAddress, blockchain)
validateAddressOrNSDebounce(domainOrAddress, blockchain, queryClient)
},
[validateAddressOrNSDebounce]
[validateAddressOrNSDebounce, queryClient]
)

return {
Expand Down
64 changes: 24 additions & 40 deletions src/renderer/src/routes/modals/AddAddress/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ChangeEvent, Fragment, useCallback, useState } from 'react'
import { ChangeEvent, Fragment } from 'react'
import { useTranslation } from 'react-i18next'
import { hasNameService } from '@cityofzion/blockchain-service'
import { Banner } from '@renderer/components/Banner'
import { BlockchainSelect } from '@renderer/components/BlockchainSelect'
import { Button } from '@renderer/components/Button'
Expand All @@ -9,11 +8,10 @@ import { Separator } from '@renderer/components/Separator'
import { StringHelper } from '@renderer/helpers/StringHelper'
import { useActions } from '@renderer/hooks/useActions'
import { useModalNavigate, useModalState } from '@renderer/hooks/useModalRouter'
import { useNameService } from '@renderer/hooks/useNameService'
import { SideModalLayout } from '@renderer/layouts/SideModal'
import { bsAggregator } from '@renderer/libs/blockchainService'
import { TBlockchainServiceKey } from '@shared/@types/blockchain'
import { TContactAddress } from '@shared/@types/store'
import { debounce } from 'lodash'

type TLocationState = {
contactName: string
Expand All @@ -23,16 +21,21 @@ type TLocationState = {

type TActionData = {
address: string
nnsAddress?: string
isAddressValid?: boolean
blockchain?: TBlockchainServiceKey
}

export const AddAddressModal = () => {
const { t } = useTranslation('modals', { keyPrefix: 'addAddress' })
const { contactName, address, handleAddAddress } = useModalState<TLocationState>()
const { modalNavigate } = useModalNavigate()
const [validating, setValidating] = useState(false)

const {
isNameService,
isValidAddressOrDomainAddress,
isValidatingAddressOrDomainAddress,
validateAddressOrNS,
validatedAddress,
} = useNameService()

const { actionData, setData, handleAct } = useActions<TActionData>({
address: address?.address || '',
Expand All @@ -42,34 +45,9 @@ export const AddAddressModal = () => {
setData({
address: event.target.value,
})
validateAddressOrNSS(event.target.value, actionData.blockchain)
validateAddressOrNS(event.target.value, actionData.blockchain)
}

// eslint-disable-next-line react-hooks/exhaustive-deps
const validateAddressOrNSS = useCallback(
debounce(async (address: string, blockchain?: TBlockchainServiceKey) => {
if (!blockchain || !address) return

setValidating(true)
let isValid = false
let nnsAddress: string | undefined
try {
const service = bsAggregator.blockchainServicesByName[blockchain]
isValid = service.validateAddress(address)
if (!isValid && hasNameService(service) && service.validateNameServiceDomainFormat(address)) {
nnsAddress = await service.resolveNameServiceDomain(address)
isValid = true
}
} catch {
/* empty */
} finally {
setValidating(false)
setData({ nnsAddress, isAddressValid: isValid })
}
}, 1000),
[setData]
)

const handleSelectBlockchain = (blockchain: TBlockchainServiceKey) => {
setData({ blockchain })
}
Expand Down Expand Up @@ -101,24 +79,30 @@ export const AddAddressModal = () => {
onChange={handleChange}
clearable
compacted
loading={validating}
loading={isValidatingAddressOrDomainAddress}
disabled={!actionData.blockchain}
error={actionData.isAddressValid === false}
error={isValidAddressOrDomainAddress === false}
/>
{actionData.nnsAddress && <p className="text-gray-300">{actionData.nnsAddress}</p>}
{isNameService && <p className="text-gray-300">{validatedAddress}</p>}

{actionData.isAddressValid !== undefined && (
{isValidAddressOrDomainAddress !== undefined && (
<Fragment>
{!actionData.isAddressValid ? (
{!isValidAddressOrDomainAddress ? (
<Banner message={t('invalidAddress')} type="error" />
) : (
<Banner message={actionData.nnsAddress ? t('nnsComplete') : t('addressComplete')} type="success" />
<Banner message={isNameService ? t('nnsComplete') : t('addressComplete')} type="success" />
)}
</Fragment>
)}
</div>

<Button label={t('saveAddress')} className="w-full" type="submit" flat disabled={!actionData.isAddressValid} />
<Button
label={t('saveAddress')}
className="w-full"
type="submit"
flat
disabled={!isValidAddressOrDomainAddress}
/>
</form>
</SideModalLayout>
)
Expand Down

0 comments on commit 7bc90a4

Please sign in to comment.