diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f257017c82..21807b05c0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,8 +4,6 @@ about: Create a report to help us improve --- - - ### Steps to reproduce diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 72213a153d..fec395aafe 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,7 @@ ### What issue have I solved? --- #INSERT_ISSUE_NUMBER +#INSERT_ISSUE_NUMBER ### How have I implemented/fixed it? diff --git a/Jenkinsfile b/Jenkinsfile index fa260a32b1..8270319a21 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { buildDiscarder(logRotator(numToKeepStr: '168', artifactNumToKeepStr: '5')) } environment { - LISK_CORE_VERSION = '1.3.0' + LISK_CORE_VERSION = '1.4.0-rc.0' } stages { stage('Install npm dependencies') { @@ -39,12 +39,8 @@ pipeline { npm run --silent build:testnet npm run --silent bundlesize - if [ -z $CHANGE_BRANCH ]; then - npm install - USE_SYSTEM_XORRISO=true npm run dist:linux - else - echo "Skipping desktop build for Linux because we're building a PR." - fi + npm install + USE_SYSTEM_XORRISO=true npm run dist:linux ''' } } @@ -119,7 +115,7 @@ pipeline { sed -i -r -e '/ports:/,+2d' docker-compose.yml # random port assignment cat <docker-compose.override.yml -version: "2" +version: "3" services: lisk: diff --git a/README.md b/README.md index 08c254b709..7a59cd254e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,12 @@ Then, in order to launch version with hardware wallet, you can run npm run dev-hardware-wallet ``` +In order to launch electron that gets live updates from already running webpack-dev-server on port 8080, you can run + +``` +LISK_HUB_URL="http://localhost:8080" npm run start +``` + #### Windows Build package for Windows (on Windows in [Git BASH](https://git-for-windows.github.io/)). diff --git a/app/package.json b/app/package.json index b3117da925..d165c17e9b 100644 --- a/app/package.json +++ b/app/package.json @@ -1,10 +1,21 @@ { "name": "lisk-hub", - "version": "1.8.0-beta.1", + "version": "1.9.0-rc.2", "description": "Lisk Hub", "main": "./build/main.js", "author": { "name": "Lisk Foundation", "email": "admin@lisk.io" + }, + "scripts": { + "postinstall": "electron-rebuild --force" + }, + "dependencies": { + "find-free-port": "2.0.0", + "node-hid": "0.7.2", + "usb": "1.3.3" + }, + "devDependencies": { + "electron-rebuild": "1.8.2" } } diff --git a/app/server.js b/app/server.js new file mode 100644 index 0000000000..432f2e50b5 --- /dev/null +++ b/app/server.js @@ -0,0 +1,33 @@ +const server = { + // eslint-disable-next-line max-statements + init: (port) => { + const express = require('express'); // eslint-disable-line + const Path = require('path'); + const bodyParser = require('body-parser'); // eslint-disable-line + + if (process.env.LISK_HUB_URL) { + return process.env.LISK_HUB_URL; + } + + const app = express(); + + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })); + + app.set('views', Path.join(__dirname, '.')); + app.use(express.static(Path.join(__dirname, '.'))); + + app.get('*', (req, res) => res.sendFile(Path.join(__dirname, 'index.html'))); + + // catch 404 and forward to error handler + app.use((req, res, next) => { + const err = new Error('Not Found'); + err.status = 404; + next(err); + }); + app.listen(port); + return `http://localhost:${port}/`; + }, +}; + +export default server; diff --git a/app/src/ledger.js b/app/src/ledger.js index 3dd95f351c..f5fcd330bc 100644 --- a/app/src/ledger.js +++ b/app/src/ledger.js @@ -1,19 +1,18 @@ import { app, ipcMain } from 'electron'; // eslint-disable-line import/no-extraneous-dependencies import Lisk from 'lisk-elements'; // eslint-disable-line import/no-extraneous-dependencies import { LedgerAccount, SupportedCoin, DposLedger } from 'dpos-ledger-api'; // eslint-disable-line import/no-extraneous-dependencies -import win from './modules/win'; +import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; // eslint-disable-line import/no-extraneous-dependencies +import win from './modules/win'; -// eslint-disable-next-line import/no-extraneous-dependencies -// TransportNodeHid = require('@ledgerhq/hw-transport-node-hid'); // mock transportnodehid, todo fix this to work also on windows and linux -const TransportNodeHid = { - open: () => {}, - listen: () => {}, - setListenDevicesDebounce: () => {}, - setListenDevicesPollingSkip: () => {}, -}; -TransportNodeHid.setListenDevicesDebounce(200); +// const TransportNodeHid = { +// open: () => {}, +// listen: () => {}, +// setListenDevicesDebounce: () => {}, +// setListenDevicesPollingSkip: () => {}, +// }; +// TransportNodeHid.setListenDevicesDebounce(200); /* TransportNodeHid.setListenDevicesDebug((msg, ...args) => { console.log(msg); @@ -50,7 +49,7 @@ const ledgerObserver = { next: async ({ device, type }) => { if (device) { if (type === 'add') { - if (process.platform === 'darwin' || await isInsideLedgerApp(device.path)) { + if (process.platform !== 'linux' || await isInsideLedgerApp(device.path)) { ledgerPath = device.path; win.send({ event: 'ledgerConnected', value: null }); } diff --git a/app/src/main.js b/app/src/main.js index b372860f02..237d1fb496 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -3,6 +3,7 @@ import electronLocalshortcut from 'electron-localshortcut'; // eslint-disable-li import { autoUpdater } from 'electron-updater'; // eslint-disable-line import/no-extraneous-dependencies import path from 'path'; import storage from 'electron-json-storage'; // eslint-disable-line import/no-extraneous-dependencies +import fp from 'find-free-port'; import win from './modules/win'; import './styles.dialog.css'; // import localeHandler from './modules/localeHandler'; @@ -11,6 +12,8 @@ import updateChecker from './modules/autoUpdater'; require('babel-polyfill'); // eslint-disable-line import/no-extraneous-dependencies require('./ledger'); +const defaultServerPort = 3000; + const checkForUpdates = updateChecker({ autoUpdater, dialog: electron.dialog, @@ -20,15 +23,21 @@ const checkForUpdates = updateChecker({ }); const { app, ipcMain } = electron; - let appIsReady = false; +const createWindow = (err, port) => { + if (err) { + throw new Error(err); + } + win.create({ + electron, path, electronLocalshortcut, storage, checkForUpdates, port, + }); +}; app.on('ready', () => { appIsReady = true; - win.create({ - electron, path, electronLocalshortcut, storage, checkForUpdates, - }); + + fp(defaultServerPort, createWindow); }); app.on('window-all-closed', () => { @@ -47,9 +56,7 @@ app.on('activate', () => { // sometimes, the event is triggered before app.on('ready', ...) // then creating new windows will fail if (win.browser === null && appIsReady) { - win.create({ - electron, path, electronLocalshortcut, storage, checkForUpdates, - }); + fp(defaultServerPort, createWindow); } }); @@ -90,6 +97,12 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => { global.myTempFunction(username, password); }); + +// ipcMain.on('ledgerConnected', () => { +// console.log('ledgerConnected'); +// store.dispatch({ type: actionTypes.connectHardwareWallet }); +// }); + // ToDo - enable this feature when it is implemented in the new design // ipcMain.on('set-locale', (event, locale) => { // const langCode = locale.substr(0, 2); diff --git a/app/src/modules/win.js b/app/src/modules/win.js index 02ce8f39f1..6488bec8d8 100644 --- a/app/src/modules/win.js +++ b/app/src/modules/win.js @@ -1,13 +1,19 @@ import localeHandler from './localeHandler'; import menu from './../menu'; import process from './process'; +import server from '../../server'; const win = { browser: null, eventStack: [], - init: ({ electron, path, electronLocalshortcut }) => { + init: ({ + electron, path, electronLocalshortcut, port, + }) => { const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; const { BrowserWindow } = electron; + + const url = server.init(port); + win.browser = new BrowserWindow({ width: width > 1680 ? 1680 : width, height: height > 1050 ? 1050 : height, @@ -28,16 +34,18 @@ const win = { win.browser.webContents.toggleDevTools(); }); - win.browser.loadURL(`file://${__dirname}/index.html`); + win.browser.loadURL(url); }, create: ({ // eslint-disable-line max-statements - electron, path, electronLocalshortcut, storage, checkForUpdates, + electron, path, electronLocalshortcut, storage, checkForUpdates, port, }) => { const { Menu } = electron; - win.init({ electron, path, electronLocalshortcut }); + win.init({ + electron, path, electronLocalshortcut, port, + }); localeHandler.send({ storage }); win.browser.on('blur', () => win.browser.webContents.send('blur')); @@ -118,4 +126,3 @@ const sendEventsFromEventStack = () => { export default win; - diff --git a/app/src/modules/win.test.js b/app/src/modules/win.test.js index 4622d59fc7..fa8b12ded0 100644 --- a/app/src/modules/win.test.js +++ b/app/src/modules/win.test.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; // eslint-disable-line import/no-extraneous-depen import { spy, mock } from 'sinon'; // eslint-disable-line import/no-extraneous-dependencies import win from './win'; import process from './process'; +import server from '../../server'; describe('Electron Browser Window Wrapper', () => { const callbacks = {}; @@ -41,11 +42,15 @@ describe('Electron Browser Window Wrapper', () => { }, app: { getName: () => ('Lisk Hub'), getVersion: () => ('some version') }, }; + const url = 'http://localhost:8080/'; let processMock; + let serverMock; beforeEach(() => { processMock = mock(process); + serverMock = mock(server); + serverMock.expects('init').returns(url); }); @@ -55,6 +60,7 @@ describe('Electron Browser Window Wrapper', () => { win.eventStack.length = 0; events.length = 0; processMock.restore(); + serverMock.restore(); }); describe('Init', () => { @@ -64,7 +70,7 @@ describe('Electron Browser Window Wrapper', () => { expect(win.browser.webPreferences.preload).to.equal('test'); expect(win.browser.center).to.equal(true); expect(win.browser.devtools).to.equal(true); - expect(win.browser.loadURL).to.have.been.calledWith(`file://${__dirname}/index.html`); + expect(win.browser.loadURL).to.have.been.calledWith(url); }); it('Creates the window of maximum size possible size on < 1680X1050 display', () => { diff --git a/config/setupJest.js b/config/setupJest.js index e136dd39bc..469a6507d7 100644 --- a/config/setupJest.js +++ b/config/setupJest.js @@ -8,6 +8,8 @@ import sinonStubPromise from 'sinon-stub-promise'; import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import i18next from 'i18next'; +import ReactPiwik from 'react-piwik'; +import crypto from 'crypto'; // TODO remove next line after upgrading node version to at least 7 import 'es7-object-polyfill'; @@ -46,6 +48,17 @@ Object.defineProperty(window, 'localStorage', { value: localStorageMock, }); +Object.defineProperty(window, 'crypto', { + value: { + getRandomValues: arr => crypto.randomBytes(arr.length), + }, +}); + +ReactPiwik.push = () => {}; +ReactPiwik.trackingEvent = () => {}; +sinon.stub(ReactPiwik.prototype, 'connectToHistory').callsFake(() => 1); +sinon.stub(ReactPiwik.prototype, 'initPiwik').callsFake(() => {}); + // https://github.com/nkbt/react-copy-to-clipboard/issues/20#issuecomment-414065452 // Polyfill window prompts to always confirm. Needed for react-copy-to-clipboard to work. global.prompt = () => true; @@ -60,4 +73,3 @@ const getSelection = () => ({ }); window.getSelection = getSelection; document.getSelection = getSelection; - diff --git a/config/webpack.config.js b/config/webpack.config.js index 5bde208d3e..29fe1e4e44 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -38,7 +38,7 @@ module.exports = { }, }, { - test: /\.(eot|svg|ttf|woff|woff2)$/, + test: /\.(eot|svg|ttf|woff|woff2|otf)$/, exclude: [/images/], options: { name: '[path][name]-[hash:6].[ext]', diff --git a/i18n/locales/en/common.json b/i18n/locales/en/common.json index 9c639e48dd..b73aa03c0a 100644 --- a/i18n/locales/en/common.json +++ b/i18n/locales/en/common.json @@ -36,6 +36,7 @@ "Auto-logout": "Auto-logout", "Back": "Back", "Back to overview": "Back to overview", + "Backup your Passphrase": "Backup your Passphrase", "Balance": "Balance", "Become a delegate": "Become a delegate", "Become a delegate (Fee: {{fee}} LSK)": "Become a delegate (Fee: {{fee}} LSK)", @@ -52,7 +53,9 @@ "Choose Network": "Choose Network", "Choose a name": "Choose a name", "Choose an ID": "Choose an ID", + "Choose the rights word missing from your Passphrase.": "Choose the rights word missing from your Passphrase.", "Choose which feeds to display.": "Choose which feeds to display.", + "Choose your Avatar": "Choose your Avatar", "Choose your name": "Choose your name", "Click here to go to the main page of Lisk Hub and find all relevant statistics and ID information": "Click here to go to the main page of Lisk Hub and find all relevant statistics and ID information", "Click here to skip": "Click here to skip", @@ -68,20 +71,27 @@ "Confirm transfer": "Confirm transfer", "Confirm vote on Ledger Nano S": "Confirm vote on Ledger Nano S", "Confirm your name": "Confirm your name", + "Confirm your passphrase": "Confirm your passphrase", "Confirmation in the next step": "Confirmation in the next step", "Confirmations": "Confirmations", "Connected to ": "Connected to ", + "Connected to:": "Connected to:", "Connecting to network": "Connecting to network", "Connection re-established": "Connection re-established", + "Continue": "Continue", "Continue to Dashboard": "Continue to Dashboard", "Copied!": "Copied!", "Copy": "Copy", "Copy Transaction ID to clipboard": "Copy Transaction ID to clipboard", + "Copy passphrase": "Copy passphrase", "Copy to Clipboard": "Copy to Clipboard", "Could not reach the network. Please try again.": "Could not reach the network. Please try again.", "Create": "Create", + "Create First Transaction": "Create First Transaction", "Create Lisk ID": "Create Lisk ID", "Create a Lisk ID to gain access to all services.": "Create a Lisk ID to gain access to all services.", + "Create an Account": "Create an Account", + "Create an account or sign in to manage your LSK, become a delegate or vote for another delegates.": "Create an account or sign in to manage your LSK, become a delegate or vote for another delegates.", "Create your Lisk ID": "Create your Lisk ID", "Create your second passphrase": "Create your second passphrase", "Currency": "Currency", @@ -103,7 +113,9 @@ "Done": "Done", "Don’t be a stranger! Connect with our worldwide community.": "Don’t be a stranger! Connect with our worldwide community.", "Dowload started": "Dowload started", + "Download PDF": "Download PDF", "Drag to reveal": "Drag to reveal", + "Each Avatar is a visual representation of the address, making it unique.": "Each Avatar is a visual representation of the address, making it unique.", "Each LSK token is worth one vote.": "Each LSK token is worth one vote.", "Edit": "Edit", "Enter IP or domain address of the node": "Enter IP or domain address of the node", @@ -117,6 +129,7 @@ "Error during login with Ledger.": "Error during login with Ledger.", "Error on Ledger Connection. Be sure your device is connected properly": "Error on Ledger Connection. Be sure your device is connected properly", "Explain Blockchain Like I'm 5": "Explain Blockchain Like I'm 5", + "Explore as a Guest": "Explore as a Guest", "Explore the network": "Explore the network", "Failed to connect to node": "Failed to connect to node", "Failed to connect: Node {{address}} is not active": "Failed to connect: Node {{address}} is not active", @@ -125,15 +138,20 @@ "Filter": "Filter", "Filter votes": "Filter votes", "Final confirmation": "Final confirmation", + "For more information refer to our ": "For more information refer to our ", "Forged": "Forged", "Get passphrase": "Get passphrase", "Get to your Dashboard": "Get to your Dashboard", + "Give feedback about this feature": "Give feedback about this feature", + "Go Back": "Go Back", "Go back to Dashboard": "Go back to Dashboard", "Go to \"Help\" on your Sidebar to start the onboarding whenever you need.": "Go to \"Help\" on your Sidebar to start the onboarding whenever you need.", "Go to settings": "Go to settings", "Go to your Dashboard": "Go to your Dashboard", "Got it": "Got it", "Great!\nYou’re almost done": "Great!\nYou’re almost done", + "Hardware login (beta): ": "Hardware login (beta): ", + "Hardware wallet login (beta):": "Hardware wallet login (beta):", "Height": "Height", "Help": "Help", "Help Center": "Help Center", @@ -143,6 +161,7 @@ "How is Lisk transparent?": "How is Lisk transparent?", "How it works": "How it works", "How would you call it?": "How would you call it?", + "Hub enables you to customize a link. Once shared with your peers,\n this link can be used to open any Lisk application and pre-fill the data for you.": "Hub enables you to customize a link. Once shared with your peers,\n this link can be used to open any Lisk application and pre-fill the data for you.", "I am responsible for keeping my passphrase safe. No one can reset it, not even Lisk.": "I am responsible for keeping my passphrase safe. No one can reset it, not even Lisk.", "I confirm (Fee: 5 LSK)": "I confirm (Fee: 5 LSK)", "I understand": "I understand", @@ -160,6 +179,9 @@ "Invalid amount": "Invalid amount", "Is Blockchain Secure?": "Is Blockchain Secure?", "It is recommended that you initialize your Lisk ID.": "It is recommended that you initialize your Lisk ID.", + "It was given in a previous step": "It was given in a previous step", + "It will cost you only the usual {{fee}} LSK transaction fee.": "It will cost you only the usual {{fee}} LSK transaction fee.", + "Keep it safe is only way to access the wallet": "Keep it safe is only way to access the wallet", "Keep the overview": "Keep the overview", "Keep track of any Lisk ID balance. Only you will see who you bookmarked.": "Keep track of any Lisk ID balance. Only you will see who you bookmarked.", "LSK received": "LSK received", @@ -206,6 +228,7 @@ "Names are unique. Once you register a name, it can't be changed.": "Names are unique. Once you register a name, it can't be changed.", "Network to connect to": "Network to connect to", "New to Hub? Take a tour": "New to Hub? Take a tour", + "New to Lisk? ": "New to Lisk? ", "New version available": "New version available", "News": "News", "Next": "Next", @@ -229,6 +252,10 @@ "Out": "Out", "Outgoing": "Outgoing", "Page not found.": "Page not found.", + "Paper version": "Paper version", + "Passphrase": "Passphrase", + "Passphrase is both ": "Passphrase is both ", + "Passphrase is both your login and password combined": "Passphrase is both your login and password combined", "Passphrase is not valid": "Passphrase is not valid", "Passphrase should have 12 words, entered passphrase has {{length}}": "Passphrase should have 12 words, entered passphrase has {{length}}", "Password": "Password", @@ -245,6 +272,7 @@ "Please use the last not-initialized account before creating a new one!": "Please use the last not-initialized account before creating a new one!", "Previous": "Previous", "Price data currently not available": "Price data currently not available", + "Privacy Policy": "Privacy Policy", "Productivity": "Productivity", "Proxy Authentication": "Proxy Authentication", "Quit": "Quit", @@ -276,7 +304,7 @@ "Retry": "Retry", "Safekeeping": "Safekeeping", "Search": "Search", - "Search for Lisk ID or Transaction ID": "Search for Lisk ID or Transaction ID", + "Search for Lisk ID, Delegate or Transaction ID": "Search for Lisk ID, Delegate or Transaction ID", "Search for a delegate": "Search for a delegate", "Search for answers in our extensive ": "Search for answers in our extensive ", "Search for delegate, Lisk ID, transaction ID": "Search for delegate, Lisk ID, transaction ID", @@ -296,6 +324,7 @@ "Send LSK": "Send LSK", "Send Lisk from Blockchain Application": "Send Lisk from Blockchain Application", "Send Lisk to Blockchain Application": "Send Lisk to Blockchain Application", + "Send anonymous usage statistics": "Send anonymous usage statistics", "Send request via E-mail": "Send request via E-mail", "Send to Address": "Send to Address", "Send to address": "Send to address", @@ -306,18 +335,26 @@ "Set max. amount": "Set max. amount", "Set up Lisk Hub and your account.": "Set up Lisk Hub and your account.", "Settings": "Settings", + "Show Less": "Show Less", + "Show More": "Show More", "Show more": "Show more", "Show passphrase": "Show passphrase", "Sidechains": "Sidechains", "Sidechains will revolutionize the way decentralized apps are developed. Here you will be able to find hosts, and monitor your sidechains soon.": "Sidechains will revolutionize the way decentralized apps are developed. Here you will be able to find hosts, and monitor your sidechains soon.", + "Sign In": "Sign In", "Sign a message": "Sign a message", "Sign back in": "Sign back in", "Sign in": "Sign in", + "Sign in with a Passphrase": "Sign in with a Passphrase", "Sorry": "Sorry", "Standby": "Standby", "Start here": "Start here", "Start the tour": "Start the tour", "Stats": "Stats", + "Step 1 / 4": "Step 1 / 4", + "Step 2 / 4": "Step 2 / 4", + "Step 3 / 4": "Step 3 / 4", + "Step 4 / 4": "Step 4 / 4", "Submit": "Submit", "Success": "Success", "Success!": "Success!", @@ -329,6 +366,7 @@ "The Lisk Academy is an entirely free, unbiased and comprehensive educational platform about blockchain technology, containing something for everyone, regardless of what level of knowledge you are at.": "The Lisk Academy is an entirely free, unbiased and comprehensive educational platform about blockchain technology, containing something for everyone, regardless of what level of knowledge you are at.", "The Wallet will show your recent transactions.": "The Wallet will show your recent transactions.", "The download has started. Depending on your internet speed, it can take several minutes. You will be informed when it is finished and be prompted to restart the app.": "The download has started. Depending on your internet speed, it can take several minutes. You will be informed when it is finished and be prompted to restart the app.", + "The easiest way to do this is to send LSK to yourself by clicking this button.": "The easiest way to do this is to send LSK to yourself by clicking this button.", "The easiest way to do this is to send LSK to yourself. It will cost you only the usual {{fee}} LSK transaction fee.": "The easiest way to do this is to send LSK to yourself. It will cost you only the usual {{fee}} LSK transaction fee.", "There are no {{filterName}} transactions.": "There are no {{filterName}} transactions.", "This helps to keep the network fair, open and honest.": "This helps to keep the network fair, open and honest.", @@ -355,6 +393,8 @@ "Try Again": "Try Again", "Try again": "Try again", "Try using menu for navigation.": "Try using menu for navigation.", + "Type at least 3 characters": "Type at least 3 characters", + "Type or insert your passphrase": "Type or insert your passphrase", "URL is invalid": "URL is invalid", "Unable to connect to the node": "Unable to connect to the node", "Unable to connect to the node, no response from the server.": "Unable to connect to the node, no response from the server.", @@ -383,11 +423,14 @@ "Voting": "Voting", "Wallet": "Wallet", "We recommend including date & time or a specific keyword.": "We recommend including date & time or a specific keyword.", + "We strongly recommend to store passphrase in a safe place.\n You can use a password manager or paperwallet.": "We strongly recommend to store passphrase in a safe place.\n You can use a password manager or paperwallet.", "Welcome back": "Welcome back", "Welcome to Lisk Hub": "Welcome to Lisk Hub", + "Welcome to the Lisk Hub!": "Welcome to the Lisk Hub!", "What is Lisk Academy?": "What is Lisk Academy?", "What is Lisk ID?": "What is Lisk ID?", "What is a Lisk ID?": "What is a Lisk ID?", + "What is passphrase?": "What is passphrase?", "What's New...": "What's New...", "Who voted for this delegate": "Who voted for this delegate", "Why should I vote?": "Why should I vote?", @@ -398,10 +441,13 @@ "You are looking into a saved account. In order to {{nextAction}} you need to enter your passphrase.": "You are looking into a saved account. In order to {{nextAction}} you need to enter your passphrase.", "You are responsible for safekeeping your second passphrase. No one can restore it, not even Lisk.": "You are responsible for safekeeping your second passphrase. No one can restore it, not even Lisk.", "You can customize amount & message.": "You can customize amount & message.", + "You can now manage and secure your LSK tokens.": "You can now manage and secure your LSK tokens.", "You can now use Lisk Hub. If you want to repeat the onboarding, navigate to \"Help\" on the sidebar." }, + "You can print your passphrase to store in a safe place.\n It is highly recommended to delete PDF file after printing.": "You can print your passphrase to store in a safe place.\n It is highly recommended to delete PDF file after printing.", "You can share your Lisk ID with anyone you wish, but never reveal your passphrase to anyone as it would allow full access to your account.": "You can share your Lisk ID with anyone you wish, but never reveal your passphrase to anyone as it would allow full access to your account.", + "You can use ": "You can use ", "You can use your passphrase to sign a message. ": "You can use your passphrase to sign a message. ", "You have already registered as a delegate.": "You have already registered as a delegate.", "You have cancelled the transaction on your hardware wallet. You can either continue or retry.": "You have cancelled the transaction on your hardware wallet. You can either continue or retry.", @@ -412,6 +458,7 @@ "You will need it to use your Lisk ID, like sending and voting. You are responsible for keeping your second passphrase safe. No one can restore it, not even Lisk.": "You will need it to use your Lisk ID, like sending and voting. You are responsible for keeping your second passphrase safe. No one can restore it, not even Lisk.", "You will send a small amount of {{fee}} LSK to yourself and therefore initialize your ID.": "You will send a small amount of {{fee}} LSK to yourself and therefore initialize your ID.", "You've received {{value}} LSK.": "You've received {{value}} LSK.", + "Your Account is created!": "Your Account is created!", "Your Lisk ID is how you recognize and interact with your unique Lisk account, think of it as your email.": "Your Lisk ID is how you recognize and interact with your unique Lisk account, think of it as your email.", "Your delegate name is being registered": "Your delegate name is being registered", "Your passphrase is used to access your Lisk ID.": "Your passphrase is used to access your Lisk ID.", @@ -429,10 +476,15 @@ "ca.": "ca.", "ca. {{price}} {{currency}}": "ca. {{price}} {{currency}}", "checking availability": "checking availability", + "combined. You saved your passphrase when registering your account.": "combined. You saved your passphrase when registering your account.", "for full access": "for full access", "if the problem persists": "if the problem persists", "missed": "missed", + "or": "or", + "tab or space ": "tab or space ", + "to go to the next field.": "to go to the next field.", "with a second passphrase": "with a second passphrase", + "your login and passphrase ": "your login and passphrase ", "{{count}} delegate(s) selected to unvote": "{{count}} delegate selected to unvote", "{{count}} delegate(s) selected to unvote_plural": "{{count}} delegates selected to unvote", "{{count}} delegate(s) selected to vote": "{{count}} delegate selected to vote", diff --git a/jest.config.js b/jest.config.js index ff6dd50d2a..ff4ad03c94 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,6 +6,7 @@ module.exports = { '/test/integration/*.test.js', ], testPathIgnorePatterns: [ + '/test/integration/wallet.test.js', 'src/actions/transactions.test.js', 'src/components/dashboard/currencyGraph.test.js', // This should be unskipped in issue #1499 'src/components/errorBoundary/index.test.js', @@ -18,7 +19,6 @@ module.exports = { 'src/components/voteUrlProcessor/index.test.js', 'src/store/middlewares/login.test.js', 'src/store/reducers/liskService.test.js', - '/test/integration/wallet.test.js', ], verbose: true, cache: false, @@ -52,12 +52,13 @@ module.exports = { 'src/components/newsFeed/news.js', 'src/components/passphrase/create/create.js', 'src/components/passphraseCreation/index.js', - 'src/components/passphraseSteps/index.js', - 'src/components/register/register.js', - 'src/components/request/specifyRequest.js', + 'src/components/passphraseSteps/index.js', // FollowUp #1515 'src/components/receive/index.js', - 'src/components/request/index.js', 'src/components/register/register.js', + 'src/components/register/register.js', + 'src/components/request/index.js', + 'src/components/request/index.js', + 'src/components/request/specifyRequest.js', 'src/components/resultBox/index.js', 'src/components/searchBar/index.js', // Passing in mocha but not in Jest 'src/components/send/steps/form/stories.js', diff --git a/package-lock.json b/package-lock.json index 294cafbe75..5fd158528e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,14 @@ { "name": "lisk-hub", - "version": "1.9.0", + "version": "1.9.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { + "9": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/9/-/9-0.0.1.tgz", + "integrity": "sha1-JXFwB5WURfk1HtU+h/U8Iohesa4=" + }, "7zip-bin": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-4.1.0.tgz", @@ -1029,7 +1034,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1077,7 +1082,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -1246,9 +1251,9 @@ } }, "@ledgerhq/hw-transport-node-hid": { - "version": "4.32.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.32.0.tgz", - "integrity": "sha512-uZO+52TBxaYVhGIULHbhvyZWx+uLjvflDwfLicQR1eYxbYACB6xLXwq4sz9gL5He966t637Zx9iPsLi87rtqvQ==", + "version": "4.33.3", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.33.3.tgz", + "integrity": "sha512-hmNAm7k385RJXY38hVUpzYgGgyk9QjScD3erNlFCTO8FnnxmEJCFUmVhWkv4sTwufuUJSpXL3ZXXNZ44qLMJpg==", "requires": { "@ledgerhq/hw-transport": "^4.32.0", "lodash": "^4.17.11", @@ -2189,9 +2194,9 @@ "dev": true }, "@types/sinon": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.0.0.tgz", - "integrity": "sha512-cuK4xM8Lg2wd8cxshcQa8RG4IK/xfyB6TNE6tNVvkrShR4xdrYgsV04q6Dp6v1Lp6biEFdzD8k8zg/ujQeiw+A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.0.tgz", + "integrity": "sha512-kcYoPw0uKioFVC/oOqafk2yizSceIQXCYnkYts9vJIwQklFRsMubTObTDrjQamUyBRd47332s85074cd/hCwxg==", "dev": true }, "@types/sinon-chai": { @@ -2435,7 +2440,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, "requires": { "mime-types": "~2.1.18", "negotiator": "0.6.1" @@ -2899,8 +2903,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-from": { "version": "2.1.1", @@ -5111,7 +5114,8 @@ "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true }, "batch": { "version": "0.6.1", @@ -5174,9 +5178,9 @@ "dev": true }, "bindings": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", - "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" }, "bip32-path": { "version": "0.4.2", @@ -5271,7 +5275,6 @@ "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, "requires": { "bytes": "3.0.0", "content-type": "~1.0.4", @@ -5289,7 +5292,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -5297,8 +5299,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" } } }, @@ -5560,15 +5561,6 @@ "node-int64": "^0.4.0" } }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -6789,14 +6781,12 @@ "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { "version": "1.6.0", @@ -6810,14 +6800,12 @@ "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "copy-concurrently": { "version": "1.0.5", @@ -7013,12 +7001,9 @@ } }, "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - } + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", + "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=" }, "create-ecdh": { "version": "4.0.3", @@ -7503,9 +7488,9 @@ "dev": true }, "cypress": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.1.3.tgz", - "integrity": "sha512-ZusTQffKBVrLDvcxEinymTH0iCUL7hM1m6q9X+557wDtpd6S4et330QQE1IW10Pnyp+vYIHpkWxDm43B9G14nA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.1.4.tgz", + "integrity": "sha512-8VJYtCAFqHXMnRDo4vdomR2CqfmhtReoplmbkXVspeKhKxU8WsZl0Nh5yeil8txxhq+YQwDrInItUqIm35Vw+g==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "0.4.1", @@ -7518,7 +7503,7 @@ "@types/lodash": "4.14.87", "@types/minimatch": "3.0.3", "@types/mocha": "2.2.44", - "@types/sinon": "4.0.0", + "@types/sinon": "7.0.0", "@types/sinon-chai": "2.7.29", "bluebird": "3.5.0", "cachedir": "1.3.0", @@ -7537,7 +7522,7 @@ "is-installed-globally": "0.1.0", "lazy-ass": "1.6.0", "listr": "0.12.0", - "lodash": "4.17.10", + "lodash": "4.17.11", "log-symbols": "2.2.0", "minimist": "1.2.0", "moment": "2.22.2", @@ -7691,12 +7676,6 @@ "graceful-fs": "^4.1.6" } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, "moment": { "version": "2.22.2", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", @@ -8113,8 +8092,7 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-indent": { "version": "5.0.0", @@ -8355,9 +8333,9 @@ } }, "dpos-ledger-api": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/dpos-ledger-api/-/dpos-ledger-api-1.1.0.tgz", - "integrity": "sha512-CzYwl/DG41kTIiCGbaMbxpIp50V6R/Di96lZ4IilEVfEQK4yCGbhKHeNW7dCwa2/Qd3C4pvqkMNDmiYm5Wew1w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dpos-ledger-api/-/dpos-ledger-api-2.0.0.tgz", + "integrity": "sha512-WQ3TuemxfHdgsn+YgH39CosCnB5FpF31UiiEQpYKbtczsoJPWruaxTO3l+cfk8eyOB+rYkabQr40rooDkTTacQ==", "requires": { "bip32-path": "^0.4.2", "crc": "^3.5.0" @@ -8440,8 +8418,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { "version": "2.6.1", @@ -8759,6 +8736,106 @@ } } }, + "electron-rebuild": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.8.2.tgz", + "integrity": "sha512-EeR4dgb6NN7ybxduUWMeeLhU/EuF+FzwFZJfMJXD0bx96K+ttAieCXOn9lTO5nA9Qn3hiS7pEpk8pZ9StpGgSg==", + "dev": true, + "requires": { + "colors": "^1.2.0", + "debug": "^2.6.3", + "detect-libc": "^1.0.3", + "fs-extra": "^3.0.1", + "node-abi": "^2.0.0", + "node-gyp": "^3.6.0", + "ora": "^1.2.0", + "rimraf": "^2.6.1", + "spawn-rx": "^2.0.10", + "yargs": "^7.0.2" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cli-spinners": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", + "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", + "dev": true + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "ora": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", + "integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "cli-cursor": "^2.1.0", + "cli-spinners": "^1.0.1", + "log-symbols": "^2.1.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, "electron-to-chromium": { "version": "1.3.91", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.91.tgz", @@ -8824,8 +8901,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "encoding": { "version": "0.1.12", @@ -9117,8 +9193,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", @@ -9684,8 +9759,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-emitter": { "version": "0.3.5", @@ -9921,7 +9995,6 @@ "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", - "dev": true, "requires": { "accepts": "~1.3.5", "array-flatten": "1.1.1", @@ -9958,20 +10031,17 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -10416,7 +10486,6 @@ "version": "1.1.1", "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -10430,8 +10499,7 @@ "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -10445,6 +10513,11 @@ "pkg-dir": "^3.0.0" } }, + "find-free-port": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-free-port/-/find-free-port-2.0.0.tgz", + "integrity": "sha1-SyLl9leesaOMQaxryz7+0bbamxs=" + }, "find-index": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", @@ -10550,8 +10623,7 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fragment-cache": { "version": "0.2.1", @@ -10570,8 +10642,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "from2": { "version": "2.3.0", @@ -12437,7 +12508,8 @@ "ieee754": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true }, "iferr": { "version": "0.1.5", @@ -12776,8 +12848,7 @@ "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", - "dev": true + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "is-absolute": { "version": "1.0.0", @@ -14972,7 +15043,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15013,7 +15084,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, @@ -15055,7 +15126,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15093,7 +15164,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -15119,7 +15190,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -15167,7 +15238,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -15447,7 +15518,7 @@ "dependencies": { "ansi-escapes": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", "dev": true }, @@ -15689,8 +15760,7 @@ "media-typer": { "version": "0.3.0", "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { "version": "4.0.0", @@ -15713,6 +15783,12 @@ "readable-stream": "^2.0.1" } }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, "meow": { "version": "3.7.0", "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -15781,8 +15857,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { "version": "1.0.1", @@ -15802,8 +15877,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { "version": "3.1.10", @@ -15847,8 +15921,7 @@ "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { "version": "1.37.0", @@ -15935,18 +16008,18 @@ } }, "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.0.tgz", + "integrity": "sha512-jWC2Eg+Np4bxah7llu1IrUNSJQxtLz/J+pOjTM0nFpJXGAaV18XBWhUn031Q1tAA/TJtA1jgwnOe9S2PQa4Lbg==", "requires": { - "safe-buffer": "^5.1.2", + "safe-buffer": "^5.1.1", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "requires": { "minipass": "^2.2.1" } @@ -16182,8 +16255,7 @@ "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "neo-async": { "version": "2.6.0", @@ -16539,19 +16611,80 @@ } }, "npm-bundled": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", - "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" }, "npm-packlist": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz", - "integrity": "sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" } }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -16777,7 +16910,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, "requires": { "ee-first": "1.1.1" } @@ -16886,7 +17018,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -16924,7 +17056,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -17228,8 +17360,7 @@ "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, "pascalcase": { "version": "0.1.1", @@ -17344,6 +17475,12 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -21752,8 +21889,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" }, "process": { "version": "0.5.2", @@ -21901,7 +22037,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", - "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.8.0" @@ -22140,8 +22275,7 @@ "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raw-body": { "version": "2.3.3", @@ -22525,6 +22659,11 @@ "warning": "^3.0.0" } }, + "react-piwik": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-piwik/-/react-piwik-1.6.0.tgz", + "integrity": "sha512-ANK/SDDA3z827vcY8w77tTC8pOkpMSw1xNs5ifImho92oNS1rIfiTdVXNW7TpqP3a8hU+p9AxPAgPZhmkYtyzw==" + }, "react-redux": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", @@ -23834,7 +23973,6 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -23854,8 +23992,7 @@ "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -23911,7 +24048,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -24355,6 +24491,26 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "snyk-nodejs-lockfile-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.9.0.tgz", + "integrity": "sha512-GRn70VDe+JISkRbnxc9vxCBV+Ekkdr79krVXbYNDJgQyIjH+FXh6PXVvpregVsvCcNqP1ctbBw/u1w6e9xX1QA==", + "requires": { + "@yarnpkg/lockfile": "^1.0.2", + "graphlib": "^2.1.5", + "lodash": "4.17.10", + "source-map-support": "^0.5.7", + "tslib": "^1.9.3", + "uuid": "^3.3.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + } + } } } }, @@ -24457,9 +24613,9 @@ "integrity": "sha512-TBrdcFXHdYuRYFCvpyUeFC+mCi6SOV3vdxgHrP7JRNnJwO8PYaKCObLJyhpRWa8IaHv/8CjJTmnEbWIh7BPHAA==" }, "snyk-nodejs-lockfile-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.9.0.tgz", - "integrity": "sha512-GRn70VDe+JISkRbnxc9vxCBV+Ekkdr79krVXbYNDJgQyIjH+FXh6PXVvpregVsvCcNqP1ctbBw/u1w6e9xX1QA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.9.1.tgz", + "integrity": "sha512-LEIljxziLofgMxdoEHYBK86uyCD+EEmF/l6tbus7qc1Pj0ouCho3Eeh9P1Nvu3hJR5BIt9nKbkmbhmYy3EauHQ==", "requires": { "@yarnpkg/lockfile": "^1.0.2", "graphlib": "^2.1.5", @@ -24854,6 +25010,34 @@ "co": "^4.6.0" } }, + "spawn-rx": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz", + "integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==", + "dev": true, + "requires": { + "debug": "^2.5.1", + "lodash.assign": "^4.2.0", + "rxjs": "^5.1.1" + }, + "dependencies": { + "rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + } + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -25858,14 +26042,14 @@ "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==" }, "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.2.tgz", + "integrity": "sha512-BfkE9CciGGgDsATqkikUHrQrraBCO+ke/1f6SFAEMnxyyfN9lxC+nW1NFWMpqH865DhHIy9vQi682gk1X7friw==", "requires": { - "chownr": "^1.1.1", + "chownr": "^1.0.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.2" @@ -26383,7 +26567,6 @@ "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.18" @@ -26960,8 +27143,7 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { "version": "3.3.2", @@ -27000,8 +27182,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "velocity-animate": { "version": "1.5.2", @@ -28192,9 +28373,9 @@ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" }, "yargs": { "version": "3.32.0", diff --git a/package.json b/package.json index df0b6f709f..9d7ca1292f 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "lisk-hub", - "version": "1.8.0-beta.1", + "version": "1.9.0-rc.2", "description": "Lisk Hub", "homepage": "https://github.com/LiskHQ/lisk-hub", "bugs": "https://github.com/LiskHQ/lisk-hub/issues", "main": "main.js", "scripts": { - "postinstall": "node ./config/checkNodeVersion.js", - "build": "npm run clean-build && npm run copy-files && npm run build-prod && npm run build-electron", + "postinstall": "npm-run-all check-node-version", + "electron-rebuild": "electron-rebuild --force", + "check-node-version": "node ./config/checkNodeVersion.js", + "install-electron-dependencies": "cd ./app && npm install && cd ..", + "build": "npm-run-all clean-build copy-files build-prod build-electron", "build:testnet": "npm run build:testnet:pre", "build:testnet:pre": "cpx \"./app/build/**\" ./app/build-testnet && npm run build:testnet:post", "build:testnet:post": "replace '\"mainnet\";//defaultNetwork' '\"testnet\";//defaultNetwork' ./app/build-testnet/index.html ", @@ -31,7 +34,7 @@ "clean-dist": "rm -rf dist", "eslint": "eslint ./", "pack": "npm install && npm run build && npm run clean-dist && npm run dist", - "pack:win": "cmd /c npm install && npm run clean-build && npm run copy-files && npm run build-prod && npm run build-electron && npm run clean-dist && npm run dist:win", + "pack:win": "cmd /c npm install && npm run install-electron-dependencies && npm run clean-build && npm run copy-files && npm run build-prod && npm run build-electron && npm run clean-dist && npm run dist:win", "storybook": "start-storybook -p 6006 -s ./src/", "build-storybook": "build-storybook", "serve": "http-server", @@ -45,22 +48,27 @@ "url": "https://github.com/LiskHQ/lisk-hub" }, "dependencies": { - "@ledgerhq/hw-transport-node-hid": "4.32.0", + "9": "0.0.1", + "@ledgerhq/hw-transport-node-hid": "4.33.3", "@ledgerhq/hw-transport-u2f": "4.32.0", "await-to-js": "2.1.1", "bignumber.js": "8.0.1", "bitcore-mnemonic": "1.7.0", + "body-parser": "1.18.3", "browser-or-node": "1.1.0", "chart.js": "2.7.3", "cheerio": "1.0.0-rc.2", - "dpos-ledger-api": "1.1.0", + "dpos-ledger-api": "2.0.0", "electron-localshortcut": "3.1.0", + "express": "4.16.4", + "find-free-port": "2.0.0", "flexboxgrid": "=6.3.1", "hard-source-webpack-plugin": "0.13.1", "history": "=4.7.2", "i18next": "12.1.0", "i18next-localstorage-cache": "=1.1.1", "i18next-xhr-backend": "1.5.1", + "inflight": "1.0.6", "is-electron": "2.2.0", "js-sha256": "0.9.0", "lisk-elements": "1.1.3", @@ -74,6 +82,7 @@ "popsicle": "9.1.0", "postcss": "6.0.12", "postcss-cssnext": "2.11.0", + "private": "0.1.8", "prop-types": "15.6.2", "qrcode.react": "0.8.0", "react": "16.2.0", @@ -86,6 +95,7 @@ "react-dom": "16.2.1", "react-i18next": "=6.0.6", "react-joyride": "1.11.4", + "react-piwik": "1.6.0", "react-redux": "5.0.6", "react-router": "4.2.0", "react-router-dom": "4.3.1", @@ -96,6 +106,7 @@ "redux-thunk": "2.3.0", "semver": "5.6.0", "snyk": "1.117.1", + "snyk-nodejs-lockfile-parser": "1.9.1", "socket.io-client": "2.2.0", "webpack-merge": "=4.1.5" }, @@ -123,7 +134,7 @@ "cpx": "=1.5.0", "css-hot-loader": "1.4.3", "css-loader": "0.28.7", - "cypress": "3.1.3", + "cypress": "3.1.4", "del-cli": "1.1.0", "electron": "3.0.11", "electron-builder": "20.38.3", @@ -132,6 +143,7 @@ "electron-updater": "4.0.6", "enzyme": "3.1.0", "enzyme-adapter-react-16": "1.7.1", + "electron-rebuild": "1.8.2", "es7-object-polyfill": "0.0.7", "eslint": "5.10.0", "eslint-config-airbnb": "15.1.0", @@ -160,6 +172,7 @@ "jsdom": "11.11.0", "json-loader": "0.5.7", "mocha-steps": "1.1.0", + "npm-run-all": "4.1.5", "postcss-for": "=2.1.1", "postcss-functions": "3.0.0", "postcss-loader": "3.0.0", diff --git a/src/actions/followedAccounts.js b/src/actions/followedAccounts.js index 97b1055000..7ee153505c 100644 --- a/src/actions/followedAccounts.js +++ b/src/actions/followedAccounts.js @@ -25,8 +25,9 @@ export const followedAccountFetchedAndUpdated = ({ account }) => (dispatch, getState) => { const liskAPIClient = getState().peers.liskAPIClient; getAccount(liskAPIClient, account.address).then((result) => { - if (result.balance !== account.balance) { + if (result.balance !== account.balance || result.publicKey !== account.publicKey) { account.balance = result.balance; + if (result.publicKey) account.publicKey = result.publicKey; dispatch(followedAccountUpdated(account)); } }); diff --git a/src/actions/followedAccounts.test.js b/src/actions/followedAccounts.test.js index 91c8b7ceda..482a89aa4a 100644 --- a/src/actions/followedAccounts.test.js +++ b/src/actions/followedAccounts.test.js @@ -54,17 +54,27 @@ describe('actions: followedAccount', () => { expect(followedAccountRemoved(data)).to.be.deep.equal(expectedAction); }); - it('should update a followed account if balance changed', () => { + it('should not update a followed account if balance and publicKey doesn\'t change', () => { stub(accountApi, 'getAccount').returnsPromise(); - // Case 1: balance does not change - accountApi.getAccount.resolves({ balance: accounts.genesis.balance }); + accountApi.getAccount.resolves({ + balance: accounts.genesis.balance, + publicKey: accounts.genesis.publicKey, + }); followedAccountFetchedAndUpdated({ account: data })(dispatch, getState); expect(dispatch).to.not.have.been.calledWith(); - // Case 2: balance does change - accountApi.getAccount.resolves({ balance: 0 }); + accountApi.getAccount.restore(); + }); + + it('should update a followed account if balance does change', () => { + stub(accountApi, 'getAccount').returnsPromise(); + + accountApi.getAccount.resolves({ + balance: 0, + publicKey: accounts.genesis.publicKey, + }); followedAccountFetchedAndUpdated({ account: data })(dispatch, getState); expect(dispatch).to.been.calledWith(followedAccountUpdated({ @@ -75,4 +85,62 @@ describe('actions: followedAccount', () => { accountApi.getAccount.restore(); }); + + it('should update a followed account if publicKey does change', () => { + stub(accountApi, 'getAccount').returnsPromise(); + + accountApi.getAccount.resolves({ + balance: accounts.genesis.balance, + publicKey: accounts.delegate.publicKey, + }); + + followedAccountFetchedAndUpdated({ account: data })(dispatch, getState); + expect(dispatch).to.been.calledWith(followedAccountUpdated({ + publicKey: accounts.delegate.publicKey, + balance: accounts.genesis.balance, + title: accounts.genesis.address, + })); + + accountApi.getAccount.restore(); + }); + + it('should not update a followed account if publicKey changed to undefined', () => { + stub(accountApi, 'getAccount').returnsPromise(); + + accountApi.getAccount.resolves({ + balance: accounts.genesis.balance, + publicKey: undefined, + }); + + followedAccountFetchedAndUpdated({ account: data })(dispatch, getState); + expect(dispatch).to.been.calledWith(followedAccountUpdated({ + publicKey: accounts.delegate.publicKey, + balance: accounts.genesis.balance, + title: accounts.genesis.address, + })); + + accountApi.getAccount.restore(); + }); + + it('should not update a followed account if publicKey previously unexisted', () => { + stub(accountApi, 'getAccount').returnsPromise(); + + const testData = { + balance: accounts.genesis.balance, + title: accounts.genesis.address, + }; + accountApi.getAccount.resolves({ + balance: accounts.genesis.balance, + publicKey: accounts.delegate.publicKey, + }); + + followedAccountFetchedAndUpdated({ account: testData })(dispatch, getState); + expect(dispatch).to.been.calledWith(followedAccountUpdated({ + publicKey: accounts.delegate.publicKey, + balance: accounts.genesis.balance, + title: accounts.genesis.address, + })); + + accountApi.getAccount.restore(); + }); }); diff --git a/src/assets/css/styles.head.css b/src/assets/css/styles.head.css index c8586097dd..f0c60b3180 100644 --- a/src/assets/css/styles.head.css +++ b/src/assets/css/styles.head.css @@ -3,7 +3,7 @@ body { } .splash-screen { - position: absolute; + position: fixed; width: 100%; height: 100%; left: 0; diff --git a/src/assets/fonts/gilroy/gilroy-medium.otf b/src/assets/fonts/gilroy/gilroy-medium.otf new file mode 100644 index 0000000000..7f03b582c4 Binary files /dev/null and b/src/assets/fonts/gilroy/gilroy-medium.otf differ diff --git a/src/assets/images/icons-v2/avatar.svg b/src/assets/images/icons-v2/avatar.svg new file mode 100644 index 0000000000..86061584a5 --- /dev/null +++ b/src/assets/images/icons-v2/avatar.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/icons-v2/check.svg b/src/assets/images/icons-v2/check.svg new file mode 100644 index 0000000000..d7659cf54f --- /dev/null +++ b/src/assets/images/icons-v2/check.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/icons-v2/circle-lock.svg b/src/assets/images/icons-v2/circle-lock.svg new file mode 100644 index 0000000000..82612bba6b --- /dev/null +++ b/src/assets/images/icons-v2/circle-lock.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/icons-v2/feedback.svg b/src/assets/images/icons-v2/feedback.svg new file mode 100644 index 0000000000..c98a22795a --- /dev/null +++ b/src/assets/images/icons-v2/feedback.svg @@ -0,0 +1,14 @@ + + + + D1F1DD12-7DF1-49F4-8479-EC1401AC3676 + Created with sketchtool. + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/icons-v2/help.svg b/src/assets/images/icons-v2/help.svg new file mode 100644 index 0000000000..9322aeb8e4 --- /dev/null +++ b/src/assets/images/icons-v2/help.svg @@ -0,0 +1,14 @@ + + + + B987E51E-FA0A-4112-94A8-706DC1B4ADFF + Created with sketchtool. + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/icons-v2/key.svg b/src/assets/images/icons-v2/key.svg new file mode 100644 index 0000000000..e888bbbe84 --- /dev/null +++ b/src/assets/images/icons-v2/key.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons-v2/lock.svg b/src/assets/images/icons-v2/lock.svg new file mode 100644 index 0000000000..551ca84d14 --- /dev/null +++ b/src/assets/images/icons-v2/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons-v2/pdf.svg b/src/assets/images/icons-v2/pdf.svg new file mode 100644 index 0000000000..82d0572f90 --- /dev/null +++ b/src/assets/images/icons-v2/pdf.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + PDF + + + + + diff --git a/src/assets/images/lisk-logo-v2.svg b/src/assets/images/lisk-logo-v2.svg new file mode 100644 index 0000000000..58ac62a20d --- /dev/null +++ b/src/assets/images/lisk-logo-v2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/accountInitialization/index.js b/src/components/accountInitialization/index.js index 3cb5b2098f..4f3ff1197f 100644 --- a/src/components/accountInitialization/index.js +++ b/src/components/accountInitialization/index.js @@ -3,28 +3,46 @@ import { connect } from 'react-redux'; import { translate } from 'react-i18next'; import grid from 'flexboxgrid/dist/flexboxgrid.css'; import { Button } from './../toolbox/buttons/button'; -import { FontIcon } from '../fontIcon'; import styles from './accountInit.css'; import fees from './../../constants/fees'; +import { parseSearchParams } from './../../utils/searchParams'; import { fromRawLsk } from '../../utils/lsk'; +import Piwik from '../../utils/piwik'; class AccountInitialization extends React.Component { closeInfo() { + Piwik.trackingEvent('AccountInit', 'button', 'Close info dialog'); this.props.nextStep(); } componentDidMount() { - const { account, transactions, address } = this.props; + const { + account, nextStep, history, transactions, address, + } = this.props; + const search = Object.keys(parseSearchParams(history.location.search)); const needsNoAccountInit = account.serverPublicKey || account.balance === 0 || transactions.pending.length > 0; - if (needsNoAccountInit || address) { - this.props.nextStep(); + + if (search.includes('initializeAccount') && !needsNoAccountInit) { + history.replace({ + pathname: history.location.pathname, + search: '', + }); + nextStep({ account, accountInit: true }, 2); + } else if (needsNoAccountInit || address || search.includes('wallet')) { + nextStep(); } } + onNext() { + const { account, nextStep } = this.props; + Piwik.trackingEvent('AccountInit', 'button', 'Next step'); + nextStep({ account, accountInit: true }, 2); + } + render() { - const { account, t, nextStep } = this.props; + const { t } = this.props; return (
@@ -33,11 +51,6 @@ class AccountInitialization extends React.Component {

{t('It is recommended that you initialize your Lisk ID.')}

{t('The easiest way to do this is to send LSK to yourself. It will cost you only the usual {{fee}} LSK transaction fee.', { fee: fromRawLsk(fees.send) })}

-

- - {t('Learn more about Lisk ID initialization')} arrow-right - -

@@ -51,7 +64,7 @@ class AccountInitialization extends React.Component {
diff --git a/src/components/accountVisual/index.js b/src/components/accountVisual/index.js index 3f1485f9d9..b4aa995bdf 100644 --- a/src/components/accountVisual/index.js +++ b/src/components/accountVisual/index.js @@ -174,6 +174,7 @@ class AccountVisual extends React.Component { constructor(props) { super(props); this.state = { isSBreakpoint: window.innerWidth <= breakpoints.s }; + this.resizeWindow = this.resizeWindow.bind(this); } shouldComponentUpdate(nextProps, state) { @@ -190,11 +191,11 @@ class AccountVisual extends React.Component { } componentDidMount() { - window.addEventListener('resize', this.resizeWindow.bind(this)); + window.addEventListener('resize', this.resizeWindow); } componentWillUnmount() { - window.removeEventListener('resize', this.resizeWindow.bind(this)); + window.removeEventListener('resize', this.resizeWindow); } render() { // eslint-disable-line max-statements diff --git a/src/components/app/app.css b/src/components/app/app.css index 3cec004a00..767c3d49ad 100644 --- a/src/components/app/app.css +++ b/src/components/app/app.css @@ -14,8 +14,8 @@ body { height: 100%; left: 0; top: 0; - align-items: center; display: flex; + align-items: flex-start; z-index: 0; & > section { diff --git a/src/components/app/appV2.css b/src/components/app/appV2.css new file mode 100644 index 0000000000..bde9b7e967 --- /dev/null +++ b/src/components/app/appV2.css @@ -0,0 +1,18 @@ +@import './variablesV2.css'; + +.v2Wrapper { + background-image: var(--color-background-wrapper); + bottom: 0; + left: 0; + padding-top: var(--header-height-m); + position: absolute; + right: 0; + top: 0; + z-index: var(--normal-index); +} + +@media (--small-viewport) { + .v2Wrapper { + padding-top: var(--header-height-s); + } +} diff --git a/src/components/app/index.js b/src/components/app/index.js index f3803230b8..dda53bdc96 100644 --- a/src/components/app/index.js +++ b/src/components/app/index.js @@ -1,7 +1,8 @@ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, Switch, withRouter } from 'react-router-dom'; import { isPathCorrect } from '../../utils/app'; import styles from './app.css'; +import stylesV2 from './appV2.css'; import Toaster from '../toaster'; import MainMenu from '../mainMenu'; import LoadingBar from '../loadingBar'; @@ -10,6 +11,7 @@ import CustomRoute from '../customRoute'; import Header from '../header'; import Dialog from '../dialog'; import NotFound from '../notFound'; +// import StatusBar from '../statusBar'; // This will be enable with PR #1674 import routes from '../../constants/routes'; // eslint-disable-next-line import/no-named-as-default @@ -22,8 +24,6 @@ class App extends React.Component { } markAsLoaded() { - this.main.classList.add(styles.loaded); - this.main.classList.add('appLoaded'); this.setState({ loaded: true }); } @@ -39,6 +39,8 @@ class App extends React.Component { const explorerRoutes = allRoutes.filter(routeObj => routeObj.pathPrefix && routeObj.pathPrefix === routes.explorer.path); + const routesV2Layout = allRoutes.filter(routeObj => routeObj.isV2Layout); + const routesOutsideMainWrapper = [ 'registerDelegate', 'register', @@ -46,69 +48,97 @@ class App extends React.Component { 'login', ]; + const { location } = this.props; + return ( -
{ this.main = el; }}> - -
-
-
+ { + routesV2Layout.filter(route => route.path === location.pathname).length > 0 + ? ( +
{ this.main = el; }}>
- {this.state.loaded ? - ( - isPathCorrect(location, explorerRoutes) ? ( -
- {explorerRoutes.map((route, key) => ( - - ))} -
- ) : - )} /> - : null - } - {this.state.loaded ? - defaultRoutes.map((route, key) => ( - ( + + /> )) - : null } - - { - routesOutsideMainWrapper.map((route, key) => ( - - )) - } -
-
-
- -
+ + + ) : ( +
{ this.main = el; }}> + +
+
+
+
+ + {this.state.loaded && + ( + isPathCorrect(location, explorerRoutes) && ( +
+ {explorerRoutes.map((route, key) => ( + + ))} +
+ ) + )} /> + } + {this.state.loaded && + defaultRoutes.map((route, key) => ( + + )) + } + + { + routesOutsideMainWrapper.map((route, key) => ( + + )) + } + +
+
+
+ +
+ ) + }
); } } -export default App; +export default withRouter(App); diff --git a/src/components/app/index.test.js b/src/components/app/index.test.js index 8e530c3475..8cab75a9cd 100644 --- a/src/components/app/index.test.js +++ b/src/components/app/index.test.js @@ -8,6 +8,7 @@ import { I18nextProvider } from 'react-i18next'; import i18n from '../../i18n'; // initialized i18next instance import App from './'; import Login from '../login'; +import Splashscreen from '../splashscreen/splashscreen'; import Transactions from '../transactionDashboard'; import Voting from '../voting'; import routes from '../../constants/routes'; @@ -25,6 +26,7 @@ const addRouter = Component => (props, path) => const publicComponent = [ { route: '/', component: Login }, + { route: '/splashscreen', component: Splashscreen }, ]; const privateComponent = [ diff --git a/src/components/app/type.css b/src/components/app/type.css index 96a181e5df..df7af0772b 100644 --- a/src/components/app/type.css +++ b/src/components/app/type.css @@ -42,6 +42,13 @@ avaialble application-wide font-style: normal; } +@font-face { + font-family: 'gilroy-base'; + src: url('../../assets/fonts/gilroy/gilroy-medium.otf'); + font-weight: 400; + font-style: normal; +} + body { font-family: var(--content-font); font-weight: 300; diff --git a/src/components/app/variables.css b/src/components/app/variables.css index a6b2687a96..b4ce6c2516 100644 --- a/src/components/app/variables.css +++ b/src/components/app/variables.css @@ -80,7 +80,7 @@ or "warn/action" ineastd of "red/green" --sidebar-width: 115px; --sidebar-width-xl: 142px; --main-box-width: 1291px; - --main-box-height: 75vh; /* stylelint-disable-line */ + --main-box-height: 80vh; /* stylelint-disable-line */ --border-radius-normal: 3px; --header-height-m: 100px; --header-height-s: 67px; diff --git a/src/components/app/variablesV2.css b/src/components/app/variablesV2.css new file mode 100644 index 0000000000..afe983f1d4 --- /dev/null +++ b/src/components/app/variablesV2.css @@ -0,0 +1,133 @@ +/***************************************** + +Please Read the guide line +before adding any rules here + +# No component specific rule +This file is inteded to solely include +global/common variables, every rule added here +must be used at least in 2 different +components. + +# Usecases +Try to isolate these variables with another +variable in the component specific stylesheet +in order to make component-wide changes +faster and consistent. + +# Avoid rules and mixins +Please avoid defining any rules here +In addition, mixins have their own files, +Please prefer only variables here. + +# Use with :global +Please do not define any variables ourside +the :root scope, since then it won't be +available application wide + +# naming +variable names should correspond to the +role of the value not th presentation +for example, a color name can include +the css rule it belongs to e.g. color +or "background-color" and "primary/secondary" +or "warn/action" ineastd of "red/green" + +****************************************/ + +:root { + /************************* + Font + *************************/ + --font-size-h1: 65px; + --font-size-h2: 32px; + --font-size-h3: 28px; + --font-size-h4: 26px; + --font-size-h5: 20px; + --font-size-h6: 16px; + --font-size-small: 10px; + --heading-font: 'Gilroy', 'Open Sans', sans-serif; + --content-font: 'Open Sans', sans-serif; + --content-font-new: 'gilroy-base', 'Open Sans', sans-serif; + --font-weight-normal: 400; + --font-weight-semi-bold: 500; + --font-weight-bold: 600; + --font-weight-very-bold: 700; + --subtitle-font-size: 18px; + --subtitle-font-size-s: 16px; + --paragraph-font-size-l: 15px; + --paragraph-font-size-s: 14px; + --button-font-size: var(--paragraph-font-size-s); + --button-font-size-s: 12px; + + /************************* + Sizes + *************************/ + --border-radius-standard: 4px; + --header-height-m: 100px; + --header-height-s: 72px; + --header-elements-height: 32px; + --header-logo-size: 56px; + + /************************* + Padding + *************************/ + --header-padding: 0 32px; + + /************************* + Colors + *************************/ + --color-primary-standard: #4e80f7; + --color-primary-light: #9db8fa; + --color-white: #fff; + --color-error: #ff7721; + --color-content: #19224d; + --color-content-light: #2e2c3b; + --color-content-lighter: rgba(46, 44, 59, 0.6); + --color-background-dark: #1e194d; + --color-dark-gray: #868ba1; + --color-light-gray: #e6e6e6; + --color-content-grayblue: #7383a7; + --color-background-wrapper: linear-gradient(120deg, #f7f9fb, #fbfcfd); + --color-background-header: #fbfcfd; + --color-link: var(--color-primary-standard); + + /************************* + Box + *************************/ + --box-shadow-standard: 0 4px 8px 0 rgba(72, 77, 97, 0.1); + --box-shadow-button: var(--box-shadow-standard); + --box-shadow-header: 0 2px 4px 0 rgba(15, 25, 42, 0.02); + --box-shadow-avatar: var(--box-shadow-standard); + + /************************* + Animation + *************************/ + --animation-speed-faster: 150ms; + --animation-speed-fast: 250ms; + --animation-speed-standard: 500ms; + --animation-speed-slow: 700ms; + --animation-speed-slowest: 1000ms; + --animation-delay-standard: 100ms; + + /************************* + Transitions + *************************/ + --transition-button: background-color var(--animation-speed-fast) ease-in-out, color var(--animation-speed-fast) ease-in-out; + + /************************* + Z-Indexes + *************************/ + --normal-index: 1; + --overlay-index: 20; + + /************************* + Media Queries + *************************/ + @custom-media --xLarge-viewport (min-width: 1400px); + @custom-media --large-viewport (max-width: 1400px); + @custom-media --medium-viewport (max-width: 1024px); + @custom-media --small-viewport (max-width: 768px); + @custom-media --xSmall-viewport (max-width: 414px); + @custom-media --xxSmall-viewport (max-width: 320px); +} diff --git a/src/components/authenticate/authenticate.js b/src/components/authenticate/authenticate.js index 19f831dd9e..26ee54910c 100644 --- a/src/components/authenticate/authenticate.js +++ b/src/components/authenticate/authenticate.js @@ -3,6 +3,7 @@ import { handleChange, authStatePrefill, authStateIsValid } from '../../utils/fo import ActionBar from '../actionBar'; import AuthInputs from '../authInputs'; import InfoParagraph from '../infoParagraph'; +import Piwik from '../../utils/piwik'; class Authenticate extends React.Component { constructor() { @@ -25,8 +26,8 @@ class Authenticate extends React.Component { this.message = `${t('You are looking into a saved account. In order to {{nextAction}} you need to enter your passphrase.', { nextAction })}`; } - update(e) { - e.preventDefault(); + update() { + Piwik.trackingEvent('Authenticate', 'button', 'Update'); const data = { passphrase: this.state.passphrase.value, }; @@ -36,6 +37,12 @@ class Authenticate extends React.Component { this.props.accountUpdated(data); } + closeDialog(e) { + e.preventDefault(); + Piwik.trackingEvent('Authenticate', 'button', 'Close dialog'); + this.props.closeDialog(); + } + render() { return (
@@ -50,7 +57,8 @@ class Authenticate extends React.Component { { passphrase, }); }); + + it('should close dialog on click the close button', () => { + wrapper.find('button.closeDialog-button').simulate('click'); + wrapper.update(); + expect(props.closeDialog).to.have.been.calledWith(); + }); }); diff --git a/src/components/autoSuggest/index.js b/src/components/autoSuggest/index.js index 2e385a5e8f..614eabbadc 100644 --- a/src/components/autoSuggest/index.js +++ b/src/components/autoSuggest/index.js @@ -11,6 +11,7 @@ import localJSONStorage from './../../utils/localJSONStorage'; import regex from './../../utils/regex'; import { saveSearch } from './../searchResult/keyAction'; import { searchEntities } from './../../constants/search'; +import Piwik from '../../utils/piwik'; class AutoSuggest extends React.Component { constructor(props) { @@ -40,7 +41,9 @@ class AutoSuggest extends React.Component { this.setState({ resultsLength, selectedIdx, placeholder }); } + // eslint-disable-next-line max-statements onResultClick(id, type, value) { + Piwik.trackingEvent('AutoSuggest', 'button', 'Results'); let urlSearch; switch (type) { case searchEntities.addresses: @@ -55,7 +58,6 @@ class AutoSuggest extends React.Component { break; } saveSearch(value, id); - this.props.searchClearSuggestions(); if (!value && [searchEntities.addresses, searchEntities.transactions].filter(entity => entity === type).length > 0) { @@ -75,6 +77,7 @@ class AutoSuggest extends React.Component { } submitSearch() { + Piwik.trackingEvent('AutoSuggest', 'button', 'Search submit'); this.onResultClick( this.selectedRow.dataset.id, this.selectedRow.dataset.type, @@ -154,6 +157,7 @@ class AutoSuggest extends React.Component { } handleSubmit() { + Piwik.trackingEvent('AutoSuggest', 'button', 'Handle submit'); if (this.state.value === '' && this.state.placeholder === '') { return; } @@ -190,6 +194,7 @@ class AutoSuggest extends React.Component { } resetSearch() { + Piwik.trackingEvent('AutoSuggest', 'button', 'Clear suggestions'); this.lastSearch = null; this.setState({ value: '', placeholder: '' }); this.props.searchClearSuggestions(); @@ -201,6 +206,7 @@ class AutoSuggest extends React.Component { } selectInput() { + Piwik.trackingEvent('AutoSuggest', 'button', 'Select Input'); this.inputRef.inputNode.select(); } @@ -254,6 +260,17 @@ class AutoSuggest extends React.Component { return this.recentSearches; } + getNoResultMessage() { + let noResultMessage; + if (this.state.value.length > 0 && this.state.value.length <= 2) { + noResultMessage = this.props.t('Type at least 3 characters'); + } + if (this.state.value.length > 2 && this.state.resultsLength === 0) { + noResultMessage = this.props.t('No results found'); + } + return noResultMessage; + } + render() { const { t } = this.props; @@ -268,28 +285,32 @@ class AutoSuggest extends React.Component {
{}} + onChange={() => { + }} className={`${styles.placeholder} autosuggest-placeholder`} type='text' - name='autosuggest-placeholder' /> + name='autosuggest-placeholder'/> { this.inputRef = el; }} - className={`${styles.input} autosuggest-input`} - theme={styles} - onClick={this.selectInput.bind(this)} - onFocus={() => this.setState({ show: true })} - onBlur={this.closeDropdown.bind(this)} - onKeyDown={this.handleKey.bind(this)} - onChange={this.search.bind(this)} - autoComplete='off'> + id='autosuggest-input' + name='searchBarInput' + value={this.state.value} + innerRef={(el) => { + this.inputRef = el; + }} + className={`${styles.input} autosuggest-input`} + theme={styles} + onClick={this.selectInput.bind(this)} + onFocus={() => this.setState({ show: true })} + onBlur={this.closeDropdown.bind(this)} + onKeyDown={this.handleKey.bind(this)} + onChange={this.search.bind(this)} + autoComplete='off'> { this.state.value !== '' || this.state.placeholder !== '' ? - : + : + onClick={this.submitSearch.bind(this)}/> }
@@ -323,7 +344,7 @@ class AutoSuggest extends React.Component { onMouseDown={this.onResultClick.bind(this)} setSelectedRow={this.setSelectedRow.bind(this)} /> - { this.state.value === '' && this.state.resultsLength === 0 ? + {this.state.value === '' && this.state.resultsLength === 0 ? : null } - - { this.state.value !== '' && this.state.resultsLength === 0 ? -

{t('No results found')}

- : null - } +

{this.getNoResultMessage()}

); diff --git a/src/components/autoSuggest/index.test.js b/src/components/autoSuggest/index.test.js index f43ebb7d18..f322fcacc7 100644 --- a/src/components/autoSuggest/index.test.js +++ b/src/components/autoSuggest/index.test.js @@ -212,7 +212,6 @@ describe('AutoSuggest', () => { which: keyCodes.tab, }); expect(submitSearchSpy).to.have.been.calledWith(); - expect(props.searchClearSuggestions).to.have.been.calledWith(); }); it('should show recent searches and close dropdown on keyboard event {escape}', () => { diff --git a/src/components/box/box.css b/src/components/box/box.css index 6e48350ecb..c2c97b21b2 100644 --- a/src/components/box/box.css +++ b/src/components/box/box.css @@ -53,8 +53,6 @@ .wrapper { padding: 0 var(--box-padding-left-M) 0 var(--box-padding-left-M); min-height: calc(100vh - var(--m-menu-bar-height) - var(--m-menu-bar-height)); /* stylelint-disable-line */ - background-color: var(--color-grayscale-mobile-background); - box-shadow: unset; } } diff --git a/src/components/confirmVotes/confirmVotes.js b/src/components/confirmVotes/confirmVotes.js index d19b0d98bc..860452c580 100644 --- a/src/components/confirmVotes/confirmVotes.js +++ b/src/components/confirmVotes/confirmVotes.js @@ -4,6 +4,7 @@ import styles from './confirmVotes.css'; import Checkbox from '../toolbox/sliderCheckbox'; import fees from '../../constants/fees'; import { fromRawLsk } from '../../utils/lsk'; +import Piwik from '../../utils/piwik'; class ConfirmVotes extends React.Component { constructor(props) { @@ -47,10 +48,24 @@ class ConfirmVotes extends React.Component { }, 120); } + onNext(data) { + Piwik.trackingEvent('ConfirmVotes', 'button', 'Next step'); + this.setState({ didSend: true }); + this.props.votePlaced(data); + } + + onPrev() { + Piwik.trackingEvent('ConfirmVotes', 'button', 'Previous step'); + this.props.prevStep({ reset: this.props.skipped }); + } + render() { const { - t, prevStep, votePlaced, skipped, - votes, account, secondPassphrase, passphrase, + t, + votes, + account, + secondPassphrase, + passphrase, } = this.props; const data = { account, @@ -70,13 +85,10 @@ class ConfirmVotes extends React.Component { { - this.setState({ didSend: true }); - votePlaced(data); - }}>{t('Confirm (Fee: 1 LSK)')} + onClick={() => this.onNext(data)}>{t('Confirm (Fee: 1 LSK)')} + onClick={() => this.onPrev()}>{t('Back')} this.textIsCopied()}> - {this.state.copied ? - {t('Copied!')} - : - - - {text || value} - - } - +
{ + e.stopPropagation(); + }}> + this.textIsCopied()}> + {this.state.copied ? + {t('Copied!')} + : + + + {text || value} + + } + +
); } } diff --git a/src/components/customRoute/customRoute.js b/src/components/customRoute/customRoute.js index 162a7a0437..329259891a 100644 --- a/src/components/customRoute/customRoute.js +++ b/src/components/customRoute/customRoute.js @@ -2,13 +2,18 @@ import React from 'react'; import { Redirect, Route } from 'react-router-dom'; import ErrorBoundary from '../errorBoundary'; import offlineStyle from '../offlineWrapper/offlineWrapper.css'; +import Piwik from '../../utils/piwik'; const CustomRoute = ({ path, component, isPrivate, exact, + settings, isAuthenticated, pathSuffix = '', pathPrefix = '', t, ...rest }) => { const { pathname, search } = rest.history.location; const fullPath = pathPrefix + path + pathSuffix; + + Piwik.tracking(rest.history, settings); + return ((isPrivate && isAuthenticated) || !isPrivate ?
diff --git a/src/components/customRoute/index.js b/src/components/customRoute/index.js index 0505a66bd7..b140c42c29 100644 --- a/src/components/customRoute/index.js +++ b/src/components/customRoute/index.js @@ -5,6 +5,7 @@ import CustomRoute from './customRoute'; const mapStateToProps = state => ({ isAuthenticated: !!state.account.publicKey, + settings: state.settings, }); export default withRouter(connect(mapStateToProps)(translate()(CustomRoute))); diff --git a/src/components/customRoute/index.test.js b/src/components/customRoute/index.test.js index 983997a71e..79f7f86a98 100644 --- a/src/components/customRoute/index.test.js +++ b/src/components/customRoute/index.test.js @@ -18,6 +18,9 @@ describe('CustomRoute', () => { history: { location: { pathname: '' } }, path: '/private', component: Private, + settings: { + statistics: false, + }, }; const options = { context: { store, history, i18n }, diff --git a/src/components/dashboard/currencyGraph.js b/src/components/dashboard/currencyGraph.js index 68125b8074..a38e94cd83 100644 --- a/src/components/dashboard/currencyGraph.js +++ b/src/components/dashboard/currencyGraph.js @@ -6,6 +6,7 @@ import React from 'react'; import { getCurrencyGraphData } from '../../actions/liskService'; +import Piwik from '../../utils/piwik'; import EmptyState from '../emptyState'; import styles from './currencyGraph.css'; @@ -159,6 +160,7 @@ class CurrencyGraph extends React.Component { } setStep(step) { + Piwik.trackingEvent('CurrencyGraph', 'button', 'Set step'); this.setState({ step, data: undefined }); this.props.getCurrencyGraphData(step); } diff --git a/src/components/dashboard/dashboard.css b/src/components/dashboard/dashboard.css index e380a0577a..b7fcf84f5f 100644 --- a/src/components/dashboard/dashboard.css +++ b/src/components/dashboard/dashboard.css @@ -3,56 +3,135 @@ .wrapper { width: 100%; margin: 0px; + padding-bottom: 50px; } -.main { - max-height: 75vh; /* stylelint-disable-line */ - padding-left: 0px; +.bannerWrapper { + margin: 0px; + padding-bottom: 20px; + width: 100%; } -.seeAllLink { - font-size: 15px; - color: var(--color-primary-standard); - margin-left: 40px; - text-decoration: none; - font-family: var(--content-font); - font-weight: var(--font-weight-bold); +.main { + height: auto; + padding: 0px; } -.newsFeedWrapper { - padding-right: 0px; -} +.latestActivity { + display: flex; + flex-direction: column; + justify-content: flex-start; + min-height: 418px; + height: auto; + margin: 0 0 20px; + padding: 0; + + & > header { + box-sizing: border-box; + padding: 20px 48px 0; + } -.send { - position: relative; + & :global(.transaction-results) { + margin: 0; + height: 270px; + overflow: hidden; + } } .bottomModuleWrapper { - height: 50%; + height: auto; min-height: 200px; padding: 0px 8px; + + & > div:first-child { + padding-right: 10px; + height: auto; + } + + & > div:last-child { + padding-left: 10px; + } +} + +.following { + height: auto; + padding: 0; +} + +.newsFeedWrapper { + padding-right: 0px; + padding-left: 15px; +} + +.seeAllLink { + font-size: 15px; + color: var(--color-primary-standard); + margin-left: 40px; + text-decoration: none; + font-family: var(--content-font); + font-weight: var(--font-weight-bold); } .graph { overflow: hidden; padding: 0px; + height: 428px; } -.latestActivity { - height: calc(50% - 20px); - margin: 0px 0px 20px; +.showMore { + align-self: flex-end; + margin-top: auto; } @media (--medium-viewport) { .wrapper { + margin: 0; + min-height: unset; + & .main { - max-width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; flex-basis: 100%; - max-height: unset; - overflow: hidden; + max-width: 100%; + min-height: min-content; + padding-bottom: 50px; } + } + .latestActivity { + height: auto; + min-height: 0; + margin: 0 8px 20px; + } + + .bottomModuleWrapper { + height: auto; + display: flex; + flex-direction: column; + flex-basis: 50%; margin: 0; + + & > div { + max-width: 100%; + padding: 0; + } + + & > div:first-child { + margin-bottom: 20px; + padding-right: 0; + } + + & > div:last-child { + padding-left: 0; + } + } + + .newsFeedWrapper { + flex-basis: 50%; + padding: 0 8px; } .graph { @@ -65,22 +144,4 @@ height: auto; min-height: 300px; } - - .bottomModuleWrapper { - height: auto; - } - - .latestActivity { - height: unset; - min-height: unset; - overflow: hidden; - } -} - -@media (--small-viewport) { - .seeAllLink { - display: block; - margin-top: 16px; - margin-left: 0; - } } diff --git a/src/components/dashboard/index.js b/src/components/dashboard/index.js index bbf8398b79..b4a9441789 100644 --- a/src/components/dashboard/index.js +++ b/src/components/dashboard/index.js @@ -1,8 +1,10 @@ +// istanbul ignore file import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { translate } from 'react-i18next'; -import grid from 'flexboxgrid/dist/flexboxgrid.css'; import React from 'react'; +import throttle from 'lodash.throttle'; +import grid from 'flexboxgrid/dist/flexboxgrid.css'; import { FontIcon } from '../fontIcon'; import Box from '../box'; import { loadTransactions } from '../../actions/transactions'; @@ -13,6 +15,12 @@ import FollowedAccounts from '../followedAccounts/index'; import QuickTips from '../quickTips'; import NewsFeed from '../newsFeed'; import removeDuplicateTransactions from '../../utils/transactions'; +import { fromRawLsk } from '../../utils/lsk'; +import breakpoints from './../../constants/breakpoints'; +import fees from './../../constants/fees'; +import ShowMore from '../showMore'; +import { SecondaryLightButton } from '../toolbox/buttons/button'; +import Banner from '../toolbox/banner/banner'; import styles from './dashboard.css'; @@ -20,6 +28,11 @@ class Dashboard extends React.Component { constructor(props) { super(props); + this.state = { + showMore: false, + isDesktop: window.innerWidth > breakpoints.m, + }; + const isLoggedIn = props.account.address; if (isLoggedIn) { @@ -28,55 +41,126 @@ class Dashboard extends React.Component { publicKey: props.account.publicKey, }); } + + this.resizeWindow = this.resizeWindow.bind(this); + this.shouldShowInitializatiion = this.shouldShowInitializatiion.bind(this); + } + + componentDidMount() { + window.addEventListener('resize', throttle(this.resizeWindow, 1000)); + } + + shouldShowInitializatiion() { + const { account, transactions } = this.props; + const needsNoAccountInit = account.serverPublicKey + || account.balance === 0 + || (transactions.pending && transactions.pending.length > 0); + return !needsNoAccountInit; + } + + componentWillUnmount() { + window.removeEventListener('resize', this.resizeWindow); + } + + resizeWindow() { + this.setState({ isDesktop: window.innerWidth > breakpoints.m }); + } + + onShowMoreToggle() { + this.setState({ showMore: !this.state.showMore }); } render() { const { - transactions, t, account, loading, history, + account, + history, + loading, + t, + transactions, } = this.props; const isLoggedIn = account.address; - return
-
- {isLoggedIn ? -
-

- {t('Latest activity')} - - {t('See all transactions')} - - -

-
- history.push(`${routes.wallet.path}?id=${props.value.id}`), - }} /> -
: - } -
-
- - - + return ( + + { isLoggedIn && this.shouldShowInitializatiion() && +
+ + {t('Create First Transaction')} + )}> +

{t('It is recommended that you initialize your Lisk ID.')}

+

{t('The easiest way to do this is to send LSK to yourself by clicking this button.')}

+

{t('It will cost you only the usual {{fee}} LSK transaction fee.', { fee: fromRawLsk(fees.send) })}

+
-
- - - + } +
+
+ { + isLoggedIn + ? +
+

+ {t('Latest activity')} + + {t('See all transactions')} + + +

+
+ history.push(`${routes.wallet.path}?id=${props.value.id}`), + showMore: this.state.showMore, + t, + transactions, + }} /> + { + transactions.length > 3 && + this.onShowMoreToggle()} + text={this.state.showMore ? t('Show Less') : t('Show More')} + /> + } +
+ : + } +
+
+ + + +
+
+ + + +
+
+ { + !this.state.isDesktop && +
+ +
+ }
+ { + this.state.isDesktop && +
+ +
+ }
-
-
- -
-
; + + ); } } @@ -84,7 +168,7 @@ const mapStateToProps = state => ({ transactions: removeDuplicateTransactions( state.transactions.pending, state.transactions.confirmed, - ).slice(0, 5), + ), pendingTransactions: state.transactions.pending, account: state.account, loading: state.loading.length > 0, diff --git a/src/components/dialog/alert.css b/src/components/dialog/alert.css new file mode 100644 index 0000000000..22c0e3f7ed --- /dev/null +++ b/src/components/dialog/alert.css @@ -0,0 +1,18 @@ +.okButton { + display: flex; + justify-content: center; + padding: 40px 0; +} + +.description { + padding: 0 50px; + font-size: 16px !important; + color: #3c5068 !important; + font-family: "Open Sans", sans-serif; + line-height: 26px !important; + text-align: center; +} + +.alertBox { + padding-top: 10px; +} diff --git a/src/components/dialog/alert.js b/src/components/dialog/alert.js index 7310109e19..b52f3e0b8b 100644 --- a/src/components/dialog/alert.js +++ b/src/components/dialog/alert.js @@ -3,12 +3,13 @@ import React from 'react'; import grid from 'flexboxgrid/dist/flexboxgrid.css'; import { Button } from '../toolbox/buttons/button'; +import styles from './alert.css'; + const Alert = ({ text, closeDialog, t }) => ( -
-

{text}

+
+

{text}


-
- +
diff --git a/src/components/errorBoundary/index.js b/src/components/errorBoundary/index.js index ea9668f87a..4cc9f0a6a1 100644 --- a/src/components/errorBoundary/index.js +++ b/src/components/errorBoundary/index.js @@ -6,6 +6,7 @@ import { FontIcon } from '../fontIcon'; import { PrimaryButton } from './../toolbox/buttons/button'; import styles from './errorBoundary.css'; import EmptyState from '../emptyState'; +import Piwik from '../../utils/piwik'; /* eslint-disable class-methods-use-this, no-unused-vars */ class ErrorBoundary extends React.Component { @@ -19,6 +20,7 @@ class ErrorBoundary extends React.Component { } reloadPage() { + Piwik.trackingEvent('ErrorBoundary', 'button', 'Reload page'); window.location.reload(); } diff --git a/src/components/feedbackForm/feedbackForm.js b/src/components/feedbackForm/feedbackForm.js index 9c5192ba58..ffda77435a 100644 --- a/src/components/feedbackForm/feedbackForm.js +++ b/src/components/feedbackForm/feedbackForm.js @@ -2,13 +2,16 @@ import React, { Component } from 'react'; import grid from 'flexboxgrid/dist/flexboxgrid.css'; import styles from './feedbackForm.css'; import Questionare from './questionare'; +import Piwik from '../../utils/piwik'; class FeedbackForm extends Component { onCancel() { + Piwik.trackingEvent('FeedbackForm', 'button', 'Cancel'); this.props.hideDialog(); } onSubmit(feedbackState) { + Piwik.trackingEvent('FeedbackForm', 'button', 'Submit'); // TODO: integrate with api this.props.sendFeedback(feedbackState); this.props.hideDialog(); diff --git a/src/components/feedbackForm/questionare.js b/src/components/feedbackForm/questionare.js index 7825e43351..a70bbaf44f 100644 --- a/src/components/feedbackForm/questionare.js +++ b/src/components/feedbackForm/questionare.js @@ -5,6 +5,7 @@ import ToolBoxInput from '../toolbox/inputs/toolBoxInput'; import { Button, PrimaryButton } from '../toolbox/buttons/button'; import RadioSelector from './radioSelector'; import { getDeviceMetadata } from '../../utils/app'; +import Piwik from '../../utils/piwik'; const ratingValues = ['angry', 'sad', 'neutral', 'happy', 'laughing']; const ratingIcons = ratingValues.map(ratingVal => [ratingVal]); @@ -48,6 +49,7 @@ class Questionare extends React.Component { } setFeedbackValue(evt, key) { + Piwik.trackingEvent('Questionare', 'button', 'Set feedback value'); this.setState({ [key]: evt.target ? evt.target.value : evt }); if (key === 'metadata' && !!evt.target.value) { @@ -55,6 +57,16 @@ class Questionare extends React.Component { } } + onCancel() { + Piwik.trackingEvent('Questionare', 'button', 'Cancel'); + this.props.onCancel(); + } + + onSubmit() { + Piwik.trackingEvent('Questionare', 'button', 'Submit'); + this.props.onSubmit(this.state); + } + render() { return (
@@ -83,10 +95,10 @@ class Questionare extends React.Component {
); diff --git a/src/components/followedAccounts/addAccountID.js b/src/components/followedAccounts/addAccountID.js index 81fa3d5d0d..352fff5609 100644 --- a/src/components/followedAccounts/addAccountID.js +++ b/src/components/followedAccounts/addAccountID.js @@ -6,6 +6,7 @@ import { Button, TertiaryButton } from '../toolbox/buttons/button'; import regex from '../../utils/regex'; import styles from './followedAccounts.css'; import AddressInput from '../addressInput/index'; +import Piwik from '../../utils/piwik'; class AddAccountID extends React.Component { constructor() { @@ -36,6 +37,15 @@ class AddAccountID extends React.Component { return undefined; } + onPrevStep() { + Piwik.trackingEvent('AddAccountID', 'button', 'Previous step'); + this.props.prevStep(); + } + + onNextStep() { + Piwik.trackingEvent('AddAccountID', 'button', 'Next step'); + this.props.nextStep({ address: this.state.address.value }); + } render() { return
@@ -53,7 +63,7 @@ class AddAccountID extends React.Component {
@@ -61,7 +71,7 @@ class AddAccountID extends React.Component { label={this.props.t('Next')} className='next' disabled={(!!this.state.address.error || !this.state.address.value)} - onClick={() => this.props.nextStep({ address: this.state.address.value })} + onClick={() => this.onNextStep()} />
diff --git a/src/components/followedAccounts/addAccountTitle.js b/src/components/followedAccounts/addAccountTitle.js index afa6bb5210..6378ff23ef 100644 --- a/src/components/followedAccounts/addAccountTitle.js +++ b/src/components/followedAccounts/addAccountTitle.js @@ -6,6 +6,7 @@ import { Button, TertiaryButton } from '../toolbox/buttons/button'; import { followedAccountAdded } from '../../actions/followedAccounts'; import styles from './followedAccounts.css'; import TitleInput from './accountTitleInput'; +import Piwik from '../../utils/piwik'; class AddAccountTitle extends React.Component { constructor() { @@ -22,12 +23,24 @@ class AddAccountTitle extends React.Component { }); } + onCancel() { + Piwik.trackingEvent('AddAccountTitle', 'button', 'Cancel'); + this.props.prevStep({ reset: true }); + } + + onAddToList() { + Piwik.trackingEvent('AddAccountTitle', 'button', 'Add to list'); + + const { addAccount, address, prevStep } = this.props; + const title = this.state.title.value; + + addAccount({ title, address }); + prevStep({ reset: true }); + } + render() { - const { - t, prevStep, addAccount, address, - } = this.props; + const { t } = this.props; - const title = this.state.title.value || address; return

{t('How would you call it?')}

@@ -40,7 +53,7 @@ class AddAccountTitle extends React.Component {
@@ -48,10 +61,7 @@ class AddAccountTitle extends React.Component { label={t('Add to list')} disabled={!!this.state.title.error || this.state.title.value === ''} className='next' - onClick={() => { - addAccount({ title, address }); - prevStep({ reset: true }); - }} + onClick={() => this.onAddToList()} />
diff --git a/src/components/followedAccounts/followedAccounts.css b/src/components/followedAccounts/followedAccounts.css index 2bac494bf2..94fbf397a2 100644 --- a/src/components/followedAccounts/followedAccounts.css +++ b/src/components/followedAccounts/followedAccounts.css @@ -22,7 +22,12 @@ } .followedAccountsWrapper { - height: 100%; + height: auto; + min-height: 428px; + + @media (--medium-viewport) { + min-height: 398px; + } } .cancelButton { @@ -35,20 +40,53 @@ flex-direction: column; justify-content: space-between; width: 100%; - height: 100%; + height: 410px; box-sizing: border-box; + + & > header { + box-sizing: border-box; + padding: 20px 48px 0; + } + + & > div { + padding: 0 48px; + box-sizing: border-box; + } + + & > footer { + padding: 0 48px 20px; + box-sizing: border-box; + } } header { & .edit { + height: 40px; + min-width: 120px; + display: flex; + justify-content: flex-end; + align-items: center; color: var(--color-primary-standard); font-weight: var(--font-weight-bold); - font-size: 16px; - line-height: 40px; + font-size: 14px; float: right; + + & > span:last-child { + margin-left: 16px; + } + + & > span > span { + font-size: 20px; + } } } +.bookmarkHeader { + padding: 20px 48px 0; + box-sizing: border-box; + height: 119px; +} + .addAccountLink { color: var(--color-primary-standard) !important; font-weight: var(--font-weight-bold); @@ -56,6 +94,8 @@ header { } .emptyList { + padding: 0 48px; + & p { color: var(--color-grayscale-dark); font-size: 16px; @@ -69,6 +109,10 @@ header { } .accounts { + width: 100%; + overflow: hidden; + height: 279px; + & .list { overflow-y: initial; @@ -80,6 +124,7 @@ header { margin-left: 0; line-height: var(--main-row-line-height); white-space: nowrap; + padding: 0 48px; &:nth-of-type(even) { background: var(--row-background-color); @@ -151,54 +196,55 @@ header { } } -@media (--xLarge-viewport) { - .accounts { - & .list { - overflow-y: auto; - margin: 0px calc(0 - var(--box-padding-left-XL)); - } - - & .rows { - padding: 0px var(--box-padding-left-XL); - } - } +.showMore { + margin-top: auto; } -@media (--large-viewport) { - .accounts { - & .list { - overflow-y: auto; - margin: 0px calc(0 - var(--box-padding-left-L)); +@media (--medium-viewport) { + .addAccount { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + height: 398px; + box-sizing: border-box; + + & > div { + padding: 0 48px; + box-sizing: border-box; } - & .rows { - padding: 0px var(--box-padding-left-L); + & > footer { + padding: 0 48px 20px; + box-sizing: border-box; } } -} - -@media (--medium-viewport) { - .addAccount { - display: block; - } .accounts { & .list { - overflow-y: initial; - padding-bottom: 100px; - margin: 0px calc(0 - var(--box-padding-left-M)); - } + overflow: hidden; - & .rows { - padding: 0px var(--box-padding-left-M); + & .rows { + display: flex; + justify-content: space-between; + align-items: center; - &:nth-of-type(even) { - background: var(--gradient-greyscale-mobile); - } + & .accountInformation { + margin: 0; + } - &:nth-of-type(odd) { - background: var(--color-grayscale-mobile-background); + &:nth-of-type(even) { + background: var(--gradient-greyscale-mobile); + } + + &:nth-of-type(odd) { + background: var(--color-grayscale-mobile-background); + } } } } } + +.showMoreToggle { + height: auto; +} diff --git a/src/components/followedAccounts/viewAccounts.js b/src/components/followedAccounts/viewAccounts.js index 5e327b4b1c..ebd1f04d30 100644 --- a/src/components/followedAccounts/viewAccounts.js +++ b/src/components/followedAccounts/viewAccounts.js @@ -9,39 +9,84 @@ import styles from './followedAccounts.css'; import routes from '../../constants/routes'; import TitleInput from './titleInputForList'; import { followedAccountRemoved } from '../../actions/followedAccounts'; +import Piwik from '../../utils/piwik'; +import ShowMore from '../showMore'; class ViewAccounts extends React.Component { constructor() { super(); - this.state = { edit: false }; + this.state = { + edit: false, + showMore: false, + }; + } + + onShowMoreToggle() { + this.setState({ showMore: !this.state.showMore }); + } + + onEditAccount() { + Piwik.trackingEvent('ViewAccounts', 'button', 'Edit account'); + this.setState({ edit: !this.state.edit }); + } + + onFollowedAccount(account) { + Piwik.trackingEvent('ViewAccounts', 'button', 'Followed account'); + + const { history } = this.props; + if (!this.state.edit) history.push(`${routes.explorer.path}${routes.accounts.path}/${account.address}`); + } + + onRemoveAccount(account) { + Piwik.trackingEvent('ViewAccounts', 'button', 'Remove account'); + this.props.removeAccount(account); + } + + onAddAccount() { + Piwik.trackingEvent('ViewAccounts', 'button', 'Add account'); + this.props.nextStep(); } render() { const { - t, accounts, history, nextStep, removeAccount, + t, + accounts, } = this.props; return
-

+
+

{t('Bookmarks')} - {accounts.length > 0 - ?
this.setState({ edit: !this.state.edit })}> - {this.state.edit ? {t('Done')} : } -
- : null + { + accounts.length > 0 && + ( +
this.setState({ edit: !this.state.edit })} + > + { + !this.state.edit && + this.onAddAccount()}>Add + } + { + this.state.edit + ? {t('Done')} + : Edit + } +
+ ) } -

- {accounts.length - ?
+

+
+ { + accounts.length + ?
{accounts.map((account, i) => (
{ - if (!this.state.edit) history.push(`${routes.explorer.path}${routes.accounts.path}/${account.address}`); - }} + onClick={() => this.onFollowedAccount(account)} >
{this.state.edit ?
removeAccount(account) }> + onClick={() => this.onRemoveAccount(account)}>
: null @@ -75,19 +120,32 @@ class ViewAccounts extends React.Component {
)) } -
nextStep()}> - {t('Add a Lisk ID')} -
+ { + !accounts.length && + ( +
this.onAddAccount()}> + {t('Add a Lisk ID')} +
+ ) + }
:

{t('Keep track of any Lisk ID balance. Only you will see who you bookmarked.')}

-
nextStep()}> +
this.onAddAccount()}> {t('Add a Lisk ID')}
} + { + accounts.length > 4 && + this.onShowMoreToggle()} + text={ this.state.showMore ? t('Show Less') : t('Show More')} + /> + }
; } } diff --git a/src/components/followedAccounts/viewAccounts.test.js b/src/components/followedAccounts/viewAccounts.test.js index 2634c6ba87..a299ab6227 100644 --- a/src/components/followedAccounts/viewAccounts.test.js +++ b/src/components/followedAccounts/viewAccounts.test.js @@ -55,6 +55,9 @@ describe('Followed accounts list Component', () => { accounts: [ { address: '123L', balance: 0, title: 'bob' }, { address: '567L', balance: 100000, title: '' }, + { address: '23467L', balance: 30000, title: '' }, + { address: '23464567L', balance: 3000, title: '' }, + { address: '2346456347L', balance: 3000, title: '' }, ], }, }); @@ -138,5 +141,16 @@ describe('Followed accounts list Component', () => { address: '567L', balance: 100000, title: 'my friend', }); }); + + it('should render showMore button propery', () => { + expect(wrapper.find('.show-more').exists()).to.equal(true); + expect(wrapper.find('.showMoreToggle').exists()).to.equal(false); + wrapper.find('.show-more').at(0).simulate('click'); + wrapper.update(); + expect(wrapper.find('.showMoreToggle').exists()).to.equal(true); + wrapper.find('.show-more').at(0).simulate('click'); + wrapper.update(); + expect(wrapper.find('.showMoreToggle').exists()).to.equal(false); + }); }); }); diff --git a/src/components/header/customCountDown.css b/src/components/header/customCountDown.css index 93f7aa9989..c661716fb0 100644 --- a/src/components/header/customCountDown.css +++ b/src/components/header/customCountDown.css @@ -1,4 +1,5 @@ @import '../app/variables.css'; +@import '../app/variablesV2.css'; .reset { margin-right: 15px; @@ -7,7 +8,8 @@ padding-bottom: 3px; cursor: pointer; color: var(--color-primary-standard); - display: inline; + display: flex; + align-items: center; font-family: 'Open Sans', sans-serif; font-weight: 600; font-size: 12px; @@ -17,6 +19,10 @@ color: var(--color-grayscale-medium); } +.time { + color: var(--color-content-light); +} + .timeout { color: var(--color-error); } @@ -29,6 +35,12 @@ font-family: 'Open Sans', sans-serif; } +.timerRow > p { + font-weight: 600; + font-size: 12px; + font-family: 'Open Sans', sans-serif; +} + @media (--medium-viewport) { .default { color: var(--color-grayscale-medium); diff --git a/src/components/header/customCountDown.js b/src/components/header/customCountDown.js index bd1d462517..4e7e3a1a55 100644 --- a/src/components/header/customCountDown.js +++ b/src/components/header/customCountDown.js @@ -1,6 +1,7 @@ import React, { Fragment } from 'react'; import Options from '../dialog/options'; import routes from './../../constants/routes'; +import Piwik from '../../utils/piwik'; import styles from './customCountDown.css'; class CustomCountDown extends React.Component { @@ -54,23 +55,36 @@ class CustomCountDown extends React.Component { this.props.closeDialog(); } + onResetTimer() { + Piwik.trackingEvent('CustomCountDown', 'button', 'Reset timer'); + this.props.resetTimer(); + } + render() { const { - minutes, autoLog, seconds, resetTimer, t, + minutes, + autoLog, + seconds, + t, } = this.props; - const min = minutes < 10 ? `0${minutes}` : minutes; - const sec = seconds < 10 ? `0${seconds}` : seconds; + const min = `0${minutes}`.slice(-2); + const sec = `0${seconds}`.slice(-2); const resetCondition = (minutes < 5); const timeoutCondition = (minutes === 0 && seconds === 0); - const resetButton = resetCondition && !timeoutCondition ?
{ - resetTimer(); - }} className={`${styles.reset} reset`}> {t('Reset')}
:
; + const resetButton = resetCondition && !timeoutCondition ? +
this.onResetTimer()} + className={`${styles.reset} reset`} + > + {t('Reset')} +
: +
; - const resetStyle = resetCondition ? styles.timeout : styles.default; + const resetStyle = resetCondition ? styles.timeout : styles.time; const timer = !timeoutCondition && - {t('Session timeout in')} {min}:{sec}; +

{t('Session timeout in')} {min}:{sec}

; const renderComponent = autoLog ? (
{resetButton} diff --git a/src/components/header/customCountDown.test.js b/src/components/header/customCountDown.test.js index dded666904..98342fe550 100644 --- a/src/components/header/customCountDown.test.js +++ b/src/components/header/customCountDown.test.js @@ -24,12 +24,12 @@ describe('customCountDown', () => { }); it('should render "Session timeout in 10:25"', () => { - expect(wrapper.find('span').text()).to.be.equal('Session timeout in 10:25'); + expect(wrapper.find('p').text()).to.be.equal('Session timeout in 10:25'); }); it('should render reset button', () => { wrapper.setProps({ minutes: 0, seconds: 59 }); - expect(wrapper.find('span').text()).to.be.equal('Session timeout in 00:59'); + expect(wrapper.find('p').text()).to.be.equal('Session timeout in 00:59'); expect(wrapper).to.have.descendants('.reset'); }); diff --git a/src/components/header/header.css b/src/components/header/header.css index 4b17992da1..03a9262a1f 100644 --- a/src/components/header/header.css +++ b/src/components/header/header.css @@ -20,6 +20,8 @@ align-items: center; justify-content: space-between; height: 138px; + box-sizing: border-box; + padding-top: 15px; & .noPadding { padding: 0px; diff --git a/src/components/header/header.js b/src/components/header/header.js index 482e0ca4fe..88901f2592 100644 --- a/src/components/header/header.js +++ b/src/components/header/header.js @@ -14,6 +14,7 @@ import styles from './header.css'; import CustomCountDown from './customCountDown'; import Options from '../dialog/options'; import routes from './../../constants/routes'; +import Piwik from '../../utils/piwik'; class Header extends React.Component { /* istanbul ignore next */ @@ -39,6 +40,8 @@ class Header extends React.Component { } openLogoutDialog() { + Piwik.trackingEvent('Header', 'button', 'Open logout dialog'); + this.props.setActiveDialog({ childComponent: Options, childComponentProps: { @@ -126,7 +129,7 @@ class Header extends React.Component { />
this.openLogoutDialog() }> + onClick={() => this.openLogoutDialog()}> {this.props.t('Logout')}
diff --git a/src/components/headerV2/headerV2.css b/src/components/headerV2/headerV2.css new file mode 100644 index 0000000000..24cb3c881b --- /dev/null +++ b/src/components/headerV2/headerV2.css @@ -0,0 +1,83 @@ +@import '../toolbox/buttons/css/base.css'; +@import '../app/variablesV2.css'; + +.wrapper { + align-items: center; + background: var(--color-background-header); + border-bottom: 1px solid var(--color-light-gray); + box-shadow: var(--box-shadow-header); + box-sizing: border-box; + display: flex; + height: var(--header-height-m); + justify-content: space-between; + padding: var(--header-padding); + position: absolute; + top: 0; + width: 100%; +} + +.logo { + height: var(--header-logo-size); + width: var(--header-logo-size); + + & > img { + display: block; + height: 100%; + width: 100%; + } +} + +.buttonsHolder { + display: flex; + justify-content: space-between; + + & > * { + margin-left: 24px; + } +} + +.settingButton > button { + font-size: var(--button-font-size-s); + height: var(--header-elements-height); + letter-spacing: 0.5px; + line-height: var(--header-elements-height); + min-height: var(--header-elements-height); + width: 144px; +} + +.dropdownHandler { + align-items: center; + background-color: var(--color-white); + border: 1px solid var(--color-light-gray); + border-radius: var(--border-radius-standard); + box-shadow: var(--box-shadow-standard); + box-sizing: border-box; + color: var(--color-content-light); + cursor: pointer; + display: flex; + font-family: var(--heading-font); + font-size: var(--button-font-size-s); + font-weight: var(--font-weight-bold); + height: var(--header-elements-height); + justify-content: space-between; + letter-spacing: 0.5px; + line-height: var(--header-elements-height); + min-height: var(--header-elements-height); + min-width: 144px; + padding: 0 24px; + position: relative; + width: auto; + + &::after { + border: 5px solid transparent; + border-bottom: 0; + border-top-color: var(--color-content-light); + content: ''; + } +} + +@media (--small-viewport) { + .wrapper { + height: var(--header-height-s); + } +} diff --git a/src/components/headerV2/headerV2.js b/src/components/headerV2/headerV2.js new file mode 100644 index 0000000000..4f43ba69bf --- /dev/null +++ b/src/components/headerV2/headerV2.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { translate } from 'react-i18next'; +import { SecondaryButtonV2 } from '../toolbox/buttons/button'; +import logo from '../../assets/images/lisk-logo-v2.svg'; +import routes from '../../constants/routes'; +import styles from './headerV2.css'; +import DropdownV2 from '../toolbox/dropdownV2/dropdownV2'; + +class HeaderV2 extends React.Component { + constructor() { + super(); + + this.state = { + showDropdown: false, + }; + + this.toggleDropdown = this.toggleDropdown.bind(this); + } + + toggleDropdown() { + const showDropdown = !this.state.showDropdown; + this.setState({ showDropdown }); + } + + render() { + const { + t, showSettings, showNetwork, networkList, + selectedNetwork, handleNetworkSelect, + } = this.props; + return ( +
+
+ +
+
+ {showSettings + && + {t('Settings')} + + } + {showNetwork + && + { networkList[selectedNetwork].label } + + {networkList && networkList.map((network, key) => ( + handleNetworkSelect(network.value)} + key={key}>{network.label} + ))} + + + } +
+
+ ); + } +} + +export default translate()(HeaderV2); diff --git a/src/components/headerV2/headerV2.test.js b/src/components/headerV2/headerV2.test.js new file mode 100644 index 0000000000..f8153c4556 --- /dev/null +++ b/src/components/headerV2/headerV2.test.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { spy } from 'sinon'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { MemoryRouter } from 'react-router-dom'; +import i18n from '../../i18n'; +import HeaderV2 from './headerV2'; + +describe('V2 Header', () => { + let wrapper; + const options = { + context: { i18n }, + childContextTypes: { + i18n: PropTypes.object.isRequired, + }, + }; + + const props = { + showSettings: true, + showNetwork: true, + selectedNetwork: 0, + networkList: [ + { label: 'Mainnet', value: 0 }, + { label: 'Testnet', value: 1 }, + { label: 'Custom Node', value: 2 }, + ], + handleNetworkSelect: spy(), + }; + + beforeEach(() => { + wrapper = mount( + + , options); + }); + + it('Should render Logo, Settings Button and Network Switcher dropdown', () => { + expect(wrapper.find('.logo')).to.be.present(); + expect(wrapper.find('.settingButton')).to.be.present(); + expect(wrapper.find('.dropdownHandler')).to.be.present(); + }); + + it('Should not render Network switcher dropdown and Settings Button', () => { + wrapper.setProps({ + children: React.cloneElement(wrapper.props().children, { + showSettings: false, + showNetwork: false, + }), + }); + expect(wrapper.find('.dropdownHandler')).to.not.be.present(); + expect(wrapper.find('.settingButton')).to.not.be.present(); + }); + + it('Should open dropdown on Network switcher click and close and call handler on option click', () => { + const { networkList, handleNetworkSelect } = props; + const randomNetwork = Math.floor(Math.random() * networkList.length); + expect(wrapper.find('.dropdownHandler')).to.be.present(); + wrapper.find('.dropdownHandler').simulate('click'); + expect(wrapper.find('DropdownV2')).to.have.prop('showDropdown', true); + wrapper.find('DropdownV2 .optionsHolder').children().at(randomNetwork).simulate('click'); + expect(handleNetworkSelect).to.have.been.calledWith(networkList[randomNetwork].value); + expect(wrapper.find('DropdownV2')).to.have.prop('showDropdown', false); + }); +}); diff --git a/src/components/help/help.js b/src/components/help/help.js index 38d474f4de..00ed6d96aa 100644 --- a/src/components/help/help.js +++ b/src/components/help/help.js @@ -4,11 +4,13 @@ import Box from '../box'; import { FontIcon } from '../fontIcon'; import { Button } from './../toolbox/buttons/button'; import styles from './help.css'; -import links from './../../constants/help'; +import links from './../../constants/externalLinks'; +import Piwik from '../../utils/piwik'; /* eslint-disable class-methods-use-this */ class Help extends React.Component { initOnboarding() { + Piwik.trackingEvent('Help', 'button', 'Init onboarding'); this.props.settingsUpdated({ onBoarding: true }); } diff --git a/src/components/help/help.test.js b/src/components/help/help.test.js index 282c9fe477..fcc95b6353 100644 --- a/src/components/help/help.test.js +++ b/src/components/help/help.test.js @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { shallow } from 'enzyme'; import { spy, mock } from 'sinon'; import Help from './help'; -import links from './../../constants/help'; +import links from './../../constants/externalLinks'; describe('Help Page', () => { let wrapper; diff --git a/src/components/hwWallet/accountCard.css b/src/components/hwWallet/accountCard.css index d10e9734dd..b4c8ac8f6a 100644 --- a/src/components/hwWallet/accountCard.css +++ b/src/components/hwWallet/accountCard.css @@ -7,6 +7,7 @@ background-color: #fff; box-shadow: 0 8px 16px 0 rgba(29, 72, 119, 0.16); margin: 10px; + cursor: pointer; & div { width: 100%; @@ -16,7 +17,6 @@ } .accountVisualWrapper { - cursor: pointer; margin: 30px 0; } diff --git a/src/components/hwWallet/accountCard.js b/src/components/hwWallet/accountCard.js index d95abf2a82..143aac09a2 100644 --- a/src/components/hwWallet/accountCard.js +++ b/src/components/hwWallet/accountCard.js @@ -4,17 +4,20 @@ import { fromRawLsk } from '../../utils/lsk'; import ToolBoxInput from '../toolbox/inputs/toolBoxInput'; import AccountVisual from '../accountVisual'; import CopyToClipboard from '../copyToClipboard'; +import keyCodes from './../../constants/keyCodes'; import styles from './accountCard.css'; const AccountCard = ({ account, hardwareAccountName, isEditMode, - changeInput, onClickHandler, index, + changeInput, onClickHandler, index, saveAccountNames, }) => ( -
-
{ onClickHandler(account, index); }}> +
{ + onClickHandler(account, index); + }}> +
LSK

{isEditMode ? -
+
{ + e.stopPropagation(); + }}> changeInput(value, account.address)} theme={styles} + onKeyDown={/* istanbul ignore next */(event) => { + if (event.keyCode === keyCodes.enter) saveAccountNames(); + }} value={hardwareAccountName}>
: null} diff --git a/src/components/hwWallet/hwWallet.js b/src/components/hwWallet/hwWallet.js index aa12db455f..b4071a422f 100644 --- a/src/components/hwWallet/hwWallet.js +++ b/src/components/hwWallet/hwWallet.js @@ -6,8 +6,10 @@ import UnlockWallet from './unlockWallet'; import LedgerLogin from './ledgerLoginHOC'; import getNetwork from '../../utils/getNetwork'; import { getAccountFromLedgerIndex } from '../../utils/ledger'; +import Piwik from '../../utils/piwik'; import { loginType } from '../../constants/hwConstants'; +import routes from '../../constants/routes'; import styles from './unlockWallet.css'; @@ -24,11 +26,13 @@ class HwWallet extends React.Component { } handleOnClick() { + Piwik.trackingEvent('HwWallet', 'button', 'Continue'); this.ledgerLogin(); } cancelLedgerLogin() { - this.setState({ isLedgerLogin: false }); + Piwik.trackingEvent('HwWallet', 'button', 'Cancel Ledger'); + this.props.history.push(`${routes.loginV2.path}`); } async ledgerLogin() { // eslint-disable-line max-statements diff --git a/src/components/hwWallet/index.js b/src/components/hwWallet/index.js index 19ea19f303..e84ca78f3f 100644 --- a/src/components/hwWallet/index.js +++ b/src/components/hwWallet/index.js @@ -3,11 +3,13 @@ import { withRouter } from 'react-router'; import { loadingStarted, loadingFinished } from '../../actions/loading'; import { liskAPIClientSet } from '../../actions/peers'; +import networks from '../../constants/networks'; + import HwWallet from './hwWallet'; const mapStateToProps = state => ({ - network: state.settings.network, - liskAPIClient: state.peers && state.peers.liskAPIClient, + network: state.settings.network || networks.mainnet.code, + peers: state.peers, }); const mapDispatchToProps = { diff --git a/src/components/hwWallet/ledgerLogin.css b/src/components/hwWallet/ledgerLogin.css index ec081b191c..594bc511a3 100644 --- a/src/components/hwWallet/ledgerLogin.css +++ b/src/components/hwWallet/ledgerLogin.css @@ -78,7 +78,7 @@ @for $i from 1 to 4 { .cube-$i { animation: float 2000ms 0.5ms infinite ease-in; - animation-delay: calc($i * 0.2) s; + animation-delay: calc($i * 0.2)s; z-index: calc(5 - $i); position: absolute; diff --git a/src/components/hwWallet/ledgerLogin.js b/src/components/hwWallet/ledgerLogin.js index fbdc9403fa..0fe7113223 100644 --- a/src/components/hwWallet/ledgerLogin.js +++ b/src/components/hwWallet/ledgerLogin.js @@ -4,6 +4,7 @@ import AccountCard from './accountCard'; import AddAccountCard from './addAccountCard'; import { FontIcon } from '../fontIcon'; import routes from '../../constants/routes'; +import Piwik from '../../utils/piwik'; import cubeImage from '../../assets/images/dark-blue-cube.svg'; import styles from './ledgerLogin.css'; @@ -36,7 +37,14 @@ class LedgerLogin extends React.Component { }, 2000); } + componentDidUpdate() { + if (this.props.account && this.props.account.address) { + this.props.history.replace(routes.dashboard.path); + } + } + selectAccount(ledgerAccount, index) { + Piwik.trackingEvent('LedgerLogin', 'button', 'Select account'); // set active peer this.props.liskAPIClientSet({ publicKey: ledgerAccount.publicKey, @@ -46,10 +54,10 @@ class LedgerLogin extends React.Component { derivationIndex: index, }, }); - this.props.history.replace(routes.dashboard.path); } async addAccount() { + Piwik.trackingEvent('LedgerLogin', 'button', 'Add account'); if (this.state.hwAccounts[this.state.hwAccounts.length - 1].isInitialized) { const output = await displayAccounts({ liskAPIClient: this.props.liskAPIClient, @@ -67,21 +75,23 @@ class LedgerLogin extends React.Component { } turnOnEditMode() { + Piwik.trackingEvent('LedgerLogin', 'button', 'Turn on edit mode'); this.setState({ isEditMode: true }); } saveAccountNames() { + Piwik.trackingEvent('LedgerLogin', 'button', 'Save account names'); this.props.settingsUpdated({ hardwareAccounts: this.state.hardwareAccountsName, }); this.setState({ isEditMode: !this.state.isEditMode }); } - changeAccountNameInput(value, account) { + changeAccountNameInput(value = '', account) { const newHardwareAccountsName = Object.assign( {}, this.state.hardwareAccountsName, - { [account]: value }, + { [account]: value.length < 20 ? value : value.substr(0, 20) }, ); this.setState({ hardwareAccountsName: newHardwareAccountsName }); } @@ -117,6 +127,7 @@ class LedgerLogin extends React.Component { key={`accountCard-${index}`} index={index} account={account} + saveAccountNames={this.saveAccountNames.bind(this)} changeInput={this.changeAccountNameInput.bind(this)} onClickHandler={this.selectAccount.bind(this)} /> ))} diff --git a/src/components/hwWallet/ledgerLogin.test.js b/src/components/hwWallet/ledgerLogin.test.js index 77c54fb0a8..9dbf72b9b2 100644 --- a/src/components/hwWallet/ledgerLogin.test.js +++ b/src/components/hwWallet/ledgerLogin.test.js @@ -38,6 +38,7 @@ describe('LedgerLogin', () => { loginType: loginType.ledger, settings: {}, t: () => {}, + history: { replace: () => {}, push: spy() }, }; wrapper = mount(, { storeState: store }); @@ -86,7 +87,7 @@ describe('LedgerLogin', () => { wrapper.find('AccountCard').props().changeInput(); expect(wrapper.state().hardwareAccountsName).to.eql({ '123L': 'test', - undefined, + undefined: '', }); }); diff --git a/src/components/login/login.css b/src/components/login/login.css index b7ce93b2fc..ca6b9735da 100644 --- a/src/components/login/login.css +++ b/src/components/login/login.css @@ -19,7 +19,24 @@ .hardwareWalletLink { cursor: pointer; - text-decoration: underline; + color: var(--color-primary-standard); + margin-left: 10px; + font-weight: 600; +} + +.feedback { + margin-top: 10px; +} + +.link { + color: var(--color-primary-standard); + font-weight: 600; + text-decoration: none; +} + +.ledgerRow { + display: flex; + justify-content: space-between; } .login { diff --git a/src/components/login/login.js b/src/components/login/login.js index f0232ab76c..e5dce66206 100644 --- a/src/components/login/login.js +++ b/src/components/login/login.js @@ -13,6 +13,7 @@ import PassphraseInput from '../passphraseInput'; import styles from './login.css'; import networks from '../../constants/networks'; import routes from '../../constants/routes'; +import feedbackLinks from '../../constants/feedbackLinks'; import getNetwork from '../../utils/getNetwork'; import { parseSearchParams } from './../../utils/searchParams'; import Box from '../box'; @@ -20,6 +21,7 @@ import Box from '../box'; import SignUp from './signUp'; import { validateUrl, addHttp, getAutoLogInData, findMatchingLoginNetwork } from '../../utils/login'; import { FontIcon } from '../fontIcon'; +import Piwik from '../../utils/piwik'; /** * The container component containing login @@ -108,6 +110,7 @@ class Login extends React.Component { } onLoginSubmission(passphrase) { + Piwik.trackingEvent('Login', 'button', 'Login submission'); const network = this.getNetwork(this.state.network); this.secondIteration = true; if (this.alreadyLoggedWithThisAddress(extractAddress(passphrase), network)) { @@ -166,19 +169,21 @@ class Login extends React.Component { return showNetworkParam === 'true' || (showNetwork && showNetworkParam !== 'false'); } - validateCorrectNode() { + validateCorrectNode(nextPath) { const { address } = this.state; const nodeURL = address !== '' ? addHttp(address) : address; - if (this.state.network === networks.customNode.code) { const liskAPIClient = new Lisk.APIClient([nodeURL], {}); liskAPIClient.node.getConstants() .then((res) => { if (res.data) { this.props.liskAPIClientSet({ - network: this.getNetwork(this.state.network), + network: { + ...this.getNetwork(this.state.network), + address: nodeURL, + }, }); - this.props.history.push(routes.register.path); + this.props.history.push(nextPath); } else { throw new Error(); } @@ -188,7 +193,7 @@ class Login extends React.Component { } else { const network = this.getNetwork(this.state.network); this.props.liskAPIClientSet({ network }); - this.props.history.push(routes.register.path); + this.props.history.push(nextPath); } } @@ -235,13 +240,30 @@ class Login extends React.Component { error={this.state.passphraseValidity} value={this.state.passphrase} onChange={this.changeHandler.bind(this, 'passphrase')} /> - {localStorage.getItem('ledger') ? -
{ - this.props.history.replace(routes.hwWallet.path); - }}> - Ledger Nano S - -
: null} + {this.props.settings && this.props.settings.isHarwareWalletConnected ? +
+
+
+ + {this.props.t('Hardware wallet login (beta):')} +
+
+ Ledger Nano S + +
+
+ +
: null }