diff --git a/web/cashtab-v2/.dockerignore b/web/cashtab-v2/.dockerignore new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/.dockerignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/web/cashtab-v2/.env b/web/cashtab-v2/.env new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/.env @@ -0,0 +1,3 @@ +REACT_APP_NETWORK=mainnet +REACT_APP_BCHA_APIS=https://rest.kingbch.com/v4/,https://rest.bitcoinabc.org/v4/ +REACT_APP_BCHA_APIS_TEST=https://free-test.fullstack.cash/v3/ diff --git a/web/cashtab-v2/.eslintrc.js b/web/cashtab-v2/.eslintrc.js new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:jest/recommended', + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 12, + sourceType: 'module', + }, + plugins: ['react', 'jest'], + rules: { 'jest/no-mocks-import': 'off' }, + settings: { react: { version: 'detect' } }, +}; diff --git a/web/cashtab-v2/.gitignore b/web/cashtab-v2/.gitignore new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Chrome Extension testing +*.crx +*.zip +*.pem +dist/ diff --git a/web/cashtab-v2/.nvmrc b/web/cashtab-v2/.nvmrc new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/.nvmrc @@ -0,0 +1 @@ +15 \ No newline at end of file diff --git a/web/cashtab-v2/Dockerfile b/web/cashtab-v2/Dockerfile new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/Dockerfile @@ -0,0 +1,34 @@ +# Multi-stage +# 1) Node image for building frontend assets +# 2) nginx stage to serve frontend assets + +# Stage 1 +FROM node:15-buster-slim AS builder + +# Install some dependencies before building +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y git && \ + apt-get install -y python + +WORKDIR /app + +# Copy only the package files and install necessary dependencies. +# This reduces cache busting when source files are changed. +COPY package.json . +COPY package-lock.json . +RUN npm ci + +# Copy the rest of the project files and build +COPY . . +RUN npm run build + +# Stage 2 +FROM nginx +COPY nginx.conf /etc/nginx/conf.d/default.conf +# Set working directory to nginx asset directory +# Copy static assets from builder stage +COPY --from=builder /app/build /usr/share/nginx/html/ +EXPOSE 80 +# Containers run nginx with global directives and daemon off +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/web/cashtab-v2/README.md b/web/cashtab-v2/README.md new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/README.md @@ -0,0 +1,137 @@ +# Cashtab + +## eCash Web Wallet + +![CashAppHome](./screenshots/ss-readme.png) + +### Features + +- Send & Receive XEC +- Import existing wallets + +## Development + +``` +npm install +npm start +``` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +## Testing + +### 1. Regression Tests + +Existing functions that are impacted by your diff must be regression tested to ensure no unintended behavior. For example, if you're adding a function to facilitate One To Many XEC transactions, you must test that the existing One to One XEC transactions work as intended. + +### 2. Unit Tests + +Where applicable, add unit tests for new functions created into the corresponding \*.test.js files and they will get picked up as part of the unit test suite. + +Run the tests in watch mode (interactive): + +``` +npm test +``` + +Run the tests and generate a coverage report (non-interactive): + +``` +npm run test:coverage +``` + +You can then browse the HTML coverage report by opening the +`coverage/lcov-report/index.html` file in your web browser. + +### 3. System/Integration Tests + +Once your unit tests have passed successfully, execute the test plan outlined in your diff via manual testing of your new Cashtab feature. + +This includes: + +- testing across Chrome and Firefox browsers to pick up any browser specific issues +- testing via the Extension plugin (see Browser Extension below) to pick up on any extension specific issues + +### 4. Mobile Tests + +Ensure the latest feature functions correctly in a mobile setting and dimension. +Start by updating the build folder with your changes included. + +``` +npm run build +``` + +Then create a new site on [Netlify](https://www.netlify.com/) by choosing to "Deploy manually" and dragging in the /web/cashtab/build folder. Your diff will now be accessible via [projectname].netlify.app, which you can load up on your iOS or Android mobile devices for testing. + +### 5. Edge Tests + +If your diff is complex in nature, then consider potential edge cases which may not get picked up through the testing approaches above. + +This includes but is not limited to: + +- interactions with fresh new wallets with no transaction history +- interactions with wallets which have old transactions relevant to the diff +- interactions with applications outside of Cashtab such as ElectrumABC or outputs generated by custom nodejs scripts. + +## Production + +In the project directory, run: + +``` +npm run build +``` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +## Browser Extension + +1. `npm run extension` +2. Open Chrome or Brave +3. Navigate to `chrome://extensions/` (or `brave://extensions/`) +4. Enable Developer Mode +5. Click "Load unpacked" +6. Select the `extension/dist` folder that was created with `npm run extension` + +## Docker deployment + +``` +npm install +docker-compose build +docker-compose up +``` + +## Redundant APIs + +Cashtab accepts multiple instances of `bch-api` as its backend. Input your desired API URLs separated commas into the `REACT_APP_BCHA_APIS` variable. For example, to run Cashtab with three redundant APIs, use: + +``` +REACT_APP_BCHA_APIS=https://rest.kingbch.com/v4/,https://wallet-service-prod.bitframe.org/v4/,https://free-main.fullstack.cash/v4/ +``` + +You can also run Cashtab with a single API, e.g. + +``` +REACT_APP_BCHA_APIS=https://rest.kingbch.com/v4/ +``` + +Cashtab will start with the first API in your list. If it receives an error from that API, it will try the next one. + +Navigate to `localhost:8080` to see the app. + +## Cashtab Roadmap + +The following features are under active development: + +- Transaction history +- Simple Ledger Postage Protocol Support +- Cashtab browser extension diff --git a/web/cashtab-v2/docker-compose.yml b/web/cashtab-v2/docker-compose.yml new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/docker-compose.yml @@ -0,0 +1,12 @@ +version: '2' + +services: + webserver: + container_name: cashtabwebserver + build: + context: . + dockerfile: Dockerfile + ports: + - 8080:80 + environment: + - NODE_ENV=production diff --git a/web/cashtab-v2/extension/README.md b/web/cashtab-v2/extension/README.md new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/README.md @@ -0,0 +1,9 @@ +# CashTab extension + +Some minor but important code changes are required to build CashTab as a browser extension. + +1. Add option to open extension in tab +2. Unique format of manifest.json with sha256 hash of any external scripts +3. CSS rules for extension pop-up sizing + +Source files unique to the browser extension are kept in the `extension/` directory. diff --git a/web/cashtab-v2/extension/public/manifest.json b/web/cashtab-v2/extension/public/manifest.json new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/public/manifest.json @@ -0,0 +1,30 @@ +{ + "manifest_version": 2, + + "name": "Cashtab", + "description": "A browser-integrated eCash wallet from Bitcoin ABC", + "version": "1.0.6", + "content_scripts": [ + { + "matches": ["file://*/*", "http://*/*", "https://*/*"], + "js": ["contentscript.js"], + "run_at": "document_idle", + "all_frames": true + } + ], + "background": { + "scripts": ["background.js"], + "persistent": false + }, + "browser_action": { + "default_popup": "index.html", + "default_title": "Cashtab" + }, + "icons": { + "16": "ecash16.png", + "48": "ecash48.png", + "128": "ecash128.png", + "192": "ecash192.png", + "512": "ecash512.png" + } +} diff --git a/web/cashtab-v2/extension/src/assets/popout.svg b/web/cashtab-v2/extension/src/assets/popout.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/src/assets/popout.svg @@ -0,0 +1,21 @@ + + + + popout + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/cashtab-v2/extension/src/background.js b/web/cashtab-v2/extension/src/background.js new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/src/background.js @@ -0,0 +1,115 @@ +const extension = require('extensionizer'); + +const NOTIFICATION_HEIGHT = 600; +const NOTIFICATION_WIDTH = 400; + +let popupIsOpen = false; +let notificationIsOpen = false; +const openMetamaskTabsIDs = {}; +const requestAccountTabIds = {}; + +// This starts listening to the port created with `extension.runtime.connect` in contentscript.js +extension.runtime.onConnect.addListener(function (port) { + console.assert(port.name == 'cashtabPort'); + port.onMessage.addListener(function (msg) { + console.log('msg received in background.js'); + console.log(msg.text); + if (msg.text == `CashTab` && msg.txInfo) { + console.log(`Caught, opening popup`); + triggerUi(msg.txInfo); + } + }); +}); + +/** + * Opens the browser popup for user confirmation + */ +/* +Breaking this function down +1) Get all active tabs in browser +2) Determine if the extension UI is currently open +3) If extension is not open AND no other UI triggered popups are open, then open one + +Eventually will need similar model. Note that it actually goes much deeper than this in MetaMask. + +To start, just open a popup +*/ +async function triggerUi(txInfo) { + /* + const tabs = await extension.getActiveTabs(); + const currentlyActiveCashtabTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id])); + if (!popupIsOpen && !currentlyActiveCashtabTab) { + await notificationManager.showPopup(); + } + */ + // Open a pop-up + let left = 0; + let top = 0; + try { + const lastFocused = await getLastFocusedWindow(); + // Position window in top right corner of lastFocused window. + top = lastFocused.top; + left = lastFocused.left + (lastFocused.width - NOTIFICATION_WIDTH); + } catch (_) { + // The following properties are more than likely 0, due to being + // opened from the background chrome process for the extension that + // has no physical dimensions + const { screenX, screenY, outerWidth } = window; + top = Math.max(screenY, 0); + left = Math.max(screenX + (outerWidth - NOTIFICATION_WIDTH), 0); + } + + console.log(`txInfo`); + console.log(txInfo); + + const queryString = Object.keys(txInfo) + .map(key => key + '=' + txInfo[key]) + .join('&'); + + // create new notification popup + const popupWindow = await openWindow({ + url: `index.html#/send?${queryString}`, + type: 'popup', + width: NOTIFICATION_WIDTH, + height: NOTIFICATION_HEIGHT, + left, + top, + }); +} + +async function openWindow(options) { + return new Promise((resolve, reject) => { + extension.windows.create(options, newWindow => { + const error = checkForError(); + if (error) { + return reject(error); + } + return resolve(newWindow); + }); + }); +} + +function checkForError() { + const { lastError } = extension.runtime; + if (!lastError) { + return undefined; + } + // if it quacks like an Error, its an Error + if (lastError.stack && lastError.message) { + return lastError; + } + // repair incomplete error object (eg chromium v77) + return new Error(lastError.message); +} + +async function getLastFocusedWindow() { + return new Promise((resolve, reject) => { + extension.windows.getLastFocused(windowObject => { + const error = checkForError(); + if (error) { + return reject(error); + } + return resolve(windowObject); + }); + }); +} diff --git a/web/cashtab-v2/extension/src/components/App.css b/web/cashtab-v2/extension/src/components/App.css new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/src/components/App.css @@ -0,0 +1,53 @@ +@import '~antd/dist/antd.less'; +@import '~@fortawesome/fontawesome-free/css/all.css'; +@import url('https://fonts.googleapis.com/css?family=Khula&display=swap&.css'); + +/* Hide 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 { + min-width: 400px; + min-height: 600px; + 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-v2/extension/src/components/App.js b/web/cashtab-v2/extension/src/components/App.js new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/src/components/App.js @@ -0,0 +1,358 @@ +import React, { useState } from 'react'; +import 'antd/dist/antd.less'; +import { Spin } from 'antd'; +import { + CashLoadingIcon, + HomeIcon, + SendIcon, + ReceiveIcon, + SettingsIcon, + AirdropIcon, +} from '@components/Common/CustomIcons'; +import '../index.css'; +import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; +import { theme } from '@assets/styles/theme'; +import Home from '@components/Home/Home'; +import Receive from '@components/Receive/Receive'; +import Tokens from '@components/Tokens/Tokens'; +import Send from '@components/Send/Send'; +import SendToken from '@components/Send/SendToken'; +import Airdrop from '@components/Airdrop/Airdrop'; +import Configure from '@components/Configure/Configure'; +import NotFound from '@components/NotFound'; +import CashTab from '@assets/cashtab_xec.png'; +import './App.css'; +import { WalletContext } from '@utils/context'; +import { isValidStoredWallet } from '@utils/cashMethods'; +import { + Route, + Redirect, + Switch, + useLocation, + useHistory, +} from 'react-router-dom'; +// 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: 3px; + border-radius: 3px; + background-color: ${props => + props.theme.modal.buttonBackground} !important; + color: ${props => props.theme.modal.color} !important; + border-color: ${props => props.theme.modal.border} !important; + :hover { + background-color: ${props => props.theme.eCashBlue} !important; + } + text-shadow: none !important; + text-shadow: none !important; + } + + .ant-modal-wrap > div > div.ant-modal-content > div > div > div.ant-modal-confirm-btns > button:hover,.ant-modal-confirm-btns > button:hover, .ant-modal-footer > button:hover, #cropControlsConfirm:hover { + color: ${props => props.theme.contrast}; + transition: all 0.3s; + background-color: ${props => props.theme.eCashBlue}; + border-color: ${props => props.theme.eCashBlue}; + } + .selectedCurrencyOption, .ant-select-dropdown { + text-align: left; + color: ${props => props.theme.contrast} !important; + background-color: ${props => + props.theme.collapses.expandedBackground} !important; + } + .cashLoadingIcon { + color: ${props => props.theme.eCashBlue} !important; + font-size: 48px !important; + } + .selectedCurrencyOption:hover { + color: ${props => props.theme.contrast} !important; + background-color: ${props => props.theme.eCashBlue} !important; + } + #addrSwitch, #cropSwitch { + .ant-switch-checked { + background-color: white !important; + } + } + #addrSwitch.ant-switch-checked, #cropSwitch.ant-switch-checked { + background-image: ${props => + props.theme.buttons.primary.backgroundImage} !important; + } + + .ant-slider-rail { + background-color: ${props => props.theme.forms.border} !important; + } + .ant-slider-track { + background-color: ${props => props.theme.eCashBlue} !important; + } + .ant-descriptions-bordered .ant-descriptions-row { + background: ${props => props.theme.contrast}; + } + .ant-modal-confirm-content, .ant-modal-confirm-title { + color: ${props => props.theme.contrast} !important; + } +`; + +const CustomApp = styled.div` + text-align: center; + font-family: 'Gilroy', sans-serif; + font-family: 'Poppins', sans-serif; + background-color: ${props => props.theme.backgroundColor}; + background-size: 100px 171px; + background-image: ${props => props.theme.backgroundImage}; + background-attachment: fixed; +`; + +const Footer = styled.div` + z-index: 2; + height: 80px; + border-top: 1px solid rgba(255, 255, 255, 0.5); + background-color: ${props => props.theme.footerBackground}; + position: fixed; + bottom: 0; + width: 500px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 50px; + @media (max-width: 768px) { + width: 100%; + padding: 0 20px; + } +`; + +export const NavButton = styled.button` + :focus, + :active { + outline: none; + } + cursor: pointer; + padding: 0; + background: none; + border: none; + font-size: 10px; + svg { + fill: ${props => props.theme.contrast}; + width: 26px; + height: auto; + } + ${({ active, ...props }) => + active && + ` + color: ${props.theme.navActive}; + svg { + fill: ${props.theme.navActive}; + } + `} +`; + +export const WalletBody = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 100vh; +`; + +export const WalletCtn = styled.div` + position: relative; + width: 500px; + min-height: 100vh; + padding: 0 0 100px; + background: ${props => props.theme.walletBackground}; + -webkit-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow}; + -moz-box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow}; + box-shadow: 0px 0px 24px 1px ${props => props.theme.shadow}; + @media (max-width: 768px) { + width: 100%; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } +`; + +export const HeaderCtn = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 15px; +`; + +export const CashTabLogo = styled.img` + width: 120px; + @media (max-width: 768px) { + width: 110px; + } +`; + +// Extension only styled components +const OpenInTabBtn = styled.button` + background: none; + border: none; +`; + +const ExtTabImg = styled.img` + max-width: 20px; +`; + +const App = () => { + const ContextValue = React.useContext(WalletContext); + const { wallet, loading } = ContextValue; + const [loadingUtxosAfterSend, setLoadingUtxosAfterSend] = useState(false); + // 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 ? ( + + ) : null} + + + + + ); +}; + +export default App; diff --git a/web/cashtab-v2/extension/src/contentscript.js b/web/cashtab-v2/extension/src/contentscript.js new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/extension/src/contentscript.js @@ -0,0 +1,35 @@ +const extension = require('extensionizer'); + +// Insert flag into window object to denote CashTab is available and active as a browser extension +// Could use a div or other approach for now, but emulate MetaMask this way so it is extensible to other items +// Try window object approach +var cashTabInject = document.createElement('script'); +cashTabInject.innerHTML = `window.bitcoinAbc = 'cashtab'`; +document.head.appendChild(cashTabInject); + +// Process page messages +// Chrome extensions communicate with web pages through the DOM +// Page sends a message to itself, chrome extension intercepts it +var port = extension.runtime.connect({ name: 'cashtabPort' }); +//console.log(`port: ${JSON.stringify(port)}`); +//console.log(port); + +window.addEventListener( + 'message', + function (event) { + if (typeof event.data.text !== 'undefined') { + console.log('Message received:'); + console.log(event.data.text); + } + + // We only accept messages from ourselves + if (event.source != window) return; + + if (event.data.type && event.data.type == 'FROM_PAGE') { + console.log(event); + console.log('Content script received: ' + event.data.text); + port.postMessage(event.data); + } + }, + false, +); diff --git a/web/cashtab-v2/manifest.json b/web/cashtab-v2/manifest.json new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "/android-icon-36x36.png", + "sizes": "36x36", + "type": "image/png", + "density": "0.75" + }, + { + "src": "/android-icon-48x48.png", + "sizes": "48x48", + "type": "image/png", + "density": "1.0" + }, + { + "src": "/android-icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "density": "1.5" + }, + { + "src": "/android-icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "density": "2.0" + }, + { + "src": "/android-icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "density": "3.0" + }, + { + "src": "/android-icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "density": "4.0" + } + ] +} diff --git a/web/cashtab-v2/nginx.conf b/web/cashtab-v2/nginx.conf new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/nginx.conf @@ -0,0 +1,41 @@ +gzip on; + +gzip_vary on; +gzip_proxied any; +gzip_comp_level 9; +gzip_buffers 16 8k; +gzip_http_version 1.1; +gzip_min_length 256; +gzip_types + application/atom+xml + application/geo+json + application/javascript + application/x-javascript + application/json + application/ld+json + application/manifest+json + application/rdf+xml + application/rss+xml + application/xhtml+xml + application/xml + font/eot + font/otf + font/ttf + image/svg+xml + text/css + text/javascript + text/plain + text/xml; + +server { + listen 80; + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/web/cashtab-v2/public/browserconfig.xml b/web/cashtab-v2/public/browserconfig.xml new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/public/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/web/cashtab-v2/public/cashtab_bg.png b/web/cashtab-v2/public/cashtab_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + + + + + + + + + + + + + + + + + + + + + + + Cashtab + + + + +
+ + diff --git a/web/cashtab-v2/public/manifest.json b/web/cashtab-v2/public/manifest.json new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/public/manifest.json @@ -0,0 +1,36 @@ +{ + "short_name": "Cashtab", + "name": "Cashtab", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "ecash48.png", + "type": "image/png", + "sizes": "48x48" + }, + { + "src": "ecash128.png", + "type": "image/png", + "sizes": "128x128" + }, + { + "src": "ecash192.png", + "type": "image/png", + "sizes": "192x192", + "purpose": "any maskable" + }, + { + "src": "ecash512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#273498", + "background_color": "#ffffff" +} diff --git a/web/cashtab-v2/public/robots.txt b/web/cashtab-v2/public/robots.txt new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/web/cashtab-v2/screenshots/ss-readme.png b/web/cashtab-v2/screenshots/ss-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + + + + + + + + + + + + + + + + + diff --git a/web/cashtab-v2/src/assets/alert-circle.svg b/web/cashtab-v2/src/assets/alert-circle.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/alert-circle.svg @@ -0,0 +1 @@ +Alert Circle \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/cashtab_xec.png b/web/cashtab-v2/src/assets/cashtab_xec.png new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@Cog \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/copy.svg b/web/cashtab-v2/src/assets/copy.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/edit.svg b/web/cashtab-v2/src/assets/edit.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/edit.svg @@ -0,0 +1 @@ +Rename \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/external-link-square-alt.svg b/web/cashtab-v2/src/assets/external-link-square-alt.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/external-link-square-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/fingerprint-solid.svg b/web/cashtab-v2/src/assets/fingerprint-solid.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/fingerprint-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/flask.svg b/web/cashtab-v2/src/assets/flask.svg new file mode 100644 --- /dev/null +++ b/web/cashtab-v2/src/assets/flask.svg @@ -0,0 +1 @@ +Flask \ No newline at end of file diff --git a/web/cashtab-v2/src/assets/fonts/Poppins-Bold.ttf b/web/cashtab-v2/src/assets/fonts/Poppins-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@