Skip to content

Commit 35ed7e8

Browse files
committed
Merge branch 'frontend-keystore-component'
2 parents 38e251e + 8db33aa commit 35ed7e8

File tree

10 files changed

+137
-101
lines changed

10 files changed

+137
-101
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
border: 1px solid;
44
display: inline-block;
55
font-size: var(--size-small);
6+
flex-shrink: 0;
67
line-height: 1;
78
padding: var(--space-eight) 10px;
89
white-space: nowrap;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.keystore {
2+
font-size: var(--size-default);
3+
gap: var(--space-quarter);
4+
word-break: break-word;
5+
}
6+
7+
.keystoreName {
8+
color: inherit;
9+
}
10+
11+
.keystore small {
12+
color: var(--color-secondary);
13+
font-size: var(--size-xsmall);
14+
}
15+
16+
@media (min-width: 1200px) {
17+
.keystore {
18+
--size-default: 20px;
19+
}
20+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright 2025 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 { useTranslation } from 'react-i18next';
18+
import type { TKeystore } from '@/api/account';
19+
import { isAmbiguousName, TAccountsByKeystore } from '@/routes/account/utils';
20+
import { Badge } from '@/components/badge/badge';
21+
import { USBSuccess } from '@/components/icon';
22+
import style from './connected-keystore.module.css';
23+
24+
type Props = {
25+
accountsByKeystore: TAccountsByKeystore[];
26+
className?: string;
27+
connectedIconOnly?: boolean;
28+
keystore: TKeystore;
29+
};
30+
31+
export const ConnectedKeystore = ({
32+
accountsByKeystore,
33+
className,
34+
connectedIconOnly,
35+
keystore,
36+
}: Props) => {
37+
const { t } = useTranslation();
38+
const classNames = className ? `${style.keystore} ${className}` : style.keystore;
39+
40+
return (
41+
<span className={classNames}>
42+
<span className={style.keystoreName}>{keystore.name}</span>
43+
{isAmbiguousName(keystore.name, accountsByKeystore) ? (
44+
// Disambiguate accounts group by adding the fingerprint.
45+
// The most common case where this would happen is when adding accounts from the
46+
// same seed using different passphrases.
47+
<>
48+
{' '}
49+
<small>({keystore.rootFingerprint})</small>
50+
</>
51+
) : null}
52+
{keystore.connected && (
53+
<>
54+
{' '}
55+
<Badge
56+
icon={props => <USBSuccess {...props} />}
57+
title={t('device.keystoreConnected')}
58+
type="success">
59+
{connectedIconOnly ? undefined : t('device.keystoreConnected')}
60+
</Badge>
61+
</>
62+
)}
63+
</span>
64+
);
65+
};

frontends/web/src/components/sidebar/sidebar.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import { deregisterTest } from '@/api/keystores';
2525
import { getVersion } from '@/api/bitbox02';
2626
import { debug } from '@/utils/env';
2727
import { AppLogoInverted, Logo } from '@/components/icon/logo';
28-
import { CloseXWhite, Cog, CogGray, Coins, Device, Eject, Linechart, RedDot, ShieldGray, USBSuccess } from '@/components/icon';
29-
import { getAccountsByKeystore, isAmbiguousName } from '@/routes/account/utils';
28+
import { CloseXWhite, Cog, CogGray, Coins, Device, Eject, Linechart, RedDot, ShieldGray } from '@/components/icon';
29+
import { getAccountsByKeystore } from '@/routes/account/utils';
3030
import { SkipForTesting } from '@/routes/device/components/skipfortesting';
31-
import { Badge } from '@/components/badge/badge';
3231
import { AppContext } from '@/contexts/AppContext';
3332
import { Button } from '@/components/forms';
33+
import { ConnectedKeystore } from '../keystore/connected-keystore';
3434
import style from './sidebar.module.css';
3535

3636
type SidebarProps = {
@@ -141,24 +141,12 @@ const Sidebar = ({
141141
{ accountsByKeystore.map(keystore => (
142142
<div key={`keystore-${keystore.keystore.rootFingerprint}`}>
143143
<div className={style.sidebarHeaderContainer}>
144-
<span
144+
<ConnectedKeystore
145+
accountsByKeystore={accountsByKeystore}
145146
className={style.sidebarHeader}
146-
hidden={!keystore.accounts.length}>
147-
<span className="p-right-quarter">
148-
{`${keystore.keystore.name} `}
149-
{ isAmbiguousName(keystore.keystore.name, accountsByKeystore) ? (
150-
// Disambiguate accounts group by adding the fingerprint.
151-
// The most common case where this would happen is when adding accounts from the
152-
// same seed using different passphrases.
153-
<> ({keystore.keystore.rootFingerprint})</>
154-
) : null }
155-
</span>
156-
<Badge
157-
className={keystore.keystore.connected ? style.sidebarIconVisible : style.sidebarIconHidden}
158-
icon={props => <USBSuccess {...props} />}
159-
type="success"
160-
title={t('device.keystoreConnected')} />
161-
</span>
147+
keystore={keystore.keystore}
148+
connectedIconOnly={true}
149+
/>
162150
</div>
163151
{ keystore.accounts.map(acc => (
164152
<GetAccountLink key={`account-${acc.code}`} {...acc} handleSidebarItemClick={handleSidebarItemClick} />

frontends/web/src/routes/account/summary/accountssummary.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Copyright 2018 Shift Devices AG
3-
* Copyright 2023-2024 Shift Crypto AG
3+
* Copyright 2023-2025 Shift Crypto AG
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import { Entry } from '@/components/guide/entry';
3535
import { Guide } from '@/components/guide/guide';
3636
import { HideAmountsButton } from '@/components/hideamountsbutton/hideamountsbutton';
3737
import { AppContext } from '@/contexts/AppContext';
38-
import { getAccountsByKeystore, isAmbiguousName } from '@/routes/account/utils';
38+
import { getAccountsByKeystore } from '@/routes/account/utils';
3939
import { RatesContext } from '@/contexts/RatesContext';
4040
import { ContentWrapper } from '@/components/contentwrapper/contentwrapper';
4141
import { GlobalBanners } from '@/components/banners';
@@ -214,11 +214,10 @@ export const AccountsSummary = ({
214214
(accountsByKeystore.map(({ keystore, accounts }) =>
215215
(
216216
<KeystoreBalance
217-
keystoreDisambiguatorName={isAmbiguousName(keystore.name, accountsByKeystore) ? keystore.rootFingerprint : undefined}
218-
connected={keystore.connected}
219-
keystoreName={keystore.name}
220217
key={keystore.rootFingerprint}
221218
accounts={accounts}
219+
accountsByKeystore={accountsByKeystore}
220+
keystore={keystore}
222221
keystoreBalance={accountsBalanceSummary?.keystoresBalance[keystore.rootFingerprint]}
223222
balances={balances}
224223
/>

frontends/web/src/routes/account/summary/keystorebalance.tsx

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2023 Shift Crypto AG
2+
* Copyright 2023-2025 Shift Crypto AG
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
1616

1717
import { useTranslation } from 'react-i18next';
1818
import * as accountApi from '@/api/account';
19-
import { getAccountsPerCoin } from '@/routes/account/utils';
19+
import type { TKeystore } from '@/api/account';
20+
import { getAccountsPerCoin, TAccountsByKeystore } from '@/routes/account/utils';
2021
import { Balances } from './accountssummary';
2122
import { BalanceRow } from './balancerow';
2223
import { SubTotalRow } from './subtotalrow';
2324
import { Amount } from '@/components/amount/amount';
2425
import { Skeleton } from '@/components/skeleton/skeleton';
25-
import { Badge } from '@/components/badge/badge';
26-
import { USBSuccess } from '@/components/icon';
26+
import { ConnectedKeystore } from '@/components/keystore/connected-keystore';
2727
import style from './accountssummary.module.css';
2828

2929
const TotalBalance = ({ total, fiatUnit }: accountApi.TKeystoreBalance) => {
@@ -41,21 +41,19 @@ const TotalBalance = ({ total, fiatUnit }: accountApi.TKeystoreBalance) => {
4141
};
4242

4343
type TProps = {
44-
accounts: accountApi.IAccount[],
45-
connected: boolean;
46-
keystoreName: string;
47-
keystoreBalance?: accountApi.TKeystoreBalance,
48-
balances?: Balances,
49-
keystoreDisambiguatorName?: string
44+
accounts: accountApi.IAccount[];
45+
accountsByKeystore: TAccountsByKeystore[];
46+
keystore: TKeystore;
47+
keystoreBalance?: accountApi.TKeystoreBalance;
48+
balances?: Balances;
5049
}
5150

5251
export const KeystoreBalance = ({
52+
accountsByKeystore,
5353
accounts,
54-
connected,
55-
keystoreName,
54+
keystore,
5655
keystoreBalance,
5756
balances,
58-
keystoreDisambiguatorName
5957
}: TProps) => {
6058
const { t } = useTranslation();
6159

@@ -65,17 +63,11 @@ export const KeystoreBalance = ({
6563
return (
6664
<div>
6765
<div className={style.accountName}>
68-
<p>{keystoreName} {keystoreDisambiguatorName && `(${keystoreDisambiguatorName})`}</p>
69-
{connected ? (
70-
<Badge
71-
icon={props => <USBSuccess {...props} />}
72-
type="success"
73-
>
74-
{t('device.keystoreConnected')}
75-
</Badge>
76-
) :
77-
null
78-
}
66+
<p>
67+
<ConnectedKeystore
68+
accountsByKeystore={accountsByKeystore}
69+
keystore={keystore} />
70+
</p>
7971
</div>
8072
<div className={style.balanceTable}>
8173
<table className={style.table}>

frontends/web/src/routes/accounts/all-accounts.module.css

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,14 @@
1010
background-color: var(--background-secondary);
1111
}
1212

13-
.keystoreHeader {
14-
color: var(--color-tertiary);
15-
display: flex;
16-
font-weight: 500;
17-
gap: 4px;
18-
}
19-
2013
.keystoreName {
21-
color: var(--color-text);
14+
color: var(--color-tertiary);
2215
}
2316

2417
.accountsList {
2518
display: flex;
2619
flex-direction: column;
27-
margin-top: var(--space-half);
20+
margin: var(--space-half) 0;
2821
gap: var(--space-half);
2922
}
3023

@@ -37,6 +30,7 @@
3730
}
3831

3932
.accountIcon {
33+
display: block;
4034
flex: 0 0 auto;
4135
margin-right: var(--space-quarter);
4236
}

frontends/web/src/routes/accounts/all-accounts.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ import * as accountApi from '@/api/account';
2222
import { getBalance } from '@/api/account';
2323
import { Logo } from '@/components/icon/logo';
2424
import { View, ViewContent } from '@/components/view/view';
25-
import { getAccountsByKeystore, isAmbiguousName } from '@/routes/account/utils';
25+
import { getAccountsByKeystore } from '@/routes/account/utils';
2626
import { HideAmountsButton } from '@/components/hideamountsbutton/hideamountsbutton';
2727
import { Main, Header } from '@/components/layout';
28-
import { Badge } from '@/components/badge/badge';
29-
import { ChevronRightDark, USBSuccess } from '@/components/icon/icon';
28+
import { ChevronRightDark } from '@/components/icon/icon';
3029
import { AllAccountsGuide } from '@/routes/accounts/all-accounts-guide';
3130
import { useMountedRef } from '@/hooks/mount';
3231
import { AmountWithUnit } from '@/components/amount/amount-with-unit';
32+
import { ConnectedKeystore } from '@/components/keystore/connected-keystore';
3333
import styles from './all-accounts.module.css';
3434

3535
type AllAccountsProps = {
@@ -101,23 +101,11 @@ export const AllAccounts = ({ accounts = [] }: AllAccountsProps) => {
101101
<div className={styles.container}>
102102
{accountsByKeystore.map(keystore => (
103103
<div key={`keystore-${keystore.keystore.rootFingerprint}`}>
104-
<div className={styles.keystoreHeader}>
105-
{keystore.keystore.name}
106-
{ isAmbiguousName(keystore.keystore.name, accountsByKeystore) ? (
107-
// Disambiguate accounts group by adding the fingerprint.
108-
// The most common case where this would happen is when adding accounts from the
109-
// same seed using different passphrases.
110-
<> ({keystore.keystore.rootFingerprint})</>
111-
) : null }
112-
{keystore.keystore.connected && (
113-
<Badge
114-
icon={props => <USBSuccess {...props} />}
115-
type="success">
116-
{t('device.keystoreConnected')}
117-
</Badge>
118-
)}
119-
120-
</div>
104+
<ConnectedKeystore
105+
accountsByKeystore={accountsByKeystore}
106+
keystore={keystore.keystore}
107+
className={styles.keystoreName}
108+
/>
121109
<div className={styles.accountsList}>
122110
{keystore.accounts.map(account => (
123111
<AccountItem key={`account-${account.code}`} account={account} />

frontends/web/src/routes/settings/manage-accounts.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
width: 50%;
3030
}
3131

32+
.connectedKeystore {
33+
flex-wrap: wrap;
34+
}
35+
3236
.keystoreName {
3337
margin-bottom: var(--space-eight);
3438
margin-right: var(--space-quarter);

0 commit comments

Comments
 (0)