Skip to content

Commit

Permalink
Updating #812 branch (#819)
Browse files Browse the repository at this point in the history
* fix: amount input field to properly interpret BTC (#800)

* feat: add SchedulerConfirmationModal component for starting scheduled sweep (#803)

* feat: add SchedulerConfirmationModal component for starting scheduled sweep

* removed dead code

* modal is centered and design changes

* used ConfirmModal and pre-written styles

* refactor: SchedulerConfirmationModal.tsx

minor production error

* build(deps): update dependencies (#813)

 @emotion/react          ^11.11.4  →  ^11.13.0
 @types/react             ^18.3.2  →   ^18.3.3
 formik                    ^2.4.5  →    ^2.4.6
 qrcode                    ^1.5.3  →    ^1.5.4
 react-bootstrap           ^2.9.2  →   ^2.10.4
 react-router-bootstrap   ^0.26.2  →   ^0.26.3
 react-router-dom         ^6.23.1  →   ^6.26.1
 husky            ^8.0.3  →    ^9.1.4
 i18next        ^23.11.4  →  ^23.13.0
 lint-staged     ^14.0.1  →   ^15.2.9
 prettier         ^3.2.5  →    ^3.3.3
 react-i18next   ^14.1.1  →   ^15.0.1

* refactor: align utxo list and modal components (#815)

* refactor: externalize UtxoIcon component and utxoTags function

* refactor: externalize UtxoConfirmations component

* refactor: reuse utxo icons in Jar details and UTXO list

* refactor(ui): simpler checkbox in utxo list

* refactor(send): vertically align balance

* refactor(send): tooltip for shortened addresses

* refactor(ui): externalize UtxoTags component

* ui(send): show considered UTXOs before performing transaction (#807)

---------

Co-authored-by: apX13_ <[email protected]>
Co-authored-by: Thebora Kompanioni <[email protected]>
  • Loading branch information
3 people authored Aug 21, 2024
1 parent ead874a commit 48f7095
Show file tree
Hide file tree
Showing 25 changed files with 1,216 additions and 923 deletions.
2 changes: 0 additions & 2 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
1,128 changes: 656 additions & 472 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@
"@types/jest": "^29.5.12",
"@types/node": "^17.0.35",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"conventional-changelog": "^5.1.0",
"http-proxy-middleware": "^3.0.0",
"husky": "^8.0.3",
"husky": "^9.1.4",
"jest-watch-typeahead": "^2.2.2",
"jest-websocket-mock": "^2.5.0",
"lint-staged": "^14.0.1",
"prettier": "^3.2.5",
"lint-staged": "^15.2.9",
"prettier": "^3.3.3",
"react-scripts": "^5.0.1",
"typescript": "^4.8.4"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/react": "^11.13.0",
"@table-library/react-table-library": "^4.1.7",
"bootstrap": "^5.3.3",
"classnames": "^2.5.1",
"formik": "^2.4.5",
"i18next": "^23.11.4",
"formik": "^2.4.6",
"i18next": "^23.13.0",
"i18next-browser-languagedetector": "^8.0.0",
"qrcode": "^1.5.3",
"qrcode": "^1.5.4",
"react": "^18.3.1",
"react-bootstrap": "^2.9.2",
"react-bootstrap": "^2.10.4",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.1",
"react-router-bootstrap": "^0.26.2",
"react-router-dom": "^6.23.1"
"react-i18next": "^15.0.1",
"react-router-bootstrap": "^0.26.3",
"react-router-dom": "^6.26.1"
},
"scripts": {
"dev:start": "REACT_APP_JAM_DEV_MODE=true npm start",
Expand All @@ -53,7 +53,7 @@
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"postinstall": "husky install",
"prepare": "husky",
"lint": "prettier --check --no-error-on-unmatched-pattern 'src/**/*.{js,jsx,ts,tsx,json,css,md}'",
"format": "prettier --write --no-error-on-unmatched-pattern 'src/**/*.{js,jsx,ts,tsx,json,css,md}'",
"version": "node scripts/changelog.mjs && git checkout -b \"prepare-v${npm_package_version}-$(date +%s)\" && git add --all && git commit --message \"chore(release): v${npm_package_version}\" && git push --set-upstream origin $(git branch --show-current)",
Expand Down
21 changes: 21 additions & 0 deletions src/components/BitcoinAmountInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,25 @@ describe('<BitcoinAmountInput />', () => {
expect(inputElement.dataset.displayUnit).toBe(undefined)
expect(inputElement.dataset.displayValue).toBe('')
})

it('amount 1.0 should be interpreted as 1 BTC', async () => {
setup({
label: 'test-label',
})
const inputElement = screen.getByLabelText('test-label')

await user.type(inputElement, '1.0')

expect(inputElement).toHaveFocus()
expect(inputElement.dataset.value).toBe('100000000')
expect(inputElement.dataset.displayUnit).toBe('BTC')
expect(inputElement.dataset.displayValue).toBe(`1.0`)

await user.tab()

expect(inputElement).not.toHaveFocus()
expect(inputElement.dataset.value).toBe('100000000')
expect(inputElement.dataset.displayUnit).toBe('BTC')
expect(inputElement.dataset.displayValue).toBe(`1.00 000 000`)
})
})
143 changes: 77 additions & 66 deletions src/components/BitcoinAmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,78 @@ const BitcoinAmountInput = forwardRef(
: undefined
}, [field, inputType])

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
setInputType({
type: 'text',
inputMode: 'decimal',
})

let displayValue = String(field.value?.value || '')
if (isValidNumber(field.value?.value)) {
displayValue = formatBtcDisplayValue(field.value!.value!)
}

form.setFieldValue(
field.name,
{
...field.value,
displayValue,
},
false,
)
field.onBlur(e)
}

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const rawUserInputOrEmpty = e.target.value ?? ''
const validNumberRegex = /^-?\d*\.?\d*$/
if (!validNumberRegex.test(rawUserInputOrEmpty)) {
return
}
const floatValueOrNan = parseFloat(rawUserInputOrEmpty)
if (!isValidNumber(floatValueOrNan)) {
form.setFieldValue(
field.name,
{
...field.value,
value: null,
userRawInputValue: e.target.value,
displayValue: e.target.value,
},
true,
)
return
} else {
const value: number = floatValueOrNan
let numberValues: string | undefined
const unit =
rawUserInputOrEmpty.includes('.') && parseFloat(rawUserInputOrEmpty)
? unitFromValue(String(rawUserInputOrEmpty))
: unitFromValue(String(value))
if (unit === 'BTC') {
const splitted = String(value).split('.')
const [integerPart, fractionalPart = ''] = splitted
const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8)
numberValues = `${integerPart}${paddedFractionalPart}`
} else {
numberValues = value.toLocaleString('en-US', {
maximumFractionDigits: 0,
useGrouping: false,
})
}

form.setFieldValue(
field.name,
{
value: parseInt(numberValues, 10),
userRawInputValue: e.target.value,
displayValue: e.target.value,
},
true,
)
}
}

return (
<>
<rb.InputGroup hasValidation={true}>
Expand All @@ -77,11 +149,12 @@ const BitcoinAmountInput = forwardRef(
data-display-value={field.value?.displayValue}
name={field.name}
autoComplete="off"
type={inputType.type}
inputMode={inputType.inputMode}
className={classNames('slashed-zeroes', className)}
value={
inputType.type === 'text' ? field.value?.displayValue ?? '' : String(field.value?.userRawInputValue ?? '')
inputType.type === 'text'
? (field.value?.displayValue ?? '')
: String(field.value?.userRawInputValue ?? '')
}
placeholder={placeholder}
min={displayInputUnit === 'BTC' ? '0.00000001' : '1'}
Expand All @@ -92,70 +165,8 @@ const BitcoinAmountInput = forwardRef(
onFocus={() => {
setInputType({ type: 'number' })
}}
onBlur={(e) => {
setInputType({
type: 'text',
inputMode: 'decimal',
})

let displayValue = String(field.value?.value || '')
if (isValidNumber(field.value?.value)) {
displayValue = formatBtcDisplayValue(field.value!.value!)
}

form.setFieldValue(
field.name,
{
...field.value,
displayValue,
},
false,
)
field.onBlur(e)
}}
onChange={(e) => {
const valueOrNan = parseFloat(e.target.value ?? '')

if (!isValidNumber(valueOrNan)) {
form.setFieldValue(
field.name,
{
...field.value,
value: null,
userRawInputValue: e.target.value,
displayValue: e.target.value,
},
true,
)
return
} else {
const value: number = valueOrNan

let numberValues: string | undefined
const unit = unitFromValue(String(value))
if (unit === 'BTC') {
const splitted = String(value).split('.')
const [integerPart, fractionalPart = ''] = splitted
const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8)
numberValues = `${integerPart}${paddedFractionalPart}`
} else {
numberValues = value.toLocaleString('en-US', {
maximumFractionDigits: 0,
useGrouping: false,
})
}

form.setFieldValue(
field.name,
{
value: parseInt(numberValues, 10),
userRawInputValue: e.target.value,
displayValue: e.target.value,
},
true,
)
}
}}
onBlur={handleBlur}
onChange={handleChange}
/>
{children}
<rb.Form.Control.Feedback type="invalid">
Expand Down
16 changes: 4 additions & 12 deletions src/components/Jam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import FeeConfigModal from './settings/FeeConfigModal'

import styles from './Jam.module.css'
import { useFeeConfigValues } from '../hooks/Fees'
import SchedulerConfirmationModal from './SchedulerConfirmationModal'

const DEST_ADDRESS_COUNT_PROD = 3
const DEST_ADDRESS_COUNT_TEST = 1
Expand Down Expand Up @@ -535,19 +536,10 @@ export default function Jam({ wallet }: JamProps) {
})}

<p className="text-secondary mb-4">{t('scheduler.description_fees')}</p>

<rb.Button
className="w-100 mb-4"
variant="dark"
size="lg"
type="submit"
<SchedulerConfirmationModal
onConfirm={handleSubmit}
disabled={isOperationDisabled || isSubmitting || !isValid}
>
<div className="d-flex justify-content-center align-items-center">
{t('scheduler.button_start')}
<Sprite symbol="caret-right" width="24" height="24" className="ms-1" />
</div>
</rb.Button>
/>
</rb.Form>
</>
)}
Expand Down
43 changes: 41 additions & 2 deletions src/components/PaymentConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PropsWithChildren, useMemo } from 'react'
import { PropsWithChildren, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import * as rb from 'react-bootstrap'
import Sprite from './Sprite'
Expand All @@ -10,6 +10,9 @@ import { AmountSats, BitcoinAddress } from '../libs/JmWalletApi'
import { jarInitial } from './jars/Jar'
import { isValidNumber } from '../utils'
import styles from './PaymentConfirmModal.module.css'
import { Utxos } from '../context/WalletContext'
import { SelectableUtxo, UtxoListDisplay } from './Send/ShowUtxos'
import Divider from './Divider'

const feeRange: (txFee: TxFee, txFeeFactor: number) => [number, number] = (txFee, txFeeFactor) => {
if (txFee.unit !== 'sats/kilo-vbyte') {
Expand Down Expand Up @@ -55,6 +58,37 @@ const useMiningFeeText = ({ tx_fees, tx_fees_factor }: Pick<FeeValues, 'tx_fees'
}, [t, tx_fees, tx_fees_factor])
}

type ReviewConsideredUtxosProps = {
utxos: SelectableUtxo[]
}
const ReviewConsideredUtxos = ({ utxos }: ReviewConsideredUtxosProps) => {
const { t } = useTranslation()
const settings = useSettings()
const [isOpen, setIsOpen] = useState<boolean>(false)

return (
<rb.Row className="mt-2">
<rb.Col xs={4} md={3} className="text-end">
<strong>{t('show_utxos.considered_utxos')}</strong>
</rb.Col>
<rb.Col xs={8} md={9}>
<Divider toggled={isOpen} onToggle={() => setIsOpen((current) => !current)} />
</rb.Col>
<rb.Collapse in={isOpen}>
<rb.Col xs={12} className="mt-2">
<UtxoListDisplay
utxos={utxos}
settings={settings}
onToggle={() => {
// No-op since these UTXOs are only for review and are not selectable
}}
/>
</rb.Col>
</rb.Collapse>
</rb.Row>
)
}

interface PaymentDisplayInfo {
sourceJarIndex?: JarIndex
destination: BitcoinAddress | string
Expand All @@ -64,6 +98,7 @@ interface PaymentDisplayInfo {
numCollaborators?: number
feeConfigValues?: FeeValues
showPrivacyInfo?: boolean
consideredUtxos?: Utxos
}

interface PaymentConfirmModalProps extends ConfirmModalProps {
Expand All @@ -80,6 +115,7 @@ export function PaymentConfirmModal({
numCollaborators,
feeConfigValues,
showPrivacyInfo = true,
consideredUtxos = [],
},
children,
...confirmModalProps
Expand All @@ -97,7 +133,7 @@ export function PaymentConfirmModal({

return (
<ConfirmModal {...confirmModalProps}>
<rb.Container className="mt-2">
<rb.Container className="mt-2" fluid>
{showPrivacyInfo && (
<rb.Row className="mt-2 mb-3">
<rb.Col xs={12} className="text-center">
Expand Down Expand Up @@ -207,6 +243,9 @@ export function PaymentConfirmModal({
</rb.Col>
</rb.Row>
)}
{consideredUtxos.length !== 0 && (
<ReviewConsideredUtxos utxos={consideredUtxos.map((it) => ({ ...it, checked: false, selectable: false }))} />
)}
{children && (
<rb.Row>
<rb.Col xs={12}>{children}</rb.Col>
Expand Down
21 changes: 21 additions & 0 deletions src/components/SchedulerConfirmationModal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.modalHeader {
display: flex !important;
justify-content: flex-start !important;
background-color: transparent !important;
padding: 1.25rem !important;
}

.modalTitle {
width: 100%;
font-size: 1rem !important;
font-weight: 400 !important;
color: var(--bs-body-color) !important;
display: flex;
justify-content: space-between;
align-items: center;
}

.modalBody {
color: var(--bs-body-color) !important;
text-align: left !important;
}
Loading

0 comments on commit 48f7095

Please sign in to comment.