mirror of
https://github.com/kremalicious/blowfish.git
synced 2024-11-15 09:35:14 +01:00
Merge pull request #60 from kremalicious/feature/next.js
Move to Next.js for renderer
This commit is contained in:
commit
00d96c594c
14
.babelrc
14
.babelrc
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/env",
|
|
||||||
{
|
|
||||||
"targets": {
|
|
||||||
"node": "current"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@babel/react"
|
|
||||||
],
|
|
||||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
# EditorConfig is awesome: http://EditorConfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
ETHERSCAN_API_KEY=
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,3 +4,6 @@ package-lock.json
|
|||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
coverage
|
coverage
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
.env
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "none"
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,8 @@ before_script:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- npm test || travis_terminate 1
|
- npm test || travis_terminate 1
|
||||||
- npm run dist
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
||||||
|
- npm run dist
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
|
@ -61,7 +61,7 @@ Alternatively, you can [build the app on your system](#build-packages).
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
The main app is a React app in `src/renderer/` wrapped within an Electron app defined in `src/main/`.
|
The main app is a React app authored with [Next.js](https://nextjs.org) in `src/renderer/` wrapped within an [Electron](https://www.electronjs.org) app defined in `src/main/`.
|
||||||
|
|
||||||
Clone, and run:
|
Clone, and run:
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ For the GitHub releases steps a GitHub personal access token, exported as `GITHU
|
|||||||
```text
|
```text
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Matthias Kretschmann
|
Copyright (c) 2020 Matthias Kretschmann
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
89
package.json
89
package.json
@ -5,18 +5,20 @@
|
|||||||
"description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.",
|
"description": "🐡 Simple Electron-based desktop app to retrieve and display your total Ocean Token balances.",
|
||||||
"main": "./src/main/index.js",
|
"main": "./src/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run lint && jest",
|
"start": "electron .",
|
||||||
"test:watch": "jest --watch",
|
"test": "npm run lint && npm run jest",
|
||||||
|
"test:watch": "npm run jest -- --watch",
|
||||||
|
"jest": "NODE_ENV=test jest -c tests/jest.config.js",
|
||||||
"lint": "eslint --ignore-path .gitignore ./src/**/*.{js,jsx} && stylelint --ignore-path .gitignore ./src/**/*.{css,scss}",
|
"lint": "eslint --ignore-path .gitignore ./src/**/*.{js,jsx} && stylelint --ignore-path .gitignore ./src/**/*.{css,scss}",
|
||||||
"start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
|
"copy:icons": "copy 'src/renderer/images/icon*' build/",
|
||||||
"build": "cross-env NODE_ENV=production webpack --config webpack.common.config.js",
|
"build:react": "cross-env NODE_ENV=production next build src/renderer && next export src/renderer && npm run copy:icons",
|
||||||
"package": "electron-builder build -ml -p never",
|
"build:electron": "electron-builder build -ml -p never",
|
||||||
"package:win": "electron-builder build -w -p never",
|
"build:electron:win": "electron-builder build -w -p never",
|
||||||
"dist": "./scripts/release-prepare.sh",
|
"dist": "./scripts/release-prepare.sh",
|
||||||
"release": "release-it --non-interactive",
|
"release": "release-it --non-interactive",
|
||||||
"changelog": "auto-changelog -p",
|
"changelog": "auto-changelog -p",
|
||||||
"format": "prettier --write 'src/**/*.{js,jsx}' && npm run format:css",
|
"format": "prettier --write --ignore-path .gitignore 'src/**/*.{js,jsx,json}' && npm run format:css",
|
||||||
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'"
|
"format:css": "prettier-stylelint --write --ignore-path .gitignore --quiet 'src/**/*.{css,scss}'"
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/kremalicious/blowfish.git",
|
"repository": "https://github.com/kremalicious/blowfish.git",
|
||||||
"homepage": "https://github.com/kremalicious/blowfish",
|
"homepage": "https://github.com/kremalicious/blowfish",
|
||||||
@ -27,61 +29,56 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coingecko/cryptoformat": "^0.3.3",
|
"@coingecko/cryptoformat": "^0.3.4",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"electron-is-dev": "^1.1.0",
|
||||||
|
"electron-next": "^3.1.5",
|
||||||
|
"electron-store": "^5.1.1",
|
||||||
"ethereum-address": "^0.0.4",
|
"ethereum-address": "^0.0.4",
|
||||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||||
"ms": "^2.1.2"
|
"ethjs-unit": "^0.1.6",
|
||||||
|
"ms": "^2.1.2",
|
||||||
|
"shortid": "^2.2.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.8.4",
|
"@babel/core": "^7.8.4",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
|
||||||
"@babel/preset-env": "^7.8.4",
|
"@babel/preset-env": "^7.8.4",
|
||||||
"@babel/preset-react": "^7.8.3",
|
|
||||||
"@jest-runner/electron": "^2.0.3",
|
"@jest-runner/electron": "^2.0.3",
|
||||||
"@reach/router": "^1.3.1",
|
|
||||||
"@react-mock/state": "^0.1.8",
|
"@react-mock/state": "^0.1.8",
|
||||||
"@svgr/webpack": "^5.0.0",
|
"@svgr/webpack": "^5.2.0",
|
||||||
"@testing-library/jest-dom": "^5.0.0",
|
"@testing-library/jest-dom": "^5.1.1",
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.1",
|
||||||
"auto-changelog": "^1.16.2",
|
"auto-changelog": "^1.16.2",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^25.1.0",
|
"babel-jest": "^25.1.0",
|
||||||
"babel-loader": "^8.0.6",
|
"copy": "^0.3.2",
|
||||||
"copy-webpack-plugin": "^5.1.1",
|
|
||||||
"cross-env": "^7.0.0",
|
"cross-env": "^7.0.0",
|
||||||
"css-loader": "^3.4.2",
|
"dotenv": "^8.2.0",
|
||||||
"electron": "^8.0.0",
|
"electron": "^8.0.1",
|
||||||
"electron-builder": "^22.3.2",
|
"electron-builder": "^22.3.2",
|
||||||
"electron-devtools-installer": "^2.2.4",
|
"electron-devtools-installer": "^2.2.4",
|
||||||
"electron-store": "^5.1.0",
|
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-prettier": "^6.10.0",
|
"eslint-config-prettier": "^6.10.0",
|
||||||
"eslint-plugin-react": "^7.18.3",
|
"eslint-plugin-react": "^7.18.3",
|
||||||
"file-loader": "^5.0.2",
|
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"next": "^9.2.2",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-pose": "^4.0.10",
|
"react-pose": "^4.0.10",
|
||||||
"release-it": "^12.4.3",
|
"release-it": "^12.6.1",
|
||||||
"style-loader": "^1.1.3",
|
"stylelint": "^13.2.0",
|
||||||
"stylelint": "^13.1.0",
|
"stylelint-config-css-modules": "^2.2.0",
|
||||||
"stylelint-config-css-modules": "^2.1.0",
|
"stylelint-config-standard": "^20.0.0"
|
||||||
"stylelint-config-standard": "^19.0.0",
|
|
||||||
"webpack": "^4.41.5",
|
|
||||||
"webpack-cli": "^3.3.10",
|
|
||||||
"webpack-dev-server": "^3.10.3"
|
|
||||||
},
|
},
|
||||||
"browserslist": "electron >= 6.0",
|
"browserslist": "electron >= 8.0",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.kremalicious.blowfish",
|
"appId": "com.kremalicious.blowfish",
|
||||||
"files": [
|
"files": [
|
||||||
"./build/**/*",
|
|
||||||
"./src/main/**/*",
|
"./src/main/**/*",
|
||||||
|
"./src/renderer/out/**/*",
|
||||||
"./src/*.js",
|
"./src/*.js",
|
||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
@ -126,29 +123,5 @@
|
|||||||
"npm": {
|
"npm": {
|
||||||
"publish": false
|
"publish": false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"rootDir": "src/renderer",
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.jsx?$": "babel-jest"
|
|
||||||
},
|
|
||||||
"moduleNameMapper": {
|
|
||||||
".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy",
|
|
||||||
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/jest/__mocks__/file-mock.js",
|
|
||||||
"\\.svg": "<rootDir>/jest/__mocks__/svgr-mock.js"
|
|
||||||
},
|
|
||||||
"testURL": "http://localhost",
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"<rootDir>/jest/setup-test-env.js"
|
|
||||||
],
|
|
||||||
"coverageDirectory": "../../coverage/",
|
|
||||||
"collectCoverage": true,
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"<rootDir>/**/*.{js,jsx}",
|
|
||||||
"!<rootDir>/jest/**/*.{js,jsx}",
|
|
||||||
"!**/node_modules/**"
|
|
||||||
],
|
|
||||||
"runner": "@jest-runner/electron",
|
|
||||||
"testEnvironment": "@jest-runner/electron/environment"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
rm -rf {dist,build}/ && \
|
rm -rf {dist,build,src/renderer/.next,src/renderer/out}/ && \
|
||||||
npm run build && \
|
npm run build:react && \
|
||||||
npm run package && \
|
npm run build:electron && \
|
||||||
|
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
@ -14,6 +14,5 @@ if [ -x "$(command -v docker)" ]; then
|
|||||||
-v ~/.cache/electron:/root/.cache/electron \
|
-v ~/.cache/electron:/root/.cache/electron \
|
||||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||||
electronuserland/builder:wine \
|
electronuserland/builder:wine \
|
||||||
/bin/bash -c "npm i && npm run build && npm run package:win"
|
/bin/bash -c "npm i && npm run build:react && npm run build:electron:win"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ const {
|
|||||||
nativeTheme,
|
nativeTheme,
|
||||||
ipcMain
|
ipcMain
|
||||||
} = require('electron')
|
} = require('electron')
|
||||||
|
const prepareNext = require('electron-next')
|
||||||
|
const isDev = require('electron-is-dev')
|
||||||
const pkg = require('../../package.json')
|
const pkg = require('../../package.json')
|
||||||
const buildMenu = require('./menu')
|
const buildMenu = require('./menu')
|
||||||
const { buildTouchbar, updateTouchbar } = require('./touchbar')
|
const { buildTouchbar, updateTouchbar } = require('./touchbar')
|
||||||
@ -13,16 +15,6 @@ const { rgbaToHex } = require('../utils')
|
|||||||
|
|
||||||
let mainWindow
|
let mainWindow
|
||||||
|
|
||||||
// Keep a reference for dev mode
|
|
||||||
let isDev = false
|
|
||||||
if (
|
|
||||||
process.defaultApp ||
|
|
||||||
/[\\/]electron-prebuilt[\\/]/.test(process.execPath) ||
|
|
||||||
/[\\/]electron[\\/]/.test(process.execPath)
|
|
||||||
) {
|
|
||||||
isDev = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = 640
|
const width = 640
|
||||||
const height = 450
|
const height = 450
|
||||||
|
|
||||||
@ -44,6 +36,7 @@ const createWindow = async () => {
|
|||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
scrollBounce: true,
|
scrollBounce: true,
|
||||||
enableBlinkFeatures: 'OverlayScrollbars'
|
enableBlinkFeatures: 'OverlayScrollbars'
|
||||||
}
|
}
|
||||||
@ -51,8 +44,8 @@ const createWindow = async () => {
|
|||||||
|
|
||||||
mainWindow.loadURL(
|
mainWindow.loadURL(
|
||||||
isDev
|
isDev
|
||||||
? 'http://localhost:8080'
|
? 'http://localhost:8000'
|
||||||
: `file://${path.join(__dirname, '../../build/index.html')}`
|
: `file://${path.join(__dirname, '../renderer/out/index.html')}`
|
||||||
)
|
)
|
||||||
|
|
||||||
createWindowEvents(mainWindow)
|
createWindowEvents(mainWindow)
|
||||||
@ -85,18 +78,22 @@ const createWindow = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', async () => {
|
||||||
createWindow()
|
await prepareNext('./src/renderer')
|
||||||
|
await createWindow()
|
||||||
|
|
||||||
mainWindow.webContents.on('dom-ready', () => {
|
mainWindow.webContents.on('dom-ready', () => {
|
||||||
switchTheme()
|
switchTheme()
|
||||||
switchAccentColor()
|
|
||||||
|
|
||||||
// add platform as class
|
// add platform as class
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
`document.getElementsByTagName('html')[0].classList.add('${process.platform}')`
|
`document.getElementsByTagName('html')[0].classList.add('${process.platform}')`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
switchAccentColor()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
// Quit when all windows are closed.
|
||||||
@ -138,22 +135,22 @@ const installDevTools = async mainWindow => {
|
|||||||
const createWindowEvents = mainWindow => {
|
const createWindowEvents = mainWindow => {
|
||||||
mainWindow.on('enter-full-screen', () =>
|
mainWindow.on('enter-full-screen', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'fullscreen\')'
|
'document.getElementsByTagName("html")[0].classList.add("fullscreen")'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('leave-full-screen', () =>
|
mainWindow.on('leave-full-screen', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'fullscreen\')'
|
'document.getElementsByTagName("html")[0].classList.remove("fullscreen")'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('blur', () =>
|
mainWindow.on('blur', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'blur\')'
|
'document.getElementsByTagName("html")[0].classList.add("blur")'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mainWindow.on('focus', () =>
|
mainWindow.on('focus', () =>
|
||||||
mainWindow.webContents.executeJavaScript(
|
mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'blur\')'
|
'document.getElementsByTagName("html")[0].classList.remove("blur")'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -197,10 +194,10 @@ const switchTheme = () => {
|
|||||||
|
|
||||||
isDarkMode
|
isDarkMode
|
||||||
? mainWindow.webContents.executeJavaScript(
|
? mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.add(\'dark\')'
|
'document.getElementsByTagName("html")[0].classList.add("dark")'
|
||||||
)
|
)
|
||||||
: mainWindow.webContents.executeJavaScript(
|
: mainWindow.webContents.executeJavaScript(
|
||||||
'document.getElementsByTagName(\'html\')[0].classList.remove(\'dark\')'
|
'document.getElementsByTagName("html")[0].classList.remove("dark")'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/main/preload.js
Normal file
12
src/main/preload.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const { webFrame, ipcRenderer } = require('electron')
|
||||||
|
const Store = require('electron-store')
|
||||||
|
|
||||||
|
const store = new Store()
|
||||||
|
|
||||||
|
// Since we disabled nodeIntegration we can reintroduce
|
||||||
|
// needed node functionality here
|
||||||
|
process.once('loaded', () => {
|
||||||
|
global.ipcRenderer = ipcRenderer
|
||||||
|
global.webFrame = webFrame
|
||||||
|
global.store = store
|
||||||
|
})
|
@ -8,7 +8,7 @@ const createButton = (
|
|||||||
value,
|
value,
|
||||||
key,
|
key,
|
||||||
mainWindow,
|
mainWindow,
|
||||||
accentColor,
|
accentColor = '#f6388a',
|
||||||
currentCurrency = 'ocean'
|
currentCurrency = 'ocean'
|
||||||
) =>
|
) =>
|
||||||
new TouchBarButton({
|
new TouchBarButton({
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import React, { useEffect } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {
|
|
||||||
Router,
|
|
||||||
createMemorySource,
|
|
||||||
createHistory,
|
|
||||||
LocationProvider,
|
|
||||||
navigate
|
|
||||||
} from '@reach/router'
|
|
||||||
import { webFrame, ipcRenderer } from 'electron'
|
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
|
||||||
import Titlebar from './components/Titlebar'
|
|
||||||
import { defaultAnimation } from './components/Animations'
|
|
||||||
import Home from './screens/Home'
|
|
||||||
import Preferences from './screens/Preferences'
|
|
||||||
import styles from './App.module.css'
|
|
||||||
|
|
||||||
//
|
|
||||||
// Disable zooming
|
|
||||||
//
|
|
||||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
|
||||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
|
||||||
|
|
||||||
const Animation = posed.div(defaultAnimation)
|
|
||||||
|
|
||||||
// Fix reach-router in packaged Electron
|
|
||||||
// https://github.com/reach/router/issues/25#issuecomment-394003652
|
|
||||||
let source = createMemorySource('/')
|
|
||||||
let history = createHistory(source)
|
|
||||||
|
|
||||||
const PosedRouter = ({ children }) => (
|
|
||||||
<LocationProvider history={history}>
|
|
||||||
{({ location }) => (
|
|
||||||
<PoseGroup animateOnMount>
|
|
||||||
<Animation key={location.key}>
|
|
||||||
<Router location={location}>{children}</Router>
|
|
||||||
</Animation>
|
|
||||||
</PoseGroup>
|
|
||||||
)}
|
|
||||||
</LocationProvider>
|
|
||||||
)
|
|
||||||
|
|
||||||
PosedRouter.propTypes = {
|
|
||||||
children: PropTypes.any.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
useEffect(() => {
|
|
||||||
ipcRenderer.on('goTo', (evt, route) => {
|
|
||||||
navigate(route)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{process.platform === 'darwin' && <Titlebar />}
|
|
||||||
<div className={styles.app}>
|
|
||||||
<PosedRouter>
|
|
||||||
<Home path="/" default />
|
|
||||||
<Preferences path="/preferences" />
|
|
||||||
</PosedRouter>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render } from '@testing-library/react'
|
|
||||||
import AppProvider from './store/AppProvider'
|
|
||||||
import App from './App'
|
|
||||||
|
|
||||||
describe('App', () => {
|
|
||||||
it('renders correctly', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<AppProvider>
|
|
||||||
<App />
|
|
||||||
</AppProvider>
|
|
||||||
)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
26
src/renderer/Layout.jsx
Normal file
26
src/renderer/Layout.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
|
import shortid from 'shortid'
|
||||||
|
import { defaultAnimation } from './components/Animations'
|
||||||
|
import Titlebar from './components/Titlebar'
|
||||||
|
import styles from './Layout.module.css'
|
||||||
|
|
||||||
|
const Animation = posed.div(defaultAnimation)
|
||||||
|
|
||||||
|
export default function Layout({ children }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{process.platform === 'darwin' && <Titlebar />}
|
||||||
|
<div className={styles.app}>
|
||||||
|
<PoseGroup animateOnMount>
|
||||||
|
<Animation key={shortid.generate()}>{children}</Animation>
|
||||||
|
</PoseGroup>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.propTypes = {
|
||||||
|
children: PropTypes.any.isRequired
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { openUrl } from '../../../utils'
|
import { openUrl } from '../../../utils'
|
||||||
import Balance from '../../components/Balance'
|
import Balance from '../Balance'
|
||||||
import { AppContext } from '../../store/createContext'
|
import { AppContext } from '../../store/createContext'
|
||||||
import styles from './Accounts.module.css'
|
import styles from './Accounts.module.css'
|
||||||
|
|
5
src/renderer/components/Home/Accounts.module.css
Normal file
5
src/renderer/components/Home/Accounts.module.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.accounts {
|
||||||
|
composes: balancewrap from '../../pages/index.module.css';
|
||||||
|
min-height: 55px;
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
import { AppContext } from '../../store/createContext'
|
import { AppContext, PriceContext } from '../../store/createContext'
|
||||||
import { cryptoFormatter } from '../../../utils'
|
import { cryptoFormatter } from '../../../utils'
|
||||||
import stylesIndex from './index.module.css'
|
import stylesIndex from '../../pages/index.module.css'
|
||||||
import styles from './Ticker.module.css'
|
import styles from './Ticker.module.css'
|
||||||
import { fadeIn } from '../../components/Animations'
|
import { fadeIn } from '../Animations'
|
||||||
|
|
||||||
const Item = posed.div(fadeIn)
|
const Item = posed.div(fadeIn)
|
||||||
|
|
||||||
const Change = ({ currency }) => {
|
const Change = ({ currency }) => {
|
||||||
const { priceChanges } = useContext(AppContext)
|
const { priceChanges } = useContext(PriceContext)
|
||||||
const isNegative = JSON.stringify(priceChanges[currency]).startsWith('-')
|
const isNegative = JSON.stringify(priceChanges[currency]).startsWith('-')
|
||||||
let classes = isNegative ? styles.negative : styles.positive
|
let classes = isNegative ? styles.negative : styles.positive
|
||||||
|
|
||||||
@ -27,13 +27,10 @@ Change.propTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Items = () => {
|
const Items = () => {
|
||||||
const {
|
const { prices } = useContext(PriceContext)
|
||||||
prices,
|
const { needsConfig, currency, toggleCurrencies, accentColor } = useContext(
|
||||||
needsConfig,
|
AppContext
|
||||||
currency,
|
)
|
||||||
toggleCurrencies,
|
|
||||||
accentColor
|
|
||||||
} = useContext(AppContext)
|
|
||||||
|
|
||||||
const activeStyle = {
|
const activeStyle = {
|
||||||
backgroundColor: accentColor,
|
backgroundColor: accentColor,
|
||||||
@ -60,7 +57,7 @@ const Items = () => {
|
|||||||
|
|
||||||
const Ticker = props => (
|
const Ticker = props => (
|
||||||
<footer className={styles.ticker} {...props}>
|
<footer className={styles.ticker} {...props}>
|
||||||
<div className={stylesIndex.balanceWrap}>
|
<div className={stylesIndex.balancewrap}>
|
||||||
<PoseGroup animateOnMount>
|
<PoseGroup animateOnMount>
|
||||||
<Items key="items" />
|
<Items key="items" />
|
||||||
</PoseGroup>
|
</PoseGroup>
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { AppContext } from '../../store/createContext'
|
import { AppContext } from '../../store/createContext'
|
||||||
import Balance from '../../components/Balance'
|
import Balance from '../Balance'
|
||||||
import { conversions } from '../../../config'
|
import { conversions } from '../../../config'
|
||||||
|
|
||||||
const calculateTotalBalance = (accounts, currency) => {
|
const calculateTotalBalance = (accounts, currency) => {
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { Link } from '@reach/router'
|
import Link from 'next/link'
|
||||||
import { AppContext } from '../../store/createContext'
|
import { AppContext } from '../../store/createContext'
|
||||||
import IconRocket from '../../images/rocket.svg'
|
import IconRocket from '../../images/rocket.svg'
|
||||||
import styles from './Welcome.module.css'
|
import styles from './Welcome.module.css'
|
||||||
@ -10,8 +10,10 @@ const Welcome = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.welcome}>
|
<div className={styles.welcome}>
|
||||||
<IconRocket />
|
<IconRocket />
|
||||||
<Link style={{ color: accentColor }} to="preferences">
|
<Link href="/preferences">
|
||||||
Add your first address to get started.
|
<a style={{ color: accentColor }}>
|
||||||
|
Add your first address to get started.
|
||||||
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { toDataUrl } from 'ethereum-blockies'
|
import { toDataUrl } from 'ethereum-blockies'
|
||||||
import posed, { PoseGroup } from 'react-pose'
|
import posed, { PoseGroup } from 'react-pose'
|
||||||
import { fadeIn } from '../../../components/Animations'
|
import { fadeIn } from '../../Animations'
|
||||||
import styles from './Saved.module.css'
|
import styles from './Saved.module.css'
|
||||||
|
|
||||||
export default function Saved({ accounts, handleDelete }) {
|
export default function Saved({ accounts, handleDelete }) {
|
@ -1,15 +1,15 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Store from 'electron-store'
|
|
||||||
import ethereum_address from 'ethereum-address'
|
import ethereum_address from 'ethereum-address'
|
||||||
|
import Store from 'electron-store'
|
||||||
import { AppContext } from '../../../store/createContext'
|
import { AppContext } from '../../../store/createContext'
|
||||||
import Saved from './Saved'
|
import Saved from './Saved'
|
||||||
import New from './New'
|
import New from './New'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
export default class Accounts extends PureComponent {
|
export default class AccountsList extends PureComponent {
|
||||||
static contextType = AppContext
|
static contextType = AppContext
|
||||||
|
|
||||||
store = new Store()
|
store = process.env.NODE_ENV === 'test' ? new Store() : global.store
|
||||||
|
|
||||||
state = { accounts: [], input: '', error: '' }
|
state = { accounts: [], input: '', error: '' }
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
.preference {
|
.preference {
|
||||||
composes: box from '../../../components/Box.module.css';
|
composes: box from '../../Box.module.css';
|
||||||
-webkit-app-region: none;
|
-webkit-app-region: none;
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ html.fullscreen {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#__next {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@ -43,7 +43,7 @@ html.fullscreen {
|
|||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark #root {
|
.dark #__next {
|
||||||
color: #e2e2e2;
|
color: #e2e2e2;
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render } from 'react-dom'
|
|
||||||
import AppProvider from './store/AppProvider'
|
|
||||||
import App from './App'
|
|
||||||
import pkg from '../../package.json'
|
|
||||||
import './index.css'
|
|
||||||
|
|
||||||
document.body.style.backgroundColor = '#141414'
|
|
||||||
|
|
||||||
// Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it
|
|
||||||
let root = document.createElement('div')
|
|
||||||
root.id = 'root'
|
|
||||||
document.body.appendChild(root)
|
|
||||||
document.title = pkg.productName
|
|
||||||
|
|
||||||
render(
|
|
||||||
<AppProvider>
|
|
||||||
<App />
|
|
||||||
</AppProvider>,
|
|
||||||
document.getElementById('root')
|
|
||||||
)
|
|
48
src/renderer/next.config.js
Normal file
48
src/renderer/next.config.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const withSvgr = (nextConfig = {}) => {
|
||||||
|
return Object.assign({}, nextConfig, {
|
||||||
|
webpack(config, options) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: '@svgr/webpack',
|
||||||
|
options: {
|
||||||
|
icon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof nextConfig.webpack === 'function') {
|
||||||
|
return nextConfig.webpack(config, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const withElectron = (nextConfig = {}) => {
|
||||||
|
return Object.assign({}, nextConfig, {
|
||||||
|
webpack: config => {
|
||||||
|
config.target = 'electron-renderer'
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = withSvgr(
|
||||||
|
withElectron({
|
||||||
|
env: {
|
||||||
|
ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY
|
||||||
|
},
|
||||||
|
exportPathMap() {
|
||||||
|
return {
|
||||||
|
'/': { page: '/' },
|
||||||
|
'/preferences': { page: '/preferences' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
32
src/renderer/pages/_app.jsx
Normal file
32
src/renderer/pages/_app.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Router from 'next/router'
|
||||||
|
// import { ipcRenderer } from 'electron'
|
||||||
|
import AppProvider from '../store/AppProvider'
|
||||||
|
import PriceProvider from '../store/PriceProvider'
|
||||||
|
import Layout from '../Layout'
|
||||||
|
|
||||||
|
import '../global.css'
|
||||||
|
|
||||||
|
export default function App({ Component, pageProps }) {
|
||||||
|
useEffect(() => {
|
||||||
|
global.ipcRenderer.on('goTo', (evt, route) => {
|
||||||
|
Router.push(route)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PriceProvider>
|
||||||
|
<AppProvider>
|
||||||
|
<Layout>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Layout>
|
||||||
|
</AppProvider>
|
||||||
|
</PriceProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
Component: PropTypes.any.isRequired,
|
||||||
|
pageProps: PropTypes.any.isRequired
|
||||||
|
}
|
47
src/renderer/pages/index.jsx
Normal file
47
src/renderer/pages/index.jsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React, { useContext } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { AppContext } from '../store/createContext'
|
||||||
|
import Welcome from '../components/Home/Welcome'
|
||||||
|
import Spinner from '../components/Spinner'
|
||||||
|
import Divider from '../components/Divider'
|
||||||
|
import Total from '../components/Home/Total'
|
||||||
|
import Ticker from '../components/Home/Ticker'
|
||||||
|
import Accounts from '../components/Home/Accounts'
|
||||||
|
import IconCog from '../images/cog.svg'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
function MainContent() {
|
||||||
|
const { isLoading, error, needsConfig } = useContext(AppContext)
|
||||||
|
|
||||||
|
return needsConfig ? (
|
||||||
|
<Welcome />
|
||||||
|
) : error ? (
|
||||||
|
<div className={styles.error}>{error}</div>
|
||||||
|
) : isLoading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Total />
|
||||||
|
<Divider />
|
||||||
|
<Accounts />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Link href="/preferences">
|
||||||
|
<a className={styles.preferences}>
|
||||||
|
<IconCog />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<MainContent />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Ticker />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
.main {
|
.main {
|
||||||
composes: box from '../../components/Box.module.css';
|
composes: box from '../components/Box.module.css';
|
||||||
min-height: 222px;
|
min-height: 222px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -25,10 +25,15 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balanceWrap {
|
.balancewrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 0.5rem;
|
grid-gap: 0.5rem;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
||||||
justify-items: start;
|
justify-items: start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: lightcoral;
|
||||||
|
}
|
18
src/renderer/pages/preferences.jsx
Normal file
18
src/renderer/pages/preferences.jsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import AccountsList from '../components/Preferences/AccountsList'
|
||||||
|
import styles from './preferences.module.css'
|
||||||
|
|
||||||
|
export default function Preferences() {
|
||||||
|
return (
|
||||||
|
<div className={styles.preferences}>
|
||||||
|
<h1 className={styles.title}>Preferences</h1>{' '}
|
||||||
|
<Link href="/">
|
||||||
|
<a className={styles.close} title="Close Preferences">
|
||||||
|
×
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<AccountsList />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
.accounts {
|
|
||||||
composes: balanceWrap from './index.module.css';
|
|
||||||
min-height: 55px;
|
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import React, { useContext } from 'react'
|
|
||||||
import { Link } from '@reach/router'
|
|
||||||
import { AppContext } from '../../store/createContext'
|
|
||||||
import Welcome from './Welcome'
|
|
||||||
import Spinner from '../../components/Spinner'
|
|
||||||
import Divider from '../../components/Divider'
|
|
||||||
import Total from './Total'
|
|
||||||
import Ticker from './Ticker'
|
|
||||||
import Accounts from './Accounts'
|
|
||||||
import IconCog from '../../images/cog.svg'
|
|
||||||
import styles from './index.module.css'
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
const { isLoading, needsConfig } = useContext(AppContext)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<main className={styles.main}>
|
|
||||||
<Link className={styles.preferences} to="/preferences">
|
|
||||||
<IconCog />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{needsConfig ? (
|
|
||||||
<Welcome />
|
|
||||||
) : isLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Total />
|
|
||||||
<Divider />
|
|
||||||
<Accounts />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<Ticker style={isLoading ? { opacity: 0 } : null} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
|
||||||
import { AppContext } from '../../store/createContext'
|
|
||||||
import context from '../../jest/__fixtures__/context'
|
|
||||||
import Home from '.'
|
|
||||||
|
|
||||||
describe('Home', () => {
|
|
||||||
it('renders correctly', () => {
|
|
||||||
const { container, getByText } = render(
|
|
||||||
<AppContext.Provider value={context}>
|
|
||||||
<Home />
|
|
||||||
</AppContext.Provider>
|
|
||||||
)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
|
||||||
fireEvent.click(getByText(/Ξ/))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders Welcome without config', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<AppContext.Provider value={{ ...context, needsConfig: true }}>
|
|
||||||
<Home />
|
|
||||||
</AppContext.Provider>
|
|
||||||
)
|
|
||||||
expect(container.firstChild).toHaveTextContent(
|
|
||||||
'Add your first address to get started.'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,16 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Link } from '@reach/router'
|
|
||||||
import Accounts from './Accounts'
|
|
||||||
import styles from './index.module.css'
|
|
||||||
|
|
||||||
const Preferences = () => (
|
|
||||||
<div className={styles.preferences}>
|
|
||||||
<h1 className={styles.title}>Preferences</h1>{' '}
|
|
||||||
<Link className={styles.close} title="Close Preferences" to="/">
|
|
||||||
×
|
|
||||||
</Link>
|
|
||||||
<Accounts />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Preferences
|
|
@ -1,120 +1,64 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { useContext, useState, useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
import { ipcRenderer } from 'electron'
|
// import { ipcRenderer } from 'electron'
|
||||||
import Store from 'electron-store'
|
import { AppContext, PriceContext } from './createContext'
|
||||||
import { AppContext } from './createContext'
|
import { refreshInterval, conversions } from '../../config'
|
||||||
import fetchData from '../utils/fetch'
|
import { getAccounts, getBalance } from './helpers'
|
||||||
import { refreshInterval, conversions, oceanTokenContract } from '../../config'
|
|
||||||
|
|
||||||
// construct initial prices Map to get consistent
|
export default function AppProvider({ children }) {
|
||||||
// order for Ticker and Touchbar
|
const { prices } = useContext(PriceContext)
|
||||||
let pricesMap = new Map()
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
pricesMap.set('ocean', 1)
|
const [accounts, setAccounts] = useState([])
|
||||||
conversions.map(key => pricesMap.set(key, 0))
|
const [needsConfig, setNeedsConfig] = useState(false)
|
||||||
|
const [currency, setCurrency] = useState('ocean')
|
||||||
|
const [accentColor, setAccentColor] = useState('#f6388a')
|
||||||
|
const [error, setError] = useState()
|
||||||
|
|
||||||
export default class AppProvider extends PureComponent {
|
useEffect(() => {
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.any.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
store = new Store()
|
|
||||||
|
|
||||||
state = {
|
|
||||||
isLoading: true,
|
|
||||||
accounts: [],
|
|
||||||
currency: 'ocean',
|
|
||||||
needsConfig: false,
|
|
||||||
prices: pricesMap,
|
|
||||||
priceChanges: Object.assign(
|
|
||||||
...conversions.map(key => ({
|
|
||||||
[key]: 0
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
toggleCurrencies: currency => this.toggleCurrencies(currency),
|
|
||||||
setBalances: () => this.setBalances(),
|
|
||||||
accentColor: '#f6388a'
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
// listener for accent color
|
// listener for accent color
|
||||||
ipcRenderer.on('accent-color', (event, accentColor) => {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
this.setState({ accentColor })
|
global.ipcRenderer.on('accent-color', (evt, accentColor) => {
|
||||||
})
|
setAccentColor(accentColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// listener for touchbar
|
useEffect(() => {
|
||||||
ipcRenderer.on('setCurrency', (evt, currency) =>
|
async function init() {
|
||||||
this.state.toggleCurrencies(currency)
|
try {
|
||||||
)
|
await setBalances()
|
||||||
|
setIsLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message)
|
||||||
|
setError(error.message)
|
||||||
|
}
|
||||||
|
|
||||||
const newPrizes = await this.fetchAndSetPrices()
|
// listener for touchbar
|
||||||
this.setState({ prices: newPrizes })
|
global.ipcRenderer.on('setCurrency', (evt, currency) =>
|
||||||
|
toggleCurrencies(currency)
|
||||||
await this.setBalances()
|
)
|
||||||
|
|
||||||
setInterval(this.fetchAndSetPrices, ms(refreshInterval))
|
|
||||||
setInterval(this.setBalances, ms(refreshInterval))
|
|
||||||
|
|
||||||
this.setState({ isLoading: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccounts() {
|
|
||||||
let accountsPref
|
|
||||||
|
|
||||||
if (this.store.has('accounts')) {
|
|
||||||
accountsPref = this.store.get('accounts')
|
|
||||||
|
|
||||||
!accountsPref.length
|
|
||||||
? this.setState({ needsConfig: true })
|
|
||||||
: this.setState({ needsConfig: false })
|
|
||||||
} else {
|
|
||||||
accountsPref = []
|
|
||||||
this.setState({ needsConfig: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accountsPref
|
init()
|
||||||
}
|
setInterval(init, ms(refreshInterval))
|
||||||
|
|
||||||
async getBalance(account) {
|
return () => {
|
||||||
const json = await fetchData(
|
clearInterval(init)
|
||||||
`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}&tag=latest`
|
}
|
||||||
)
|
}, [prices])
|
||||||
|
|
||||||
const balance = json.result / 1e18 // Convert from vodka 10^18
|
|
||||||
return balance
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchAndSetPrices = async () => {
|
|
||||||
const currencies = conversions.join(',')
|
|
||||||
const json = await fetchData(
|
|
||||||
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}&include_24hr_change=true`
|
|
||||||
)
|
|
||||||
|
|
||||||
let newPrices = new Map(this.state.prices) // make a shallow copy of the Map
|
|
||||||
conversions.map(key => newPrices.set(key, json['ocean-protocol'][key])) // modify the copy
|
|
||||||
|
|
||||||
const newPriceChanges = await Object.assign(
|
|
||||||
...conversions.map(key => ({
|
|
||||||
[key]: json['ocean-protocol'][key + '_24h_change']
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
ipcRenderer.send('prices-updated', Array.from(newPrices)) // convert Map to array, ipc messages seem to kill it
|
|
||||||
this.setState({ prices: newPrices, priceChanges: newPriceChanges })
|
|
||||||
return newPrices
|
|
||||||
}
|
|
||||||
|
|
||||||
setBalances = async () => {
|
|
||||||
const accountsPref = await this.getAccounts()
|
|
||||||
|
|
||||||
|
async function setBalances() {
|
||||||
let newAccounts = []
|
let newAccounts = []
|
||||||
|
const { needsConfig, accountsPref } = await getAccounts()
|
||||||
|
setNeedsConfig(needsConfig)
|
||||||
|
|
||||||
for (const account of accountsPref) {
|
for (const account of accountsPref) {
|
||||||
const oceanBalance = await this.getBalance(account)
|
const oceanBalance = await getBalance(account)
|
||||||
|
|
||||||
const conversionsBalance = Object.assign(
|
const conversionsBalance = Object.assign(
|
||||||
...conversions.map(key => ({
|
...conversions.map(key => ({
|
||||||
[key]: oceanBalance * this.state.prices.get(key) || 0
|
[key]: oceanBalance * prices.get(key) || 0
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,22 +73,31 @@ export default class AppProvider extends PureComponent {
|
|||||||
newAccounts.push(newAccount)
|
newAccounts.push(newAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAccounts !== this.state.accounts) {
|
if (newAccounts !== accounts) {
|
||||||
this.setState({ accounts: newAccounts })
|
setAccounts(newAccounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCurrencies(currency) {
|
function toggleCurrencies(currency) {
|
||||||
const pricesNew = Array.from(this.state.prices)
|
setCurrency(currency)
|
||||||
ipcRenderer.send('currency-updated', pricesNew, currency)
|
const pricesNew = Array.from(prices)
|
||||||
this.setState({ currency })
|
global.ipcRenderer.send('currency-updated', pricesNew, currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const context = {
|
||||||
return (
|
isLoading,
|
||||||
<AppContext.Provider value={this.state}>
|
accounts,
|
||||||
{this.props.children}
|
currency,
|
||||||
</AppContext.Provider>
|
needsConfig,
|
||||||
)
|
accentColor,
|
||||||
|
error,
|
||||||
|
toggleCurrencies,
|
||||||
|
setBalances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <AppContext.Provider value={context}>{children}</AppContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
AppProvider.propTypes = {
|
||||||
|
children: PropTypes.any.isRequired
|
||||||
}
|
}
|
||||||
|
53
src/renderer/store/PriceProvider.jsx
Normal file
53
src/renderer/store/PriceProvider.jsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ms from 'ms'
|
||||||
|
import { PriceContext } from './createContext'
|
||||||
|
import { refreshInterval, conversions } from '../../config'
|
||||||
|
import { fetchAndSetPrices } from './helpers'
|
||||||
|
|
||||||
|
export default function PriceProvider({ children }) {
|
||||||
|
// construct initial prices Map to get consistent
|
||||||
|
// order for Ticker and Touchbar
|
||||||
|
let pricesMap = new Map()
|
||||||
|
pricesMap.set('ocean', 1)
|
||||||
|
conversions.map(key => pricesMap.set(key, 0))
|
||||||
|
|
||||||
|
const [prices, setPrices] = useState(pricesMap)
|
||||||
|
const [priceChanges, setPriceChanges] = useState(
|
||||||
|
Object.assign(
|
||||||
|
...conversions.map(key => ({
|
||||||
|
[key]: 0
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function init() {
|
||||||
|
try {
|
||||||
|
const { newPrices, newPriceChanges } = await fetchAndSetPrices(prices)
|
||||||
|
setPrices(newPrices)
|
||||||
|
setPriceChanges(newPriceChanges)
|
||||||
|
global.ipcRenderer.send('prices-updated', Array.from(newPrices)) // convert Map to array, ipc messages seem to kill it
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
|
setInterval(init, ms(refreshInterval))
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(init)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PriceContext.Provider value={{ prices, priceChanges }}>
|
||||||
|
{children}
|
||||||
|
</PriceContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceProvider.propTypes = {
|
||||||
|
children: PropTypes.any.isRequired
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
|
||||||
const AppContext = createContext()
|
const AppContext = createContext()
|
||||||
|
const PriceContext = createContext()
|
||||||
|
|
||||||
export { AppContext }
|
export { AppContext, PriceContext }
|
||||||
|
47
src/renderer/store/helpers.js
Normal file
47
src/renderer/store/helpers.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import Store from 'electron-store'
|
||||||
|
import unit from 'ethjs-unit'
|
||||||
|
import { fetchData } from '../../utils'
|
||||||
|
import { oceanTokenContract, conversions } from '../../config'
|
||||||
|
|
||||||
|
export async function fetchAndSetPrices(prices) {
|
||||||
|
const currencies = conversions.join(',')
|
||||||
|
const json = await fetchData(
|
||||||
|
`https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}&include_24hr_change=true`
|
||||||
|
)
|
||||||
|
|
||||||
|
let newPrices = new Map(prices) // make a shallow copy of the Map
|
||||||
|
conversions.map(key => newPrices.set(key, json['ocean-protocol'][key])) // modify the copy
|
||||||
|
|
||||||
|
const newPriceChanges = await Object.assign(
|
||||||
|
...conversions.map(key => ({
|
||||||
|
[key]: json['ocean-protocol'][key + '_24h_change']
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return { newPrices, newPriceChanges }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBalance(account) {
|
||||||
|
const json = await fetchData(
|
||||||
|
`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${oceanTokenContract}&address=${account}&tag=latest&apikey=${process.env.ETHERSCAN_API_KEY}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const balance = unit.fromWei(`${json.result}`, 'ether')
|
||||||
|
return balance
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAccounts() {
|
||||||
|
let needsConfig
|
||||||
|
let accountsPref
|
||||||
|
const store = process.env.NODE_ENV === 'test' ? new Store() : global.store
|
||||||
|
|
||||||
|
if (store.has('accounts')) {
|
||||||
|
accountsPref = store.get('accounts')
|
||||||
|
needsConfig = !accountsPref.length
|
||||||
|
} else {
|
||||||
|
accountsPref = []
|
||||||
|
needsConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return { needsConfig, accountsPref }
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
const fetchData = async url => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url)
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
return console.log('Non-200 response: ' + response.status) // eslint-disable-line
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = await response.json()
|
|
||||||
if (!json) return
|
|
||||||
|
|
||||||
return json
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Error parsing json:' + error) // eslint-disable-line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fetchData
|
|
18
src/utils.js
18
src/utils.js
@ -1,5 +1,20 @@
|
|||||||
const { app, shell } = require('electron')
|
const { app, shell } = require('electron')
|
||||||
const { formatCurrency } = require('@coingecko/cryptoformat')
|
const { formatCurrency } = require('@coingecko/cryptoformat')
|
||||||
|
const axios = require('axios')
|
||||||
|
|
||||||
|
const fetchData = async url => {
|
||||||
|
try {
|
||||||
|
const response = await axios(url)
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return console.error('Non-200 response: ' + response.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing json: ' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isFiat = currency => currency === 'eur' || currency === 'usd'
|
const isFiat = currency => currency === 'eur' || currency === 'usd'
|
||||||
|
|
||||||
@ -65,5 +80,6 @@ module.exports = {
|
|||||||
rgbaToHex,
|
rgbaToHex,
|
||||||
locale,
|
locale,
|
||||||
numberFormatter,
|
numberFormatter,
|
||||||
cryptoFormatter
|
cryptoFormatter,
|
||||||
|
fetchData
|
||||||
}
|
}
|
||||||
|
10
tests/Layout.test.jsx
Normal file
10
tests/Layout.test.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import Layout from '../src/renderer/Layout'
|
||||||
|
|
||||||
|
describe('Layout', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(<Layout>Hello</Layout>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
22
tests/Providers.test.jsx
Normal file
22
tests/Providers.test.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, waitForElement } from '@testing-library/react'
|
||||||
|
import AppProvider from '../src/renderer/store/AppProvider'
|
||||||
|
import PriceProvider from '../src/renderer/store/PriceProvider'
|
||||||
|
import { PriceContext } from '../src/renderer/store/createContext'
|
||||||
|
import { priceContext } from './__fixtures__/context'
|
||||||
|
|
||||||
|
describe('Providers', () => {
|
||||||
|
it('PriceProvider', async () => {
|
||||||
|
const { getByText } = render(<PriceProvider>Hello</PriceProvider>)
|
||||||
|
await waitForElement(() => getByText('Hello'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('AppProvider', async () => {
|
||||||
|
const { getByText } = render(
|
||||||
|
<PriceContext.Provider value={priceContext}>
|
||||||
|
<AppProvider>Hello</AppProvider>
|
||||||
|
</PriceContext.Provider>
|
||||||
|
)
|
||||||
|
await waitForElement(() => getByText('Hello'))
|
||||||
|
})
|
||||||
|
})
|
@ -12,7 +12,12 @@ const priceChanges = {
|
|||||||
eth: -17.538786176215627
|
eth: -17.538786176215627
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export const priceContext = {
|
||||||
|
prices,
|
||||||
|
priceChanges
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appContext = {
|
||||||
accentColor: '#0a5fff',
|
accentColor: '#0a5fff',
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
@ -28,7 +33,5 @@ export default {
|
|||||||
],
|
],
|
||||||
currency: 'ocean',
|
currency: 'ocean',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
needsConfig: false,
|
needsConfig: false
|
||||||
prices,
|
|
||||||
priceChanges
|
|
||||||
}
|
}
|
28
tests/__mocks__/electron.js
Normal file
28
tests/__mocks__/electron.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const electron = {
|
||||||
|
require: jest.fn(),
|
||||||
|
match: jest.fn(),
|
||||||
|
app: jest.fn(),
|
||||||
|
remote: jest.fn(),
|
||||||
|
shell: jest.fn(),
|
||||||
|
dialog: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const remote = {
|
||||||
|
getCurrentWindow: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipcRenderer = {
|
||||||
|
on: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// for the shell module above
|
||||||
|
const shell = {
|
||||||
|
openExternal: jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
electron,
|
||||||
|
remote,
|
||||||
|
shell,
|
||||||
|
ipcRenderer
|
||||||
|
}
|
3
tests/__mocks__/electronStore.js
Normal file
3
tests/__mocks__/electronStore.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
has: () => jest.fn()
|
||||||
|
}
|
16
tests/babel.config.js
Normal file
16
tests/babel.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const { devDependencies } = require('../package.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'next/babel',
|
||||||
|
{
|
||||||
|
'preset-env': {
|
||||||
|
targets: {
|
||||||
|
electron: devDependencies.electron.replace(/^\^|~/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
import { AppContext } from '../../../store/createContext'
|
|
||||||
import { StateMock } from '@react-mock/state'
|
import { StateMock } from '@react-mock/state'
|
||||||
import Accounts from '.'
|
import { AppContext } from '../../src/renderer/store/createContext'
|
||||||
|
import Accounts from '../../src/renderer/components/Preferences/AccountsList'
|
||||||
|
|
||||||
describe('Accounts', () => {
|
describe('Accounts', () => {
|
||||||
const ui = (
|
const ui = (
|
10
tests/components/Spinner.test.jsx
Normal file
10
tests/components/Spinner.test.jsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import Spinner from '../../src/renderer/components/Spinner'
|
||||||
|
|
||||||
|
describe('Spinner', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(<Spinner />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
34
tests/jest.config.js
Normal file
34
tests/jest.config.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
module.exports = {
|
||||||
|
rootDir: '../',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[t|j]sx?$': ['babel-jest', { configFile: './tests/babel.config.js' }]
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
'.+\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy',
|
||||||
|
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||||
|
'<rootDir>/tests/__mocks__/file-mock.js',
|
||||||
|
'\\.svg': '<rootDir>/tests/__mocks__/svgr-mock.js'
|
||||||
|
},
|
||||||
|
testMatch: ['**/?(*.)+(spec|test).jsx'],
|
||||||
|
testPathIgnorePatterns: [
|
||||||
|
'<rootDir>/src/renderer/.next',
|
||||||
|
'<rootDir>/src/renderer/out',
|
||||||
|
'<rootDir>/node_modules',
|
||||||
|
'<rootDir>/build',
|
||||||
|
'<rootDir>/dist',
|
||||||
|
'<rootDir>/coverage'
|
||||||
|
],
|
||||||
|
testURL: 'http://localhost',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/tests/setupTests.js'],
|
||||||
|
runner: '@jest-runner/electron',
|
||||||
|
testEnvironment: '@jest-runner/electron/environment',
|
||||||
|
coverageDirectory: '<rootDir>/coverage/',
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'<rootDir>/src/renderer/**/*.{js,jsx}',
|
||||||
|
'!<rootDir>/src/renderer/next.config.js',
|
||||||
|
'!<rootDir>/src/renderer/out/**/*',
|
||||||
|
'!<rootDir>/src/renderer/.next/**/*',
|
||||||
|
'!**/node_modules/**'
|
||||||
|
]
|
||||||
|
}
|
36
tests/pages/index.test.jsx
Normal file
36
tests/pages/index.test.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import {
|
||||||
|
AppContext,
|
||||||
|
PriceContext
|
||||||
|
} from '../../src/renderer/store/createContext'
|
||||||
|
import { appContext, priceContext } from '../__fixtures__/context'
|
||||||
|
import Home from '../../src/renderer/pages/index'
|
||||||
|
|
||||||
|
describe('Home', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<PriceContext.Provider value={priceContext}>
|
||||||
|
<AppContext.Provider value={appContext}>
|
||||||
|
<Home />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</PriceContext.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
fireEvent.click(getByText(/Ξ/))
|
||||||
|
// fireEvent.click(getByText(/0x/))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders Welcome without config', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<PriceContext.Provider value={priceContext}>
|
||||||
|
<AppContext.Provider value={{ ...appContext, needsConfig: true }}>
|
||||||
|
<Home />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</PriceContext.Provider>
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toHaveTextContent(
|
||||||
|
'Add your first address to get started.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { AppContext } from '../../store/createContext'
|
import { AppContext } from '../../src/renderer/store/createContext'
|
||||||
import Preferences from '.'
|
import Preferences from '../../src/renderer/pages/preferences'
|
||||||
|
|
||||||
describe('Preferences', () => {
|
describe('Preferences', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
@ -1 +1,3 @@
|
|||||||
import '@testing-library/jest-dom/extend-expect'
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
|
|
||||||
|
jest.mock('electron-store')
|
@ -1,74 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
const CopyPlugin = require('copy-webpack-plugin')
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
||||||
|
|
||||||
const defaultInclude = [path.resolve(__dirname, 'src', 'renderer')]
|
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: isDevelopment ? 'development' : 'production',
|
|
||||||
entry: path.resolve(__dirname, 'src', 'renderer', 'index.js'),
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, 'build'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: isDevelopment ? '/' : './'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(js|jsx)?$/,
|
|
||||||
use: ['babel-loader'],
|
|
||||||
include: defaultInclude
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
exclude: /\.module\.css$/,
|
|
||||||
use: ['style-loader', 'css-loader'],
|
|
||||||
include: defaultInclude
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.module\.css$/,
|
|
||||||
include: defaultInclude,
|
|
||||||
loader: [
|
|
||||||
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
modules: {
|
|
||||||
localIdentName: '[name]__[local]___[hash:base64:5]'
|
|
||||||
},
|
|
||||||
localsConvention: 'camelCase',
|
|
||||||
sourceMap: isDevelopment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(jpe?g|png|gif)$/,
|
|
||||||
use: ['file-loader?name=img/[name]__[hash:base64:5].[ext]'],
|
|
||||||
include: defaultInclude
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.svg$/,
|
|
||||||
use: ['@svgr/webpack'],
|
|
||||||
include: defaultInclude
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['*', '.js', '.jsx', '.css']
|
|
||||||
},
|
|
||||||
target: 'electron-renderer',
|
|
||||||
plugins: [
|
|
||||||
new HtmlWebpackPlugin(),
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
|
|
||||||
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
|
|
||||||
}),
|
|
||||||
new CopyPlugin([
|
|
||||||
{ from: './src/renderer/images/icon.*', to: './', flatten: true }
|
|
||||||
])
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const common = require('./webpack.common.config')
|
|
||||||
const { spawn } = require('child_process')
|
|
||||||
|
|
||||||
module.exports = Object.assign({}, common, {
|
|
||||||
devtool: 'cheap-source-map',
|
|
||||||
devServer: {
|
|
||||||
contentBase: path.resolve(__dirname, 'build'),
|
|
||||||
stats: 'minimal',
|
|
||||||
before: () => {
|
|
||||||
spawn('electron', ['.'], {
|
|
||||||
shell: true,
|
|
||||||
env: process.env,
|
|
||||||
stdio: 'inherit'
|
|
||||||
})
|
|
||||||
.on('close', () => process.exit(0))
|
|
||||||
.on('error', spawnError => console.error(spawnError)) // eslint-disable-line no-console
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
Loading…
Reference in New Issue
Block a user