diff --git a/web/cashtab/src/assets/styles/theme.js b/web/cashtab/src/assets/styles/theme.js
new file mode 100644
index 000000000..f887f4b48
--- /dev/null
+++ b/web/cashtab/src/assets/styles/theme.js
@@ -0,0 +1,3 @@
+export const theme = {
+ iconOutlined: '#3e3f42',
+};
diff --git a/web/cashtab/src/components/App.css b/web/cashtab/src/components/App.css
index ad7a48277..6a93f313f 100644
--- a/web/cashtab/src/components/App.css
+++ b/web/cashtab/src/components/App.css
@@ -1,152 +1,148 @@
@import '~antd/dist/antd.less';
@import '~@fortawesome/fontawesome-free/css/all.css';
@import url('https://fonts.googleapis.com/css?family=Khula&display=swap&.css');
/* Hide up and down arros on input type="number" */
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Hide up and down arros on input type="number" */
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
html,
body {
max-width: 100%;
overflow-x: hidden;
}
/* Hide scroll bars on antd modals*/
.ant-modal-wrap.ant-modal-centered::-webkit-scrollbar {
display: none;
}
/* ITEMS BELOW TO BE MOVED TO STYLED COMPONENTS*/
-/* Configure.js, CustomIcons.js */
-.anticon {
- color: #3e3f42;
-}
/* EnhancedInputs.js */
.ant-input-group-addon {
background-color: #f4f4f4 !important;
border: 1px solid rgb(234, 237, 243);
color: #3e3f42 !important;
}
/* useWallet.js, useBCH.js */
@media (max-width: 768px) {
.ant-notification {
width: 100%;
top: 20px !important;
max-width: unset;
margin-right: 0;
}
}
/* OK and Cancel button colors on Modals*/
.ant-card-actions > li > span:hover,
.ant-btn:hover,
.ant-btn:focus {
color: #f59332;
transition: color 0.3s;
background-color: white;
}
.ant-btn {
border-radius: 8px;
background-color: #fff;
color: rgb(62, 63, 66);
font-weight: bold;
}
/*Custom Input Fields */
input.ant-input,
.ant-select-selection {
background-color: #fff !important;
box-shadow: none !important;
border-radius: 4px;
font-weight: bold;
color: rgb(62, 63, 66);
opacity: 1;
height: 50px;
}
.ant-select-selection:hover {
border: 1px solid #eaedf3;
}
.ant-select-selection-selected-value {
color: rgb(62, 63, 66);
}
.ant-select-dropdown-menu-item {
color: #444;
background-color: #fff;
}
.ant-select-dropdown-menu-item-active,
.ant-select-dropdown-menu-item:hover {
color: #fff;
background-color: #ff8d00 !important;
}
.selectedCurrencyOption:hover {
color: #fff !important;
background-color: #ff8d00 !important;
}
.ant-input-affix-wrapper {
background-color: #fff;
border: 1px solid #eaedf3 !important;
}
input.ant-input,
.ant-select-selection {
border: none;
}
.ant-input::placeholder {
text-align: left;
}
.ant-select-selector {
height: 60px !important;
border: 1px solid #eaedf3 !important;
}
/*Revs with updated Antd*/
.ant-select-single .ant-select-selector .ant-select-selection-item,
.ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 60px;
text-align: left;
color: #3e3f42;
font-weight: bold;
}
/* Handle new antd error formatting */
.ant-form-item-has-error
> div
> div.ant-form-item-control-input
> div
> span
> span
> span.ant-input-affix-wrapper {
background-color: #fff;
border-color: #f04134 !important;
}
.ant-form-item-has-error .ant-input,
.ant-form-item-has-error .ant-input-affix-wrapper,
.ant-form-item-has-error .ant-input:hover,
.ant-form-item-has-error .ant-input-affix-wrapper:hover {
background-color: #fff;
border-color: #f04134 !important;
}
.ant-form-item-has-error
.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input)
.ant-select-selector {
background-color: #fff;
border-color: #f04134 !important;
}
diff --git a/web/cashtab/src/components/App.js b/web/cashtab/src/components/App.js
index 459599f54..6b857cf19 100644
--- a/web/cashtab/src/components/App.js
+++ b/web/cashtab/src/components/App.js
@@ -1,243 +1,248 @@
import React from 'react';
import 'antd/dist/antd.less';
import '../index.css';
-import styled from 'styled-components';
+import styled, { ThemeProvider } from 'styled-components';
+import { theme } from '@assets/styles/theme';
import {
FolderOpenFilled,
CaretRightOutlined,
SettingFilled,
} from '@ant-design/icons';
import Wallet from '@components/Wallet/Wallet';
import Send from '@components/Send/Send';
import SendToken from '@components/Send/SendToken';
import Configure from '@components/Configure/Configure';
import NotFound from '@components/NotFound';
import CashTab from '@assets/cashtab.png';
import TabCash from '@assets/tabcash.png';
import ABC from '@assets/bitcoinabclogo.png';
import './App.css';
import { WalletContext } from '@utils/context';
import { checkForTokenById } from '@utils/tokenMethods.js';
import WalletLabel from '@components/Common/WalletLabel.js';
import {
Route,
Redirect,
Switch,
useLocation,
useHistory,
} from 'react-router-dom';
import fbt from 'fbt';
const CustomApp = styled.div`
text-align: center;
font-family: 'Gilroy', sans-serif;
background-color: #fbfbfd;
`;
const Footer = styled.div`
background-color: #fff;
border-radius: 20px;
position: fixed;
bottom: 0;
width: 500px;
@media (max-width: 768px) {
width: 100%;
}
border-top: 1px solid #e2e2e2;
`;
export const NavButton = styled.button`
:focus,
:active {
outline: none;
}
cursor: pointer;
padding: 24px 12px 12px 12px;
margin: 0 28px;
@media (max-width: 360px) {
margin: 0 12px;
}
background-color: #fff;
border: none;
font-size: 12px;
font-weight: bold;
.anticon {
display: block;
color: rgb(148, 148, 148);
font-size: 24px;
margin-bottom: 6px;
}
${({ active }) =>
active &&
`
color: #ff8d00;
.anticon {
color: #ff8d00;
}
`}
`;
export const WalletBody = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 100vh;
background: linear-gradient(270deg, #040c3c, #212c6e);
`;
export const WalletCtn = styled.div`
position: relative;
width: 500px;
background-color: #fff;
min-height: 100vh;
padding: 10px 30px 120px 30px;
background: #fff;
-webkit-box-shadow: 0px 0px 24px 1px rgba(0, 0, 0, 1);
-moz-box-shadow: 0px 0px 24px 1px rgba(0, 0, 0, 1);
box-shadow: 0px 0px 24px 1px rgba(0, 0, 0, 1);
@media (max-width: 768px) {
width: 100%;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
`;
export const HeaderCtn = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 20px 0 30px;
margin-bottom: 20px;
justify-content: space-between;
border-bottom: 1px solid #e2e2e2;
a {
color: #848484;
:hover {
color: #ff8d00;
}
}
@media (max-width: 768px) {
a {
font-size: 12px;
}
padding: 10px 0 20px;
}
`;
export const EasterEgg = styled.img`
position: fixed;
bottom: -195px;
margin: 0;
right: 10%;
transition-property: bottom;
transition-duration: 1.5s;
transition-timing-function: ease-out;
:hover {
bottom: 0;
}
@media screen and (max-width: 1250px) {
display: none;
}
`;
export const CashTabLogo = styled.img`
width: 120px;
@media (max-width: 768px) {
width: 110px;
}
`;
export const AbcLogo = styled.img`
width: 150px;
@media (max-width: 768px) {
width: 120px;
}
`;
const App = () => {
const ContextValue = React.useContext(WalletContext);
const { wallet, tokens } = ContextValue;
const hasTab = checkForTokenById(
tokens,
'50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e',
);
const location = useLocation();
const history = useHistory();
const selectedKey =
location && location.pathname ? location.pathname.substr(1) : '';
return (
-
-
-
-
-
- {hasTab && }
-
-
-
-
-
-
-
-
-
-
-
-
- (
-
+
+
+
+
+
+
+ {hasTab && (
+
)}
- />
-
-
-
-
-
-
-
- {wallet ? (
-
-
+ history.push('/send')}
+ >
+
+ Send
+
+ history.push('/configure')}
+ >
+
+ Settings
+
+
+ ) : null}
+
+
+
);
};
export default App;
diff --git a/web/cashtab/src/components/Common/CustomIcons.js b/web/cashtab/src/components/Common/CustomIcons.js
index 2b123ba9f..eb5caf9c4 100644
--- a/web/cashtab/src/components/Common/CustomIcons.js
+++ b/web/cashtab/src/components/Common/CustomIcons.js
@@ -1,32 +1,51 @@
import * as React from 'react';
import styled from 'styled-components';
-import { LoadingOutlined } from '@ant-design/icons';
+import {
+ CopyOutlined,
+ DollarOutlined,
+ LoadingOutlined,
+ WalletOutlined,
+ QrcodeOutlined,
+} from '@ant-design/icons';
export const CashLoadingIcon = (
);
+export const ThemedCopyOutlined = styled(CopyOutlined)`
+ color: ${props => props.theme.iconOutlined} !important;
+`;
+export const ThemedDollarOutlined = styled(DollarOutlined)`
+ color: ${props => props.theme.iconOutlined} !important;
+`;
+export const ThemedWalletOutlined = styled(WalletOutlined)`
+ color: ${props => props.theme.iconOutlined} !important;
+`;
+export const ThemedQrcodeOutlined = styled(QrcodeOutlined)`
+ color: ${props => props.theme.iconOutlined} !important;
+`;
+
export const LoadingBlock = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
flex-direction: column;
svg {
width: 50px;
height: 50px;
fill: #ff8d00;
}
`;
export const CashLoader = () => (
);
diff --git a/web/cashtab/src/components/Common/EnhancedInputs.js b/web/cashtab/src/components/Common/EnhancedInputs.js
index 8dd181be8..d61b93d63 100644
--- a/web/cashtab/src/components/Common/EnhancedInputs.js
+++ b/web/cashtab/src/components/Common/EnhancedInputs.js
@@ -1,183 +1,186 @@
import * as React from 'react';
import { Form, Input, Select } from 'antd';
-import { DollarOutlined, WalletOutlined } from '@ant-design/icons';
+import {
+ ThemedDollarOutlined,
+ ThemedWalletOutlined,
+} from '@components/Common/CustomIcons';
import styled from 'styled-components';
import { ScanQRCode } from './ScanQRCode';
import useBCH from '@hooks/useBCH';
import { currency } from '@components/Common/Ticker.js';
export const InputAddonText = styled.span`
width: 100%;
height: 100%;
display: block;
${props =>
props.disabled
? `
cursor: not-allowed;
`
: `cursor: pointer;`}
`;
export const InputNumberAddonText = styled.span`
background-color: #f4f4f4 !important;
border: 1px solid rgb(234, 237, 243);
color: #3e3f42 !important;
height: 50px;
line-height: 47px;
* {
color: #3e3f42 !important;
}
${props =>
props.disabled
? `
cursor: not-allowed;
`
: `cursor: pointer;`}
`;
export const SendBchInput = ({
onMax,
inputProps,
selectProps,
...otherProps
}) => {
const { Option } = Select;
const currencies = [
{
value: currency.ticker,
label: currency.ticker,
},
{ value: 'USD', label: 'USD' },
];
const currencyOptions = currencies.map(currency => {
return (
);
});
const CurrencySelect = (
);
return (
+
) : (
)
}
{...inputProps}
/>
{CurrencySelect}
max
);
};
export const FormItemWithMaxAddon = ({ onMax, inputProps, ...otherProps }) => {
return (
}
addonAfter={
max
}
{...inputProps}
/>
);
};
// loadWithCameraOpen prop: if true, load page with camera scanning open
export const FormItemWithQRCodeAddon = ({
onScan,
loadWithCameraOpen,
inputProps,
...otherProps
}) => {
return (
}
+ prefix={}
autoComplete="off"
addonAfter={
}
{...inputProps}
/>
);
};
export const AddressValidators = () => {
const { BCH } = useBCH();
return {
safelyDetectAddressFormat: value => {
try {
return BCH.Address.detectAddressFormat(value);
} catch (error) {
return null;
}
},
isSLPAddress: value =>
AddressValidators.safelyDetectAddressFormat(value) === 'slpaddr',
isBCHAddress: value =>
AddressValidators.safelyDetectAddressFormat(value) === 'cashaddr',
isLegacyAddress: value =>
AddressValidators.safelyDetectAddressFormat(value) === 'legacy',
}();
};
diff --git a/web/cashtab/src/components/Common/ScanQRCode.js b/web/cashtab/src/components/Common/ScanQRCode.js
index f65010a1f..33eaf4acd 100644
--- a/web/cashtab/src/components/Common/ScanQRCode.js
+++ b/web/cashtab/src/components/Common/ScanQRCode.js
@@ -1,170 +1,170 @@
import React, { useState } from 'react';
import { Alert, Modal } from 'antd';
-import { QrcodeOutlined } from '@ant-design/icons';
+import { ThemedQrcodeOutlined } from '@components/Common/CustomIcons';
import styled from 'styled-components';
import { BrowserQRCodeReader } from '@zxing/library';
import {
currency,
isValidCashPrefix,
isValidTokenPrefix,
} from '@components/Common/Ticker.js';
import { Event } from '@utils/GoogleAnalytics';
const StyledScanQRCode = styled.span`
display: block;
`;
const StyledModal = styled(Modal)`
width: 400px !important;
height: 400px !important;
.ant-modal-close {
top: 0 !important;
right: 0 !important;
}
`;
const QRPreview = styled.video`
width: 100%;
`;
export const ScanQRCode = ({
width,
loadWithCameraOpen,
onScan = () => null,
...otherProps
}) => {
const [visible, setVisible] = useState(loadWithCameraOpen);
const [error, setError] = useState(false);
// Use these states to debug video errors on mobile
// Note: iOS chrome/brave/firefox does not support accessing camera, will throw error
// iOS users can use safari
// todo only show scanner with safari
//const [mobileError, setMobileError] = useState(false);
//const [mobileErrorMsg, setMobileErrorMsg] = useState(false);
const [activeCodeReader, setActiveCodeReader] = useState(null);
const teardownCodeReader = codeReader => {
if (codeReader !== null) {
codeReader.reset();
codeReader.stop();
codeReader = null;
setActiveCodeReader(codeReader);
}
};
const parseContent = content => {
let type = 'unknown';
let values = {};
// If what scanner reads from QR code begins with 'bitcoincash:' or 'simpleledger:' or their successor prefixes
if (isValidCashPrefix(content) || isValidTokenPrefix(content)) {
type = 'address';
values = { address: content };
// Event("Category", "Action", "Label")
// Track number of successful QR code scans
// BCH or slp?
let eventLabel = currency.ticker;
const isToken = content.split(currency.tokenPrefix).length > 1;
if (isToken) {
eventLabel = currency.tokenTicker;
}
Event('ScanQRCode.js', 'Address Scanned', eventLabel);
}
return { type, values };
};
const scanForQrCode = async () => {
const codeReader = new BrowserQRCodeReader();
setActiveCodeReader(codeReader);
try {
// Need to execute this before you can decode input
// eslint-disable-next-line no-unused-vars
const videoInputDevices = await codeReader.getVideoInputDevices();
//console.log(`videoInputDevices`, videoInputDevices);
//setMobileError(JSON.stringify(videoInputDevices));
// choose your media device (webcam, frontal camera, back camera, etc.)
// TODO implement if necessary
//const selectedDeviceId = videoInputDevices[0].deviceId;
//const previewElem = document.querySelector("#test-area-qr-code-webcam");
const content = await codeReader.decodeFromInputVideoDevice(
undefined,
'test-area-qr-code-webcam',
);
const result = parseContent(content.text);
// stop scanning and fill form if it's an address
if (result.type === 'address') {
// Hide the scanner
setVisible(false);
onScan(result.values.address);
return teardownCodeReader(codeReader);
}
} catch (err) {
console.log(`Error in QR scanner:`);
console.log(err);
console.log(JSON.stringify(err.message));
//setMobileErrorMsg(JSON.stringify(err.message));
setError(err);
teardownCodeReader(codeReader);
}
// stop scanning after 20s no matter what
};
React.useEffect(() => {
if (!visible) {
setError(false);
// Stop the camera if user closes modal
if (activeCodeReader !== null) {
teardownCodeReader(activeCodeReader);
}
} else {
scanForQrCode();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible]);
return (
<>
setVisible(!visible)}
>
-
+
setVisible(false)}
footer={null}
>
{visible ? (
{error ? (
<>
{/*
{mobileError}
{mobileErrorMsg}
*/}
>
) : (
)}
) : null}
>
);
};
diff --git a/web/cashtab/src/components/Configure/Configure.js b/web/cashtab/src/components/Configure/Configure.js
index 8d4050ca2..64ad4e137 100644
--- a/web/cashtab/src/components/Configure/Configure.js
+++ b/web/cashtab/src/components/Configure/Configure.js
@@ -1,586 +1,589 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { Collapse, Form, Input, Modal, Spin, Alert } from 'antd';
import {
PlusSquareOutlined,
WalletFilled,
- WalletOutlined,
ImportOutlined,
- CopyOutlined,
LockOutlined,
} from '@ant-design/icons';
import { WalletContext } from '@utils/context';
import { StyledCollapse } from '@components/Common/StyledCollapse';
import PrimaryButton, {
SecondaryButton,
SmartButton,
} from '@components/Common/PrimaryButton';
-import { CashLoader, CashLoadingIcon } from '@components/Common/CustomIcons';
+import {
+ CashLoader,
+ CashLoadingIcon,
+ ThemedCopyOutlined,
+ ThemedWalletOutlined,
+} 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';
const { Panel } = Collapse;
const SettingsLink = styled.a`
text-decoration: underline;
color: #ff8d00;
:visited {
text-decoration: underline;
color: #ff8d00;
}
`;
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: #444;
margin: 0;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
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;
@media (max-width: 768px) {
font-size: 14px;
}
}
svg {
stroke: #444;
fill: #444;
width: 25px;
height: 25px;
margin-right: 20px;
cursor: pointer;
:first-child:hover {
stroke: #ff8d00;
fill: #ff8d00;
}
:hover {
stroke: red;
fill: red;
}
}
`;
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: #444;
margin: 0;
text-align: left;
font-weight: bold;
@media (max-width: 500px) {
font-size: 14px;
}
}
h4 {
font-size: 16px;
display: inline-block;
color: #ff8d00 !important;
margin: 0;
text-align: right;
}
@media (max-width: 500px) {
flex-direction: column;
margin-bottom: 12px;
}
`;
const StyledConfigure = styled.div`
h2 {
color: #444;
font-size: 25px;
}
p {
color: #444;
}
`;
const StyledSpacer = styled.div`
height: 1px;
width: 100%;
background-color: #e2e2e2;
margin: 60px 0 50px;
`;
const Configure = () => {
const ContextValue = React.useContext(WalletContext);
const { wallet, loading, apiError } = ContextValue;
const {
addNewSavedWallet,
activateWallet,
renameWallet,
deleteWallet,
validateMnemonic,
getSavedWallets,
} = 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 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(false);
useEffect(() => {
// Update savedWallets every time the active wallet changes
updateSavedWallets(wallet);
}, [wallet]);
// 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);
await updateSavedWallets(wallet);
};
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) {
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);
};
return (
{walletToBeRenamed !== null && (
cancelRenameWallet()}
>
}
placeholder="Enter new wallet name"
name="newName"
value={newWalletName}
onChange={e => handleWalletNameInput(e)}
/>
)}
{walletToBeDeleted !== null && (
cancelDeleteWallet()}
>
}
placeholder={`Type "delete ${walletToBeDeleted.name}" to confirm`}
name="walletToBeDeletedInput"
value={confirmationOfWalletToBeDeleted}
onChange={e => handleWalletToDeleteInput(e)}
/>
)}
- Backup your wallet
+ Backup your wallet
{wallet && wallet.mnemonic && (
{wallet && wallet.mnemonic
? wallet.mnemonic
: ''}
)}
- Manage Wallets
+ Manage Wallets
{apiError ? (
<>
An error occured on our end. Reconnecting...
>
) : (
<>
updateSavedWalletsOnCreate()}
>
New Wallet
openSeedInput(!seedInput)}
>
Import Wallet
{seedInput && (
<>
Copy and paste your mnemonic seed phrase
below to import an existing wallet
}
placeholder="mnemonic (seed phrase)"
name="mnemonic"
autoComplete="off"
onChange={e => handleChange(e)}
required
/>
submit()}
>
Import
>
)}
>
)}
{savedWallets && savedWallets.length > 0 && (
<>
{wallet.name}
Currently active
{savedWallets.map(sw => (
{sw.name}
showPopulatedRenameWalletModal(
sw,
)
}
/>
showPopulatedDeleteWalletModal(
sw,
)
}
/>
))}
>
)}
[
Documentation
]
);
};
export default Configure;
diff --git a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
index fad5b353e..945f92d06 100644
--- a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
+++ b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
@@ -1,449 +1,449 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Configure with a wallet 1`] = `
Backup your wallet
Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
Click to reveal seed phrase
Manage Wallets
[
Documentation
]
`;
exports[`Configure without a wallet 1`] = `
Backup your wallet
Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
Manage Wallets
[
Documentation
]
`;
diff --git a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
index 275638411..82db81ee8 100644
--- a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
+++ b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
@@ -1,508 +1,508 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Wallet with BCH balances 1`] = `
Array [
0.06047469
BCHA
,
$
NaN
USD
,
,
,
View Transactions
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
0.06047469
BCHA
,
$
NaN
USD
,
,
,
View Transactions
,
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
BCHA
payments, or load it up with
BCHA
to send to others
,
0
BCHA
,
,
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
Welcome to Cashtab!
,
Cashtab is an
open source,
non-custodial web wallet for
Bitcoin ABC
.
Want to learn more?
Check out the Cashtab documentation.
,
,
,
]
`;