Merge pull request #1785 from lbryio/wallet-encryption
Add basic wallet encryption flows
This commit is contained in:
commit
71bc347224
16 changed files with 483 additions and 34 deletions
|
@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
* Wallet Encryption/Decryption user flows ([#1785](https://github.com/lbryio/lbry-desktop/pull/1785))
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"formik": "^0.10.4",
|
||||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-redux": "lbryio/lbry-redux#e0909b08647a790d155f3189b9f9bf0b3e55bd17",
|
||||
"lbry-redux": "lbryio/lbry-redux#b4fffe863df316bc73183567ab978221ee623b8c",
|
||||
"localforage": "^1.7.1",
|
||||
"mime": "^2.3.1",
|
||||
"mixpanel-browser": "^2.17.1",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectDaemonVersionMatched } from 'redux/selectors/app';
|
||||
import { selectNotification } from 'lbry-redux';
|
||||
import { doCheckDaemonVersion } from 'redux/actions/app';
|
||||
import { doCheckDaemonVersion, doNotifyUnlockWallet } from 'redux/actions/app';
|
||||
import SplashScreen from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -11,6 +11,10 @@ const select = state => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
checkDaemonVersion: () => dispatch(doCheckDaemonVersion()),
|
||||
notifyUnlockWallet: () => dispatch(doNotifyUnlockWallet()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SplashScreen);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(SplashScreen);
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import * as React from 'react';
|
||||
import { Lbry, MODALS } from 'lbry-redux';
|
||||
import LoadScreen from './internal/load-screen';
|
||||
import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
||||
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
|
||||
import ModalUpgrade from 'modal/modalUpgrade';
|
||||
import ModalDownloading from 'modal/modalDownloading';
|
||||
|
||||
type Props = {
|
||||
checkDaemonVersion: () => Promise<any>,
|
||||
notifyUnlockWallet: () => Promise<any>,
|
||||
notification: ?{
|
||||
id: string,
|
||||
},
|
||||
|
@ -17,6 +19,7 @@ type State = {
|
|||
message: string,
|
||||
isRunning: boolean,
|
||||
isLagging: boolean,
|
||||
launchedModal: boolean,
|
||||
};
|
||||
|
||||
export class SplashScreen extends React.PureComponent<Props, State> {
|
||||
|
@ -28,6 +31,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
|
|||
message: __('Connecting'),
|
||||
isRunning: false,
|
||||
isLagging: false,
|
||||
launchedModal: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -38,8 +42,11 @@ export class SplashScreen extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
_updateStatusCallback(status) {
|
||||
const { notifyUnlockWallet } = this.props;
|
||||
const { launchedModal } = this.state;
|
||||
|
||||
const startupStatus = status.startup_status;
|
||||
if (startupStatus.code == 'started') {
|
||||
if (startupStatus.code === 'started') {
|
||||
// Wait until we are able to resolve a name before declaring
|
||||
// that we are done.
|
||||
// TODO: This is a hack, and the logic should live in the daemon
|
||||
|
@ -61,6 +68,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) {
|
||||
const format =
|
||||
status.blockchain_status.blocks_behind == 1 ? '%s block behind' : '%s blocks behind';
|
||||
|
@ -69,6 +77,17 @@ export class SplashScreen extends React.PureComponent<Props, State> {
|
|||
details: __(format, status.blockchain_status.blocks_behind),
|
||||
isLagging: startupStatus.is_lagging,
|
||||
});
|
||||
} else if (startupStatus.code === 'waiting_for_wallet_unlock') {
|
||||
this.setState({
|
||||
message: __('Unlock Wallet'),
|
||||
details: __('Please unlock your wallet to proceed.'),
|
||||
isLagging: false,
|
||||
isRunning: true,
|
||||
});
|
||||
|
||||
if (launchedModal === false) {
|
||||
this.setState({ launchedModal: true }, () => notifyUnlockWallet());
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
message: __('Network Loading'),
|
||||
|
@ -114,6 +133,7 @@ export class SplashScreen extends React.PureComponent<Props, State> {
|
|||
in the modals won't work. */}
|
||||
{isRunning && (
|
||||
<React.Fragment>
|
||||
{notificationId === MODALS.WALLET_UNLOCK && <ModalWalletUnlock />}
|
||||
{notificationId === MODALS.INCOMPATIBLE_DAEMON && <ModalIncompatibleDaemon />}
|
||||
{notificationId === MODALS.UPGRADE && <ModalUpgrade />}
|
||||
{notificationId === MODALS.DOWNLOADING && <ModalDownloading />}
|
||||
|
|
|
@ -23,6 +23,9 @@ import ModalSendTip from '../modalSendTip';
|
|||
import ModalPublish from '../modalPublish';
|
||||
import ModalOpenExternalLink from '../modalOpenExternalLink';
|
||||
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
|
||||
import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
|
||||
import ModalWalletDecrypt from 'modal/modalWalletDecrypt';
|
||||
import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
||||
|
||||
type Props = {
|
||||
modal: string,
|
||||
|
@ -165,6 +168,12 @@ class ModalRouter extends React.PureComponent<Props> {
|
|||
return <ModalConfirmTransaction {...notificationProps} />;
|
||||
case MODALS.CONFIRM_THUMBNAIL_UPLOAD:
|
||||
return <ModalConfirmThumbnailUpload {...notificationProps} />;
|
||||
case MODALS.WALLET_ENCRYPT:
|
||||
return <ModalWalletEncrypt {...notificationProps} />;
|
||||
case MODALS.WALLET_DECRYPT:
|
||||
return <ModalWalletDecrypt {...notificationProps} />;
|
||||
case MODALS.WALLET_UNLOCK:
|
||||
return <ModalWalletUnlock {...notificationProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
23
src/renderer/modal/modalWalletDecrypt/index.js
Normal file
23
src/renderer/modal/modalWalletDecrypt/index.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doHideNotification,
|
||||
doWalletStatus,
|
||||
doWalletDecrypt,
|
||||
selectWalletDecryptSucceeded,
|
||||
} from 'lbry-redux';
|
||||
import ModalWalletDecrypt from './view';
|
||||
|
||||
const select = state => ({
|
||||
walletDecryptSucceded: selectWalletDecryptSucceeded(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideNotification()),
|
||||
decryptWallet: password => dispatch(doWalletDecrypt(password)),
|
||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ModalWalletDecrypt);
|
63
src/renderer/modal/modalWalletDecrypt/view.jsx
Normal file
63
src/renderer/modal/modalWalletDecrypt/view.jsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Form, FormRow, FormField } from 'component/common/form';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
unlockWallet: string => void,
|
||||
walletDecryptSucceded: boolean,
|
||||
updateWalletStatus: boolean,
|
||||
};
|
||||
|
||||
class ModalWalletDecrypt extends React.PureComponent<Props> {
|
||||
state = {
|
||||
submitted: false, // Prior actions could be marked complete
|
||||
};
|
||||
|
||||
submitDecryptForm() {
|
||||
this.setState({ submitted: true });
|
||||
this.props.decryptWallet();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (state.submitted && props.walletDecryptSucceded === true) {
|
||||
props.closeModal();
|
||||
props.updateWalletStatus();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { closeModal, walletDecryptSucceded } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
contentLabel={__('Decrypt Wallet')}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__('Decrypt Wallet')}
|
||||
abortButtonLabel={__('Cancel')}
|
||||
onConfirmed={() => this.submitDecryptForm()}
|
||||
onAborted={closeModal}
|
||||
>
|
||||
<Form onSubmit={() => this.submitDecryptForm()}>
|
||||
{__(
|
||||
'Your wallet has been encrypted with a local password, performing this action will remove this password.'
|
||||
)}
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Learn more')}
|
||||
href="https://lbry.io/faq/wallet-encryption"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalWalletDecrypt;
|
26
src/renderer/modal/modalWalletEncrypt/index.js
Normal file
26
src/renderer/modal/modalWalletEncrypt/index.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doHideNotification,
|
||||
doWalletStatus,
|
||||
doWalletEncrypt,
|
||||
selectWalletEncryptPending,
|
||||
selectWalletEncryptSucceeded,
|
||||
selectWalletEncryptResult,
|
||||
} from 'lbry-redux';
|
||||
import ModalWalletEncrypt from './view';
|
||||
|
||||
const select = state => ({
|
||||
walletEncryptSucceded: selectWalletEncryptSucceeded(state),
|
||||
walletEncryptResult: selectWalletEncryptResult(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideNotification()),
|
||||
encryptWallet: password => dispatch(doWalletEncrypt(password)),
|
||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ModalWalletEncrypt);
|
144
src/renderer/modal/modalWalletEncrypt/view.jsx
Normal file
144
src/renderer/modal/modalWalletEncrypt/view.jsx
Normal file
|
@ -0,0 +1,144 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Form, FormRow, FormField } from 'component/common/form';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
unlockWallet: string => void,
|
||||
walletEncryptSucceded: boolean,
|
||||
walletEncryptResult: boolean,
|
||||
updateWalletStatus: boolean,
|
||||
};
|
||||
|
||||
class ModalWalletEncrypt extends React.PureComponent<Props> {
|
||||
state = {
|
||||
newPassword: null,
|
||||
newPasswordConfirm: null,
|
||||
passwordMismatch: false,
|
||||
understandConfirmed: false,
|
||||
understandError: false,
|
||||
submitted: false, // Prior actions could be marked complete
|
||||
failMessage: false,
|
||||
};
|
||||
|
||||
onChangeNewPassword(event) {
|
||||
this.setState({ newPassword: event.target.value });
|
||||
}
|
||||
|
||||
onChangeNewPasswordConfirm(event) {
|
||||
this.setState({ newPasswordConfirm: event.target.value });
|
||||
}
|
||||
|
||||
onChangeUnderstandConfirm(event) {
|
||||
this.setState({
|
||||
understandConfirmed: /^.?i understand.?$/i.test(event.target.value),
|
||||
});
|
||||
}
|
||||
|
||||
submitEncryptForm() {
|
||||
const { state } = this;
|
||||
|
||||
let invalidEntries = false;
|
||||
|
||||
if (state.newPassword !== state.newPasswordConfirm) {
|
||||
this.setState({ passwordMismatch: true });
|
||||
invalidEntries = true;
|
||||
}
|
||||
|
||||
if (state.understandConfirmed === false) {
|
||||
this.setState({ understandError: true });
|
||||
invalidEntries = true;
|
||||
}
|
||||
|
||||
if (invalidEntries === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ submitted: true });
|
||||
this.props.encryptWallet(state.newPassword);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (state.submitted) {
|
||||
if (props.walletEncryptSucceded === true) {
|
||||
props.closeModal();
|
||||
props.updateWalletStatus();
|
||||
} else if (props.walletEncryptSucceded === false) {
|
||||
// See https://github.com/lbryio/lbry/issues/1307
|
||||
this.setState({ failMessage: 'Unable to encrypt wallet.' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { closeModal } = this.props;
|
||||
|
||||
const { passwordMismatch, understandError, failMessage } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
contentLabel={__('Encrypt Wallet')}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__('Encrypt Wallet')}
|
||||
abortButtonLabel={__('Cancel')}
|
||||
onConfirmed={() => this.submitEncryptForm()}
|
||||
onAborted={closeModal}
|
||||
>
|
||||
<Form onSubmit={() => this.submitEncryptForm()}>
|
||||
{__(
|
||||
'Encrypting your wallet will require a password to access your local wallet data when LBRY starts. Please enter a new password for your wallet.'
|
||||
)}
|
||||
<FormRow padded>
|
||||
<FormField
|
||||
stretch
|
||||
error={passwordMismatch === true ? 'Passwords do not match' : false}
|
||||
label={__('New Password')}
|
||||
type="password"
|
||||
name="wallet-new-password"
|
||||
onChange={event => this.onChangeNewPassword(event)}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow padded>
|
||||
<FormField
|
||||
stretch
|
||||
error={passwordMismatch === true ? 'Passwords do not match' : false}
|
||||
label={__('Confirm Password')}
|
||||
type="password"
|
||||
name="wallet-new-password-confirm"
|
||||
onChange={event => this.onChangeNewPasswordConfirm(event)}
|
||||
/>
|
||||
</FormRow>
|
||||
<br />
|
||||
{__(
|
||||
'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
|
||||
)}
|
||||
<FormRow padded>
|
||||
<FormField
|
||||
stretch
|
||||
error={understandError === true ? 'You must enter "I understand"' : false}
|
||||
label={__('Enter "I understand"')}
|
||||
type="text"
|
||||
name="wallet-understand"
|
||||
onChange={event => this.onChangeUnderstandConfirm(event)}
|
||||
/>
|
||||
</FormRow>
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Learn more')}
|
||||
href="https://lbry.io/faq/wallet-encryption"
|
||||
/>
|
||||
</div>
|
||||
{failMessage && <div className="error-text">{__(failMessage)}</div>}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalWalletEncrypt;
|
24
src/renderer/modal/modalWalletUnlock/index.js
Normal file
24
src/renderer/modal/modalWalletUnlock/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doHideNotification,
|
||||
doWalletUnlock,
|
||||
selectWalletUnlockPending,
|
||||
selectWalletUnlockSucceeded,
|
||||
} from 'lbry-redux';
|
||||
import { doQuit } from 'redux/actions/app';
|
||||
import ModalWalletUnlock from './view';
|
||||
|
||||
const select = state => ({
|
||||
walletUnlockSucceded: selectWalletUnlockSucceeded(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideNotification()),
|
||||
quit: () => dispatch(doQuit()),
|
||||
unlockWallet: password => dispatch(doWalletUnlock(password)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ModalWalletUnlock);
|
73
src/renderer/modal/modalWalletUnlock/view.jsx
Normal file
73
src/renderer/modal/modalWalletUnlock/view.jsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Form, FormRow, FormField } from 'component/common/form';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
quit: () => void,
|
||||
unlockWallet: string => void,
|
||||
walletUnlockSucceded: boolean,
|
||||
};
|
||||
|
||||
class ModalWalletUnlock extends React.PureComponent<Props> {
|
||||
state = {
|
||||
password: null,
|
||||
};
|
||||
|
||||
componentDidUpdate() {
|
||||
const { props } = this;
|
||||
|
||||
if (props.walletUnlockSucceded === true) {
|
||||
props.closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
onChangePassword(event) {
|
||||
this.setState({ password: event.target.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { quit, unlockWallet, walletUnlockSucceded, closeModal } = this.props;
|
||||
|
||||
const { password } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
contentLabel={__('Unlock Wallet')}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__('Unlock')}
|
||||
abortButtonLabel={__('Exit')}
|
||||
onConfirmed={() => unlockWallet(password)}
|
||||
onAborted={quit}
|
||||
>
|
||||
<Form onSubmit={() => unlockWallet(password)}>
|
||||
{__(
|
||||
'Your wallet has been encrypted with a local password. Please enter your wallet password to proceed.'
|
||||
)}
|
||||
<FormRow padded>
|
||||
<FormField
|
||||
stretch
|
||||
error={walletUnlockSucceded === false ? 'Incorrect Password' : false}
|
||||
label={__('Wallet Password')}
|
||||
type="password"
|
||||
name="wallet-password"
|
||||
onChange={event => this.onChangePassword(event)}
|
||||
/>
|
||||
</FormRow>
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Learn more')}
|
||||
href="https://lbry.io/faq/wallet-encryption"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalWalletUnlock;
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as settings from 'constants/settings';
|
||||
import { doClearCache } from 'redux/actions/app';
|
||||
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
|
||||
import {
|
||||
doSetDaemonSetting,
|
||||
doSetClientSetting,
|
||||
|
@ -13,6 +13,7 @@ import {
|
|||
selectLanguages,
|
||||
} from 'redux/selectors/settings';
|
||||
import { selectCurrentLanguage } from 'redux/selectors/app';
|
||||
import { doWalletStatus, selectWalletIsEncrypted } from 'lbry-redux';
|
||||
import SettingsPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -26,6 +27,7 @@ const select = state => ({
|
|||
languages: selectLanguages(state),
|
||||
automaticDarkModeEnabled: makeSelectClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||
walletEncrypted: selectWalletIsEncrypted(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -34,6 +36,9 @@ const perform = dispatch => ({
|
|||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
getThemes: () => dispatch(doGetThemes()),
|
||||
changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)),
|
||||
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
|
||||
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
|
||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -31,6 +31,9 @@ type Props = {
|
|||
themes: Array<string>,
|
||||
automaticDarkModeEnabled: boolean,
|
||||
autoplay: boolean,
|
||||
encryptWallet: () => void,
|
||||
decryptWallet: () => void,
|
||||
walletEncrypted: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -58,7 +61,10 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getThemes();
|
||||
const { props } = this;
|
||||
|
||||
props.getThemes();
|
||||
props.updateWalletStatus();
|
||||
}
|
||||
|
||||
onRunOnStartChange(event: SyntheticInputEvent<*>) {
|
||||
|
@ -111,6 +117,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
|
||||
}
|
||||
|
||||
onChangeEncryptWallet() {
|
||||
const { props } = this;
|
||||
props.walletEncrypted ? props.decryptWallet() : props.encryptWallet();
|
||||
}
|
||||
|
||||
setDaemonSetting(name: string, value: boolean | string | Price) {
|
||||
this.props.setDaemonSetting(name, value);
|
||||
}
|
||||
|
@ -138,6 +149,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
themes,
|
||||
automaticDarkModeEnabled,
|
||||
autoplay,
|
||||
walletEncrypted,
|
||||
} = this.props;
|
||||
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -272,32 +284,43 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
)}
|
||||
/>
|
||||
</section>
|
||||
{
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Theme')}</div>
|
||||
<FormField
|
||||
name="theme_select"
|
||||
type="select"
|
||||
onChange={this.onThemeChange}
|
||||
value={currentTheme}
|
||||
disabled={automaticDarkModeEnabled}
|
||||
>
|
||||
{themes.map(theme => (
|
||||
<option key={theme} value={theme}>
|
||||
{theme}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="automatic_dark_mode"
|
||||
onChange={e => this.onAutomaticDarkModeChange(e.target.checked)}
|
||||
checked={automaticDarkModeEnabled}
|
||||
disabled={isDarkModeEnabled}
|
||||
postfix={__('Automatic dark mode (9pm to 8am)')}
|
||||
/>
|
||||
</section>
|
||||
}
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Theme')}</div>
|
||||
<FormField
|
||||
name="theme_select"
|
||||
type="select"
|
||||
onChange={this.onThemeChange}
|
||||
value={currentTheme}
|
||||
disabled={automaticDarkModeEnabled}
|
||||
>
|
||||
{themes.map(theme => (
|
||||
<option key={theme} value={theme}>
|
||||
{theme}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="automatic_dark_mode"
|
||||
onChange={e => this.onAutomaticDarkModeChange(e.target.checked)}
|
||||
checked={automaticDarkModeEnabled}
|
||||
disabled={isDarkModeEnabled}
|
||||
postfix={__('Automatic dark mode (9pm to 8am)')}
|
||||
/>
|
||||
</section>
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Wallet Security')}</div>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="encrypt_wallet"
|
||||
onChange={e => this.onChangeEncryptWallet(e)}
|
||||
checked={walletEncrypted}
|
||||
postfix={__('Encrypt my wallet with a custom password.')}
|
||||
helper={__(
|
||||
'Secure your local wallet data with a custom password. Lost passwords cannot be recovered.'
|
||||
)}
|
||||
/>
|
||||
</section>
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Application Cache')}</div>
|
||||
<span className="card__subtitle">
|
||||
|
|
|
@ -274,6 +274,36 @@ export function doCheckDaemonVersion() {
|
|||
};
|
||||
}
|
||||
|
||||
export function doNotifyEncryptWallet() {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
id: MODALS.WALLET_ENCRYPT,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function doNotifyDecryptWallet() {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
id: MODALS.WALLET_DECRYPT,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function doNotifyUnlockWallet() {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
doNotify({
|
||||
id: MODALS.WALLET_UNLOCK,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function doAlertError(errorList) {
|
||||
return dispatch => {
|
||||
dispatch(
|
||||
|
|
|
@ -370,6 +370,10 @@ p {
|
|||
}
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.thumbnail-preview {
|
||||
height: var(--thumbnail-preview-height);
|
||||
width: var(--thumbnail-preview-width);
|
||||
|
|
|
@ -5221,9 +5221,9 @@ lazy-val@^1.0.3:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#e0909b08647a790d155f3189b9f9bf0b3e55bd17:
|
||||
lbry-redux@lbryio/lbry-redux#b4fffe863df316bc73183567ab978221ee623b8c:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/e0909b08647a790d155f3189b9f9bf0b3e55bd17"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/b4fffe863df316bc73183567ab978221ee623b8c"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue