From d80d1828925543abab001425cfa87cba6cfa4d0c Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 13 Apr 2023 11:10:03 +0200 Subject: [PATCH 1/9] add & edit contacts --- src/components/list/ContactRow.tsx | 5 +- .../tabs/contacts/screens/ContactsAdd.tsx | 172 ++++++++++++++---- .../tabs/contacts/screens/ContactsDetails.tsx | 9 + .../tabs/contacts/screens/ContactsRoot.tsx | 6 +- src/store/contact/contact.types.ts | 1 + src/store/moralis/moralis.effects.ts | 37 +++- 6 files changed, 188 insertions(+), 42 deletions(-) diff --git a/src/components/list/ContactRow.tsx b/src/components/list/ContactRow.tsx index 40cc5c66e..5791bdeb5 100644 --- a/src/components/list/ContactRow.tsx +++ b/src/components/list/ContactRow.tsx @@ -39,6 +39,7 @@ export interface ContactRowProps { tag?: number; // backward compatibility destinationTag?: number; email?: string; + domain?: string; } interface Props { @@ -49,7 +50,7 @@ interface Props { const ContactRow = ({contact, onPress}: Props) => { const theme = useTheme(); const underlayColor = theme.dark ? '#121212' : '#fbfbff'; - const {coin: _coin, name, email, address, chain} = contact; + const {coin: _coin, name, email, address, chain, domain} = contact; const coin = getCurrencyAbbreviation(_coin, chain); return ( @@ -60,7 +61,7 @@ const ContactRow = ({contact, onPress}: Props) => {
{name}
- {email ? email : address} + {domain ? domain : email ? email : address}
diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index e5a3685ca..566f97dc7 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -69,6 +69,11 @@ import { import Checkbox from '../../../../components/checkbox/Checkbox'; import {IsERCToken} from '../../../../store/wallet/utils/currency'; import {Analytics} from '../../../../store/analytics/analytics.effects'; +import { + getAddressByENSDomain, + getAddressByUnstoppableDomain, + getENSDomainByAddress, +} from '../../../../store/moralis/moralis.effects'; const InputContainer = styled.View<{hideInput?: boolean}>` display: ${({hideInput}) => (!hideInput ? 'flex' : 'none')}; @@ -150,6 +155,7 @@ const schema = yup.object().shape({ email: yup.string().email().trim(), destinationTag: yup.number(), address: yup.string().required(), + domain: yup.string(), }); const SearchImageContainer = styled.View` @@ -194,6 +200,7 @@ const ContactsAdd = ({ const dispatch = useAppDispatch(); const [validAddress, setValidAddress] = useState(false); + const [validDomain, setValidDomain] = useState(false); const [xrpValidAddress, setXrpValidAddress] = useState(false); const [evmValidAddress, setEvmValidAddress] = useState(false); const [searchInput, setSearchInput] = useState(''); @@ -202,6 +209,7 @@ const ContactsAdd = ({ const [coinValue, setCoinValue] = useState(''); const [networkValue, setNetworkValue] = useState(''); const [chainValue, setChainValue] = useState(''); + const [domainValue, setDomainValue] = useState(''); const [tokenModalVisible, setTokenModalVisible] = useState(false); const [currencyModalVisible, setCurrencyModalVisible] = useState(false); @@ -299,12 +307,15 @@ const ContactsAdd = ({ coin: string, network: string, chain: string, + domain: string, ) => { setValidAddress(true); setAddressValue(address); setCoinValue(coin); setNetworkValue(network); setChainValue(chain); + setValidDomain(true); + setDomainValue(domain); _setSelectedCurrency(coin); @@ -320,13 +331,19 @@ const ContactsAdd = ({ return; } }; - - const processAddress = ( - address?: string, - coin?: string, - network?: string, - chain?: string, - ) => { + const processAddressOrDomain = ({ + address, + coin, + network, + chain, + domain, + }: { + address?: string; + coin?: string; + network?: string; + chain?: string; + domain?: string; + }) => { if (address) { const coinAndNetwork = GetCoinAndNetwork(address, undefined, chain); if (coinAndNetwork) { @@ -341,6 +358,7 @@ const ContactsAdd = ({ coin || coinAndNetwork.coin, network || coinAndNetwork.network, chain || coinAndNetwork.coin, + domain || '', ); } else { // try testnet @@ -355,29 +373,79 @@ const ContactsAdd = ({ coin || coinAndNetwork.coin, network || 'testnet', chain || coinAndNetwork.coin, + domain || '', ); } } } else { - setCoinValue(''); - setNetworkValue(''); - setAddressValue(''); - setValidAddress(false); - setEvmValidAddress(false); - setXrpValidAddress(false); + processDomain({domain: address}); } + } else { + resetValues(); } }; + const processDomain = useMemo( + () => + debounce(async ({domain}: {domain: string}) => { + try { + if (!domain) { + return; + } + + const addressByENS = await dispatch(getAddressByENSDomain({domain})); + const addressByUnstoppableDomain = await dispatch( + getAddressByUnstoppableDomain({domain}), + ); + + if (addressByENS || addressByUnstoppableDomain) { + setValidDomain(true); + processAddressOrDomain({ + address: addressByENS || addressByUnstoppableDomain, + domain, + }); + } else { + resetValues(); + } + } catch (e) { + resetValues(); + } + }, 300), + [], + ); + + const resetValues = () => { + setCoinValue(''); + setNetworkValue(''); + setAddressValue(''); + setValidAddress(false); + setEvmValidAddress(false); + setXrpValidAddress(false); + setValidDomain(false); + }; + const onSubmit = handleSubmit((contact: ContactRowProps) => { if (!validAddress) { setError('address', { type: 'manual', - message: t('Invalid address'), + message: t('Invalid address or domain'), + }); + return; + } + + if (!validDomain && domainValue) { + setError('domain', { + type: 'manual', + message: t('Invalid domain'), }); return; } + if (addressValue && domainValue) { + contact.address = addressValue; + contact.domain = domainValue; + } + if (coinValue && chainValue && networkValue) { contact.coin = coinValue; contact.chain = chainValue; @@ -516,27 +584,47 @@ const ContactsAdd = ({ params: { onScanComplete: address => { setValue('address', address, {shouldDirty: true}); - processAddress(address); + processAddressOrDomain({address}); }, }, }); }; + const fetchENSDomainByAddress = async () => { + try { + const domain = await dispatch( + getENSDomainByAddress({address: addressValue}), + ); + if (domain) { + setValue('domain', domain); + setDomainValue(domain); + setValidDomain(true); + } + } catch (err) { + console.error(err); + } + }; + useEffect(() => { if (contact) { - processAddress( - contact.address, - contact.coin, - contact.network, - contact.chain, - ); + processAddressOrDomain({ + address: contact.address, + coin: contact.coin, + network: contact.network, + chain: contact.chain, + domain: contact.domain, + }); setValue('address', contact.address!, {shouldDirty: true}); setValue('name', contact.name || ''); setValue('email', contact.email); setValue('chain', contact.chain!); setValue('destinationTag', contact.tag || contact.destinationTag); + setValue('domain', contact.domain); + if (context === 'edit' && evmValidAddress && !domainValue) { + fetchENSDomainByAddress(); + } } - }, [contact]); + }, [contact, evmValidAddress]); return ( @@ -581,12 +669,12 @@ const ContactsAdd = ({ control={control} render={({field: {onChange, onBlur, value}}) => ( { onChange(newValue); - processAddress(newValue); + processAddressOrDomain({address: newValue}); }} error={errors.address?.message} value={value} @@ -606,16 +694,30 @@ const ContactsAdd = ({ )} ) : ( - - ( - - )} - name="address" - defaultValue="" - /> - + <> + + ( + + )} + name="address" + defaultValue="" + /> + + {evmValidAddress && domainValue ? ( + + ( + + )} + name="domain" + defaultValue="" + /> + + ) : null} + )} {!contact && evmValidAddress ? ( diff --git a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx index dbd7069b8..1b31ebf60 100644 --- a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx @@ -323,6 +323,15 @@ const ContactsDetails = ({ + {contact.domain ? ( + <> +
+ + {t('Domain')} + {contact.domain} + + + ) : null} {contact.network !== 'livenet' ? ( <>
diff --git a/src/navigation/tabs/contacts/screens/ContactsRoot.tsx b/src/navigation/tabs/contacts/screens/ContactsRoot.tsx index e81ca1f35..8025338f0 100644 --- a/src/navigation/tabs/contacts/screens/ContactsRoot.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsRoot.tsx @@ -158,8 +158,10 @@ const ContactsRoot: React.FC = () => { const updateSearchResults = debounce((text: string) => { setSearchVal(text); - const results = contactList.filter(contact => - contact.name.toLowerCase().includes(text.toLocaleLowerCase()), + const results = contactList.filter( + contact => + contact.name.toLowerCase().includes(text.toLocaleLowerCase()) || + contact.domain?.toLowerCase().includes(text.toLowerCase()), ); setSearchResults(results); }, 300); diff --git a/src/store/contact/contact.types.ts b/src/store/contact/contact.types.ts index a98e57176..82a153e8f 100644 --- a/src/store/contact/contact.types.ts +++ b/src/store/contact/contact.types.ts @@ -24,6 +24,7 @@ interface DeleteContact { coin: string; network: string; chain?: string; + domain?: string; } interface MigrateContacts { diff --git a/src/store/moralis/moralis.effects.ts b/src/store/moralis/moralis.effects.ts index 5dabf6cbd..12f8b6f66 100644 --- a/src/store/moralis/moralis.effects.ts +++ b/src/store/moralis/moralis.effects.ts @@ -507,7 +507,7 @@ export const getAddressByENSDomain = ({domain}: {domain: string}): Effect> => async dispatch => { try { - const response = await Moralis.EvmApi.resolve.resolveDomain({ + const response = await Moralis.EvmApi.resolve.resolveENSDomain({ domain, }); @@ -516,7 +516,7 @@ export const getAddressByENSDomain = '[moralis/getAddressByENSDomain]: get address by ENS domain successfully', ), ); - return response?.toJSON(); + return response?.raw.address; } catch (e) { let errorStr; if (e instanceof Error) { @@ -533,6 +533,36 @@ export const getAddressByENSDomain = } }; +export const getAddressByUnstoppableDomain = + ({domain}: {domain: string}): Effect> => + async dispatch => { + try { + const response = await Moralis.EvmApi.resolve.resolveDomain({ + domain, + }); + + dispatch( + LogActions.info( + '[moralis/getAddressByUnstoppableDomain]: get address by Unstoppable domain successfully', + ), + ); + return response?.raw.address; + } catch (e) { + let errorStr; + if (e instanceof Error) { + errorStr = e.message; + } else { + errorStr = JSON.stringify(e); + } + dispatch( + LogActions.error( + `[moralis/getAddressByUnstoppableDomain]: an error occurred while getting address by Unstoppable domain: ${errorStr}`, + ), + ); + throw e; + } + }; + export const getENSDomainByAddress = ({address}: {address: string}): Effect> => async dispatch => { @@ -546,7 +576,8 @@ export const getENSDomainByAddress = '[moralis/getENSDomainByAddress]: get ENS domain by address successfully', ), ); - return response?.toJSON(); + + return response?.raw.name; } catch (e) { let errorStr; if (e instanceof Error) { From 692b0e9d75a2eb53adf5909d355a2a95b62991e0 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 14 Apr 2023 15:43:30 +0200 Subject: [PATCH 2/9] wallet details, send to, contact add and other fixes --- src/components/avatar/ENSDomainIcon.tsx | 35 +++ src/components/avatar/ProfileIcon.tsx | 4 +- .../avatar/UnstoppableDomainIcon.tsx | 27 +++ src/components/home-card/HomeCard.tsx | 11 +- .../copy-to-clipboard/CopyToClipboardIcon.tsx | 25 ++ src/components/icons/share/Share.tsx | 45 +++- src/components/list/ContactRow.tsx | 21 +- .../modal/transact-menu/TransactMenuIcons.tsx | 18 +- .../tabs/contacts/components/ContactIcon.tsx | 25 +- .../tabs/contacts/screens/ContactsAdd.tsx | 100 ++++++-- .../tabs/contacts/screens/ContactsDetails.tsx | 1 + .../tabs/home/components/CustomizeSvg.tsx | 4 +- .../tabs/home/components/LinkingButtons.tsx | 14 +- .../wallet/components/DomainPill.tsx | 44 ++++ .../wallet/components/ShareAddressModal.tsx | 216 ++++++++++++++++++ .../wallet/screens/WalletDetails.tsx | 197 ++++++++++++---- src/navigation/wallet/screens/send/SendTo.tsx | 3 +- 17 files changed, 692 insertions(+), 98 deletions(-) create mode 100644 src/components/avatar/ENSDomainIcon.tsx create mode 100644 src/components/avatar/UnstoppableDomainIcon.tsx create mode 100644 src/components/icons/copy-to-clipboard/CopyToClipboardIcon.tsx create mode 100644 src/navigation/wallet/components/DomainPill.tsx create mode 100644 src/navigation/wallet/components/ShareAddressModal.tsx diff --git a/src/components/avatar/ENSDomainIcon.tsx b/src/components/avatar/ENSDomainIcon.tsx new file mode 100644 index 000000000..7f1081a4e --- /dev/null +++ b/src/components/avatar/ENSDomainIcon.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import Svg, {Defs, LinearGradient, Path, Stop} from 'react-native-svg'; + +interface ENSDomainIconProps { + size?: number; +} + +const ENSDomainIcon: React.FC = props => { + let {size = 20} = props; + + return ( + + + + + + + + + + + + ); +}; + +export default ENSDomainIcon; diff --git a/src/components/avatar/ProfileIcon.tsx b/src/components/avatar/ProfileIcon.tsx index 3ecd02818..125b9667d 100644 --- a/src/components/avatar/ProfileIcon.tsx +++ b/src/components/avatar/ProfileIcon.tsx @@ -1,7 +1,7 @@ import React from 'react'; import * as Svg from 'react-native-svg'; import {useTheme} from 'styled-components/native'; -import {Midnight, ProgressBlue} from '../../styles/colors'; +import {LinkBlue, Midnight, ProgressBlue} from '../../styles/colors'; interface ProfileIconProps { color?: Svg.Color; @@ -14,7 +14,7 @@ const ProfileIcon: React.FC = props => { const theme = useTheme(); size = size || 35; - color = color || (theme.dark ? '#4989FF' : '#9FAFF5'); + color = color || (theme.dark ? LinkBlue : '#9FAFF5'); background = background || (theme.dark ? Midnight : ProgressBlue); return ( diff --git a/src/components/avatar/UnstoppableDomainIcon.tsx b/src/components/avatar/UnstoppableDomainIcon.tsx new file mode 100644 index 000000000..40f45cc82 --- /dev/null +++ b/src/components/avatar/UnstoppableDomainIcon.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import * as Svg from 'react-native-svg'; + +interface UnstoppableDomainIconProps { + size?: number; +} + +const UnstoppableDomainIcon: React.FC = props => { + const {size = 20} = props; + + return ( + <> + + + + + + ); +}; + +export default UnstoppableDomainIcon; diff --git a/src/components/home-card/HomeCard.tsx b/src/components/home-card/HomeCard.tsx index a22051909..f755a33a5 100644 --- a/src/components/home-card/HomeCard.tsx +++ b/src/components/home-card/HomeCard.tsx @@ -1,7 +1,14 @@ import * as React from 'react'; import {ReactElement, ReactNode} from 'react'; import styled, {useTheme} from 'styled-components/native'; -import {Action, LightBlack, Slate, SlateDark, White} from '../../styles/colors'; +import { + Action, + LightBlack, + Midnight, + Slate, + SlateDark, + White, +} from '../../styles/colors'; import Haptic from '../haptic-feedback/haptic'; import { ActiveOpacity, @@ -92,7 +99,7 @@ const FooterArrow = styled.TouchableOpacity` height: 35px; align-self: flex-end; border-radius: 50px; - background-color: ${({theme}) => (theme.dark ? '#0C204E' : '#ECEFFD')}; + background-color: ${({theme}) => (theme.dark ? Midnight : '#ECEFFD')}; align-items: center; justify-content: center; `; diff --git a/src/components/icons/copy-to-clipboard/CopyToClipboardIcon.tsx b/src/components/icons/copy-to-clipboard/CopyToClipboardIcon.tsx new file mode 100644 index 000000000..4427a854e --- /dev/null +++ b/src/components/icons/copy-to-clipboard/CopyToClipboardIcon.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import * as Svg from 'react-native-svg'; +import {Action} from '../../../styles/colors'; + +interface CopyToClipboardIconProps { + size?: number; + color?: Svg.Color; +} + +const CopyToClipboardIcon: React.FC = props => { + let {size = 20, color = Action} = props; + + return ( + + + + ); +}; + +export default CopyToClipboardIcon; diff --git a/src/components/icons/share/Share.tsx b/src/components/icons/share/Share.tsx index 6879db741..accb872f3 100644 --- a/src/components/icons/share/Share.tsx +++ b/src/components/icons/share/Share.tsx @@ -9,32 +9,57 @@ import { } from '../../../styles/colors'; import styled from 'styled-components/native'; -const ShareSvg: React.FC<{isDark: boolean}> = ({isDark}) => { +interface StyleProps { + showBackground?: boolean; +} + +const ShareSvg: React.FC<{ + isDark: boolean; + width: number; + height: number; + fillColor?: string; +}> = ({isDark, width, height, fillColor}) => { return ( - + ); }; -const ShareContainer = styled.View` - height: 40px; - width: 40px; +const ShareContainer = styled.View` + height: ${({showBackground}) => (showBackground ? '40px' : '10px')}; + width: ${({showBackground}) => (showBackground ? '40px' : '10px')}; border-radius: 50px; - background: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; + background: ${({theme: {dark}, showBackground}) => + showBackground ? (dark ? LightBlack : NeutralSlate) : 'transparent'}; align-items: center; justify-content: center; `; -const ShareIcon = () => { +const ShareIcon = ({ + width = 14, + height = 16, + showBackground = true, + fillColor, +}: { + width?: number; + height?: number; + showBackground?: boolean; + fillColor?: string; +}) => { const theme = useTheme(); return ( - - + + ); }; diff --git a/src/components/list/ContactRow.tsx b/src/components/list/ContactRow.tsx index 5791bdeb5..a137a0be1 100644 --- a/src/components/list/ContactRow.tsx +++ b/src/components/list/ContactRow.tsx @@ -30,6 +30,8 @@ const RowContainer = styled.View` align-items: center; `; +export type DomainType = 'ens' | 'unstoppable'; + export interface ContactRowProps { address: string; coin: string; @@ -40,6 +42,7 @@ export interface ContactRowProps { destinationTag?: number; email?: string; domain?: string; + domainType?: DomainType; } interface Props { @@ -50,13 +53,27 @@ interface Props { const ContactRow = ({contact, onPress}: Props) => { const theme = useTheme(); const underlayColor = theme.dark ? '#121212' : '#fbfbff'; - const {coin: _coin, name, email, address, chain, domain} = contact; + const { + coin: _coin, + name, + email, + address, + chain, + domain, + domainType, + } = contact; const coin = getCurrencyAbbreviation(_coin, chain); return ( - +
{name}
diff --git a/src/components/modal/transact-menu/TransactMenuIcons.tsx b/src/components/modal/transact-menu/TransactMenuIcons.tsx index 6aa55143f..eaf0da08d 100644 --- a/src/components/modal/transact-menu/TransactMenuIcons.tsx +++ b/src/components/modal/transact-menu/TransactMenuIcons.tsx @@ -17,7 +17,7 @@ const BuyCrypto = () => { fill={theme.dark ? Midnight : Action} /> @@ -37,17 +37,17 @@ const BuyGiftCard = () => { fill={theme.dark ? Midnight : Action} /> @@ -86,11 +86,11 @@ const Exchange = () => { fill={theme.dark ? Midnight : Action} /> @@ -109,7 +109,7 @@ const Receive = () => { fill={theme.dark ? Midnight : Action} /> { return ( { fill={theme.dark ? Midnight : Action} /> ReactElement); badgeImg: string | ((props?: any) => ReactElement); size?: number; + domainType?: DomainType; } const ContactIconContainer = styled.View` @@ -39,10 +44,23 @@ const CoinBadgeContainer = styled.View` bottom: -1px; `; -const CoinBadge: React.FC = ({size = 20, img, badgeImg}) => { +const CoinBadge: React.FC = ({ + size = 20, + img, + badgeImg, + domainType, +}) => { return ( - + {domainType ? ( + domainType === 'ens' ? ( + + ) : ( + + ) + ) : ( + + )} ); }; @@ -53,6 +71,7 @@ const ContactIcon: React.FC = ({ size = 50, name, badge, + domainType, }) => { const tokenOptions = useAppSelector(({WALLET}: RootState) => { return { @@ -72,12 +91,12 @@ const ContactIcon: React.FC = ({ tokenOptions[getCurrencyAbbreviation(coin, chain)]?.logoURI ? (tokenOptions[getCurrencyAbbreviation(coin, chain)].logoURI as string) : ''); - const coinBadge = img ? ( ) : null; diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index 566f97dc7..17168297d 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -28,7 +28,10 @@ import { } from '../../../../components/styled/Containers'; import {ValidateCoinAddress} from '../../../../store/wallet/utils/validations'; import {GetCoinAndNetwork} from '../../../../store/wallet/effects/address/address'; -import {ContactRowProps} from '../../../../components/list/ContactRow'; +import { + ContactRowProps, + DomainType, +} from '../../../../components/list/ContactRow'; import {useNavigation} from '@react-navigation/core'; import {RootState} from '../../../../store'; import { @@ -74,6 +77,8 @@ import { getAddressByUnstoppableDomain, getENSDomainByAddress, } from '../../../../store/moralis/moralis.effects'; +import ENSDomainIcon from '../../../../components/avatar/ENSDomainIcon'; +import UnstoppableDomainIcon from '../../../../components/avatar/UnstoppableDomainIcon'; const InputContainer = styled.View<{hideInput?: boolean}>` display: ${({hideInput}) => (!hideInput ? 'flex' : 'none')}; @@ -97,6 +102,13 @@ const AddressBadge = styled.View` top: 50%; `; +const DomainBadge = styled.View` + position: absolute; + left: 5px; + top: 52%; + z-index: 1; +`; + const ScanButtonContainer = styled.TouchableOpacity` background: ${({theme}) => (theme && theme.dark ? '#000' : '#fff')}; position: absolute; @@ -210,6 +222,7 @@ const ContactsAdd = ({ const [networkValue, setNetworkValue] = useState(''); const [chainValue, setChainValue] = useState(''); const [domainValue, setDomainValue] = useState(''); + const [domainTypeValue, setDomainTypeValue] = useState(); const [tokenModalVisible, setTokenModalVisible] = useState(false); const [currencyModalVisible, setCurrencyModalVisible] = useState(false); @@ -308,6 +321,7 @@ const ContactsAdd = ({ network: string, chain: string, domain: string, + domainType?: DomainType, ) => { setValidAddress(true); setAddressValue(address); @@ -316,6 +330,7 @@ const ContactsAdd = ({ setChainValue(chain); setValidDomain(true); setDomainValue(domain); + setDomainTypeValue(domainType); _setSelectedCurrency(coin); @@ -337,12 +352,14 @@ const ContactsAdd = ({ network, chain, domain, + domainType, }: { address?: string; coin?: string; network?: string; chain?: string; domain?: string; + domainType?: DomainType; }) => { if (address) { const coinAndNetwork = GetCoinAndNetwork(address, undefined, chain); @@ -359,6 +376,7 @@ const ContactsAdd = ({ network || coinAndNetwork.network, chain || coinAndNetwork.coin, domain || '', + domainType, ); } else { // try testnet @@ -374,6 +392,7 @@ const ContactsAdd = ({ network || 'testnet', chain || coinAndNetwork.coin, domain || '', + domainType, ); } } @@ -392,17 +411,24 @@ const ContactsAdd = ({ if (!domain) { return; } - const addressByENS = await dispatch(getAddressByENSDomain({domain})); const addressByUnstoppableDomain = await dispatch( getAddressByUnstoppableDomain({domain}), ); - if (addressByENS || addressByUnstoppableDomain) { + if (addressByENS) { + setValidDomain(true); + processAddressOrDomain({ + address: addressByENS, + domain, + domainType: 'ens', + }); + } else if (addressByUnstoppableDomain) { setValidDomain(true); processAddressOrDomain({ - address: addressByENS || addressByUnstoppableDomain, + address: addressByUnstoppableDomain, domain, + domainType: 'unstoppable', }); } else { resetValues(); @@ -422,6 +448,7 @@ const ContactsAdd = ({ setEvmValidAddress(false); setXrpValidAddress(false); setValidDomain(false); + setDomainTypeValue(undefined); }; const onSubmit = handleSubmit((contact: ContactRowProps) => { @@ -432,7 +459,7 @@ const ContactsAdd = ({ }); return; } - + if (!validDomain && domainValue) { setError('domain', { type: 'manual', @@ -441,9 +468,10 @@ const ContactsAdd = ({ return; } - if (addressValue && domainValue) { + if (addressValue && domainValue && domainTypeValue) { contact.address = addressValue; contact.domain = domainValue; + contact.domainType = domainTypeValue; } if (coinValue && chainValue && networkValue) { @@ -598,6 +626,7 @@ const ContactsAdd = ({ if (domain) { setValue('domain', domain); setDomainValue(domain); + setDomainTypeValue('ens'); setValidDomain(true); } } catch (err) { @@ -613,6 +642,7 @@ const ContactsAdd = ({ network: contact.network, chain: contact.chain, domain: contact.domain, + domainType: contact.domainType, }); setValue('address', contact.address!, {shouldDirty: true}); setValue('name', contact.name || ''); @@ -665,20 +695,36 @@ const ContactsAdd = ({ {!contact ? ( + {validDomain && domainTypeValue ? ( + domainTypeValue === 'ens' ? ( + + + + ) : ( + + + + ) + ) : null} ( - { - onChange(newValue); - processAddressOrDomain({address: newValue}); - }} - error={errors.address?.message} - value={value} - /> + <> + { + onChange(newValue); + processAddressOrDomain({address: newValue}); + }} + error={errors.address?.message} + value={value} + /> + )} name="address" defaultValue="" @@ -707,10 +753,28 @@ const ContactsAdd = ({ {evmValidAddress && domainValue ? ( + {validDomain && domainTypeValue ? ( + domainTypeValue === 'ens' ? ( + + + + ) : ( + + + + ) + ) : null} ( - + )} name="domain" defaultValue="" diff --git a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx index 1b31ebf60..c747ea564 100644 --- a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx @@ -293,6 +293,7 @@ const ContactsDetails = ({ size={100} name={contact.name} chain={contact.chain} + domainType={contact.domainType} />
diff --git a/src/navigation/tabs/home/components/CustomizeSvg.tsx b/src/navigation/tabs/home/components/CustomizeSvg.tsx index 6f4de2900..4e8fd9e41 100644 --- a/src/navigation/tabs/home/components/CustomizeSvg.tsx +++ b/src/navigation/tabs/home/components/CustomizeSvg.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Circle, Path, Svg} from 'react-native-svg'; import {useTheme} from 'styled-components/native'; -import {Action, LinkBlue} from '../../../../styles/colors'; +import {Action, LinkBlue, Midnight} from '../../../../styles/colors'; const CustomizeSvg: React.FC<{ width: number; @@ -14,7 +14,7 @@ const CustomizeSvg: React.FC<{ cx="17.5" cy="17.5" r="17.5" - fill={theme.dark ? '#0C204E' : '#ECEFFD'} + fill={theme.dark ? Midnight : '#ECEFFD'} /> (dark ? '#0C204E' : Action)}; + background: ${({theme: {dark}}) => (dark ? Midnight : Action)}; margin: 11px 0 8px; `; @@ -49,7 +49,7 @@ const BuySvg = () => { fill-rule="evenodd" clip-rule="evenodd" d="M4.80223 0.805603L4.79692 0.805615L4.79275 0.805603C4.7812 0.805603 4.7697 0.80587 4.75828 0.806399C4.12091 0.829525 3.6113 1.35352 3.6113 1.99653L3.6113 3.65842C2.7443 3.82869 2.03432 4.20006 1.48137 4.77254C0.841615 5.435 0.521739 6.29936 0.521739 7.3656C0.521739 8.15425 0.68323 8.81355 1.00621 9.34352C1.3354 9.87349 1.79503 10.3183 2.38509 10.6779C2.97516 11.0375 3.67081 11.3593 4.47205 11.6432C5.28571 11.9334 5.85714 12.252 6.18634 12.599C6.51553 12.946 6.68012 13.3908 6.68012 13.9334C6.68012 14.4886 6.5 14.9397 6.13975 15.2867C5.7795 15.6337 5.25776 15.8072 4.57453 15.8072C4.20807 15.8072 3.85093 15.7347 3.50311 15.5896C3.15528 15.4382 2.86957 15.1826 2.64596 14.823C2.42236 14.4571 2.31056 13.9524 2.31056 13.3088H0C0 14.2994 0.186335 15.1038 0.559006 15.7221C0.931677 16.3404 1.41304 16.8072 2.00311 17.1227C2.51151 17.3891 3.04757 17.5664 3.6113 17.6548L3.6113 19.0035C3.6113 19.3705 3.77735 19.6988 4.03842 19.9173C4.17198 20.0861 4.37865 20.1944 4.61063 20.1944C4.639 20.1944 4.667 20.1928 4.69453 20.1896C4.73 20.1928 4.76593 20.1944 4.80223 20.1944C5.45997 20.1944 5.99316 19.6612 5.99316 19.0035V17.584C6.80457 17.4034 7.47455 17.0572 8.00311 16.5454C8.6677 15.8956 9 15.0186 9 13.9145C9 13.1259 8.83851 12.4697 8.51553 11.946C8.19876 11.4224 7.74534 10.9807 7.15528 10.6211C6.57143 10.2552 5.87888 9.92396 5.07764 9.62743C4.2205 9.31198 3.63043 8.98705 3.30745 8.65267C2.99068 8.31828 2.8323 7.88611 2.8323 7.35614C2.8323 6.81355 2.98137 6.3656 3.2795 6.01229C3.58385 5.65267 4.05901 5.47286 4.70497 5.47286C5.30745 5.47286 5.7795 5.69683 6.12112 6.14478C6.46894 6.59273 6.64286 7.21103 6.64286 7.99967H8.9441C8.9441 6.73153 8.63665 5.72207 8.02174 4.97128C7.50604 4.32986 6.82985 3.9058 5.99316 3.69912V1.99653C5.99316 1.3388 5.45997 0.805603 4.80223 0.805603Z" - fill={theme.dark ? '#4989FF' : White} + fill={theme.dark ? LinkBlue : White} /> ); @@ -61,11 +61,11 @@ const SwapSvg = () => { ); @@ -79,7 +79,7 @@ const ReceiveSvg = () => { fill-rule="evenodd" clip-rule="evenodd" d="M7.86536 12.7592L10.4752 10.1493C10.9423 9.68222 11.6997 9.68222 12.1668 10.1493C12.6339 10.6164 12.6339 11.3738 12.1668 11.8409L7.52223 16.4855C7.49556 16.5126 7.46761 16.5385 7.43848 16.5629C7.35839 16.6304 7.27154 16.6856 7.18038 16.7287C7.02539 16.802 6.8521 16.8431 6.66924 16.8431C6.49542 16.8431 6.33026 16.806 6.18124 16.7393C6.0654 16.6876 5.95609 16.6166 5.85822 16.5261C5.84522 16.5142 5.8325 16.5019 5.82004 16.4893L1.17162 11.8409C0.704511 11.3738 0.704511 10.6164 1.17162 10.1493C1.63874 9.68222 2.39608 9.68222 2.86319 10.1493L5.47312 12.7593V1.69154C5.47312 1.03094 6.00864 0.495422 6.66924 0.495422C7.32984 0.495422 7.86535 1.03094 7.86536 1.69154V12.7592Z" - fill={theme.dark ? '#4989FF' : White} + fill={theme.dark ? LinkBlue : White} /> ); @@ -93,7 +93,7 @@ const SendSvg = () => { fill-rule="evenodd" clip-rule="evenodd" d="M5.47327 4.57932L2.86339 7.1892C2.39627 7.65631 1.63893 7.65631 1.17182 7.1892C0.704706 6.72209 0.704706 5.96475 1.17182 5.49763L5.81631 0.853139C5.84375 0.825228 5.87254 0.798659 5.90259 0.773535C5.98103 0.707867 6.0659 0.653832 6.15494 0.611428C6.31077 0.537076 6.48522 0.495453 6.66938 0.495453C6.8432 0.495453 7.00836 0.532528 7.15738 0.599202C7.27322 0.650919 7.38253 0.721983 7.4804 0.812388C7.4934 0.824381 7.50613 0.836661 7.51858 0.849213L12.167 5.49763C12.6341 5.96475 12.6341 6.72208 12.167 7.1892C11.6999 7.65631 10.9425 7.65631 10.4754 7.1892L7.8655 4.57927L7.8655 15.647C7.8655 16.3076 7.32998 16.8431 6.66938 16.8431C6.00879 16.8431 5.47327 16.3076 5.47327 15.647L5.47327 4.57932Z" - fill={theme.dark ? '#4989FF' : White} + fill={theme.dark ? LinkBlue : White} /> ); diff --git a/src/navigation/wallet/components/DomainPill.tsx b/src/navigation/wallet/components/DomainPill.tsx new file mode 100644 index 000000000..0bfefdc01 --- /dev/null +++ b/src/navigation/wallet/components/DomainPill.tsx @@ -0,0 +1,44 @@ +import React, {ReactElement, useState} from 'react'; +import styled from 'styled-components/native'; +import {LightBlack, Midnight, White} from '../../../styles/colors'; +import {H7} from '../../../components/styled/Text'; +import {ActiveOpacity} from '../../../components/styled/Containers'; + +interface Props { + icon?: ReactElement; + description: string; + onPress?: () => void; +} + +const PillContainer = styled.TouchableOpacity` + background-color: ${({theme: {dark}}) => (dark ? Midnight : '#ECEFFD')}; + flex-direction: row; + border-radius: 40px; + align-items: center; + justify-content: space-around; + padding: 0 10px 0 10px; + height: 28px; + max-width: 117px; +`; + +const PillText = styled(H7)` + color: ${({theme: {dark}}) => (dark ? White : LightBlack)}; + flex-direction: row; + margin: 0px 10px; +`; + +const DomainPill = ({icon, description, onPress}: Props) => { + return ( + + + {description} + + <>{icon} + + ); +}; + +export default DomainPill; diff --git a/src/navigation/wallet/components/ShareAddressModal.tsx b/src/navigation/wallet/components/ShareAddressModal.tsx new file mode 100644 index 000000000..6235f0dde --- /dev/null +++ b/src/navigation/wallet/components/ShareAddressModal.tsx @@ -0,0 +1,216 @@ +import React, {useEffect, useState} from 'react'; +import styled from 'styled-components/native'; +import {BaseText} from '../../../components/styled/Text'; +import SheetModal from '../../../components/modal/base/sheet/SheetModal'; +import {SheetContainer} from '../../../components/styled/Containers'; +import {Black, White, SlateDark} from '../../../styles/colors'; +import {ScrollView, SafeAreaView, Share} from 'react-native'; +import {useTranslation} from 'react-i18next'; +import ENSDomainIcon from '../../../components/avatar/ENSDomainIcon'; +import CopyToClipboardIcon from '../../../components/icons/copy-to-clipboard/CopyToClipboardIcon'; +import {Wallet} from '../../../store/wallet/wallet.models'; +import {CurrencyImage} from '../../../components/currency-image/CurrencyImage'; +import {DomainType} from '../../../components/list/ContactRow'; +import UnstoppableDomainIcon from '../../../components/avatar/UnstoppableDomainIcon'; +import BitpaySvg from '../../../../assets/img/wallet/transactions/bitpay.svg'; +import haptic from '../../../components/haptic-feedback/haptic'; +import Clipboard from '@react-native-community/clipboard'; +import CopiedSvg from '../../../../assets/img/copied-success.svg'; + +const ShareAddressContainer = styled(SheetContainer)` + background-color: ${({theme: {dark}}) => (dark ? Black : White)}; +`; + +const ModalHeader = styled.View` + margin: 10px 0 20px 0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + position: relative; +`; + +const ModalHeaderText = styled(BaseText)` + font-size: 18px; + font-weight: bold; +`; + +const LabelTip = styled.View` + border: 1px solid ${SlateDark}; + border-radius: 12px; + padding: 16px; + height: 57px; + margin-bottom: 16px; +`; + +const Row = styled.TouchableOpacity` + flex-direction: row; + justify-content: space-between; + align-items: center; +`; + +const RowLabelContainer = styled.View` + max-width: 75%; + flex-direction: row; + align-items: center; + justify-content: flex-start; +`; + +const RowLabel = styled(BaseText)` + font-size: 14px; + padding-left: 8px; +`; + +const CopyToClipboardContainer = styled.TouchableOpacity` + align-items: center; + justify-content: flex-end; +`; + +interface Props { + isVisible: boolean; + closeModal: () => void; + wallet: Wallet; + domain?: string; + domainType?: DomainType; + email?: string; +} + +const ShareAddressModal = ({ + isVisible, + closeModal, + wallet, + domain, + domainType, + email, +}: Props) => { + const {t} = useTranslation(); + const [copiedEmail, setCopiedEmail] = useState(false); + const [copiedDomain, setCopiedDomain] = useState(false); + const [copiedAddress, setCopiedAddress] = useState(false); + const {receiveAddress, img, badgeImg} = wallet; + + const copyToClipboard = (text: string) => { + haptic('impactLight'); + Clipboard.setString(text); + }; + + useEffect(() => { + const timer = setTimeout(() => { + setCopiedEmail(false); + }, 3000); + return () => clearTimeout(timer); + }, [copiedEmail]); + + useEffect(() => { + const timer = setTimeout(() => { + setCopiedAddress(false); + }, 3000); + return () => clearTimeout(timer); + }, [copiedAddress]); + + useEffect(() => { + const timer = setTimeout(() => { + setCopiedDomain(false); + }, 3000); + return () => clearTimeout(timer); + }, [copiedDomain]); + + const shareAddress = async (text: string) => { + await Share.share({ + message: text, + }); + }; + + return ( + + + + + {t('Share Address')} + + + {email ? ( + + shareAddress(email)}> + + + + {email} + + + { + copyToClipboard(email); + setCopiedEmail(true); + }}> + {!copiedEmail ? ( + + ) : ( + + )} + + + + ) : null} + + {domain && domainType ? ( + + shareAddress(domain)}> + + {domainType === 'ens' ? ( + + ) : ( + + )} + + {domain} + + + { + copyToClipboard(domain); + setCopiedDomain(true); + }}> + {!copiedDomain ? ( + + ) : ( + + )} + + + + ) : null} + + {receiveAddress ? ( + + shareAddress(receiveAddress)}> + + + + {receiveAddress} + + + { + copyToClipboard(receiveAddress); + setCopiedAddress(true); + }}> + {!copiedAddress ? ( + + ) : ( + + )} + + + + ) : null} + + + + + ); +}; + +export default ShareAddressModal; diff --git a/src/navigation/wallet/screens/WalletDetails.tsx b/src/navigation/wallet/screens/WalletDetails.tsx index c45b6fa5a..ac37e7e81 100644 --- a/src/navigation/wallet/screens/WalletDetails.tsx +++ b/src/navigation/wallet/screens/WalletDetails.tsx @@ -1,7 +1,7 @@ import {useNavigation, useTheme} from '@react-navigation/native'; import {StackScreenProps} from '@react-navigation/stack'; import i18next from 'i18next'; -import _ from 'lodash'; +import _, {debounce} from 'lodash'; import React, { ReactElement, useCallback, @@ -49,9 +49,11 @@ import { Wallet, } from '../../../store/wallet/wallet.models'; import { + Action, Air, Black, LightBlack, + LinkBlue, LuckySevens, SlateDark, White, @@ -127,6 +129,15 @@ import SentBadgeSvg from '../../../../assets/img/sent-badge.svg'; import {Analytics} from '../../../store/analytics/analytics.effects'; import {getGiftCardIcons} from '../../../lib/gift-cards/gift-card'; import {BillPayAccount} from '../../../store/shop/shop.models'; +import DomainPill from '../components/DomainPill'; +import ShareIcon from '../../../components/icons/share/Share'; +import {getENSDomainByAddress} from '../../../store/moralis/moralis.effects'; +import ShareAddressModal from '../components/ShareAddressModal'; +import CopyToClipboardIcon from '../../../components/icons/copy-to-clipboard/CopyToClipboardIcon'; +import haptic from '../../../components/haptic-feedback/haptic'; +import Clipboard from '@react-native-community/clipboard'; +import CopiedSvg from '../../../../assets/img/copied-success.svg'; +import {DomainType} from '../../../components/list/ContactRow'; export type WalletDetailsScreenParamList = { walletId: string; @@ -145,7 +156,7 @@ const WalletDetailsContainer = styled.View` `; const HeaderContainer = styled.View` - margin: 32px 0 24px; + margin: 10px 0 24px; `; const Row = styled.View` @@ -162,7 +173,7 @@ const TouchableRow = styled.TouchableOpacity` `; const BalanceContainer = styled.View` - padding: 0 15px 40px; + padding: 0 15px 29px; flex-direction: column; `; @@ -254,6 +265,12 @@ const TypeText = styled(BaseText)` color: ${({theme: {dark}}) => (dark ? LuckySevens : SlateDark)}; `; +const DomainPillContainer = styled.TouchableOpacity` + flex-direction: row; + justify-content: center; + margin-top: 12px; +`; + const getWalletType = ( key: Key, wallet: Wallet, @@ -298,6 +315,7 @@ const WalletDetails: React.FC = ({route}) => { const contactList = useAppSelector(({CONTACT}) => CONTACT.list); const {defaultAltCurrency, hideAllBalances} = useAppSelector(({APP}) => APP); const fullWalletObj = findWalletById(wallets, walletId) as Wallet; + const key = keys[fullWalletObj.keyId]; const uiFormattedWallet = buildUIFormattedWallet( fullWalletObj, @@ -473,6 +491,61 @@ const WalletDetails: React.FC = ({route}) => { const [onEndReachedCalledDuringLoading, setOnEndReachedCalledDuringLoading] = useState(true); + const user = useAppSelector(({BITPAY_ID}) => BITPAY_ID.user[network]); + const [domain, setDomain] = useState(); + const [domainType, setDomainType] = useState(); + const [showShareAddressModal, setShowShareAddressModal] = useState(false); + const [showShareAddressIcon, setShowShareAddressIcon] = useState(false); + const [copied, setCopied] = useState(false); + + const fetchENSDomainByAddress = useMemo( + () => + debounce(async ({address}: {address: string}) => { + try { + if (!address) { + return; + } + const _domain = await dispatch(getENSDomainByAddress({address})); + if (_domain) { + setDomain(_domain); + setDomainType('ens'); + } + } catch (err) { + console.error(err); + } + }, 300), + [], + ); + + if ( + fullWalletObj.receiveAddress && + SUPPORTED_EVM_COINS.includes(currencyAbbreviation.toLocaleLowerCase()) + ) { + fetchENSDomainByAddress({address: fullWalletObj.receiveAddress}); + } + + const copyToClipboard = (text: string) => { + haptic('impactLight'); + Clipboard.setString(text); + setCopied(true); + }; + + useEffect(() => { + if (!copied) { + return; + } + const timer = setTimeout(() => { + setCopied(false); + }, 3000); + + return () => clearTimeout(timer); + }, [copied]); + + useEffect(() => { + const _arr = _.compact([fullWalletObj.receiveAddress, domain, user?.email]); + setShowShareAddressIcon(_arr.length > 1 ? true : false); + }, [fullWalletObj.receiveAddress, domain]); + const setNeedActionTxps = (pendingTxps: TransactionProposal[]) => { const txpsPending: TransactionProposal[] = []; const txpsUnsent: TransactionProposal[] = []; @@ -952,6 +1025,45 @@ const WalletDetails: React.FC = ({route}) => { return ( <> + + {walletType && ( + + {walletType.icon ? ( + {walletType.icon} + ) : null} + {walletType.title} + + )} + {protocolName ? ( + + + + + {protocolName} + + ) : null} + {IsShared(fullWalletObj) ? ( + + + Multisig {fullWalletObj.credentials.m}/ + {fullWalletObj.credentials.n} + + + ) : null} + {['xrp'].includes(fullWalletObj?.currencyAbbreviation) ? ( + setShowBalanceDetailsModal(true)}> + + + ) : null} + {['xrp'].includes(fullWalletObj?.currencyAbbreviation) && + Number(fullWalletObj?.balance?.cryptoConfirmedLocked) >= + 10 ? ( + + {t('Activated')} + + ) : null} + { @@ -990,45 +1102,35 @@ const WalletDetails: React.FC = ({route}) => { )} - - {walletType && ( - - {walletType.icon ? ( - {walletType.icon} - ) : null} - {walletType.title} - - )} - {protocolName ? ( - - - - - {protocolName} - - ) : null} - {IsShared(fullWalletObj) ? ( - - - Multisig {fullWalletObj.credentials.m}/ - {fullWalletObj.credentials.n} - - - ) : null} - {['xrp'].includes(fullWalletObj?.currencyAbbreviation) ? ( - setShowBalanceDetailsModal(true)}> - - - ) : null} - {['xrp'].includes(fullWalletObj?.currencyAbbreviation) && - Number(fullWalletObj?.balance?.cryptoConfirmedLocked) >= - 10 ? ( - - {t('Activated')} - - ) : null} - + {fullWalletObj.receiveAddress ? ( + + + ) : !copied ? ( + + ) : ( + + ) + } + onPress={() => { + showShareAddressIcon + ? setShowShareAddressModal(true) + : copyToClipboard(fullWalletObj.receiveAddress!); + }} + description={fullWalletObj.receiveAddress} + /> + + ) : null} {fullWalletObj ? ( @@ -1231,6 +1333,17 @@ const WalletDetails: React.FC = ({route}) => { wallet={fullWalletObj} /> ) : null} + + {fullWalletObj ? ( + setShowShareAddressModal(false)} + wallet={fullWalletObj} + domain={domain} + domainType={domainType} + email={user?.email} + /> + ) : null} ); }; diff --git a/src/navigation/wallet/screens/send/SendTo.tsx b/src/navigation/wallet/screens/send/SendTo.tsx index 5e6db7b4b..a83ffe214 100644 --- a/src/navigation/wallet/screens/send/SendTo.tsx +++ b/src/navigation/wallet/screens/send/SendTo.tsx @@ -348,7 +348,8 @@ const SendTo = () => { contact.chain === chain.toLowerCase() && contact.network === network && (contact.name.toLowerCase().includes(searchInput.toLowerCase()) || - contact.email?.toLowerCase().includes(searchInput.toLowerCase())), + contact.email?.toLowerCase().includes(searchInput.toLowerCase()) || + contact.domain?.toLowerCase().includes(searchInput.toLowerCase())), ); }, [allContacts, currencyAbbreviation, network, searchInput]); From 4c2ff3d7d75b55a8856336f6340376376ee17443 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 14 Apr 2023 16:17:10 +0200 Subject: [PATCH 3/9] allow moralis url prefix --- scripts/allowed-url-prefixes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/allowed-url-prefixes.js b/scripts/allowed-url-prefixes.js index 1b5271556..c603f8232 100644 --- a/scripts/allowed-url-prefixes.js +++ b/scripts/allowed-url-prefixes.js @@ -45,6 +45,7 @@ const allowedUrlPrefixes = [ 'https://cloudflare-eth.com/', 'https://polygon-rpc.com/', 'https://matic-mumbai.chainstacklabs.com', + 'https://deep-index.moralis.io/api/v2/', ].concat(developmentOnlyAllowedUrlPrefixes); const allowedUrlPrefixString = allowedUrlPrefixes.join(','); From 9632fb59b6346c43a0eaea9103b95851f9dd8e65 Mon Sep 17 00:00:00 2001 From: juan Date: Fri, 14 Apr 2023 18:00:21 +0200 Subject: [PATCH 4/9] updates --- src/components/list/ContactRow.tsx | 2 +- .../tabs/contacts/components/ContactIcon.tsx | 2 +- .../tabs/contacts/screens/ContactsAdd.tsx | 55 +++++++++++-------- src/store/moralis/moralis.constants.ts | 14 +++++ src/store/wallet/utils/validations.ts | 25 +++++++++ 5 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 src/store/moralis/moralis.constants.ts diff --git a/src/components/list/ContactRow.tsx b/src/components/list/ContactRow.tsx index a137a0be1..d1ffd82f6 100644 --- a/src/components/list/ContactRow.tsx +++ b/src/components/list/ContactRow.tsx @@ -30,7 +30,7 @@ const RowContainer = styled.View` align-items: center; `; -export type DomainType = 'ens' | 'unstoppable'; +export type DomainType = 'ENSDomain' | 'UnstoppableDomain'; export interface ContactRowProps { address: string; diff --git a/src/navigation/tabs/contacts/components/ContactIcon.tsx b/src/navigation/tabs/contacts/components/ContactIcon.tsx index 10597d8fe..4d16e4f54 100644 --- a/src/navigation/tabs/contacts/components/ContactIcon.tsx +++ b/src/navigation/tabs/contacts/components/ContactIcon.tsx @@ -53,7 +53,7 @@ const CoinBadge: React.FC = ({ return ( {domainType ? ( - domainType === 'ens' ? ( + domainType === 'ENSDomain' ? ( ) : ( diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index 17168297d..040b73307 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -26,7 +26,10 @@ import { SearchInput, Column, } from '../../../../components/styled/Containers'; -import {ValidateCoinAddress} from '../../../../store/wallet/utils/validations'; +import { + ValidateCoinAddress, + ValidateURI, +} from '../../../../store/wallet/utils/validations'; import {GetCoinAndNetwork} from '../../../../store/wallet/effects/address/address'; import { ContactRowProps, @@ -397,7 +400,8 @@ const ContactsAdd = ({ } } } else { - processDomain({domain: address}); + const data = ValidateURI(address); + processDomain({data}); } } else { resetValues(); @@ -406,30 +410,35 @@ const ContactsAdd = ({ const processDomain = useMemo( () => - debounce(async ({domain}: {domain: string}) => { + debounce(async ({data}: {data: any}) => { try { - if (!domain) { + if (!data.data) { return; } - const addressByENS = await dispatch(getAddressByENSDomain({domain})); - const addressByUnstoppableDomain = await dispatch( - getAddressByUnstoppableDomain({domain}), - ); - - if (addressByENS) { - setValidDomain(true); - processAddressOrDomain({ - address: addressByENS, - domain, - domainType: 'ens', - }); - } else if (addressByUnstoppableDomain) { - setValidDomain(true); - processAddressOrDomain({ - address: addressByUnstoppableDomain, - domain, - domainType: 'unstoppable', - }); + if (data.type === 'UnstoppableDomain') { + const addressByUnstoppableDomain = await dispatch( + getAddressByUnstoppableDomain({domain: data.data}), + ); + if (addressByUnstoppableDomain) { + setValidDomain(true); + processAddressOrDomain({ + address: addressByUnstoppableDomain, + domain: data.data, + domainType: data.type, + }); + } + } else if (data.type === 'ENSDomain') { + const addressByENS = await dispatch( + getAddressByENSDomain({domain: data.data}), + ); + if (addressByENS) { + setValidDomain(true); + processAddressOrDomain({ + address: addressByENS, + domain: data.data, + domainType: data.type, + }); + } } else { resetValues(); } diff --git a/src/store/moralis/moralis.constants.ts b/src/store/moralis/moralis.constants.ts new file mode 100644 index 000000000..44335414c --- /dev/null +++ b/src/store/moralis/moralis.constants.ts @@ -0,0 +1,14 @@ +export const UnstoppableDomains = [ + '.crypto', + '.nft', + '.x', + '.wallet', + '.polygon', + '.dao', + '.888', + '.zil', + '.blockchain', + '.bitcoin', +]; + +export const ENSDomains = ['.eth']; diff --git a/src/store/wallet/utils/validations.ts b/src/store/wallet/utils/validations.ts index 5af27ffaf..0a8b7c7c3 100644 --- a/src/store/wallet/utils/validations.ts +++ b/src/store/wallet/utils/validations.ts @@ -1,4 +1,5 @@ import {BwcProvider} from '../../../lib/bwc'; +import {ENSDomains, UnstoppableDomains} from '../../moralis/moralis.constants'; import {ExtractBitPayUriAddress} from './decode-uri'; const BWC = BwcProvider.getInstance(); @@ -204,6 +205,14 @@ export const CheckIfLegacyBCH = (address: string): boolean => { ); }; +export const IsValidUnstoppableDomain = (domain: string): boolean => { + return UnstoppableDomains.some(item => domain.endsWith(item)); +}; + +export const IsValidENSDomain = (domain: string): boolean => { + return ENSDomains.some(item => domain.endsWith(item)); +}; + export const ValidateURI = (data: string): any => { if (!data) { return; @@ -353,6 +362,22 @@ export const ValidateURI = (data: string): any => { }; } + if (IsValidUnstoppableDomain(data)) { + return { + data, + type: 'UnstoppableDomain', + title: 'Unstoppable Domain', + }; + } + + if (IsValidENSDomain(data)) { + return { + data, + type: 'ENSDomain', + title: 'ENS Domain', + }; + } + return; }; From f47d9c4d6cbf6c6362f38618d83cf809317f2d95 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 17 Apr 2023 13:54:18 +0200 Subject: [PATCH 5/9] confirm page, send to views. Improvements --- src/components/list/ContactRow.tsx | 28 +++--- .../tabs/contacts/screens/ContactsAdd.tsx | 67 +++++++------ .../tabs/contacts/screens/ContactsDetails.tsx | 2 +- .../wallet/components/ShareAddressModal.tsx | 14 ++- .../wallet/screens/WalletDetails.tsx | 18 ++-- src/navigation/wallet/screens/send/SendTo.tsx | 95 ++++++++++++++++++- .../wallet/screens/send/confirm/Confirm.tsx | 6 +- .../wallet/screens/send/confirm/Shared.tsx | 22 ++++- src/store/scan/scan.effects.ts | 6 ++ src/store/wallet/wallet.models.ts | 3 + 10 files changed, 185 insertions(+), 76 deletions(-) diff --git a/src/components/list/ContactRow.tsx b/src/components/list/ContactRow.tsx index d1ffd82f6..b529464e3 100644 --- a/src/components/list/ContactRow.tsx +++ b/src/components/list/ContactRow.tsx @@ -32,6 +32,12 @@ const RowContainer = styled.View` export type DomainType = 'ENSDomain' | 'UnstoppableDomain'; +export interface DomainProps { + domainName: string; + domainType: DomainType; + domainAddress?: string; +} + export interface ContactRowProps { address: string; coin: string; @@ -41,8 +47,7 @@ export interface ContactRowProps { tag?: number; // backward compatibility destinationTag?: number; email?: string; - domain?: string; - domainType?: DomainType; + domain?: DomainProps; } interface Props { @@ -53,16 +58,9 @@ interface Props { const ContactRow = ({contact, onPress}: Props) => { const theme = useTheme(); const underlayColor = theme.dark ? '#121212' : '#fbfbff'; - const { - coin: _coin, - name, - email, - address, - chain, - domain, - domainType, - } = contact; + const {coin: _coin, name, email, address, chain, domain} = contact; const coin = getCurrencyAbbreviation(_coin, chain); + const {domainName, domainType, domainAddress} = domain || {}; return ( @@ -78,7 +76,13 @@ const ContactRow = ({contact, onPress}: Props) => {
{name}
- {domain ? domain : email ? email : address} + {domainAddress + ? domainAddress + : domainName + ? domainName + : email + ? email + : address}
diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index 040b73307..acbfeb3e4 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -33,7 +33,7 @@ import { import {GetCoinAndNetwork} from '../../../../store/wallet/effects/address/address'; import { ContactRowProps, - DomainType, + DomainProps, } from '../../../../components/list/ContactRow'; import {useNavigation} from '@react-navigation/core'; import {RootState} from '../../../../store'; @@ -224,8 +224,7 @@ const ContactsAdd = ({ const [coinValue, setCoinValue] = useState(''); const [networkValue, setNetworkValue] = useState(''); const [chainValue, setChainValue] = useState(''); - const [domainValue, setDomainValue] = useState(''); - const [domainTypeValue, setDomainTypeValue] = useState(); + const [domainValue, setDomainValue] = useState(); const [tokenModalVisible, setTokenModalVisible] = useState(false); const [currencyModalVisible, setCurrencyModalVisible] = useState(false); @@ -323,8 +322,7 @@ const ContactsAdd = ({ coin: string, network: string, chain: string, - domain: string, - domainType?: DomainType, + domain?: DomainProps, ) => { setValidAddress(true); setAddressValue(address); @@ -333,7 +331,6 @@ const ContactsAdd = ({ setChainValue(chain); setValidDomain(true); setDomainValue(domain); - setDomainTypeValue(domainType); _setSelectedCurrency(coin); @@ -355,14 +352,12 @@ const ContactsAdd = ({ network, chain, domain, - domainType, }: { address?: string; coin?: string; network?: string; chain?: string; - domain?: string; - domainType?: DomainType; + domain?: DomainProps; }) => { if (address) { const coinAndNetwork = GetCoinAndNetwork(address, undefined, chain); @@ -378,8 +373,7 @@ const ContactsAdd = ({ coin || coinAndNetwork.coin, network || coinAndNetwork.network, chain || coinAndNetwork.coin, - domain || '', - domainType, + domain, ); } else { // try testnet @@ -394,8 +388,7 @@ const ContactsAdd = ({ coin || coinAndNetwork.coin, network || 'testnet', chain || coinAndNetwork.coin, - domain || '', - domainType, + domain, ); } } @@ -423,8 +416,10 @@ const ContactsAdd = ({ setValidDomain(true); processAddressOrDomain({ address: addressByUnstoppableDomain, - domain: data.data, - domainType: data.type, + domain: { + domainName: data.data, + domainType: data.type, + }, }); } } else if (data.type === 'ENSDomain') { @@ -435,8 +430,10 @@ const ContactsAdd = ({ setValidDomain(true); processAddressOrDomain({ address: addressByENS, - domain: data.data, - domainType: data.type, + domain: { + domainName: data.data, + domainType: data.type, + }, }); } } else { @@ -457,7 +454,7 @@ const ContactsAdd = ({ setEvmValidAddress(false); setXrpValidAddress(false); setValidDomain(false); - setDomainTypeValue(undefined); + setDomainValue(undefined); }; const onSubmit = handleSubmit((contact: ContactRowProps) => { @@ -477,10 +474,9 @@ const ContactsAdd = ({ return; } - if (addressValue && domainValue && domainTypeValue) { + if (addressValue && domainValue) { contact.address = addressValue; contact.domain = domainValue; - contact.domainType = domainTypeValue; } if (coinValue && chainValue && networkValue) { @@ -629,13 +625,15 @@ const ContactsAdd = ({ const fetchENSDomainByAddress = async () => { try { - const domain = await dispatch( + const domainName = await dispatch( getENSDomainByAddress({address: addressValue}), ); - if (domain) { - setValue('domain', domain); - setDomainValue(domain); - setDomainTypeValue('ens'); + if (domainName) { + setValue('domain', domainName); + setDomainValue({ + domainName, + domainType: 'ENSDomain', + }); setValidDomain(true); } } catch (err) { @@ -651,7 +649,6 @@ const ContactsAdd = ({ network: contact.network, chain: contact.chain, domain: contact.domain, - domainType: contact.domainType, }); setValue('address', contact.address!, {shouldDirty: true}); setValue('name', contact.name || ''); @@ -659,11 +656,11 @@ const ContactsAdd = ({ setValue('chain', contact.chain!); setValue('destinationTag', contact.tag || contact.destinationTag); setValue('domain', contact.domain); - if (context === 'edit' && evmValidAddress && !domainValue) { + if (context === 'edit' && !domainValue) { fetchENSDomainByAddress(); } } - }, [contact, evmValidAddress]); + }, [contact]); return ( @@ -704,8 +701,8 @@ const ContactsAdd = ({ {!contact ? ( - {validDomain && domainTypeValue ? ( - domainTypeValue === 'ens' ? ( + {validDomain && domainValue ? ( + domainValue.domainType === 'ENSDomain' ? ( @@ -760,10 +757,10 @@ const ContactsAdd = ({ defaultValue="" /> - {evmValidAddress && domainValue ? ( + {domainValue ? ( - {validDomain && domainTypeValue ? ( - domainTypeValue === 'ens' ? ( + {validDomain && domainValue ? ( + domainValue.domainType === 'ENSDomain' ? ( @@ -779,14 +776,14 @@ const ContactsAdd = ({ )} name="domain" - defaultValue="" + defaultValue={undefined} /> ) : null} diff --git a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx index c747ea564..83e637fa2 100644 --- a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx @@ -293,7 +293,7 @@ const ContactsDetails = ({ size={100} name={contact.name} chain={contact.chain} - domainType={contact.domainType} + domainType={contact.domain?.domainType} />
diff --git a/src/navigation/wallet/components/ShareAddressModal.tsx b/src/navigation/wallet/components/ShareAddressModal.tsx index 6235f0dde..a13c59e97 100644 --- a/src/navigation/wallet/components/ShareAddressModal.tsx +++ b/src/navigation/wallet/components/ShareAddressModal.tsx @@ -10,7 +10,7 @@ import ENSDomainIcon from '../../../components/avatar/ENSDomainIcon'; import CopyToClipboardIcon from '../../../components/icons/copy-to-clipboard/CopyToClipboardIcon'; import {Wallet} from '../../../store/wallet/wallet.models'; import {CurrencyImage} from '../../../components/currency-image/CurrencyImage'; -import {DomainType} from '../../../components/list/ContactRow'; +import {DomainProps} from '../../../components/list/ContactRow'; import UnstoppableDomainIcon from '../../../components/avatar/UnstoppableDomainIcon'; import BitpaySvg from '../../../../assets/img/wallet/transactions/bitpay.svg'; import haptic from '../../../components/haptic-feedback/haptic'; @@ -70,8 +70,7 @@ interface Props { isVisible: boolean; closeModal: () => void; wallet: Wallet; - domain?: string; - domainType?: DomainType; + domain?: DomainProps; email?: string; } @@ -80,7 +79,6 @@ const ShareAddressModal = ({ closeModal, wallet, domain, - domainType, email, }: Props) => { const {t} = useTranslation(); @@ -155,11 +153,11 @@ const ShareAddressModal = ({ ) : null} - {domain && domainType ? ( + {domain ? ( - shareAddress(domain)}> + shareAddress(domain.domainName)}> - {domainType === 'ens' ? ( + {domain.domainType === 'ENSDomain' ? ( ) : ( @@ -170,7 +168,7 @@ const ShareAddressModal = ({ { - copyToClipboard(domain); + copyToClipboard(domain.domainName); setCopiedDomain(true); }}> {!copiedDomain ? ( diff --git a/src/navigation/wallet/screens/WalletDetails.tsx b/src/navigation/wallet/screens/WalletDetails.tsx index ac37e7e81..9398637a3 100644 --- a/src/navigation/wallet/screens/WalletDetails.tsx +++ b/src/navigation/wallet/screens/WalletDetails.tsx @@ -137,7 +137,7 @@ import CopyToClipboardIcon from '../../../components/icons/copy-to-clipboard/Cop import haptic from '../../../components/haptic-feedback/haptic'; import Clipboard from '@react-native-community/clipboard'; import CopiedSvg from '../../../../assets/img/copied-success.svg'; -import {DomainType} from '../../../components/list/ContactRow'; +import {DomainProps} from '../../../components/list/ContactRow'; export type WalletDetailsScreenParamList = { walletId: string; @@ -492,8 +492,7 @@ const WalletDetails: React.FC = ({route}) => { useState(true); const user = useAppSelector(({BITPAY_ID}) => BITPAY_ID.user[network]); - const [domain, setDomain] = useState(); - const [domainType, setDomainType] = useState(); + const [domain, setDomain] = useState(); const [showShareAddressModal, setShowShareAddressModal] = useState(false); const [showShareAddressIcon, setShowShareAddressIcon] = useState(false); const [copied, setCopied] = useState(false); @@ -505,10 +504,9 @@ const WalletDetails: React.FC = ({route}) => { if (!address) { return; } - const _domain = await dispatch(getENSDomainByAddress({address})); - if (_domain) { - setDomain(_domain); - setDomainType('ens'); + const domainName = await dispatch(getENSDomainByAddress({address})); + if (domainName) { + setDomain({domainName, domainType: 'ENSDomain'}); } } catch (err) { console.error(err); @@ -517,10 +515,7 @@ const WalletDetails: React.FC = ({route}) => { [], ); - if ( - fullWalletObj.receiveAddress && - SUPPORTED_EVM_COINS.includes(currencyAbbreviation.toLocaleLowerCase()) - ) { + if (fullWalletObj.receiveAddress) { fetchENSDomainByAddress({address: fullWalletObj.receiveAddress}); } @@ -1340,7 +1335,6 @@ const WalletDetails: React.FC = ({route}) => { closeModal={() => setShowShareAddressModal(false)} wallet={fullWalletObj} domain={domain} - domainType={domainType} email={user?.email} /> ) : null} diff --git a/src/navigation/wallet/screens/send/SendTo.tsx b/src/navigation/wallet/screens/send/SendTo.tsx index a83ffe214..f99d1c13b 100644 --- a/src/navigation/wallet/screens/send/SendTo.tsx +++ b/src/navigation/wallet/screens/send/SendTo.tsx @@ -86,13 +86,17 @@ import {toFiat} from '../../../../store/wallet/utils/wallet'; import Settings from '../../../../components/settings/Settings'; import OptionsSheet, {Option} from '../../components/OptionsSheet'; import Icons from '../../components/WalletIcons'; -import ContactRow from '../../../../components/list/ContactRow'; +import ContactRow, {DomainProps} from '../../../../components/list/ContactRow'; import {ReceivingAddress} from '../../../../store/bitpay-id/bitpay-id.models'; import {BitPayIdEffects} from '../../../../store/bitpay-id'; import {getCurrencyCodeFromCoinAndChain} from '../../../bitpay-id/utils/bitpay-id-utils'; import {Analytics} from '../../../../store/analytics/analytics.effects'; import {LogActions} from '../../../../store/log'; import CopySvg from '../../../../../assets/img/copy.svg'; +import { + getAddressByENSDomain, + getAddressByUnstoppableDomain, +} from '../../../../store/moralis/moralis.effects'; const ValidDataTypes: string[] = [ 'BitcoinAddress', @@ -110,6 +114,8 @@ const ValidDataTypes: string[] = [ 'DogecoinUri', 'LitecoinUri', 'BitPayUri', + 'UnstoppableDomain', + 'ENSDomain', ]; const SafeAreaView = styled.SafeAreaView` @@ -273,6 +279,7 @@ const SendTo = () => { const [emailAddressSearchPromise, setEmailAddressSearchPromise] = useState< Promise >(Promise.resolve([])); + const [domain, setDomain] = useState(); const {wallet} = route.params; const {currencyAbbreviation, id, chain, network} = wallet; @@ -349,7 +356,9 @@ const SendTo = () => { contact.network === network && (contact.name.toLowerCase().includes(searchInput.toLowerCase()) || contact.email?.toLowerCase().includes(searchInput.toLowerCase()) || - contact.domain?.toLowerCase().includes(searchInput.toLowerCase())), + contact.domain?.domainName + ?.toLowerCase() + .includes(searchInput.toLowerCase())), ); }, [allContacts, currencyAbbreviation, network, searchInput]); @@ -478,16 +487,65 @@ const SendTo = () => { ); } } else if (ValidDataTypes.includes(data?.type)) { - if (dispatch(checkCoinAndNetwork(text))) { + if (data.type === 'UnstoppableDomain' || data.type === 'ENSDomain') { + processDomain({data}); + } else if (dispatch(checkCoinAndNetwork(text))) { setSearchInput(text); await sleep(0); dispatch( - incomingData(text, {wallet, context, name, email, destinationTag}), + incomingData(text, { + wallet, + context, + name, + email, + destinationTag, + domain, + }), ); } } }; + const processDomain = useMemo( + () => + debounce(async ({data}: {data: any}) => { + try { + if (data.type === 'UnstoppableDomain') { + const addressByUnstoppableDomain = await dispatch( + getAddressByUnstoppableDomain({domain: data.data}), + ); + setDomain( + addressByUnstoppableDomain + ? { + domainName: data.data, + domainType: 'UnstoppableDomain', + domainAddress: addressByUnstoppableDomain, + } + : undefined, + ); + } else if (data.type === 'ENSDomain') { + const addressByENS = await dispatch( + getAddressByENSDomain({domain: data.data}), + ); + setDomain( + addressByENS + ? { + domainName: data.data, + domainType: 'ENSDomain', + domainAddress: addressByENS, + } + : undefined, + ); + } else { + setDomain(undefined); + } + } catch (e) { + setDomain(undefined); + } + }, 300), + [], + ); + const onSearchInputChange = debounce((text: string) => { validateAndNavigateToConfirm(text); }, 300); @@ -584,7 +642,7 @@ const SendTo = () => { { @@ -723,6 +781,33 @@ const SendTo = () => { ) : null} + {domain && domain.domainAddress ? ( + + + {ContactsSvg({})} + {t('Domain')} + + { + if (domain.domainAddress) { + validateAndNavigateToConfirm(domain.domainAddress, { + context: 'domain', + name: domain.domainName, + }); + } + }} + /> + + ) : null} + setShowWalletOptions(false)} diff --git a/src/navigation/wallet/screens/send/confirm/Confirm.tsx b/src/navigation/wallet/screens/send/confirm/Confirm.tsx index 4b03482fc..a44fc65a0 100644 --- a/src/navigation/wallet/screens/send/confirm/Confirm.tsx +++ b/src/navigation/wallet/screens/send/confirm/Confirm.tsx @@ -367,13 +367,16 @@ const Confirm = () => { recipientType: r.type, recipientCoin: currencyAbbreviation, recipientChain: r.chain, + recipientDomain: r.domain, }; }); } if ( recipient.type && - (recipient.type === 'coinbase' || recipient.type === 'contact') + (recipient.type === 'coinbase' || + recipient.type === 'contact' || + recipient.type === 'domain') ) { recipientData = { recipientName: recipient.name, @@ -381,6 +384,7 @@ const Confirm = () => { img: recipient.type, recipientChain: recipient.chain, recipientType: recipient.type, + recipientDomain: recipient.domain, }; } else { recipientData = sendingTo; diff --git a/src/navigation/wallet/screens/send/confirm/Shared.tsx b/src/navigation/wallet/screens/send/confirm/Shared.tsx index 162f13c4e..b1a301f28 100644 --- a/src/navigation/wallet/screens/send/confirm/Shared.tsx +++ b/src/navigation/wallet/screens/send/confirm/Shared.tsx @@ -53,6 +53,8 @@ import {LuckySevens} from '../../../../../styles/colors'; import {IsERCToken} from '../../../../../store/wallet/utils/currency'; import {CurrencyListIcons} from '../../../../../constants/SupportedCurrencyOptions'; import ContactIcon from '../../../../tabs/contacts/components/ContactIcon'; +import ENSDomainIcon from '../../../../../components/avatar/ENSDomainIcon'; +import UnstoppableDomainIcon from '../../../../../components/avatar/UnstoppableDomainIcon'; // Styled export const ConfirmContainer = styled.SafeAreaView` @@ -172,6 +174,7 @@ export const SendingTo: React.VFC = ({ recipientCoin, recipientChain, recipientType, + recipientDomain, } = recipient; let badgeImg; @@ -201,7 +204,12 @@ export const SendingTo: React.VFC = ({ ' ' + (recipientList.length === 1 ? t('Recipient') : t('Recipients')); } else { - description = recipientName || recipientEmail || recipientAddress || ''; + description = + recipientDomain?.domainName || + recipientName || + recipientEmail || + recipientAddress || + ''; } return ( @@ -219,7 +227,17 @@ export const SendingTo: React.VFC = ({ copied ? ( ) : recipientType === 'contact' || recipientEmail ? ( - + + ) : recipientType === 'domain' && recipientDomain ? ( + recipientDomain.domainType === 'ENSDomain' ? ( + + ) : ( + + ) ) : ( ) diff --git a/src/store/scan/scan.effects.ts b/src/store/scan/scan.effects.ts index 1d011c162..27a3c9e56 100644 --- a/src/store/scan/scan.effects.ts +++ b/src/store/scan/scan.effects.ts @@ -82,6 +82,7 @@ import {calculateUsdToAltFiat} from '../buy-crypto/buy-crypto.effects'; import {IsUtxoCoin} from '../wallet/utils/currency'; import {BWCErrorMessage} from '../../constants/BWCError'; import {walletConnectV2OnSessionProposal} from '../wallet-connect-v2/wallet-connect-v2.effects'; +import {DomainProps} from '../../components/list/ContactRow'; export const incomingData = ( @@ -92,6 +93,7 @@ export const incomingData = name?: string; email?: string; destinationTag?: number; + domain?: DomainProps; }, ): Effect> => async dispatch => { @@ -455,6 +457,7 @@ const goToConfirm = chain: string; destinationTag?: number; network?: Network; + domain?: DomainProps; }; amount: number; wallet?: Wallet; @@ -568,6 +571,7 @@ export const goToAmount = chain: string; network?: Network; destinationTag?: number; + domain?: DomainProps; }; wallet?: Wallet; opts?: { @@ -1334,6 +1338,7 @@ const handlePlainAddress = name?: string; email?: string; destinationTag?: number; + domain?: DomainProps; }, ): Effect => dispatch => { @@ -1350,6 +1355,7 @@ const handlePlainAddress = address, network, destinationTag: opts?.destinationTag, + domain: opts?.domain, }; dispatch(goToAmount({coin, chain, recipient, wallet: opts?.wallet})); }; diff --git a/src/store/wallet/wallet.models.ts b/src/store/wallet/wallet.models.ts index f451fc4b2..eee14f748 100644 --- a/src/store/wallet/wallet.models.ts +++ b/src/store/wallet/wallet.models.ts @@ -5,6 +5,7 @@ import {RootState} from '../index'; import {Invoice} from '../shop/shop.models'; import {Network} from '../../constants'; import {FeeLevels} from './effects/fee/fee'; +import {DomainProps} from '../../components/list/ContactRow'; export interface KeyMethods { _checkCoin?: Function; @@ -191,6 +192,7 @@ export interface Recipient { amount?: number; destinationTag?: number; chain?: string; + domain?: DomainProps; } export interface CustomTransactionData { @@ -370,6 +372,7 @@ export interface TxDetailsSendingTo { recipientAltAmountStr?: string; recipientCoin?: string; recipientChain?: string; + recipientDomain?: DomainProps; } export interface TxDetailsSendingFrom { From f2a0176404509979187c754c76edda1fd87200e6 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 17 Apr 2023 17:56:24 +0200 Subject: [PATCH 6/9] DomainType updates --- .../tabs/contacts/screens/ContactsAdd.tsx | 31 +++++++++++-------- .../tabs/contacts/screens/ContactsDetails.tsx | 6 ++-- src/store/contact/contact.types.ts | 1 - 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index acbfeb3e4..cd5913489 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -82,6 +82,7 @@ import { } from '../../../../store/moralis/moralis.effects'; import ENSDomainIcon from '../../../../components/avatar/ENSDomainIcon'; import UnstoppableDomainIcon from '../../../../components/avatar/UnstoppableDomainIcon'; +import _ from 'lodash'; const InputContainer = styled.View<{hideInput?: boolean}>` display: ${({hideInput}) => (!hideInput ? 'flex' : 'none')}; @@ -170,7 +171,10 @@ const schema = yup.object().shape({ email: yup.string().email().trim(), destinationTag: yup.number(), address: yup.string().required(), - domain: yup.string(), + domain: yup.object().shape({ + domainName: yup.string(), + domainType: yup.string(), + }), }); const SearchImageContainer = styled.View` @@ -329,8 +333,10 @@ const ContactsAdd = ({ setCoinValue(coin); setNetworkValue(network); setChainValue(chain); - setValidDomain(true); - setDomainValue(domain); + if (!_.values(domain).every(_.isEmpty)) { + setValidDomain(true); + setDomainValue(domain); + } _setSelectedCurrency(coin); @@ -466,7 +472,7 @@ const ContactsAdd = ({ return; } - if (!validDomain && domainValue) { + if (!validDomain && !_.values(domainValue).every(_.isEmpty)) { setError('domain', { type: 'manual', message: t('Invalid domain'), @@ -623,17 +629,16 @@ const ContactsAdd = ({ }); }; - const fetchENSDomainByAddress = async () => { + const fetchENSDomainByAddress = async (address: string) => { try { - const domainName = await dispatch( - getENSDomainByAddress({address: addressValue}), - ); + const domainName = await dispatch(getENSDomainByAddress({address})); if (domainName) { - setValue('domain', domainName); - setDomainValue({ + const _domain: DomainProps = { domainName, domainType: 'ENSDomain', - }); + }; + setValue('domain', _domain); + setDomainValue(_domain); setValidDomain(true); } } catch (err) { @@ -656,8 +661,8 @@ const ContactsAdd = ({ setValue('chain', contact.chain!); setValue('destinationTag', contact.tag || contact.destinationTag); setValue('domain', contact.domain); - if (context === 'edit' && !domainValue) { - fetchENSDomainByAddress(); + if (context === 'edit' && _.values(contact.domain).every(_.isEmpty)) { + fetchENSDomainByAddress(contact.address!); } } }, [contact]); diff --git a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx index 83e637fa2..06084876f 100644 --- a/src/navigation/tabs/contacts/screens/ContactsDetails.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsDetails.tsx @@ -324,12 +324,14 @@ const ContactsDetails = ({ - {contact.domain ? ( + {contact.domain?.domainName ? ( <>
{t('Domain')} - {contact.domain} + + {contact.domain.domainName} + ) : null} diff --git a/src/store/contact/contact.types.ts b/src/store/contact/contact.types.ts index 82a153e8f..a98e57176 100644 --- a/src/store/contact/contact.types.ts +++ b/src/store/contact/contact.types.ts @@ -24,7 +24,6 @@ interface DeleteContact { coin: string; network: string; chain?: string; - domain?: string; } interface MigrateContacts { From c10c2ae3f6389c11b8e83f190d7764f90dec9ffd Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 17 Apr 2023 19:59:35 +0200 Subject: [PATCH 7/9] icons updates --- src/components/avatar/ENSDomainIcon.tsx | 74 ++++++++++++++++--- .../avatar/UnstoppableDomainIcon.tsx | 45 +++++++---- .../wallet/components/ShareAddressModal.tsx | 6 +- src/navigation/wallet/screens/send/SendTo.tsx | 5 +- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/components/avatar/ENSDomainIcon.tsx b/src/components/avatar/ENSDomainIcon.tsx index 7f1081a4e..9387fb094 100644 --- a/src/components/avatar/ENSDomainIcon.tsx +++ b/src/components/avatar/ENSDomainIcon.tsx @@ -1,22 +1,46 @@ import React from 'react'; -import Svg, {Defs, LinearGradient, Path, Stop} from 'react-native-svg'; +import Svg, {Circle, Defs, LinearGradient, Path, Stop} from 'react-native-svg'; interface ENSDomainIconProps { size?: number; + showBackground?: boolean; } const ENSDomainIcon: React.FC = props => { - let {size = 20} = props; + const {size = 20, showBackground = true} = props; + const viewBox = showBackground ? '0 0 20 20' : '0 0 18 21'; return ( - + + + + + + + + + + + @@ -24,10 +48,36 @@ const ENSDomainIcon: React.FC = props => { - + {showBackground ? ( + <> + + + + + + + + ) : ( + + )} ); }; diff --git a/src/components/avatar/UnstoppableDomainIcon.tsx b/src/components/avatar/UnstoppableDomainIcon.tsx index 40f45cc82..4969a10fc 100644 --- a/src/components/avatar/UnstoppableDomainIcon.tsx +++ b/src/components/avatar/UnstoppableDomainIcon.tsx @@ -1,26 +1,41 @@ import React from 'react'; -import * as Svg from 'react-native-svg'; - +import Svg, {Circle, Path} from 'react-native-svg'; interface UnstoppableDomainIconProps { size?: number; + showBackground?: boolean; } const UnstoppableDomainIcon: React.FC = props => { - const {size = 20} = props; + const {size = 20, showBackground = true} = props; return ( - <> - - - - - + + {showBackground ? ( + <> + + + + + ) : ( + <> + + + + )} + ); }; diff --git a/src/navigation/wallet/components/ShareAddressModal.tsx b/src/navigation/wallet/components/ShareAddressModal.tsx index a13c59e97..84bb65a46 100644 --- a/src/navigation/wallet/components/ShareAddressModal.tsx +++ b/src/navigation/wallet/components/ShareAddressModal.tsx @@ -158,12 +158,12 @@ const ShareAddressModal = ({ shareAddress(domain.domainName)}> {domain.domainType === 'ENSDomain' ? ( - + ) : ( - + )} - {domain} + {domain.domainName} { useEffect(() => { return navigation.addListener('blur', () => - setTimeout(() => setSearchInput(''), 300), + setTimeout(() => { + setSearchInput(''); + setDomain(undefined); + }, 300), ); }, [navigation]); From 004948d4a8dd90b30a937fc3864ce417e81a94c4 Mon Sep 17 00:00:00 2001 From: juan Date: Mon, 17 Apr 2023 20:33:42 +0200 Subject: [PATCH 8/9] wallet opts --- .../tabs/contacts/screens/ContactsAdd.tsx | 4 +- .../wallet/screens/WalletDetails.tsx | 56 ++++++++++--------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index cd5913489..fc771ece8 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -641,9 +641,7 @@ const ContactsAdd = ({ setDomainValue(_domain); setValidDomain(true); } - } catch (err) { - console.error(err); - } + } catch (e) {} }; useEffect(() => { diff --git a/src/navigation/wallet/screens/WalletDetails.tsx b/src/navigation/wallet/screens/WalletDetails.tsx index 9398637a3..3623f2c7f 100644 --- a/src/navigation/wallet/screens/WalletDetails.tsx +++ b/src/navigation/wallet/screens/WalletDetails.tsx @@ -362,30 +362,34 @@ const WalletDetails: React.FC = ({route}) => { const ShareAddress = async () => { try { await sleep(1000); - const address = (await dispatch( - createWalletAddress({wallet: fullWalletObj, newAddress: false}), - )) as string; - - Share.share( - { - message: address, - title: t('Share Address'), - }, - { - dialogTitle: t('Share Address'), - subject: t('Share Address'), - excludedActivityTypes: [ - 'print', - 'addToReadingList', - 'markupAsPDF', - 'openInIbooks', - 'postToFacebook', - 'postToTwitter', - 'saveToCameraRoll', - 'sharePlay', - ], - }, - ); + if (showShareAddressIcon) { + setShowShareAddressModal(true); + } else { + const address = (await dispatch( + createWalletAddress({wallet: fullWalletObj, newAddress: false}), + )) as string; + + Share.share( + { + message: address, + title: t('Share Address'), + }, + { + dialogTitle: t('Share Address'), + subject: t('Share Address'), + excludedActivityTypes: [ + 'print', + 'addToReadingList', + 'markupAsPDF', + 'openInIbooks', + 'postToFacebook', + 'postToTwitter', + 'saveToCameraRoll', + 'sharePlay', + ], + }, + ); + } } catch (e) {} }; @@ -508,9 +512,7 @@ const WalletDetails: React.FC = ({route}) => { if (domainName) { setDomain({domainName, domainType: 'ENSDomain'}); } - } catch (err) { - console.error(err); - } + } catch (e) {} }, 300), [], ); From 8cf2c516c7935fb6e3c02be190d360e7f7d08f6b Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 22 Aug 2023 16:26:57 +0200 Subject: [PATCH 9/9] master rebase --- src/navigation/tabs/contacts/screens/ContactsAdd.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx index fc771ece8..42fe14b1c 100644 --- a/src/navigation/tabs/contacts/screens/ContactsAdd.tsx +++ b/src/navigation/tabs/contacts/screens/ContactsAdd.tsx @@ -108,7 +108,7 @@ const AddressBadge = styled.View` const DomainBadge = styled.View` position: absolute; - left: 5px; + left: 12px; top: 52%; z-index: 1; `; @@ -479,7 +479,7 @@ const ContactsAdd = ({ }); return; } - + if (addressValue && domainValue) { contact.address = addressValue; contact.domain = domainValue; @@ -721,12 +721,15 @@ const ContactsAdd = ({ <> { + setValidDomain(false); + setDomainValue(undefined); onChange(newValue); processAddressOrDomain({address: newValue}); }} @@ -781,7 +784,7 @@ const ContactsAdd = ({ label={t('DOMAIN')} value={value?.domainName} style={{ - paddingLeft: 30, + paddingLeft: 40, }} /> )}