Skip to content

Commit 3d00463

Browse files
committed
frontend: added hiding amounts feature
A feature to hide amounts (fiat & coin) on all screens except essential parts of send screen, improving privacy when using the app.
1 parent 3df5496 commit 3d00463

File tree

25 files changed

+280
-38
lines changed

25 files changed

+280
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44
- Drop support for SAI token
55
- Log Javascript console messages in the app log.txt
6+
- Add amounts hiding feature to enhance privacy when using the app in public
67

78
## 4.39.0
89
- Bundle BitBox02 firmware version v9.15.0

frontends/web/src/components/accountselector/accountselector.module.css

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
.balance, .balanceSingleValue{
2-
margin-left: auto;
1+
.balance, .balanceSingleValue {
32
color: var(--color-secondary);
3+
margin-left: auto;
4+
text-transform: uppercase;
45
}
56

67
.balanceSingleValue {

frontends/web/src/components/accountselector/accountselector.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
import { FunctionComponent, useEffect, useState } from 'react';
17+
import { FunctionComponent, useContext, useEffect, useState } from 'react';
1818
import { useTranslation } from 'react-i18next';
19+
import Select, { components, SingleValueProps, OptionProps, SingleValue, DropdownIndicatorProps } from 'react-select';
1920
import { AccountCode, IAccount } from '../../api/account';
2021
import { Button } from '../forms';
21-
import Select, { components, SingleValueProps, OptionProps, SingleValue, DropdownIndicatorProps } from 'react-select';
2222
import Logo from '../icon/logo';
23+
import AppContext from '../../contexts/AppContext';
2324
import styles from './accountselector.module.css';
2425

2526
export type TOption = {
@@ -39,29 +40,31 @@ type TAccountSelector = {
3940
}
4041

4142
const SelectSingleValue: FunctionComponent<SingleValueProps<TOption>> = (props) => {
43+
const { hideAmounts } = useContext(AppContext);
4244
const { label, coinCode, balance } = props.data;
4345
return (
4446
<div className={styles.singleValueContainer}>
4547
<components.SingleValue {...props}>
4648
<div className={styles.valueContainer}>
4749
{coinCode ? <Logo coinCode={coinCode} alt={coinCode} /> : null}
4850
<span className={styles.selectLabelText}>{label}</span>
49-
{coinCode && balance && <span className={styles.balanceSingleValue}>{balance}</span>}
51+
{coinCode && balance && <span className={styles.balanceSingleValue}>{hideAmounts ? `*** ${coinCode}` : balance}</span>}
5052
</div>
5153
</components.SingleValue>
5254
</div>
5355
);
5456
};
5557

5658
const SelectOption: FunctionComponent<OptionProps<TOption>> = (props) => {
59+
const { hideAmounts } = useContext(AppContext);
5760
const { label, coinCode, balance } = props.data;
5861

5962
return (
6063
<components.Option {...props}>
6164
<div className={styles.valueContainer}>
6265
{coinCode ? <Logo coinCode={coinCode} alt={coinCode} /> : null}
6366
<span className={styles.selectLabelText}>{label}</span>
64-
{coinCode && balance && <span className={styles.balance}>{balance}</span>}
67+
{coinCode && balance && <span className={styles.balance}>{hideAmounts ? `*** ${coinCode}` : balance}</span>}
6568
</div>
6669
</components.Option>
6770
);

frontends/web/src/components/amount/amount.test.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
17-
import { describe, expect, it } from 'vitest';
16+
import { useContext } from 'react';
17+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
1818
import { render } from '@testing-library/react';
1919
import { Amount } from './amount';
2020
import { CoinUnit, ConversionUnit } from './../../api/account';
2121

22+
vi.mock('react', () => ({
23+
...vi.importActual('react'),
24+
useContext: vi.fn(),
25+
createContext: vi.fn()
26+
}));
27+
28+
2229
const validateSpacing = (values: string[], elements: Element[]) => {
2330
// each element in `values` is an expected
2431
// "spaced" element. E.g:
@@ -41,6 +48,22 @@ const validateSpacing = (values: string[], elements: Element[]) => {
4148

4249
describe('Amount formatting', () => {
4350

51+
beforeEach(() => {
52+
(useContext as Mock).mockReturnValue({ hideAmounts: false });
53+
});
54+
55+
describe('hide amounts', () => {
56+
57+
it('should render triple-asterisks (***) when amount is set to be hidden', () => {
58+
(useContext as Mock).mockReturnValue({ hideAmounts: true });
59+
const { container } = render(
60+
<Amount amount="1'340.25" unit={'EUR'} />
61+
);
62+
expect(container).toHaveTextContent('***');
63+
});
64+
65+
});
66+
4467
describe('sat amounts', () => {
4568
let coins: CoinUnit[] = ['sat', 'tsat'];
4669
coins.forEach((coin) => {
@@ -255,4 +278,9 @@ describe('Amount formatting', () => {
255278

256279
});
257280
});
281+
282+
afterEach(() => {
283+
vi.clearAllMocks();
284+
});
285+
258286
});

frontends/web/src/components/amount/amount.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import style from './amount.module.css';
16+
import { useContext } from 'react';
17+
import AppContext from '../../contexts/AppContext';
18+
1719
import { CoinUnit, ConversionUnit } from './../../api/account';
20+
import style from './amount.module.css';
1821

1922
type TProps = {
2023
amount: string;
2124
unit: CoinUnit | ConversionUnit;
2225
removeBtcTrailingZeroes?: boolean;
26+
alwaysShowAmounts?: boolean
2327
};
2428

25-
export const Amount = ({ amount, unit, removeBtcTrailingZeroes }: TProps) => {
29+
export const Amount = ({ amount, unit, removeBtcTrailingZeroes, alwaysShowAmounts = false }: TProps) => {
30+
const { hideAmounts } = useContext(AppContext);
2631
const formatSats = (amount: string): JSX.Element => {
2732
const blocks: JSX.Element[] = [];
2833
const blockSize = 3;
@@ -52,6 +57,10 @@ export const Amount = ({ amount, unit, removeBtcTrailingZeroes }: TProps) => {
5257
</span>;
5358
};
5459

60+
if (hideAmounts && !alwaysShowAmounts) {
61+
return '***';
62+
}
63+
5564
switch (unit) {
5665
case 'BTC':
5766
case 'TBTC':

frontends/web/src/components/balance/balance.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import { Amount } from '../../components/amount/amount';
2222
import style from './balance.module.css';
2323

2424
type TProps = {
25-
balance?: IBalance;
26-
noRotateFiat?: boolean;
25+
balance?: IBalance;
26+
noRotateFiat?: boolean;
2727
}
2828

2929
export const Balance = ({
@@ -46,11 +46,17 @@ export const Balance = ({
4646
<Amount
4747
amount={balance.available.amount}
4848
unit={balance.available.unit}
49-
removeBtcTrailingZeroes/>
49+
removeBtcTrailingZeroes
50+
/>
5051
</td>
5152
<td className={style.availableUnit}>{balance.available.unit}</td>
5253
</tr>
53-
<FiatConversion amount={balance.available} tableRow noAction={noRotateFiat} noBtcZeroes/>
54+
<FiatConversion
55+
amount={balance.available}
56+
tableRow
57+
noAction={noRotateFiat}
58+
noBtcZeroes
59+
/>
5460
</tbody>
5561
</table>
5662
{

frontends/web/src/components/forms/button.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
will-change: background-color, color;
2424
}
2525

26+
.button:hover {
27+
cursor: pointer;
28+
}
29+
2630
.button:focus {
2731
outline: none;
2832
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.button {
2+
display: flex;
3+
}
4+
5+
.button img {
6+
width: 18px;
7+
height: 18px;
8+
}
9+
10+
.button span {
11+
width: 96px;
12+
margin-left: var(--space-eight);
13+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2023 Shift Crypto AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useContext } from 'react';
18+
import { useTranslation } from 'react-i18next';
19+
import { useLoad } from '../../hooks/api';
20+
import AppContext from '../../contexts/AppContext';
21+
import { Button } from '../forms/button';
22+
import { EyeClosed, EyeOpened } from '../icon';
23+
import { getConfig } from '../../utils/config';
24+
import styles from './hideamountsbutton.module.css';
25+
26+
export const HideAmountsButton = () => {
27+
const { t } = useTranslation();
28+
const { hideAmounts, toggleHideAmounts } = useContext(AppContext);
29+
const config = useLoad(getConfig);
30+
return (
31+
<>
32+
{
33+
config && config.frontend.allowHideAmounts ?
34+
<Button className={styles.button} onClick={toggleHideAmounts} transparent>
35+
{hideAmounts ?
36+
<><EyeClosed /> <span>{t('newSettings.appearance.hideAmounts.showAmounts')}</span></> :
37+
<><EyeOpened /> <span>{t('newSettings.appearance.hideAmounts.hideAmounts')}</span></>
38+
}
39+
</Button> :
40+
null
41+
}
42+
</>
43+
44+
);
45+
};
Lines changed: 4 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)