View on GitHub
File Changes
m
+1/-0
### Features

                      
- Implemented the necessary UI changes for the Incentivized Testnet network ([PR 1657](https://github.com/input-output-hk/daedalus/pull/1657))
+
- Implemented "Transfer funds" wizard for Incentivized Testnet version of Daedalus ([PR 1634](https://github.com/input-output-hk/daedalus/pull/1634), [PR 1659](https://github.com/input-output-hk/daedalus/pull/1659), [PR 1660](https://github.com/input-output-hk/daedalus/pull/1660))
- Implemented "Network info" overlay ([PR 1655](https://github.com/input-output-hk/daedalus/pull/1655))
- Disable "Manual update" notification for Incentivized Testnet version of Daedalus ([PR 1652](https://github.com/input-output-hk/daedalus/pull/1652))
- Update rewards screen for incentivized testnet ([PR 1643](https://github.com/input-output-hk/daedalus/pull/1643))
m
+1/-1

                      
1. Run `yarn nix:dev` from `daedalus`.
2. Run `yarn dev` from the subsequent `nix-shell`
-
3. Once Daedalus has started, and has gotten past the loading screen, run `yarn v2-api-importer` from a new terminal window. This is only required if you wish to import some funded wallets
+
3. Once Daedalus has started, and has gotten past the loading screen, run `yarn v2:wallet:importer` from a new terminal window. This is only required if you wish to import some funded wallets

                      
### QA testnet

                      
m
+2/-1
    "nix:dev": "nix-shell",
    "nix:qa": "nix-shell --argstr cluster qa",
    "nix:nightly": "nix-shell --argstr cluster nightly",
-
    "v2-api-importer": "node utils/v2-api-importer/index.js",
+
    "v2:wallet:importer": "node utils/v2-api-importer/index.js",
+
    "v2:legacy:wallet:importer": "node utils/v2-api-importer/legacy.js",
    "js-launcher": "node utils/js-launcher/index.js"
  },
  "bin": {
// ======= WALLET ACTIONS =======

                      
export default class WalletsActions {
-
  // Create Wallet
+
  /* ----------  Create Wallet  ---------- */
  createWallet: Action<{
    name: string,
    spendingPassword: string,
  createWalletChangeStep: Action<any> = new Action();
  createWalletClose: Action<any> = new Action();
  createWalletAbort: Action<any> = new Action();
-
  // ---
+

                      
  restoreWallet: Action<{
    recoveryPhrase: string,
    walletName: string,
  getWallet: Action<{ walletId: string }> = new Action();
  updateWalletLocalData: Action<any> = new Action();
  updateRecoveryPhraseVerificationDate: Action<any> = new Action();
+

                      
+
  /* ----------  Transfer Funds  ---------- */
+
  transferFundsNextStep: Action<any> = new Action();
+
  transferFundsPrevStep: Action<any> = new Action();
+
  transferFundsSetSourceWalletId: Action<{
+
    sourceWalletId: string,
+
  }> = new Action();
+
  transferFundsSetTargetWalletId: Action<{
+
    targetWalletId: string,
+
  }> = new Action();
+
  transferFundsRedeem: Action<any> = new Action();
+
  transferFundsClose: Action<any> = new Action();
+
  transferFundsCalculateFee: Action<{ sourceWalletId: string }> = new Action();
+
  transferFunds: Action<{ spendingPassword: string }> = new Action();
}
import { getWalletUtxos } from './wallets/requests/getWalletUtxos';
import { getWallet } from './wallets/requests/getWallet';
import { getWalletIdAndBalance } from './wallets/requests/getWalletIdAndBalance';
+
import { transferFundsCalculateFee } from './wallets/requests/transferFundsCalculateFee';
+
import { transferFunds } from './wallets/requests/transferFunds';

                      
// News requests
import { getNews } from './news/requests/getNews';
  GetWalletRequest,
  GetWalletIdAndBalanceRequest,
  GetWalletIdAndBalanceResponse,
+
  TransferFundsCalculateFeeRequest,
+
  TransferFundsCalculateFeeResponse,
+
  TransferFundsRequest,
+
  TransferFundsResponse,
} from './wallets/types';

                      
// News Types
      } else {
        response = await getTransactionHistory(this.config, walletId, params);
      }
-

                      
      const transactions = response.map(tx =>
        _createTransactionFromServerData(tx)
      );
    }
  };

                      
+
  transferFundsCalculateFee = async (
+
    request: TransferFundsCalculateFeeRequest
+
  ): Promise<BigNumber> => {
+
    const { sourceWalletId } = request;
+
    Logger.debug('AdaApi::transferFundsCalculateFee called', {
+
      parameters: { sourceWalletId },
+
    });
+
    try {
+
      const response: TransferFundsCalculateFeeResponse = await transferFundsCalculateFee(
+
        this.config,
+
        {
+
          sourceWalletId,
+
        }
+
      );
+
      Logger.debug('AdaApi::transferFundsCalculateFee success', { response });
+
      return _createMigrationFeeFromServerData(response);
+
    } catch (error) {
+
      Logger.error('AdaApi::transferFundsCalculateFee error', { error });
+
      throw new GenericApiError();
+
    }
+
  };
+

                      
+
  transferFunds = async (
+
    request: TransferFundsRequest
+
  ): Promise<TransferFundsResponse> => {
+
    const { sourceWalletId, targetWalletId, passphrase } = request;
+
    Logger.debug('AdaApi::transferFunds called', {
+
      parameters: { sourceWalletId, targetWalletId },
+
    });
+

                      
+
    try {
+
      const response: TransferFundsResponse = await transferFunds(this.config, {
+
        sourceWalletId,
+
        targetWalletId,
+
        passphrase,
+
      });
+
      Logger.debug('AdaApi::transferFunds success', { response });
+
      return response;
+
    } catch (error) {
+
      Logger.error('AdaApi::transferFunds error', { error });
+
      if (error.code === 'wrong_encryption_passphrase') {
+
        throw new IncorrectSpendingPasswordError();
+
      }
+
      throw new GenericApiError();
+
    }
+
  };
+

                      
  testReset = async (): Promise<void> => {
    Logger.debug('AdaApi::testReset called');
    try {
    return new BigNumber(amount).dividedBy(LOVELACES_PER_ADA);
  }
);
+

                      
+
const _createMigrationFeeFromServerData = action(
+
  'AdaApi::_createTransactionFeeFromServerData',
+
  (data: TransactionFee) => {
+
    const amount = get(data, ['migration_cost', 'quantity'], 0);
+
    return new BigNumber(amount).dividedBy(LOVELACES_PER_ADA);
+
  }
+
);
+
// @flow
+
import type { RequestConfig } from '../../common/types';
+
import type { TransferFundsRequest, TransferFundsResponse } from '../types';
+
import { request } from '../../utils/request';
+

                      
+
export const transferFunds = (
+
  config: RequestConfig,
+
  { sourceWalletId, targetWalletId, passphrase }: TransferFundsRequest
+
): Promise<TransferFundsResponse> =>
+
  request(
+
    {
+
      method: 'POST',
+
      path: `/v2/byron-wallets/${sourceWalletId}/migrations/${targetWalletId}`,
+
      ...config,
+
    },
+
    {},
+
    { passphrase }
+
  );
+
// @flow
+
import type { RequestConfig } from '../../common/types';
+
import type {
+
  TransferFundsCalculateFeeRequest,
+
  TransferFundsCalculateFeeResponse,
+
} from '../types';
+
import { request } from '../../utils/request';
+

                      
+
export const transferFundsCalculateFee = (
+
  config: RequestConfig,
+
  { sourceWalletId }: TransferFundsCalculateFeeRequest
+
): Promise<TransferFundsCalculateFeeResponse> =>
+
  request({
+
    method: 'GET',
+
    path: `/v2/byron-wallets/${sourceWalletId}/migrations`,
+
    ...config,
+
  });
// @flow
import BigNumber from 'bignumber.js';

                      
+
export type Block = {
+
  slot_number: number,
+
  epoch_number: number,
+
  height: {
+
    quantity: number,
+
    unit: 'block',
+
  },
+
};
+

                      
+
export type Input = {
+
  address?: string,
+
  amount?: {
+
    quantity: number,
+
    unit: 'lovelace',
+
  },
+
  id: string,
+
  index: number,
+
};
+

                      
+
export type Output = {
+
  address: string,
+
  amount: {
+
    quantity: number,
+
    unit: 'lovelace',
+
  },
+
};
+

                      
export type AdaWallet = {
  id: string,
  address_pool_gap: number,
    last_updated_at: string,
  },
  state: WalletSyncState,
-
  tip: {
-
    slot_number: number,
-
    epoch_number: number,
-
    height: {
-
      quantity: number,
-
      unit: 'block',
-
    },
-
  },
+
  tip: Block,
};

                      
export type LegacyAdaWallets = Array<LegacyAdaWallet>;
export type GetWalletRequest = {
  walletId: string,
};
+

                      
+
export type TransferFundsCalculateFeeRequest = {
+
  sourceWalletId: string,
+
};
+

                      
+
export type TransferFundsCalculateFeeResponse = {
+
  migration_cost: {
+
    quantity: number,
+
    unit: 'lovelace',
+
  },
+
};
+

                      
+
export type TransferFundsRequest = {
+
  sourceWalletId: string,
+
  targetWalletId: string,
+
  passphrase: string,
+
};
+

                      
+
export type TransferFundsResponse = {
+
  id: string,
+
  amount: {
+
    quantity: number,
+
    unit: 'lovelace',
+
  },
+
  inserted_at?: {
+
    time: Date,
+
    block: Block,
+
  },
+
  pending_since?: {
+
    time: Date,
+
    block: Block,
+
  },
+
  depth: {
+
    quantity: number,
+
    unit: 'block',
+
  },
+
  direction: 'incoming' | 'outgoing',
+
  inputs: Array<Input>,
+
  outputs: Array<Output>,
+
  status: 'pending' | 'in_ledger',
+
};
  leftIcon?: ?string,
  children?: ?Node,
  activeWallet?: ?Wallet,
+
  onTransferFunds?: Function,
+
  onWalletAdd?: Function,
+
  hasAnyWallets?: boolean,
+
  onLearnMore?: Function,
};

                      
@observer
export default class TopBar extends Component<Props> {
  render() {
-
    const { onLeftIconClick, leftIcon, activeWallet, children } = this.props;
+
    const {
+
      onLeftIconClick,
+
      leftIcon,
+
      activeWallet,
+
      children,
+
      hasAnyWallets,
+
      onTransferFunds,
+
      onWalletAdd,
+
      onLearnMore,
+
    } = this.props;

                      
    const topBarStyles = classNames([
      styles.topBar,
      activeWallet ? styles.withWallet : styles.withoutWallet,
    ]);

                      
+
    const hasLegacyNotification =
+
      activeWallet &&
+
      activeWallet.isLegacy &&
+
      activeWallet.amount.gt(0) &&
+
      ((hasAnyWallets && onTransferFunds) || onWalletAdd);
+

                      
+
    const onTransferFundsFn =
+
      onTransferFunds && activeWallet
+
        ? () => onTransferFunds(activeWallet.id)
+
        : () => {};
+

                      
    const topBarTitle = activeWallet ? (
      <span className={styles.walletInfo}>
        <span className={styles.walletName}>
          )}
          {children}
        </div>
-
        {activeWallet && activeWallet.isLegacy && (
-
          <LegacyNotification onLearnMore={() => null} onMove={() => null} />
+
        {hasLegacyNotification && (
+
          <LegacyNotification
+
            onLearnMore={onLearnMore}
+
            onTransferFunds={onTransferFundsFn}
+
            hasAnyWallets={hasAnyWallets}
+
            onWalletAdd={onWalletAdd}
+
          />
        )}
      </header>
    );
    defaultMessage: '!!!Move all of the ada from this wallet',
    description: 'Move all ada action of legacy notification.',
  },
+
  addWallet: {
+
    id: 'wallet.legacy.notification.addWallet',
+
    defaultMessage: '!!!Move all of the ada from this wallet',
+
    description: 'Add wallet action of legacy notification.',
+
  },
+
  learnMoreLinkUrl: {
+
    id: 'wallet.legacy.notification.learnMore.url',
+
    defaultMessage: '!!!https://iohk.zendesk.com/hc/en-us',
+
    description: '"Learn more" link URL',
+
  },
});

                      
type Props = {
  onLearnMore: Function,
-
  onMove: Function,
+
  onTransferFunds: Function,
+
  hasAnyWallets?: boolean,
+
  onWalletAdd?: boolean,
};

                      
@observer
    intl: intlShape.isRequired,
  };

                      
+
  onLearnMore = () => {
+
    const { intl } = this.context;
+
    const learnMoreLinkUrl = intl.formatMessage(messages.learnMoreLinkUrl);
+
    this.props.onLearnMore(learnMoreLinkUrl);
+
  };
+

                      
  render() {
    const { intl } = this.context;
-
    const { onLearnMore, onMove } = this.props;
+
    const { onTransferFunds, hasAnyWallets, onWalletAdd } = this.props;
    const title = intl.formatMessage(messages.title);
    const description = intl.formatMessage(messages.description);
    const actionLearnMore = intl.formatMessage(messages.actionLearnMore);
-
    const actionMove = intl.formatMessage(messages.actionMove);
+

                      
+
    const buttonLabel = hasAnyWallets
+
      ? intl.formatMessage(messages.actionMove)
+
      : intl.formatMessage(messages.addWallet);
+

                      
+
    const buttonAction = hasAnyWallets ? onTransferFunds : onWalletAdd;

                      
    return (
      <div className={styles.component}>
          <Button
            className={styles.actionLearnMore}
            label={actionLearnMore}
-
            onClick={onLearnMore}
-
            skin={ButtonSkin}
-
          />
-
          <Button
-
            className={styles.actionMove}
-
            label={actionMove}
-
            onClick={onMove}
+
            onClick={this.onLearnMore}
            skin={ButtonSkin}
          />
+
          {
+
            <Button
+
              className={styles.actionMove}
+
              label={buttonLabel}
+
              onClick={buttonAction}
+
              skin={ButtonSkin}
+
            />
+
          }
        </div>
      </div>
    );
import DelegationStepsNotAvailableDialog from './DelegationStepsNotAvailableDialog';
import DelegationStepsChooseStakePoolDialog from './DelegationStepsChooseStakePoolDialog';
import type { StakePool } from '../../../api/staking/types';
-

                      
-
type DelegationWalletData = {
-
  id: string,
-
  isAcceptableSetupWallet: boolean,
-
  label: string,
-
  value: string,
-
};
+
import Wallet from '../../../domains/Wallet';

                      
type Props = {
  activeStep: number,
  onLearnMoreClick: Function,
  onSelectWallet: Function,
  onSelectPool: Function,
+
  isWalletAcceptable: Function,
  stepsList: Array<string>,
-
  wallets: Array<DelegationWalletData>,
+
  wallets: Array<Wallet>,
  minDelegationFunds: number,
  stakePoolsDelegatingList: Array<StakePool>,
  stakePoolsList: Array<StakePool>,
  onOpenExternalLink: Function,
  currentTheme: string,
-
  selectedWallet: ?DelegationWalletData,
+
  selectedWalletId: string,
  selectedPool: ?StakePool,
};

                      
      stakePoolsList,
      onOpenExternalLink,
      currentTheme,
-
      selectedWallet,
+
      selectedWalletId,
      selectedPool,
+
      isWalletAcceptable,
    } = this.props;

                      
    if (isDisabled) {
            stepsList={stepsList}
            wallets={wallets}
            minDelegationFunds={minDelegationFunds}
-
            selectedWallet={selectedWallet}
+
            selectedWalletId={selectedWalletId}
            onBack={onBack}
            onClose={onClose}
            onSelectWallet={onSelectWallet}
+
            isWalletAcceptable={isWalletAcceptable}
          />
        );
        break;
  FormattedHTMLMessage,
  FormattedMessage,
} from 'react-intl';
-
import { get } from 'lodash';
import classNames from 'classnames';
-
import { Select } from 'react-polymorph/lib/components/Select';
-
import { SelectSkin } from 'react-polymorph/lib/skins/simple/SelectSkin';
import { Stepper } from 'react-polymorph/lib/components/Stepper';
import { StepperSkin } from 'react-polymorph/lib/skins/simple/StepperSkin';
import commonStyles from './DelegationSteps.scss';
import styles from './DelegationStepsChooseWalletDialog.scss';
import DialogCloseButton from '../../widgets/DialogCloseButton';
import DialogBackButton from '../../widgets/DialogBackButton';
import Dialog from '../../widgets/Dialog';
+
import WalletsDropdown from '../../widgets/forms/WalletsDropdown';
+
import Wallet from '../../../domains/Wallet';

                      
const messages = defineMessages({
  title: {
  },
});

                      
-
type DelegationWalletData = {
-
  id: string,
-
  label: string,
-
  value: string,
-
  isAcceptableSetupWallet: boolean,
-
};
-

                      
type Props = {
  onClose: Function,
  onSelectWallet: Function,
  onBack: Function,
-
  wallets: Array<DelegationWalletData>,
+
  wallets: Array<Wallet>,
  stepsList: Array<string>,
  minDelegationFunds: number,
-
  selectedWallet: ?Object,
+
  selectedWalletId: string,
+
  isWalletAcceptable: Function,
};

                      
type State = {
-
  selectedWallet: ?DelegationWalletData,
+
  selectedWalletId: string,
};

                      
export default class DelegationStepsChooseWalletDialog extends Component<
  };

                      
  state = {
-
    selectedWallet: this.props.selectedWallet,
+
    selectedWalletId: this.props.selectedWalletId,
  };

                      
-
  onWalletChange = (selectedWallet: DelegationWalletData) => {
-
    this.setState({ selectedWallet });
+
  onWalletChange = (selectedWalletId: string) => {
+
    this.setState({ selectedWalletId });
  };

                      
  onSelectWallet = () => {
-
    const { selectedWallet } = this.state;
-
    const selectedWalletId = get(selectedWallet, 'id');
+
    const { selectedWalletId } = this.state;
    this.props.onSelectWallet(selectedWalletId);
  };

                      
  render() {
    const { intl } = this.context;
-
    const { selectedWallet } = this.state;
+
    const { selectedWalletId } = this.state;
    const {
      wallets,
      stepsList,
      minDelegationFunds,
      onClose,
      onBack,
+
      isWalletAcceptable,
    } = this.props;

                      
-
    const selectedWalletValue = get(selectedWallet, 'value', '');
-
    const isAcceptableSetupWallet = get(
-
      selectedWallet,
-
      'isAcceptableSetupWallet',
-
      false
-
    );
-

                      
+
    const selectedWallet: ?Wallet =
+
      wallets.find(
+
        (wallet: Wallet) => wallet && wallet.id === selectedWalletId
+
      ) || null;
+
    const amount = selectedWallet ? selectedWallet.amount : null;
+
    const isAcceptableSetupWallet = amount && isWalletAcceptable(amount);
    const actions = [
      {
        className: 'continueButton',
        label: intl.formatMessage(messages.continueButtonLabel),
        onClick: this.onSelectWallet.bind(this),
        primary: true,
-
        disabled: !selectedWalletValue || !isAcceptableSetupWallet,
+
        disabled: !selectedWalletId || !isAcceptableSetupWallet,
      },
    ];

                      

                      
    const walletSelectClasses = classNames([
      styles.walletSelect,
-
      selectedWallet && !isAcceptableSetupWallet ? styles.error : null,
+
      selectedWalletId && !isAcceptableSetupWallet ? styles.error : null,
    ]);

                      
    const stepsIndicatorLabel = (
              values={{ minDelegationFunds }}
            />
          </p>
-
          <Select
+
          <WalletsDropdown
            className={walletSelectClasses}
            label={intl.formatMessage(messages.selectWalletInputLabel)}
-
            options={wallets}
-
            optionRenderer={option => (
-
              <div
-
                className={styles.customOptionStyle}
-
                role="presentation"
-
                onClick={this.onWalletChange.bind(this, option)}
-
              >
-
                <div className={styles.optionLabel}>{option.label}</div>
-
                <div className={styles.optionValue}>{option.value}</div>
-
              </div>
-
            )}
-
            selectionRenderer={option => (
-
              <div className={styles.customValueStyle}>
-
                <div className={styles.label}>{option.label}</div>
-
                <div className={styles.value}>{option.value}</div>
-
              </div>
-
            )}
+
            wallets={wallets}
+
            onChange={(walletId: string) => this.onWalletChange(walletId)}
            placeholder={intl.formatMessage(
              messages.selectWalletInputPlaceholder
            )}
-
            skin={SelectSkin}
-
            value={selectedWalletValue}
+
            value={selectedWalletId}
          />
-
          {selectedWallet && !isAcceptableSetupWallet && (
+
          {selectedWalletId && !isAcceptableSetupWallet && (
            <p className={styles.errorMessage}>
              <FormattedHTMLMessage
                {...messages.errorMessage}
        font-weight: 500;
      }
    }
-

                      
-
    .customOptionStyle {
-
      color: var(
-
        --theme-delegation-steps-choose-wallet-error-select-options-color
-
      );
-

                      
-
      .optionLabel {
-
        font-size: 14px;
-
        line-height: 1.43;
-
      }
-

                      
-
      .optionValue {
-
        font-size: 12px;
-
        line-height: 1.33;
-
        opacity: 0.5;
-
      }
-
    }
-

                      
-
    .customValueStyle {
-
      color: var(--theme-delegation-steps-choose-wallet-custom-value-color);
-
      height: 100%;
-
      padding: 8px 20px;
-

                      
-
      .label {
-
        font-size: 14px;
-
        line-height: 1.43;
-
      }
-

                      
-
      .value {
-
        font-size: 12px;
-
        line-height: 1.33;
-
        opacity: 0.5;
-
      }
-
    }
-

                      
-
    :global {
-
      .SimpleOptions_option {
-
        padding: 8px 20px;
-
      }
-
    }
  }
}
+
// @flow
+
import React, { Component } from 'react';
+
import { defineMessages, intlShape } from 'react-intl';
+
import DialogCloseButton from '../../widgets/DialogCloseButton';
+
import Dialog from '../../widgets/Dialog';
+
import styles from './TransferFundsStep1Dialog.scss';
+
import Wallet from '../../../domains/Wallet';
+
import WalletsDropdown from '../../widgets/forms/WalletsDropdown';
+
import WalletsDropdownOption from '../../widgets/forms/WalletsDropdownOption';
+
import { formattedWalletAmount } from '../../../utils/formatters';
+

                      
+
const messages = defineMessages({
+
  dialogTitle: {
+
    id: 'wallet.transferFunds.dialog1.title',
+
    defaultMessage: '!!!Transfer funds from the legacy wallet',
+
    description: 'Title  in the transfer funds form.',
+
  },
+
  sourceWallet: {
+
    id: 'wallet.transferFunds.dialog1.sourceWallet',
+
    defaultMessage: '!!!From wallet',
+
    description: 'sourceWallet in the transfer funds form.',
+
  },
+
  targetWallet: {
+
    id: 'wallet.transferFunds.dialog1.targetWallet',
+
    defaultMessage: '!!!To walet',
+
    description: 'targetWallet in the transfer funds form.',
+
  },
+
  buttonLabel: {
+
    id: 'global.dialog.button.continue',
+
    defaultMessage: '!!!Continue',
+
    description: 'buttonLabel in the transfer funds form.',
+
  },
+
});
+

                      
+
type Props = {
+
  onClose: Function,
+
  onContinue: Function,
+
  onSetSourceWallet: Function,
+
  targetWalletId: string,
+
  sourceWallet: $Shape<Wallet>,
+
  wallets: Array<$Shape<Wallet>>,
+
};
+

                      
+
export default class TransferFundsStep1Dialog extends Component<Props> {
+
  static contextTypes = {
+
    intl: intlShape.isRequired,
+
  };
+

                      
+
  render() {
+
    const { intl } = this.context;
+
    const {
+
      onClose,
+
      onContinue,
+
      onSetSourceWallet,
+
      targetWalletId,
+
      sourceWallet,
+
      wallets,
+
    } = this.props;
+

                      
+
    return (
+
      <Dialog
+
        title={intl.formatMessage(messages.dialogTitle)}
+
        actions={[
+
          {
+
            label: intl.formatMessage(messages.buttonLabel),
+
            onClick: onContinue,
+
            primary: true,
+
            disabled: !this.props.targetWalletId,
+
          },
+
        ]}
+
        closeOnOverlayClick
+
        onClose={onClose}
+
        closeButton={<DialogCloseButton />}
+
      >
+
        <p className={styles.label}>
+
          {intl.formatMessage(messages.sourceWallet)}
+
        </p>
+
        <div className={styles.sourceWallet}>
+
          <WalletsDropdownOption
+
            label={sourceWallet.name}
+
            detail={formattedWalletAmount(sourceWallet.amount)}
+
            selected
+
          />
+
        </div>
+
        <WalletsDropdown
+
          label={intl.formatMessage(messages.targetWallet)}
+
          wallets={wallets}
+
          onChange={onSetSourceWallet}
+
          value={targetWalletId}
+
        />
+
      </Dialog>
+
    );
+
  }
+
}
+
.label {
+
  color: var(--rp-formfield-label-text-color, #5e6066);
+
  font-family: var(--font-medium);
+
  font-size: 16px;
+
  line-height: 1.38;
+
  margin-bottom: 10px;
+
}
+
.sourceWallet {
+
  background-color: rgba(94, 96, 102, 0.5);
+
  margin-bottom: 20px;
+
}
+
// @flow
+
import React, { Component } from 'react';
+
import { observer } from 'mobx-react';
+
import classnames from 'classnames';
+
import BigNumber from 'bignumber.js';
+
import { defineMessages, intlShape, FormattedMessage } from 'react-intl';
+
import { Input } from 'react-polymorph/lib/components/Input';
+
import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin';
+
import DialogCloseButton from '../../widgets/DialogCloseButton';
+
import DialogBackButton from '../../widgets/DialogBackButton';
+
import Dialog from '../../widgets/Dialog';
+
import styles from './TransferFundsStep2Dialog.scss';
+
import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm';
+
import { formattedWalletAmount } from '../../../utils/formatters';
+
import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig';
+
import globalMessages from '../../../i18n/global-messages';
+
import LocalizableError from '../../../i18n/LocalizableError';
+
import Wallet from '../../../domains/Wallet';
+
import { submitOnEnter } from '../../../utils/form';
+
import { DECIMAL_PLACES_IN_ADA } from '../../../config/numbersConfig';
+

                      
+
const messages = defineMessages({
+
  dialogTitle: {
+
    id: 'wallet.transferFunds.dialog2.title',
+
    defaultMessage: '!!!Transfer funds from the legacy wallet',
+
    description: 'Title in the transfer funds form.',
+
  },
+
  description: {
+
    id: 'wallet.transferFunds.dialog2.label.description',
+
    defaultMessage:
+
      '!!!Confirm transfer from the {sourceWalletName}wallet to the {targetWalletName} wallet.',
+
    description: 'description in the transfer funds form.',
+
  },
+
  labelTo: {
+
    id: 'wallet.transferFunds.dialog2.label.to',
+
    defaultMessage: '!!!To',
+
    description: 'Label To in the transfer funds form',
+
  },
+
  labelAmount: {
+
    id: 'wallet.transferFunds.dialog2.label.amount',
+
    defaultMessage: '!!!Amount',
+
    description: 'Label Amount in the transfer funds form',
+
  },
+
  labelFees: {
+
    id: 'wallet.transferFunds.dialog2.label.fees',
+
    defaultMessage: '!!!Fees',
+
    description: 'Label Fees in the transfer funds form',
+
  },
+
  labelTotal: {
+
    id: 'wallet.transferFunds.dialog2.label.total',
+
    defaultMessage: '!!!Total',
+
    description: 'Total Fees in the transfer funds form',
+
  },
+
  buttonLabel: {
+
    id: 'wallet.transferFunds.dialog2.label.buttonLabel',
+
    defaultMessage: '!!!Transfer funds',
+
    description: 'buttonLabel in the transfer funds form.',
+
  },
+
  passphraseFieldPlaceholder: {
+
    id: 'wallet.transferFunds.dialog2.passphraseFieldPlaceholder',
+
    defaultMessage: '!!!Type your spending password',
+
    description: 'passphraseFieldPlaceholder in the transfer funds form.',
+
  },
+
  passphraseLabel: {
+
    id: 'wallet.transferFunds.dialog2.passphraseLabel',
+
    defaultMessage: '!!!Spending password',
+
    description: 'passphraseLabel in the transfer funds form.',
+
  },
+
});
+

                      
+
messages.fieldIsRequired = globalMessages.fieldIsRequired;
+

                      
+
type Props = {
+
  onFinish: Function,
+
  onClose: Function,
+
  onBack: Function,
+
  // addresses: Array<any>,
+
  sourceWallet: $Shape<Wallet>,
+
  targetWallet: $Shape<Wallet>,
+
  transferFundsFee: ?BigNumber,
+
  isSubmitting?: boolean,
+
  error?: ?LocalizableError,
+
};
+

                      
+
type State = {
+
  total: string,
+
  fees: ?number,
+
  amount: ?string,
+
};
+

                      
+
@observer
+
export default class TransferFundsStep2Dialog extends Component<Props, State> {
+
  static contextTypes = {
+
    intl: intlShape.isRequired,
+
  };
+

                      
+
  state = {
+
    total: formattedWalletAmount(this.props.sourceWallet.amount, false),
+
    fees: null,
+
    amount: null,
+
  };
+

                      
+
  componentWillReceiveProps(nextProps: Props) {
+
    const { transferFundsFee, sourceWallet } = nextProps;
+
    // "FREEZ" current amounts with component state
+
    if (transferFundsFee && !this.state.fees && !this.state.amount) {
+
      const fees = transferFundsFee.toFormat(DECIMAL_PLACES_IN_ADA);
+
      const amount = formattedWalletAmount(
+
        sourceWallet.amount.minus(fees),
+
        false
+
      );
+
      this.setState({ fees, amount });
+
    }
+
  }
+

                      
+
  form = new ReactToolboxMobxForm(
+
    {
+
      fields: {
+
        spendingPassword: {
+
          type: 'password',
+
          label: this.context.intl.formatMessage(messages.passphraseLabel),
+
          placeholder: this.context.intl.formatMessage(
+
            messages.passphraseFieldPlaceholder
+
          ),
+
          value: '',
+
          validators: [
+
            ({ field }) => {
+
              if (field.value === '') {
+
                return [
+
                  false,
+
                  this.context.intl.formatMessage(messages.fieldIsRequired),
+
                ];
+
              }
+
              return [true];
+
            },
+
          ],
+
        },
+
      },
+
    },
+
    {
+
      options: {
+
        validateOnChange: true,
+
        validationDebounceWait: FORM_VALIDATION_DEBOUNCE_WAIT,
+
      },
+
    }
+
  );
+

                      
+
  submit = () => {
+
    this.form.submit({
+
      onSuccess: form => {
+
        const { spendingPassword } = form.values();
+
        this.props.onFinish(spendingPassword);
+
      },
+
      onError: () => {},
+
    });
+
  };
+

                      
+
  handleSubmitOnEnter = (event: {}) =>
+
    this.form.$('spendingPassword').isValid &&
+
    submitOnEnter(this.submit, event);
+

                      
+
  render() {
+
    const { intl } = this.context;
+
    const { total, fees, amount } = this.state;
+
    const {
+
      onClose,
+
      onBack,
+
      // addresses,
+
      sourceWallet,
+
      targetWallet,
+
      isSubmitting,
+
      error,
+
    } = this.props;
+

                      
+
    const spendingPasswordField = this.form.$('spendingPassword');
+

                      
+
    const buttonClasses = classnames([
+
      'confirmButton',
+
      isSubmitting ? styles.submitButtonSpinning : null,
+
    ]);
+

                      
+
    const actions = [
+
      {
+
        label: intl.formatMessage(messages.buttonLabel),
+
        onClick: this.submit,
+
        primary: true,
+
        className: buttonClasses,
+
        disabled: isSubmitting || !spendingPasswordField.isValid,
+
      },
+
    ];
+

                      
+
    return (
+
      <Dialog
+
        className={styles.dialog}
+
        title={intl.formatMessage(messages.dialogTitle)}
+
        actions={actions}
+
        closeOnOverlayClick
+
        onClose={onClose}
+
        closeButton={<DialogCloseButton />}
+
@import '../../../themes/mixins/loading-spinner';
+
@import '../../../themes/mixins/error-message';
+

                      
+
.dialog {
+
  font-family: var(--font-light);
+
  line-height: 1.38;
+
  b {
+
    font-family: var(--font-medium);
+
  }
+

                      
+
  .description {
+
    margin-bottom: 20px;
+
  }
+

                      
+
  .label {
+
    font-family: var(--font-medium);
+
    margin-bottom: 6px;
+
  }
+

                      
+
  .sourceWallet {
+
    background-color: rgba(94, 96, 102, 0.5);
+
    margin-bottom: 20px;
+
  }
+

                      
+
  .addresses {
+
    margin-bottom: 20px;
+
    li {
+
      overflow-wrap: break-word;
+
    }
+
  }
+

                      
+
  .amountGroup {
+
    display: inline-block;
+
    font-family: var(--font-medium);
+
    margin-bottom: 20px;
+
    width: 50%;
+
  }
+

                      
+
  .amount {
+
    color: #ea4c5b;
+
    &:after {
+
      content: ' ADA';
+
      opacity: 0.5;
+
    }
+
  }
+

                      
+
  .error {
+
    @include error-message;
+
    margin-top: 27px;
+
    text-align: center;
+
  }
+
}
+

                      
+
.submitButtonSpinning {
+
  box-shadow: none !important;
+
  @include loading-spinner('../../../assets/images/spinner-light.svg');
+
}
+
// @flow
+
import React, { Component } from 'react';
+
import type { Element } from 'react';
+
import { Select } from 'react-polymorph/lib/components/Select';
+
import { SelectSkin } from 'react-polymorph/lib/skins/simple/SelectSkin';
+
import { omit } from 'lodash';
+
import WalletsDropdownOption from './WalletsDropdownOption';
+

                      
+
import { formattedWalletAmount } from '../../../utils/formatters';
+
import Wallet from '../../../domains/Wallet';
+

                      
+
type SelectProps = {
+
  allowBlank: boolean,
+
  autoFocus: boolean,
+
  className?: string,
+
  context: any,
+
  error?: string | Element<any>,
+
  label?: string | Element<any>,
+
  isOpeningUpward: boolean,
+
  onBlur?: Function,
+
  onChange?: Function,
+
  onFocus?: Function,
+
  optionRenderer?: Function,
+
  options: Array<any>,
+
  placeholder?: string,
+
  selectionRenderer?: Function,
+
  skin?: Element<any>,
+
  theme: ?Object, // will take precedence over theme in context if passed
+
  themeId: string,
+
  themeOverrides: Object,
+
  value: string,
+
};
+

                      
+
type Props = {
+
  ...$Shape<SelectProps>,
+
  wallets: Array<$Shape<Wallet>>,
+
};
+

                      
+
type WalletOption = {
+
  label: string,
+
  detail: string,
+
  value: string,
+
};
+

                      
+
export default class WalletsDropdown extends Component<Props> {
+
  static defaultProps = {
+
    optionRenderer: ({ label, detail }: WalletOption) => (
+
      <WalletsDropdownOption label={label} detail={detail} />
+
    ),
+
    selectionRenderer: ({ label, detail }: WalletOption) => (
+
      <WalletsDropdownOption label={label} detail={detail} selected />
+
    ),
+
    skin: SelectSkin,
+
  };
+

                      
+
  render() {
+
    const { wallets, ...props } = this.props;
+
    const walletsData = wallets.map(
+
      ({ name: label, id: value, amount }: Wallet) => {
+
        const detail = formattedWalletAmount(amount);
+
        return {
+
          detail,
+
          label,
+
          value,
+
        };
+
      }
+
    );
+
    const selectOptions = omit(props, 'options');
+
    return <Select options={walletsData} {...selectOptions} />;
+
  }
+
}
+
// @flow
+
import React, { Component } from 'react';
+
import classnames from 'classnames';
+
import styles from './WalletsDropdownOption.scss';
+

                      
+
export type WalletOption = {
+
  label: string,
+
  detail: string,
+
  selected?: boolean,
+
};
+

                      
+
export default class WalletsDropdownOption extends Component<WalletOption> {
+
  render() {
+
    const { label, detail, selected } = this.props;
+
    const componentStyles = classnames(styles.component, {
+
      [styles.selected]: selected,
+
    });
+
    return (
+
      <div className={componentStyles}>
+
        <div className={styles.label}>{label}</div>
+
        <div className={styles.detail}>{detail}</div>
+
      </div>
+
    );
+
  }
+
}
+
.component {
+
  color: var(--theme-delegation-steps-choose-wallet-error-select-options-color);
+
  font-family: var(--font-regular);
+
  :global {
+
    .SimpleOptions_option {
+
      padding: 8px 20px;
+
    }
+
  }
+
}
+
.label {
+
  font-size: 14px;
+
  line-height: 1.43;
+
}
+
.detail {
+
  font-size: 12px;
+
  line-height: 1.33;
+
  opacity: 0.5;
+
}
+
.selected {
+
  color: var(--theme-delegation-steps-choose-wallet-custom-value-color);
+
  height: 100%;
+
  padding: 8px 20px;
+
}
Diff too large – View on GitHub