diff --git a/web/cashtab/extension/src/components/App.js b/web/cashtab/extension/src/components/App.js
index 6c916a94b..de3215e66 100644
--- a/web/cashtab/extension/src/components/App.js
+++ b/web/cashtab/extension/src/components/App.js
@@ -1,325 +1,343 @@
import React, { useState } from 'react';
import 'antd/dist/antd.less';
import { Spin } from 'antd';
-import { CashLoadingIcon } from '@components/Common/CustomIcons';
+import {
+ CashLoadingIcon,
+ HomeIcon,
+ SendIcon,
+ ReceiveIcon,
+ SettingsIcon,
+} from '@components/Common/CustomIcons';
import '../index.css';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { theme } from '@assets/styles/theme';
-import {
- FolderOpenFilled,
- CaretRightOutlined,
- SettingFilled,
- AppstoreAddOutlined,
-} from '@ant-design/icons';
-import Wallet from '@components/Wallet/Wallet';
+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 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 WalletLabel from '@components/Common/WalletLabel.js';
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';
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: 8px;
- background-color: ${props => props.theme.modals.buttons.background};
- color: ${props => props.theme.wallet.text.secondary};
- font-weight: bold;
+ 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.primary};
- transition: color 0.3s;
- background-color: ${props => props.theme.modals.buttons.background};
+ color: ${props => props.theme.contrast};
+ transition: all 0.3s;
+ background-color: ${props => props.theme.eCashBlue};
+ border-color: ${props => props.theme.eCashBlue};
}
- .selectedCurrencyOption {
+ .selectedCurrencyOption, .ant-select-dropdown {
text-align: left;
- color: ${props => props.theme.wallet.text.secondary} !important;
- background-color: ${props => props.theme.contrast} !important;
+ color: ${props => props.theme.contrast} !important;
+ background-color: ${props =>
+ props.theme.collapses.expandedBackground} !important;
}
.cashLoadingIcon {
- color: ${props => props.theme.primary} !important;
+ color: ${props => props.theme.eCashBlue} !important;
font-size: 48px !important;
}
.selectedCurrencyOption:hover {
color: ${props => props.theme.contrast} !important;
- background-color: ${props => props.theme.primary} !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.primary} !important;
+ 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;
}
`;
const CustomApp = styled.div`
text-align: center;
font-family: 'Gilroy', sans-serif;
- background-color: ${props => props.theme.app.background};
+ 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;
- background-color: ${props => props.theme.footer.background};
- border-radius: 20px;
+ 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;
}
- border-top: 1px solid ${props => props.theme.wallet.borders.color};
`;
export const NavButton = styled.button`
:focus,
:active {
outline: none;
}
cursor: pointer;
- padding: 24px 12px 12px 12px;
- margin: 0 28px;
- @media (max-width: 475px) {
- margin: 0 20px;
- }
- @media (max-width: 420px) {
- margin: 0 12px;
- }
- @media (max-width: 350px) {
- margin: 0 8px;
- }
- background-color: ${props => props.theme.footer.background};
+ padding: 0;
+ background: none;
border: none;
- font-size: 12px;
- font-weight: bold;
- .anticon {
- display: block;
- color: ${props => props.theme.footer.navIconInactive};
- font-size: 24px;
- margin-bottom: 6px;
+ font-size: 10px;
+ svg {
+ fill: ${props => props.theme.contrast};
+ width: 26px;
+ height: auto;
}
${({ active, ...props }) =>
active &&
- `
- color: ${props.theme.primary};
- .anticon {
- color: ${props.theme.primary};
- }
+ `
+ 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;
- background-image: ${props => props.theme.app.sidebars};
- background-attachment: fixed;
`;
export const WalletCtn = styled.div`
position: relative;
width: 500px;
- background-color: ${props => props.theme.footerBackground};
min-height: 100vh;
- padding: 10px 30px 120px 30px;
- background: ${props => props.theme.wallet.background};
- -webkit-box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
- -moz-box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
- box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
+ 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: 20px 0 30px;
- margin-bottom: 20px;
justify-content: space-between;
- border-bottom: 1px solid ${props => props.theme.wallet.borders.color};
-
- a {
- color: ${props => props.theme.wallet.text.secondary};
-
- :hover {
- color: ${props => props.theme.primary};
- }
- }
-
- @media (max-width: 768px) {
- a {
- font-size: 12px;
- }
- padding: 10px 0 20px;
- }
+ 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);
// 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}`);
};
return (
{/*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')}
>
-
- Wallet
+
history.push('/tokens')}
+ active={selectedKey === 'send'}
+ onClick={() => history.push('/send')}
>
-
- eTokens
+
-
history.push('/send')}
+ active={selectedKey === 'receive'}
+ onClick={() => history.push('receive')}
>
-
- Send
+
history.push('/configure')}
>
-
- Settings
+
) : null}
);
};
export default App;
diff --git a/web/cashtab/public/cashtab_bg.png b/web/cashtab/public/cashtab_bg.png
index 372c389b7..bed03771d 100644
Binary files a/web/cashtab/public/cashtab_bg.png and b/web/cashtab/public/cashtab_bg.png differ
diff --git a/web/cashtab/public/cashtab_twitter.png b/web/cashtab/public/cashtab_twitter.png
index df43505da..ee3268618 100644
Binary files a/web/cashtab/public/cashtab_twitter.png and b/web/cashtab/public/cashtab_twitter.png differ
diff --git a/web/cashtab/src/assets/alert-circle.svg b/web/cashtab/src/assets/alert-circle.svg
new file mode 100644
index 000000000..7c01d8f61
--- /dev/null
+++ b/web/cashtab/src/assets/alert-circle.svg
@@ -0,0 +1 @@
+Alert Circle
\ No newline at end of file
diff --git a/web/cashtab/src/assets/cashtab_xec.png b/web/cashtab/src/assets/cashtab_xec.png
index 39c68e7a9..51bd8541e 100644
Binary files a/web/cashtab/src/assets/cashtab_xec.png and b/web/cashtab/src/assets/cashtab_xec.png differ
diff --git a/web/cashtab/src/assets/cog.svg b/web/cashtab/src/assets/cog.svg
new file mode 100644
index 000000000..10c3c4fda
--- /dev/null
+++ b/web/cashtab/src/assets/cog.svg
@@ -0,0 +1 @@
+Cog
\ No newline at end of file
diff --git a/web/cashtab/src/assets/flask.svg b/web/cashtab/src/assets/flask.svg
new file mode 100644
index 000000000..7f8593ab4
--- /dev/null
+++ b/web/cashtab/src/assets/flask.svg
@@ -0,0 +1 @@
+Flask
\ No newline at end of file
diff --git a/web/cashtab/src/assets/fonts/Poppins-Bold.ttf b/web/cashtab/src/assets/fonts/Poppins-Bold.ttf
new file mode 100755
index 000000000..b94d47f3a
Binary files /dev/null and b/web/cashtab/src/assets/fonts/Poppins-Bold.ttf differ
diff --git a/web/cashtab/src/assets/fonts/Poppins-Regular.ttf b/web/cashtab/src/assets/fonts/Poppins-Regular.ttf
new file mode 100755
index 000000000..be06e7fdc
Binary files /dev/null and b/web/cashtab/src/assets/fonts/Poppins-Regular.ttf differ
diff --git a/web/cashtab/src/assets/home.svg b/web/cashtab/src/assets/home.svg
new file mode 100644
index 000000000..94cf77d4c
--- /dev/null
+++ b/web/cashtab/src/assets/home.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/web/cashtab/src/assets/receive.svg b/web/cashtab/src/assets/receive.svg
new file mode 100644
index 000000000..8b3aaeb92
--- /dev/null
+++ b/web/cashtab/src/assets/receive.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/cashtab/src/assets/send.svg b/web/cashtab/src/assets/send.svg
new file mode 100644
index 000000000..f7b9fb467
--- /dev/null
+++ b/web/cashtab/src/assets/send.svg
@@ -0,0 +1 @@
+Send
\ No newline at end of file
diff --git a/web/cashtab/src/assets/styles/theme.js b/web/cashtab/src/assets/styles/theme.js
index fc96fc8b9..4a28eeb27 100644
--- a/web/cashtab/src/assets/styles/theme.js
+++ b/web/cashtab/src/assets/styles/theme.js
@@ -1,96 +1,71 @@
export const theme = {
- primary: '#00ABE7',
- brandSecondary: '#CD0BC3',
+ eCashBlue: '#00ABE7',
+ eCashPurple: '#ff21d0',
+ darkBlue: '#273498',
contrast: '#fff',
- app: {
- sidebars: `url("/cashtab_bg.png")`,
- background: '#fbfbfd',
- },
- wallet: {
- background: '#fff',
- encryption: '#DC143C',
- text: {
- primary: '#273498',
- secondary: '#273498',
- },
- switch: {
- activeCash: {
- shadow: 'inset 8px 8px 16px #0074C2, inset -8px -8px 16px #273498',
- },
- activeToken: {
- background: '#CD0BC3',
- shadow: 'inset 5px 5px 11px #FF21D0, inset -5px -5px 11px #CD0BC3',
- },
- inactive: {
- background: 'linear-gradient(145deg, #eeeeee, #c8c8c8)',
- },
- },
- borders: { color: '#e2e2e2' },
- shadow: 'rgba(0, 0, 0, 1)',
- },
- tokenListItem: {
- background: '#ffffff',
- color: '',
- boxShadow:
- 'rgb(136 172 243 / 25%) 0px 10px 30px,rgb(0 0 0 / 3%) 0px 1px 1px, rgb(0 51 167 / 10%) 0px 10px 20px',
- border: '#e9eaed',
- hoverBorder: '#231F20',
- },
- footer: {
- background: '#fff',
- navIconInactive: '#949494',
+ backgroundImage: `url("/cashtab_bg.png")`,
+ backgroundColor: '#d5d5d7',
+ walletBackground: '#152b45',
+ walletInfoContainer: '#255173',
+ footerBackground: '#152b45',
+ navActive: '#00ABE7',
+ encryptionRed: '#DC143C',
+ genesisGreen: '#00e781',
+ receivedMessage: 'rgba(0,171,231,0.2)',
+ sentMessage: 'rgba(255, 255, 255, 0.1)',
+ lightWhite: 'rgba(255,255,255,0.4)',
+ dropdownText: '#000',
+ shadow: 'rgba(0, 0, 0, 0.4)',
+ switchButtonActiveText: '#fff',
+ advancedCollapse: {
+ background: '#255173',
+ color: '#fff',
+ icon: '#fff',
+ expandedBackground: 'rgba(0,0,0,0.2)',
},
forms: {
error: '#FF21D0',
- border: '#e7edf3',
- text: '#001137',
- addonBackground: '#f4f4f4',
- addonForeground: '#3e3f42',
- selectionBackground: '#fff',
- darkLabel: '#0074c2',
- lightLabel: '#c0c0c0',
- },
- icons: { outlined: '#273498' },
- modals: {
- buttons: { background: '#fff' },
+ border: '#17171f',
+ text: '#fff',
+ addonBackground: '#255173',
+ addonForeground: '#fff',
+ selectionBackground: '#255173',
+ placeholder: 'rgba(255,255,255,0.3)',
+ highlightBox: '#00ABE7',
},
+ icons: { outlined: '#fff' },
settings: {
delete: '#CD0BC3',
- background: '#eee',
+ background: 'rgba(0,0,0,0.4)',
},
qr: {
- copyBorderCash: '#00ABE7',
- copyBorderToken: '#FF21D0',
background: '#fff',
- token: '#231F20',
- shadow: 'rgb(136 172 243 / 25%) 0px 10px 30px, rgb(0 0 0 / 3%) 0px 1px 1px, rgb(0 51 167 / 10%) 0px 10px 20px',
},
buttons: {
primary: {
backgroundImage:
'linear-gradient(270deg, #0074C2 0%, #273498 100%)',
color: '#fff',
hoverShadow: '0px 3px 10px -5px rgba(0, 0, 0, 0.75)',
disabledOverlay: 'rgba(255, 255, 255, 0.5)',
},
secondary: {
- background: '#e9eaed',
- color: '#444',
+ background: '#4b67e1',
+ color: '#fff',
hoverShadow: '0px 3px 10px -5px rgba(0, 0, 0, 0.75)',
disabledOverlay: 'rgba(255, 255, 255, 0.5)',
},
},
collapses: {
- background: '#fbfcfd',
- expandedBackground: '#fff',
- border: '#eaedf3',
- color: '#3e3f42',
+ background: '#255173',
+ expandedBackground: '#26415a',
+ border: '#17171f',
+ color: '#fff',
},
- generalSettings: {
- item: {
- icon: '#949494',
- title: '#949494',
- },
- background: '#fff',
+ modal: {
+ background: '#255173',
+ border: '#17171f',
+ color: '#fff',
+ buttonBackground: '#26415a',
},
};
diff --git a/web/cashtab/src/components/App.css b/web/cashtab/src/components/App.css
index 63140c657..fdc009cb0 100644
--- a/web/cashtab/src/components/App.css
+++ b/web/cashtab/src/components/App.css
@@ -1,51 +1,65 @@
@import '~antd/dist/antd.less';
@import '~@fortawesome/fontawesome-free/css/all.css';
@import url('https://fonts.googleapis.com/css?family=Khula&display=swap&.css');
+@font-face {
+ font-family: 'Poppins';
+ src: local('Poppins'),
+ url(../assets/fonts/Poppins-Regular.ttf) format('truetype');
+ font-weight: normal;
+}
+
+@font-face {
+ font-family: 'Poppins';
+ src: local('Poppins'),
+ url(../assets/fonts/Poppins-Bold.ttf) format('truetype');
+ font-weight: 700;
+}
+
/* Hide scrollbars but keep functionality*/
/* Hide scrollbar for Chrome, Safari and Opera */
body::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
body {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* 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*/
/* useWallet.js, useBCH.js */
@media (max-width: 768px) {
.ant-notification {
width: 100%;
top: 20px !important;
max-width: unset;
margin-right: 0;
}
}
diff --git a/web/cashtab/src/components/App.js b/web/cashtab/src/components/App.js
index 917167651..085b2cfdd 100644
--- a/web/cashtab/src/components/App.js
+++ b/web/cashtab/src/components/App.js
@@ -1,358 +1,363 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
import 'antd/dist/antd.less';
-import { Modal, Spin } from 'antd';
-import { CashLoadingIcon } from '@components/Common/CustomIcons';
+import { Spin } from 'antd';
+import {
+ CashLoadingIcon,
+ HomeIcon,
+ SendIcon,
+ ReceiveIcon,
+ SettingsIcon,
+} from '@components/Common/CustomIcons';
import '../index.css';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { theme } from '@assets/styles/theme';
-import {
- FolderOpenFilled,
- CaretRightOutlined,
- SettingFilled,
- AppstoreAddOutlined,
-} from '@ant-design/icons';
-import Wallet from '@components/Wallet/Wallet';
+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 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 WalletLabel from '@components/Common/WalletLabel.js';
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 ABC from '@assets/logo_topright.png';
import { checkForTokenById } from '@utils/tokenMethods.js';
// Biometric security import not used in extension/src/components/App.js
import ProtectableComponentWrapper from './Authentication/ProtectableComponentWrapper';
-const GlobalStyle = createGlobalStyle`
+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: 8px;
- background-color: ${props => props.theme.modals.buttons.background};
- color: ${props => props.theme.wallet.text.secondary};
- font-weight: bold;
+ 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.primary};
- transition: color 0.3s;
- background-color: ${props => props.theme.modals.buttons.background};
+ color: ${props => props.theme.contrast};
+ transition: all 0.3s;
+ background-color: ${props => props.theme.eCashBlue};
+ border-color: ${props => props.theme.eCashBlue};
}
- .selectedCurrencyOption {
+ .selectedCurrencyOption, .ant-select-dropdown {
text-align: left;
- color: ${props => props.theme.wallet.text.secondary} !important;
- background-color: ${props => props.theme.contrast} !important;
+ color: ${props => props.theme.contrast} !important;
+ background-color: ${props =>
+ props.theme.collapses.expandedBackground} !important;
}
.cashLoadingIcon {
- color: ${props => props.theme.primary} !important;
+ color: ${props => props.theme.eCashBlue} !important;
font-size: 48px !important;
}
.selectedCurrencyOption:hover {
color: ${props => props.theme.contrast} !important;
- background-color: ${props => props.theme.primary} !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.primary} !important;
+ 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;
}
`;
const CustomApp = styled.div`
text-align: center;
- font-family: 'Gilroy', sans-serif;
- background-color: ${props => props.theme.app.background};
+ 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;
- background-color: ${props => props.theme.footer.background};
- border-radius: 20px 20px 0 0;
+ height: 80px;
+ border-top: 1px solid rgba(255, 255, 255, 0.5);
+ background-color: ${props => props.theme.footerBackground};
position: fixed;
bottom: 0;
width: 500px;
- box-shadow: rgb(136 172 243 / 25%) 0px 10px 30px,
- rgb(0 0 0 / 3%) 0px 1px 1px, rgb(0 51 167 / 10%) 0px 10px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 50px;
@media (max-width: 768px) {
width: 100%;
+ padding: 0 20px;
}
`;
export const NavButton = styled.button`
:focus,
:active {
outline: none;
}
cursor: pointer;
- padding: 24px 12px 12px 12px;
- margin: 0 28px;
- @media (max-width: 475px) {
- margin: 0 20px;
- }
- @media (max-width: 420px) {
- margin: 0 12px;
- }
- @media (max-width: 350px) {
- margin: 0 8px;
- }
- background-color: ${props => props.theme.footer.background};
+ padding: 0;
+ background: none;
border: none;
- font-size: 10.5px;
- font-weight: bold;
- .anticon {
- display: block;
- color: ${props => props.theme.footer.navIconInactive};
- font-size: 24px;
- margin-bottom: 6px;
+ font-size: 10px;
+ svg {
+ fill: ${props => props.theme.contrast};
+ width: 26px;
+ height: auto;
}
${({ active, ...props }) =>
active &&
`
- color: ${props.theme.primary};
- .anticon {
- color: ${props.theme.primary};
+ 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;
- background-image: ${props => props.theme.app.sidebars};
- background-attachment: fixed;
`;
export const WalletCtn = styled.div`
position: relative;
width: 500px;
- background-color: ${props => props.theme.footerBackground};
min-height: 100vh;
- padding: 10px 30px 120px 30px;
- background: ${props => props.theme.wallet.background};
- -webkit-box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
- -moz-box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
- box-shadow: 0px 0px 24px 1px ${props => props.theme.wallet.shadow};
+ 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: 20px 0 30px;
- margin-bottom: 20px;
- justify-content: space-between;
-
- a {
- color: ${props => props.theme.wallet.text.secondary};
-
- :hover {
- color: ${props => props.theme.primary};
- }
- }
-
- @media (max-width: 768px) {
- a {
- font-size: 12px;
- }
- padding: 10px 0 20px;
- }
+ 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);
// 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*/}
- {/*Begin component not included in extension as replaced by open in tab link*/}
-
-
-
- {/*Begin component not included in extension as replaced by open in tab link*/}
-
-
+
+
+
+
(
)}
/>
{wallet ? (
history.push('/wallet')}
>
-
- Wallet
+
history.push('/tokens')}
+ active={selectedKey === 'send'}
+ onClick={() => history.push('/send')}
>
-
- eTokens
+
-
history.push('/send')}
+ active={selectedKey === 'receive'}
+ onClick={() => history.push('receive')}
>
-
- Send
+
history.push('/configure')}
>
-
- Settings
+
) : null}
);
};
export default App;
diff --git a/web/cashtab/src/components/Authentication/SignIn.js b/web/cashtab/src/components/Authentication/SignIn.js
index a1c188e3f..b9f48bddb 100644
--- a/web/cashtab/src/components/Authentication/SignIn.js
+++ b/web/cashtab/src/components/Authentication/SignIn.js
@@ -1,155 +1,155 @@
import React, { useContext, useEffect, useState } from 'react';
import { Modal, Spin } from 'antd';
import styled from 'styled-components';
import { AuthenticationContext } from '@utils/context';
import { ThemedLockOutlined } from '@components/Common/CustomIcons';
import PrimaryButton from '@components/Common/PrimaryButton';
import { ReactComponent as FingerprintSVG } from '@assets/fingerprint-solid.svg';
const StyledSignIn = styled.div`
h2 {
- color: ${props => props.theme.wallet.text.primary};
+ color: ${props => props.theme.contrast};
font-size: 25px;
}
p {
- color: ${props => props.theme.wallet.text.secondary};
+ color: ${props => props.theme.darkBlue};
}
`;
const UnlockButton = styled(PrimaryButton)`
position: relative;
width: auto;
margin: 30px auto;
padding: 20px 30px;
svg {
fill: ${props => props.theme.buttons.primary.color};
}
@media (max-width: 768px) {
font-size: 16px;
padding: 15px 20px;
}
:disabled {
cursor: not-allowed;
box-shadow: none;
::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: ${props => props.theme.buttons.primary.disabledOverlay};
z-index: 10;
}
}
`;
const StyledFingerprintIcon = styled.div`
width: 48px;
height: 48px;
margin: auto;
`;
const SignIn = () => {
const authentication = useContext(AuthenticationContext);
const [isVisible, setIsVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleDocVisibilityChange = () => {
document.visibilityState === 'visible'
? setIsVisible(true)
: setIsVisible(false);
};
const handleSignIn = async () => {
try {
setIsLoading(true);
await authentication.signIn();
} catch (err) {
Modal.error({
title: 'Authentication Error',
content: 'Cannot get Credential from your device',
centered: true,
});
}
setIsLoading(false);
};
const handleSignInAndSuppressError = async () => {
try {
setIsLoading(true);
await authentication.signIn();
} catch (err) {
// fail silently
}
setIsLoading(false);
};
useEffect(() => {
if (document.visibilityState === 'visible') {
setIsVisible(true);
}
document.addEventListener(
'visibilitychange',
handleDocVisibilityChange,
);
return () => {
document.removeEventListener(
'visibilitychange',
handleDocVisibilityChange,
);
};
}, []);
useEffect(() => {
// This will trigger the plaform authenticator as soon as the component becomes visible
// (when switch back to this app), without any user gesture (such as clicking a button)
// In platforms that require user gesture in order to invoke the platform authenticator,
// this will fail. We want it to fail silently, and then show user a button to activate
// the platform authenticator
if (isVisible && authentication) {
handleSignInAndSuppressError();
}
}, [isVisible]);
let signInBody;
if (authentication) {
signInBody = (
<>
This wallet can be unlocked with your{' '}
fingerprint / device pin
Unlock
{isLoading ? : ''}
>
);
} else {
signInBody = Authentication is not supported
;
}
return (
Wallet Unlock
{signInBody}
);
};
export default SignIn;
diff --git a/web/cashtab/src/components/Authentication/SignUp.js b/web/cashtab/src/components/Authentication/SignUp.js
index 31b67c8db..dbe1d6b99 100644
--- a/web/cashtab/src/components/Authentication/SignUp.js
+++ b/web/cashtab/src/components/Authentication/SignUp.js
@@ -1,74 +1,76 @@
import React, { useContext } from 'react';
import { Modal } from 'antd';
import styled from 'styled-components';
import { AuthenticationContext } from '@utils/context';
import { ThemedLockOutlined } from '@components/Common/CustomIcons';
import PrimaryButton, {
SecondaryButton,
} from '@components/Common/PrimaryButton';
const StyledSignUp = styled.div`
+ padding: 0px 30px;
+ margin-top: 20px;
h2 {
- color: ${props => props.theme.wallet.text.primary};
+ color: ${props => props.theme.contrast};
font-size: 25px;
}
p {
- color: ${props => props.theme.wallet.text.secondary};
+ color: ${props => props.theme.contrast};
}
`;
const SignUp = () => {
const authentication = useContext(AuthenticationContext);
const handleSignUp = async () => {
try {
await authentication.signUp();
} catch (err) {
Modal.error({
title: 'Registration Error',
content: 'Cannot create Credential on your device',
centered: true,
});
}
};
let signUpBody;
if (authentication) {
signUpBody = (
Enable wallet lock to protect your funds.
You will need to unlock with your{' '}
fingerprint / device pin in order to access
the wallet.
This lock can also be enabled / disabled under
Settings / General Settings / App Lock
Enable Lock
authentication.turnOffAuthentication()}
>
Skip
);
} else {
signUpBody = Authentication is not supported
;
}
return (
Wallet Lock
{signUpBody}
);
};
export default SignUp;
diff --git a/web/cashtab/src/components/Common/Alerts.js b/web/cashtab/src/components/Common/Alerts.js
deleted file mode 100644
index 81790d5de..000000000
--- a/web/cashtab/src/components/Common/Alerts.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import { Alert } from 'antd';
-
-const TokenIconAlert = () => {
- return (
- <>
-
- >
- );
-};
-
-export default TokenIconAlert;
diff --git a/web/cashtab/src/components/Common/Atoms.js b/web/cashtab/src/components/Common/Atoms.js
index afa1ec70c..84258f309 100644
--- a/web/cashtab/src/components/Common/Atoms.js
+++ b/web/cashtab/src/components/Common/Atoms.js
@@ -1,63 +1,84 @@
import styled from 'styled-components';
export const LoadingCtn = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 400px;
flex-direction: column;
svg {
width: 50px;
height: 50px;
- fill: ${props => props.theme.primary};
+ fill: ${props => props.theme.eCashBlue};
}
`;
-export const BalanceHeaderWrap = styled.div`
- color: ${props => props.theme.wallet.text.primary};
- width: 100%;
- font-size: 30px;
- font-weight: bold;
+export const SidePaddingCtn = styled.div`
+ padding: 0px 30px;
@media (max-width: 768px) {
- font-size: 23px;
+ padding: 0px 15px;
}
`;
+export const FormLabel = styled.label`
+ font-size: 16px;
+ margin-bottom: 5px;
+ text-align: left;
+ width: 100%;
+ display: inline-block;
+ color: ${props => props.theme.contrast};
+`;
+
+export const WalletInfoCtn = styled.div`
+ background: ${props => props.theme.walletInfoContainer};
+ width: 100%;
+ padding: 40px 20px;
+`;
+
export const BalanceHeaderFiatWrap = styled.div`
- color: ${props => props.theme.wallet.text.secondary};
+ color: ${props => props.theme.contrast};
width: 100%;
- font-size: 18px;
- margin-bottom: 20px;
- font-weight: bold;
+ font-size: 16px;
@media (max-width: 768px) {
font-size: 16px;
}
`;
+export const BalanceHeaderWrap = styled.div`
+ color: ${props => props.theme.contrast};
+ width: 100%;
+ font-size: 28px;
+ margin-bottom: 0px;
+ font-weight: bold;
+ line-height: 1.4em;
+ @media (max-width: 768px) {
+ font-size: 24px;
+ }
+`;
+
export const ZeroBalanceHeader = styled.div`
- color: ${props => props.theme.wallet.text.primary};
+ color: ${props => props.theme.contrast};
width: 100%;
font-size: 14px;
margin-bottom: 5px;
`;
export const TokenParamLabel = styled.span`
font-weight: bold;
`;
export const AlertMsg = styled.p`
color: ${props => props.theme.forms.error} !important;
`;
export const ConvertAmount = styled.div`
- color: ${props => props.theme.wallet.text.secondary};
+ color: ${props => props.theme.contrast};
width: 100%;
font-size: 14px;
margin-bottom: 10px;
- font-weight: bold;
@media (max-width: 768px) {
font-size: 12px;
}
`;
diff --git a/web/cashtab/src/components/Common/BalanceHeaderFiat.js b/web/cashtab/src/components/Common/BalanceHeaderFiat.js
index 9fa83c4b8..dec4d14a1 100644
--- a/web/cashtab/src/components/Common/BalanceHeaderFiat.js
+++ b/web/cashtab/src/components/Common/BalanceHeaderFiat.js
@@ -1,28 +1,28 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { BalanceHeaderFiatWrap } from '@components/Common/Atoms';
import { currency } from '@components/Common/Ticker.js';
const BalanceHeaderFiat = ({ balance, settings, fiatPrice }) => {
return (
{settings
- ? `${currency.fiatCurrencies[settings.fiatCurrency].symbol} `
- : '$ '}
+ ? `${currency.fiatCurrencies[settings.fiatCurrency].symbol}`
+ : '$'}
{parseFloat((balance * fiatPrice).toFixed(2)).toLocaleString()}{' '}
{settings
? `${currency.fiatCurrencies[
settings.fiatCurrency
].slug.toUpperCase()} `
: 'USD'}
);
};
BalanceHeaderFiat.propTypes = {
balance: PropTypes.number,
settings: PropTypes.object,
fiatPrice: PropTypes.number,
};
export default BalanceHeaderFiat;
diff --git a/web/cashtab/src/components/Common/CustomIcons.js b/web/cashtab/src/components/Common/CustomIcons.js
index 59ae456c3..c487f449f 100644
--- a/web/cashtab/src/components/Common/CustomIcons.js
+++ b/web/cashtab/src/components/Common/CustomIcons.js
@@ -1,74 +1,89 @@
import * as React from 'react';
import styled from 'styled-components';
import {
CopyOutlined,
DollarOutlined,
LoadingOutlined,
WalletOutlined,
QrcodeOutlined,
SettingOutlined,
LockOutlined,
} 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';
export const CashLoadingIcon = ;
export const CashReceivedNotificationIcon = () => (
);
export const TokenReceivedNotificationIcon = () => (
);
export const MessageSignedNotificationIcon = () => (
);
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.icons.outlined} !important;
+ 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 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.primary};
+ 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 SendIcon = styled(Send)`
+ transform: rotate(-35deg);
+`;
diff --git a/web/cashtab/src/components/Common/EnhancedInputs.js b/web/cashtab/src/components/Common/EnhancedInputs.js
index 66c027e3d..ed8a23389 100644
--- a/web/cashtab/src/components/Common/EnhancedInputs.js
+++ b/web/cashtab/src/components/Common/EnhancedInputs.js
@@ -1,363 +1,375 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Form, Input, Select } from 'antd';
const { TextArea } = Input;
import {
ThemedDollarOutlined,
ThemedWalletOutlined,
} from '@components/Common/CustomIcons';
import styled, { css } from 'styled-components';
import ScanQRCode from './ScanQRCode';
import useBCH from '@hooks/useBCH';
import { currency } from '@components/Common/Ticker.js';
export const AntdFormCss = css`
.ant-input-group-addon {
background-color: ${props =>
props.theme.forms.addonBackground} !important;
border: 1px solid ${props => props.theme.forms.border};
color: ${props => props.theme.forms.addonForeground} !important;
}
input.ant-input,
.ant-select-selection {
background-color: ${props =>
props.theme.forms.selectionBackground} !important;
box-shadow: none !important;
border-radius: 4px;
font-weight: bold;
color: ${props => props.theme.forms.text};
opacity: 1;
- height: 50px;
+ height: 45px;
}
textarea.ant-input,
.ant-select-selection {
background-color: ${props =>
props.theme.forms.selectionBackground} !important;
box-shadow: none !important;
border-radius: 4px;
font-weight: bold;
color: ${props => props.theme.forms.text};
opacity: 1;
height: 50px;
min-height: 100px;
}
.ant-input-affix-wrapper {
background-color: ${props => props.theme.forms.selectionBackground};
- border: 1px solid ${props => props.theme.wallet.borders.color} !important;
+ border: 1px solid ${props => props.theme.forms.border} !important;
+ }
+ .ant-input-wrapper .anticon-qrcode {
+ color: ${props => props.theme.forms.addonForeground} !important;
+ }
+ input.ant-input::placeholder,
+ .ant-select-selection::placeholder {
+ color: ${props => props.theme.forms.placeholder} !important;
}
.ant-select-selector {
- height: 60px !important;
- border: 1px solid ${props => props.theme.wallet.borders.color} !important;
+ height: 55px !important;
+ border: 1px solid ${props => props.theme.forms.border} !important;
background-color: ${props =>
props.theme.forms.selectionBackground}!important;
}
.ant-form-item-has-error
> div
> div.ant-form-item-control-input
> div
> span
> span
> span.ant-input-affix-wrapper {
background-color: ${props => props.theme.forms.selectionBackground};
border-color: ${props => props.theme.forms.error} !important;
}
+ .ant-input:hover {
+ border-color: ${props => props.theme.forms.highlightBox};
+ }
+
.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: ${props => props.theme.forms.selectionBackground};
border-color: ${props => props.theme.forms.error} !important;
}
.ant-form-item-has-error
.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input)
.ant-select-selector {
background-color: ${props => props.theme.forms.selectionBackground};
border-color: ${props => props.theme.forms.error} !important;
}
.ant-select-single .ant-select-selector .ant-select-selection-item,
.ant-select-single .ant-select-selector .ant-select-selection-placeholder {
- line-height: 60px;
+ line-height: 55px;
text-align: left;
color: ${props => props.theme.forms.text};
font-weight: bold;
}
.ant-form-item-has-error .ant-input-group-addon {
color: ${props => props.theme.forms.error} !important;
border-color: ${props => props.theme.forms.error} !important;
}
.ant-form-item-explain.ant-form-item-explain-error {
color: ${props => props.theme.forms.error} !important;
}
`;
export const AntdFormWrapper = styled.div`
${AntdFormCss}
`;
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: ${props => props.theme.forms.addonBackground} !important;
border: 1px solid ${props => props.theme.forms.border};
color: ${props => props.theme.forms.addonForeground} !important;
height: 50px;
line-height: 47px;
* {
color: ${props => props.theme.forms.addonForeground} !important;
}
${props =>
props.disabled
? `
cursor: not-allowed;
`
: `cursor: pointer;`}
`;
export const SendBchInput = ({
onMax,
inputProps,
selectProps,
activeFiatCode,
...otherProps
}) => {
const { Option } = Select;
const currencies = [
{
value: currency.ticker,
label: currency.ticker,
},
{
value: activeFiatCode ? activeFiatCode : 'USD',
label: activeFiatCode ? activeFiatCode : 'USD',
},
];
const currencyOptions = currencies.map(currency => {
return (
{currency.label}
);
});
const CurrencySelect = (
{currencyOptions}
);
return (
) : (
)
}
{...inputProps}
/>
{CurrencySelect}
max
);
};
SendBchInput.propTypes = {
onMax: PropTypes.func,
inputProps: PropTypes.object,
selectProps: PropTypes.object,
activeFiatCode: PropTypes.string,
};
export const DestinationAmount = ({ onMax, inputProps, ...otherProps }) => {
return (
}
addonAfter={
max
}
{...inputProps}
/>
);
};
DestinationAmount.propTypes = {
onMax: PropTypes.func,
inputProps: PropTypes.object,
};
// loadWithCameraOpen prop: if true, load page with camera scanning open
export const DestinationAddressSingle = ({
onScan,
loadWithCameraOpen,
inputProps,
...otherProps
}) => {
return (
}
autoComplete="off"
addonAfter={
}
{...inputProps}
/>
);
};
DestinationAddressSingle.propTypes = {
onScan: PropTypes.func,
loadWithCameraOpen: PropTypes.bool,
inputProps: PropTypes.object,
};
export const DestinationAddressMulti = ({ inputProps, ...otherProps }) => {
return (
}
autoComplete="off"
{...inputProps}
/>
);
};
DestinationAddressMulti.propTypes = {
inputProps: PropTypes.object,
};
export const CurrencySelectDropdown = selectProps => {
const { Option } = Select;
// Build select dropdown from currency.fiatCurrencies
const currencyMenuOptions = [];
const currencyKeys = Object.keys(currency.fiatCurrencies);
for (let i = 0; i < currencyKeys.length; i += 1) {
const currencyMenuOption = {};
currencyMenuOption.value =
currency.fiatCurrencies[currencyKeys[i]].slug;
currencyMenuOption.label = `${
currency.fiatCurrencies[currencyKeys[i]].name
} (${currency.fiatCurrencies[currencyKeys[i]].symbol})`;
currencyMenuOptions.push(currencyMenuOption);
}
const currencyOptions = currencyMenuOptions.map(currencyMenuOption => {
return (
{currencyMenuOption.label}
);
});
return (
{currencyOptions}
);
};
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/PrimaryButton.js b/web/cashtab/src/components/Common/PrimaryButton.js
index fc29bff92..179329140 100644
--- a/web/cashtab/src/components/Common/PrimaryButton.js
+++ b/web/cashtab/src/components/Common/PrimaryButton.js
@@ -1,104 +1,98 @@
import styled from 'styled-components';
const PrimaryButton = styled.button`
- border: none;
+ border: 2px solid ${props => props.theme.eCashBlue};
color: ${props => props.theme.buttons.primary.color};
- background-image: ${props => props.theme.buttons.primary.backgroundImage};
+ background: none;
+ font-weight: bold;
+ background-color: ${props => props.theme.eCashBlue};
transition: all 0.5s ease;
background-size: 200% auto;
font-size: 18px;
width: 100%;
padding: 20px 0;
- border-radius: 4px;
+ border-radius: 0px;
margin-bottom: 20px;
cursor: pointer;
:hover {
background-position: right center;
-webkit-box-shadow: ${props => props.theme.buttons.primary.hoverShadow};
-moz-box-shadow: ${props => props.theme.buttons.primary.hoverShadow};
box-shadow: ${props => props.theme.buttons.primary.hoverShadow};
}
svg {
fill: ${props => props.theme.buttons.primary.color};
}
@media (max-width: 768px) {
font-size: 16px;
padding: 15px 0;
}
`;
const SecondaryButton = styled.button`
border: none;
color: ${props => props.theme.buttons.secondary.color};
background: ${props => props.theme.buttons.secondary.background};
transition: all 0.5s ease;
font-size: 18px;
width: 100%;
padding: 15px 0;
border-radius: 4px;
cursor: pointer;
outline: none;
margin-bottom: 20px;
:hover {
-webkit-box-shadow: ${props =>
props.theme.buttons.secondary.hoverShadow};
-moz-box-shadow: ${props => props.theme.buttons.secondary.hoverShadow};
box-shadow: ${props => props.theme.buttons.secondary.hoverShadow};
}
svg {
fill: ${props => props.theme.buttons.secondary.color};
}
@media (max-width: 768px) {
font-size: 16px;
padding: 12px 0;
}
`;
const SmartButton = styled.button`
${({ disabled = false, ...props }) =>
disabled === true
? `
background-image: 'none';
color: ${props.theme.buttons.secondary.color};
background: ${props.theme.buttons.secondary.background};
- :hover {
- -webkit-box-shadow: 0px 3px 10px -5px rgba(0, 0, 0, 0.75);
- -moz-box-shadow: 0px 3px 10px -5px rgba(0, 0, 0, 0.75);
- box-shadow: 0px 3px 10px -5px rgba(0, 0, 0, 0.75);
- }
+ opacity: 0.3;
svg {
fill: ${props.theme.buttons.secondary.color};
}
`
: `
- background-image: ${props.theme.buttons.primary.backgroundImage};
- color: ${props.theme.buttons.primary.color};
- :hover {
- background-position: right center;
- -webkit-box-shadow: ${props.theme.buttons.primary.hoverShadow};
- -moz-box-shadow: ${props.theme.buttons.primary.hoverShadow};
- box-shadow: ${props.theme.buttons.primary.hoverShadow};
- }
+ opacity: 1;
+ background-image: 'none';
+ color: ${props.theme.buttons.secondary.color};
+ background: ${props.theme.buttons.secondary.background};
svg {
- fill: ${props.theme.buttons.primary.color};
+ fill: ${props.theme.buttons.secondary.color};
}
`}
border: none;
transition: all 0.5s ease;
font-size: 18px;
width: 100%;
padding: 15px 0;
border-radius: 4px;
cursor: pointer;
outline: none;
margin-bottom: 20px;
@media (max-width: 768px) {
font-size: 16px;
padding: 12px 0;
}
`;
export default PrimaryButton;
export { SecondaryButton, SmartButton };
diff --git a/web/cashtab/src/components/Common/QRCode.js b/web/cashtab/src/components/Common/QRCode.js
index faffe9cd3..d6469f7bc 100644
--- a/web/cashtab/src/components/Common/QRCode.js
+++ b/web/cashtab/src/components/Common/QRCode.js
@@ -1,252 +1,244 @@
import React, { useState } from 'react';
import styled from 'styled-components';
import RawQRCode from 'qrcode.react';
import { currency } from '@components/Common/Ticker.js';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Event } from '@utils/GoogleAnalytics';
import { convertToEcashPrefix } from '@utils/cashMethods';
export const StyledRawQRCode = styled(RawQRCode)`
cursor: pointer;
- border-radius: 26px;
+ border-radius: 10px;
background: ${props => props.theme.qr.background};
- box-shadow: ${props => props.theme.qr.shadow};
margin-bottom: 10px;
path:first-child {
fill: ${props => props.theme.qr.background};
}
:hover {
border-color: ${({ xec = 0, ...props }) =>
- xec === 1 ? props.theme.primary : props.theme.qr.token};
+ xec === 1 ? props.theme.eCashBlue : props.theme.eCashPurple};
}
@media (max-width: 768px) {
border-radius: 18px;
width: 170px;
height: 170px;
}
`;
const Copied = styled.div`
font-size: 18px;
font-weight: bold;
width: 100%;
text-align: center;
background-color: ${({ xec = 0, ...props }) =>
- xec === 1 ? props.theme.primary : props.theme.qr.token};
+ xec === 1 ? props.theme.eCashBlue : props.theme.eCashPurple};
border: 1px solid;
border-color: ${({ xec = 0, ...props }) =>
- xec === 1
- ? props.theme.qr.copyBorderCash
- : props.theme.qr.copyBorderToken};
+ xec === 1 ? props.theme.eCashBlue : props.theme.eCashPurple};
color: ${props => props.theme.contrast};
position: absolute;
top: 65px;
padding: 30px 0;
@media (max-width: 768px) {
top: 52px;
padding: 20px 0;
}
`;
const PrefixLabel = styled.span`
text-align: right;
- font-size: 14px;
font-weight: bold;
+ color: ${({ xec = 0, ...props }) =>
+ xec === 1 ? props.theme.eCashBlue : props.theme.eCashPurple};
@media (max-width: 768px) {
font-size: 12px;
}
@media (max-width: 400px) {
font-size: 10px;
}
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
`;
const AddressHighlightTrim = styled.span`
font-weight: bold;
- font-size: 14px;
+ color: ${props => props.theme.contrast};
@media (max-width: 768px) {
font-size: 12px;
}
@media (max-width: 400px) {
font-size: 10px;
}
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
`;
const CustomInput = styled.div`
- font-size: 12px;
- color: ${({ xec = 0, ...props }) =>
- xec === 1
- ? props.theme.wallet.text.secondary
- : props.theme.brandSecondary};
+ font-size: 14px;
+ color: ${props => props.theme.lightWhite};
text-align: center;
cursor: pointer;
- margin-bottom: 0px;
+ margin-bottom: 10px;
padding: 6px 0;
font-family: 'Roboto Mono', monospace;
border-radius: 5px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
input {
border: none;
width: 100%;
text-align: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
- color: ${props => props.theme.wallet.text.primary};
+ color: ${props => props.theme.contrast};
padding: 10px 0;
background: transparent;
margin-bottom: 15px;
display: none;
}
input:focus {
outline: none;
}
input::selection {
background: transparent;
- color: ${props => props.theme.wallet.text.primary};
+ color: ${props => props.theme.contrast};
}
@media (max-width: 768px) {
font-size: 10px;
input {
font-size: 10px;
margin-bottom: 10px;
}
}
@media (max-width: 400px) {
font-size: 7px;
input {
font-size: 10px;
margin-bottom: 10px;
}
}
`;
export const QRCode = ({
address,
isCashAddress,
size = 210,
onClick = () => null,
...otherProps
}) => {
address = address ? convertToEcashPrefix(address) : '';
const [visible, setVisible] = useState(false);
const trimAmount = 8;
const address_trim = address ? address.length - trimAmount : '';
const addressSplit = address ? address.split(':') : [''];
const addressPrefix = addressSplit[0];
const prefixLength = addressPrefix.length + 1;
const txtRef = React.useRef(null);
const handleOnClick = evt => {
setVisible(true);
setTimeout(() => {
setVisible(false);
}, 1500);
onClick(evt);
};
const handleOnCopy = () => {
// Event.("Category", "Action", "Label")
// xec or etoken?
let eventLabel = currency.ticker;
if (address && !isCashAddress) {
eventLabel = currency.tokenTicker;
// Event('Category', 'Action', 'Label')
Event('Wallet', 'Copy Address', eventLabel);
}
setVisible(true);
setTimeout(() => {
txtRef.current.select();
}, 100);
};
return (
);
};
diff --git a/web/cashtab/src/components/Common/StyledCollapse.js b/web/cashtab/src/components/Common/StyledCollapse.js
index 6aaeb6ac3..782c03f8b 100644
--- a/web/cashtab/src/components/Common/StyledCollapse.js
+++ b/web/cashtab/src/components/Common/StyledCollapse.js
@@ -1,88 +1,92 @@
import styled from 'styled-components';
import { Collapse } from 'antd';
export const StyledCollapse = styled(Collapse)`
background: ${props => props.theme.collapses.background} !important;
border: 1px solid ${props => props.theme.collapses.border} !important;
.ant-collapse-content {
- border: 1px solid ${props => props.theme.collapses.border};
border-top: none;
background-color: ${props =>
props.theme.collapses.expandedBackground} !important;
}
.ant-collapse-item {
border-bottom: none !important;
}
*:not(button) {
color: ${props => props.theme.collapses.color} !important;
}
`;
export const TokenCollapse = styled(Collapse)`
${({ disabled = false, ...props }) =>
disabled === true
? `
background: ${props.theme.buttons.secondary.background} !important;
.ant-collapse-header {
font-size: 18px;
font-weight: bold;
color: ${props.theme.buttons.secondary.color} !important;
svg {
color: ${props.theme.buttons.secondary.color} !important;
}
}
.ant-collapse-arrow {
font-size: 18px;
}
`
: `
- background: ${props.theme.primary} !important;
+ background: ${props.theme.eCashBlue} !important;
.ant-collapse-header {
font-size: 18px;
font-weight: bold;
color: ${props.theme.contrast} !important;
svg {
color: ${props.theme.contrast} !important;
}
}
.ant-collapse-arrow {
font-size: 18px;
}
`}
`;
export const AdvancedCollapse = styled(Collapse)`
+ .ant-collapse-content {
+ background-color: ${props =>
+ props.theme.advancedCollapse.expandedBackground} !important;
+ }
${({ disabled = false, ...props }) =>
disabled === true
? `
background: ${props.theme.buttons.secondary.background} !important;
.ant-collapse-header {
font-size: 18px;
font-weight: normal;
color: ${props.theme.buttons.secondary.color} !important;
svg {
color: ${props.theme.buttons.secondary.color} !important;
}
}
.ant-collapse-arrow {
font-size: 18px;
}
`
: `
- background: ${props.theme.primary} !important;
+ background: ${props.theme.advancedCollapse.background} !important;
.ant-collapse-header {
font-size: 18px;
font-weight: bold;
- color: ${props.theme.contrast} !important;
+ color: ${props.theme.advancedCollapse.color} !important;
svg {
- color: ${props.theme.contrast} !important;
+ color: ${props.theme.advancedCollapse.icon} !important;
}
}
.ant-collapse-arrow {
font-size: 18px;
}
+
`}
`;
diff --git a/web/cashtab/src/components/Common/WalletLabel.js b/web/cashtab/src/components/Common/WalletLabel.js
index 93e9b4481..2d01ee92c 100644
--- a/web/cashtab/src/components/Common/WalletLabel.js
+++ b/web/cashtab/src/components/Common/WalletLabel.js
@@ -1,30 +1,29 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const WalletName = styled.h4`
- font-size: 20px;
- font-weight: bold;
+ font-size: 16px;
display: inline-block;
- color: ${props => props.theme.primary};
+ color: ${props => props.theme.lightWhite};
margin-bottom: 0px;
@media (max-width: 400px) {
font-size: 16px;
}
`;
const WalletLabel = ({ name }) => {
return (
<>
{name && typeof name === 'string' && (
{name}
)}
>
);
};
WalletLabel.propTypes = {
name: PropTypes.string,
};
export default WalletLabel;
diff --git a/web/cashtab/src/components/Common/__tests__/__snapshots__/StyledCollapse.test.js.snap b/web/cashtab/src/components/Common/__tests__/__snapshots__/StyledCollapse.test.js.snap
index 7eae8d7d0..0aacbf10e 100644
--- a/web/cashtab/src/components/Common/__tests__/__snapshots__/StyledCollapse.test.js.snap
+++ b/web/cashtab/src/components/Common/__tests__/__snapshots__/StyledCollapse.test.js.snap
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Render StyledCollapse component 1`] = `
`;
diff --git a/web/cashtab/src/components/Configure/Configure.js b/web/cashtab/src/components/Configure/Configure.js
index 5a1445744..92d661581 100644
--- a/web/cashtab/src/components/Configure/Configure.js
+++ b/web/cashtab/src/components/Configure/Configure.js
@@ -1,751 +1,788 @@
/* 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';
const { Panel } = Collapse;
const SettingsLink = styled.a`
text-decoration: underline;
- color: ${props => props.theme.primary};
+ color: ${props => props.theme.eCashBlue};
:visited {
text-decoration: underline;
- color: ${props => props.theme.primary};
+ color: ${props => props.theme.eCashBlue};
}
:hover {
- color: ${props => props.theme.brandSecondary};
+ color: ${props => props.theme.eCashPurple};
}
`;
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.wallet.text.secondary};
+ 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.wallet.text.secondary};
+ 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.wallet.text.secondary};
- fill: ${props => props.theme.wallet.text.secondary};
+ 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.primary};
- fill: ${props => props.theme.primary};
+ 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.wallet.text.secondary};
+ 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.primary} !important;
+ 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.wallet.text.primary};
+ color: ${props => props.theme.contrast};
font-size: 25px;
}
+ svg {
+ fill: ${props => props.theme.eCashBlue};
+ }
p {
- color: ${props => props.theme.wallet.text.secondary};
+ color: ${props => props.theme.darkBlue};
}
`;
const StyledSpacer = styled.div`
height: 1px;
width: 100%;
- background-color: ${props => props.theme.wallet.borders.color};
+ 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.generalSettings.item.title};
+ color: ${props => props.theme.contrast};
}
.anticon {
- color: ${props => props.theme.generalSettings.item.icon};
+ color: ${props => props.theme.contrast};
}
.ant-switch {
- background-color: ${props => props.theme.generalSettings.item.icon};
- .anticon {
- color: ${props => props.theme.generalSettings.background};
- }
+ background-color: #bdbdbd;
}
.ant-switch-checked {
- background-color: ${props => props.theme.primary};
+ 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 {
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 (
-
- {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
-
-
- {showTranslationWarning && (
+
+
+ {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
+
- )}
- {wallet && wallet.mnemonic && (
-
-
-
- {wallet && wallet.mnemonic ? wallet.mnemonic : ''}
-
-
-
- )}
-
-
- Manage Wallets
-
- {apiError ? (
-
- ) : (
- <>
- updateSavedWalletsOnCreate()}>
- New Wallet
-
- openSeedInput(!seedInput)}>
- Import Wallet
-
- {seedInput && (
- <>
-
- Copy and paste your mnemonic seed phrase below
- to import an existing wallet
-
-
-
- }
- type="email"
- placeholder="mnemonic (seed phrase)"
- name="mnemonic"
- autoComplete="off"
- onChange={e => handleChange(e)}
- required
- />
-
- submit()}
- >
- Import
-
-
-
- >
- )}
- >
- )}
- {savedWallets && savedWallets.length > 0 && (
- <>
+ {showTranslationWarning && (
+
+ )}
+ {wallet && wallet.mnemonic && (
-
-
- {wallet.name}
- Currently active
-
-
- {savedWallets.map(sw => (
-
-
-
- {sw.name}
-
-
-
-
- [
- {sw && sw.state
- ? formatSavedBalance(
- sw.state.balances
- .totalBalance,
- )
- : 'N/A'}{' '}
- XEC]
-
-
-
-
- showPopulatedRenameWalletModal(
- sw,
- )
- }
- />
-
- showPopulatedDeleteWalletModal(
- sw,
- )
- }
- />
-
- updateSavedWalletsOnLoad(sw)
- }
- >
- Activate
-
-
-
- ))}
-
+
+
+ {wallet && wallet.mnemonic
+ ? wallet.mnemonic
+ : ''}
+
- >
- )}
-
-
- Fiat Currency
-
-
-
- changeCashtabSettings('fiatCurrency', fiatCode)
- }
- />
-
-
-
- General Settings
-
-
-
- Lock App
-
- {authentication ? (
- }
- unCheckedChildren={ }
- checked={
- authentication.isAuthenticationRequired &&
- authentication.credentialId
- ? true
- : false
- }
- // checked={false}
- onChange={handleAppLockToggle}
- />
+ )}
+
+
+ Manage Wallets
+
+ {apiError ? (
+
) : (
- }>
- Not Supported
-
+ <>
+ updateSavedWalletsOnCreate()}
+ >
+ New Wallet
+
+ openSeedInput(!seedInput)}
+ >
+ Import Wallet
+
+ {seedInput && (
+ <>
+
+ Copy and paste your mnemonic seed phrase
+ below to import an existing wallet
+
+
+
+ }
+ type="email"
+ placeholder="mnemonic (seed phrase)"
+ name="mnemonic"
+ autoComplete="off"
+ onChange={e => handleChange(e)}
+ required
+ />
+
+ submit()}
+ >
+ Import
+
+
+
+ >
+ )}
+ >
)}
-
- [
-
- Documentation
-
- ]
-
+ {savedWallets && savedWallets.length > 0 && (
+ <>
+
+
+
+
+ {wallet.name}
+
+ Currently active
+
+
+ {savedWallets.map(sw => (
+
+
+
+ {sw.name}
+
+
+
+
+ [
+ {sw && sw.state
+ ? formatSavedBalance(
+ sw.state.balances
+ .totalBalance,
+ )
+ : 'N/A'}{' '}
+ XEC]
+
+
+
+
+ showPopulatedRenameWalletModal(
+ sw,
+ )
+ }
+ />
+
+ showPopulatedDeleteWalletModal(
+ sw,
+ )
+ }
+ />
+
+ updateSavedWalletsOnLoad(
+ sw,
+ )
+ }
+ >
+ Activate
+
+
+
+ ))}
+
+
+
+ >
+ )}
+
+
+ Fiat Currency
+
+
+
+ changeCashtabSettings('fiatCurrency', fiatCode)
+ }
+ />
+
+
+
+ General Settings
+
+
+
+ Lock App
+
+ {authentication ? (
+ }
+ unCheckedChildren={ }
+ checked={
+ authentication.isAuthenticationRequired &&
+ authentication.credentialId
+ ? true
+ : false
+ }
+ // checked={false}
+ onChange={handleAppLockToggle}
+ />
+ ) : (
+ }>
+ Not Supported
+
+ )}
+
+ [
+
+ 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 ce012003b..e990f6784 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,781 +1,789 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Configure with a wallet 1`] = `
-
-
-
-
-
-
- Backup your wallet
-
-
-
+
-
-
-
-
+
+
+
+
+ Backup your wallet
+
+
+
+
+
+
+
-
- Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
+
+
+ Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
+
-
-
-
-
-
-
-
- Click to reveal seed phrase
+
+
+
+
+ Click to reveal seed phrase
+
-
-
-
-
-
-
-
-
- Manage Wallets
-
-
-
-
+
+
-
-
-
-
- New Wallet
-
-
-
+
+
+
+ Manage Wallets
+
+
-
-
-
-
- Import Wallet
-
-
-
-
+
+
+
+
+ New Wallet
+
+
-
-
-
-
- Fiat Currency
-
-
+
+
+
+
+ Import Wallet
+
-
+
+
-
-
-
-
- US Dollar ($)
-
-
-
+
+ Fiat Currency
+
+
+
-
-
-
-
+
+
+ US Dollar ($)
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
- General Settings
-
-
+ className="sc-jAaTju gLeerB"
+ />
+
- Lock App
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [
+
+ Documentation
+
+ ]
-
- [
-
- Documentation
-
- ]
`;
exports[`Configure without a wallet 1`] = `
-
-
-
-
-
-
- Backup your wallet
-
-
-
+
-
-
-
-
+
+
+
+
+ Backup your wallet
+
+
+
+
+
+
+
-
- Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
+
+
+ Your seed phrase is the only way to restore your wallet. Write it down. Keep it safe.
+
-
-
-
-
-
-
-
-
- Manage Wallets
-
-
-
-
+
+
-
-
-
-
- New Wallet
-
-
-
+
+
+
+ Manage Wallets
+
+
-
-
-
-
- Import Wallet
-
-
-
-
+
+
+
+
+ New Wallet
+
+
-
-
-
-
- Fiat Currency
-
-
+
+
+
+
+ Import Wallet
+
-
+
+
-
-
-
-
- US Dollar ($)
-
-
-
+
+ Fiat Currency
+
+
+
-
-
-
-
+
+
+ US Dollar ($)
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
- General Settings
-
-
+ className="sc-jAaTju gLeerB"
+ />
+
- Lock App
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [
+
+ Documentation
+
+ ]
-
- [
-
- Documentation
-
- ]
`;
diff --git a/web/cashtab/src/components/Home/Home.js b/web/cashtab/src/components/Home/Home.js
new file mode 100644
index 000000000..2f8425626
--- /dev/null
+++ b/web/cashtab/src/components/Home/Home.js
@@ -0,0 +1,257 @@
+import React from 'react';
+import styled from 'styled-components';
+import { WalletContext } from '@utils/context';
+import OnBoarding from '@components/OnBoarding/OnBoarding';
+import { currency } from '@components/Common/Ticker.js';
+import { Link } from 'react-router-dom';
+import TokenList from './TokenList';
+import TxHistory from './TxHistory';
+import ApiError from '@components/Common/ApiError';
+import BalanceHeader from '@components/Common/BalanceHeader';
+import BalanceHeaderFiat from '@components/Common/BalanceHeaderFiat';
+import {
+ LoadingCtn,
+ WalletInfoCtn,
+ SidePaddingCtn,
+} from '@components/Common/Atoms';
+import { getWalletState } from '@utils/cashMethods';
+import WalletLabel from '@components/Common/WalletLabel.js';
+
+export const Tabs = styled.div`
+ margin: auto;
+ display: inline-block;
+ text-align: center;
+ width: 100%;
+ margin: 20px 0;
+`;
+
+export const TabLabel = styled.button`
+ :focus,
+ :active {
+ outline: none;
+ }
+ color: ${props => props.theme.lightWhite};
+ border: none;
+ background: none;
+ font-size: 18px;
+ cursor: pointer;
+ margin: 0 20px;
+ padding: 0;
+
+ @media (max-width: 400px) {
+ font-size: 16px;
+ }
+
+ ${({ active, ...props }) =>
+ active &&
+ `
+ color: ${props.theme.contrast};
+ border-bottom: 2px solid ${props.theme.eCashBlue}
+
+ `}
+ ${({ token, ...props }) =>
+ token &&
+ `
+ border-color:${props.theme.eCashPurple}
+ `}
+`;
+
+export const TabPane = styled.div`
+ color: ${props => props.theme.contrast};
+ ${({ active }) =>
+ !active &&
+ `
+ display: none;
+ `}
+`;
+
+export const Links = styled(Link)`
+ color: ${props => props.theme.darkBlue};
+ width: 100%;
+ font-size: 16px;
+ margin: 10px 0 20px 0;
+ border: 1px solid ${props => props.theme.darkBlue};
+ padding: 14px 0;
+ display: inline-block;
+ border-radius: 3px;
+ transition: all 200ms ease-in-out;
+ svg {
+ fill: ${props => props.theme.darkBlue};
+ }
+ :hover {
+ color: ${props => props.theme.eCashBlue};
+ border-color: ${props => props.theme.eCashBlue};
+ svg {
+ fill: ${props => props.theme.eCashBlue};
+ }
+ }
+ @media (max-width: 768px) {
+ padding: 10px 0;
+ font-size: 14px;
+ }
+`;
+
+export const ExternalLink = styled.a`
+ color: ${props => props.theme.darkBlue};
+ width: 100%;
+ font-size: 16px;
+ margin: 0 0 20px 0;
+ border: 1px solid ${props => props.theme.darkBlue};
+ padding: 14px 0;
+ display: inline-block;
+ border-radius: 3px;
+ transition: all 200ms ease-in-out;
+ svg {
+ fill: ${props => props.theme.darkBlue};
+ transition: all 200ms ease-in-out;
+ }
+ :hover {
+ color: ${props => props.theme.eCashBlue};
+ border-color: ${props => props.theme.eCashBlue};
+ svg {
+ fill: ${props => props.theme.eCashBlue};
+ }
+ }
+ @media (max-width: 768px) {
+ padding: 10px 0;
+ font-size: 14px;
+ }
+`;
+
+export const AddrSwitchContainer = styled.div`
+ text-align: center;
+ padding: 6px 0 12px 0;
+`;
+
+const CreateToken = styled(Link)`
+ color: ${props => props.theme.contrast};
+ border: 1px solid ${props => props.theme.contrast};
+ padding: 8px 15px;
+ border-radius: 5px;
+ margin-top: 10px;
+ margin-bottom: 20px;
+ display: inline-block;
+ width: 100%;
+ :hover {
+ background: ${props => props.theme.eCashPurple};
+ border-color: ${props => props.theme.eCashPurple};
+ color: ${props => props.theme.contrast};
+ }
+`;
+
+const WalletInfo = () => {
+ const ContextValue = React.useContext(WalletContext);
+ const { wallet, fiatPrice, apiError, cashtabSettings } = ContextValue;
+ const walletState = getWalletState(wallet);
+ const { balances, parsedTxHistory, tokens } = walletState;
+ const [activeTab, setActiveTab] = React.useState('txHistory');
+
+ const hasHistory = parsedTxHistory && parsedTxHistory.length > 0;
+
+ return (
+ <>
+
+
+
+
+
+ {apiError && }
+
+
+
+ setActiveTab('txHistory')}
+ >
+ Transactions
+
+ setActiveTab('tokens')}
+ >
+ eTokens
+
+
+
+
+
+ {!hasHistory && (
+ <>
+
+ 🎉
+
+ Congratulations on your new wallet!{' '}
+
+ 🎉
+
+ Start using the wallet immediately to receive{' '}
+ {currency.ticker} payments, or load it up with{' '}
+ {currency.ticker} to send to others
+ >
+ )}
+
+
+
+ Create eToken
+
+ {tokens && tokens.length > 0 ? (
+
+ ) : (
+
+ Tokens sent to your {currency.tokenTicker} address
+ will appear here
+
+ )}
+
+
+ >
+ );
+};
+
+const Home = () => {
+ const ContextValue = React.useContext(WalletContext);
+ const { wallet, previousWallet, loading } = ContextValue;
+
+ return (
+ <>
+ {loading ? (
+
+ ) : (
+ <>
+ {(wallet && wallet.Path1899) ||
+ (previousWallet && previousWallet.path1899) ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+ >
+ );
+};
+
+export default Home;
diff --git a/web/cashtab/src/components/Wallet/TokenList.js b/web/cashtab/src/components/Home/TokenList.js
similarity index 100%
rename from web/cashtab/src/components/Wallet/TokenList.js
rename to web/cashtab/src/components/Home/TokenList.js
diff --git a/web/cashtab/src/components/Home/TokenListItem.js b/web/cashtab/src/components/Home/TokenListItem.js
new file mode 100644
index 000000000..d33322b17
--- /dev/null
+++ b/web/cashtab/src/components/Home/TokenListItem.js
@@ -0,0 +1,89 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import makeBlockie from 'ethereum-blockies-base64';
+import { Img } from 'react-image';
+import { currency } from '@components/Common/Ticker';
+
+const TokenIcon = styled.div`
+ height: 32px;
+ width: 32px;
+ margin-right: 10px;
+`;
+
+const TokenNameCtn = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+const Wrapper = styled.div`
+ display: flex;
+ align-items: center;
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
+ color: ${props => props.theme.contrast};
+ padding: 10px 0;
+ justify-content: space-between;
+ h4 {
+ font-size: 16px;
+ color: ${props => props.theme.contrast};
+ margin: 0;
+ font-weight: bold;
+ }
+ :hover {
+ h4 {
+ color: ${props => props.theme.eCashPurple};
+ }
+ }
+`;
+
+const TokenListItem = ({ ticker, balance, tokenId }) => {
+ return (
+
+
+
+ {currency.tokenIconsUrl !== '' ? (
+
+ }
+ />
+ ) : (
+
+ )}
+
+ {ticker}
+
+
+ {balance}
+
+ );
+};
+
+TokenListItem.propTypes = {
+ ticker: PropTypes.string,
+ balance: PropTypes.string,
+ tokenId: PropTypes.string,
+};
+
+export default TokenListItem;
diff --git a/web/cashtab/src/components/Wallet/Tx.js b/web/cashtab/src/components/Home/Tx.js
similarity index 64%
rename from web/cashtab/src/components/Wallet/Tx.js
rename to web/cashtab/src/components/Home/Tx.js
index 97ddd5c08..f43b15f33 100644
--- a/web/cashtab/src/components/Wallet/Tx.js
+++ b/web/cashtab/src/components/Home/Tx.js
@@ -1,489 +1,570 @@
import React from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import {
- ArrowUpOutlined,
- ArrowDownOutlined,
- ExperimentOutlined,
- ExclamationOutlined,
-} from '@ant-design/icons';
+ SendIcon,
+ ReceiveIcon,
+ GenesisIcon,
+ UnparsedIcon,
+} from '@components/Common/CustomIcons';
import { currency } from '@components/Common/Ticker';
import makeBlockie from 'ethereum-blockies-base64';
import { Img } from 'react-image';
import { fromLegacyDecimals } from '@utils/cashMethods';
import { formatBalance, formatDate } from '@utils/formatting';
-const SentTx = styled(ArrowUpOutlined)`
- color: ${props => props.theme.secondary} !important;
+
+const TxIcon = styled.div`
+ svg {
+ width: 20px;
+ height: 20px;
+ }
+ height: 40px;
+ width: 40px;
+ border: 1px solid #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100px;
`;
-const ReceivedTx = styled(ArrowDownOutlined)`
- color: ${props => props.theme.primary} !important;
+
+const SentTx = styled(TxIcon)`
+ svg {
+ margin-right: -3px;
+ }
+ fill: ${props => props.theme.contrast};
`;
-const GenesisTx = styled(ExperimentOutlined)`
- color: ${props => props.theme.primary} !important;
+const ReceivedTx = styled(TxIcon)`
+ svg {
+ fill: ${props => props.theme.eCashBlue};
+ }
+ border-color: ${props => props.theme.eCashBlue};
`;
-const UnparsedTx = styled(ExclamationOutlined)`
- color: ${props => props.theme.primary} !important;
+const GenesisTx = styled(TxIcon)`
+ border-color: ${props => props.theme.genesisGreen};
+ svg {
+ fill: ${props => props.theme.genesisGreen};
+ }
+`;
+const UnparsedTx = styled(TxIcon)`
+ color: ${props => props.theme.eCashBlue} !important;
`;
const DateType = styled.div`
text-align: left;
padding: 12px;
@media screen and (max-width: 500px) {
font-size: 0.8rem;
}
`;
-const OpReturnType = styled.span`
+
+const LeftTextCtn = styled.div`
text-align: left;
- width: 300%;
- max-height: 200px;
- padding: 3px;
- margin: auto;
- word-break: break-word;
- padding-left: 13px;
- padding-right: 30px;
- /* invisible scrollbar */
- overflow: hidden;
- height: 100%;
- margin-right: -50px; /* Maximum width of scrollbar */
- padding-right: 50px; /* Maximum width of scrollbar */
- overflow-y: scroll;
- ::-webkit-scrollbar {
- display: none;
+ display: flex;
+ align-items: left;
+ flex-direction: column;
+ margin-left: 10px;
+ h3 {
+ color: ${props => props.theme.contrast};
+ font-size: 14px;
+ font-weight: 700;
+ margin: 0;
+ }
+ .genesis {
+ color: ${props => props.theme.genesisGreen};
+ }
+ .received {
+ color: ${props => props.theme.eCashBlue};
+ }
+ h4 {
+ font-size: 12px;
+ color: ${props => props.theme.lightWhite};
+ margin: 0;
}
`;
+
+const RightTextCtn = styled.div`
+ text-align: right;
+ display: flex;
+ align-items: left;
+ flex-direction: column;
+ margin-left: 10px;
+ h3 {
+ color: ${props => props.theme.contrast};
+ font-size: 14px;
+ font-weight: 700;
+ margin: 0;
+ }
+ .genesis {
+ color: ${props => props.theme.genesisGreen};
+ }
+ .received {
+ color: ${props => props.theme.eCashBlue};
+ }
+ h4 {
+ font-size: 12px;
+ color: ${props => props.theme.lightWhite};
+ margin: 0;
+ }
+`;
+const OpReturnType = styled.div`
+ text-align: right;
+ width: 100%;
+ padding: 10px;
+ border-radius: 5px;
+ background: ${props => props.theme.sentMessage};
+ margin-top: 15px;
+ h4 {
+ color: ${props => props.theme.lightWhite};
+ margin: 0;
+ font-size: 12px;
+ display: inline-block;
+ }
+ p {
+ color: ${props => props.theme.contrast};
+ margin: 0;
+ font-size: 14px;
+ margin-bottom: 10px;
+ overflow-wrap: break-word;
+ }
+ a {
+ color: ${props => props.theme.contrast};
+ margin: 0;
+ font-size: 10px;
+ border: 1px solid ${props => props.theme.contrast};
+ border-radius: 5px;
+ padding: 2px 10px;
+ opacity: 0.6;
+ }
+ a:hover {
+ opacity: 1;
+ border-color: ${props => props.theme.eCashBlue};
+ color: ${props => props.theme.contrast};
+ background: ${props => props.theme.eCashBlue};
+ }
+ ${({ received, ...props }) =>
+ received &&
+ `
+ text-align: left;
+ background: ${props.theme.receivedMessage};
+ `}
+`;
const SentLabel = styled.span`
font-weight: bold;
color: ${props => props.theme.secondary} !important;
`;
const ReceivedLabel = styled.span`
font-weight: bold;
- color: ${props => props.theme.primary} !important;
+ color: ${props => props.theme.eCashBlue} !important;
+`;
+const GenesisLabel = styled.span`
+ font-weight: bold;
+ color: ${props => props.theme.genesisGreen} !important;
`;
const CashtabMessageLabel = styled.span`
text-align: left;
font-weight: bold;
- color: ${props => props.theme.primary} !important;
+ color: ${props => props.theme.eCashBlue} !important;
white-space: nowrap;
`;
const EncryptionMessageLabel = styled.span`
- text-align: left;
font-weight: bold;
- color: ${props => props.theme.wallet.encryption};
+ font-size: 12px;
+ color: ${props => props.theme.encryptionRed};
white-space: nowrap;
`;
const UnauthorizedDecryptionMessage = styled.span`
text-align: left;
- color: ${props => props.theme.wallet.encryption};
+ color: ${props => props.theme.encryptionRed};
white-space: nowrap;
font-style: italic;
`;
const MessageLabel = styled.span`
text-align: left;
font-weight: bold;
color: ${props => props.theme.secondary} !important;
white-space: nowrap;
`;
const ReplyMessageLabel = styled.span`
- color: ${props => props.theme.primary} !important;
-`;
-const TxIcon = styled.div`
- svg {
- width: 32px;
- height: 32px;
- }
- height: 32px;
- width: 32px;
- @media screen and (max-width: 500px) {
- svg {
- width: 24px;
- height: 24px;
- }
- height: 24px;
- width: 24px;
- }
+ color: ${props => props.theme.eCashBlue} !important;
`;
const TxInfo = styled.div`
- padding: 12px;
- font-size: 1rem;
text-align: right;
-
- color: ${props =>
- props.outgoing ? props.theme.secondary : props.theme.primary};
+ display: flex;
+ align-items: left;
+ flex-direction: column;
+ margin-left: 10px;
+ flex-grow: 2;
+ h3 {
+ color: ${props => props.theme.contrast};
+ font-size: 14px;
+ font-weight: 700;
+ margin: 0;
+ }
+ .genesis {
+ color: ${props => props.theme.genesisGreen};
+ }
+ .received {
+ color: ${props => props.theme.eCashBlue};
+ }
+ h4 {
+ font-size: 12px;
+ color: ${props => props.theme.lightWhite};
+ margin: 0;
+ }
@media screen and (max-width: 500px) {
font-size: 0.8rem;
}
`;
-const TxFiatPrice = styled.span`
- font-size: 0.8rem;
-`;
+
const TokenInfo = styled.div`
- display: grid;
- grid-template-rows: 50%;
- grid-template-columns: 24px auto;
- padding: 12px;
- font-size: 1rem;
+ display: flex;
+ flex-grow: 1;
+ justify-content: flex-end;
color: ${props =>
- props.outgoing ? props.theme.secondary : props.theme.primary};
+ props.outgoing ? props.theme.secondary : props.theme.eCashBlue};
@media screen and (max-width: 500px) {
font-size: 0.8rem;
grid-template-columns: 16px auto;
}
`;
const TxTokenIcon = styled.div`
img {
height: 24px;
width: 24px;
}
@media screen and (max-width: 500px) {
img {
height: 16px;
width: 16px;
}
}
grid-column-start: 1;
grid-column-end: span 1;
grid-row-start: 1;
grid-row-end: span 2;
align-self: center;
`;
-const TokenTxAmt = styled.div`
- padding-left: 12px;
+const TokenTxAmt = styled.h3`
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
-const TokenName = styled.div`
- padding-left: 12px;
- font-size: 0.8rem;
- @media screen and (max-width: 500px) {
- font-size: 0.6rem;
- }
+const TokenName = styled.h4`
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const TxWrapper = styled.div`
- display: grid;
- grid-template-columns: 36px 30% 50%;
-
- justify-content: space-between;
+ display: flex;
align-items: center;
- padding: 15px 25px;
- border-radius: 16px;
- background: ${props => props.theme.tokenListItem.background};
- margin-bottom: 12px;
- box-shadow: ${props => props.theme.tokenListItem.boxShadow};
-
- :hover {
- transform: translateY(-2px);
- box-shadow: rgb(136 172 243 / 25%) 0px 10px 30px,
- rgb(0 0 0 / 3%) 0px 1px 1px, rgb(0 51 167 / 10%) 0px 10px 20px;
- transition: all 0.8s cubic-bezier(0.075, 0.82, 0.165, 1) 0s;
- }
- @media screen and (max-width: 500px) {
- grid-template-columns: 24px 30% 50%;
- padding: 12px 12px;
- }
+ border-top: 1px solid rgba(255, 255, 255, 0.12);
+ color: ${props => props.theme.contrast};
+ padding: 10px 0;
+ flex-wrap: wrap;
`;
const Tx = ({ data, fiatPrice, fiatCurrency }) => {
const txDate =
typeof data.blocktime === 'undefined'
? formatDate()
: formatDate(data.blocktime, navigator.language);
// if data only includes height and txid, then the tx could not be parsed by cashtab
// render as such but keep link to block explorer
let unparsedTx = false;
if (!Object.keys(data).includes('outgoingTx')) {
unparsedTx = true;
}
return (
<>
{unparsedTx ? (
-
-
-
+
+
+
+
Unparsed
{txDate}
Open in Explorer
) : (
-
- {data.outgoingTx ? (
- <>
- {data.tokenTx &&
- data.tokenInfo.transactionType === 'GENESIS' ? (
-
- ) : (
-
- )}
- >
- ) : (
-
- )}
-
-
+ {data.outgoingTx ? (
+ <>
+ {data.tokenTx &&
+ data.tokenInfo.transactionType === 'GENESIS' ? (
+
+
+
+ ) : (
+
+
+
+ )}
+ >
+ ) : (
+
+
+
+ )}
+
+
{data.outgoingTx ? (
<>
{data.tokenTx &&
data.tokenInfo.transactionType === 'GENESIS' ? (
- Genesis
+ Genesis
) : (
- Sent
+ Sent
)}
>
) : (
- Received
+ Received
)}
-
- {txDate}
-
+ {txDate}
+
{data.tokenTx ? (
{data.tokenTx && data.tokenInfo ? (
<>
{currency.tokenIconsUrl !== '' ? (
}
/>
) : (
)}
{data.outgoingTx ? (
- <>
+
{data.tokenInfo.transactionType ===
'GENESIS' ? (
<>
-
+
+{' '}
{data.tokenInfo.qtyReceived.toString()}
{
data.tokenInfo
.tokenTicker
}
{
data.tokenInfo
.tokenName
}
>
) : (
<>
-{' '}
{data.tokenInfo.qtySent.toString()}
{
data.tokenInfo
.tokenTicker
}
{
data.tokenInfo
.tokenName
}
>
)}
- >
+
) : (
- <>
-
+
+
+{' '}
{data.tokenInfo.qtyReceived.toString()}
{data.tokenInfo.tokenTicker}
{data.tokenInfo.tokenName}
- >
+
)}
>
) : (
Token Tx
)}
) : (
<>
{data.outgoingTx ? (
<>
- -{' '}
- {formatBalance(
- fromLegacyDecimals(data.amountSent),
- )}{' '}
- {currency.ticker}
-
+
+ -
+ {formatBalance(
+ fromLegacyDecimals(
+ data.amountSent,
+ ),
+ )}{' '}
+ {currency.ticker}
+
{fiatPrice !== null &&
!isNaN(data.amountSent) && (
-
- -{' '}
+
+ -
{
currency.fiatCurrencies[
fiatCurrency
].symbol
}
{(
fromLegacyDecimals(
data.amountSent,
) * fiatPrice
).toFixed(2)}{' '}
{
currency.fiatCurrencies
.fiatCurrency
}
-
+
)}
>
) : (
<>
- +{' '}
- {formatBalance(
- fromLegacyDecimals(
- data.amountReceived,
- ),
- )}{' '}
- {currency.ticker}
-
+
+ +
+ {formatBalance(
+ fromLegacyDecimals(
+ data.amountReceived,
+ ),
+ )}{' '}
+ {currency.ticker}
+
{fiatPrice !== null &&
!isNaN(data.amountReceived) && (
-
- +{' '}
+
+ +
{
currency.fiatCurrencies[
fiatCurrency
].symbol
}
{(
fromLegacyDecimals(
data.amountReceived,
) * fiatPrice
).toFixed(2)}{' '}
{
currency.fiatCurrencies
.fiatCurrency
}
-
+
)}
>
)}
>
)}
{data.opReturnMessage && (
<>
-
-
+
{data.isCashtabMessage ? (
-
- Cashtab Message
-
+ Cashtab Message
) : (
-
- External Message
-
+ External Message
)}
{data.isEncryptedMessage ? (
- Encrypted
) : (
''
)}
{/*unencrypted OP_RETURN Message*/}
{data.opReturnMessage &&
- !data.isEncryptedMessage
- ? data.opReturnMessage
- : ''}
+ !data.isEncryptedMessage ? (
+ {data.opReturnMessage}
+ ) : (
+ ''
+ )}
{/*encrypted and wallet is authorized to view OP_RETURN Message*/}
{data.opReturnMessage &&
data.isEncryptedMessage &&
- data.decryptionSuccess
- ? data.opReturnMessage
- : ''}
+ data.decryptionSuccess ? (
+ {data.opReturnMessage}
+ ) : (
+ ''
+ )}
{/*encrypted but wallet is not authorized to view OP_RETURN Message*/}
{data.opReturnMessage &&
data.isEncryptedMessage &&
!data.decryptionSuccess ? (
{data.opReturnMessage}
) : (
''
)}
{!data.outgoingTx && data.replyAddress ? (
-
-
-
- Reply To Message
-
+ Reply To Message
) : (
''
)}
>
)}
)}
>
);
};
Tx.propTypes = {
data: PropTypes.object,
fiatPrice: PropTypes.number,
fiatCurrency: PropTypes.string,
};
export default Tx;
diff --git a/web/cashtab/src/components/Wallet/TxHistory.js b/web/cashtab/src/components/Home/TxHistory.js
similarity index 100%
rename from web/cashtab/src/components/Wallet/TxHistory.js
rename to web/cashtab/src/components/Home/TxHistory.js
diff --git a/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js b/web/cashtab/src/components/Home/__mocks__/walletAndBalancesMock.js
similarity index 100%
rename from web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js
rename to web/cashtab/src/components/Home/__mocks__/walletAndBalancesMock.js
diff --git a/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js b/web/cashtab/src/components/Home/__tests__/Home.test.js
similarity index 93%
rename from web/cashtab/src/components/Wallet/__tests__/Wallet.test.js
rename to web/cashtab/src/components/Home/__tests__/Home.test.js
index 972769b62..cb5dce457 100644
--- a/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js
+++ b/web/cashtab/src/components/Home/__tests__/Home.test.js
@@ -1,92 +1,92 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ThemeProvider } from 'styled-components';
import { theme } from '@assets/styles/theme';
-import Wallet from '../Wallet';
+import Home from '../Home';
import {
walletWithBalancesAndTokens,
walletWithBalancesMock,
walletWithoutBalancesMock,
walletWithBalancesAndTokensWithCorrectState,
} from '../__mocks__/walletAndBalancesMock';
import { BrowserRouter as Router } from 'react-router-dom';
let realUseContext;
let useContextMock;
beforeEach(() => {
realUseContext = React.useContext;
useContextMock = React.useContext = jest.fn();
});
afterEach(() => {
React.useContext = realUseContext;
});
test('Wallet without BCH balance', () => {
useContextMock.mockReturnValue(walletWithoutBalancesMock);
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances', () => {
useContextMock.mockReturnValue(walletWithBalancesMock);
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances and tokens', () => {
useContextMock.mockReturnValue(walletWithBalancesAndTokens);
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances and tokens and state field', () => {
useContextMock.mockReturnValue(walletWithBalancesAndTokensWithCorrectState);
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Without wallet defined', () => {
useContextMock.mockReturnValue({
wallet: {},
balances: { totalBalance: 0 },
});
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
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
new file mode 100644
index 000000000..b8d61ae0e
--- /dev/null
+++ b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
@@ -0,0 +1,481 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
+
+exports[`Wallet with BCH balances 1`] = `
+Array [
+
+
+ MigrationTestAlpha
+
+
+ 0
+
+ XEC
+
+
+ $
+ NaN
+
+ USD
+
+
,
+
+
+
+ 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 with BCH balances and tokens 1`] = `
+Array [
+
+
+ MigrationTestAlpha
+
+
+ 0
+
+ XEC
+
+
+ $
+ NaN
+
+ USD
+
+
,
+
+
+
+ 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 with BCH balances and tokens and state field 1`] = `
+Array [
+
+
+ MigrationTestAlpha
+
+
+ 0.06
+
+ XEC
+
+
+ $
+ NaN
+
+ USD
+
+
,
+
+
+
+ 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 [
+
+
+ MigrationTestAlpha
+
+
+ 0
+
+ XEC
+
+
+ $
+ NaN
+
+ USD
+
+
,
+
+
+
+ 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[`Without wallet defined 1`] = `
+
+`;
diff --git a/web/cashtab/src/components/OnBoarding/OnBoarding.js b/web/cashtab/src/components/OnBoarding/OnBoarding.js
index 09b0df2c6..971f014c9 100644
--- a/web/cashtab/src/components/OnBoarding/OnBoarding.js
+++ b/web/cashtab/src/components/OnBoarding/OnBoarding.js
@@ -1,155 +1,168 @@
import React, { useState } from 'react';
import styled from 'styled-components';
import { WalletContext } from '@utils/context';
import { Input, Form, Modal } from 'antd';
import { AntdFormWrapper } from '@components/Common/EnhancedInputs';
import {
ExclamationCircleOutlined,
PlusSquareOutlined,
ImportOutlined,
LockOutlined,
} from '@ant-design/icons';
import PrimaryButton, {
SecondaryButton,
SmartButton,
} from '@components/Common/PrimaryButton';
import { currency } from '@components/Common/Ticker.js';
import { Event } from '@utils/GoogleAnalytics';
+export const WelcomeCtn = styled.div`
+ margin-top: 20px;
+ padding: 0px 30px;
+ color: ${props => props.theme.contrast};
+ h2 {
+ color: ${props => props.theme.contrast};
+ }
+`;
+
export const WelcomeText = styled.p`
- color: ${props => props.theme.wallet.text.secondary};
width: 100%;
font-size: 16px;
margin-bottom: 60px;
text-align: left;
`;
export const WelcomeLink = styled.a`
text-decoration: underline;
- color: ${props => props.theme.primary};
+ color: ${props => props.theme.eCashBlue};
+ :hover {
+ color: ${props => props.theme.eCashPurple} !important;
+ text-decoration: underline !important;
+ }
`;
const OnBoarding = () => {
const ContextValue = React.useContext(WalletContext);
const { createWallet, validateMnemonic } = ContextValue;
const [formData, setFormData] = useState({
dirty: true,
mnemonic: '',
});
const [seedInput, openSeedInput] = useState(false);
const [isValidMnemonic, setIsValidMnemonic] = useState(false);
const { confirm } = Modal;
async function submit() {
setFormData({
...formData,
dirty: false,
});
if (!formData.mnemonic) {
return;
}
// Event("Category", "Action", "Label")
// Track number of created wallets from onboarding
Event('Onboarding.js', 'Create Wallet', 'Imported');
createWallet(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 }));
};
function showBackupConfirmModal() {
confirm({
title: "Don't forget to back up your wallet",
icon: ,
+ cancelButtonProps: { style: { display: 'none' } },
content: `Once your wallet is created you can back it up by writing down your 12-word seed. You can find your seed on the Settings page. If you are browsing in Incognito mode or if you clear your browser history, you will lose any funds that are not backed up!`,
okText: 'Okay, make me a wallet!',
onOk() {
// Event("Category", "Action", "Label")
// Track number of created wallets from onboarding
Event('Onboarding.js', 'Create Wallet', 'New');
createWallet();
},
});
}
return (
- <>
+
Welcome to Cashtab!
Cashtab is an{' '}
open source,
{' '}
non-custodial web wallet for {currency.name}.
Want to learn more?{' '}
Check out the Cashtab documentation.
showBackupConfirmModal()}>
New Wallet
openSeedInput(!seedInput)}>
Import Wallet
{seedInput && (
}
type="email"
placeholder="mnemonic (seed phrase)"
name="mnemonic"
autoComplete="off"
onChange={e => handleChange(e)}
required
/>
submit()}
>
Import
)}
- >
+
);
};
export default OnBoarding;
diff --git a/web/cashtab/src/components/Receive/Receive.js b/web/cashtab/src/components/Receive/Receive.js
new file mode 100644
index 000000000..1e04a721a
--- /dev/null
+++ b/web/cashtab/src/components/Receive/Receive.js
@@ -0,0 +1,137 @@
+import React from 'react';
+import styled from 'styled-components';
+import { WalletContext } from '@utils/context';
+import OnBoarding from '@components/OnBoarding/OnBoarding';
+import { QRCode } from '@components/Common/QRCode';
+import { currency } from '@components/Common/Ticker.js';
+import { LoadingCtn } from '@components/Common/Atoms';
+
+export const ReceiveCtn = styled.div`
+ width: 100%;
+ margin-top: 100px;
+ h2 {
+ color: ${props => props.theme.contrast};
+ margin: 0 0 20px;
+ }
+`;
+
+export const SwitchBtnCtn = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-content: space-between;
+ margin-bottom: 15px;
+ .nonactiveBtn {
+ color: ${props => props.theme.walletBackground};
+ background: ${props => props.theme.contrast} !important;
+ opacity: 0.7;
+ box-shadow: none !important;
+ }
+ .slpActive {
+ background: ${props => props.theme.eCashPurple} !important;
+ }
+`;
+
+export const SwitchBtn = styled.div`
+ font-weight: bold;
+ display: inline-block;
+ cursor: pointer;
+ color: ${props => props.theme.switchButtonActiveText};
+ font-size: 14px;
+ padding: 6px 0;
+ width: 100px;
+ margin: 0 1px;
+ text-decoration: none;
+ background: ${props => props.theme.eCashBlue};
+ user-select: none;
+ :first-child {
+ border-radius: 100px 0 0 100px;
+ }
+ :nth-child(2) {
+ border-radius: 0 100px 100px 0;
+ }
+`;
+
+const WalletInfo = () => {
+ const ContextValue = React.useContext(WalletContext);
+ const { wallet } = ContextValue;
+ const [isCashAddress, setIsCashAddress] = React.useState(true);
+
+ const handleChangeAddress = () => {
+ setIsCashAddress(!isCashAddress);
+ };
+
+ return (
+
+ Receive {isCashAddress ? 'XEC' : 'eToken'}
+ {wallet && ((wallet.Path245 && wallet.Path145) || wallet.Path1899) && (
+ <>
+ {wallet.Path1899 ? (
+ <>
+
+ >
+ ) : (
+ <>
+
+ >
+ )}
+ >
+ )}
+
+
+ handleChangeAddress()}
+ className={isCashAddress ? null : 'nonactiveBtn'}
+ >
+ {currency.ticker}
+
+ handleChangeAddress()}
+ className={isCashAddress ? 'nonactiveBtn' : 'slpActive'}
+ >
+ {currency.tokenTicker}
+
+
+
+ );
+};
+
+const Receive = () => {
+ const ContextValue = React.useContext(WalletContext);
+ const { wallet, previousWallet, loading } = ContextValue;
+
+ return (
+ <>
+ {loading ? (
+
+ ) : (
+ <>
+ {(wallet && wallet.Path1899) ||
+ (previousWallet && previousWallet.path1899) ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+ >
+ );
+};
+
+export default Receive;
diff --git a/web/cashtab/src/components/Send/__tests__/Send.test.js b/web/cashtab/src/components/Receive/__tests__/Receive.test.js
similarity index 85%
copy from web/cashtab/src/components/Send/__tests__/Send.test.js
copy to web/cashtab/src/components/Receive/__tests__/Receive.test.js
index b905392b2..899551074 100644
--- a/web/cashtab/src/components/Send/__tests__/Send.test.js
+++ b/web/cashtab/src/components/Receive/__tests__/Receive.test.js
@@ -1,115 +1,109 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { ThemeProvider } from 'styled-components';
import { theme } from '@assets/styles/theme';
-import Send from '@components/Send/Send';
-import BCHJS from '@psf/bch-js';
+import Receive from '@components/Receive/Receive';
import {
walletWithBalancesAndTokens,
walletWithBalancesMock,
walletWithoutBalancesMock,
walletWithBalancesAndTokensWithCorrectState,
-} from '../../Wallet/__mocks__/walletAndBalancesMock';
+} from '../../Home/__mocks__/walletAndBalancesMock';
import { BrowserRouter as Router } from 'react-router-dom';
let realUseContext;
let useContextMock;
beforeEach(() => {
realUseContext = React.useContext;
useContextMock = React.useContext = jest.fn();
// Mock method not implemented in JSDOM
// See reference at https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
});
afterEach(() => {
React.useContext = realUseContext;
});
test('Wallet without BCH balance', () => {
useContextMock.mockReturnValue(walletWithoutBalancesMock);
- const testBCH = new BCHJS();
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances', () => {
useContextMock.mockReturnValue(walletWithBalancesMock);
- const testBCH = new BCHJS();
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances and tokens', () => {
useContextMock.mockReturnValue(walletWithBalancesAndTokens);
- const testBCH = new BCHJS();
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Wallet with BCH balances and tokens and state field', () => {
useContextMock.mockReturnValue(walletWithBalancesAndTokensWithCorrectState);
- const testBCH = new BCHJS();
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
test('Without wallet defined', () => {
useContextMock.mockReturnValue({
wallet: {},
balances: { totalBalance: 0 },
loading: false,
});
- const testBCH = new BCHJS();
const component = renderer.create(
-
+
,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
diff --git a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
similarity index 86%
rename from web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
rename to web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
index 48bf4efba..94ce4a59c 100644
--- a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
+++ b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
@@ -1,622 +1,534 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
-Array [
-
-
- 🎉
-
- Congratulations on your new wallet!
-
-
- 🎉
-
-
- Start using the wallet immediately to receive
-
- XEC
- payments, or load it up with
-
- XEC
- to send to others
-
,
-
- 0
-
+
+
+ Receive
XEC
- ,
+
Copied
ecash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7ccxkrr6zd
ecash:
qzagy47m
vh6qxkvcn3acjnz73rkhkc6y7c
cxkrr6zd
-
,
+
,
-]
+
+
`;
exports[`Wallet with BCH balances and tokens 1`] = `
-Array [
-
-
- 🎉
-
- Congratulations on your new wallet!
-
-
- 🎉
-
-
- Start using the wallet immediately to receive
-
- XEC
- payments, or load it up with
-
- XEC
- to send to others
-
,
-
- 0
-
+
+
+ Receive
XEC
- ,
+
Copied
ecash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7ccxkrr6zd
ecash:
qzagy47m
vh6qxkvcn3acjnz73rkhkc6y7c
cxkrr6zd
-
,
+
,
-]
+
+
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
-Array [
-
- 0.06
-
+
+
+ Receive
XEC
- ,
-
- $
- NaN
-
- USD
-
,
+
Copied
ecash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7ccxkrr6zd
ecash:
qzagy47m
vh6qxkvcn3acjnz73rkhkc6y7c
cxkrr6zd
-
,
+
,
-]
+
+
`;
exports[`Wallet without BCH balance 1`] = `
-Array [
-
-
- 🎉
-
- Congratulations on your new wallet!
-
-
- 🎉
-
-
- Start using the wallet immediately to receive
-
- XEC
- payments, or load it up with
-
- XEC
- to send to others
-
,
-
- 0
-
+
+
+ Receive
XEC
- ,
+
Copied
ecash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7ccxkrr6zd
ecash:
qzagy47m
vh6qxkvcn3acjnz73rkhkc6y7c
cxkrr6zd
-
,
+
,
-]
+
+
`;
exports[`Without wallet defined 1`] = `
-Array [
+
`;
diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js
index 7263a82b3..fe3410f6f 100644
--- a/web/cashtab/src/components/Send/Send.js
+++ b/web/cashtab/src/components/Send/Send.js
@@ -1,1022 +1,1057 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import PropTypes from 'prop-types';
import { WalletContext } from '@utils/context';
import {
AntdFormWrapper,
SendBchInput,
DestinationAddressSingle,
DestinationAddressMulti,
} from '@components/Common/EnhancedInputs';
import { AdvancedCollapse } from '@components/Common/StyledCollapse';
import { Form, message, Modal, Alert, Collapse, Input, Button } from 'antd';
const { Panel } = Collapse;
const { TextArea } = Input;
import { Row, Col, Switch } from 'antd';
import PrimaryButton, {
SecondaryButton,
SmartButton,
} from '@components/Common/PrimaryButton';
import useBCH from '@hooks/useBCH';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {
sendXecNotification,
errorNotification,
messageSignedNotification,
} from '@components/Common/Notifications';
import { isMobile, isIOS, isSafari } from 'react-device-detect';
import { currency, parseAddressForParams } from '@components/Common/Ticker.js';
import { Event } from '@utils/GoogleAnalytics';
import {
fiatToCrypto,
shouldRejectAmountInput,
isValidXecAddress,
isValidEtokenAddress,
isValidXecSendAmount,
} from '@utils/validation';
import BalanceHeader from '@components/Common/BalanceHeader';
import BalanceHeaderFiat from '@components/Common/BalanceHeaderFiat';
import {
ZeroBalanceHeader,
ConvertAmount,
AlertMsg,
+ WalletInfoCtn,
+ SidePaddingCtn,
+ FormLabel,
} from '@components/Common/Atoms';
import {
getWalletState,
convertToEcashPrefix,
toLegacyCash,
toLegacyCashArray,
fromSmallestDenomination,
} from '@utils/cashMethods';
import ApiError from '@components/Common/ApiError';
import { formatFiatBalance, formatBalance } from '@utils/formatting';
import { TokenParamLabel } from '@components/Common/Atoms';
import { PlusSquareOutlined } from '@ant-design/icons';
import styled from 'styled-components';
import { CopyToClipboard } from 'react-copy-to-clipboard';
-
-const StyledSpacer = styled.div`
- height: 1px;
- width: 100%;
- background-color: ${props => props.theme.wallet.borders.color};
- margin: 60px 0 50px;
-`;
+import WalletLabel from '@components/Common/WalletLabel.js';
const SignMessageLabel = styled.div`
text-align: left;
- color: ${props => props.theme.forms.darkLabel};
-`;
-const RecipientModeLabel = styled.div`
- color: ${props => props.theme.forms.lightLabel};
+ color: ${props => props.theme.forms.text};
`;
+
const TextAreaLabel = styled.div`
text-align: left;
- color: ${props => props.theme.forms.darkLabel};
+ color: ${props => props.theme.forms.text};
padding-left: 1px;
`;
+const AmountPreviewCtn = styled.div`
+ margin-top: -30px;
+`;
+
+const SendInputCtn = styled.div`
+ .ant-form-item-with-help {
+ margin-bottom: 32px;
+ }
+`;
+
const LocaleFormattedValue = styled.h3`
- color: ${props => props.theme.forms.text};
+ color: ${props => props.theme.contrast};
+ font-weight: bold;
+ margin-bottom: 0;
`;
// Note jestBCH is only used for unit tests; BCHJS must be mocked for jest
const SendBCH = ({ jestBCH, passLoadingStatus }) => {
// use balance parameters from wallet.state object and not legacy balances parameter from walletState, if user has migrated wallet
// this handles edge case of user with old wallet who has not opened latest Cashtab version yet
// If the wallet object from ContextValue has a `state key`, then check which keys are in the wallet object
// Else set it as blank
const ContextValue = React.useContext(WalletContext);
const location = useLocation();
const { wallet, fiatPrice, apiError, cashtabSettings } = ContextValue;
const walletState = getWalletState(wallet);
const { balances, slpBalancesAndUtxos } = walletState;
// Modal settings
const [showConfirmMsgToSign, setShowConfirmMsgToSign] = useState(false);
const [msgToSign, setMsgToSign] = useState('');
const [signMessageIsValid, setSignMessageIsValid] = useState(null);
const [isOneToManyXECSend, setIsOneToManyXECSend] = useState(false);
const [opReturnMsg, setOpReturnMsg] = useState(false);
const [isEncryptedOptionalOpReturnMsg, setIsEncryptedOptionalOpReturnMsg] =
useState(false);
const [bchObj, setBchObj] = useState(false);
// Get device window width
// If this is less than 769, the page will open with QR scanner open
const { width } = useWindowDimensions();
// Load with QR code open if device is mobile and NOT iOS + anything but safari
const scannerSupported = width < 769 && isMobile && !(isIOS && !isSafari);
const [formData, setFormData] = useState({
value: '',
address: '',
});
const [queryStringText, setQueryStringText] = useState(null);
const [sendBchAddressError, setSendBchAddressError] = useState(false);
const [sendBchAmountError, setSendBchAmountError] = useState(false);
const [selectedCurrency, setSelectedCurrency] = useState(currency.ticker);
// Support cashtab button from web pages
const [txInfoFromUrl, setTxInfoFromUrl] = useState(false);
// Show a confirmation modal on transactions created by populating form from web page button
const [isModalVisible, setIsModalVisible] = useState(false);
const [messageSignature, setMessageSignature] = useState('');
const [sigCopySuccess, setSigCopySuccess] = useState('');
const userLocale = navigator.language;
const clearInputForms = () => {
setFormData({
value: '',
address: '',
});
setOpReturnMsg(''); // OP_RETURN message has its own state field
};
const showModal = () => {
setIsModalVisible(true);
};
const handleOk = () => {
setIsModalVisible(false);
send();
};
const handleCancel = () => {
setIsModalVisible(false);
};
const { getBCH, getRestUrl, sendXec, calcFee, signPkMessage } = useBCH();
// If the balance has changed, unlock the UI
// This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked
useEffect(() => {
passLoadingStatus(false);
}, [balances.totalBalance]);
useEffect(() => {
// jestBCH is only ever specified for unit tests, otherwise app will use getBCH();
const BCH = jestBCH ? jestBCH : getBCH();
// set the BCH instance to state, for other functions to reference
setBchObj(BCH);
// Manually parse for txInfo object on page load when Send.js is loaded with a query string
// if this was routed from Wallet screen's Reply to message link then prepopulate the address and value field
if (location && location.state && location.state.replyAddress) {
setFormData({
address: location.state.replyAddress,
value: `${fromSmallestDenomination(currency.dustSats)}`,
});
}
// Do not set txInfo in state if query strings are not present
if (
!window.location ||
!window.location.hash ||
window.location.hash === '#/send'
) {
return;
}
const txInfoArr = window.location.hash.split('?')[1].split('&');
// Iterate over this to create object
const txInfo = {};
for (let i = 0; i < txInfoArr.length; i += 1) {
let txInfoKeyValue = txInfoArr[i].split('=');
let key = txInfoKeyValue[0];
let value = txInfoKeyValue[1];
txInfo[key] = value;
}
console.log(`txInfo from page params`, txInfo);
setTxInfoFromUrl(txInfo);
populateFormsFromUrl(txInfo);
}, []);
function populateFormsFromUrl(txInfo) {
if (txInfo && txInfo.address && txInfo.value) {
setFormData({
address: txInfo.address,
value: txInfo.value,
});
}
}
function handleSendXecError(errorObj, oneToManyFlag) {
// Set loading to false here as well, as balance may not change depending on where error occured in try loop
passLoadingStatus(false);
let message;
if (!errorObj.error && !errorObj.message) {
message = `Transaction failed: no response from ${getRestUrl()}.`;
} else if (
/Could not communicate with full node or other external service/.test(
errorObj.error,
)
) {
message = 'Could not communicate with API. Please try again.';
} else if (
errorObj.error &&
errorObj.error.includes(
'too-long-mempool-chain, too many unconfirmed ancestors [limit: 50] (code 64)',
)
) {
message = `The ${currency.ticker} you are trying to send has too many unconfirmed ancestors to send (limit 50). Sending will be possible after a block confirmation. Try again in about 10 minutes.`;
} else {
message =
errorObj.message || errorObj.error || JSON.stringify(errorObj);
}
if (oneToManyFlag) {
errorNotification(errorObj, message, 'Sending XEC one to many');
} else {
errorNotification(errorObj, message, 'Sending XEC');
}
}
async function send() {
setFormData({
...formData,
});
if (isOneToManyXECSend) {
// this is a one to many XEC send transactions
// ensure multi-recipient input is not blank
if (!formData.address) {
return;
}
// Event("Category", "Action", "Label")
// Track number of XEC send-to-many transactions
Event('Send.js', 'SendToMany', selectedCurrency);
passLoadingStatus(true);
const { address } = formData;
//convert each line from TextArea input
let addressAndValueArray = address.split('\n');
try {
// construct array of XEC->BCH addresses due to bch-api constraint
let cleanAddressAndValueArray =
toLegacyCashArray(addressAndValueArray);
const link = await sendXec(
bchObj,
wallet,
slpBalancesAndUtxos.nonSlpUtxos,
currency.defaultFee,
opReturnMsg,
true, // indicate send mode is one to many
cleanAddressAndValueArray,
);
sendXecNotification(link);
clearInputForms();
} catch (e) {
handleSendXecError(e, isOneToManyXECSend);
}
} else {
// standard one to one XEC send transaction
if (
!formData.address ||
!formData.value ||
Number(formData.value) <= 0
) {
return;
}
// Event("Category", "Action", "Label")
// Track number of BCHA send transactions and whether users
// are sending BCHA or USD
Event('Send.js', 'Send', selectedCurrency);
passLoadingStatus(true);
const { address, value } = formData;
// Get the param-free address
let cleanAddress = address.split('?')[0];
// Ensure address has bitcoincash: prefix and checksum
cleanAddress = toLegacyCash(cleanAddress);
// Calculate the amount in BCH
let bchValue = value;
if (selectedCurrency !== 'XEC') {
bchValue = fiatToCrypto(value, fiatPrice);
}
// encrypted message limit truncation
let optionalOpReturnMsg;
if (isEncryptedOptionalOpReturnMsg) {
optionalOpReturnMsg = opReturnMsg.substring(
0,
currency.opReturn.encryptedMsgCharLimit,
);
} else {
optionalOpReturnMsg = opReturnMsg;
}
try {
const link = await sendXec(
bchObj,
wallet,
slpBalancesAndUtxos.nonSlpUtxos,
currency.defaultFee,
optionalOpReturnMsg,
false, // sendToMany boolean flag
null, // address array not applicable for one to many tx
cleanAddress,
bchValue,
isEncryptedOptionalOpReturnMsg,
);
sendXecNotification(link);
clearInputForms();
} catch (e) {
handleSendXecError(e, isOneToManyXECSend);
}
}
}
const handleAddressChange = e => {
const { value, name } = e.target;
let error = false;
let addressString = value;
// validate address
const isValid = isValidXecAddress(addressString);
// parse address for parameters
const addressInfo = parseAddressForParams(addressString);
/*
Model
addressInfo =
{
address: '',
queryString: '',
amount: null,
};
*/
const { address, queryString, amount } = addressInfo;
// If query string,
// Show an alert that only amount and currency.ticker are supported
setQueryStringText(queryString);
// Is this valid address?
if (!isValid) {
error = `Invalid ${currency.ticker} address`;
// If valid address but token format
if (isValidEtokenAddress(address)) {
error = `eToken addresses are not supported for ${currency.ticker} sends`;
}
}
setSendBchAddressError(error);
// Set amount if it's in the query string
if (amount !== null) {
// Set currency to BCHA
setSelectedCurrency(currency.ticker);
// Use this object to mimic user input and get validation for the value
let amountObj = {
target: {
name: 'value',
value: amount,
},
};
handleBchAmountChange(amountObj);
setFormData({
...formData,
value: amount,
});
}
// Set address field to user input
setFormData(p => ({
...p,
[name]: value,
}));
};
const handleMultiAddressChange = e => {
const { value, name } = e.target;
let error;
if (!value) {
error = 'Input must not be blank';
setSendBchAddressError(error);
return setFormData(p => ({
...p,
[name]: value,
}));
}
//convert each line from the