react-redux#batch JavaScript Examples
The following examples show how to use
react-redux#batch.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Settings.js From os-league-tools with MIT License | 6 votes |
function ThemeSelectCard({ label, theme }) {
const activeTheme = useSelector(state => state.settings.theme);
const dispatch = useDispatch();
const selected = activeTheme === theme;
const selectedStyle = selected ? 'border-x-2 border-accent bg-secondary-alt' : 'cursor-pointer bg-hover';
return (
<div
className={`rounded p-2 w-[100px] min-w-[100px] ${selectedStyle}`}
onClick={() =>
batch(() => {
dispatch(update({ field: 'theme', value: theme }));
dispatch(update({ field: 'mode', value: theme.split('-')[1] }));
})
}
>
<img className='h-9 w-9 mx-auto' src={images[`icon-${theme}.png`]} alt='' />
<span className={`text-center heading-block-sm small-caps force-wrap ${selected && 'text-accent'}`}>
{label}
</span>
</div>
);
}
Example #2
Source File: common.js From os-league-tools with MIT License | 6 votes |
export default function loadNewState(dispatch, newState) {
batch(() => {
dispatch(loadTasksState({ forceOverwrite: true, newState: newState.tasks || {} }));
dispatch(loadSettingsState({ forceOverwrite: true, newState: newState.settings || {} }));
dispatch(loadUnlocksState({ forceOverwrite: true, newState: newState.unlocks || {} }));
dispatch(loadCharacterState({ forceOverwrite: true, newState: newState.character || {} }));
dispatch(loadFragmentState({ forceOverwrite: true, newState: newState.fragments || {} }));
});
}
Example #3
Source File: actionCreator.js From techno-broadlink with MIT License | 6 votes |
requestDevices = () => {
return async dispatch => {
dispatch(setIsBusy(true));
await dispatch({
type: GET_DEVICES,
payload: discoverDevices(),
})
.then(data => {
dispatch(setIsBusy(false));
if (data.value.length > 0) {
batch(() => {
dispatch(
setShowAlert(
'success',
`Discovered ${data.value.length} devices`,
true
)
);
});
// default to first device
dispatch(setSelectedDevice(data.value[0]));
} else {
dispatch(setShowAlert('error', `Did not discover any devices`, true));
}
})
.catch(() => {
dispatch(setIsBusy(false));
});
};
}
Example #4
Source File: actionCreator.js From techno-broadlink with MIT License | 6 votes |
requestLearnCommand = (ipAddress, commandName) => {
return async dispatch => {
dispatch(setIsBusy(true));
// unfortunately there's a delay
setTimeout(() => {
dispatch(
setShowAlert('info', 'Please press a button on your remote', true)
);
}, 4800);
await dispatch({
type: LEARN_COMMAND,
payload: learnCommand(ipAddress, commandName),
})
.then(data => {
batch(() => {
dispatch(setIsBusy(false));
dispatch(setSelectedDevice(data.value));
dispatch(setLearnOpen(false));
dispatch(setLearnInput(''));
// have to time with info message :|
setTimeout(() => {
dispatch(setShowAlert('success', `Learned command!`, true));
}, 1000);
});
})
.catch(() => {
dispatch(setIsBusy(false));
dispatch(setLearnOpen(false));
dispatch(setLearnInput(''));
dispatch(setShowAlert('error', `Error learning command`, true));
});
};
}
Example #5
Source File: actionCreator.js From techno-broadlink with MIT License | 6 votes |
requestDeleteCommand = (ipAddress, commandId) => {
return async dispatch => {
dispatch(setIsBusy(true));
await dispatch({
type: DELETE_COMMAND,
payload: deleteCommand(ipAddress, commandId),
})
.then(data => {
batch(() => {
dispatch(setIsBusy(false));
dispatch(setSelectedDevice(data.value));
dispatch(setShowAlert('success', `Deleted command!`, true));
dispatch(setDeleteOpen(false));
});
})
.catch(() => {
dispatch(setIsBusy(true));
dispatch(setShowAlert('error', `Error deleting command`, true));
});
};
}
Example #6
Source File: actionCreator.js From techno-broadlink with MIT License | 6 votes |
deboucedRequestRenameDevice = debounce(
async (ipAddress, deviceName, dispatch) => {
dispatch(setIsBusy(true));
await dispatch({
type: RENAME_DEVICE,
payload: renameDevice(ipAddress, deviceName),
}).then(data => {
batch(() => {
dispatch(setIsBusy(false));
});
});
},
1500
)
Example #7
Source File: actions.js From chromeless with Mozilla Public License 2.0 | 6 votes |
updateActiveQuery = (activeQuery) => (dispatch, getState) => Promise.resolve()
.then(async () => {
batch(() => {
dispatch({
type: INSTALLED_SET_IS_SEARCHING,
isSearching: true,
});
dispatch({
type: INSTALLED_UPDATE_ACTIVE_QUERY,
activeQuery,
});
});
const { apps, sortedAppIds } = getState().appManagement;
let newSortedAppIds = null;
if (activeQuery) {
const worker = new Worker();
const filterApps = Comlink.wrap(worker);
newSortedAppIds = await filterApps(apps, sortedAppIds, activeQuery);
worker.terminate();
}
if (getState().installed.query !== activeQuery) return;
batch(() => {
dispatch({
type: INSTALLED_SET_IS_SEARCHING,
isSearching: false,
});
dispatch({
type: INSTALLED_UPDATE_SORTED_APP_IDS,
sortedAppIds: newSortedAppIds,
});
});
})
.catch((err) => {
// eslint-disable-next-line no-console
console.log(err);
})
Example #8
Source File: ManageCharactersModal.js From os-league-tools with MIT License | 5 votes |
export default function ManageCharactersModal({ isOpen, setIsOpen }) {
const characterState = useSelector(state => state.character);
const [characterText, setCharacterText] = useState(characterState.username || '');
const dispatch = useDispatch();
const updateAndFetchHiscores = () => {
batch(() => {
dispatch(updateUsername(characterText));
dispatch(
fetchHiscores({
...characterState,
username: characterText,
})
);
});
};
const prompt = characterState.username
? 'If your display name has changed, update it below.'
: "Enter your character's display name to automatically load your stats from hiscores:";
return (
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
onClose={() => {}}
className='w-[26rem] shadow shadow-primary rounded-md bg-primary-alt'
>
<Modal.Header className='text-center small-caps tracking-wide text-xl text-accent font-semibold'>
Manage character
</Modal.Header>
<Modal.Body className='text-primary text-sm'>
<div className='m-2 mt-1'>{prompt}</div>
<div className='m-2 mt-1 flex justify-around'>
<input
className='input-primary text-sm form-input w-40 ml-2'
onChange={e => {
setCharacterText(e.target.value);
}}
placeholder={PLACEHOLDER_USERNAMES[Math.floor(Math.random() * PLACEHOLDER_USERNAMES.length)]}
value={characterText}
onKeyPress={e => e.key === 'Enter' && updateAndFetchHiscores()}
type='text'
/>
<button className='w-40 button-filled' type='button' onClick={updateAndFetchHiscores}>
{characterState.hiscoresCache.loading ? (
<span>
<Spinner />
</span>
) : (
'Submit'
)}
</button>
</div>
<div className='my-1 flex justify-around'>
{characterState.hiscoresCache.error && (
<p className='text-error text-sm'>{characterState.hiscoresCache.error}</p>
)}
</div>
</Modal.Body>
</Modal>
);
}
Example #9
Source File: index.js From chromeless with Mozilla Public License 2.0 | 5 votes |
loadListeners = (store) => {
window.ipcRenderer.on('log', (e, message) => {
// eslint-disable-next-line
if (message) console.log(message);
});
window.ipcRenderer.on('clean-app-management', () => {
store.dispatch(cleanAppManagement());
});
window.ipcRenderer.on('set-app', (e, id, app) => {
store.dispatch(setApp(id, app));
});
window.ipcRenderer.on('set-app-batch', (e, apps) => {
batch(() => {
apps.forEach((app) => {
store.dispatch(setApp(app.id, app));
});
});
});
window.ipcRenderer.on('remove-app', (e, id) => store.dispatch(removeApp(id)));
window.ipcRenderer.on('set-preference', (e, name, value) => {
store.dispatch(setPreference(name, value));
});
window.ipcRenderer.on('set-preferences', (e, newState) => {
store.dispatch(setPreferences(newState));
});
window.ipcRenderer.on('set-system-preference', (e, name, value) => {
store.dispatch(setSystemPreference(name, value));
});
window.ipcRenderer.on('go-to-preferences', () => store.dispatch(changeRoute(ROUTE_PREFERENCES)));
window.ipcRenderer.on('open-dialog-about', () => {
store.dispatch(openDialogAbout());
});
window.ipcRenderer.on('native-theme-updated', () => {
store.dispatch(updateShouldUseDarkColors(getShouldUseDarkColors()));
});
window.ipcRenderer.on('update-updater', (e, updaterObj) => {
store.dispatch(updateUpdater(updaterObj));
});
window.ipcRenderer.on('update-installation-progress', (e, progress) => {
store.dispatch(updateInstallationProgress(progress));
});
window.ipcRenderer.on('set-scanning-for-installed', (e, scanning) => {
store.dispatch(setScanningForInstalled(scanning));
});
window.ipcRenderer.on('set-is-full-screen', (e, isFullScreen) => {
store.dispatch(updateIsFullScreen(isFullScreen));
});
window.ipcRenderer.on('set-is-maximized', (e, isMaximized) => {
store.dispatch(updateIsMaximized(isMaximized));
});
}
Example #10
Source File: Home.js From web-wallet with Apache License 2.0 | 4 votes |
function Home () {
const dispatch = useDispatch();
const [ mobileMenuOpen, setMobileMenuOpen ] = useState(false);
const depositModalState = useSelector(selectModalState('depositModal'));
const transferModalState = useSelector(selectModalState('transferModal'));
const exitModalState = useSelector(selectModalState('exitModal'));
const mergeModalState = useSelector(selectModalState('mergeModal'));
const ledgerConnectModalState = useSelector(selectModalState('ledgerConnectModal'));
const walletMethod = useSelector(selectWalletMethod());
const transactions = useSelector(selectChildchainTransactions, isEqual);
const ethDeposits = useSelector(selectEthDeposits, isEqual);
const erc20Deposits = useSelector(selectErc20Deposits, isEqual);
const transactedTokens = useMemo(() => {
const depositedTokens = erc20Deposits.map(e => e.tokenInfo.currency);
if (ethDeposits.length !== 0) {
depositedTokens.push(eth);
}
const xputs = flatten(transactions
.filter(i => i.status !== 'Pending')
.map(i => [ ...i.inputs, ...i.outputs ])
);
const txTokens = xputs.map(i => i.currency);
return uniq([ ...txTokens, ...depositedTokens ]);
}, [ transactions ]);
useEffect(() => {
const body = document.getElementsByTagName('body')[0];
mobileMenuOpen
? body.style.overflow = 'hidden'
: body.style.overflow = 'auto';
}, [ mobileMenuOpen ]);
useEffect(() => {
for (const token of transactedTokens) {
dispatch(getExitQueue(token));
}
}, [ dispatch, transactedTokens ]);
// calls only on boot
useEffect(() => {
window.scrollTo(0, 0);
dispatch(fetchDeposits());
dispatch(fetchExits());
}, [ dispatch, transactedTokens ]);
useInterval(() => {
batch(() => {
// infura call
dispatch(fetchEthStats());
dispatch(checkPendingDepositStatus());
dispatch(checkPendingExitStatus());
// watcher only calls
dispatch(checkWatcherStatus());
dispatch(fetchBalances());
dispatch(fetchTransactions());
});
}, POLL_INTERVAL);
useInterval(() => {
dispatch(fetchFees());
dispatch(fetchGas());
}, POLL_INTERVAL * 10);
return (
<>
<DepositModal open={depositModalState} />
<TransferModal open={transferModalState} />
<ExitModal open={exitModalState} />
<MergeModal open={mergeModalState} />
<LedgerConnect
open={walletMethod === 'browser'
? ledgerConnectModalState
: false
}
/>
<div className={styles.Home}>
<div className={styles.sidebar}>
<img className={styles.logo} src={logo} alt='omg-network' />
<Status />
</div>
<div className={styles.main}>
<MobileHeader
mobileMenuOpen={mobileMenuOpen}
onHamburgerClick={() => setMobileMenuOpen(open => !open)}
/>
<MobileMenu mobileMenuOpen={mobileMenuOpen} />
<Account/>
<Transactions/>
</div>
</div>
</>
);
}
Example #11
Source File: AddressInput.jsx From one-wallet with Apache License 2.0 | 4 votes |
AddressInput = ({ setAddressCallback, currentWallet, addressValue, extraSelectOptions, disableManualInput, disabled, style, allowTemp, useHex }) => {
const dispatch = useDispatch()
const history = useHistory()
const state = useRef({ last: undefined, lastTime: Date.now() }).current
const [searchingAddress, setSearchingAddress] = useState(false)
const [searchValue, setSearchValue] = useState('')
const [showQrCodeScanner, setShowQrCodeScanner] = useState('')
const walletsMap = useSelector(state => state.wallet)
const wallets = Object.keys(walletsMap).map((k) => walletsMap[k])
const knownAddresses = useSelector(state =>
state.global.knownAddresses || {}
)
const network = useSelector(state => state.global.network)
const { isMobile } = useWindowDimensions()
const deleteKnownAddress = useCallback((address) => {
setAddressCallback({ value: '', label: '' })
dispatch(globalActions.deleteKnownAddress(address))
}, [dispatch])
const onSearchAddress = (value) => {
setSearchingAddress(true)
setSearchValue(value)
}
// Scanned value for address prefill will be ROOT_URL/to/{address}?d=xxx, we take address here for prefill.
const onScan = async (v) => {
setSearchingAddress(true)
if (!updateQRCodeState(v, state)) {
return
}
state.last = v
state.lastTime = Date.now()
const pattern = WalletConstants.qrcodePattern
let m = v.match(pattern)
if (!m) {
m = util.isValidBech32(v) && v.match(WalletConstants.oneAddressPattern)
if (!m) {
message.error('Unrecognizable code')
return
}
}
const maybeAddress = m[1]
const validAddress = util.safeNormalizedAddress(maybeAddress)
if (maybeAddress && validAddress) {
const oneAddress = util.safeOneAddress(validAddress)
const domainName = await api.blockchain.domain.reverseLookup({ address: validAddress })
onSelectAddress({
label: domainName || (useHex ? validAddress : oneAddress),
value: validAddress
})
setShowQrCodeScanner(false)
}
if (maybeAddress && !validAddress) {
message.error('Address not found')
setShowQrCodeScanner(false)
}
setSearchingAddress(false)
}
useWaitExecution(
async () => {
try {
const value = trim(searchValue)
const validAddress = util.safeNormalizedAddress(value)
if (validAddress) {
const domainName = await api.blockchain.domain.reverseLookup({ address: validAddress })
setAddressCallback({
value: validAddress,
domainName,
filterValue: searchValue,
selected: false
})
}
if (!isEmpty(value) && value.includes(`${ONEConstants.Domain.DEFAULT_PARENT_LABEL}.${ONEConstants.Domain.DEFAULT_TLD}`)) {
const resolvedAddress = await api.blockchain.domain.resolve({ name: value })
if (!util.isEmptyAddress(resolvedAddress)) {
setAddressCallback({
value: resolvedAddress,
domainName: value,
filterValue: searchValue,
selected: false
})
}
}
setSearchingAddress(false)
} catch (e) {
console.error(e)
setSearchingAddress(false)
}
},
true,
delayDomainOperationMillis,
[searchValue, setSearchingAddress, setAddressCallback]
)
const walletsAddresses = wallets.map((wallet) => wallet.address)
/**
* Determines if the input wallet wallet is for the current wallet. Applicable for existing wallet management.
*/
const notCurrentWallet = useCallback((inputWalletAddress) =>
!currentWallet || currentWallet?.address !== inputWalletAddress,
[currentWallet])
useEffect(() => {
const initKnownAddresses = async () => {
const existingKnownAddresses = Object.keys(knownAddresses)
.map((address) => knownAddresses[address])
const walletsNotInKnownAddresses = wallets.filter((wallet) =>
!existingKnownAddresses.find((knownAddress) =>
knownAddress.address === wallet.address && knownAddress.network === wallet.network && (!wallet.temp || allowTemp))
)
const knownAddressesWithoutDomain = existingKnownAddresses.filter((knownAddress) =>
!isEmpty(knownAddress.domain?.name)
)
const unlabelledWalletAddress = wallets.filter(w => existingKnownAddresses.find(a => !a.label && !a.domain?.name && a.address === w.address && (!w.temp || allowTemp)))
batch(() => {
// Init the known address entries for existing wallets.
walletsNotInKnownAddresses.forEach((wallet) => {
dispatch(globalActions.setKnownAddress({
label: wallet.name,
address: wallet.address,
network: wallet.network,
creationTime: wallet.effectiveTime,
numUsed: 0
}))
})
unlabelledWalletAddress.forEach((w) => {
dispatch(globalActions.setKnownAddress({
...knownAddresses[w],
label: w.name,
}))
})
})
// Batch these separately since these promise may take a while to resolve.
const domainWalletAddresses = await Promise.all(knownAddressesWithoutDomain.map(async (knownAddress) => {
const domainName = await api.blockchain.domain.reverseLookup({ address: knownAddress.address })
const nowInMillis = new Date().valueOf()
if (!isEmpty(domainName)) {
return {
...knownAddress,
domain: {
...knownAddress.domain,
name: domainName,
lookupTime: nowInMillis
}
}
}
return null
}))
batch(() => {
domainWalletAddresses.forEach(dwa => {
if (dwa) {
dispatch(globalActions.setKnownAddress({
...dwa
}))
}
})
})
}
initKnownAddresses()
}, [])
const onSelectAddress = useCallback((addressObject) => {
const validAddress = util.normalizedAddress(addressObject.value)
const nowInMillis = new Date().valueOf()
if (validAddress) {
const existingKnownAddress = knownAddresses[validAddress]
// console.log(addressObject)
setAddressCallback(addressObject.label
? {
...addressObject,
selected: true
}
: {
value: addressObject.value,
label: useHex ? addressObject.value : util.safeOneAddress(addressObject.value),
domainName: addressObject.domainName,
selected: true
})
dispatch(globalActions.setKnownAddress({
...existingKnownAddress,
label: existingKnownAddress?.label,
creationTime: existingKnownAddress?.creationTime || nowInMillis,
numUsed: (existingKnownAddress?.numUsed || 0) + 1,
network: network,
lastUsedTime: nowInMillis,
address: validAddress,
domain: {
...existingKnownAddress?.domain,
name: addressObject.domainName,
lookupTime: nowInMillis
}
}))
}
}, [knownAddresses, setAddressCallback])
const showSelectManualInputAddress = !disableManualInput && util.safeOneAddress(addressValue.value) &&
!wallets[util.safeNormalizedAddress(addressValue.value)] &&
!Object.keys(knownAddresses).includes(util.safeNormalizedAddress(addressValue.value))
const knownAddressesOptions = Object.keys(knownAddresses).map((address) => ({
address,
label: knownAddresses[address].label,
network: knownAddresses[address].network,
domain: knownAddresses[address].domain
}))
/**
* Since we are applying setAddressCallback on searching, the addressValue is set as we typing in valid address or domain name.
* When user click away (blur) from the Select box without clicking an option, the addressValue will be set incorrectly as
* we are performing extra logic in the onSelectAddress.
* Perform manual onSelectAddress when user click away from Select box or press keyboard without clicking a Select Option in search/filter result.
*/
const onEnterSelect = (e) => {
if (e.type === 'keydown' && e.keyCode === 13 || e.type === 'blur') {
!addressValue.selected && onSelectAddress({
...addressValue,
label: addressValue.domainName
})
}
}
/**
* Builds the Select Option component with given props.
* ONE address is only used for display, normalized address is used for any internal operations and keys.
* @param {*} address normalized address for the selection.
* @param {*} key key for the iterated rendering.
* @param {*} displayActionButton indicates if this option should display edit and delete button or not.
* @param {*} label of the rendered address option.
* @param {*} domainName of the displayed address.
* @param {*} filterValue value used to perform filter, this value should match the user input value.
* @returns Select Option component for the address.
*/
const buildSelectOption = ({
address,
key = address,
displayActionButton,
label,
domainName,
filterValue
}) => {
const oneAddress = util.safeOneAddress(address)
const addressDisplay = util.shouldShortenAddress({ label, isMobile }) ? util.ellipsisAddress(useHex ? address : oneAddress) : (useHex ? address : oneAddress)
const displayLabel = `${label ? `(${label})` : ''} ${addressDisplay}`
return (
<Select.Option key={addressDisplay} value={filterValue} style={{ padding: 0 }}>
<Row align='middle'>
<Col span={!displayActionButton ? 24 : 20}>
<Tooltip title={useHex ? address : oneAddress}>
<Button
block
type='text'
style={{ textAlign: 'left', height: '100%', padding: '5px' }}
onClick={() => {
onSelectAddress({ value: address, label: displayLabel, key, domainName })
}}
>
<Space direction={isMobile ? 'vertical' : 'horizontal'}>
{label ? `(${label})` : ''}
{addressDisplay}
</Space>
</Button>
</Tooltip>
</Col>
{displayActionButton &&
<Col span={4}>
<Row justify='space-between'>
<Button
key='edit'
type='text' style={{ textAlign: 'left', height: '50px', padding: 8 }} onClick={(e) => {
history.push(Paths.addressDetail(address))
e.stopPropagation()
return false
}}
>
<EditOutlined />
</Button>
<Button
key='delete'
type='text' style={{ textAlign: 'left', height: '50px', padding: 8, marginRight: 16 }} onClick={(e) => {
deleteKnownAddress(address)
e.stopPropagation()
return false
}}
>
<CloseOutlined style={{ color: 'red' }} />
</Button>
</Row>
</Col>}
</Row>
</Select.Option>
)
}
// Make sure there is no value set for Select input if no selection since we are using labelInValue, which a default value/label
// will cover the inner search input that will make the right-click to paste not available.
const selectInputValueProp = addressValue.value !== ''
? {
value: addressValue
}
: {}
return (
<>
<Space direction='vertical' style={{ width: '100%' }}>
<Select
suffixIcon={<ScanButton isMobile={isMobile} setShowQrCodeScanner={setShowQrCodeScanner} showQrCodeScanner={showQrCodeScanner} />}
placeholder={useHex ? '0x...' : 'one1......'}
labelInValue
style={{
width: isMobile ? '100%' : 500,
borderBottom: '1px dashed black',
fontSize: 16,
...style
}}
notFoundContent={searchingAddress ? <Spin size='small' /> : <Text type='secondary'>No address found</Text>}
bordered={false}
showSearch
onBlur={onEnterSelect}
onInputKeyDown={onEnterSelect}
onSearch={onSearchAddress}
disabled={disabled}
{
...selectInputValueProp
}
>
{
knownAddressesOptions
.filter((knownAddress) =>
knownAddress.network === network &&
notCurrentWallet(knownAddress.address) &&
(!walletsMap?.[knownAddress.address]?.temp || allowTemp) && // not a temporary wallet
!util.isDefaultRecoveryAddress(knownAddress.address))
.sort((knownAddress) => knownAddress.label ? -1 : 0)
.map((knownAddress, index) => {
const oneAddress = util.safeOneAddress(knownAddress.address)
const addressLabel = knownAddress.label || knownAddress.domain?.name
// Only display actions for addresses that are not selected.
// User's wallets addresses are not deletable.
const displayActionButton = addressValue.value !== knownAddress.address && !walletsAddresses.includes(knownAddress.address)
return buildSelectOption({
key: index,
address: knownAddress.address,
displayActionButton,
label: addressLabel,
domainName: knownAddress.domain?.name,
filterValue: `${knownAddress.address} ${useHex ? knownAddress.address : oneAddress} ${knownAddress.domain?.name}`
})
})
}
{
showSelectManualInputAddress && buildSelectOption({
address: addressValue.value,
label: addressValue.domainName,
domainName: addressValue.domainName,
filterValue: addressValue.filterValue
})
}
{
extraSelectOptions ? extraSelectOptions.map(buildSelectOption) : <></>
}
</Select>
{showQrCodeScanner && <QrCodeScanner shouldInit onScan={onScan} />}
</Space>
</>
)
}
Example #12
Source File: List.jsx From one-wallet with Apache License 2.0 | 4 votes |
List = () => {
const history = useHistory()
const { isMobile } = useWindowDimensions()
const wallets = useSelector(state => state.wallet)
const balances = useSelector(state => state.balance || {})
const price = useSelector(state => state.global.price)
const network = useSelector(state => state.global.network)
const dispatch = useDispatch()
const totalBalance = Object.keys(balances)
.filter(a => wallets[a] && wallets[a].network === network && !wallets[a].temp)
.map(a => balances[a])
.reduce((a, b) => a.add(new BN(b?.balance || 0, 10)), new BN(0)).toString()
const { formatted, fiatFormatted } = util.computeBalance(totalBalance, price)
const titleLevel = isMobile ? 4 : 3
const [purged, setPurged] = useState(false)
const purge = (wallet) => {
Sentry.withScope(scope => {
scope.setContext('wallet', omit(['hseed'], wallet))
Sentry.captureMessage('purge1')
})
deleteWalletLocally({ wallet, wallets, dispatch, silent: true })
}
useEffect(() => {
if (purged || !wallets || Object.keys(wallets).length === 0) {
return
}
async function scanWalletsForPurge () {
await cleanStorage({ wallets })
const now = Date.now()
setPurged(true)
Object.keys(wallets || {}).forEach((address) => {
const wallet = wallets[address]
if (!wallet) {
return
}
if (address === 'undefined') {
// leftover stale wallet due to buggy code
dispatch(walletActions.deleteWallet('undefined'))
return
}
if (
(wallet?.temp && wallet.temp < now) ||
address === ONEConstants.EmptyAddress ||
!wallet.network
) {
purge(wallet)
}
})
}
scanWalletsForPurge()
}, [wallets])
useEffect(() => {
batch(() => {
values(wallets).filter(w => isMatchingWallet(w)).forEach(({ address }) => {
dispatch(walletActions.fetchWallet({ address }))
dispatch(balanceActions.fetchBalance({ address }))
})
})
}, [])
const isMatchingWallet = (w) => {
return w.network === network &&
!w.temp &&
w.address !== ONEConstants.EmptyAddress
}
const matchedWallets = values(wallets).filter(w => isMatchingWallet(w))
if (!matchedWallets.length) {
return (
<AnimatedSection
show
style={{ maxWidth: 720 }}
>
<Hint>
No wallet found on this device for the selected network, you can either{' '}
<Button type='link' onClick={() => history.push(Paths.create)} style={{ padding: 0 }}>create one</Button>
{' '}now or{' '}
<Button type='link' onClick={() => history.push(Paths.restore)} style={{ padding: 0 }}> restore one </Button>
{' '}you had before.
</Hint>
</AnimatedSection>
)
}
return (
<Space direction='vertical' style={{ width: '100%' }}>
<Row gutter={[24, 24]}>
{matchedWallets.map((w, i) => <Col span={isMobile && 24} key={`${w.address}-${i}`}><WalletCard wallet={w} /></Col>)}
</Row>
<Row style={{ marginTop: 36 }}>
<Space direction='vertical'>
<Space align='baseline' style={{ justifyContent: 'space-between', marginLeft: isMobile ? '24px' : undefined }}>
<Title level={titleLevel} style={{ marginRight: isMobile ? 16 : 48 }}>Total Balance</Title>
<Title level={titleLevel}>{formatted}</Title><Text type='secondary'>ONE</Text>
</Space>
<Space align='baseline' style={{ justifyContent: 'space-between', marginLeft: isMobile ? '24px' : undefined }}>
<Title level={titleLevel} style={{ marginRight: isMobile ? 16 : 48, opacity: 0 }}>Total Balance</Title>
<Title style={{ whiteSpace: 'nowrap' }} level={titleLevel}>≈ ${fiatFormatted}</Title><Text type='secondary'>USD</Text>
</Space>
</Space>
</Row>
</Space>
)
}
Example #13
Source File: Upgrade.jsx From one-wallet with Apache License 2.0 | 4 votes |
Upgrade = ({ address, prompt, onClose }) => {
const history = useHistory()
const dispatch = useDispatch()
const network = useSelector(state => state.global.network)
const [confirmUpgradeVisible, setConfirmUpgradeVisible] = useState(false)
const wallets = useSelector(state => state.wallet)
const wallet = wallets[address] || {}
const [skipUpdate, setSkipUpdate] = useState(false)
const { majorVersion, minorVersion, lastResortAddress, doubleOtp, forwardAddress, temp } = wallet
const isDevVersion = parseInt(minorVersion) === 0
const requireUpdate = majorVersion && (!(parseInt(majorVersion) >= ONEConstants.MajorVersion) || isDevVersion)
const canUpgrade = majorVersion >= config.minUpgradableVersion
const latestVersion = { majorVersion: ONEConstants.MajorVersion, minorVersion: ONEConstants.MinorVersion }
const balances = useSelector(state => state.balance || {})
const { balance } = util.computeBalance(balances[address]?.balance || 0)
const maxSpend = BN.min(util.getMaxSpending(wallet), new BN(balance))
const { formatted: maxSpendFormatted } = util.computeBalance(maxSpend.toString())
const balanceGreaterThanLimit = new BN(balance).gt(new BN(maxSpend))
// const needSetRecoveryAddressFirst = balanceGreaterThanLimit && util.isDefaultRecoveryAddress(lastResortAddress)
const needSetRecoveryAddressFirst = util.isDefaultRecoveryAddress(lastResortAddress)
const needSpecialSteps = balanceGreaterThanLimit && !util.isDefaultRecoveryAddress(lastResortAddress)
const [minTransferGas] = useState(100000)
const { isMobile } = useWindowDimensions()
const { state: otpState } = useOtpState()
const { otpInput, otp2Input } = otpState
const resetOtp = otpState.resetOtp
const [stage, setStage] = useState(-1)
const { resetWorker, recoverRandomness } = useRandomWorker()
useEffect(() => {
if (prompt) {
setSkipUpdate(false)
}
}, [prompt])
const { prepareValidation, prepareProof, prepareProofFailed, onRevealSuccess, ...helpers } = ShowUtils.buildHelpers({ setStage, resetOtp, network, resetWorker })
const doUpgrade = async () => {
if (stage >= 0) {
return
}
const { otp, otp2, invalidOtp2, invalidOtp } = prepareValidation({ state: { otpInput, otp2Input, doubleOtp: wallet.doubleOtp }, checkAmount: false, checkDest: false }) || {}
if (invalidOtp || invalidOtp2) return
prepareProof && prepareProof()
const { eotp, index, layers } = await EOTPDerivation.deriveEOTP({ otp, otp2, wallet, prepareProofFailed })
if (!eotp) {
return
}
message.info('Retrieving latest information for the wallet...')
const {
root,
height,
interval,
t0,
lifespan,
maxOperationsPerInterval,
lastResortAddress,
spendingLimit,
spendingAmount, // ^classic
spendingInterval, // v12
highestSpendingLimit,
lastLimitAdjustmentTime,
lastSpendingInterval,
spentAmount, // ^v15
} = await api.blockchain.getWallet({ address, raw: true })
const backlinks = await api.blockchain.getBacklinks({ address }) // v9
let oldCores = [] // v14
if (majorVersion >= 14) {
oldCores = await api.blockchain.getOldInfos({ address, raw: true })
}
// TODO: always add a new identification key, computed using keccak(eotp) or similar. This key will be used for address prediction and contract verification only. It will be automatically ignored for other purposes (due to shorter length)
const upgradeIdentificationKey = ONEUtil.hexString(ONEUtil.keccak(new Uint8Array([...eotp, ...new Uint8Array(new Uint32Array([index]).buffer)])))
let identificationKeys = []; let innerCores = [] // v15
if (majorVersion >= 15) {
[innerCores, identificationKeys] = await Promise.all([
api.blockchain.getInnerCores({ address, raw: true }),
api.blockchain.getIdentificationKeys({ address }),
])
}
identificationKeys.unshift(upgradeIdentificationKey)
const transformedLastResortAddress = util.isDefaultRecoveryAddress(lastResortAddress) || util.isBlacklistedAddress(lastResortAddress) ? ONEConstants.TreasuryAddress : lastResortAddress
const { address: newAddress } = await api.relayer.create({
root,
height,
interval,
t0,
lifespan,
slotSize: maxOperationsPerInterval,
lastResortAddress: transformedLastResortAddress,
spendingAmount,
spendingLimit,
spendingInterval,
backlinks: [...backlinks, address],
oldCores,
highestSpendingLimit,
lastLimitAdjustmentTime,
lastSpendingInterval,
spentAmount,
innerCores,
identificationKeys, // ^v15
})
SmartFlows.commitReveal({
wallet,
otp,
otp2,
eotp,
index,
layers,
recoverRandomness,
commitHashGenerator: ONE.computeDestOnlyHash,
commitRevealArgs: { dest: newAddress },
revealAPI: api.relayer.revealForward,
prepareProof,
prepareProofFailed,
...helpers,
onRevealSuccess: async (txId, messages) => {
onRevealSuccess(txId, messages)
setStage(-1)
resetOtp()
resetWorker()
const newWallet = {
...wallet,
address: newAddress,
innerRoots: wallet.innerRoots || [],
identificationKeys: wallet.identificationKeys || [],
backlinks,
_merge: true
}
const oldWallet = {
...wallet,
temp: wallet.effectiveTime + wallet.duration,
forwardAddress: newAddress,
_merge: true
}
batch(() => {
dispatch(walletActions.updateWallet(newWallet))
dispatch(walletActions.updateWallet(oldWallet))
dispatch(walletActions.fetchWallet({ address: newAddress }))
})
message.success('Upgrade completed! Redirecting to wallet in 2 seconds...')
setTimeout(() => history.push(Paths.showAddress(util.safeOneAddress(newAddress))), 2000)
}
})
}
const skip = () => {
setConfirmUpgradeVisible(false)
setSkipUpdate(true)
onClose && onClose()
}
const skipVersion = () => {
dispatch(walletActions.userSkipVersion({ address, version: ONEUtil.getVersion(latestVersion) }))
skip()
}
if (!requireUpdate || skipUpdate || !canUpgrade || temp || !util.isEmptyAddress(forwardAddress) || wallet.skipVersion === ONEUtil.getVersion(latestVersion)) {
return <></>
}
return (
<Card style={CardStyle} bodyStyle={{ height: '100%' }}>
<Space
direction='vertical'
align='center'
size='large'
style={{
height: '100%',
justifyContent: 'start',
paddingTop: isMobile ? 32 : 192,
paddingLeft: isMobile ? 16 : 64,
paddingRight: isMobile ? 16 : 64,
display: 'flex'
}}
>
{!confirmUpgradeVisible &&
<>
<Title level={isMobile ? 4 : 2}>
An upgrade is available
{isDevVersion && <Text><br />(Dev version detected)</Text>}
</Title>
<Text>Your wallet: v{ONEUtil.getVersion(wallet)}</Text>
<Text>Latest version: v{ONEUtil.getVersion(latestVersion)}</Text>
<Button type='primary' shape='round' size='large' onClick={() => setConfirmUpgradeVisible(true)}>Upgrade Now</Button>
<Button size='large' shape='round' onClick={skip}>Do it later</Button>
<Button type='text' danger onClick={skipVersion}>Skip this version</Button>
<Text>For more details about this upgrade, see <Link target='_blank' href={util.releaseNotesUrl(latestVersion)} rel='noreferrer'> release notes for v{ONEUtil.getVersion(latestVersion)}</Link></Text>
</>}
{confirmUpgradeVisible &&
<>
{needSetRecoveryAddressFirst &&
<>
<Title level={4}>
To protect your assets, please set a recovery address prior to upgrading.
</Title>
<Button size='large' type='primary' shape='round' onClick={() => { skip(); history.push(Paths.showAddress(address, 'help')) }}>Set Now</Button>
</>}
{needSpecialSteps &&
<>
<Title type='danger' level={4}>
You have a high value wallet. Follow these steps:
</Title>
<Steps current={0} direction='vertical'>
<Step title='Confirm the upgrade' description={`You will get a new address. Only ${maxSpendFormatted} ONE will there. Don't panic.`} />
<Step
title='Approve asset transfer'
description={(
<Space direction='vertical'>
<Text>Send 0.1 ONE from your recovery address</Text>
<WalletAddress address={lastResortAddress} showLabel alwaysShowOptions />
<Text>to the current address <b>(use at least {minTransferGas} gas limit)</b></Text>
<WalletAddress address={address} showLabel alwaysShowOptions />
<Text>(To abort upgrade, recover assets, and deprecate the wallet, send 1.0 ONE instead)</Text>
</Space>)}
/>
</Steps>
</>}
{!needSetRecoveryAddressFirst &&
<>
<OtpStack shouldAutoFocus walletName={autoWalletNameHint(wallet)} doubleOtp={doubleOtp} otpState={otpState} onComplete={doUpgrade} action='confirm upgrade' />
<Title level={3}>
How upgrade works:
</Title>
<Timeline>
<Timeline.Item>Each upgrade gives you a new address</Timeline.Item>
<Timeline.Item>Your old address auto-forward assets to new address</Timeline.Item>
<Timeline.Item>In rare cases, some assets may be left over (e.g. ERC20 tokens)</Timeline.Item>
<Timeline.Item>You can take control of old addresses (under "About" tab)</Timeline.Item>
<Timeline.Item>You can inspect and reclaim what's left there at any time</Timeline.Item>
</Timeline>
</>}
{stage < 0 && <Button size='large' shape='round' onClick={skip}>Do it later</Button>}
{stage < 0 && <Button type='text' danger onClick={skipVersion}>Skip this version</Button>}
</>}
{stage >= 0 && (
<Row>
<Steps current={stage}>
<Step title='Clone' description='Cloning to new version' />
<Step title='Prepare' description='Preparing for transfer' />
<Step title='Link' description='Linking two versions' />
</Steps>
</Row>)}
</Space>
</Card>
)
}
Example #14
Source File: Show.jsx From one-wallet with Apache License 2.0 | 4 votes |
Show = () => {
const history = useHistory()
const location = useLocation()
const dispatch = useDispatch()
const wallets = useSelector(state => state.wallet)
const match = useRouteMatch(Paths.show)
const { address: routeAddress, action } = match ? match.params : {}
const oneAddress = util.safeOneAddress(routeAddress)
const address = util.safeNormalizedAddress(routeAddress)
const selectedAddress = useSelector(state => state.global.selectedWallet)
const wallet = wallets[address] || {}
const [section, setSection] = useState(action)
const [command, setCommand] = useState(action)
const network = useSelector(state => state.global.network)
const [activeTab, setActiveTab] = useState('coins')
const { expert } = wallet
const dev = useSelector(state => state.global.dev)
useEffect(() => {
if (!wallet.address) {
return history.push(Paths.wallets)
}
if (address && (address !== selectedAddress)) {
dispatch(globalActions.selectWallet(address))
}
const fetch = () => dispatch(balanceActions.fetchBalance({ address }))
const handler = setInterval(() => {
if (!document.hidden) { fetch() }
}, WalletConstants.fetchBalanceFrequency)
batch(() => {
fetch()
dispatch(walletActions.fetchWallet({ address }))
})
return () => { clearInterval(handler) }
}, [address])
const selectedToken = wallet?.selectedToken || HarmonyONE
useEffect(() => {
const m = matchPath(location.pathname, { path: Paths.show })
const { action } = m ? m.params : {}
if (action !== 'nft' && action !== 'transfer' && selectedToken.key !== 'one' && selectedToken.tokenType !== ONEConstants.TokenType.ERC20) {
dispatch(walletActions.setSelectedToken({ token: null, address }))
}
if (SpecialCommands.includes(action)) {
setCommand(action)
} else {
setCommand('')
}
if (tabList.find(t => t.key === action)) {
setSection(undefined)
setActiveTab(action)
return
} else if (SectionList.includes(action)) {
setSection(action)
return
}
setSection('')
}, [location])
const showTab = (tab) => { history.push(Paths.showAddress(oneAddress, tab)) }
const showStartScreen = () => { history.push(Paths.showAddress(oneAddress)) }
// UI Rendering below
if (!wallet.address || wallet.network !== network) {
return <Redirect to={Paths.wallets} />
}
const displayTabList = tabList.filter(e => e.tab && ((!e.expert || expert) || (!e.dev || dev)) && (!e.requireNetwork || e.requireNetwork(network)))
return (
<>
{!section &&
<AnimatedSection
title={<WalletTitle address={address} onQrCodeClick={() => showTab('qr')} onScanClick={() => showTab('scan')} />}
tabList={displayTabList}
activeTabKey={activeTab}
onTabChange={key => showTab(key)}
wide
>
<Warnings address={address} />
{activeTab === 'about' && <About address={address} />}
{activeTab === 'coins' && <Balance address={address} />}
{activeTab === 'coins' && <ERC20Grid address={address} />}
{activeTab === 'nft' && <NFTDashboard address={address} />}
{activeTab === 'help' && <Recovery address={address} />}
{activeTab === 'swap' && <Swap address={address} />}
{activeTab === 'gift' && <Gift address={address} />}
{activeTab === 'qr' && <QRCode address={address} name={wallet.name} />}
{activeTab === 'scan' && <Scan address={address} />}
{activeTab === 'call' && <Call address={address} headless />}
{activeTab === 'sign' && <Sign address={address} headless />}
{activeTab === 'history' && <TransactionViewer address={address} />}
<Upgrade address={address} prompt={command === 'upgrade'} onClose={showStartScreen} />
<CheckForwardState address={address} onClose={() => history.push(Paths.wallets)} />
<CheckRoots address={address} onClose={() => history.push(Paths.wallets)} />
</AnimatedSection>}
{section === 'transfer' && <Send address={address} onClose={showStartScreen} />}
{section === 'limit' && <Limit address={address} onClose={showStartScreen} />}
{section === 'recover' && <DoRecover address={address} onClose={showStartScreen} />}
{section === 'setRecoveryAddress' && <SetRecovery address={address} onClose={showStartScreen} />}
{section === 'domain' && <PurchaseDomain address={address} onClose={showStartScreen} />}
{section === 'domainTransfer' && <TransferDomain address={address} onClose={showStartScreen} />}
{section === 'reclaim' && <Reclaim address={address} onClose={showStartScreen} />}
{section === 'extend' && <Extend address={address} onClose={showStartScreen} />}
{section === 'stake' && <Stake address={address} onClose={showStartScreen} />}
{section === 'unstake' && <Unstake address={address} />}
{section === 'collectStakeReward' && <CollectStakeReward address={address} />}
</>
)
}