diff --git a/web/cashtab/extension/public/manifest.json b/web/cashtab/extension/public/manifest.json
index cdbcdfe14..a8012d2e8 100644
--- a/web/cashtab/extension/public/manifest.json
+++ b/web/cashtab/extension/public/manifest.json
@@ -1,30 +1,30 @@
{
"manifest_version": 2,
"name": "Cashtab",
"description": "A browser-integrated eCash wallet from Bitcoin ABC",
- "version": "1.0.5",
+ "version": "1.0.6",
"content_scripts": [
{
"matches": ["file://*/*", "http://*/*", "https://*/*"],
"js": ["contentscript.js"],
"run_at": "document_idle",
"all_frames": true
}
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_popup": "index.html",
"default_title": "Cashtab"
},
"icons": {
"16": "ecash16.png",
"48": "ecash48.png",
"128": "ecash128.png",
"192": "ecash192.png",
"512": "ecash512.png"
}
}
diff --git a/web/cashtab/src/components/Configure/Configure.js b/web/cashtab/src/components/Configure/Configure.js
index bebf2baa7..8dfd5d52d 100644
--- a/web/cashtab/src/components/Configure/Configure.js
+++ b/web/cashtab/src/components/Configure/Configure.js
@@ -1,792 +1,813 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { Collapse, Form, Input, Modal, Alert, Switch, Tag } from 'antd';
import {
PlusSquareOutlined,
WalletFilled,
ImportOutlined,
LockOutlined,
CheckOutlined,
CloseOutlined,
LockFilled,
ExclamationCircleFilled,
} from '@ant-design/icons';
import { WalletContext, AuthenticationContext } from '@utils/context';
import { SidePaddingCtn } from '@components/Common/Atoms';
import { StyledCollapse } from '@components/Common/StyledCollapse';
import {
AntdFormWrapper,
CurrencySelectDropdown,
} from '@components/Common/EnhancedInputs';
import PrimaryButton, {
SecondaryButton,
SmartButton,
} from '@components/Common/PrimaryButton';
import {
ThemedCopyOutlined,
ThemedWalletOutlined,
ThemedDollarOutlined,
ThemedSettingOutlined,
} from '@components/Common/CustomIcons';
import { ReactComponent as Trashcan } from '@assets/trashcan.svg';
import { ReactComponent as Edit } from '@assets/edit.svg';
import { Event } from '@utils/GoogleAnalytics';
import ApiError from '@components/Common/ApiError';
import { formatSavedBalance } from '@utils/formatting';
+import { useHistory } from 'react-router-dom';
const { Panel } = Collapse;
const SettingsLink = styled.a`
text-decoration: underline;
color: ${props => props.theme.eCashBlue};
:visited {
text-decoration: underline;
color: ${props => props.theme.eCashBlue};
}
:hover {
color: ${props => props.theme.eCashPurple};
}
`;
+const PrivacyLink = styled.button`
+ text-decoration: underline;
+ color: ${props => props.theme.eCashBlue};
+ :visited {
+ text-decoration: underline;
+ color: ${props => props.theme.eCashBlue};
+ }
+ :hover {
+ color: ${props => props.theme.eCashPurple};
+ }
+ background: none !important;
+ border: none;
+ padding: 0 !important;
+ cursor: pointer;
+`;
+
const SWRow = styled.div`
border-radius: 3px;
padding: 10px 0;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6px;
@media (max-width: 500px) {
flex-direction: column;
margin-bottom: 12px;
}
`;
const SWName = styled.div`
width: 50%;
display: flex;
align-items: center;
justify-content: space-between;
word-wrap: break-word;
hyphens: auto;
@media (max-width: 500px) {
width: 100%;
justify-content: center;
margin-bottom: 15px;
}
h3 {
font-size: 16px;
color: ${props => props.theme.darkBlue};
margin: 0;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
h3.overflow {
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
}
h3.overflow:hover {
background-color: ${props => props.theme.settings.background};
overflow: visible;
inline-size: 100px;
white-space: normal;
}
`;
const SWBalance = styled.div`
width: 50%;
display: flex;
align-items: center;
justify-content: space-between;
word-wrap: break-word;
hyphens: auto;
@media (max-width: 500px) {
width: 100%;
justify-content: center;
margin-bottom: 15px;
}
div {
font-size: 13px;
color: ${props => props.theme.darkBlue};
margin: 0;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
div.overflow {
width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
div.overflow:hover {
background-color: ${props => props.theme.settings.background};
overflow: visible;
inline-size: 150px;
white-space: normal;
}
`;
const SWButtonCtn = styled.div`
width: 50%;
display: flex;
align-items: center;
justify-content: flex-end;
@media (max-width: 500px) {
width: 100%;
justify-content: center;
}
button {
cursor: pointer;
background: transparent;
border: 1px solid #fff;
box-shadow: none;
color: #fff;
border-radius: 3px;
opacity: 0.6;
transition: all 200ms ease-in-out;
:hover {
opacity: 1;
background: ${props => props.theme.eCashBlue};
border-color: ${props => props.theme.eCashBlue};
}
@media (max-width: 768px) {
font-size: 14px;
}
}
svg {
stroke: ${props => props.theme.eCashBlue};
fill: ${props => props.theme.eCashBlue};
width: 25px;
height: 25px;
margin-right: 20px;
cursor: pointer;
:first-child:hover {
stroke: ${props => props.theme.eCashBlue};
fill: ${props => props.theme.eCashBlue};
}
:hover {
stroke: ${props => props.theme.settings.delete};
fill: ${props => props.theme.settings.delete};
}
}
`;
const AWRow = styled.div`
padding: 10px 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
h3 {
font-size: 16px;
display: inline-block;
color: ${props => props.theme.darkBlue};
margin: 0;
text-align: left;
font-weight: bold;
@media (max-width: 500px) {
font-size: 14px;
}
}
h4 {
font-size: 16px;
display: inline-block;
color: ${props => props.theme.eCashBlue} !important;
margin: 0;
text-align: right;
}
@media (max-width: 500px) {
flex-direction: column;
margin-bottom: 12px;
}
`;
const StyledConfigure = styled.div`
h2 {
color: ${props => props.theme.contrast};
font-size: 25px;
}
svg {
fill: ${props => props.theme.eCashBlue};
}
p {
color: ${props => props.theme.darkBlue};
}
`;
const StyledSpacer = styled.div`
height: 1px;
width: 100%;
background-color: ${props => props.theme.lightWhite};
margin: 60px 0 50px;
`;
const GeneralSettingsItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
.ant-switch svg {
fill: #717171;
}
.title {
color: ${props => props.theme.contrast};
}
.anticon {
color: ${props => props.theme.contrast};
}
.ant-switch {
background-color: #bdbdbd;
}
.ant-switch-checked {
background-color: ${props => props.theme.eCashBlue};
svg {
fill: ${props => props.theme.contrast};
}
}
`;
const Configure = () => {
const ContextValue = React.useContext(WalletContext);
const authentication = React.useContext(AuthenticationContext);
const { wallet, apiError } = ContextValue;
+ const history = useHistory();
const {
addNewSavedWallet,
activateWallet,
renameWallet,
deleteWallet,
validateMnemonic,
getSavedWallets,
cashtabSettings,
changeCashtabSettings,
} = ContextValue;
const [savedWallets, setSavedWallets] = useState([]);
const [formData, setFormData] = useState({
dirty: true,
mnemonic: '',
});
const [showRenameWalletModal, setShowRenameWalletModal] = useState(false);
const [showDeleteWalletModal, setShowDeleteWalletModal] = useState(false);
const [walletToBeRenamed, setWalletToBeRenamed] = useState(null);
const [walletToBeDeleted, setWalletToBeDeleted] = useState(null);
const [newWalletName, setNewWalletName] = useState('');
const [
confirmationOfWalletToBeDeleted,
setConfirmationOfWalletToBeDeleted,
] = useState('');
const [newWalletNameIsValid, setNewWalletNameIsValid] = useState(null);
const [walletDeleteValid, setWalletDeleteValid] = useState(null);
const [seedInput, openSeedInput] = useState(false);
const [showTranslationWarning, setShowTranslationWarning] = useState(false);
const showPopulatedDeleteWalletModal = walletInfo => {
setWalletToBeDeleted(walletInfo);
setShowDeleteWalletModal(true);
};
const showPopulatedRenameWalletModal = walletInfo => {
setWalletToBeRenamed(walletInfo);
setShowRenameWalletModal(true);
};
const cancelRenameWallet = () => {
// Delete form value
setNewWalletName('');
setShowRenameWalletModal(false);
};
const cancelDeleteWallet = () => {
setWalletToBeDeleted(null);
setConfirmationOfWalletToBeDeleted('');
setShowDeleteWalletModal(false);
};
const updateSavedWallets = async activeWallet => {
if (activeWallet) {
let savedWallets;
try {
savedWallets = await getSavedWallets(activeWallet);
setSavedWallets(savedWallets);
} catch (err) {
console.log(`Error in getSavedWallets()`);
console.log(err);
}
}
};
const [isValidMnemonic, setIsValidMnemonic] = useState(null);
useEffect(() => {
// Update savedWallets every time the active wallet changes
updateSavedWallets(wallet);
}, [wallet]);
useEffect(() => {
const detectedBrowserLang = navigator.language;
if (!detectedBrowserLang.includes('en-')) {
setShowTranslationWarning(true);
}
}, []);
// Need this function to ensure that savedWallets are updated on new wallet creation
const updateSavedWalletsOnCreate = async importMnemonic => {
// Event("Category", "Action", "Label")
// Track number of times a different wallet is activated
Event('Configure.js', 'Create Wallet', 'New');
const walletAdded = await addNewSavedWallet(importMnemonic);
if (!walletAdded) {
Modal.error({
title: 'This wallet already exists!',
content: 'Wallet not added',
});
} else {
Modal.success({
content: 'Wallet added to your saved wallets',
});
}
await updateSavedWallets(wallet);
};
// Same here
// TODO you need to lock UI here until this is complete
// Otherwise user may try to load an already-loading wallet, wreak havoc with indexedDB
const updateSavedWalletsOnLoad = async walletToActivate => {
// Event("Category", "Action", "Label")
// Track number of times a different wallet is activated
Event('Configure.js', 'Activate', '');
await activateWallet(walletToActivate);
};
async function submit() {
setFormData({
...formData,
dirty: false,
});
// Exit if no user input
if (!formData.mnemonic) {
return;
}
// Exit if mnemonic is invalid
if (!isValidMnemonic) {
return;
}
// Event("Category", "Action", "Label")
// Track number of times a different wallet is activated
Event('Configure.js', 'Create Wallet', 'Imported');
updateSavedWalletsOnCreate(formData.mnemonic);
}
const handleChange = e => {
const { value, name } = e.target;
// Validate mnemonic on change
// Import button should be disabled unless mnemonic is valid
setIsValidMnemonic(validateMnemonic(value));
setFormData(p => ({ ...p, [name]: value }));
};
const changeWalletName = async () => {
if (newWalletName === '' || newWalletName.length > 24) {
setNewWalletNameIsValid(false);
return;
}
// Hide modal
setShowRenameWalletModal(false);
// Change wallet name
console.log(
`Changing wallet ${walletToBeRenamed.name} name to ${newWalletName}`,
);
const renameSuccess = await renameWallet(
walletToBeRenamed.name,
newWalletName,
);
if (renameSuccess) {
Modal.success({
content: `Wallet "${walletToBeRenamed.name}" renamed to "${newWalletName}"`,
});
} else {
Modal.error({
content: `Rename failed. All wallets must have a unique name.`,
});
}
await updateSavedWallets(wallet);
// Clear wallet name for form
setNewWalletName('');
};
const deleteSelectedWallet = async () => {
if (!walletDeleteValid && walletDeleteValid !== null) {
return;
}
if (
confirmationOfWalletToBeDeleted !==
`delete ${walletToBeDeleted.name}`
) {
setWalletDeleteValid(false);
return;
}
// Hide modal
setShowDeleteWalletModal(false);
// Change wallet name
console.log(`Deleting wallet "${walletToBeDeleted.name}"`);
const walletDeletedSuccess = await deleteWallet(walletToBeDeleted);
if (walletDeletedSuccess) {
Modal.success({
content: `Wallet "${walletToBeDeleted.name}" successfully deleted`,
});
} else {
Modal.error({
content: `Error deleting ${walletToBeDeleted.name}.`,
});
}
await updateSavedWallets(wallet);
// Clear wallet delete confirmation from form
setConfirmationOfWalletToBeDeleted('');
};
const handleWalletNameInput = e => {
const { value } = e.target;
// validation
if (value && value.length && value.length < 24) {
setNewWalletNameIsValid(true);
} else {
setNewWalletNameIsValid(false);
}
setNewWalletName(value);
};
const handleWalletToDeleteInput = e => {
const { value } = e.target;
if (value && value === `delete ${walletToBeDeleted.name}`) {
setWalletDeleteValid(true);
} else {
setWalletDeleteValid(false);
}
setConfirmationOfWalletToBeDeleted(value);
};
const handleAppLockToggle = (checked, e) => {
if (checked) {
// if there is an existing credential, that means user has registered
// simply turn on the Authentication Required flag
if (authentication.credentialId) {
authentication.turnOnAuthentication();
} else {
// there is no existing credential, that means user has not registered
// user need to register
authentication.signUp();
}
} else {
authentication.turnOffAuthentication();
}
};
return (
{wallet && wallet.mnemonic
? wallet.mnemonic
: ''}
Copy and paste your mnemonic seed phrase
below to import an existing wallet
{apiError ? (
{wallet.name}
Currently active
{sw.name}