diff --git a/web/cashtab/extension/src/components/App.js b/web/cashtab/extension/src/components/App.js
index 1bd162f51..21d97d338 100644
--- a/web/cashtab/extension/src/components/App.js
+++ b/web/cashtab/extension/src/components/App.js
@@ -1,621 +1,638 @@
import React, { useState, useEffect } from 'react';
import 'antd/dist/antd.less';
import PropTypes from 'prop-types';
import { Spin, Modal } from 'antd';
import {
CashLoadingIcon,
HomeIcon,
SendIcon,
ReceiveIcon,
SettingsIcon,
AirdropIcon,
+ ThemedSignAndVerifyMsg,
} from 'components/Common/CustomIcons';
import '../index.css';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { theme } from 'assets/styles/theme';
import Home from 'components/Home/Home';
import Receive from 'components/Receive/Receive';
import Tokens from 'components/Tokens/Tokens';
import Send from 'components/Send/Send';
import SendToken from 'components/Send/SendToken';
import Airdrop from 'components/Airdrop/Airdrop';
import Configure from 'components/Configure/Configure';
import NotFound from 'components/NotFound';
import Cashtab from 'assets/cashtab_xec.png';
import './App.css';
import { WalletContext } from 'utils/context';
import { isValidStoredWallet, convertToEcashPrefix } from 'utils/cashMethods';
import {
Route,
Redirect,
Switch,
useLocation,
useHistory,
} from 'react-router-dom';
// Extension-only import used for open in new tab link
import PopOut from 'assets/popout.svg';
// Extension-only import used for interacting with the extension API
import extension from 'extensionizer';
const GlobalStyle = createGlobalStyle`
*::placeholder {
color: ${props => props.theme.forms.placeholder} !important;
}
*::selection {
background: ${props => props.theme.eCashBlue} !important;
}
.ant-modal-content, .ant-modal-header, .ant-modal-title {
background-color: ${props => props.theme.modal.background} !important;
color: ${props => props.theme.modal.color} !important;
}
.ant-modal-content svg {
fill: ${props => props.theme.modal.color};
}
.ant-modal-footer button {
background-color: ${props =>
props.theme.modal.buttonBackground} !important;
color: ${props => props.theme.modal.color} !important;
border-color: ${props => props.theme.modal.border} !important;
:hover {
background-color: ${props => props.theme.eCashBlue} !important;
}
}
.ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button, .ant-modal > button, .ant-modal-confirm-btns > button, .ant-modal-footer > button, #cropControlsConfirm{
border-radius: 3px;
border-radius: 3px;
background-color: ${props =>
props.theme.modal.buttonBackground} !important;
color: ${props => props.theme.modal.color} !important;
border-color: ${props => props.theme.modal.border} !important;
:hover {
background-color: ${props => props.theme.eCashBlue} !important;
}
text-shadow: none !important;
text-shadow: none !important;
}
.ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button:hover,.ant-modal-confirm-btns > button:hover, .ant-modal-footer > button:hover, #cropControlsConfirm:hover {
color: ${props => props.theme.contrast};
transition: all 0.3s;
background-color: ${props => props.theme.eCashBlue};
border-color: ${props => props.theme.eCashBlue};
}
.selectedCurrencyOption, .ant-select-dropdown {
text-align: left;
color: ${props => props.theme.contrast} !important;
background-color: ${props =>
props.theme.collapses.expandedBackground} !important;
}
.cashLoadingIcon {
color: ${props => props.theme.eCashBlue} !important;
font-size: 48px !important;
}
.selectedCurrencyOption:hover {
color: ${props => props.theme.contrast} !important;
background-color: ${props => props.theme.eCashBlue} !important;
}
#addrSwitch, #cropSwitch {
.ant-switch-checked {
background-color: white !important;
}
}
#addrSwitch.ant-switch-checked, #cropSwitch.ant-switch-checked {
background-image: ${props =>
props.theme.buttons.primary.backgroundImage} !important;
}
.ant-slider-rail {
background-color: ${props => props.theme.forms.border} !important;
}
.ant-slider-track {
background-color: ${props => props.theme.eCashBlue} !important;
}
.ant-descriptions-bordered .ant-descriptions-row {
background: ${props => props.theme.contrast};
}
.ant-modal-confirm-content, .ant-modal-confirm-title {
color: ${props => props.theme.contrast} !important;
}
.ant-form-item-explain {
div {
color: ${props => props.theme.forms.text};
}
}
.ant-input-prefix {
color: ${props => props.theme.eCashBlue};
}
.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
margin: auto !important;
}
.ant-spin-nested-loading>div>.ant-spin {
position: fixed !important;
}
`;
const CustomApp = styled.div`
text-align: center;
font-family: 'Gilroy', sans-serif;
font-family: 'Poppins', sans-serif;
background-color: ${props => props.theme.backgroundColor};
background-size: 100px 171px;
background-image: ${props => props.theme.backgroundImage};
background-attachment: fixed;
`;
const Footer = styled.div`
z-index: 2;
height: 80px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
background-color: ${props => props.theme.footerBackground};
position: fixed;
bottom: 0;
width: 500px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 50px;
@media (max-width: 768px) {
width: 100%;
padding: 0 20px;
}
`;
const NavWrapper = styled.div`
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 1.3rem;
margin-bottom: 5px;
`;
const NavIcon = styled.span`
position: relative;
background-color: ${props =>
props.clicked ? 'transparent' : props.theme.buttons.primary.color};
width: 2rem;
height: 2px;
display: inline-block;
transition: transform 300ms, top 300ms, background-color 300ms;
&::before,
&::after {
content: '';
background-color: ${props => props.theme.buttons.primary.color};
width: 2rem;
height: 2px;
display: inline-block;
position: absolute;
left: 0;
transition: transform 300ms, top 300ms, background-color 300ms;
}
&::before {
top: ${props => (props.clicked ? '0' : '-0.8rem')};
transform: ${props => (props.clicked ? 'rotate(135deg)' : 'rotate(0)')};
}
&::after {
top: ${props => (props.clicked ? '0' : '0.8rem')};
transform: ${props =>
props.clicked ? 'rotate(-135deg)' : 'rotate(0)'};
}
`;
const NavMenu = styled.div`
position: fixed;
float: right;
margin-right: 1px;
bottom: 5rem;
display: flex;
width: 8.23rem;
flex-direction: column;
border: ${props => (props.open ? '0.1px solid' : '0px solid')};
border-color: ${props =>
props.open ? props.theme.contrast : 'transparent'};
justify-content: center;
align-items: center;
overflow: hidden;
@media (max-width: 768px) {
right: 0;
margin-right: 0;
}
transition: ${props =>
props.open
? 'max-height 1000ms ease-in-out , border-color 800ms ease-in-out, border-width 800ms ease-in-out'
: 'max-height 300ms cubic-bezier(0, 1, 0, 1), border-color 600ms ease-in-out, border-width 800ms ease-in-out'};
max-height: ${props => (props.open ? '100rem' : '0')};
`;
const NavItem = styled.button`
display: flex;
justify-content: right;
align-items: center;
width: 100%;
white-space: nowrap;
height: 3rem;
background-color: ${props => props.theme.walletBackground};
border: none;
color: ${props => props.theme.contrast};
+ gap: 6px;
cursor: pointer;
- gap: 1rem;
&:hover {
color: ${props => props.theme.navActive};
svg {
fill: ${props => props.theme.navActive};
}
}
svg {
fill: ${props => props.theme.contrast};
- width: 26px;
+ max-width: 26px;
height: auto;
+ flex: 1;
+ }
+ p {
+ flex: 2;
+ margin-top: 5px;
}
`;
export const NavButton = styled.button`
:focus,
:active {
outline: none;
}
cursor: pointer;
padding: 0;
background: none;
border: none;
font-size: 10px;
svg {
fill: ${props => props.theme.contrast};
width: 26px;
height: auto;
}
${({ active, ...props }) =>
active &&
`
color: ${props.theme.navActive};
svg {
fill: ${props.theme.navActive};
}
`}
`;
export const WalletBody = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 100vh;
`;
export const WalletCtn = styled.div`
position: relative;
width: 500px;
min-height: 100vh;
padding: 0 0 100px;
background: ${props => props.theme.walletBackground};
-webkit-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
-moz-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
@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: space-between;
width: 100%;
padding: 15px;
`;
export const CashtabLogo = styled.img`
width: 120px;
@media (max-width: 768px) {
width: 110px;
}
`;
// Extension only styled components
const OpenInTabBtn = styled.button`
background: none;
border: none;
`;
const ExtTabImg = styled.img`
max-width: 20px;
`;
const App = () => {
const ContextValue = React.useContext(WalletContext);
const { wallet, loading } = ContextValue;
const [loadingUtxosAfterSend, setLoadingUtxosAfterSend] = useState(false);
const [navMenuClicked, setNavMenuClicked] = useState(false);
const [showApproveAddressShareModal, setShowApproveAddressShareModal] =
useState(false);
const [addressRequestTabId, setAddressRequestTabId] = useState(null);
const [addressRequestTabUrl, setAddressRequestTabUrl] = useState('');
const handleNavMenuClick = () => setNavMenuClicked(!navMenuClicked);
// If wallet is unmigrated, do not show page until it has migrated
// An invalid wallet will be validated/populated after the next API call, ETA 10s
const validWallet = isValidStoredWallet(wallet);
const location = useLocation();
const history = useHistory();
const selectedKey =
location && location.pathname ? location.pathname.substr(1) : '';
// openInTab is an extension-only method
const openInTab = () => {
window.open(`index.html#/${selectedKey}`);
};
// Connect to extension messaging port
const port = extension.runtime.connect({ name: 'cashtabPort' });
// Extension storage get method
const getObjectFromExtensionStorage = async function (key) {
return new Promise((resolve, reject) => {
try {
extension.storage.sync.get(key, function (value) {
resolve(value[key]);
});
} catch (err) {
reject(err);
}
});
};
const copyAddressToExtensionStorage = async wallet => {
// Get address from active wallet
let address;
try {
address = wallet.Path1899.cashAddress;
address = convertToEcashPrefix(address);
console.log(`Address fetched from extension`, address);
} catch (err) {
// The wallet object can be 'false' when Cashtab first loads. In this case, we want this function to do nothing.
return console.log(
`Wallet not loaded yet, exiting copyAddressToExtension`,
);
}
// Save the address to extension storage API
// Check for stored value
const storedAddress = await getObjectFromExtensionStorage(['address']);
console.log(`storedAddress`, storedAddress);
if (address === storedAddress) {
// No need to store it again
console.log(`Active wallet address already in extension storage`);
return;
}
// If the address has not been set (or if the user has changed wallets since it was last set), set it
await extension.storage.sync.set({ address: address }, function () {
console.log(
`Address ${address} saved to storage under key 'address'`,
);
});
};
const handleApprovedAddressShare = () => {
console.log(`handleApprovedAddressShare called`);
// Let the background script know you approved this request
port.postMessage({
type: 'FROM_CASHTAB',
text: 'Cashtab',
addressRequestApproved: true,
url: addressRequestTabUrl,
tabId: addressRequestTabId,
});
setShowApproveAddressShareModal(false);
// Close the popup after user action
window.close();
};
const handleRejectedAddressShare = () => {
console.log(`handleRejectedAddressShare called`);
// Let the background script know you denied this request
port.postMessage({
type: 'FROM_CASHTAB',
text: 'Cashtab',
addressRequestApproved: false,
url: addressRequestTabUrl,
tabId: addressRequestTabId,
});
setShowApproveAddressShareModal(false);
// Close the popup after user action
window.close();
};
useEffect(() => {
copyAddressToExtensionStorage(wallet);
}, [wallet]);
useEffect(() => {
// Parse for query string asking for user approval of sharing extension info with a web page
// Do not set txInfo in state if query strings are not present
if (
!window.location ||
!window.location.hash ||
window.location.hash === '#/wallet'
) {
return;
}
try {
let windowHash = window.location.hash;
let queryStringArray = windowHash.split('#/wallet?');
let queryString = queryStringArray[1];
let queryStringParams = new URLSearchParams(queryString);
let request = queryStringParams.get('request');
let tabId = queryStringParams.get('tabId');
let tabUrl = queryStringParams.get('tabUrl');
console.log(`request`, request);
console.log(`tabId`, tabId);
console.log(`tabUrl`, tabUrl);
if (request !== 'addressRequest') {
return;
}
// Open a modal that asks for user approval
setAddressRequestTabId(tabId);
setAddressRequestTabUrl(tabUrl);
setShowApproveAddressShareModal(true);
} catch (err) {
// If you can't parse this, forget about it
return;
}
// Modal onApprove function should post a message that gets to background.js
}, []);
return (
{showApproveAddressShareModal && (
handleApprovedAddressShare()}
onCancel={() => handleRejectedAddressShare()}
>
The web page {addressRequestTabUrl} is requesting your
eCash address.
)}
{/*Begin extension-only components*/}
openInTab()}
>
{/*End extension-only components*/}
{/*Note that the extension does not support biometric security*/}
{/*Hence is not pulled in*/}
(
)}
/>
{wallet ? (
history.push('/wallet')}
>
history.push('/send')}
>
history.push('receive')}
>
history.push('/airdrop')
}
>
{' '}
- Airdrop
+ Airdrop
+
+ history.push('/signverifymsg')
+ }
+ >
+ Sign & Verify
+
+
history.push('/configure')
}
>
- Settings
+ Settings
) : null}
);
};
App.propTypes = {
match: PropTypes.string,
};
export default App;
diff --git a/web/cashtab/src/assets/audit.svg b/web/cashtab/src/assets/audit.svg
new file mode 100644
index 000000000..539e72f5f
--- /dev/null
+++ b/web/cashtab/src/assets/audit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap b/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap
index 93e5a8902..b5cb8e720 100644
--- a/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap
+++ b/web/cashtab/src/components/Airdrop/__tests__/__snapshots__/Airdrop.test.js.snap
@@ -1,701 +1,701 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
,
,
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
,
]
`;
diff --git a/web/cashtab/src/components/App.js b/web/cashtab/src/components/App.js
index c4b5e7b80..c54165ead 100644
--- a/web/cashtab/src/components/App.js
+++ b/web/cashtab/src/components/App.js
@@ -1,508 +1,526 @@
import React, { useState } from 'react';
import 'antd/dist/antd.less';
import PropTypes from 'prop-types';
import { Spin } from 'antd';
import {
CashLoadingIcon,
HomeIcon,
SendIcon,
ReceiveIcon,
SettingsIcon,
AirdropIcon,
+ ThemedSignAndVerifyMsg,
} from 'components/Common/CustomIcons';
import '../index.css';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { theme } from 'assets/styles/theme';
import Home from 'components/Home/Home';
import Receive from 'components/Receive/Receive';
import Tokens from 'components/Tokens/Tokens';
import Send from 'components/Send/Send';
import SendToken from 'components/Send/SendToken';
import Airdrop from 'components/Airdrop/Airdrop';
import Configure from 'components/Configure/Configure';
import NotFound from 'components/NotFound';
import CashTab from 'assets/cashtab_xec.png';
import './App.css';
import { WalletContext } from 'utils/context';
import { isValidStoredWallet } from 'utils/cashMethods';
import {
Route,
Redirect,
Switch,
useLocation,
useHistory,
} from 'react-router-dom';
// Easter egg imports not used in extension/src/components/App.js
import TabCash from 'assets/tabcash.png';
import { checkForTokenById } from 'utils/tokenMethods.js';
// Biometric security import not used in extension/src/components/App.js
import ProtectableComponentWrapper from './Authentication/ProtectableComponentWrapper';
import ServiceWorkerWrapper from './Common/ServiceWorkerWrapper';
const GlobalStyle = createGlobalStyle`
*::placeholder {
color: ${props => props.theme.forms.placeholder} !important;
}
*::selection {
background: ${props => props.theme.eCashBlue} !important;
}
.ant-modal-content, .ant-modal-header, .ant-modal-title {
background-color: ${props => props.theme.modal.background} !important;
color: ${props => props.theme.modal.color} !important;
}
.ant-modal-content svg {
fill: ${props => props.theme.modal.color};
}
.ant-modal-footer button {
background-color: ${props =>
props.theme.modal.buttonBackground} !important;
color: ${props => props.theme.modal.color} !important;
border-color: ${props => props.theme.modal.border} !important;
:hover {
background-color: ${props => props.theme.eCashBlue} !important;
}
}
.ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button, .ant-modal > button, .ant-modal-confirm-btns > button, .ant-modal-footer > button, #cropControlsConfirm {
border-radius: 3px;
background-color: ${props =>
props.theme.modal.buttonBackground} !important;
color: ${props => props.theme.modal.color} !important;
border-color: ${props => props.theme.modal.border} !important;
:hover {
background-color: ${props => props.theme.eCashBlue} !important;
}
text-shadow: none !important;
}
.ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button:hover,.ant-modal-confirm-btns > button:hover, .ant-modal-footer > button:hover, #cropControlsConfirm:hover {
color: ${props => props.theme.contrast};
transition: all 0.3s;
background-color: ${props => props.theme.eCashBlue};
border-color: ${props => props.theme.eCashBlue};
}
.selectedCurrencyOption, .ant-select-dropdown {
text-align: left;
color: ${props => props.theme.contrast} !important;
background-color: ${props =>
props.theme.collapses.expandedBackground} !important;
}
.cashLoadingIcon {
color: ${props => props.theme.eCashBlue} !important;
font-size: 48px !important;
}
.selectedCurrencyOption:hover {
color: ${props => props.theme.contrast} !important;
background-color: ${props => props.theme.eCashBlue} !important;
}
#addrSwitch, #cropSwitch {
.ant-switch-checked {
background-color: white !important;
}
}
#addrSwitch.ant-switch-checked, #cropSwitch.ant-switch-checked {
background-image: ${props =>
props.theme.buttons.primary.backgroundImage} !important;
}
.ant-slider-rail {
background-color: ${props => props.theme.forms.border} !important;
}
.ant-slider-track {
background-color: ${props => props.theme.eCashBlue} !important;
}
.ant-descriptions-bordered .ant-descriptions-row {
background: ${props => props.theme.contrast};
}
.ant-modal-confirm-content, .ant-modal-confirm-title {
color: ${props => props.theme.contrast} !important;
}
.ant-form-item-explain {
div {
color: ${props => props.theme.forms.text};
}
}
.ant-input-prefix {
color: ${props => props.theme.eCashBlue};
}
.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot {
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
margin: auto !important;
}
.ant-spin-nested-loading>div>.ant-spin {
position: fixed !important;
}
`;
const CustomApp = styled.div`
text-align: center;
font-family: 'Poppins', sans-serif;
background-color: ${props => props.theme.backgroundColor};
background-size: 100px 171px;
background-image: ${props => props.theme.backgroundImage};
background-attachment: fixed;
`;
const Footer = styled.div`
z-index: 2;
height: 80px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
background-color: ${props => props.theme.footerBackground};
position: fixed;
bottom: 0;
width: 500px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 50px;
@media (max-width: 768px) {
width: 100%;
padding: 0 20px;
}
`;
const NavWrapper = styled.div`
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 1.3rem;
margin-bottom: 5px;
`;
const NavIcon = styled.span`
position: relative;
background-color: ${props =>
props.clicked ? 'transparent' : props.theme.buttons.primary.color};
width: 2rem;
height: 2px;
display: inline-block;
transition: transform 300ms, top 300ms, background-color 300ms;
&::before,
&::after {
content: '';
background-color: ${props => props.theme.buttons.primary.color};
width: 2rem;
height: 2px;
display: inline-block;
position: absolute;
left: 0;
transition: transform 300ms, top 300ms, background-color 300ms;
}
&::before {
top: ${props => (props.clicked ? '0' : '-0.8rem')};
transform: ${props => (props.clicked ? 'rotate(135deg)' : 'rotate(0)')};
}
&::after {
top: ${props => (props.clicked ? '0' : '0.8rem')};
transform: ${props =>
props.clicked ? 'rotate(-135deg)' : 'rotate(0)'};
}
`;
const NavMenu = styled.div`
position: fixed;
float: right;
margin-right: 1px;
bottom: 5rem;
display: flex;
width: 8.23rem;
flex-direction: column;
border: ${props => (props.open ? '1px solid' : '0px solid')};
border-color: ${props =>
props.open ? props.theme.contrast : 'transparent'};
justify-content: center;
align-items: center;
+
@media (max-width: 768px) {
right: 0;
margin-right: 0;
}
overflow: hidden;
transition: ${props =>
props.open
? 'max-height 1000ms ease-in-out , border-color 800ms ease-in-out, border-width 800ms ease-in-out'
: 'max-height 300ms cubic-bezier(0, 1, 0, 1), border-color 600ms ease-in-out, border-width 800ms ease-in-out'};
max-height: ${props => (props.open ? '100rem' : '0')};
`;
const NavItem = styled.button`
display: flex;
justify-content: right;
align-items: center;
width: 100%;
white-space: nowrap;
height: 3rem;
background-color: ${props => props.theme.walletBackground};
border: none;
color: ${props => props.theme.contrast};
+ gap: 6px;
cursor: pointer;
- gap: 1rem;
&:hover {
color: ${props => props.theme.navActive};
svg {
fill: ${props => props.theme.navActive};
}
}
svg {
fill: ${props => props.theme.contrast};
- width: 26px;
+ max-width: 26px;
height: auto;
+ flex: 1;
+ }
+ p {
+ flex: 2;
+ margin-top: 5px;
}
`;
export const NavButton = styled.button`
:focus,
:active {
outline: none;
}
cursor: pointer;
padding: 0;
background: none;
border: none;
font-size: 10px;
svg {
fill: ${props => props.theme.contrast};
width: 26px;
height: auto;
}
${({ active, ...props }) =>
active &&
`
color: ${props.theme.navActive};
svg {
fill: ${props.theme.navActive};
}
`}
`;
export const WalletBody = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 100vh;
`;
export const WalletCtn = styled.div`
position: relative;
width: 500px;
min-height: 100vh;
padding: 0 0 100px;
background: ${props => props.theme.walletBackground};
-webkit-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
-moz-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow};
@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: 15px 0;
`;
export const CashTabLogo = styled.img`
width: 120px;
@media (max-width: 768px) {
width: 110px;
}
`;
// AbcLogo styled component not included in extension, replaced by open in new tab link
export const AbcLogo = styled.img`
width: 150px;
@media (max-width: 768px) {
width: 120px;
}
`;
// Easter egg styled component not used in extension/src/components/App.js
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;
}
`;
const App = () => {
const ContextValue = React.useContext(WalletContext);
const { wallet, loading } = ContextValue;
const [loadingUtxosAfterSend, setLoadingUtxosAfterSend] = useState(false);
const [navMenuClicked, setNavMenuClicked] = useState(false);
const handleNavMenuClick = () => setNavMenuClicked(!navMenuClicked);
// If wallet is unmigrated, do not show page until it has migrated
// An invalid wallet will be validated/populated after the next API call, ETA 10s
const validWallet = isValidStoredWallet(wallet);
const location = useLocation();
const history = useHistory();
const selectedKey =
location && location.pathname ? location.pathname.substr(1) : '';
// Easter egg boolean not used in extension/src/components/App.js
const hasTab = validWallet
? checkForTokenById(
wallet.state.tokens,
'50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e',
)
: false;
return (
{/*Begin component not included in extension as desktop only*/}
{hasTab && (
)}
{/*End component not included in extension as desktop only*/}
(
)}
/>
{wallet ? (
history.push('/wallet')}
>
history.push('/send')}
>
history.push('receive')}
>
history.push('/airdrop')
}
>
{' '}
- Airdrop
+ Airdrop
+
+ history.push('/signverifymsg')
+ }
+ >
+ Sign & Verify
+
+
history.push('/configure')
}
>
- Settings
+ Settings
) : null}
);
};
App.propTypes = {
match: PropTypes.string,
};
export default App;
diff --git a/web/cashtab/src/components/Common/CustomIcons.js b/web/cashtab/src/components/Common/CustomIcons.js
index b75ae002b..32b5ea1d8 100644
--- a/web/cashtab/src/components/Common/CustomIcons.js
+++ b/web/cashtab/src/components/Common/CustomIcons.js
@@ -1,186 +1,191 @@
import * as React from 'react';
import styled from 'styled-components';
import {
CopyOutlined,
DollarOutlined,
LoadingOutlined,
WalletOutlined,
QrcodeOutlined,
SettingOutlined,
LockOutlined,
ContactsOutlined,
FireOutlined,
} from '@ant-design/icons';
import { Image } from 'antd';
import { currency } from 'components/Common/Ticker';
import { ReactComponent as Send } from 'assets/send.svg';
import { ReactComponent as Receive } from 'assets/receive.svg';
import { ReactComponent as Genesis } from 'assets/flask.svg';
import { ReactComponent as Unparsed } from 'assets/alert-circle.svg';
import { ReactComponent as Home } from 'assets/home.svg';
import { ReactComponent as Settings } from 'assets/cog.svg';
import { ReactComponent as CopySolid } from 'assets/copy.svg';
import { ReactComponent as LinkSolid } from 'assets/external-link-square-alt.svg';
import { ReactComponent as Airdrop } from 'assets/airdrop-icon.svg';
import { ReactComponent as Pdf } from 'assets/file-pdf.svg';
import { ReactComponent as Plus } from 'assets/plus.svg';
import { ReactComponent as Download } from 'assets/download.svg';
import { ReactComponent as Edit } from 'assets/edit.svg';
import { ReactComponent as Trashcan } from 'assets/trashcan.svg';
import { ReactComponent as FingerprintSVG } from 'assets/fingerprint-solid.svg';
import { ReactComponent as EyeSVG } from 'assets/eye.svg';
import { ReactComponent as EyeInvisibleSVG } from 'assets/eye-invisible.svg';
+import { ReactComponent as Audit } from 'assets/audit.svg';
export const CashLoadingIcon = ;
export const CashReceivedNotificationIcon = () => (
);
export const TokenReceivedNotificationIcon = () => (
);
export const MessageSignedNotificationIcon = () => (
);
export const ThemedBurnOutlined = styled(FireOutlined)`
color: ${props => props.theme.eCashPurple} !important;
`;
export const ThemedCopyOutlined = styled(CopyOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedDollarOutlined = styled(DollarOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedWalletOutlined = styled(WalletOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedQrcodeOutlined = styled(QrcodeOutlined)`
color: ${props => props.theme.walletBackground} !important;
`;
export const ThemedSettingOutlined = styled(SettingOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedLockOutlined = styled(LockOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedContactsOutlined = styled(ContactsOutlined)`
color: ${props => props.theme.icons.outlined} !important;
`;
export const ThemedContactSendOutlined = styled(Send)`
color: ${props => props.theme.icons.outlined} !important;
transform: rotate(-35deg);
padding: 0.15rem 0rem 0.18rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedCopySolid = styled(CopySolid)`
fill: ${props => props.theme.contrast};
padding: 0rem 0rem 0.27rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedLinkSolid = styled(LinkSolid)`
fill: ${props => props.theme.contrast};
padding: 0.15rem 0rem 0.18rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedPdfSolid = styled(Pdf)`
fill: ${props => props.theme.contrast};
padding: 0.15rem 0rem 0.18rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedPlusOutlined = styled(Plus)`
fill: ${props => props.theme.contrast};
padding: 0.15rem 0rem 0.18rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedDownloadOutlined = styled(Download)`
fill: ${props => props.theme.contrast};
padding: 0.15rem 0rem 0.18rem 0rem;
height: 1.3em;
width: 1.3em;
`;
export const ThemedEditOutlined = styled(Edit)`
stroke: ${props => props.theme.eCashBlue};
fill: ${props => props.theme.eCashBlue};
width: 20px;
height: 20px;
cursor: pointer;
`;
export const ThemedTrashcanOutlined = styled(Trashcan)`
stroke: ${props => props.theme.eCashBlue};
fill: ${props => props.theme.eCashBlue};
width: 20px;
height: 20px;
cursor: pointer;
`;
export const ThemedFingerprintSVG = styled(FingerprintSVG)`
fill: ${props => props.theme.buttons.primary.color};
`;
export const ThemedEyeSVG = styled(EyeSVG)`
fill: ${props => props.theme.buttons.primary.color};
width: 15px;
height: 15px;
cursor: pointer;
`;
export const ThemedInvisibleEyeSVG = styled(EyeInvisibleSVG)`
fill: ${props => props.theme.buttons.primary.color};
width: 15px;
height: 15px;
cursor: pointer;
`;
+export const ThemedSignAndVerifyMsg = styled(Audit)`
+ min-width: 24px;
+`;
+
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: ${props => props.theme.eCashBlue};
}
`;
export const CashLoader = () => (
);
export const ReceiveIcon = () => ;
export const GenesisIcon = () => ;
export const UnparsedIcon = () => ;
export const HomeIcon = () => ;
export const SettingsIcon = () => ;
export const AirdropIcon = () => ;
export const SendIcon = styled(Send)`
transform: rotate(-35deg);
`;
export const CustomSpinner = ;
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 6309d4886..aa38b4932 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,1129 +1,1129 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances and tokens 1`] = `
Backup your wallet
Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
Manage Wallets
New Wallet
Import Wallet
Fiat Currency
General Settings
Hide messages from unknown sender
`;
exports[`Without wallet defined 1`] = `
Backup your wallet
Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
Manage Wallets
New Wallet
Import Wallet
Fiat Currency
General Settings
Hide messages from unknown sender
`;
diff --git a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
index 284478223..ac679af15 100644
--- a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
+++ b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
@@ -1,631 +1,631 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
,
Transactions
eTokens
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
Create eToken
Tokens sent to your
eToken
address will appear here
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
,
Transactions
eTokens
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
Create eToken
Tokens sent to your
eToken
address will appear here
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
,
Transactions
eTokens
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
,
Transactions
eTokens
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
Create eToken
Tokens sent to your
eToken
address will appear here
,
]
`;
exports[`Without wallet defined 1`] = `
`;
diff --git a/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
index 0ff976e0a..d1285a175 100644
--- a/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
+++ b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
@@ -1,819 +1,819 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
You currently have 0
XEC
Deposit some funds to use this feature
Receive
XEC
`;
exports[`Wallet with BCH balances and tokens 1`] = `
You currently have 0
XEC
Deposit some funds to use this feature
Receive
XEC
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
`;
exports[`Wallet without BCH balance 1`] = `
You currently have 0
XEC
Deposit some funds to use this feature
Receive
XEC
`;
exports[`Without wallet defined 1`] = `
`;
diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
index cd0f1898b..38a8a33d4 100644
--- a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
+++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
@@ -1,3017 +1,3017 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
,
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
]
`;
diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
index 6fdafd72b..f671f2423 100644
--- a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
+++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
@@ -1,238 +1,238 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances and tokens 1`] = `null`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
`;
exports[`Without wallet defined 1`] = `null`;
diff --git a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
index 462d77e42..31c76dafd 100644
--- a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
+++ b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
@@ -1,354 +1,354 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Create a Token
Create eToken
`;
diff --git a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
index 9929e7503..550d0db5a 100644
--- a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
+++ b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
@@ -1,789 +1,789 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
,
Create a Token
You need at least
5.5
XEC
(
$
NaN
USD
) to create a token
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
,
Create a Token
You need at least
5.5
XEC
(
$
NaN
USD
) to create a token
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
,
Create a Token
Create eToken
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
,
Create a Token
You need at least
5.5
XEC
(
$
NaN
USD
) to create a token
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
,
Create a Token
You need at least
5.5
XEC
(
$
NaN
USD
) to create a token
,
]
`;