Changeset View
Changeset View
Standalone View
Standalone View
cashtab/src/components/Alias/Alias.js
import React, { useState, useEffect } from 'react'; | import React, { useState, useEffect } from 'react'; | ||||
import styled from 'styled-components'; | import styled from 'styled-components'; | ||||
import { WalletContext } from 'utils/context'; | import { WalletContext } from 'utils/context'; | ||||
import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
import WalletLabel from 'components/Common/WalletLabel.js'; | import WalletLabel from 'components/Common/WalletLabel.js'; | ||||
import { | import { | ||||
ZeroBalanceHeader, | ZeroBalanceHeader, | ||||
SidePaddingCtn, | SidePaddingCtn, | ||||
WalletInfoCtn, | WalletInfoCtn, | ||||
} from 'components/Common/Atoms'; | } from 'components/Common/Atoms'; | ||||
import { DestinationAddressSingle } from 'components/Common/EnhancedInputs'; | import { | ||||
import { AntdFormWrapper } from 'components/Common/EnhancedInputs'; | AntdFormWrapper, | ||||
AliasInput, | |||||
AliasAddressInput, | |||||
CashtabCheckbox, | |||||
} from 'components/Common/EnhancedInputs'; | |||||
import { Form, Modal } from 'antd'; | import { Form, Modal } from 'antd'; | ||||
import { SmartButton } from 'components/Common/PrimaryButton'; | import { SmartButton } from 'components/Common/PrimaryButton'; | ||||
import BalanceHeader from 'components/Common/BalanceHeader'; | import BalanceHeader from 'components/Common/BalanceHeader'; | ||||
import BalanceHeaderFiat from 'components/Common/BalanceHeaderFiat'; | import BalanceHeaderFiat from 'components/Common/BalanceHeaderFiat'; | ||||
import { Row, Col } from 'antd'; | import { Row, Col } from 'antd'; | ||||
import { UserOutlined } from '@ant-design/icons'; | import { UserOutlined } from '@ant-design/icons'; | ||||
import { | import { | ||||
getWalletState, | getWalletState, | ||||
fromSatoshisToXec, | fromSatoshisToXec, | ||||
getAliasRegistrationFee, | getAliasRegistrationFee, | ||||
convertToEcashPrefix, | convertToEcashPrefix, | ||||
getAliasByteSize, | getAliasByteSize, | ||||
} from 'utils/cashMethods'; | } from 'utils/cashMethods'; | ||||
import { isAliasAvailable, isAddressRegistered } from 'utils/chronik'; | import { isAliasAvailable, isAddressRegistered } from 'utils/chronik'; | ||||
import { currency } from 'components/Common/Ticker.js'; | import { currency } from 'components/Common/Ticker.js'; | ||||
import { registerNewAlias } from 'utils/transactions'; | import { registerNewAlias } from 'utils/transactions'; | ||||
import { | import { | ||||
errorNotification, | errorNotification, | ||||
registerAliasNotification, | registerAliasNotification, | ||||
} from 'components/Common/Notifications'; | } from 'components/Common/Notifications'; | ||||
import { isAliasFormat, isValidAliasString } from 'utils/validation'; | import { isAliasFormat, isValidAliasString } from 'utils/validation'; | ||||
import { getPendingAliases } from 'utils/aliasUtils'; | import { getPendingAliases } from 'utils/aliasUtils'; | ||||
import cashaddr from 'ecashaddrjs'; | |||||
export const CheckboxContainer = styled.div` | |||||
text-align: left; | |||||
margin-bottom: 12px; | |||||
`; | |||||
export const NamespaceCtn = styled.div` | export const NamespaceCtn = styled.div` | ||||
width: 100%; | width: 100%; | ||||
margin-top: 50px; | margin-top: 50px; | ||||
margin-bottom: 20px; | margin-bottom: 20px; | ||||
overflow-wrap: break-word; | overflow-wrap: break-word; | ||||
h2 { | h2 { | ||||
color: ${props => props.theme.contrast}; | color: ${props => props.theme.contrast}; | ||||
Show All 25 Lines | const { | ||||
isAliasServerOnline, | isAliasServerOnline, | ||||
} = ContextValue; | } = ContextValue; | ||||
const walletState = getWalletState(wallet); | const walletState = getWalletState(wallet); | ||||
const { balances } = walletState; | const { balances } = walletState; | ||||
const [formData, setFormData] = useState({ | const [formData, setFormData] = useState({ | ||||
aliasName: '', | aliasName: '', | ||||
aliasAddress: '', | aliasAddress: '', | ||||
}); | }); | ||||
const [useThisAddressChecked, setUseThisAddressChecked] = useState(false); | |||||
const [isValidAliasInput, setIsValidAliasInput] = useState(false); // tracks whether to activate the registration button | const [isValidAliasInput, setIsValidAliasInput] = useState(false); // tracks whether to activate the registration button | ||||
const [isValidAliasAddressInput, setIsValidAliasAddressInput] = | |||||
useState(false); // tracks whether to activate the registration button | |||||
const [aliasValidationError, setAliasValidationError] = useState(false); | const [aliasValidationError, setAliasValidationError] = useState(false); | ||||
const [aliasAddressValidationError, setAliasAddressValidationError] = | |||||
useState(false); | |||||
const [activeWalletAliases, setActiveWalletAliases] = useState([]); // stores the list of aliases registered to this active wallet | const [activeWalletAliases, setActiveWalletAliases] = useState([]); // stores the list of aliases registered to this active wallet | ||||
const [aliasLength, setAliasLength] = useState(false); // real time tracking of alias char length | const [aliasLength, setAliasLength] = useState(false); // real time tracking of alias char length | ||||
const [aliasFee, setAliasFee] = useState(false); // real time tracking of alias registration fee | const [aliasFee, setAliasFee] = useState(false); // real time tracking of alias registration fee | ||||
// Show a confirmation modal on alias registrations | // Show a confirmation modal on alias registrations | ||||
const [isModalVisible, setIsModalVisible] = useState(false); | const [isModalVisible, setIsModalVisible] = useState(false); | ||||
useEffect(() => { | useEffect(() => { | ||||
passLoadingStatus(false); | passLoadingStatus(false); | ||||
}, [balances.totalBalance]); | }, [balances.totalBalance]); | ||||
useEffect(async () => { | useEffect(async () => { | ||||
// only run this useEffect block if wallet or cashtabCache is defined | // only run this useEffect block if wallet or cashtabCache is defined | ||||
if ( | if ( | ||||
!wallet || | !wallet || | ||||
typeof wallet === 'undefined' || | typeof wallet === 'undefined' || | ||||
!cashtabCache || | !cashtabCache || | ||||
typeof cashtabCache === 'undefined' | typeof cashtabCache === 'undefined' | ||||
) { | ) { | ||||
return; | return; | ||||
} | } | ||||
passLoadingStatus(true); | passLoadingStatus(true); | ||||
// Set address of active wallet to default alias registration address | // Default to registering the user's active wallet | ||||
// Use formdata approach as we will later add a form field for aliasAddress | // Must be called in this useEffect to ensure that wallet is loaded | ||||
setFormData(formData => ({ | // Call with this function to ensure that checkbox state and checkbox are updated | ||||
...formData, | handleDefaultAddressCheckboxChange({ target: { checked: true } }); | ||||
aliasAddress: wallet.Path1899.cashAddress, | |||||
})); | |||||
// check whether the address is attached to an onchain alias on page load | // check whether the address is attached to an onchain alias on page load | ||||
const walletHasAlias = isAddressRegistered( | const walletHasAlias = isAddressRegistered( | ||||
wallet, | wallet, | ||||
cashtabCache.aliasCache, | cashtabCache.aliasCache, | ||||
); | ); | ||||
// retrieve aliases for this active wallet from cache for rendering on the frontend | // retrieve aliases for this active wallet from cache for rendering on the frontend | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | const handleAliasNameInput = e => { | ||||
} | } | ||||
setFormData(p => ({ | setFormData(p => ({ | ||||
...p, | ...p, | ||||
[name]: value, | [name]: value, | ||||
})); | })); | ||||
}; | }; | ||||
const handleDefaultAddressCheckboxChange = e => { | |||||
/* handleDefaultAddressCheckboxChange | |||||
* | |||||
* Function to handle user action of checking or unchecking the | |||||
* checkbox on this page labeled 'Register active wallet address' | |||||
* | |||||
* May be called programmatically by mocking the usual js event | |||||
* of a user checking the box | |||||
* | |||||
* If the box is checked, set formData for aliasAddress to the active wallet's address | |||||
* If the box is unchecked, clear formData for aliasAddress | |||||
*/ | |||||
const checked = e.target.checked; | |||||
setUseThisAddressChecked(checked); | |||||
if (checked) { | |||||
// Set address of active wallet to default alias registration address | |||||
handleAliasAddressInput({ | |||||
target: { | |||||
name: 'aliasAddress', | |||||
value: wallet.Path1899.cashAddress, | |||||
}, | |||||
}); | |||||
} else { | |||||
// Clear the form if the user unchecks | |||||
handleAliasAddressInput({ | |||||
target: { | |||||
name: 'aliasAddress', | |||||
value: '', | |||||
}, | |||||
}); | |||||
} | |||||
}; | |||||
const handleAliasAddressInput = e => { | |||||
/* handleAliasAddressInput | |||||
* | |||||
* Function called to handle any changes to the aliasAddress input form | |||||
* | |||||
* May be called programmatically by mocking the usual js event | |||||
* of a user updating the addressName input field | |||||
*/ | |||||
let { name, value } = e.target; | |||||
// remove any whitespaces | |||||
value = value.trim(); | |||||
// Validate | |||||
let decoded; | |||||
let isValidAddress = false; | |||||
try { | |||||
decoded = cashaddr.decode(value, true); | |||||
const { hash } = decoded; | |||||
// We only support 20-byte payloads | |||||
isValidAddress = hash.length === 40; | |||||
} catch (err) { | |||||
// Invalid cashaddress | |||||
// Log to console for user support | |||||
console.log(`Invalid address`, err); | |||||
} | |||||
if (isValidAddress) { | |||||
setIsValidAliasAddressInput(true); | |||||
setAliasAddressValidationError(false); | |||||
} else { | |||||
setAliasAddressValidationError( | |||||
'Invalid alias registration address.', | |||||
); | |||||
setIsValidAliasAddressInput(false); | |||||
} | |||||
setFormData(p => ({ | |||||
...p, | |||||
[name]: value, | |||||
})); | |||||
}; | |||||
function handleAliasRegistrationError(errorObj) { | function handleAliasRegistrationError(errorObj) { | ||||
// Set loading to false here as well, as balance may not change depending on where error occured in try loop | // Set loading to false here as well, as balance may not change depending on where error occured in try loop | ||||
passLoadingStatus(false); | passLoadingStatus(false); | ||||
let message; | let message; | ||||
if ( | if ( | ||||
errorObj.error && | errorObj.error && | ||||
errorObj.error.includes( | errorObj.error.includes( | ||||
'too-long-mempool-chain, too many unconfirmed ancestors [limit: 50] (code 64)', | 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 50] (code 64)', | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | return ( | ||||
<SidePaddingCtn> | <SidePaddingCtn> | ||||
<AntdFormWrapper> | <AntdFormWrapper> | ||||
<Form | <Form | ||||
style={{ | style={{ | ||||
width: 'auto', | width: 'auto', | ||||
}} | }} | ||||
> | > | ||||
<Form.Item> | <Form.Item> | ||||
<DestinationAddressSingle | <AliasInput | ||||
validateStatus={ | validateStatus={ | ||||
isValidAliasInput ? '' : 'error' | isValidAliasInput ? '' : 'error' | ||||
} | } | ||||
help={ | help={ | ||||
aliasValidationError | aliasValidationError | ||||
? aliasValidationError | ? aliasValidationError | ||||
: '' | : '' | ||||
} | } | ||||
inputProps={{ | inputProps={{ | ||||
addonAfter: ' . xec', | addonAfter: ' . xec', | ||||
placeholder: | placeholder: | ||||
'Enter a desired alias', | 'Enter a desired alias', | ||||
name: 'aliasName', | name: 'aliasName', | ||||
onChange: e => | onChange: e => | ||||
handleAliasNameInput(e), | handleAliasNameInput(e), | ||||
required: true, | required: true, | ||||
}} | }} | ||||
/> | /> | ||||
<CheckboxContainer> | |||||
<CashtabCheckbox | |||||
checked={useThisAddressChecked} | |||||
onChange={ | |||||
handleDefaultAddressCheckboxChange | |||||
} | |||||
> | |||||
Register active wallet address | |||||
</CashtabCheckbox> | |||||
</CheckboxContainer> | |||||
{!useThisAddressChecked && ( | |||||
<AliasAddressInput | |||||
validateStatus={ | |||||
isValidAliasAddressInput | |||||
? '' | |||||
: 'error' | |||||
} | |||||
help={ | |||||
aliasAddressValidationError | |||||
? aliasAddressValidationError | |||||
: '' | |||||
} | |||||
inputProps={{ | |||||
placeholder: | |||||
'Enter address for this alias', | |||||
value: formData.aliasAddress, | |||||
disabled: | |||||
useThisAddressChecked, | |||||
name: 'aliasAddress', | |||||
onChange: e => | |||||
handleAliasAddressInput( | |||||
e, | |||||
), | |||||
required: true, | |||||
}} | |||||
/> | |||||
)} | |||||
{aliasLength && | {aliasLength && | ||||
aliasFee && | aliasFee && | ||||
`Registration fee for this ${aliasLength} byte Alias is ${fromSatoshisToXec( | `Registration fee for this ${aliasLength} byte Alias is ${fromSatoshisToXec( | ||||
aliasFee, | aliasFee, | ||||
)} XEC`} | )} XEC`} | ||||
</Form.Item> | </Form.Item> | ||||
<Form.Item> | <Form.Item> | ||||
<SmartButton | <SmartButton | ||||
disabled={!isValidAliasInput} | disabled={ | ||||
!isValidAliasInput || | |||||
!isValidAliasAddressInput | |||||
} | |||||
onClick={() => | onClick={() => | ||||
setIsModalVisible(true) | setIsModalVisible(true) | ||||
} | } | ||||
> | > | ||||
Register Alias | Register Alias | ||||
</SmartButton> | </SmartButton> | ||||
</Form.Item> | </Form.Item> | ||||
</Form> | </Form> | ||||
▲ Show 20 Lines • Show All 44 Lines • Show Last 20 Lines |