Skip to content

Commit 4f60863

Browse files
committed
Merge branch 'frontend-unlock-software-keystore'
2 parents cd5c60a + 7aff45f commit 4f60863

File tree

10 files changed

+159
-23
lines changed

10 files changed

+159
-23
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Replace sidebar with bottom navigation bar for mobile devices
88
- Introduce full screen selector for mobile in place of dropdown
99
- Fix wrong estimated confirmation time for ERC20 tokens.
10+
- Enable unlock test wallet in testnet
1011

1112
# v4.47.2
1213
- Linux: fix compatiblity with some versions of Mesa that are incompatible with the bundled wayland libraries

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ const Sidebar = ({
221221
<Device />
222222
</div>
223223
<span className={style.sidebarLabel}>
224-
Software keystore
224+
{t('testWallet.connect.title')}
225225
</span>
226226
</SkipForTesting>
227227
</div>
@@ -233,7 +233,7 @@ const Sidebar = ({
233233
<Eject alt={t('sidebar.leave')} />
234234
</div>
235235
<span className={style.sidebarLabel}>
236-
Eject software keystore
236+
{t('testWallet.disconnect.title')}
237237
</span>
238238
</Button>
239239
</div>

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

+15
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,21 @@
18291829
"title": "Success"
18301830
}
18311831
},
1832+
"testWallet": {
1833+
"connect": {
1834+
"description": "A software test wallet to test without a device in Testnet.",
1835+
"title": "Test wallet"
1836+
},
1837+
"disconnect": {
1838+
"description": "Removes the software test wallet.",
1839+
"title": "Disconnect test wallet"
1840+
},
1841+
"prompt": {
1842+
"button": "Unlock",
1843+
"passwordLabel": "Test Password",
1844+
"title": "Unlock test wallet"
1845+
}
1846+
},
18321847
"transaction": {
18331848
"confirmation": "Confirmations",
18341849
"details": {

frontends/web/src/routes/device/components/skipfortesting.tsx

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Copyright 2018 Shift Devices AG
3-
* Copyright 2024 Shift Crypto AG
3+
* Copyright 2024-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.
@@ -16,12 +16,12 @@
1616
*/
1717

1818
import React, { ReactNode, useContext, useState } from 'react';
19+
import { useTranslation } from 'react-i18next';
1920
import { AppContext } from '@/contexts/AppContext';
2021
import { registerTest } from '@/api/keystores';
2122
import { Button } from '@/components/forms';
2223
import { PasswordSingleInput } from '@/components/password';
2324
import { Dialog, DialogButtons } from '@/components/dialog/dialog';
24-
import { debug, runningInIOS } from '@/utils/env';
2525

2626
type TProps = {
2727
children?: ReactNode;
@@ -32,38 +32,40 @@ export const SkipForTesting = ({
3232
children,
3333
className,
3434
}: TProps) => {
35+
const { t } = useTranslation();
3536
const { isTesting } = useContext(AppContext);
3637
const [dialog, setDialog] = useState(false);
37-
const show = (debug || runningInIOS()) && isTesting;
3838
const [testPIN, setTestPIN] = useState('');
3939
const registerTestingDevice = async (e: React.SyntheticEvent) => {
4040
e.preventDefault();
4141
await registerTest(testPIN);
4242
setDialog(false);
4343
};
4444

45-
if (!show) {
45+
if (!isTesting) {
4646
return null;
4747
}
48-
const title = 'Unlock software keystore';
4948
return (
5049
<>
5150
<Button
5251
className={className}
5352
onClick={() => setDialog(true)}
5453
primary
5554
>
56-
{children ? children : title}
55+
{children ? children : t('testWallet.prompt.title')}
5756
</Button>
58-
<Dialog open={dialog} title={title} onClose={() => setDialog(false)}>
57+
<Dialog
58+
open={dialog}
59+
title={t('testWallet.prompt.title')}
60+
onClose={() => setDialog(false)}>
5961
<form onSubmit={registerTestingDevice}>
6062
<PasswordSingleInput
6163
autoFocus
62-
label="Test Password"
63-
onValidPassword={(pw) => pw ? setTestPIN(pw) : setTestPIN('')}/>
64+
label={t('testWallet.prompt.passwordLabel')}
65+
onValidPassword={(pw) => setTestPIN(pw ? pw : '')}/>
6466
<DialogButtons>
6567
<Button primary type="submit">
66-
Unlock
68+
{t('testWallet.prompt.button')}
6769
</Button>
6870
</DialogButtons>
6971
</form>

frontends/web/src/routes/settings/advanced-settings.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { EnableCustomFeesToggleSetting } from './components/advanced-settings/en
2525
import { EnableCoinControlSetting } from './components/advanced-settings/enable-coin-control-setting';
2626
import { ConnectFullNodeSetting } from './components/advanced-settings/connect-full-node-setting';
2727
import { EnableTorProxySetting } from './components/advanced-settings/enable-tor-proxy-setting';
28+
import { UnlockSoftwareKeystore } from './components/advanced-settings/unlock-software-keystore';
2829
import { RestartInTestnetSetting } from './components/advanced-settings/restart-in-testnet-setting';
2930
import { ExportLogSetting } from './components/advanced-settings/export-log-setting';
3031
import { getConfig } from '@/utils/config';
@@ -69,6 +70,8 @@ export const AdvancedSettings = ({ devices, hasAccounts }: TPagePropsWithSetting
6970
setConfig(fetchedConfig);
7071
}, [fetchedConfig]);
7172

73+
const deviceIDs = Object.keys(devices);
74+
7275
return (
7376
<GuideWrapper>
7477
<GuidedContent>
@@ -96,6 +99,7 @@ export const AdvancedSettings = ({ devices, hasAccounts }: TPagePropsWithSetting
9699
<EnableAuthSetting backendConfig={backendConfig} onChangeConfig={setConfig} />
97100
<EnableTorProxySetting proxyConfig={proxyConfig} onChangeConfig={setConfig} />
98101
<RestartInTestnetSetting backendConfig={backendConfig} onChangeConfig={setConfig} />
102+
<UnlockSoftwareKeystore deviceIDs={deviceIDs}/>
99103
<ConnectFullNodeSetting />
100104
<ExportLogSetting />
101105
</WithSettingsTabs>

frontends/web/src/routes/settings/bb02-settings.module.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
}
88

99
.skeletonWrapper {
10-
margin-bottom: var(--size-default);
10+
margin-bottom: var(--space-half);
1111
}
1212

1313

14-
@media (max-width: 768px) {
14+
@media (max-width: 768px) {
1515
.skeletonWrapper {
1616
margin-bottom: 2px;
1717
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.settingsItemButton {
2+
background-color: transparent !important;
3+
border: none;
4+
display: flex;
5+
margin-bottom: var(--space-half);
6+
height: auto;
7+
padding: 0;
8+
width: 100%;
9+
}
10+
11+
@media (max-width: 768px) {
12+
.settingsItemButton {
13+
margin-bottom: 5px;
14+
}
15+
}
16+
17+
.settingsItemButton > * {
18+
margin-bottom: 0;
19+
}
20+
21+
.chevronRight {
22+
bottom: 0;
23+
margin: auto;
24+
position: absolute;
25+
right: var(--space-quarter);
26+
top: 3px;
27+
}
28+
29+
.ejectIconRight {
30+
bottom: 0;
31+
margin: auto;
32+
position: absolute;
33+
right: var(--space-half);
34+
top: 3px;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 { useDefault } from '@/hooks/default';
19+
import { useLoad } from '@/hooks/api';
20+
import { useKeystores } from '@/hooks/backend';
21+
import { getTesting } from '@/api/backend';
22+
import { deregisterTest } from '@/api/keystores';
23+
import { SettingsItem } from '@/routes/settings/components/settingsItem/settingsItem';
24+
import { SkipForTesting } from '@/routes/device/components/skipfortesting';
25+
import { ChevronRightDark, Eject } from '@/components/icon';
26+
import styles from './unlock-software-keystore.module.css';
27+
28+
type TProps = {
29+
deviceIDs: string[];
30+
};
31+
32+
export const UnlockSoftwareKeystore = ({
33+
deviceIDs,
34+
}: TProps) => {
35+
const { t } = useTranslation();
36+
const isTesting = useDefault(useLoad(getTesting), false);
37+
const keystores = useKeystores();
38+
39+
if (!isTesting || deviceIDs.length) {
40+
return null;
41+
}
42+
if (keystores?.some(({ type }) => type === 'software')) {
43+
return (
44+
<SettingsItem
45+
settingName={t('testWallet.disconnect.title')}
46+
secondaryText={t('testWallet.disconnect.description')}
47+
onClick={() => deregisterTest()}
48+
extraComponent={
49+
<Eject
50+
className={styles.ejectIconRight}
51+
width={18}
52+
height={18}
53+
alt={t('sidebar.leave')}
54+
/>
55+
}
56+
/>
57+
);
58+
}
59+
return (
60+
<SkipForTesting className={styles.settingsItemButton}>
61+
<SettingsItem
62+
settingName={t('testWallet.connect.title')}
63+
secondaryText={t('testWallet.connect.description')}
64+
hideChevron
65+
extraComponent={
66+
<ChevronRightDark
67+
className={styles.chevronRight}
68+
width={24}
69+
height={24}
70+
/>
71+
}
72+
/>
73+
</SkipForTesting>
74+
);
75+
};

frontends/web/src/routes/settings/components/settingsItem/settingsItem.module.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
font-weight: 400;
1515
padding: 16px 24px;
1616
color: var(--color-dark);
17-
margin-bottom: var(--size-default);
17+
margin-bottom: var(--space-half);
1818
}
1919

2020
.chevronRight {

frontends/web/src/routes/settings/components/settingsItem/settingsItem.tsx

+12-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type TProps = {
2424
collapseOnSmall?: boolean;
2525
displayedValue?: string | ReactNode;
2626
extraComponent?: ReactNode;
27+
hideChevron?: boolean;
2728
hideDisplayedValueOnSmall?: boolean;
2829
onClick?: () => void;
2930
secondaryText?: string;
@@ -37,6 +38,7 @@ export const SettingsItem = ({
3738
collapseOnSmall = false,
3839
displayedValue = '',
3940
extraComponent,
41+
hideChevron = false,
4042
hideDisplayedValueOnSmall = false,
4143
onClick,
4244
secondaryText,
@@ -62,8 +64,8 @@ export const SettingsItem = ({
6264
</div>
6365
);
6466

65-
const content =
66-
(<>
67+
const content = (
68+
<>
6769
<span className={styles.content} title={title}>
6870
<div className={styles.primaryText}>{settingName}</div>
6971
{ secondaryText ? (
@@ -72,7 +74,7 @@ export const SettingsItem = ({
7274
</span>
7375
{rightContent}
7476
</>
75-
);
77+
);
7678

7779
// render as div when it's notButton
7880
// otherwise, render as button
@@ -92,11 +94,13 @@ export const SettingsItem = ({
9294
${collapseOnSmall ? styles.collapse : ''}`}
9395
onClick={onClick}>
9496
{content}
95-
<ChevronRightDark
96-
className={styles.chevronRight}
97-
width={24}
98-
height={24}
99-
/>
97+
{!hideChevron && (
98+
<ChevronRightDark
99+
className={styles.chevronRight}
100+
width={24}
101+
height={24}
102+
/>
103+
)}
100104
</button>
101105
)}
102106
</>

0 commit comments

Comments
 (0)