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

[ISSUE-205] Refactor AddressInput and GeneralRecipientInput #465

Merged
merged 6 commits into from
Oct 23, 2024
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
76 changes: 26 additions & 50 deletions src/components/Claim/Link/Initial.view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client'
import GeneralRecipientInput from '@/components/Global/GeneralRecipientInput'
import GeneralRecipientInput, { GeneralRecipientUpdate } from '@/components/Global/GeneralRecipientInput'
import * as _consts from '../Claim.consts'
import { useContext, useEffect, useState } from 'react'
import Icon from '@/components/Global/Icon'
Expand All @@ -9,7 +9,6 @@ import useClaimLink from '../useClaimLink'
import * as context from '@/context'
import Loading from '@/components/Global/Loading'
import * as consts from '@/constants'
import * as interfaces from '@/interfaces'
import * as utils from '@/utils'
import MoreInfo from '@/components/Global/MoreInfo'
import TokenSelectorXChain from '@/components/Global/TokenSelector/TokenSelectorXChain'
Expand Down Expand Up @@ -126,17 +125,6 @@ export const InitialClaimLinkView = ({
}
}

const _estimatePoints = async () => {
const USDValue = Number(claimLinkData.tokenAmount) * (tokenPrice ?? 0)
const estimatedPoints = await estimatePoints({
address: recipient.address ?? address ?? '',
chainId: claimLinkData.chainId,
amountUSD: USDValue,
actionType: ActionType.CLAIM,
})
setEstimatedPoints(estimatedPoints)
}

const handleIbanRecipient = async () => {
try {
setErrorState({
Expand Down Expand Up @@ -255,10 +243,24 @@ export const InitialClaimLinkView = ({
}

useEffect(() => {
if (recipient) {
_estimatePoints()
let isMounted = true
if (recipient?.address && isValidRecipient) {
const amountUSD = Number(claimLinkData.tokenAmount) * (tokenPrice ?? 0)
estimatePoints({
address: recipient.address,
chainId: claimLinkData.chainId,
amountUSD,
actionType: ActionType.CLAIM,
}).then((points) => {
if (isMounted) {
setEstimatedPoints(points)
}
})
}
return () => {
isMounted = false
}
}, [recipient])
}, [recipient.address, isValidRecipient, claimLinkData.tokenAmount, claimLinkData.chainId, tokenPrice])

useEffect(() => {
if (recipient.address) return
Expand Down Expand Up @@ -463,42 +465,16 @@ export const InitialClaimLinkView = ({
<GeneralRecipientInput
className="px-1"
placeholder="wallet address / ENS / IBAN / US account number"
value={recipient.name ? recipient.name : (recipient.address ?? '')}
onSubmit={(name: string, address: string) => {
setRecipient({ name, address })
setInputChanging(false)
}}
_setIsValidRecipient={({ isValid, error }: { isValid: boolean; error?: string }) => {
setIsValidRecipient(isValid)
if (error) {
setErrorState({
showError: true,
errorMessage: error,
})
} else {
setErrorState({
showError: false,
errorMessage: '',
})
}
setInputChanging(false)
}}
setIsValueChanging={() => {
setInputChanging(true)
}}
setRecipientType={(type: interfaces.RecipientType) => {
setRecipientType(type)
}}
onDeleteClick={() => {
setRecipientType('address')
setRecipient({
name: undefined,
address: '',
})
recipient={recipient}
onUpdate={(update: GeneralRecipientUpdate) => {
setRecipient(update.recipient)
setRecipientType(update.type)
setIsValidRecipient(update.isValid)
setErrorState({
showError: false,
errorMessage: '',
showError: !update.isValid,
errorMessage: update.errorMessage,
})
setInputChanging(update.isChanging)
}}
/>
{recipient && isValidRecipient && recipientType !== 'iban' && recipientType !== 'us' && (
Expand Down
151 changes: 21 additions & 130 deletions src/components/Global/AddressInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,150 +1,41 @@
'use client'
import { useEffect, useState } from 'react'
import Icon from '@/components/Global/Icon'
import * as utils from '@/utils'
import { ethers } from 'ethers'
import { isAddress } from 'viem'

import { resolveFromEnsName } from '@/utils'
import ValidatedInput, { InputUpdate } from '@/components/Global/ValidatedInput'

type AddressInputProps = {
className?: string
placeholder: string
value: string
onSubmit: any
_setIsValidRecipient: any
setIsValueChanging?: any
onDeleteClick: any
onUpdate: (update: InputUpdate) => void
className?: string
}

const AddressInput = ({
placeholder,
value,
onSubmit,
_setIsValidRecipient,
setIsValueChanging,
onDeleteClick,
}: AddressInputProps) => {
const [userInput, setUserInput] = useState<string>(value)
const [recipient, setRecipient] = useState<string>(value)
const [debouncedRecipient, setDebouncedRecipient] = useState<string>('')
const [isValidRecipient, setIsValidRecipient] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [type, setType] = useState<'address' | 'ens'>('address')

async function checkAddress(recipient: string) {
const AddressInput = ({ placeholder = 'Enter a valid address', value, onUpdate, className }: AddressInputProps) => {
async function checkAddress(recipient: string): Promise<boolean> {
try {
if (recipient.toLowerCase().endsWith('.eth')) {
const resolvedAddress = await utils.resolveFromEnsName(recipient.toLowerCase())
if (resolvedAddress) {
setRecipient(recipient)
setIsValidRecipient(true)
setType('ens')
onSubmit(recipient)
} else {
setIsValidRecipient(false)
}
} else if (ethers.utils.isAddress(recipient)) {
setRecipient(recipient)
setIsValidRecipient(true)
setType('address')
onSubmit(recipient)
const resolvedAddress = await resolveFromEnsName(recipient.toLowerCase())
return !!resolvedAddress
} else {
setIsValidRecipient(false)
return isAddress(recipient, { strict: false })
}
} catch (error) {
console.error('Error while validating recipient input field:', error)
setIsValidRecipient(false)
} finally {
setIsLoading(false)
return false
jjramirezn marked this conversation as resolved.
Show resolved Hide resolved
}
}

useEffect(() => {
_setIsValidRecipient(isValidRecipient)
}, [isValidRecipient])

useEffect(() => {
if (recipient && isValidRecipient) {
onSubmit(recipient)
}
}, [recipient])

useEffect(() => {
setIsLoading(true)
const handler = setTimeout(() => {
setDebouncedRecipient(userInput)
}, 750)
return () => {
clearTimeout(handler)
}
}, [userInput])

useEffect(() => {
if (debouncedRecipient) {
checkAddress(debouncedRecipient)
}
}, [debouncedRecipient])

useEffect(() => {
setUserInput(value)
}, [value])

return (
<div
className={`relative w-full max-w-96 border border-n-1 dark:border-white${
userInput && !isLoading && isValidRecipient
? ' border border-n-1 dark:border-white'
: userInput && !isLoading && !isValidRecipient
? ' border-n-1 border-red dark:border-red'
: ''
}`}
>
<div className="absolute left-1 top-1/2 flex h-6 w-6 -translate-y-1/2 items-center justify-center bg-white text-h8 font-medium">
To:
</div>
<input
className={`transition-color h-12 w-full rounded-none bg-transparent
bg-white px-4 pl-8 text-h8 font-medium outline-none placeholder:text-sm focus:border-purple-1 dark:border-white dark:bg-n-1 dark:text-white dark:placeholder:text-white/75 dark:focus:border-purple-1`}
type="text"
placeholder={placeholder}
value={userInput}
onSubmit={(e) => {
e.preventDefault()
}}
onChange={(e) => {
setIsValueChanging(true)
if (e.target.value) {
setUserInput(e.target.value)
} else {
setIsValidRecipient(false)
setUserInput('')
}
}}
spellCheck="false"
/>
{userInput?.length > 0 ? (
isLoading ? (
<div className="absolute right-2 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center bg-white">
<div
className="h-4 w-4 animate-spin rounded-full border-2 border-solid border-current border-r-transparent motion-reduce:animate-none"
role="status"
/>
</div>
) : (
userInput && (
<button
onClick={(e) => {
e.preventDefault()
setUserInput('')
onDeleteClick()
setIsValidRecipient(false)
}}
className="absolute right-2 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center bg-white"
>
<Icon className="h-6 w-6 dark:fill-white" name="close" />
</button>
)
)
) : null}
</div>
<ValidatedInput
placeholder={placeholder}
label="To"
value={value}
debounceTime={750}
validate={checkAddress}
onUpdate={onUpdate}
className={className}
/>
)
}

Expand Down
Loading