├── .firebaserc ├── .gitignore ├── .prettierrc.yml ├── .travis.yml ├── LICENSE ├── README.md ├── account-fixtures.json ├── azure-pipelines.yml ├── config ├── common.config.js ├── extension.config.js └── web.config.js ├── firebase.json ├── jest.config.js ├── package.json ├── scripts ├── create-build-zip.js ├── publish-extension.js └── update-extension-version.js ├── src ├── app │ ├── app-context.ts │ ├── app.component.tsx │ ├── app.container.tsx │ ├── app.scss │ ├── components │ │ ├── balance │ │ │ ├── balance.component.tsx │ │ │ └── balance.container.tsx │ │ ├── bottom-bar │ │ │ ├── __tests__ │ │ │ │ └── bottom-bar-component.test.tsx │ │ │ └── bottom-bar.components.tsx │ │ ├── copy │ │ │ ├── copy.component.tsx │ │ │ └── copy.scss │ │ ├── create-password │ │ │ ├── create-password.component.tsx │ │ │ └── create-password.scss │ │ ├── currency-total │ │ │ ├── currency-total.component.tsx │ │ │ └── currency-total.container.tsx │ │ ├── currency │ │ │ ├── currency.component.tsx │ │ │ └── currency.container.tsx │ │ ├── list-item │ │ │ ├── list-item.component.scss │ │ │ └── list-item.component.tsx │ │ ├── material-components │ │ │ ├── bottom-navigation │ │ │ │ ├── bottom-navigation-action.component.tsx │ │ │ │ ├── bottom-navigation.component.tsx │ │ │ │ └── bottom-navigation.scss │ │ │ └── loader │ │ │ │ ├── loader.component.tsx │ │ │ │ └── loader.scss │ │ ├── testnet-warning │ │ │ ├── testnet-warning.component.tsx │ │ │ ├── testnet-warning.container.tsx │ │ │ └── testnet-warning.scss │ │ ├── textarea-auto-size │ │ │ ├── textarea-auto-size.components.tsx │ │ │ └── textarea-auto-size.scss │ │ ├── top-bar │ │ │ ├── top-bar.component.tsx │ │ │ ├── top-bar.container.tsx │ │ │ └── top-bar.scss │ │ └── translate │ │ │ └── translate.component.tsx │ ├── data │ │ ├── action.ts │ │ ├── app │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ └── state.ts │ │ ├── common │ │ │ └── actions.ts │ │ ├── currency │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ └── state.ts │ │ ├── index.ts │ │ ├── page-config │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ └── state.ts │ │ ├── reducers.ts │ │ ├── user-preferences │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ └── state.ts │ │ └── wallet │ │ │ ├── actions.ts │ │ │ ├── reducer.ts │ │ │ └── state.ts │ ├── layouts │ │ └── default │ │ │ ├── default.component.tsx │ │ │ ├── default.container.tsx │ │ │ └── default.scss │ ├── pages │ │ ├── account │ │ │ ├── account.component.tsx │ │ │ ├── account.container.tsx │ │ │ ├── account.scss │ │ │ └── components │ │ │ │ ├── account-card │ │ │ │ ├── account-card.component.tsx │ │ │ │ └── account-card.scss │ │ │ │ └── address-card │ │ │ │ ├── address-card.component.tsx │ │ │ │ ├── address-card.container.tsx │ │ │ │ └── address-card.scss │ │ ├── confirmation-screen │ │ │ ├── confirmation-screen.component.tsx │ │ │ ├── confirmation-screen.container.tsx │ │ │ └── screens │ │ │ │ ├── account-access │ │ │ │ ├── account-access.component.tsx │ │ │ │ ├── account-access.container.tsx │ │ │ │ └── account-access.scss │ │ │ │ ├── sign-message │ │ │ │ ├── sign-message.component.tsx │ │ │ │ ├── sign-message.container.tsx │ │ │ │ └── sign-message.scss │ │ │ │ └── transaction-confirmation │ │ │ │ ├── transaction-confirmation.component.tsx │ │ │ │ ├── transaction-confirmation.container.tsx │ │ │ │ └── transaction-confirmation.scss │ │ ├── create-account │ │ │ ├── create-account.component.tsx │ │ │ ├── create-account.scss │ │ │ ├── tab-add │ │ │ │ ├── tab-add.component.tsx │ │ │ │ └── tab-add.container.tsx │ │ │ ├── tab-connect │ │ │ │ ├── devices │ │ │ │ │ └── ledger │ │ │ │ │ │ └── ledger.component.tsx │ │ │ │ ├── tab-connect.component.tsx │ │ │ │ ├── tab-connect.container.tsx │ │ │ │ └── tab-connect.scss │ │ │ └── tab-import │ │ │ │ ├── tab-import.component.tsx │ │ │ │ └── tab-import.container.tsx │ │ ├── create-wallet │ │ │ ├── components │ │ │ │ ├── step1 │ │ │ │ │ ├── step1.component.tsx │ │ │ │ │ └── step1.scss │ │ │ │ └── step2 │ │ │ │ │ ├── step2.component.tsx │ │ │ │ │ └── step2.scss │ │ │ ├── create-wallet.component.tsx │ │ │ ├── create-wallet.container.tsx │ │ │ └── create-wallet.scss │ │ ├── dashboard │ │ │ ├── dashboard.component.tsx │ │ │ ├── dashboard.container.tsx │ │ │ └── dashboard.scss │ │ ├── import-wallet │ │ │ ├── choose-import-type │ │ │ │ ├── choose-import-type.component.tsx │ │ │ │ └── choose-import-type.scss │ │ │ ├── cloud-restore │ │ │ │ ├── cloud-restore.component.tsx │ │ │ │ ├── cloud-restore.container.tsx │ │ │ │ └── cloud-restore.scss │ │ │ ├── enter-mnemonic │ │ │ │ ├── enter-mnemonic.component.tsx │ │ │ │ └── enter-mnemonic.scss │ │ │ ├── import-wallet.component.tsx │ │ │ ├── import-wallet.container.tsx │ │ │ └── import-wallet.scss │ │ ├── landing │ │ │ ├── landing.component.tsx │ │ │ ├── landing.container.tsx │ │ │ └── landing.scss │ │ ├── receive │ │ │ ├── receive.component.tsx │ │ │ ├── receive.container.tsx │ │ │ └── receive.scss │ │ ├── request-account-access │ │ │ ├── request-account-access.component.tsx │ │ │ ├── request-account-access.container.tsx │ │ │ └── request-account-access.scss │ │ ├── reveal │ │ │ ├── reveal.component.tsx │ │ │ ├── reveal.container.tsx │ │ │ └── reveal.scss │ │ ├── send │ │ │ ├── components │ │ │ │ └── transaction-fee │ │ │ │ │ ├── components │ │ │ │ │ └── gas-fee │ │ │ │ │ │ ├── gas-fee.component.tsx │ │ │ │ │ │ └── gas-fee.container.tsx │ │ │ │ │ ├── transaction-fee.component.tsx │ │ │ │ │ ├── transaction-fee.container.tsx │ │ │ │ │ └── transaction-fee.scss │ │ │ ├── send.component.tsx │ │ │ ├── send.container.tsx │ │ │ └── send.scss │ │ ├── settings │ │ │ ├── pages │ │ │ │ ├── backup │ │ │ │ │ ├── backup.component.tsx │ │ │ │ │ └── backup.scss │ │ │ │ ├── currency │ │ │ │ │ ├── currency.component.tsx │ │ │ │ │ └── currency.container.tsx │ │ │ │ ├── disclaimer │ │ │ │ │ ├── disclaimer.component.tsx │ │ │ │ │ └── disclaimer.scss │ │ │ │ ├── network-options │ │ │ │ │ ├── network-options.component.tsx │ │ │ │ │ ├── network-options.container.tsx │ │ │ │ │ └── network-options.scss │ │ │ │ └── zilliqa-account-recover │ │ │ │ │ ├── zilliqa-account-recover.component.tsx │ │ │ │ │ ├── zilliqa-account-recover.container.tsx │ │ │ │ │ └── zilliqa-account-recover.scss │ │ │ ├── settings.component.scss │ │ │ ├── settings.component.tsx │ │ │ └── settings.container.tsx │ │ ├── support-us │ │ │ ├── support-us.component.tsx │ │ │ └── support-us.scss │ │ └── transaction-details │ │ │ ├── transaction-details.component.tsx │ │ │ ├── transaction-details.container.tsx │ │ │ └── transaction-details.scss │ ├── routes.tsx │ ├── styles │ │ ├── _base.scss │ │ ├── _mixins.scss │ │ ├── _vars.scss │ │ ├── material-components.scss │ │ ├── reset.scss │ │ └── utils.scss │ ├── translations │ │ ├── __mocks__ │ │ │ └── en.ts │ │ ├── en.ts │ │ └── types.ts │ ├── types.ts │ └── utils │ │ ├── __tests__ │ │ ├── merge-deep.test.ts │ │ ├── screen-size-match-media.test.ts │ │ └── translate.test.ts │ │ ├── account.ts │ │ ├── authentication │ │ ├── auth-interface.ts │ │ ├── google-auth.ts │ │ └── oAuth2.ts │ │ ├── backup.ts │ │ ├── cloud-storage │ │ ├── cloud-storage-provider.ts │ │ └── google-drive-provider.ts │ │ ├── currency-conversion.ts │ │ ├── event-emitter.ts │ │ ├── feature.ts │ │ ├── merge-deep.ts │ │ ├── navigation.ts │ │ ├── platform-utils.ts │ │ ├── remove-type.ts │ │ ├── screen-size-match-media.ts │ │ ├── string.ts │ │ ├── translate.ts │ │ └── ud-api-client.ts ├── assets │ ├── favicon.ico │ ├── icon-128px.png │ ├── icon-16px.png │ ├── icon-192px.png │ ├── icon-256px.png │ ├── icon-32px.png │ ├── icon-48px.png │ ├── icon-512px.png │ ├── icon-64px.png │ ├── icon-green-128px.png │ ├── icon-green-16px.png │ ├── icon-green-192px.png │ ├── icon-green-256px.png │ ├── icon-green-32px.png │ ├── icon-green-48px.png │ ├── icon-green-512px.png │ ├── icon-green-64px.png │ ├── icons │ │ ├── account-type-hd.svg │ │ ├── account-type-ledger.svg │ │ ├── account-type-loose.svg │ │ ├── cloud │ │ │ ├── dropbox-flat-icon.svg │ │ │ ├── dropbox.svg │ │ │ ├── google-drive-flat-icon.svg │ │ │ ├── google-drive.svg │ │ │ ├── one-drive-flat-icon.svg │ │ │ └── one-drive.svg │ │ ├── hw │ │ │ └── ledger.svg │ │ ├── receive.svg │ │ └── send.svg │ ├── logo.svg │ └── token-logos │ │ ├── eth.svg │ │ └── zil.svg ├── near.png ├── platforms │ ├── extension │ │ ├── background │ │ │ ├── browser-icon-manager.ts │ │ │ ├── index.ts │ │ │ └── remote-interface.ts │ │ ├── content-script │ │ │ ├── dapp-integration │ │ │ │ ├── index.ts │ │ │ │ ├── inject.ts │ │ │ │ └── providers │ │ │ │ │ └── zilliqa.ts │ │ │ ├── permission-manager.ts │ │ │ └── utils │ │ │ │ ├── communication │ │ │ │ ├── base-communication-controller.ts │ │ │ │ ├── communication.ts │ │ │ │ └── dapp-communication-controller.ts │ │ │ │ └── constants.ts │ │ ├── index.html │ │ ├── index.tsx │ │ ├── manifest.json │ │ ├── types.ts │ │ └── utils.ts │ └── web │ │ ├── index.html │ │ ├── index.tsx │ │ └── manifest.json ├── plugins │ ├── app-remote-config │ │ ├── extension │ │ │ ├── app-remote-config-controller.ts │ │ │ └── index.ts │ │ ├── iapp-remote-config-plugin.ts │ │ └── web │ │ │ └── index.ts │ ├── confirmation-screen │ │ ├── extension │ │ │ ├── confirmation-screen-controller.ts │ │ │ └── index.ts │ │ └── iconfirmation-screen-plugin.ts │ ├── core │ │ ├── extension │ │ │ └── bg-communication-plugin.ts │ │ └── web │ │ │ └── extension-plugin-to-web.ts │ ├── dapp-access │ │ ├── extension │ │ │ ├── dapp-access-controller.ts │ │ │ └── index.ts │ │ └── idapp-access.ts │ ├── iplugins.ts │ ├── ledger-hw │ │ ├── extension │ │ │ ├── index.ts │ │ │ └── ledger-hw-controller.ts │ │ ├── iledger-hw-plugin.ts │ │ └── web │ │ │ └── index.tsx │ └── wallet │ │ ├── base-wallet-controller.ts │ │ ├── extension │ │ ├── index.ts │ │ └── wallet-controller.ts │ │ ├── iwallet-plugin.ts │ │ └── web │ │ ├── index.ts │ │ └── wallet-controller.ts ├── tests │ └── global-setup.ts └── utils │ ├── blockchain │ ├── blockchain-info.ts │ ├── nonce-manager.ts │ ├── types.ts │ └── utils.ts │ ├── deferred.ts │ ├── object-utils.ts │ └── response.ts ├── test.js ├── tsconfig.json └── tslint.json /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "moonlet-cb426" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | package-lock.json 3 | node_modules 4 | /build 5 | /*.log 6 | .idea 7 | preact.config.js 8 | coverage 9 | .vscode 10 | .scannerwork 11 | .history 12 | 13 | 14 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | singleQuote: true 3 | tabWidth: 4 4 | parser: typescript 5 | 6 | overrides: 7 | - files: "*.scss" 8 | options: 9 | parser: scss 10 | - files: "*.json" 11 | options: 12 | parser: json 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 cryptoland.tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moonlet Wallet Suite 2 | 3 | [![Build Status](https://travis-ci.org/cryptolandtech/moonlet.svg?branch=master)](https://travis-ci.org/cryptolandtech/moonlet) 4 | 5 | ## Project setup 6 | Install dependencies 7 | ``` bash 8 | npm install 9 | ``` 10 | 11 | ## Web app 12 | ``` bash 13 | # serve with hot reload at localhost:8080 14 | npm run dev:web 15 | 16 | # build for production 17 | npm run build:web 18 | ``` 19 | 20 | ## Extension 21 | ``` bash 22 | # serve with hot reload 23 | npm run dev:ext 24 | 25 | # build for production 26 | npm run build:ext 27 | ``` 28 | 29 | ### Test extension in Chrome 30 | The extension build will be found in `build` folder. 31 | To install extension, open Chrome, go to _Extensions_, activate _Developer mode_, select _Load unpacked_, choose `build` folder from disk. 32 | Changes in manifest file need an extension reload. 33 | 34 | For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). 35 | -------------------------------------------------------------------------------- /account-fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "17f343a7a13bac8026d6c84e2ee2925aeba1fa61": { 3 | "privateKey": "ed50e832ef7722239de17e49cd40d86c16df4649275452af470e9b84ef14eea8", 4 | "amount": 100000, 5 | "nonce": 0 6 | }, 7 | "693e20469bffde10dd4d252d5f907cebbd201bb6": { 8 | "privateKey": "195adc169bab9b6ed6c858089a42e47692b2c7fbe5a1b109c7225bcbd1ca89b9", 9 | "amount": 100000, 10 | "nonce": 0 11 | }, 12 | "78e44924bbc5f4b2c60b2aa9cedf7e0e66c68e36": { 13 | "privateKey": "29658ab3df1ba6bbefd52365f8048dbcb56313cd3ebae31a3c0bb76d9d083fd1", 14 | "amount": 100000, 15 | "nonce": 0 16 | }, 17 | "94af5652316326d299985d6ea0fcfb8353717dec": { 18 | "privateKey": "254cfa989a2681d0213fbc27340882a286e526b17aa9d16108e9d3d43c1548ac", 19 | "amount": 100000, 20 | "nonce": 0 21 | }, 22 | "c4c29d19c63fe6eee06145de24c0a6813e452b62": { 23 | "privateKey": "fa6959db47bc4b12aae0e45ea145d2ba9bb51d2a610f8536be743d93e19d9ec2", 24 | "amount": 100000, 25 | "nonce": 0 26 | }, 27 | "baf5f8f783de95a157d294ac74f9ffb77ded0a99": { 28 | "privateKey": "032827725aad3ee0d9ab22e44bffc9d2a2fdea9a3eb885ad6b64e3723a3bade6", 29 | "amount": 100000, 30 | "nonce": 0 31 | }, 32 | "240f1f305271bef98b7ed021fb9939ce2db65bc1": { 33 | "privateKey": "febfcd14fb3e24e37944870436014474dcfafc4edb8f331f11da67d189883edd", 34 | "amount": 100000, 35 | "nonce": 0 36 | }, 37 | "bb620c967a86e98ec620712692bca463645b35a0": { 38 | "privateKey": "5185dbb2b53756bfbbbd3ed4dc828dd8a25d4d1a7471c2590db7d538f8ba4604", 39 | "amount": 100000, 40 | "nonce": 0 41 | }, 42 | "44526c8eef2efab582b049003741079b36f7ad3b": { 43 | "privateKey": "3b6ea2747a253b93abbb2aceebf4d687bc3c937bd40adc00e709168d69d8cdfe", 44 | "amount": 100000, 45 | "nonce": 0 46 | }, 47 | "22537dfddb1232be8ce10c7fc4b784f61a4375a9": { 48 | "privateKey": "0da6b2b62792202ad94d44ac66b58a751783f3dd36551c994b0864216d726652", 49 | "amount": 100000, 50 | "nonce": 0 51 | }, 52 | "6a6e10812fae135506fb1a3212c1bb9f8fa6bdd3": { 53 | "privateKey": "fdff8786738f4421a2518c119c9a75aff77f27419641fbafe047c2e482055037", 54 | "amount": 100000, 55 | "nonce": 0 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js with webpack 2 | # Build a Node.js project using the webpack CLI. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | steps: 13 | - task: NodeTool@0 14 | inputs: 15 | versionSpec: '8.x' 16 | displayName: 'Install Node.js' 17 | 18 | - script: | 19 | npm install 20 | npm run build:ext 21 | displayName: 'Build extension' 22 | -------------------------------------------------------------------------------- /config/common.config.js: -------------------------------------------------------------------------------- 1 | import preactCliTypeScript from 'preact-cli-plugin-typescript'; 2 | 3 | /** 4 | * Function that mutates original webpack config. 5 | * Supports asynchronous changes when promise is returned. 6 | * 7 | * @param {object} config original webpack config. 8 | * @param {object} env options passed to CLI. 9 | * @param {WebpackConfigHelpers} helpers object with useful helpers when working with config. 10 | **/ 11 | export default function(config, env, helpers) { 12 | preactCliTypeScript(config); 13 | 14 | // this is needed by webextension-polyfill-ts module 15 | for (let loader of config.module.loaders) { 16 | if (loader.loader === 'babel-loader') { 17 | loader.options.plugins.push([ 18 | 'babel-plugin-transform-builtin-extend', 19 | { 20 | globals: ['WeakMap'] 21 | } 22 | ]); 23 | break; 24 | } 25 | } 26 | 27 | config.plugins.push( 28 | new helpers.webpack.DefinePlugin({ 29 | 'process.env.PUBLIC_PATH': JSON.stringify(config.output.publicPath) 30 | }) 31 | ); 32 | 33 | config.node.process = 'mock'; 34 | config.node.Buffer = true; 35 | } 36 | -------------------------------------------------------------------------------- /config/web.config.js: -------------------------------------------------------------------------------- 1 | import common from './common.config'; 2 | import { resolve } from 'path'; 3 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 4 | /** 5 | * Function that mutates original webpack config. 6 | * Supports asynchronous changes when promise is returned. 7 | * 8 | * @param {object} config original webpack config. 9 | * @param {object} env options passed to CLI. 10 | * @param {WebpackConfigHelpers} helpers object with useful helpers when working with config. 11 | **/ 12 | export default function(config, env, helpers) { 13 | // apply common config 14 | common(config, env, helpers); 15 | 16 | config.plugins.push( 17 | new helpers.webpack.DefinePlugin({ 18 | 'process.env.PLATFORM': JSON.stringify('WEB') // same as in Platform enum 19 | }) 20 | ); 21 | 22 | // customize config 23 | config.resolve.alias['preact-cli-entrypoint'] = resolve( 24 | process.cwd(), 25 | 'src', 26 | 'platforms', 27 | 'web', 28 | 'index' 29 | ); 30 | 31 | // overwrite manifest.json 32 | config.plugins.unshift( 33 | new CopyWebpackPlugin([ 34 | { 35 | from: resolve(process.cwd(), 'src', 'platforms', 'web', 'manifest.json'), 36 | to: 'manifest.json' 37 | } 38 | ]) 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build/web", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 5 | "rewrites": [ 6 | { 7 | "source": "**", 8 | "destination": "/index.html" 9 | } 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | snapshotSerializers: ['preact-render-spy/snapshot'], 7 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | moduleNameMapper: { 10 | '\\.(scss)$': 'identity-obj-proxy' 11 | }, 12 | setupFiles: ['/src/tests/global-setup.ts'], 13 | collectCoverage: true, 14 | coverageDirectory: 'coverage', 15 | collectCoverageFrom: [ 16 | 'src/app/**/*.{ts,tsx}', 17 | '!src/tests/**', 18 | '!**/node_modules/**', 19 | '!**/vendor/**' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /scripts/create-build-zip.js: -------------------------------------------------------------------------------- 1 | const zipFolder = require('zip-folder'); 2 | const resolve = require('path').resolve; 3 | const version = require('../build/extension/manifest.json').version; 4 | 5 | const SOURCE_FOLDER = resolve(__dirname, '../build/extension'); 6 | const ZIP_FILE_NAME = resolve(__dirname, `../build/chrome-extension-${version}.zip`); 7 | 8 | zipFolder(SOURCE_FOLDER, ZIP_FILE_NAME, function(err) { 9 | if (err) { 10 | console.log('oh no! ', err); 11 | } else { 12 | console.log(`Successfully zipped the ${SOURCE_FOLDER} directory as ${ZIP_FILE_NAME}`); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /scripts/publish-extension.js: -------------------------------------------------------------------------------- 1 | const zipFolder = require('zip-folder'); 2 | const resolve = require('path').resolve; 3 | const fs = require('fs'); 4 | 5 | const SOURCE_FOLDER = resolve(__dirname, '../build/extension'); 6 | const ZIP_FILE_NAME = resolve(__dirname, '../build/extension.zip'); 7 | 8 | const REFRESH_TOKEN = process.env.REFRESH_TOKEN; 9 | const EXTENSION_ID = process.env.EXTENSION_ID; 10 | const CLIENT_SECRET = process.env.CLIENT_SECRET; 11 | const CLIENT_ID = process.env.CLIENT_ID; 12 | 13 | const webStore = require('chrome-webstore-upload')({ 14 | extensionId: EXTENSION_ID, 15 | clientId: CLIENT_ID, 16 | clientSecret: CLIENT_SECRET, 17 | refreshToken: REFRESH_TOKEN 18 | }); 19 | 20 | const manifest = require(resolve(__dirname, '../build/extension/manifest.json')); 21 | if (process.env.TRAVIS_BUILD_NUMBER) { 22 | const ver = manifest.version.split('.'); 23 | manifest.version = `${ver[0]}.${ver[1]}.${process.env.TRAVIS_BUILD_NUMBER}`; 24 | fs.writeFileSync( 25 | resolve(__dirname, '../build/extension/manifest.json'), 26 | JSON.stringify(manifest) 27 | ); 28 | } 29 | 30 | zipFolder(SOURCE_FOLDER, ZIP_FILE_NAME, function(err) { 31 | if (err) { 32 | console.log('oh no! ', err); 33 | } else { 34 | console.log(`Successfully zipped the ${SOURCE_FOLDER} directory as ${ZIP_FILE_NAME}`); 35 | // will be invoking upload process 36 | console.log(`Uploading the new version (${manifest.version})...`); 37 | upload(ZIP_FILE_NAME); 38 | } 39 | }); 40 | 41 | function upload(zipName) { 42 | const extensionSource = fs.createReadStream(zipName); 43 | webStore 44 | .uploadExisting(extensionSource) 45 | .then(res => { 46 | if (res.uploadState !== 'SUCCESS') { 47 | console.log(`Error while uploading ZIP:`, res.itemError); 48 | return process.exit(1); 49 | } 50 | 51 | console.log('Successfully uploaded the ZIP'); 52 | console.log('Publishing the new version'); 53 | publish(); 54 | }) 55 | .catch(error => { 56 | console.log(`Error while uploading ZIP: ${error}`); 57 | process.exit(1); 58 | }); 59 | } 60 | 61 | function publish() { 62 | // publish the uploaded zip 63 | webStore 64 | .publish('default') 65 | .then(res => { 66 | console.log('Successfully published the newer version'); 67 | }) 68 | .catch(error => { 69 | console.log(`Error while publishing uploaded extension: ${error}`); 70 | process.exit(1); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /scripts/update-extension-version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const resolve = require('path').resolve; 3 | const manifest = require('../src/platforms/extension/manifest.json'); 4 | const packageJson = require('../package.json'); 5 | 6 | manifest.version = packageJson.version; 7 | 8 | fs.writeFileSync( 9 | resolve(__dirname, '../src/platforms/extension/manifest.json'), 10 | JSON.stringify(manifest, null, 4) 11 | ); 12 | -------------------------------------------------------------------------------- /src/app/app-context.ts: -------------------------------------------------------------------------------- 1 | import { IPlugins } from './../plugins/iplugins'; 2 | import { IWalletPlugin } from '../plugins/wallet/iwallet-plugin'; 3 | 4 | const context = {}; 5 | 6 | export const appContext = (key, value?) => { 7 | if (key) { 8 | if (value) { 9 | context[key] = value; 10 | return true; 11 | } else { 12 | return context[key]; 13 | } 14 | } 15 | 16 | return undefined; 17 | }; 18 | 19 | export const getPlugins = (): IPlugins => { 20 | return appContext('plugins'); 21 | }; 22 | 23 | export const getWalletPlugin = (): IWalletPlugin => { 24 | return getPlugins().wallet; 25 | }; 26 | -------------------------------------------------------------------------------- /src/app/app.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'preact-redux'; 2 | import App from './app.component'; 3 | import { createChangePage, createChangeScreenSize } from './data/page-config/actions'; 4 | import { IState } from './data/'; 5 | import { filterAccounts } from '../utils/blockchain/utils'; 6 | import { getPlugins } from './app-context'; 7 | import { createUpdateConversionRates } from './data/currency/actions'; 8 | 9 | const mapStateToProps = (state: IState, ownProps) => { 10 | const accounts = filterAccounts( 11 | state.wallet.data || {}, 12 | !(state.userPreferences || ({} as any)).testNet, 13 | (state.userPreferences || ({} as any)).networks 14 | ); 15 | 16 | return { 17 | ...ownProps, 18 | accounts, 19 | walletStatus: state.wallet.status, 20 | app: state.app 21 | }; 22 | }; 23 | 24 | const mapDispatchToProps = { 25 | onScreenSizeChange: createChangeScreenSize, 26 | onRouteChange: createChangePage, 27 | updateExchangeRates: () => createUpdateConversionRates(getPlugins()) 28 | }; 29 | 30 | export default connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(App); 34 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | @import './styles/base'; 2 | 3 | @import './styles/reset'; 4 | @import './styles/utils'; 5 | 6 | html, 7 | body { 8 | height: 100%; 9 | font-family: Roboto, sans-serif; 10 | color: #666666; 11 | } 12 | 13 | a { 14 | color: #666666; 15 | text-decoration: none; 16 | } 17 | 18 | .app-root { 19 | min-height: 100%; 20 | } 21 | 22 | * { 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | @import './styles/material-components'; 28 | -------------------------------------------------------------------------------- /src/app/components/balance/balance.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { Blockchain } from 'moonlet-core/src/core/blockchain'; 3 | import { IAccountBalance, IAccountsBalances } from '../../data/wallet/state'; 4 | import Currency from '../currency/currency.container'; 5 | import { BLOCKCHAIN_INFO } from '../../../utils/blockchain/blockchain-info'; 6 | import { formatAmount } from '../../../utils/blockchain/utils'; 7 | 8 | interface IProps { 9 | blockchain: Blockchain; 10 | address: string; 11 | hideCurrency: boolean; 12 | convert: boolean; 13 | 14 | balances: IAccountsBalances; 15 | updateBalance: (blockchain: Blockchain, address: string) => any; 16 | } 17 | 18 | export class Balance extends Component { 19 | constructor(props: IProps) { 20 | super(props); 21 | this.updateBalance(); 22 | } 23 | 24 | // componentDidMount() { 25 | // this.updateBalance; 26 | // } 27 | 28 | public componentDidUpdate(prevProps: IProps, prevState, prevContext) { 29 | if ( 30 | prevProps.blockchain !== this.props.blockchain || 31 | prevProps.address !== this.props.address 32 | ) { 33 | this.updateBalance(); 34 | } 35 | } 36 | 37 | public updateBalance() { 38 | const props = this.props; 39 | if ( 40 | props.balances && 41 | props.balances[props.blockchain] && 42 | props.balances[props.blockchain][props.address] && 43 | !props.balances[props.blockchain][props.address].loading 44 | ) { 45 | // balance available 46 | if (Date.now() - props.balances[props.blockchain][props.address].lastUpdate > 60000) { 47 | // balance is old, update it 48 | props.updateBalance(props.blockchain, props.address); 49 | } 50 | } else { 51 | props.updateBalance(props.blockchain, props.address); 52 | } 53 | } 54 | 55 | public render(props: IProps) { 56 | let balance: IAccountBalance = { loading: true, amount: undefined, lastUpdate: undefined }; 57 | if ( 58 | props.balances && 59 | props.balances[props.blockchain] && 60 | props.balances[props.blockchain][props.address] 61 | ) { 62 | balance = props.balances[props.blockchain][props.address]; 63 | } 64 | 65 | return ( 66 | 67 | {balance.amount && ( 68 | 76 | )} 77 | {!balance.amount && '...'} 78 | 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/components/balance/balance.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'preact-redux'; 2 | import { Balance } from './balance.component'; 3 | import { IState } from '../../data'; 4 | import { createGetBalance } from '../../data/wallet/actions'; 5 | import { getWalletPlugin } from '../../app-context'; 6 | 7 | const mapStateToProps = (state: IState, ownProps) => { 8 | return { 9 | ...ownProps, 10 | balances: state.wallet.balances || {} 11 | }; 12 | }; 13 | 14 | const mapDispatchToProps = { 15 | updateBalance: (blockchain, address) => createGetBalance(getWalletPlugin(), blockchain, address) 16 | }; 17 | 18 | export default connect( 19 | mapStateToProps, 20 | mapDispatchToProps 21 | )(Balance); 22 | -------------------------------------------------------------------------------- /src/app/components/bottom-bar/__tests__/bottom-bar-component.test.tsx: -------------------------------------------------------------------------------- 1 | describe('component test', () => { 2 | it('should work', () => { 3 | // const snap = shallow(); 4 | // expect(snap).toMatchSnapshot(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /src/app/components/bottom-bar/bottom-bar.components.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component, RenderableProps } from 'preact'; 2 | 3 | import { BottomNavigation } from '../material-components/bottom-navigation/bottom-navigation.component'; 4 | import { Platform } from '../../types'; 5 | import { translate } from '../../utils/translate'; 6 | 7 | interface IProps { 8 | platform: Platform; 9 | } 10 | 11 | export class BottomBar extends Component { 12 | public render(props: RenderableProps) { 13 | return ( 14 | 15 | 20 | 25 | 30 | {props.platform === Platform.EXTENSION && ( 31 | 36 | )} 37 | 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/copy/copy.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { CopyToClipboard } from 'react-copy-to-clipboard'; 3 | import { Translate } from '../translate/translate.component'; 4 | import Icon from 'preact-material-components/Icon'; 5 | 6 | import './copy.scss'; 7 | 8 | interface IProps { 9 | text: string; 10 | } 11 | 12 | interface IState { 13 | textCopied: boolean; 14 | } 15 | 16 | export class Copy extends Component { 17 | public notCopiedTimeout; 18 | 19 | constructor(props: IProps) { 20 | super(props); 21 | } 22 | 23 | public componentWillReceiveProps(nextPops) { 24 | if (this.props.text !== nextPops.text) { 25 | this.setState({ 26 | textCopied: false 27 | }); 28 | } 29 | } 30 | 31 | public render() { 32 | return ( 33 | { 36 | clearTimeout(this.notCopiedTimeout); 37 | this.notCopiedTimeout = setTimeout( 38 | () => this.setState({ textCopied: false }), 39 | 5000 40 | ); 41 | this.setState({ textCopied: true }); 42 | }} 43 | > 44 |
45 | {this.props.children || []} 46 |
51 | 58 | {this.state.textCopied ? 'check' : 'file_copy'} 59 |
60 |
61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/app/components/copy/copy.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/base'; 2 | 3 | .copy-component { 4 | opacity: 0.8; 5 | font-size: 14px; 6 | cursor: pointer; 7 | 8 | &.copied { 9 | color: $mdc-theme-secondary; 10 | } 11 | 12 | span { 13 | padding: 0 8px; 14 | } 15 | i { 16 | font-size: 14px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/create-password/create-password.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/base'; 2 | 3 | .create-wallet-step3 { 4 | color: rgba(0, 0, 0, 0.6); 5 | padding: 16px; 6 | 7 | .mdc-layout-grid__inner { 8 | margin-top: 20px; 9 | } 10 | 11 | .subtitle { 12 | margin-bottom: 48px; 13 | } 14 | 15 | .rules { 16 | margin: 32px 0; 17 | .ok { 18 | color: green; 19 | } 20 | 21 | .error { 22 | color: red; 23 | } 24 | 25 | p { 26 | font-size: 13px; 27 | line-height: 15px; 28 | } 29 | 30 | i.mdc-icon { 31 | line-height: 15px; 32 | font-size: 15px; 33 | float: left; 34 | margin-right: 6px; 35 | } 36 | } 37 | 38 | .back.mdc-button { 39 | margin-right: 8px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/components/currency-total/currency-total.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { ICurrency } from '../../data/currency/state'; 3 | 4 | interface IProps { 5 | amounts: Array<{ amount: number; coin: string }>; 6 | hideCurrency: boolean; 7 | 8 | conversionRates: ICurrency; 9 | preferredCurrency: string; 10 | } 11 | 12 | export class CurrencyTotal extends Component { 13 | public render() { 14 | let loading = false; 15 | const currency = this.props.preferredCurrency; 16 | let amount = 0; 17 | 18 | if (this.props.conversionRates) { 19 | this.props.amounts.map(balance => { 20 | if ( 21 | typeof balance.amount === 'number' && 22 | balance.amount >= 0 && 23 | this.props.conversionRates[balance.coin] && 24 | this.props.conversionRates[balance.coin][this.props.preferredCurrency] 25 | ) { 26 | amount += 27 | balance.amount * 28 | this.props.conversionRates[balance.coin][this.props.preferredCurrency]; 29 | } else { 30 | loading = true; 31 | } 32 | }); 33 | } else { 34 | loading = true; 35 | } 36 | 37 | return ( 38 | 39 | {loading && '...'} 40 | {!loading && {amount.toFixed(2)}}{' '} 41 | {!loading && !this.props.hideCurrency && {currency}} 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/components/currency-total/currency-total.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'preact-redux'; 2 | import { CurrencyTotal } from './currency-total.component'; 3 | import { IState } from '../../data'; 4 | 5 | const mapStateToProps = (state: IState, ownProps) => { 6 | return { 7 | ...ownProps, 8 | conversionRates: state.currency, 9 | preferredCurrency: state.userPreferences.preferredCurrency 10 | }; 11 | }; 12 | 13 | export default connect(mapStateToProps)(CurrencyTotal); 14 | -------------------------------------------------------------------------------- /src/app/components/currency/currency.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { ICurrency } from '../../data/currency/state'; 3 | 4 | interface IProps { 5 | amount: number; 6 | currency: string; 7 | convert?: boolean; 8 | hideCurrency?: boolean; 9 | 10 | conversionRates: ICurrency; 11 | preferredCurrency: string; 12 | } 13 | 14 | export class Currency extends Component { 15 | public render() { 16 | let { amount, currency } = this.props; 17 | let loading = false; 18 | 19 | if ( 20 | this.props.convert && 21 | this.props.conversionRates && 22 | this.props.conversionRates[this.props.currency] 23 | ) { 24 | currency = this.props.preferredCurrency; 25 | amount = 26 | amount * 27 | this.props.conversionRates[this.props.currency][this.props.preferredCurrency]; 28 | } else if (this.props.convert) { 29 | loading = true; 30 | } 31 | 32 | return ( 33 | 34 | {loading && '...'} 35 | {!loading && {this.props.convert ? amount.toFixed(2) : amount}}{' '} 36 | {!loading && !this.props.hideCurrency && {currency}} 37 | 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/currency/currency.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'preact-redux'; 2 | import { Currency } from './currency.component'; 3 | import { IState } from '../../data'; 4 | 5 | const mapStateToProps = (state: IState, ownProps) => { 6 | return { 7 | ...ownProps, 8 | conversionRates: state.currency, 9 | preferredCurrency: state.userPreferences.preferredCurrency 10 | }; 11 | }; 12 | 13 | export default connect(mapStateToProps)(Currency); 14 | -------------------------------------------------------------------------------- /src/app/components/list-item/list-item.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolandtech/moonlet/6b85117e9ef7b52091ba388adbedb92a67aa971b/src/app/components/list-item/list-item.component.scss -------------------------------------------------------------------------------- /src/app/components/list-item/list-item.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import List from 'preact-material-components/List'; 3 | 4 | interface IProps { 5 | icon?: any; 6 | iconProps?: {}; 7 | primaryText: string | JSX.Element; 8 | secondaryText?: string | JSX.Element; 9 | href?: string; 10 | target?: string; 11 | noDivider?: boolean; 12 | disabled?: boolean; 13 | className?: string; 14 | onClick?: (e?) => any; 15 | } 16 | 17 | export class ListItem extends Component { 18 | constructor(props: IProps) { 19 | super(props); 20 | } 21 | 22 | public getItemText(primary, secondary) { 23 | if (primary && secondary) { 24 | return ( 25 | 26 | {primary} 27 | {secondary} 28 | 29 | ); 30 | } else { 31 | return primary; 32 | } 33 | } 34 | 35 | public getListItem() { 36 | let ItemComponent = List.Item; 37 | if (!this.props.disabled && this.props.href) { 38 | ItemComponent = List.LinkItem as any; 39 | } 40 | 41 | let itemProps = {}; 42 | 43 | if (this.props.href) { 44 | itemProps = { 45 | onClick: this.props.onClick, 46 | href: this.props.href === '#' ? '' : this.props.href, 47 | target: this.props.target || '_self' 48 | }; 49 | } 50 | 51 | return ( 52 | 58 | {this.props.icon && ( 59 | 60 | {this.props.icon} 61 | 62 | )} 63 | {this.getItemText(this.props.primaryText, this.props.secondaryText)} 64 | {this.props.href && keyboard_arrow_right} 65 | {this.props.children && this.props.children} 66 | 67 | ); 68 | } 69 | 70 | public render() { 71 | return ( 72 |
73 | {this.getListItem()} {!this.props.noDivider && } 74 |
75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/components/material-components/bottom-navigation/bottom-navigation-action.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component, RenderableProps } from 'preact'; 2 | import Match, { Link } from 'preact-router/match'; 3 | 4 | interface IActionProps { 5 | icon: string; 6 | label?: string; 7 | active?: boolean; 8 | href: string; 9 | } 10 | export class BottomNavigationAction extends Component { 11 | public render(props: RenderableProps) { 12 | const activeClass = 'mdc-bottom-navigation__action--active'; 13 | return ( 14 | 15 | {({ matches }) => ( 16 |
  • 17 | 18 | 24 | 25 | {props.label && ( 26 | {props.label} 27 | )} 28 | 29 |
  • 30 | )} 31 |
    32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/material-components/bottom-navigation/bottom-navigation.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component, RenderableProps } from 'preact'; 2 | 3 | import './bottom-navigation.scss'; 4 | import { BottomNavigationAction } from './bottom-navigation-action.component'; 5 | 6 | export class BottomNavigation extends Component { 7 | public static readonly Action = BottomNavigationAction; 8 | 9 | public render(props: RenderableProps<{}>) { 10 | return ( 11 | 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/components/material-components/loader/loader.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | 3 | import './loader.scss'; 4 | 5 | interface IProps { 6 | width: string; 7 | height: string; 8 | className?: string; 9 | } 10 | 11 | export class Loader extends Component { 12 | public render() { 13 | let className = 'loader-component'; 14 | if (this.props.className) { 15 | className += ' ' + this.props.className; 16 | } 17 | return ( 18 | 25 | 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/components/material-components/loader/loader.scss: -------------------------------------------------------------------------------- 1 | // Here is where the magic happens 2 | 3 | $offset: 187; 4 | $duration: 1.4s; 5 | 6 | .loader-component { 7 | animation: loader-component-rotator $duration linear infinite; 8 | 9 | .path { 10 | stroke-dasharray: $offset; 11 | stroke-dashoffset: 0; 12 | transform-origin: center; 13 | animation: loader-component-dash $duration ease-in-out infinite, 14 | loader-component-colors ($duration * 4) ease-in-out infinite; 15 | } 16 | } 17 | 18 | @keyframes loader-component-rotator { 19 | 0% { 20 | transform: rotate(0deg); 21 | } 22 | 100% { 23 | transform: rotate(270deg); 24 | } 25 | } 26 | 27 | @keyframes loader-component-colors { 28 | 0% { 29 | stroke: #4285f4; 30 | } 31 | 25% { 32 | stroke: #de3e35; 33 | } 34 | 50% { 35 | stroke: #f7c223; 36 | } 37 | 75% { 38 | stroke: #1b9a59; 39 | } 40 | 100% { 41 | stroke: #4285f4; 42 | } 43 | } 44 | 45 | @keyframes loader-component-dash { 46 | 0% { 47 | stroke-dashoffset: $offset; 48 | } 49 | 50% { 50 | stroke-dashoffset: $offset/4; 51 | transform: rotate(135deg); 52 | } 53 | 100% { 54 | stroke-dashoffset: $offset; 55 | transform: rotate(450deg); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/components/testnet-warning/testnet-warning.component.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | 3 | import './testnet-warning.scss'; 4 | import Icon from 'preact-material-components/Icon'; 5 | import { translate } from '../../utils/translate'; 6 | import { GenericAccount } from 'moonlet-core/src/core/account'; 7 | 8 | interface IProps { 9 | account: GenericAccount; 10 | testNet: boolean; 11 | } 12 | 13 | export class TestnetWarning extends Component { 14 | public render() { 15 | const params = this.props.account 16 | ? { 17 | blockchain: this.props.account.node.blockchain, 18 | testnetName: this.props.account.node.network.name 19 | } 20 | : undefined; 21 | 22 | const content = ( 23 |
    24 | report_problem 25 |
    26 | {translate('App.labels.warning')}!{' '} 27 | {translate( 28 | params 29 | ? 'TestnetWarningComponent.specific' 30 | : 'TestnetWarningComponent.generic', 31 | params 32 | )}{' '} 33 | {translate('TestnetWarningComponent.goTo')} 34 |
    35 |
    36 | ); 37 | 38 | return this.props.testNet ? content : null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/components/testnet-warning/testnet-warning.container.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'preact-redux'; 2 | import { IState } from '../../data'; 3 | import { TestnetWarning } from './testnet-warning.component'; 4 | 5 | const mapStateToProps = (state: IState, ownProps) => { 6 | return { 7 | ...ownProps, 8 | testNet: state.userPreferences.testNet 9 | }; 10 | }; 11 | 12 | export default connect(mapStateToProps)(TestnetWarning); 13 | -------------------------------------------------------------------------------- /src/app/components/testnet-warning/testnet-warning.scss: -------------------------------------------------------------------------------- 1 | .testnet-warning-component { 2 | background-color: #fee94e; 3 | padding: 16px; 4 | font-size: 14px; 5 | line-height: 16px; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | 10 | .mdc-icon { 11 | color: rgba(red, 0.8); 12 | margin-right: 16px; 13 | } 14 | 15 | .text { 16 | flex: 1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/textarea-auto-size/textarea-auto-size.components.tsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import Textarea from 'react-textarea-autosize'; 3 | 4 | import './textarea-auto-size.scss'; 5 | 6 | interface IProps { 7 | outlined?: boolean; 8 | noBorder?: boolean; 9 | label?: string; 10 | value?: string; 11 | className?: string; 12 | placeholder?: string; 13 | disabled?: boolean; 14 | helperText?: string | JSX.Element; 15 | helperTextInside?: string | JSX.Element; 16 | helperTextValidationMsg?: boolean; 17 | 18 | inputRef?: (el) => any; 19 | onChange?: (e: any) => any; 20 | onBlur?: (e: any) => any; 21 | } 22 | 23 | interface IState { 24 | rows: number; 25 | } 26 | 27 | export class TextareaAutoSize extends Component { 28 | public textareaElement; 29 | 30 | constructor(props: IProps) { 31 | super(props); 32 | 33 | this.state = { 34 | rows: 1 35 | }; 36 | } 37 | 38 | public render() { 39 | let className = ''; 40 | if (this.props.outlined) { 41 | className = 'outlined'; 42 | } else if (this.props.noBorder) { 43 | className = 'no-border'; 44 | } 45 | 46 | let helperTextClassName = 47 | 'mdc-text-field-helper-text mdc-text-field-helper-text--persistent'; 48 | if (this.props.helperTextValidationMsg) { 49 | helperTextClassName += ' mdc-text-field-helper-text--validation-msg'; 50 | } 51 | 52 | return ( 53 |
    54 |
    55 | {this.props.label && {this.props.label}} 56 |