1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-27 10:48:22 +00:00

l10n support for xrp validation

This commit is contained in:
slowbackspace 2019-04-29 15:36:05 +02:00
parent 8d1c01bc61
commit 934df38064
6 changed files with 145 additions and 48 deletions

View File

@ -6,6 +6,8 @@ import { findDevice, getPendingAmount } from 'reducers/utils';
import { toDecimalAmount } from 'utils/formatUtils';
import { toFiatCurrency } from 'utils/fiatConverter';
import * as validators from 'utils/validators';
import l10nMessages from 'views/Wallet/views/Account/Send/validation.messages';
import l10nCommonMessages from 'views/common.messages';
import type {
Dispatch,
@ -150,11 +152,11 @@ const addressValidation = ($state: State): PayloadAction<State> => (
const { address } = state;
if (address.length < 1) {
state.errors.address = 'Address is not set';
state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_SET;
} else if (!AddressValidator.validate(address, 'XRP')) {
state.errors.address = 'Address is not valid';
state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_VALID;
} else if (address.toLowerCase() === account.descriptor.toLowerCase()) {
state.errors.address = 'Cannot send to myself';
state.errors.address = l10nMessages.TR_CANNOT_SEND_TO_MYSELF;
}
return state;
};
@ -226,9 +228,13 @@ const addressLabel = ($state: State): PayloadAction<State> => (
currentNetworkAccount.deviceState
);
if (device) {
state.infos.address = `${
device.instanceLabel
} Account #${currentNetworkAccount.index + 1}`;
state.infos.address = {
...l10nCommonMessages.TR_DEVICE_LABEL_ACCOUNT_HASH,
values: {
deviceLabel: device.instanceLabel,
number: currentNetworkAccount.index + 1,
},
};
}
} else {
// corner-case: the same derivation path is used on different networks
@ -241,11 +247,14 @@ const addressLabel = ($state: State): PayloadAction<State> => (
const { networks } = getState().localStorage.config;
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
if (device && otherNetwork) {
state.warnings.address = `Looks like it's ${
device.instanceLabel
} Account #${otherNetworkAccount.index + 1} address of ${
otherNetwork.name
} network`;
state.warnings.address = {
...l10nCommonMessages.TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL,
values: {
deviceLabel: device.instanceLabel,
number: otherNetworkAccount.index + 1,
network: otherNetwork.name,
},
};
}
}
}
@ -268,19 +277,22 @@ const amountValidation = ($state: State): PayloadAction<State> => (
const { amount } = state;
if (amount.length < 1) {
state.errors.amount = 'Amount is not set';
state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_SET;
} else if (amount.length > 0 && !validators.isNumber(amount)) {
state.errors.amount = 'Amount is not a number';
state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_A_NUMBER;
} else {
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
if (!validators.hasDecimals(state.amount, 6)) {
state.errors.amount = 'Maximum 6 decimals allowed';
state.errors.amount = {
...l10nMessages.TR_MAXIMUM_DECIMALS_ALLOWED,
values: { decimals: 6 },
};
} else if (
new BigNumber(state.total).isGreaterThan(
new BigNumber(account.balance).minus(pendingAmount)
)
) {
state.errors.amount = 'Not enough funds';
state.errors.amount = l10nMessages.TR_NOT_ENOUGH_FUNDS;
}
}
@ -288,15 +300,23 @@ const amountValidation = ($state: State): PayloadAction<State> => (
!state.errors.amount &&
new BigNumber(account.balance).minus(state.total).lt(account.reserve)
) {
state.errors.amount = `Not enough funds. Reserved amount for this account is ${
account.reserve
} ${state.networkSymbol}`;
state.errors.amount = {
...l10nMessages.TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT,
values: {
reservedAmount: account.reserve,
networkSymbol: state.networkSymbol,
},
};
}
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${
state.minAmount
} ${state.networkSymbol}`;
state.errors.amount = {
...l10nMessages.TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING,
values: {
reservedAmount: state.minAmount,
networkSymbol: state.networkSymbol,
},
};
}
return state;
@ -317,15 +337,15 @@ export const feeValidation = ($state: State): PayloadAction<State> => (
const { fee } = state;
if (fee.length < 1) {
state.errors.fee = 'Fee is not set';
state.errors.fee = l10nMessages.TR_FEE_IS_NOT_SET;
} else if (fee.length > 0 && !validators.isAbs(fee)) {
state.errors.fee = 'Fee must be an absolute number';
state.errors.fee = l10nMessages.TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER;
} else {
const gl: BigNumber = new BigNumber(fee);
if (gl.isLessThan(network.fee.minFee)) {
state.errors.fee = 'Fee is below recommended';
state.errors.fee = l10nMessages.TR_FEE_IS_BELOW_RECOMMENDED;
} else if (gl.isGreaterThan(network.fee.maxFee)) {
state.errors.fee = 'Fee is above recommended';
state.errors.fee = l10nMessages.TR_FEE_IS_ABOVE_RECOMMENDED;
}
}
return state;
@ -340,11 +360,11 @@ export const destinationTagValidation = ($state: State): PayloadAction<State> =>
const { destinationTag } = state;
if (destinationTag.length > 0 && !validators.isAbs(destinationTag)) {
state.errors.destinationTag = 'Destination tag must be an absolute number';
state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE;
}
if (parseInt(destinationTag, 10) > U_INT_32) {
state.errors.destinationTag = 'Number is too big';
state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_IS_NOT_VALID;
}
return state;
@ -386,9 +406,16 @@ export const getFeeLevels = (
const { network } = getState().selectedAccount;
if (!network) return []; // flowtype fallback
const l10nFeeMap = {
Low: l10nCommonMessages.TR_LOW_FEE,
Normal: l10nCommonMessages.TR_NORMAL_FEE,
High: l10nCommonMessages.TR_HIGH_FEE,
};
// map BlockchainFeeLevel to SendFormReducer FeeLevel
const levels = feeLevels.map(level => ({
value: level.name,
localizedValue: l10nFeeMap[level.value] || level.value,
fee: level.value,
label: `${toDecimalAmount(level.value, network.decimals)} ${network.symbol}`,
}));
@ -398,11 +425,13 @@ export const getFeeLevels = (
selected && selected.value === 'Custom'
? {
value: 'Custom',
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
fee: selected.fee,
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
}
: {
value: 'Custom',
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
fee: '0',
label: '',
};

View File

@ -3,12 +3,13 @@
import * as SEND from 'actions/constants/send';
import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype';
import type { Action, MessageDescriptor } from 'flowtype';
export type FeeLevel = {
label: string,
fee: string,
value: string,
localizedValue?: MessageDescriptor,
};
export type State = {
@ -33,9 +34,9 @@ export type State = {
destinationTag: string,
total: string,
errors: { [k: string]: string },
warnings: { [k: string]: string },
infos: { [k: string]: string },
errors: { [k: string]: MessageDescriptor },
warnings: { [k: string]: MessageDescriptor },
infos: { [k: string]: MessageDescriptor },
sending: boolean,
};

View File

@ -52,7 +52,7 @@ const TooltipContainer = styled.div`
margin-left: 6px;
`;
const getFeeInputState = (feeErrors: string, feeWarnings: string): ?string => {
const getFeeInputState = (feeErrors: boolean, feeWarnings: boolean): ?string => {
let state = null;
if (feeWarnings && !feeErrors) {
state = 'warning';
@ -63,7 +63,7 @@ const getFeeInputState = (feeErrors: string, feeWarnings: string): ?string => {
return state;
};
const getDestinationTagInputState = (errors: string, warnings: string): ?string => {
const getDestinationTagInputState = (errors: boolean, warnings: boolean): ?string => {
let state = null;
if (warnings && !errors) {
state = 'warning';
@ -90,7 +90,7 @@ const AdvancedForm = (props: Props) => {
<AdvancedSettingsWrapper>
<InputRow>
<StyledInput
state={getFeeInputState(errors.fee, warnings.fee)}
state={getFeeInputState(!!errors.fee, !!warnings.fee)}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
@ -125,7 +125,13 @@ const AdvancedForm = (props: Props) => {
</Left>
</InputLabelWrapper>
}
bottomText={errors.fee || warnings.fee || infos.fee}
bottomText={
<>
{(errors.fee && <FormattedMessage {...errors.fee} />) ||
(warnings.fee && <FormattedMessage {...warnings.fee} />) ||
(infos.fee && <FormattedMessage {...infos.fee} />)}
</>
}
value={fee}
onChange={event => onFeeChange(event.target.value)}
/>
@ -134,8 +140,8 @@ const AdvancedForm = (props: Props) => {
<InputRow>
<StyledInput
state={getDestinationTagInputState(
errors.destinationTag,
warnings.destinationTag
!!errors.destinationTag,
!!warnings.destinationTag
)}
autoComplete="off"
autoCorrect="off"
@ -172,7 +178,17 @@ const AdvancedForm = (props: Props) => {
</InputLabelWrapper>
}
bottomText={
errors.destinationTag || warnings.destinationTag || infos.destinationTag
<>
{(errors.destinationTag && (
<FormattedMessage {...errors.destinationTag} />
)) ||
(warnings.destinationTag && (
<FormattedMessage {...warnings.destinationTag} />
)) ||
(infos.destinationTag && (
<FormattedMessage {...infos.destinationTag} />
))}
</>
}
value={destinationTag}
onChange={event => onDestinationTagChange(event.target.value)}

View File

@ -202,8 +202,8 @@ const StyledIcon = styled(Icon)`
// render helpers
const getAddressInputState = (
address: string,
addressErrors: string,
addressWarnings: string
addressErrors: boolean,
addressWarnings: boolean
): ?string => {
let state = null;
if (address && !addressErrors) {
@ -218,7 +218,7 @@ const getAddressInputState = (
return state;
};
const getAmountInputState = (amountErrors: string, amountWarnings: string): ?string => {
const getAmountInputState = (amountErrors: boolean, amountWarnings: boolean): ?string => {
let state = null;
if (amountWarnings && !amountErrors) {
state = 'warning';
@ -317,13 +317,19 @@ const AccountSend = (props: Props) => {
</Title>
<InputRow>
<Input
state={getAddressInputState(address, errors.address, warnings.address)}
state={getAddressInputState(address, !!errors.address, !!warnings.address)}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
topLabel={props.intl.formatMessage(l10nCommonMessages.TR_ADDRESS)}
bottomText={errors.address || warnings.address || infos.address}
bottomText={
<>
{(errors.address && <FormattedMessage {...errors.address} />) ||
(warnings.address && <FormattedMessage {...warnings.address} />) ||
(infos.address && <FormattedMessage {...infos.address} />)}
</>
}
value={address}
onChange={event => onAddressChange(event.target.value)}
sideAddons={[
@ -335,7 +341,7 @@ const AccountSend = (props: Props) => {
</InputRow>
<AmountRow>
<Input
state={getAmountInputState(errors.amount, warnings.amount)}
state={getAmountInputState(!!errors.amount, !!warnings.amount)}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
@ -357,7 +363,13 @@ const AccountSend = (props: Props) => {
}
value={amount}
onChange={event => onAmountChange(event.target.value)}
bottomText={errors.amount || warnings.amount || infos.amount}
bottomText={
<>
{(errors.amount && <FormattedMessage {...errors.amount} />) ||
(warnings.amount && <FormattedMessage {...warnings.amount} />) ||
(infos.amount && <FormattedMessage {...infos.amount} />)}
</>
}
sideAddons={[
<SetMaxAmountButton key="icon" onClick={() => onSetMax()} isWhite={!setMax}>
{!setMax && (
@ -385,7 +397,7 @@ const AccountSend = (props: Props) => {
<LocalAmountWrapper>
<EqualsSign>=</EqualsSign>
<LocalAmountInput
state={getAmountInputState(errors.amount, warnings.amount)}
state={getAmountInputState(!!errors.amount, !!warnings.amount)}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"

View File

@ -91,6 +91,44 @@ const definedMessages: Messages = defineMessages({
id: 'TR_DATA_IS_NOT_VALID_HEX',
defaultMessage: 'Data is not valid hexadecimal',
},
TR_CANNOT_SEND_TO_MYSELF: {
id: 'TR_CANNOT_SEND_TO_MYSELF',
defaultMessage: 'Cannot send to myself',
},
TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT: {
id: 'TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT',
defaultMessage:
'Not enough funds. Reserved amount for this account is {reservedAmount} {networkSymbol}',
},
TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING: {
id: 'TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING',
defaultMessage:
'Amount is too low. Minimum amount for creating a new account is {minimalAmount} {networkSymbol}',
},
TR_FEE_IS_NOT_SET: {
id: 'TR_FEE_IS_NOT_SET',
defaultMessage: 'Fee is not set',
},
TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER: {
id: 'TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER',
defaultMessage: 'Fee must be an absolute number',
},
TR_FEE_IS_BELOW_RECOMMENDED: {
id: 'TR_FEE_IS_BELOW_RECOMMENDED',
defaultMessage: 'Fee is below recommended',
},
TR_FEE_IS_ABOVE_RECOMMENDED: {
id: 'TR_FEE_IS_ABOVE_RECOMMENDED',
defaultMessage: 'Fee is above recommended',
},
TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE: {
id: 'TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE',
defaultMessage: 'Destination tag must be an absolute number',
},
TR_DESTINATION_TAG_IS_NOT_VALID: {
id: 'TR_DESTINATION_TAG_IS_NOT_VALID',
defaultMessage: 'Destination tag is not valid',
},
});
export default definedMessages;

View File

@ -23,8 +23,9 @@ const definedMessages: Messages = defineMessages({
},
TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL: {
id: 'TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL',
defaultMessage: 'Looks like it is {deviceLabel} Account #{number} of {network}',
description: 'Example: Looks like it is My Trezor Account #1 of ETH',
defaultMessage:
'Looks like it is {deviceLabel} Account #{number} address of {network} network',
description: 'Example: Looks like it is My Trezor Account #1 address of ETH network',
},
TR_IMPORTED_ACCOUNT_HASH: {
id: 'TR_IMPORTED_ACCOUNT_HASH',