├── .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 | ![Component Diagram](https://user-images.githubusercontent.com/33935506/34460739-2e3b6a84-ee20-11e7-8808-a654fbdaaf55.png) 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 | ![react-bitcoin-monitor-services](https://user-images.githubusercontent.com/33935506/34460740-2e7c490a-ee20-11e7-936e-60c637b630da.png) 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 | ![](https://user-images.githubusercontent.com/33935506/34460771-4309303a-ee21-11e7-8a41-867a266e092c.png) | ![](https://user-images.githubusercontent.com/33935506/34460772-4337f320-ee21-11e7-9cc5-aa3b78f5f6b0.png) 52 | ![](https://user-images.githubusercontent.com/33935506/34460773-436739be-ee21-11e7-9604-8450e09ee9cc.png) | ![](https://user-images.githubusercontent.com/33935506/34460774-43a80264-ee21-11e7-8323-fbee96ccce35.png) 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 |
7 |
8 |
9 | 10 |
11 |
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 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 25 | 31 | 32 | 33 | 36 | 39 | 45 | 46 | 47 | 50 | 53 | 59 | 60 | 61 | 76 | 79 | 90 | 91 | 92 |
BeforeCurrent
20 | 21 | 23 | {formatNumber(props.prices.USD.previousRate)} 24 | 26 | {formatNumber(props.prices.USD.currentRate)} 27 | 30 |
34 | 35 | 37 | {formatNumber(props.prices.GBP.previousRate)} 38 | 40 | {formatNumber(props.prices.GBP.currentRate)} 41 | 44 |
48 | 49 | 51 | {formatNumber(props.prices.EUR.previousRate)} 52 | 54 | {formatNumber(props.prices.EUR.currentRate)} 55 | 58 |
62 | 75 | 77 | {props.prices.SELECTED && formatNumber(props.prices.SELECTED.previousRate)} 78 | 80 | { 81 | props.prices.SELECTED && 82 |
83 | {formatNumber(props.prices.SELECTED.currentRate)} 84 | 87 |
88 | } 89 |
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 | }; --------------------------------------------------------------------------------