diff --git a/package-lock.json b/package-lock.json index e05b27df..bb544cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3023,9 +3023,9 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" }, "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.4.tgz", + "integrity": "sha512-8I1sgZHfVwcSOY6mSGpVU3lw/GSIZvusg8dD2+OGehCJpOhQRLNcH0qb9upQnOH4XhgxxFJSg6E2kx95deb1Tw==", "requires": { "boom": "5.x.x" }, @@ -3377,6 +3377,89 @@ "es5-ext": "^0.10.9" } }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", + "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==" + }, + "d3-ease": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", + "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + }, + "d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz", + "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==" + }, + "d3-scale": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", + "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-shape": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.2.tgz", + "integrity": "sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.10.tgz", + "integrity": "sha512-hF+NTLCaJHF/JqHN5hE8HVGAXPStEq6/omumPE/SxyHVrR7/qQxusFDo0t0c/44+sCGHthC7yNGFZIEgju0P8g==" + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", + "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, "damerau-levenshtein": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", @@ -12076,6 +12159,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.1.tgz", "integrity": "sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==" }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "react-hint": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-hint/-/react-hint-3.1.0.tgz", @@ -13587,6 +13675,11 @@ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, + "shrink-array": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/shrink-array/-/shrink-array-0.0.2.tgz", + "integrity": "sha512-M84/lNmWHajLnjSjBc+YceutISmQZsWsRm9vaHSkzK/ueZWzjZ3NwJRsBEt7df+KNE9fSYD2v4JWfwl68LubBg==" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -15327,6 +15420,83 @@ "extsprintf": "^1.2.0" } }, + "victory": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/victory/-/victory-0.27.0.tgz", + "integrity": "sha512-p9NNNCuPOrZNGYriwQ39k4E4kPswvEBXcIkgjNSN3kldqHj6TZhBBOOysQ3EHvPddzak3zb3ZV7Ykv1o9m4OjQ==", + "requires": { + "victory-chart": "^27.0.0", + "victory-core": "^23.0.2", + "victory-pie": "^16.0.0" + } + }, + "victory-chart": { + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-27.1.1.tgz", + "integrity": "sha512-6QLtfDLQQdtzNfLkFIR5QR9SmWwkre0yLz0auanth9XnBdwKoMf+g+l06tsKvNfHxNw8ZaG0C/J+KwtU/5ChPg==", + "requires": { + "d3-voronoi": "^1.1.2", + "lodash": "^4.17.5", + "react-fast-compare": "^2.0.0", + "victory-core": "^24.0.0" + }, + "dependencies": { + "victory-core": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-24.0.1.tgz", + "integrity": "sha512-loC+UafBR8nMnLkQ3suSkjMTVMlLsUaI7BiBmFYYFjy1eqEkPwA+Pa51Tbu0KIunR/1alpEjOxP1MBz1Vc1FFg==", + "requires": { + "d3-ease": "^1.0.0", + "d3-interpolate": "^1.1.1", + "d3-scale": "^1.0.0", + "d3-shape": "^1.2.0", + "d3-timer": "^1.0.0", + "lodash": "^4.17.5", + "react-fast-compare": "^2.0.0" + } + } + } + }, + "victory-core": { + "version": "23.0.7", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-23.0.7.tgz", + "integrity": "sha512-RrMxMtuI4imaphp8si8wwPpOBBbJFICYMg8mQlSwFqne3w8HQ86Y4U4SRIQth5bnK4HDmZkdOJr1K7h3x07XPA==", + "requires": { + "d3-ease": "^1.0.0", + "d3-interpolate": "^1.1.1", + "d3-scale": "^1.0.0", + "d3-shape": "^1.2.0", + "d3-timer": "^1.0.0", + "lodash": "^4.17.5", + "react-fast-compare": "^2.0.0" + } + }, + "victory-pie": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-16.1.0.tgz", + "integrity": "sha512-OV1C2e7KGkGjrPUf7Hr68PWuDzxsFqQkdC9E/dZ2HGQKqaYZ9bdkLC0lZYEr7mKsUs6D8iPe4RTWXyEfX84nMA==", + "requires": { + "d3-shape": "^1.0.0", + "lodash": "^4.17.5", + "victory-core": "^24.0.0" + }, + "dependencies": { + "victory-core": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-24.0.1.tgz", + "integrity": "sha512-loC+UafBR8nMnLkQ3suSkjMTVMlLsUaI7BiBmFYYFjy1eqEkPwA+Pa51Tbu0KIunR/1alpEjOxP1MBz1Vc1FFg==", + "requires": { + "d3-ease": "^1.0.0", + "d3-interpolate": "^1.1.1", + "d3-scale": "^1.0.0", + "d3-shape": "^1.2.0", + "d3-timer": "^1.0.0", + "lodash": "^4.17.5", + "react-fast-compare": "^2.0.0" + } + } + } + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", diff --git a/package.json b/package.json index ab41d7d9..110c6b67 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "redux-actions": "2.4.0", "reselect": "3.0.1", "safe-buffer": "5.1.2", + "shrink-array": "0.0.2", "smart-round": "1.0.0", "socket.io-client": "2.1.1", "startinterval2": "1.0.1", @@ -91,6 +92,7 @@ "supports-color": "5.4.0", "tough-cookie": "2.4.3", "universal-analytics": "0.4.17", + "victory": "0.27.0", "web3": "1.0.0-beta.34" }, "devDependencies": { diff --git a/public/main/main-window.js b/public/main/main-window.js index 60be6a48..8a1430cb 100644 --- a/public/main/main-window.js +++ b/public/main/main-window.js @@ -61,7 +61,7 @@ function loadWindow () { mainWindow = new BrowserWindow({ show: false, width: 1140, - height: 700, + height: 750, minWidth: 640, minHeight: 578, useContentSize: true diff --git a/src/components/Auction.js b/src/components/Auction.js index 6f9af3b4..7c388290 100644 --- a/src/components/Auction.js +++ b/src/components/Auction.js @@ -1,4 +1,5 @@ import CountDownProvider from './providers/CountDownProvider' +import AuctionChartCard from './AuctionChartCard' import * as selectors from '../selectors' import BuyMETDrawer from './BuyMETDrawer' import { connect } from 'react-redux' @@ -65,7 +66,7 @@ const Body = styled.div` align-items: center; flex-direction: column; - @media (min-width: 1200px) { + @media (min-width: 1140px) { align-items: flex-start; margin-top: 4.8rem; flex-direction: row; @@ -78,7 +79,7 @@ const BuyBtn = styled(Btn)` margin-bottom: 3.2rem; min-width: 300px; - @media (min-width: 1200px) { + @media (min-width: 1140px) { margin-bottom: 0; order: 1; min-width: auto; @@ -92,7 +93,7 @@ const StatsContainer = styled.div` width: 100%; margin-right: 0; - @media (min-width: 1200px) { + @media (min-width: 1140px) { margin-right: 1.6rem; max-width: 660px; } @@ -117,7 +118,7 @@ const Badge = styled.div` padding: 0.4rem 0.8rem; margin-right: 0.4rem; - @media (min-width: 1100px) { + @media (min-width: 1140px) { font-size: 2rem; } ` @@ -127,7 +128,7 @@ const Price = styled.div` font-weight: 600; text-shadow: 0 1px 1px ${p => p.theme.colors.darkShade}; - @media (min-width: 1100px) { + @media (min-width: 1140px) { font-size: 2.4rem; } ` @@ -138,7 +139,7 @@ const USDPrice = styled.div` font-weight: 600; text-align: right; - @media (min-width: 1100px) { + @media (min-width: 1140px) { font-size: 1.6rem; } ` @@ -148,7 +149,7 @@ const AvailableAmount = styled.div` font-weight: 600; text-shadow: 0 1px 1px ${p => p.theme.colors.darkShade}; - @media (min-width: 1100px) { + @media (min-width: 1140px) { font-size: 2.4rem; } ` @@ -285,6 +286,7 @@ class Auction extends React.Component { + + moment() + .subtract({ days: 7 }) + .unix() + +class AuctionChartCard extends Component { + state = { + chartStatus: 'pending', + chartData: [] + } + + // eslint-disable-next-line arrow-body-style + retrieveData = () => { + this.setState({ chartStatus: 'pending', chartData: [] }) + + const from = sevenDaysAgo() + const now = moment().unix() + + fetch(`${config.MET_API_URL}/history?from=${from}&to=${now}`) + .then(response => response.json()) + .then(chartData => { + if (!this._isMounted) return + this.setState({ + chartStatus: 'success', + chartData: chartData.map(point => ({ + x: point.timestamp, + y: parseInt(point.currentAuctionPrice, 10) + })) + }) + }) + .catch(() => { + if (!this._isMounted) return + this.setState({ chartStatus: 'failure', chartData: [] }) + }) + } + + componentDidMount() { + this._isMounted = true + this.retrieveData() + } + + componentWillUnmount() { + this._isMounted = false + } + + static getDerivedStateFromProps(props, state) { + const point = { + y: parseInt(props.auction.currentPrice, 10), + x: moment().unix() + } + const from = sevenDaysAgo() + const newChartData = state.chartData.concat(point).filter(p => p.x >= from) + + return { + chartData: + newChartData.length > 500 + ? shrinkArray(newChartData, 500, last) + : newChartData + } + } + + render() { + return ( + + ) + } +} + +const mapStateToProps = state => ({ + auction: state.auction.status +}) + +export default connect(mapStateToProps)(AuctionChartCard) diff --git a/src/components/Converter.js b/src/components/Converter.js index 80d2d501..946119b8 100644 --- a/src/components/Converter.js +++ b/src/components/Converter.js @@ -1,3 +1,4 @@ +import ConverterChartCard from './ConverterChartCard' import * as selectors from '../selectors' import ConvertDrawer from './ConvertDrawer' import { connect } from 'react-redux' @@ -20,7 +21,7 @@ const Container = styled.div` align-items: center; flex-direction: column; - @media (min-width: 1180px) { + @media (min-width: 1140px) { padding: 3.2rem 4.8rem; align-items: flex-start; flex-direction: row; @@ -31,7 +32,7 @@ const ConvertBtn = styled(Btn)` margin-top: 3.2rem; min-width: 260px; - @media (min-width: 1180px) { + @media (min-width: 1140px) { min-width: 200px; margin-top: 0; } @@ -50,7 +51,7 @@ const StatsContainer = styled.div` margin-right: 0; width: 100%; - @media (min-width: 1180px) { + @media (min-width: 1140px) { margin-right: 1.6rem; max-width: 660px; } @@ -189,6 +190,7 @@ class Converter extends React.Component { + + moment() + .subtract({ days: 7 }) + .unix() + +class ConverterChartCard extends Component { + state = { + chartStatus: 'pending', + chartData: [] + } + + // eslint-disable-next-line arrow-body-style + retrieveData = () => { + this.setState({ chartStatus: 'pending', chartData: [] }) + + const from = sevenDaysAgo() + const now = moment().unix() + + fetch(`${config.MET_API_URL}/history?from=${from}&to=${now}`) + .then(response => response.json()) + .then(data => data.filter(p => Boolean(p.currentConverterPrice))) + .then(data => { + if (!this._isMounted) return + this.setState({ + chartStatus: 'success', + chartData: data.map(point => ({ + x: point.timestamp, + y: parseInt(point.currentConverterPrice, 10) + })) + }) + }) + .catch(() => { + if (!this._isMounted) return + this.setState({ chartStatus: 'failure', chartData: [] }) + }) + } + + componentDidMount() { + this._isMounted = true + this.retrieveData() + } + + componentWillUnmount() { + this._isMounted = false + } + + static getDerivedStateFromProps(props, state) { + const point = { + y: parseInt(props.converter.currentPrice, 10), + x: moment().unix() + } + const from = sevenDaysAgo() + const newChartData = state.chartData.concat(point).filter(p => p.x >= from) + + return { + chartData: + newChartData.length > 500 + ? shrinkArray(newChartData, 500, last) + : newChartData + } + } + + render() { + return ( + + ) + } +} + +const mapStateToProps = state => ({ + converter: state.converter.status +}) + +export default connect(mapStateToProps)(ConverterChartCard) diff --git a/src/components/common/ChartCard.js b/src/components/common/ChartCard.js new file mode 100644 index 00000000..2432d91d --- /dev/null +++ b/src/components/common/ChartCard.js @@ -0,0 +1,78 @@ +import { VictoryGroup, VictoryLine } from 'victory' +import React, { Component } from 'react' +import LoadingBar from './LoadingBar' +import { Btn } from './Btn' +import styled from 'styled-components' +import Sp from './Spacing' + +const Container = styled.div` + padding: 32px 16px 24px; + min-height: 232px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +` + +const Label = styled.div` + margin-top: 1.6rem; + text-align: center; + font-size: 13px; + font-weight: 200; +` + +const LoadingContainer = styled.div` + text-align: center; + font-size: 13px; + font-weight: 200; + width: 100%; + max-width: 300px; +` + +const ChartContainer = styled.div` + width: 100%; +` + +const FailureContainer = styled.div` + text-align: center; +` + +export default class ChartCard extends Component { + // eslint-disable-next-line complexity + render() { + return ( + + {this.props.chartStatus === 'pending' && ( + + + + Loading data... + + )} + {this.props.chartStatus === 'success' && ( + + + + + + + )} + {this.props.chartStatus === 'failure' && ( + + Retry + + + )} + + ) + } +} diff --git a/src/components/common/index.js b/src/components/common/index.js index 86290800..64d09f1e 100644 --- a/src/components/common/index.js +++ b/src/components/common/index.js @@ -8,6 +8,7 @@ import LoadingBar from './LoadingBar' import WalletIcon from './WalletIcon' import CloseIcon from './CloseIcon' import CheckIcon from './CheckIcon' +import ChartCard from './ChartCard' import TextInput from './TextInput' import Tooltips from './Tooltips' import CopyIcon from './CopyIcon' @@ -30,6 +31,7 @@ export { WalletIcon, CloseIcon, CheckIcon, + ChartCard, TextInput, Tooltips, CopyIcon, diff --git a/src/config.js b/src/config.js index 1590cfbc..c046532d 100644 --- a/src/config.js +++ b/src/config.js @@ -8,6 +8,7 @@ export default { CONVERTER_ADDR: addresses.autonomousConverter.toLowerCase(), MET_EXPLORER_URL: process.env.REACT_APP_MET_EXPLORER_URL || defaultExplorerUrl, SENTRY_DSN: process.env.REACT_APP_SENTRY_DSN || 'https://d9905c2eec994071935593d4085d3547@sentry.io/290706', + MET_API_URL: process.env.REACT_APP_MET_API_URL || 'https://api.metronome.io', ETH_DEFAULT_GAS_LIMIT: '21000', MET_DEFAULT_GAS_LIMIT: '250000', DEFAULT_GAS_PRICE: '1000000000',