Skip to content

Commit

Permalink
fix: Amount input correctly handles numbers and dots (#481)
Browse files Browse the repository at this point in the history
* fix: Amount input correctly handles numbers and dots

* fix: Sending amount with trailing zero and balance check with correct units

* fix: Switch to big.js for unit conversion

* fix: remove Pi from amount dropdown units

* fix: move bigjs to shared

* fix: run yarn

Co-authored-by: Charlie Varley <charlie.varley@iota.org>
  • Loading branch information
obany and cvarley100 committed Mar 17, 2021
1 parent bf27066 commit 6060a8d
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 69 deletions.
54 changes: 29 additions & 25 deletions packages/shared/components/inputs/Amount.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="typescript">
import { Unit } from '@iota/unit-converter'
import { Input, Text } from 'shared/components'
import { convertUnitsNoE } from 'shared/lib/units'
export let amount = undefined
export let unit = Unit.Mi
Expand All @@ -10,20 +11,17 @@
export let maxClick = () => {}
export let error = ''
export let disabled = false
export let autofocus = false
const Units = Object.values(Unit).filter((x) => x !== 'Pi')
let dropdown = false
function onKey(e) {
if (e.keyCode === 8 || e.target.value.length <= 12) {
return true
} else {
e.target.value = e.target.value.substring(0, 12)
}
}
const clickOutside = () => {
dropdown = false
}
const onSelect = (index) => {
amount = convertUnitsNoE(amount, unit, index)
unit = index
}
</script>
Expand All @@ -32,10 +30,6 @@
amount-input {
nav {
border-radius: 10px;
&.active {
@apply opacity-100;
@apply pointer-events-auto;
}
button {
&:hover,
&.active {
Expand All @@ -51,31 +45,41 @@
<amount-input class="relative block {classes}">
<Input
{error}
type="number"
placeholder={label || locale('general.amount')}
on:keydown={onKey}
bind:value={amount}
{disabled} />
maxlength={17}
{disabled}
{autofocus}
integer={unit === Unit.i}
float={unit !== Unit.i} />
<actions class="absolute right-0 top-2.5 h-8 flex flex-row items-center text-12 text-gray-500 dark:text-white">
<button on:click={maxClick} class={`pr-3 ${disabled ? "cursor-auto" : "hover:text-blue-500 cursor-pointer"}`} {disabled}>{locale('actions.max').toUpperCase()}</button>
<button
on:click={maxClick}
class={`pr-3 ${disabled ? 'cursor-auto' : 'hover:text-blue-500 focus:text-blue-500 cursor-pointer'}`}
{disabled}>{locale('actions.max').toUpperCase()}</button>
<button
on:click={(e) => {
e.preventDefault()
e.stopPropagation()
dropdown = !dropdown
}}
class={`w-10 h-full text-center px-2 border-l border-solid border-gray-500 ${disabled ? "cursor-auto" : "cursor-pointer"}`}
class={`w-10 h-full text-center px-2 border-l border-solid border-gray-500 ${disabled ? 'cursor-auto' : 'hover:text-blue-500 focus:text-blue-500 cursor-pointer'}`}
{disabled}>
{unit}
<nav
class:active={dropdown && !disabled}
class="absolute w-10 overflow-y-auto bg-white border border-solid border-gray-500 pointer-events-none opacity-0 z-10 text-left top-10 right-0">
{#each Object.values(Unit) as _unit}
<button class="text-center w-full py-2" on:click={() => onSelect(_unit)} class:active={unit === _unit}>
<Text type="p" smaller>{_unit}</Text>
</button>
{/each}
</nav>
{#if !disabled && dropdown}
<nav
class="absolute w-10 overflow-y-auto bg-white border border-solid border-gray-500 z-10 text-left top-10 right-0">
{#each Units as _unit}
<button
id={_unit}
class="text-center w-full py-2"
on:click={() => onSelect(_unit)}
class:active={unit === _unit}>
<Text type="p" smaller>{_unit}</Text>
</button>
{/each}
</nav>
{/if}
</button>
</actions>
</amount-input>
32 changes: 23 additions & 9 deletions packages/shared/components/inputs/Input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,38 @@
export let type = 'text'
export let error
export let maxlength = null
export let numeric = false
export let float = false
export let integer = false
export let autofocus = false
export let submitHandler = undefined
export let disabled = false
let inputElement
const handleInput = (event) => {
value = event.target.value
const handleInput = (e) => {
value = e.target.value
}
const onKeyPress = (e) => {
// if the input is numeric, we accept only numbers and enter press
if (numeric && e.keyCode !== 13 && (e.which < 48 || e.which > 57)) {
e.preventDefault()
}
if (e.keyCode === 13 && submitHandler) {
submitHandler()
if (e.keyCode !== 8) {
const isReturn = e.keyCode === 13
if (isReturn && submitHandler) {
submitHandler()
}
if (maxlength && value.length >= maxlength) {
e.preventDefault()
}
if ((float || integer) && !isReturn) {
// if the input is float, we accept one dot
if (float && (e.keyCode === 46 || e.keyCode === 190)) {
if (value.indexOf('.') >= 0) {
e.preventDefault()
}
} else if (e.keyCode < 48 || e.keyCode > 57) {
// if float or interger we accept numbers
e.preventDefault()
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/components/inputs/Password.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
export let locale = undefined
export let maxlength = undefined
export let error = null
export let numeric = false
export let integer = false
export let autofocus = false
export let submitHandler = undefined
export let disabled = false
Expand Down Expand Up @@ -61,7 +61,7 @@
{type}
bind:value
{maxlength}
{numeric}
{integer}
{autofocus}
{disabled}
placeholder={placeholder || locale('general.password')}
Expand Down
29 changes: 29 additions & 0 deletions packages/shared/lib/units.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Unit, convertUnits } from '@iota/unit-converter'
import Big from 'big.js'

// Set this to avoid small numbers switching to exponential format
Big.NE = -20

/**
* IOTA Units Map
Expand Down Expand Up @@ -64,3 +68,28 @@ const getUnit = (value: number): Unit => {

return bestUnits
}

/**
* Convert the value to different units.
* @param value The value to convert.
* @param fromUnit The form unit.
* @param toUnit The to unit.
* @returns The formatted unit.
*/
export const convertUnitsNoE = (value: number, fromUnit: Unit, toUnit: Unit): string => {
if (value === 0) {
return "0";
}

if (!value) {
return "";
}

if (fromUnit === toUnit) {
return value.toString();
}

const scaledValue = new Big(value).times(UNIT_MAP[fromUnit].val).div(UNIT_MAP[toUnit].val)

return toUnit === Unit.i ? scaledValue.toFixed(0) : scaledValue.toString();
}
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"author": "IOTA Foundation <contact@iota.org>",
"license": "Apache-2.0",
"dependencies": {
"big.js": "^6.0.3",
"@iota/unit-converter": "^1.0.0-beta.30",
"chart.js": "^2.9.3",
"qr.js": "0.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@
showRevealToggle
{locale}
maxlength="6"
numeric
integer
placeholder={locale('views.settings.changePincode.currentPincode')}
disabled={pinCodeBusy} />
<Password
Expand All @@ -353,7 +353,7 @@
showRevealToggle
{locale}
maxlength="6"
numeric
integer
placeholder={locale('views.settings.changePincode.newPincode')}
disabled={pinCodeBusy} />
<Password
Expand All @@ -362,7 +362,7 @@
showRevealToggle
{locale}
maxlength="6"
numeric
integer
placeholder={locale('views.settings.changePincode.confirmNewPincode')}
disabled={pinCodeBusy} />
<div class="flex flex-row items-center">
Expand Down
68 changes: 38 additions & 30 deletions packages/shared/routes/dashboard/wallet/views/Send.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { accountRoute, walletRoute } from 'shared/lib/router'
import type { TransferProgressEventType } from 'shared/lib/typings/events'
import { AccountRoutes, WalletRoutes } from 'shared/lib/typings/routes'
import { convertUnitsNoE } from 'shared/lib/units'
import { ADDRESS_LENGTH, validateBech32Address } from 'shared/lib/utils'
import { isTransferring, transferState, WalletAccount } from 'shared/lib/wallet'
import { getContext, onMount } from 'svelte'
Expand All @@ -24,12 +25,15 @@
let selectedSendType = SEND_TYPE.EXTERNAL
let unit = Unit.Mi
let amount = convertUnits($sendParams.amount, Unit.i, unit).toString()
let amount = $sendParams.amount === 0 ? '' : convertUnitsNoE($sendParams.amount, Unit.i, unit)
let to = undefined
let amountError = ''
let addressPrefix = ($account ?? $accounts[0]).depositAddress.split('1')[0]
let addressError = ''
// This looks odd but sets a reactive dependency on amount, so when it changes the error will clear
$: amount, amountError = ''
let transferSteps: {
[key in TransferProgressEventType | 'Complete']: {
label: string
Expand Down Expand Up @@ -95,38 +99,41 @@
amountError = locale('error.send.amountNoFloat')
} else {
let amountAsFloat = Number.parseFloat(amount)
if (amountAsFloat.toString() !== amount) {
if (Number.isNaN(amountAsFloat)) {
amountError = locale('error.send.amountInvalidFormat')
} else if (amountAsFloat > from.balance) {
amountError = locale('error.send.amountTooHigh')
} else if (amountAsFloat <= 0) {
amountError = locale('error.send.amountZero')
}
} else {
const amountAsI = convertUnits(amountAsFloat, unit, Unit.i)
if (amountAsI > from.balance) {
amountError = locale('error.send.amountTooHigh')
} else if (amountAsI <= 0) {
amountError = locale('error.send.amountZero')
}
if (selectedSendType === SEND_TYPE.EXTERNAL) {
// Validate address length
if ($sendParams.address.length !== ADDRESS_LENGTH) {
addressError = locale('error.send.addressLength', {
values: {
length: ADDRESS_LENGTH,
},
})
} else if (!validateBech32Address(addressPrefix, $sendParams.address)) {
addressError = locale('error.send.wrongAddressFormat', {
values: {
prefix: addressPrefix,
},
})
if (selectedSendType === SEND_TYPE.EXTERNAL) {
// Validate address length
if ($sendParams.address.length !== ADDRESS_LENGTH) {
addressError = locale('error.send.addressLength', {
values: {
length: ADDRESS_LENGTH,
},
})
} else if (!validateBech32Address(addressPrefix, $sendParams.address)) {
addressError = locale('error.send.wrongAddressFormat', {
values: {
prefix: addressPrefix,
},
})
}
}
}
if (!amountError && !addressError) {
$sendParams.amount = convertUnits(amountAsFloat, unit, Unit.i)
if (!amountError && !addressError) {
$sendParams.amount = amountAsI
if (selectedSendType === SEND_TYPE.INTERNAL) {
internalTransfer(from.id, to.id, $sendParams.amount)
} else {
send(from.id, $sendParams.address, $sendParams.amount)
if (selectedSendType === SEND_TYPE.INTERNAL) {
internalTransfer(from.id, to.id, $sendParams.amount)
} else {
send(from.id, $sendParams.address, $sendParams.amount)
}
}
}
}
Expand All @@ -147,7 +154,7 @@
}
}
const handleMaxClick = () => {
amount = convertUnits(from.balance, Unit.i, unit).toString()
amount = convertUnitsNoE(from.balance, Unit.i, unit)
}
onMount(() => {
to = $accounts.length === 2 ? accountsDropdownItems[from.id === $accounts[0].id ? 1 : 0] : to
Expand Down Expand Up @@ -196,7 +203,8 @@
maxClick={handleMaxClick}
{locale}
classes="mb-2"
disabled={$isTransferring} />
disabled={$isTransferring}
autofocus />
{#if selectedSendType === SEND_TYPE.INTERNAL}
<Dropdown
value={to?.label || ''}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,11 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==

big.js@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.0.3.tgz#8b4d99ac7023668e0e465d3f78c23b8ac29ad381"
integrity sha512-n6yn1FyVL1EW2DBAr4jlU/kObhRzmr+NNRESl65VIOT8WBJj/Kezpx2zFdhJUqYI6qrtTW7moCStYL5VxeVdPA==

bignumber.js@*:
version "9.0.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
Expand Down

0 comments on commit 6060a8d

Please sign in to comment.