Skip to content

Commit

Permalink
Merge pull request #301 from CityOfZion/CU-86a69431g
Browse files Browse the repository at this point in the history
CU-86a69431g-NEON3 - Implement an icon to paste values into address f…
  • Loading branch information
thiagocbalducci authored Jan 21, 2025
2 parents c1e56e3 + 9a3a198 commit 7f4655b
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 17 deletions.
3 changes: 3 additions & 0 deletions src/renderer/src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cloneElement, forwardRef, MouseEvent, useImperativeHandle, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MdCancel, MdContentCopy, MdContentPasteGo, MdVisibility, MdVisibilityOff } from 'react-icons/md'
import { StyleHelper } from '@renderer/helpers/StyleHelper'
import { TestHelper } from '@renderer/helpers/TestHelper'
Expand Down Expand Up @@ -50,6 +51,7 @@ export const Input = forwardRef<HTMLInputElement, TInputProps>(
},
ref
) => {
const { t: tCommonGeneral } = useTranslation('common', { keyPrefix: 'general' })
const isTypePassword = type === 'password'
const internalRef = useRef<HTMLInputElement>(null)
const [hidden, setHidden] = useState(isTypePassword)
Expand Down Expand Up @@ -172,6 +174,7 @@ export const Input = forwardRef<HTMLInputElement, TInputProps>(

{pastable && (
<IconButton
aria-label={tCommonGeneral('pasteFromClipboard')}
icon={<MdContentPasteGo aria-hidden={true} />}
onClick={handlePaste}
colorSchema="neon"
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/src/components/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChangeEventHandler, forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { MdCancel, MdContentPasteGo } from 'react-icons/md'
import { StyleHelper } from '@renderer/helpers/StyleHelper'

Expand Down Expand Up @@ -30,6 +31,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TProps>(
},
ref
) => {
const { t: tCommonGeneral } = useTranslation('common', { keyPrefix: 'general' })
const internalRef = useRef<HTMLTextAreaElement>(null)

const handlePaste = async () => {
Expand Down Expand Up @@ -117,10 +119,13 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TProps>(

{pastable && (
<IconButton
icon={<MdContentPasteGo className="text-neon" />}
onClick={handlePaste}
aria-label={tCommonGeneral('pasteFromClipboard')}
type="button"
colorSchema="neon"
compacted
disabled={props.disabled}
icon={<MdContentPasteGo aria-hidden={true} className="text-neon" />}
onClick={handlePaste}
/>
)}

Expand Down
24 changes: 24 additions & 0 deletions src/renderer/src/hooks/usePressOnce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useRef, useState } from 'react'

export const usePressOnce = () => {
const [isPressing, setIsPressing] = useState(false)
const isPressingRef = useRef(false)

const handlePressOnce = (callback: (() => void) | (() => Promise<void>)) => async () => {
if (isPressing || isPressingRef.current) return

setIsPressing(true)
isPressingRef.current = true

try {
await callback()
} catch (error) {
console.error(error)
} finally {
setIsPressing(false)
isPressingRef.current = false
}
}

return { isPressing, isPressingRef, handlePressOnce }
}
4 changes: 3 additions & 1 deletion src/renderer/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"remove": "Remove",
"copy": "Copy to clipboard",
"swap": "Swap",
"logo": "Neon Wallet's logo"
"logo": "Neon Wallet's logo",
"pasteFromClipboard": "Paste from clipboard",
"pasteFromClipboardError": "Failed to paste from clipboard"
},
"walletConnect": {
"name": "Neon Wallet",
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/modals/AddAddress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const AddAddressModal = () => {
onChange={handleChange}
clearable
compacted
pastable
loading={isValidatingAddressOrDomainAddress}
disabled={!actionData.blockchain}
error={isValidAddressOrDomainAddress === false}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/modals/AddCustomNetwork/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const AddCustomNetwork = () => {
compacted
placeholder={t('urlPlaceholder')}
label={t('urlLabel')}
pastable
value={actionData.url}
onChange={setDataFromEventWrapper('url')}
loading={actionData.validating}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/modals/DappConnection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const DappConnectionModal = () => {
<Input
placeholder={t('inputPlaceholder')}
clearable
pastable
value={actionData.url}
onChange={handleChange}
errorMessage={actionState.errors.url}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/modals/Import/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const ImportModal = () => {
onChange={handleChange}
compacted
clearable
pastable
multiline={actionData.inputType === 'mnemonic'}
/>

Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/pages/LoginKey/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const LoginKeyPage = () => {
value={actionData.text}
onChange={handleChange}
clearable
pastable
multiline={actionData.inputType === 'mnemonic'}
{...TestHelper.buildTestObject('login-key-textarea')}
/>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/routes/pages/Send/SendRecipient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export const SendRecipient = ({
className="w-full"
placeholder={t('addressPlaceholder')}
clearable={false}
pastable
buttons={
<IconButton
icon={<TbUsers aria-hidden={true} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const SettingsEncryptKeyPage = (): JSX.Element => {
<div className="flex flex-col gap-4">
<SettingsEncryptInputStep
step={1}
pastable
description={t('encryptKey.titleInput1')}
placeholder={t('encryptKey.inputPrivateKeyPlaceholder')}
onChange={handlePrivateKeyChange}
Expand Down
54 changes: 40 additions & 14 deletions src/renderer/src/routes/pages/Swap/SwapPageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChangeEvent, Fragment, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { MdInfoOutline, MdRestartAlt } from 'react-icons/md'
import { MdContentPasteGo, MdInfoOutline, MdRestartAlt } from 'react-icons/md'
import { TbDiamond, TbHelp, TbReplace, TbStepInto, TbStepOut, TbUsers, TbWallet } from 'react-icons/tb'
import { VscCircleFilled } from 'react-icons/vsc'
import {
Expand Down Expand Up @@ -35,6 +35,7 @@ import { useCurrentLoginSessionSelector } from '@renderer/hooks/useAuthSelector'
import { useBalance } from '@renderer/hooks/useBalances'
import { useHasContactsByBlockchain } from '@renderer/hooks/useContactSelector'
import { useModalNavigate } from '@renderer/hooks/useModalRouter'
import { usePressOnce } from '@renderer/hooks/usePressOnce'
import { useAppDispatch } from '@renderer/hooks/useRedux'
import { useSelectedNetworkByBlockchainSelector } from '@renderer/hooks/useSettingsSelector'
import { bsAggregator, doesBlockchainSupported } from '@renderer/libs/blockchainService'
Expand Down Expand Up @@ -63,11 +64,13 @@ type TProps = {

export const SwapPageContent = ({ account }: TProps) => {
const { t } = useTranslation('pages', { keyPrefix: 'swap' })
const { t: tCommonGeneral } = useTranslation('common', { keyPrefix: 'general' })
const { modalNavigateWrapper, modalNavigate } = useModalNavigate()
const { networkByBlockchain } = useSelectedNetworkByBlockchainSelector()
const { currentLoginSessionRef } = useCurrentLoginSessionSelector()
const { accounts } = useAccountsSelector()
const dispatch = useAppDispatch()
const pressOncePasteAddressToReceive = usePressOnce()

const swapChainsByServiceName = useMemo(() => {
const chainsByServiceName: Partial<Record<TBlockchainServiceKey, string[]>> = {}
Expand Down Expand Up @@ -277,6 +280,17 @@ export const SwapPageContent = ({ account }: TProps) => {
swapServiceRef.current.setExtraIdToReceive(event.target.value)
}

const handlePasteAddressToReceive = async () => {
try {
const text = await navigator.clipboard.readText()

await swapServiceRef.current!.setAddressToReceive(text)
} catch (error) {
ToastHelper.error({ message: tCommonGeneral('pasteFromClipboardError') })
console.error(error)
}
}

const handleChangeAmountToUse = (event: ChangeEvent<HTMLInputElement>) => {
const amount = NumberHelper.formatString(event.target.value, actionData.selectedTokenToUse.value?.decimals, 24)

Expand Down Expand Up @@ -649,19 +663,31 @@ export const SwapPageContent = ({ account }: TProps) => {
placeholder={t('form.addressToReceivePlaceholder')}
clearable={false}
buttons={
<IconButton
icon={<TbUsers aria-hidden={true} className="w-5 h-5 min-w-5 min-h-5" />}
colorSchema="neon"
type="button"
onClick={modalNavigateWrapper('select-contact', {
state: {
onSelectContact: handleSelectContactToReceive,
blockchain: tokenToReceiveBlockchain,
},
})}
compacted
disabled={isContactsAndAccountsSelectionDisabled || !hasContactsByBlockchain}
/>
<Fragment>
<IconButton
aria-label={tCommonGeneral('pasteFromClipboard')}
colorSchema="neon"
type="button"
compacted
disabled={isRecipientDisabled || pressOncePasteAddressToReceive.isPressing}
icon={<MdContentPasteGo aria-hidden={true} className="w-5 h-5 min-w-5 min-h-5" />}
onClick={pressOncePasteAddressToReceive.handlePressOnce(handlePasteAddressToReceive)}
/>

<IconButton
icon={<TbUsers aria-hidden={true} className="w-5 h-5 min-w-5 min-h-5" />}
colorSchema="neon"
type="button"
onClick={modalNavigateWrapper('select-contact', {
state: {
onSelectContact: handleSelectContactToReceive,
blockchain: tokenToReceiveBlockchain,
},
})}
compacted
disabled={isContactsAndAccountsSelectionDisabled || !hasContactsByBlockchain}
/>
</Fragment>
}
loading={actionData.selectedAddressToReceive.loading}
errorMessage={
Expand Down
2 changes: 2 additions & 0 deletions src/shared/@types/i18next-resources.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface Resources {
copy: 'Copy to clipboard'
swap: 'Swap'
logo: "Neon Wallet's logo"
pasteFromClipboard: 'Paste from clipboard'
pasteFromClipboardError: 'Failed to paste from clipboard'
}
walletConnect: {
name: 'Neon Wallet'
Expand Down

0 comments on commit 7f4655b

Please sign in to comment.