Skip to content

Commit 604c904

Browse files
committed
Merge branch 'frontend-watchonly-individual-watchonly-toggle' into staging-watchonly
2 parents 888f481 + 9879770 commit 604c904

File tree

6 files changed

+113
-49
lines changed

6 files changed

+113
-49
lines changed

backend/accounts.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ func copyBool(b *bool) *bool {
493493
// AccountSetWatch sets the account's persisted watch flag to `watch`. Set to `true` if the account
494494
// should be loaded even if its keystore is not connected.
495495
// If `watch` is set to `false`, the account is unloaded and the frontend notified.
496+
// If `watch` is set to `true`, the account is loaded (if the global watchonly flag is enabled) and the frontend notified.
496497
func (backend *Backend) AccountSetWatch(filter func(*config.Account) bool, watch *bool) error {
497498
err := backend.config.ModifyAccountsConfig(func(accountsConfig *config.AccountsConfig) error {
498499
for _, acct := range accountsConfig.Accounts {
@@ -524,10 +525,8 @@ func (backend *Backend) AccountSetWatch(filter func(*config.Account) bool, watch
524525
}
525526
}
526527

527-
if watch == nil || !*watch {
528-
backend.initAccounts(false)
529-
backend.emitAccountsStatusChanged()
530-
}
528+
backend.initAccounts(false)
529+
backend.emitAccountsStatusChanged()
531530
return nil
532531
}
533532

Lines changed: 3 additions & 0 deletions
Loading

frontends/web/src/components/icon/icon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import closeXDarkSVG from './assets/icons/close-x-dark.svg';
4444
import externalLink from './assets/icons/external-link.svg';
4545
import eyeClosedSVG from './assets/icons/eye-closed.svg';
4646
import eyeOpenedSVG from './assets/icons/eye-opened.svg';
47+
import eyeOpenedDarkSVG from './assets/icons/eye-opened-dark.svg';
4748
import globeDarkSVG from './assets/icons/globe-dark.svg';
4849
import globeLightSVG from './assets/icons/globe-light.svg';
4950
import guideSVG from './assets/icons/guide.svg';
@@ -154,6 +155,7 @@ export const EditLight = (props: ImgProps) => (<img src={editLightSVG} draggable
154155
export const ExternalLink = (props: ImgProps) => (<img src={externalLink} draggable={false} {...props} />);
155156
export const EyeClosed = (props: ImgProps) => (<img src={eyeClosedSVG} draggable={false} {...props} />);
156157
export const EyeOpened = (props: ImgProps) => (<img src={eyeOpenedSVG} draggable={false} {...props} />);
158+
export const EyeOpenedDark = (props: ImgProps) => (<img src={eyeOpenedDarkSVG} draggable={false} {...props} />);
157159
export const GlobeDark = (props: ImgProps) => (<img src={globeDarkSVG} draggable={false} {...props} />);
158160
export const GlobeLight = (props: ImgProps) => (<img src={globeLightSVG} draggable={false} {...props} />);
159161
export const GuideActive = (props: ImgProps) => (<img src={guideSVG} draggable={false} {...props} />);

frontends/web/src/locales/en/app.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,8 +1039,11 @@
10391039
},
10401040
"loading": "loading…",
10411041
"manageAccounts": {
1042+
"accountHidden": "This account has been hidden from your watch-only accounts. To see it again, please plug in your BitBox02.",
10421043
"editAccount": "Edit",
10431044
"editAccountNameTitle": "Edit account name",
1045+
"hideAccount": "Hide account",
1046+
"hideAccountDescription": "Hide your account so next time you open the BitBoxApp, it won't show up in your list of accounts. You can always add it again later.",
10441047
"noAccounts": "no accounts found",
10451048
"settings": {
10461049
"hideTokens": "Hide tokens",

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,34 @@
140140
margin-left: 10px;
141141
outline: none;
142142
}
143+
144+
145+
.watchOnlyContainer {
146+
align-items: center;
147+
display: flex;
148+
justify-content: space-between;
149+
margin-top: var(--space-half);
150+
}
151+
152+
.watchOnlyTitle {
153+
color: var(--color-secondary);
154+
margin: 0;
155+
margin-left: var(--space-quarter);
156+
}
157+
158+
.watchOnlyNote {
159+
font-size: var(--size-small);
160+
margin: 0;
161+
margin-top: var(--space-half);
162+
}
163+
164+
.watchOnlyAccountHidden {
165+
background-color: var(--color-success);
166+
margin-top: var(--space-half);
167+
padding: var(--space-half);
168+
}
169+
170+
.watchOnlyAccountHidden p {
171+
color: var(--color-alt);
172+
margin: 0;
173+
}

frontends/web/src/routes/settings/manage-accounts.tsx

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as backendAPI from '../../api/backend';
2222
import { alertUser } from '../../components/alert/Alert';
2323
import { Button, Input } from '../../components/forms';
2424
import Logo from '../../components/icon/logo';
25+
import { EyeOpenedDark } from '../../components/icon';
2526
import { GuideWrapper, GuidedContent, Header, Main } from '../../components/layout';
2627
import { Toggle } from '../../components/toggle/toggle';
2728
import { Dialog, DialogButtons } from '../../components/dialog/dialog';
@@ -34,7 +35,6 @@ import { MobileHeader } from '../settings/components/mobile-header';
3435
import Guide from './manage-account-guide';
3536
import style from './manage-accounts.module.css';
3637

37-
3838
interface ManageAccountsProps {
3939
deviceIDs: string[];
4040
hasAccounts: boolean;
@@ -47,21 +47,20 @@ type TShowTokens = {
4747
}
4848

4949
interface State {
50-
editAccountCode?: string;
51-
editAccountNewName: string;
5250
editErrorMessage?: string;
5351
accounts: accountAPI.IAccount[];
5452
showTokens: TShowTokens;
5553
watchonly?: boolean;
54+
currentlyEditedAccount?: accountAPI.IAccount
5655
}
5756

5857
class ManageAccounts extends Component<Props, State> {
5958
public readonly state: State = {
60-
editAccountNewName: '',
6159
editErrorMessage: undefined,
6260
accounts: [],
6361
showTokens: {},
6462
watchonly: undefined,
63+
currentlyEditedAccount: undefined
6564
};
6665

6766
private fetchAccounts = () => {
@@ -74,7 +73,7 @@ class ManageAccounts extends Component<Props, State> {
7473
}
7574

7675
private renderAccounts = (accounts: accountAPI.IAccount[]) => {
77-
const { watchonly, showTokens } = this.state;
76+
const { showTokens } = this.state;
7877
const { t } = this.props;
7978
return accounts.filter(account => !account.isToken).map(account => {
8079
const active = account.active;
@@ -93,7 +92,8 @@ class ManageAccounts extends Component<Props, State> {
9392
</div>
9493
<button
9594
className={style.editBtn}
96-
onClick={() => this.setState({ editAccountCode: account.code, editAccountNewName: account.name })}>
95+
onClick={() => this.setState({ currentlyEditedAccount: account })}
96+
>
9797
{t('manageAccounts.editAccount')}
9898
</button>
9999
<Toggle
@@ -105,19 +105,6 @@ class ManageAccounts extends Component<Props, State> {
105105
this.toggleAccount(account.code, !active)
106106
.then(() => event.target.disabled = false);
107107
}} />
108-
{ watchonly ? (<>
109-
Hide account (TODO: move this to the edit dialog):
110-
<Toggle
111-
checked={!account.watch}
112-
className={style.toggle}
113-
id={account.code}
114-
onChange={async (event) => {
115-
event.target.disabled = true;
116-
await this.setWatch(account.code, !account.watch);
117-
event.target.disabled = false;
118-
}} />
119-
</>
120-
) : null }
121108
{active && account.coinCode === 'eth' ? (
122109
<div className={style.tokenSection}>
123110
<div className={`${style.tokenContainer} ${tokensVisible ? style.tokenContainerOpen : ''}`}>
@@ -138,6 +125,41 @@ class ManageAccounts extends Component<Props, State> {
138125
});
139126
};
140127

128+
private renderWatchOnlyToggle = () => {
129+
const { t } = this.props;
130+
const { currentlyEditedAccount } = this.state;
131+
if (!currentlyEditedAccount) {
132+
return;
133+
}
134+
135+
return (
136+
<div className="flex flex-column">
137+
<div className={style.watchOnlyContainer}>
138+
<div className="flex">
139+
<EyeOpenedDark width={18} height={18} />
140+
<p className={style.watchOnlyTitle}>{t('manageAccounts.hideAccount')}</p>
141+
</div>
142+
<Toggle
143+
checked={!currentlyEditedAccount.watch}
144+
className={style.toggle}
145+
id={currentlyEditedAccount.code}
146+
onChange={async (event) => {
147+
event.target.disabled = true;
148+
await this.setWatch(currentlyEditedAccount.code, !currentlyEditedAccount.watch);
149+
this.setState({ currentlyEditedAccount: { ...currentlyEditedAccount, watch: !currentlyEditedAccount.watch } });
150+
event.target.disabled = false;
151+
}}
152+
/>
153+
</div>
154+
<p className={style.watchOnlyNote}>{t('manageAccounts.hideAccountDescription')}</p>
155+
{
156+
!currentlyEditedAccount.watch && <div className={style.watchOnlyAccountHidden}>
157+
<p>{t('manageAccounts.accountHidden')}</p>
158+
</div>
159+
}
160+
</div>);
161+
};
162+
141163
private toggleAccount = (accountCode: string, active: boolean) => {
142164
return backendAPI.setAccountActive(accountCode, active).then(({ success, errorMessage }) => {
143165
if (success) {
@@ -224,9 +246,13 @@ class ManageAccounts extends Component<Props, State> {
224246

225247
private updateAccountName = (event: React.SyntheticEvent) => {
226248
event.preventDefault();
227-
const { editAccountCode, editAccountNewName } = this.state;
249+
const { currentlyEditedAccount } = this.state;
228250

229-
backendAPI.renameAccount(editAccountCode!, editAccountNewName!)
251+
if (!currentlyEditedAccount) {
252+
return;
253+
}
254+
255+
backendAPI.renameAccount(currentlyEditedAccount.code, currentlyEditedAccount.name)
230256
.then(result => {
231257
if (!result.success) {
232258
if (result.errorCode) {
@@ -238,16 +264,15 @@ class ManageAccounts extends Component<Props, State> {
238264
}
239265
this.fetchAccounts();
240266
this.setState({
241-
editAccountCode: undefined,
242-
editAccountNewName: '',
243267
editErrorMessage: undefined,
268+
currentlyEditedAccount: undefined,
244269
});
245270
});
246271
};
247272

248273
public render() {
249274
const { t, deviceIDs, hasAccounts } = this.props;
250-
const { accounts, editAccountCode, editAccountNewName, editErrorMessage } = this.state;
275+
const { accounts, editErrorMessage, currentlyEditedAccount, watchonly } = this.state;
251276
const accountsByKeystore = getAccountsByKeystore(accounts);
252277
return (
253278
<GuideWrapper>
@@ -264,46 +289,47 @@ class ManageAccounts extends Component<Props, State> {
264289
<View fullscreen={false}>
265290
<ViewContent>
266291
<WithSettingsTabs deviceIDs={deviceIDs} hideMobileMenu hasAccounts={hasAccounts}>
267-
<>
268-
<Button
269-
className={style.addAccountBtn}
270-
primary
271-
onClick={() => route('/add-account', true)}>
272-
{t('addAccount.title')}
273-
</Button>
274-
275-
{
276-
accountsByKeystore.map(keystore => (<React.Fragment key={keystore.keystore.rootFingerprint}>
292+
<Button
293+
className={style.addAccountBtn}
294+
primary
295+
onClick={() => route('/add-account', true)}>
296+
{t('addAccount.title')}
297+
</Button>
298+
{
299+
accountsByKeystore.map(keystore => (
300+
<React.Fragment key={keystore.keystore.rootFingerprint}>
277301
<p>{keystore.keystore.name}</p>
278302
<div className="box slim divide m-bottom-large">
279-
{ this.renderAccounts(keystore.accounts) }
303+
{this.renderAccounts(keystore.accounts)}
280304
</div>
281-
</React.Fragment>))
282-
}
283-
{ accounts.length === 0 ? t('manageAccounts.noAccounts') : null }
284-
305+
</React.Fragment>
306+
))
307+
}
308+
{currentlyEditedAccount && (
285309
<Dialog
286-
open={!!(editAccountCode)}
287-
onClose={() => this.setState({ editAccountCode: undefined, editAccountNewName: '', editErrorMessage: undefined })}
310+
open={!!(currentlyEditedAccount)}
311+
onClose={() => this.setState({ currentlyEditedAccount: undefined })}
288312
title={t('manageAccounts.editAccountNameTitle')}>
289313
<form onSubmit={this.updateAccountName}>
290314
<Message type="error" hidden={!editErrorMessage}>
291315
{editErrorMessage}
292316
</Message>
293317
<Input
294-
onInput={e => this.setState({ editAccountNewName: e.target.value })}
295-
value={editAccountNewName} />
318+
onInput={e => this.setState({ currentlyEditedAccount: { ...currentlyEditedAccount, name: e.target.value } })}
319+
value={currentlyEditedAccount.name}
320+
/>
321+
{watchonly && this.renderWatchOnlyToggle()}
296322
<DialogButtons>
297323
<Button
298-
disabled={!editAccountNewName}
324+
disabled={!currentlyEditedAccount.name}
299325
primary
300326
type="submit">
301327
{t('button.update')}
302328
</Button>
303329
</DialogButtons>
304330
</form>
305331
</Dialog>
306-
</>
332+
)}
307333
</WithSettingsTabs>
308334
</ViewContent>
309335
</View>

0 commit comments

Comments
 (0)