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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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} />}
    </>
  )
}