├── .eslintrc
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── src
├── components
│ ├── App.js
│ ├── BitcoinMonitor
│ │ ├── BitcoinMonitor.js
│ │ ├── Display.js
│ │ └── Indicator.js
│ └── Header.js
├── favicon.ico
├── index.html
├── index.js
├── services
│ ├── BitcoinService.js
│ ├── CurrencyService.js
│ └── currencies.json
└── styles
│ ├── _base.scss
│ ├── _bootstrap.scss
│ ├── _settings.scss
│ ├── app.scss
│ └── components
│ ├── BitcoinMonitor
│ ├── _bitcoin-monitor.scss
│ └── _display.scss
│ └── _header.scss
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "commonjs": true,
6 | "es6": true,
7 | "node": true
8 | },
9 | "extends": [ "eslint:recommended", "plugin:react/recommended" ],
10 | "parserOptions": {
11 | "ecmaVersion": 6,
12 | "ecmaFeatures": {
13 | "jsx": true
14 | },
15 | "sourceType": "module"
16 | },
17 | "plugins": [
18 | "react"
19 | ],
20 | "rules": {
21 | "no-console": "off",
22 | "indent": [
23 | "error",
24 | 4
25 | ],
26 | "quotes": [
27 | "error",
28 | "single"
29 | ],
30 | "semi": [
31 | "error",
32 | "always"
33 | ]
34 | }
35 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 | /public
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | /.vscode/**/*
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Bitcoin Monitor
2 |
3 | An app that monitors changes in the _Bitcoin Price Index (BPI)_.
4 |
5 | By default, the BPI will be shown for _USD_, _GBP_, and _EUR_. Optionally, one can select ones own currency to display in addition to the 3 aforementioned currencies.
6 |
7 | _Bitcoin Monitor_ makes use of the _[CoinDesk Bitcoin Price Index API]_. Go [here](https://www.coindesk.com/price/) for more price information by CoinDesk.
8 |
9 | Go **[here](http://react-bitcoin-monitor.drminnaar.me/)** for live demo.
10 |
11 | The application is composed of the following components:
12 |
13 | * Header - A heading that displays application title
14 |
15 | * BitcoinMonitor - The primary (root) component that manages state for itself and all underlying components. It is also responsible for connecting to _BitcoinService_ to retrieve _Bitcoin Price Index_ data.
16 |
17 | * Display - Displays the _Bitcoin Price Index_ data for USD, GBP, and EUR as a default. Also allows one to select a currency as a fourth price to monitor.
18 |
19 | * Indicator - Used by the _Display_ component, it displays an indicator that shows if the current price is up or down from previous price.
20 |
21 | Component Diagram
22 | 
23 |
24 | The following services are used to obtain _Bitcoin Price Index_ data:
25 |
26 | * BitcoinService
27 |
28 | A wrapper that is responsible for integrating with the [CoinDesk Bitcoin Price Index API]
29 |
30 | Service Diagram
31 | 
32 |
33 | Features:
34 |
35 | * Show BPI for USD, GBP, and EUR
36 | * Show BPI for any custom selected currency
37 |
38 | This project also demonstrates:
39 |
40 | * a typcial React project layout structure
41 | * babel setup and configuration
42 | * webpack setup and configuration
43 | * eslint setup and configuration
44 | * SCSS setup and configuration
45 | * [CoinDesk Bitcoin Price Index API] integration
46 |
47 | **Screenshots:**
48 |
49 | ... | ...
50 | ---|---
51 |  | 
52 |  | 
53 |
54 | ---
55 |
56 | ## Developed With
57 |
58 | * [Visual Studio Code](https://code.visualstudio.com/) - A source code editor developed by Microsoft for Windows, Linux and macOS. It includes support for debugging, embedded Git control, syntax highlighting, intelligent code completion, snippets, and code refactoring
59 | * [Node.js](https://nodejs.org/en/) - Javascript runtime
60 | * [React](https://reactjs.org/) - A javascript library for building user interfaces
61 | * [Babel](https://babeljs.io/) - A transpiler for javascript
62 | * [Webpack](https://webpack.js.org/) - A module bundler
63 | * [SCSS](http://sass-lang.com/) - A css metalanguage
64 | * [Bootstrap 4](https://getbootstrap.com/) - Bootstrap is an open source toolkit for developing with HTML, CSS, and JS
65 | * [Axios](https://github.com/axios/axios) - Promise based HTTP client for the browser and node.js
66 | * [CoinDesk Bitcoin Price Index API] - Provides Bitcoin Price Index data
67 | * [Surge] - Static web publishing for Front-End Developers
68 |
69 | ---
70 |
71 | ## Related Projects
72 |
73 | * [react-starter]
74 |
75 | A basic template that consists of the essential elements that are required to start building a React application
76 |
77 | * [react-clicker]
78 |
79 | A basic React app that allows one to increase, decrease, or reset a counter
80 |
81 | * [react-clock-basic]
82 |
83 | A basic clock that displays the current date and time
84 |
85 | * [react-timer-basic]
86 |
87 | A basic timer that will start a countdown based on an input of time in seconds
88 |
89 | * [react-timer-advanced]
90 |
91 | A countdown timer that offers an advanced UI experience
92 |
93 | * [react-masterminds]
94 |
95 | A basic game of guessing a number with varying degrees of difficulty
96 |
97 | * [react-movie-cards]
98 |
99 | A basic application that displays a list of movies as a list of cards
100 |
101 | * [react-calculator-standard]
102 |
103 | A calculator that provides the essential arithmetic operations, an expression builder, and a complete history of all expressions
104 |
105 | * [react-weather-standard]
106 |
107 | A weather application that displays the current weather, daily forecasts, and hourly forecasts based on your current geolocation
108 |
109 | ---
110 |
111 | ## Getting Started
112 |
113 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
114 |
115 | ### Prerequisites
116 |
117 | The following software is required to be installed on your system:
118 |
119 | * Node 8.x
120 | * Npm 3.x
121 |
122 | Type the following commands in the terminal to verify your node and npm versions
123 |
124 | ```bash
125 | node -v
126 | npm -v
127 | ```
128 |
129 | ### Install
130 |
131 | Follow the following steps to get development environment running.
132 |
133 | * Clone _'react-bitcoin-monitor'_ repository from GitHub
134 |
135 | ```bash
136 | git clone https://github.com/drminnaar/react-bitcoin-monitor.git
137 | ```
138 |
139 | _OR USING SSH_
140 |
141 | ```bash
142 | git clone git@github.com:drminnaar/react-bitcoin-monitor.git
143 | ```
144 |
145 | * Install node modules
146 |
147 | ```bash
148 | cd react-bitcoin-monitor
149 | npm install
150 | ```
151 |
152 | ### Build
153 |
154 | * Build application
155 |
156 | This command will also run ESLint as part of build process.
157 |
158 | ```bash
159 | npm run build
160 | ```
161 |
162 | * Build application and start watching for changes
163 |
164 | This command will also run ESLint as part of build process.
165 |
166 | ```bash
167 | npm run build:watch
168 | ```
169 |
170 | ### Run ESlint
171 |
172 | * Lint project using ESLint
173 |
174 | ```bash
175 | npm run lint
176 | ```
177 |
178 | * Lint project using ESLint, and autofix
179 |
180 | ```bash
181 | npm run lint:fix
182 | ```
183 |
184 | ### Run
185 |
186 | * Run start
187 |
188 | This will run the _'serve'_ npm task
189 |
190 | ```bash
191 | npm start
192 | ```
193 |
194 | * Run webpack dev server
195 |
196 | ```bash
197 | npm run serve:dev
198 | ```
199 |
200 | * Alternatively run live-server (simple development http server with live reload capability)
201 |
202 | ```bash
203 | npm run serve
204 | ```
205 |
206 | ---
207 |
208 | ## Versioning
209 |
210 | I use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/drminnaar/react-bitcoin-monitor/tags).
211 |
212 | ## Authors
213 |
214 | * **Douglas Minnaar** - *Initial work* - [drminnaar](https://github.com/drminnaar)
215 |
216 | [CoinDesk Bitcoin Price Index API]: https://www.coindesk.com/api/
217 | [Surge]: https://surge.sh/
218 | [react-starter]: https://github.com/drminnaar/react-starter
219 | [react-clicker]: https://github.com/drminnaar/react-clicker
220 | [react-clock-basic]: https://github.com/drminnaar/react-clock-basic
221 | [react-timer-basic]: https://github.com/drminnaar/react-timer-basic
222 | [react-timer-advanced]: https://github.com/drminnaar/react-timer-advanced
223 | [react-masterminds]: https://github.com/drminnaar/react-masterminds
224 | [react-movie-cards]: https://github.com/drminnaar/react-movie-cards
225 | [react-calculator-standard]: https://github.com/drminnaar/react-calculator-standard
226 | [react-weather-standard]: https://github.com/drminnaar/react-weather-standard
227 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-bitcoin-monitor",
3 | "version": "1.0.0",
4 | "description": "An app that monitors changes in the Bitcoin Price Index (BPI). Powered by CoinDesk.",
5 | "scripts": {
6 | "build": "webpack -d",
7 | "build:watch": "webpack --watch",
8 | "lint": "eslint .; exit 0",
9 | "lint:fix": "eslint . --fix",
10 | "serve": "webpack -p && live-server ./public",
11 | "serve:dev": "webpack-dev-server --open",
12 | "start": "npm run serve",
13 | "test": "echo \"No tests available\" && exit 0"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/drminnaar/react-bitcoin-monitor.git"
18 | },
19 | "license": "MIT",
20 | "babel": {
21 | "presets": [
22 | "env",
23 | "react"
24 | ]
25 | },
26 | "devDependencies": {
27 | "babel-cli": "^6.26.0",
28 | "babel-core": "^6.26.0",
29 | "babel-eslint": "^8.2.1",
30 | "babel-loader": "^7.1.2",
31 | "babel-preset-env": "^1.6.1",
32 | "babel-preset-react": "^6.24.1",
33 | "clean-webpack-plugin": "^0.1.18",
34 | "css-loader": "^0.28.9",
35 | "eslint": "^4.17.0",
36 | "eslint-loader": "^1.9.0",
37 | "eslint-plugin-react": "^7.6.1",
38 | "file-loader": "^1.1.5",
39 | "html-webpack-plugin": "^2.30.1",
40 | "live-server": "^1.2.0",
41 | "node-sass": "^4.7.2",
42 | "sass-loader": "^6.0.6",
43 | "style-loader": "^0.20.1",
44 | "url-loader": "^0.6.2",
45 | "webpack": "^3.8.1",
46 | "webpack-dev-server": "^2.11.1"
47 | },
48 | "dependencies": {
49 | "axios": "^0.17.1",
50 | "prop-types": "^15.6.0",
51 | "react": "^16.2.0",
52 | "react-dom": "^16.2.0"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Header } from './Header';
3 | import { BitcoinMonitor } from './BitcoinMonitor/BitcoinMonitor';
4 |
5 | const App = () => (
6 |
12 | );
13 |
14 | export default App;
--------------------------------------------------------------------------------
/src/components/BitcoinMonitor/BitcoinMonitor.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Display } from './Display';
3 | import { BitcoinService} from '../../services/BitcoinService';
4 | import { CurrencyService } from '../../services/CurrencyService';
5 |
6 | const bitcoinService = new BitcoinService();
7 | const currencyService = new CurrencyService();
8 |
9 | const mapToPrice = (currentPrice, previousPrice) => {
10 | return {
11 | code: currentPrice.code,
12 | description: currentPrice.description,
13 | currentRate: currentPrice.currentRate,
14 | previousRate: previousPrice ? previousPrice.currentRate : 0
15 | };
16 | };
17 |
18 | class BitcoinMonitor extends Component {
19 |
20 | constructor(props){
21 | super(props);
22 |
23 | this.state = {
24 | prices: {},
25 | ready: false,
26 | currencies: []
27 | };
28 |
29 | this.handleOnRefresh = this.handleOnRefresh.bind(this);
30 | this.loadBitcoinPriceIndex = this.loadBitcoinPriceIndex.bind(this);
31 | this.handleOnCurrencyChanged = this.handleOnCurrencyChanged.bind(this);
32 | }
33 |
34 |
35 | handleOnRefresh() {
36 | if (this.state.prices.SELECTED) {
37 |
38 | const currency = this.state.prices.SELECTED.code;
39 |
40 | bitcoinService
41 | .getPrice(currency)
42 | .then(price => {
43 | if (price) {
44 | this.loadBitcoinPriceIndex(price);
45 | } else {
46 | this.loadBitcoinPriceIndex();
47 | }
48 | })
49 | .catch(error => console.log(error.message));
50 |
51 | } else {
52 | this.loadBitcoinPriceIndex();
53 | }
54 | }
55 |
56 |
57 | handleOnCurrencyChanged(event) {
58 |
59 | const currency = event.target.value;
60 |
61 | bitcoinService
62 | .getPrice(currency)
63 | .then(price => {
64 | if (price) {
65 | this.setState(prevState => {
66 | const prices = prevState.prices;
67 | prices.SELECTED = mapToPrice(price[currency], prevState.prices[currency]);
68 | return { prices: prices };
69 | });
70 | }
71 | })
72 | .catch(error => console.log(error.message));
73 | }
74 |
75 |
76 | loadBitcoinPriceIndex(additionalPrice) {
77 | bitcoinService
78 | .getPrices()
79 | .then(prices => {
80 | if (prices) {
81 | this.setState(prevState => {
82 | const newPrices = {
83 | EUR: mapToPrice(prices.EUR, prevState.prices.EUR),
84 | GBP: mapToPrice(prices.GBP, prevState.prices.GBP),
85 | USD: mapToPrice(prices.USD, prevState.prices.USD)
86 | };
87 |
88 | if (additionalPrice) {
89 | newPrices.SELECTED = mapToPrice(
90 | Object.values(additionalPrice)[0],
91 | prevState.prices.SELECTED);
92 | }
93 |
94 | return { prices: newPrices, ready: true };
95 | });
96 | }
97 | })
98 | .catch(error => console.log(error.message));
99 | }
100 |
101 |
102 | loadSupportedCurrencies() {
103 | this.setState(() => ({ currencies: currencyService.getSupportedCurrencies() }));
104 | }
105 |
106 |
107 | componentDidMount(){
108 | this.loadSupportedCurrencies();
109 | this.loadBitcoinPriceIndex();
110 | }
111 |
112 |
113 | render() {
114 | return (
115 | this.state.ready === true &&
116 |
117 |
121 |
122 |
123 |
126 |
127 |
128 |
129 | );
130 | }
131 | }
132 |
133 | export { BitcoinMonitor };
--------------------------------------------------------------------------------
/src/components/BitcoinMonitor/Display.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Indicator } from './Indicator';
4 |
5 | const formatNumber = (number) => number.toFixed(4).toString().padEnd(11, '0');
6 |
7 | const Display = (props) => (
8 |
9 |
10 |
11 |
12 | |
13 | Before |
14 | Current |
15 |
16 |
17 |
18 |
19 |
20 |
21 | |
22 |
23 | {formatNumber(props.prices.USD.previousRate)}
24 | |
25 |
26 | {formatNumber(props.prices.USD.currentRate)}
27 |
30 | |
31 |
32 |
33 |
34 |
35 | |
36 |
37 | {formatNumber(props.prices.GBP.previousRate)}
38 | |
39 |
40 | {formatNumber(props.prices.GBP.currentRate)}
41 |
44 | |
45 |
46 |
47 |
48 |
49 | |
50 |
51 | {formatNumber(props.prices.EUR.previousRate)}
52 | |
53 |
54 | {formatNumber(props.prices.EUR.currentRate)}
55 |
58 | |
59 |
60 |
61 |
62 |
75 | |
76 |
77 | {props.prices.SELECTED && formatNumber(props.prices.SELECTED.previousRate)}
78 | |
79 |
80 | {
81 | props.prices.SELECTED &&
82 |
83 | {formatNumber(props.prices.SELECTED.currentRate)}
84 |
87 |
88 | }
89 | |
90 |
91 |
92 |
93 |
94 | );
95 |
96 | Display.propTypes = {
97 | currencies: PropTypes.array,
98 | prices: PropTypes.object,
99 | onCurrencyChanged: PropTypes.func
100 | };
101 |
102 | export { Display };
--------------------------------------------------------------------------------
/src/components/BitcoinMonitor/Indicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Indicator = (props) => {
5 |
6 | let indicator = null;
7 |
8 | if (props.currentRate > props.previousRate) {
9 | indicator = ;
10 | } else if (props.currentRate < props.previousRate) {
11 | indicator = ;
12 | } else {
13 | indicator = ;
14 | }
15 |
16 | return indicator;
17 | };
18 |
19 | Indicator.propTypes = {
20 | currentRate: PropTypes.number,
21 | previousRate: PropTypes.number
22 | };
23 |
24 | export { Indicator };
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Header = (props) => (
5 |
13 | );
14 |
15 | Header.defaultProps = {
16 | title: 'App'
17 | };
18 |
19 | Header.propTypes = {
20 | title: PropTypes.string
21 | };
22 |
23 | export { Header };
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drminnaar/react-bitcoin-monitor/06c5a967ee39e4c76cc71bc55e5867c8546cc5ac/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React Bitcoin Monitor
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './components/App';
4 | import './styles/app.scss';
5 |
6 | ReactDOM.render(, document.getElementById('app'));
--------------------------------------------------------------------------------
/src/services/BitcoinService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const mapToPrice = (bpiPrice) => {
4 | return {
5 | code: bpiPrice.code,
6 | description: bpiPrice.description,
7 | currentRate: bpiPrice.rate_float
8 | };
9 | };
10 |
11 | const BASE_URL = 'https://api.coindesk.com/v1/bpi';
12 |
13 | export class BitcoinService {
14 |
15 | getPrice(currency) {
16 | return new Promise((resolve, reject) => {
17 | axios
18 | .get(`${BASE_URL}/currentprice/${currency}.json`)
19 | .then(response => {
20 | if (response.data && response.data.bpi) {
21 | resolve({
22 | [currency]: mapToPrice(response.data.bpi[currency])
23 | });
24 | }
25 | })
26 | .catch(error => reject(error.message));
27 | });
28 | }
29 |
30 | getPrices() {
31 | return new Promise((resolve, reject) => {
32 | axios.get(`${BASE_URL}/currentprice.json`)
33 | .then(response => {
34 | if (response.data && response.data.bpi) {
35 | resolve({
36 | EUR: mapToPrice(response.data.bpi.EUR),
37 | GBP: mapToPrice(response.data.bpi.GBP),
38 | USD: mapToPrice(response.data.bpi.USD)
39 | });
40 | } else {
41 | reject('No Bitcoin prices available');
42 | }
43 | });
44 | });
45 | }
46 | }
--------------------------------------------------------------------------------
/src/services/CurrencyService.js:
--------------------------------------------------------------------------------
1 | const currencies = require('./currencies');
2 |
3 | export class CurrencyService {
4 |
5 | getSupportedCurrencies() {
6 | return currencies;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/services/currencies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "currency": "",
4 | "country": ""
5 | },
6 | {
7 | "currency": "AED",
8 | "country": "United Arab Emirates Dirham"
9 | },
10 | {
11 | "currency": "AFN",
12 | "country": "Afghan Afghani"
13 | },
14 | {
15 | "currency": "ALL",
16 | "country": "Albanian Lek"
17 | },
18 | {
19 | "currency": "AMD",
20 | "country": "Armenian Dram"
21 | },
22 | {
23 | "currency": "ANG",
24 | "country": "Netherlands Antillean Guilder"
25 | },
26 | {
27 | "currency": "AOA",
28 | "country": "Angolan Kwanza"
29 | },
30 | {
31 | "currency": "ARS",
32 | "country": "Argentine Peso"
33 | },
34 | {
35 | "currency": "AUD",
36 | "country": "Australian Dollar"
37 | },
38 | {
39 | "currency": "AWG",
40 | "country": "Aruban Florin"
41 | },
42 | {
43 | "currency": "AZN",
44 | "country": "Azerbaijani Manat"
45 | },
46 | {
47 | "currency": "BAM",
48 | "country": "Bosnia-Herzegovina Convertible Mark"
49 | },
50 | {
51 | "currency": "BBD",
52 | "country": "Barbadian Dollar"
53 | },
54 | {
55 | "currency": "BDT",
56 | "country": "Bangladeshi Taka"
57 | },
58 | {
59 | "currency": "BGN",
60 | "country": "Bulgarian Lev"
61 | },
62 | {
63 | "currency": "BHD",
64 | "country": "Bahraini Dinar"
65 | },
66 | {
67 | "currency": "BIF",
68 | "country": "Burundian Franc"
69 | },
70 | {
71 | "currency": "BMD",
72 | "country": "Bermudan Dollar"
73 | },
74 | {
75 | "currency": "BND",
76 | "country": "Brunei Dollar"
77 | },
78 | {
79 | "currency": "BOB",
80 | "country": "Bolivian Boliviano"
81 | },
82 | {
83 | "currency": "BRL",
84 | "country": "Brazilian Real"
85 | },
86 | {
87 | "currency": "BSD",
88 | "country": "Bahamian Dollar"
89 | },
90 | {
91 | "currency": "BTC",
92 | "country": "Bitcoin"
93 | },
94 | {
95 | "currency": "BTN",
96 | "country": "Bhutanese Ngultrum"
97 | },
98 | {
99 | "currency": "BWP",
100 | "country": "Botswanan Pula"
101 | },
102 | {
103 | "currency": "BYR",
104 | "country": "Belarusian Ruble"
105 | },
106 | {
107 | "currency": "BZD",
108 | "country": "Belize Dollar"
109 | },
110 | {
111 | "currency": "CAD",
112 | "country": "Canadian Dollar"
113 | },
114 | {
115 | "currency": "CDF",
116 | "country": "Congolese Franc"
117 | },
118 | {
119 | "currency": "CHF",
120 | "country": "Swiss Franc"
121 | },
122 | {
123 | "currency": "CLF",
124 | "country": "Chilean Unit of Account (UF)"
125 | },
126 | {
127 | "currency": "CLP",
128 | "country": "Chilean Peso"
129 | },
130 | {
131 | "currency": "CNY",
132 | "country": "Chinese Yuan"
133 | },
134 | {
135 | "currency": "COP",
136 | "country": "Colombian Peso"
137 | },
138 | {
139 | "currency": "CRC",
140 | "country": "Costa Rican Colón"
141 | },
142 | {
143 | "currency": "CUP",
144 | "country": "Cuban Peso"
145 | },
146 | {
147 | "currency": "CVE",
148 | "country": "Cape Verdean Escudo"
149 | },
150 | {
151 | "currency": "CZK",
152 | "country": "Czech Republic Koruna"
153 | },
154 | {
155 | "currency": "DJF",
156 | "country": "Djiboutian Franc"
157 | },
158 | {
159 | "currency": "DKK",
160 | "country": "Danish Krone"
161 | },
162 | {
163 | "currency": "DOP",
164 | "country": "Dominican Peso"
165 | },
166 | {
167 | "currency": "DZD",
168 | "country": "Algerian Dinar"
169 | },
170 | {
171 | "currency": "EEK",
172 | "country": "Estonian Kroon"
173 | },
174 | {
175 | "currency": "EGP",
176 | "country": "Egyptian Pound"
177 | },
178 | {
179 | "currency": "ERN",
180 | "country": "Eritrean Nnakfa"
181 | },
182 | {
183 | "currency": "ETB",
184 | "country": "Ethiopian Birr"
185 | },
186 | {
187 | "currency": "EUR",
188 | "country": "Euro"
189 | },
190 | {
191 | "currency": "FJD",
192 | "country": "Fijian Dollar"
193 | },
194 | {
195 | "currency": "FKP",
196 | "country": "Falkland Islands Pound"
197 | },
198 | {
199 | "currency": "GBP",
200 | "country": "British Pound Sterling"
201 | },
202 | {
203 | "currency": "GEL",
204 | "country": "Georgian Lari"
205 | },
206 | {
207 | "currency": "GHS",
208 | "country": "Ghanaian Cedi"
209 | },
210 | {
211 | "currency": "GIP",
212 | "country": "Gibraltar Pound"
213 | },
214 | {
215 | "currency": "GMD",
216 | "country": "Gambian Dalasi"
217 | },
218 | {
219 | "currency": "GNF",
220 | "country": "Guinean Franc"
221 | },
222 | {
223 | "currency": "GTQ",
224 | "country": "Guatemalan Quetzal"
225 | },
226 | {
227 | "currency": "GYD",
228 | "country": "Guyanaese Dollar"
229 | },
230 | {
231 | "currency": "HKD",
232 | "country": "Hong Kong Dollar"
233 | },
234 | {
235 | "currency": "HNL",
236 | "country": "Honduran Lempira"
237 | },
238 | {
239 | "currency": "HRK",
240 | "country": "Croatian Kuna"
241 | },
242 | {
243 | "currency": "HTG",
244 | "country": "Haitian Gourde"
245 | },
246 | {
247 | "currency": "HUF",
248 | "country": "Hungarian Forint"
249 | },
250 | {
251 | "currency": "IDR",
252 | "country": "Indonesian Rupiah"
253 | },
254 | {
255 | "currency": "ILS",
256 | "country": "Israeli New Sheqel"
257 | },
258 | {
259 | "currency": "INR",
260 | "country": "Indian Rupee"
261 | },
262 | {
263 | "currency": "IQD",
264 | "country": "Iraqi Dinar"
265 | },
266 | {
267 | "currency": "IRR",
268 | "country": "Iranian Rial"
269 | },
270 | {
271 | "currency": "ISK",
272 | "country": "Icelandic Króna"
273 | },
274 | {
275 | "currency": "JEP",
276 | "country": "Jersey Pound"
277 | },
278 | {
279 | "currency": "JMD",
280 | "country": "Jamaican Dollar"
281 | },
282 | {
283 | "currency": "JOD",
284 | "country": "Jordanian Dinar"
285 | },
286 | {
287 | "currency": "JPY",
288 | "country": "Japanese Yen"
289 | },
290 | {
291 | "currency": "KES",
292 | "country": "Kenyan Shilling"
293 | },
294 | {
295 | "currency": "KGS",
296 | "country": "Kyrgystani Som"
297 | },
298 | {
299 | "currency": "KHR",
300 | "country": "Cambodian Riel"
301 | },
302 | {
303 | "currency": "KMF",
304 | "country": "Comorian Franc"
305 | },
306 | {
307 | "currency": "KPW",
308 | "country": "North Korean Won"
309 | },
310 | {
311 | "currency": "KRW",
312 | "country": "South Korean Won"
313 | },
314 | {
315 | "currency": "KWD",
316 | "country": "Kuwaiti Dinar"
317 | },
318 | {
319 | "currency": "KYD",
320 | "country": "Cayman Islands Dollar"
321 | },
322 | {
323 | "currency": "KZT",
324 | "country": "Kazakhstani Tenge"
325 | },
326 | {
327 | "currency": "LAK",
328 | "country": "Laotian Kip"
329 | },
330 | {
331 | "currency": "LBP",
332 | "country": "Lebanese Pound"
333 | },
334 | {
335 | "currency": "LKR",
336 | "country": "Sri Lankan Rupee"
337 | },
338 | {
339 | "currency": "LRD",
340 | "country": "Liberian Dollar"
341 | },
342 | {
343 | "currency": "LSL",
344 | "country": "Lesotho Loti"
345 | },
346 | {
347 | "currency": "LTL",
348 | "country": "Lithuanian Litas"
349 | },
350 | {
351 | "currency": "LVL",
352 | "country": "Latvian Lats"
353 | },
354 | {
355 | "currency": "LYD",
356 | "country": "Libyan Dinar"
357 | },
358 | {
359 | "currency": "MAD",
360 | "country": "Moroccan Dirham"
361 | },
362 | {
363 | "currency": "MDL",
364 | "country": "Moldovan Leu"
365 | },
366 | {
367 | "currency": "MGA",
368 | "country": "Malagasy Ariary"
369 | },
370 | {
371 | "currency": "MKD",
372 | "country": "Macedonian Denar"
373 | },
374 | {
375 | "currency": "MMK",
376 | "country": "Myanma Kyat"
377 | },
378 | {
379 | "currency": "MNT",
380 | "country": "Mongolian Tugrik"
381 | },
382 | {
383 | "currency": "MOP",
384 | "country": "Macanese Pataca"
385 | },
386 | {
387 | "currency": "MRO",
388 | "country": "Mauritanian Ouguiya"
389 | },
390 | {
391 | "currency": "MTL",
392 | "country": "Maltese Lira"
393 | },
394 | {
395 | "currency": "MUR",
396 | "country": "Mauritian Rupee"
397 | },
398 | {
399 | "currency": "MVR",
400 | "country": "Maldivian Rufiyaa"
401 | },
402 | {
403 | "currency": "MWK",
404 | "country": "Malawian Kwacha"
405 | },
406 | {
407 | "currency": "MXN",
408 | "country": "Mexican Peso"
409 | },
410 | {
411 | "currency": "MYR",
412 | "country": "Malaysian Ringgit"
413 | },
414 | {
415 | "currency": "MZN",
416 | "country": "Mozambican Metical"
417 | },
418 | {
419 | "currency": "NAD",
420 | "country": "Namibian Dollar"
421 | },
422 | {
423 | "currency": "NGN",
424 | "country": "Nigerian Naira"
425 | },
426 | {
427 | "currency": "NIO",
428 | "country": "Nicaraguan Córdoba"
429 | },
430 | {
431 | "currency": "NOK",
432 | "country": "Norwegian Krone"
433 | },
434 | {
435 | "currency": "NPR",
436 | "country": "Nepalese Rupee"
437 | },
438 | {
439 | "currency": "NZD",
440 | "country": "New Zealand Dollar"
441 | },
442 | {
443 | "currency": "OMR",
444 | "country": "Omani Rial"
445 | },
446 | {
447 | "currency": "PAB",
448 | "country": "Panamanian Balboa"
449 | },
450 | {
451 | "currency": "PEN",
452 | "country": "Peruvian Nuevo Sol"
453 | },
454 | {
455 | "currency": "PGK",
456 | "country": "Papua New Guinean Kina"
457 | },
458 | {
459 | "currency": "PHP",
460 | "country": "Philippine Peso"
461 | },
462 | {
463 | "currency": "PKR",
464 | "country": "Pakistani Rupee"
465 | },
466 | {
467 | "currency": "PLN",
468 | "country": "Polish Zloty"
469 | },
470 | {
471 | "currency": "PYG",
472 | "country": "Paraguayan Guarani"
473 | },
474 | {
475 | "currency": "QAR",
476 | "country": "Qatari Rial"
477 | },
478 | {
479 | "currency": "RON",
480 | "country": "Romanian Leu"
481 | },
482 | {
483 | "currency": "RSD",
484 | "country": "Serbian Dinar"
485 | },
486 | {
487 | "currency": "RUB",
488 | "country": "Russian Ruble"
489 | },
490 | {
491 | "currency": "RWF",
492 | "country": "Rwandan Franc"
493 | },
494 | {
495 | "currency": "SAR",
496 | "country": "Saudi Riyal"
497 | },
498 | {
499 | "currency": "SBD",
500 | "country": "Solomon Islands Dollar"
501 | },
502 | {
503 | "currency": "SCR",
504 | "country": "Seychellois Rupee"
505 | },
506 | {
507 | "currency": "SDG",
508 | "country": "Sudanese Pound"
509 | },
510 | {
511 | "currency": "SEK",
512 | "country": "Swedish Krona"
513 | },
514 | {
515 | "currency": "SGD",
516 | "country": "Singapore Dollar"
517 | },
518 | {
519 | "currency": "SHP",
520 | "country": "Saint Helena Pound"
521 | },
522 | {
523 | "currency": "SLL",
524 | "country": "Sierra Leonean Leone"
525 | },
526 | {
527 | "currency": "SOS",
528 | "country": "Somali Shilling"
529 | },
530 | {
531 | "currency": "SRD",
532 | "country": "Surinamese Dollar"
533 | },
534 | {
535 | "currency": "STD",
536 | "country": "São Tomé and Príncipe Dobra"
537 | },
538 | {
539 | "currency": "SVC",
540 | "country": "Salvadoran Colón"
541 | },
542 | {
543 | "currency": "SYP",
544 | "country": "Syrian Pound"
545 | },
546 | {
547 | "currency": "SZL",
548 | "country": "Swazi Lilangeni"
549 | },
550 | {
551 | "currency": "THB",
552 | "country": "Thai Baht"
553 | },
554 | {
555 | "currency": "TJS",
556 | "country": "Tajikistani Somoni"
557 | },
558 | {
559 | "currency": "TMT",
560 | "country": "Turkmenistani Manat"
561 | },
562 | {
563 | "currency": "TND",
564 | "country": "Tunisian Dinar"
565 | },
566 | {
567 | "currency": "TOP",
568 | "country": "Tongan Pa?anga"
569 | },
570 | {
571 | "currency": "TRY",
572 | "country": "Turkish Lira"
573 | },
574 | {
575 | "currency": "TTD",
576 | "country": "Trinidad and Tobago Dollar"
577 | },
578 | {
579 | "currency": "TWD",
580 | "country": "New Taiwan Dollar"
581 | },
582 | {
583 | "currency": "TZS",
584 | "country": "Tanzanian Shilling"
585 | },
586 | {
587 | "currency": "UAH",
588 | "country": "Ukrainian Hryvnia"
589 | },
590 | {
591 | "currency": "UGX",
592 | "country": "Ugandan Shilling"
593 | },
594 | {
595 | "currency": "USD",
596 | "country": "United States Dollar"
597 | },
598 | {
599 | "currency": "UYU",
600 | "country": "Uruguayan Peso"
601 | },
602 | {
603 | "currency": "UZS",
604 | "country": "Uzbekistan Som"
605 | },
606 | {
607 | "currency": "VEF",
608 | "country": "Venezuelan Bolívar Fuerte"
609 | },
610 | {
611 | "currency": "VND",
612 | "country": "Vietnamese Dong"
613 | },
614 | {
615 | "currency": "VUV",
616 | "country": "Vanuatu Vatu"
617 | },
618 | {
619 | "currency": "WST",
620 | "country": "Samoan Tala"
621 | },
622 | {
623 | "currency": "XAF",
624 | "country": "CFA Franc BEAC"
625 | },
626 | {
627 | "currency": "XAG",
628 | "country": "Silver (troy ounce)"
629 | },
630 | {
631 | "currency": "XAU",
632 | "country": "Gold (troy ounce)"
633 | },
634 | {
635 | "currency": "XBT",
636 | "country": "Bitcoin"
637 | },
638 | {
639 | "currency": "XCD",
640 | "country": "East Caribbean Dollar"
641 | },
642 | {
643 | "currency": "XDR",
644 | "country": "Special Drawing Rights"
645 | },
646 | {
647 | "currency": "XOF",
648 | "country": "CFA Franc BCEAO"
649 | },
650 | {
651 | "currency": "XPF",
652 | "country": "CFP Franc"
653 | },
654 | {
655 | "currency": "YER",
656 | "country": "Yemeni Rial"
657 | },
658 | {
659 | "currency": "ZAR",
660 | "country": "South African Rand"
661 | },
662 | {
663 | "currency": "ZMK",
664 | "country": "Zambian Kwacha (pre-2013)"
665 | },
666 | {
667 | "currency": "ZMW",
668 | "country": "Zambian Kwacha"
669 | },
670 | {
671 | "currency": "ZWL",
672 | "country": "Zimbabwean Dollar"
673 | }
674 | ]
--------------------------------------------------------------------------------
/src/styles/_base.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: $smoke;
3 | font-family: $font-family;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/src/styles/_bootstrap.scss:
--------------------------------------------------------------------------------
1 | .btn {
2 | cursor: pointer;
3 | }
--------------------------------------------------------------------------------
/src/styles/_settings.scss:
--------------------------------------------------------------------------------
1 | $font-family: 'Open Sans', Helvetica, Arial, sans-serif;
2 | $smoke: whitesmoke;
--------------------------------------------------------------------------------
/src/styles/app.scss:
--------------------------------------------------------------------------------
1 | @import '_settings.scss';
2 | @import '_base.scss';
3 | @import '_bootstrap.scss';
4 | @import './components/_header.scss';
5 | @import './components/BitcoinMonitor/_bitcoin-monitor.scss';
6 | @import './components/BitcoinMonitor/_display.scss';
--------------------------------------------------------------------------------
/src/styles/components/BitcoinMonitor/_bitcoin-monitor.scss:
--------------------------------------------------------------------------------
1 | .btn-refresh {
2 | border-radius: 50%;
3 | color: white;
4 | background-color: #4CAF50;
5 | height: 4rem;
6 | width: 4rem;
7 | }
--------------------------------------------------------------------------------
/src/styles/components/BitcoinMonitor/_display.scss:
--------------------------------------------------------------------------------
1 | .currency {
2 | cursor: pointer;
3 | text-align: center;
4 | width: 90px;
5 | }
6 |
7 | .table-body > tr > td {
8 | font-size: 0.8rem;
9 | text-align: center;
10 | }
--------------------------------------------------------------------------------
/src/styles/components/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | height: 100px;
3 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CleanWebPackPlugin = require('clean-webpack-plugin');
3 | const HtmlWebPackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'bundle.js',
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | enforce: 'pre',
15 | test: /\.js$/,
16 | loader: 'eslint-loader',
17 | options: {
18 | failOnWarning: true,
19 | failOnerror: true
20 | },
21 | exclude: /node_modules/
22 | },
23 | {
24 | test: /\.js$/,
25 | loader: 'babel-loader',
26 | exclude: /node_modules/
27 | },
28 | {
29 | test: /\.s?css$/,
30 | use: [ 'style-loader', 'css-loader', 'sass-loader' ],
31 | exclude: /node_modules/
32 | },
33 | {
34 | test: /\.svg$/,
35 | loader: 'url-loader',
36 | exclude: /node_modules/
37 | }
38 | ]
39 | },
40 | plugins: [
41 | new CleanWebPackPlugin([ 'public' ], { root: path.resolve(__dirname)}),
42 | new HtmlWebPackPlugin({
43 | template: './src/index.html',
44 | favicon: './src/favicon.ico',
45 | inject: false
46 | })
47 | ],
48 | devtool: 'cheap-module-eval-source-map',
49 | devServer: {
50 | contentBase: path.resolve(__dirname, 'public'),
51 | compress: true,
52 | port: 9000
53 | }
54 | };
--------------------------------------------------------------------------------