├── .nvmrc ├── .prettierrc ├── src ├── utils │ ├── Constants.js │ └── index.js ├── style │ ├── index.scss │ ├── veriables.scss │ └── global.scss ├── containers │ ├── Main │ │ ├── index.js │ │ ├── Main.module.scss │ │ └── Main.js │ └── Index.js ├── components │ ├── Header │ │ ├── index.js │ │ ├── Header.module.scss │ │ ├── Header.js │ │ └── images │ │ │ └── logo.svg │ ├── CoinCard │ │ ├── index.js │ │ ├── CoinCard.module.scss │ │ └── CoinCard.js │ ├── Checkbox │ │ ├── index.js │ │ ├── Checkbox.module.scss │ │ └── Checkbox.js │ ├── Favorite │ │ ├── index.js │ │ ├── FavoriteItem │ │ │ ├── index.js │ │ │ ├── FavoriteItem.module.scss │ │ │ └── FavoriteItem.js │ │ ├── Favorite.module.scss │ │ └── Favorite.js │ ├── Autocomplite │ │ ├── index.js │ │ ├── images │ │ │ └── search-icon.svg │ │ ├── Autocomplite.module.scss │ │ ├── Autocomplite.js │ │ └── names.js │ ├── TableHeader │ │ ├── index.js │ │ ├── TableHeader.js │ │ └── TableHeader.module.scss │ └── Chart │ │ ├── Chart.module.scss │ │ └── Chart.js ├── index.js ├── services │ └── index.js └── serviceWorker.js ├── public ├── robots.txt ├── logo.png ├── manifest.json └── index.html ├── .eslintrc ├── .gitignore ├── README.md ├── LICENSE └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.15.0 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /src/utils/Constants.js: -------------------------------------------------------------------------------- 1 | export default BASE_URL = 'https://api.coincap.io/v2/'; -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | @import './veriables.scss'; 2 | @import './global.scss'; 3 | -------------------------------------------------------------------------------- /src/containers/Main/index.js: -------------------------------------------------------------------------------- 1 | import Main from './Main'; 2 | 3 | export default Main; -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export default Header; -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Halo-Lab/metabase-chrome-extension/HEAD/public/logo.png -------------------------------------------------------------------------------- /src/components/CoinCard/index.js: -------------------------------------------------------------------------------- 1 | import CoinCard from './CoinCard'; 2 | 3 | export default CoinCard; -------------------------------------------------------------------------------- /src/components/Checkbox/index.js: -------------------------------------------------------------------------------- 1 | import Checkbox from './Checkbox'; 2 | 3 | export default Checkbox; 4 | -------------------------------------------------------------------------------- /src/components/Favorite/index.js: -------------------------------------------------------------------------------- 1 | import Favorite from './Favorite'; 2 | 3 | export default Favorite; 4 | -------------------------------------------------------------------------------- /src/components/Autocomplite/index.js: -------------------------------------------------------------------------------- 1 | import Autocomplite from './Autocomplite'; 2 | 3 | export default Autocomplite; -------------------------------------------------------------------------------- /src/components/TableHeader/index.js: -------------------------------------------------------------------------------- 1 | import TableHeader from './TableHeader'; 2 | 3 | export default TableHeader; 4 | -------------------------------------------------------------------------------- /src/components/Favorite/FavoriteItem/index.js: -------------------------------------------------------------------------------- 1 | import FavoriteItem from './FavoriteItem'; 2 | 3 | export default FavoriteItem; 4 | -------------------------------------------------------------------------------- /src/containers/Index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Main from './Main'; 4 | 5 | import '../style/index.scss'; 6 | 7 | const Index = () => { 8 | return
; 9 | }; 10 | 11 | export default Index; 12 | -------------------------------------------------------------------------------- /src/style/veriables.scss: -------------------------------------------------------------------------------- 1 | $color-white: #fff; 2 | $color-black: #3A3A3A; 3 | $color-blue: #2642BA; 4 | $color-green: #14C396; 5 | $color-pink: #FF86A5; 6 | $color-grey: #8B8B8B; 7 | $color-light-grey: #CECDCD; 8 | $color-bg:#FAF9F9; 9 | 10 | $font: 'Roboto', Arial, sans-serif; -------------------------------------------------------------------------------- /src/components/Header/Header.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style/veriables.scss'; 2 | .header { 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-between; 6 | padding: 32px 24px; 7 | font-size: 24px; 8 | line-height: 1.2; 9 | background-color: $color-white; 10 | } 11 | 12 | .checkbox { 13 | position: relative; 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": ["error"], 6 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }], 7 | "react/jsx-props-no-spreading": ["off"], 8 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 9 | } 10 | } -------------------------------------------------------------------------------- /src/components/Favorite/Favorite.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 24px; 3 | background: #faf9f9; 4 | } 5 | 6 | .title { 7 | margin: 0; 8 | margin-bottom: 16px; 9 | font-weight: 600; 10 | font-size: 16px; 11 | line-height: 19px; 12 | color: #3a3a3a; 13 | } 14 | 15 | :global { 16 | .swiper-container { 17 | overflow: visible !important; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Chart/Chart.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | width: 312px; 4 | height: 184px; 5 | border: 1px solid #f0f0f0; 6 | border-radius: 15px; 7 | overflow: hidden; 8 | } 9 | .canvas { 10 | position: absolute; 11 | bottom: -10px; 12 | left: -11px; 13 | width: calc(100% + 11px) !important; 14 | height: 100%; 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/components/TableHeader/TableHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './TableHeader.module.scss'; 3 | 4 | const TableHeader = () => { 5 | return ( 6 |
7 | Name 8 | Price 9 | 24HR 10 |
11 | ); 12 | }; 13 | 14 | export default TableHeader; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Metabase", 3 | "version": "1.0", 4 | "manifest_version": 2, 5 | "browser_action": { 6 | "default_icon": { 7 | "24": "logo.png" 8 | }, 9 | "default_title": "Metabase Real-time cryptocurrency trading charts, and more", 10 | "default_popup": "index.html" 11 | }, 12 | "content_security_policy": "script-src 'self' 'sha256-CMyYic0d7L0Q9AwjGU0n6buHFRR6bU3TOAe0P7DEJrk='; object-src 'self'" 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Checkbox/Checkbox.module.scss: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | position: absolute; 3 | z-index: 5; 4 | left: 8px; 5 | svg { 6 | width: 16px; 7 | height: 16px; 8 | } 9 | } 10 | 11 | .label { 12 | cursor: pointer; 13 | &:hover circle { 14 | fill: #2642ba; 15 | transition: fill 0.5s; 16 | } 17 | } 18 | 19 | .input { 20 | position: absolute; 21 | visibility: hidden; 22 | opacity: 0; 23 | 24 | &:checked + .label circle { 25 | fill: #2642ba; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Index from './containers/Index'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | 7 | ReactDOM.render(( 8 | 9 | ), document.getElementById('root')); 10 | 11 | // If you want your app to work offline and load faster, you can change 12 | // unregister() to register() below. Note this comes with some pitfalls. 13 | // Learn more about service workers: https://bit.ly/CRA-PWA 14 | serviceWorker.unregister(); 15 | -------------------------------------------------------------------------------- /src/components/Autocomplite/images/search-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/TableHeader/TableHeader.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style/veriables.scss'; 2 | .wrapper { 3 | display: flex; 4 | align-items: center; 5 | background-color: rgba($color: $color-light-grey, $alpha: 0.1); 6 | color: $color-grey; 7 | font-size: 13px; 8 | line-height: 15px; 9 | text-transform: uppercase; 10 | padding: 9px 32px; 11 | } 12 | 13 | .name { 14 | width: 70%; 15 | } 16 | 17 | .price { 18 | width: 30%; 19 | text-align: right; 20 | } 21 | 22 | .percent { 23 | width: 25%; 24 | text-align: right; 25 | } 26 | -------------------------------------------------------------------------------- /src/style/global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | height: 100%; 6 | width: 100%; 7 | overflow-x: hidden; 8 | } 9 | 10 | * { 11 | box-sizing: border-box; 12 | outline: none; 13 | } 14 | 15 | body { 16 | font-family: $font; 17 | width: 456px; 18 | min-height: 505px; 19 | max-height: 90vh; 20 | overflow-y: scroll; 21 | background: $color-bg; 22 | color: $color-black; 23 | } 24 | 25 | p { 26 | margin: 0; 27 | } 28 | 29 | ul, 30 | ol { 31 | padding: 0; 32 | margin: 0; 33 | list-style: none; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './Header.module.scss'; 3 | import Logo from './images/logo.svg'; 4 | import Autocomplite from '../Autocomplite'; 5 | 6 | import PropTypes from 'prop-types'; 7 | 8 | const Header = ({ findCoin, getCoins }) => { 9 | return ( 10 |
11 | logo icon 12 | 13 |
14 | ); 15 | }; 16 | 17 | Header.propTypes = { 18 | findCoin: PropTypes.func, 19 | getCoins: PropTypes.func 20 | }; 21 | 22 | export default Header; 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metabase Chrome Extension 2 | 3 | Metabase is a chrome extension that shows the current course of various cryptocurrencies. 4 | The main task is to track the cryptocurrency. The server receives updated data, and it monitors the prices of the cryptocurrency. The extension gets updated statistics from the [CoinCap](https://coincap.io) API. CoinCap is a useful tool for real-time pricing and market activity for over 1,000 cryptocurrencies. 5 | 6 | ## Installation 7 | 8 | Extension is available for downloading in [Chrome Web Store](https://chrome.google.com/webstore/detail/metabase/coichcomkldmiibhiedhncflegehdnog?hl=en) 9 | 10 | ## Word from author 11 | 12 | Have fun! ✌️ 13 | 14 | 15 | Supported by Halo lab 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | const CoinService = { 2 | baseUrl: 'https://api.coincap.io/v2/', 3 | fetch(url, callback){ 4 | return fetch(url) 5 | .then(response => response.json()) 6 | .then(callback) 7 | .catch(error => console.log('error', error)); 8 | }, 9 | findCoin(name, callback) { 10 | const url = `${this.baseUrl}assets/${name}`; 11 | return this.fetch(url, callback); 12 | }, 13 | findCoins(names, callback) { 14 | const url = `${this.baseUrl}assets?ids=${names}`; 15 | return this.fetch(url, callback); 16 | }, 17 | limit(offset=0, limit , callback) { 18 | const url = `${this.baseUrl}assets?offset=${offset}&limit=${limit}`; 19 | return this.fetch(url, callback); 20 | }, 21 | history(name, period, callback) { 22 | const url = `${this.baseUrl}assets/${name}/history?interval=${period}`; 23 | return this.fetch(url, callback); 24 | } 25 | } 26 | 27 | export default CoinService; -------------------------------------------------------------------------------- /src/containers/Main/Main.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style/veriables.scss'; 2 | 3 | .container { 4 | position: relative; 5 | padding-bottom: 24px; 6 | &::before { 7 | content: ''; 8 | position: fixed; 9 | left: 0; 10 | bottom: 0; 11 | width: 100%; 12 | height: 8px; 13 | z-index: 10; 14 | background: linear-gradient(90deg, #2642ba -5%, #65c19c 149.01%); 15 | } 16 | } 17 | 18 | .CoinContainer { 19 | position: relative; 20 | // height: 360px; 21 | overflow-y: auto; 22 | } 23 | 24 | .button { 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | color: $color-white; 29 | background: $color-blue; 30 | font-weight: 500; 31 | font-size: 14px; 32 | line-height: 17px; 33 | width: 144px; 34 | height: 40px; 35 | border-radius: 25px; 36 | margin-left: auto; 37 | margin-top: 16px; 38 | margin-right: 24px; 39 | border: none; 40 | transition: background 0.3s; 41 | 42 | &:hover { 43 | background: lighten($color-blue, 20%); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Favorite/Favorite.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Item from './FavoriteItem'; 4 | 5 | import styles from './Favorite.module.scss'; 6 | import Swiper from 'react-id-swiper'; 7 | import 'swiper/swiper.scss'; 8 | 9 | const Favorite = ({ data, toogleFavorite }) => { 10 | const params = { 11 | slidesPerView: 'auto', 12 | spaceBetween: 16 13 | }; 14 | return ( 15 |
16 |

Favorite

17 | 18 | {data.map((item, index) => { 19 | return ( 20 | 21 | ); 22 | })} 23 | 24 |
25 | ); 26 | }; 27 | 28 | Favorite.defaultProps = { 29 | data: [], 30 | toogleFavorite: () => {} 31 | }; 32 | 33 | Favorite.propTypes = { 34 | data: PropTypes.array, 35 | toogleFavorite: PropTypes.func 36 | }; 37 | 38 | export default Favorite; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Tvorohov Oleksandr 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const transformToStringWithPoint = (cost, symbol) => { 2 | return cost.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',' + (symbol || '')); 3 | }; 4 | 5 | export const cutValueAfterPoint = (price, type = 'price') => { 6 | const newPrice = Number(price); 7 | const numberAfterPoint = newPrice < 1 && type !== 'percent' ? 6 : 2; 8 | return newPrice.toFixed(numberAfterPoint); 9 | }; 10 | 11 | const DAY_MILLISECONDS = 86400000; 12 | 13 | export const chartPeriod = period => { 14 | switch (period) { 15 | case '12H': 16 | return { 17 | history: 'm5', 18 | time: DAY_MILLISECONDS / 2 19 | }; 20 | case '1D': 21 | return { 22 | history: 'm30', 23 | time: DAY_MILLISECONDS 24 | }; 25 | case '1W': 26 | return { 27 | history: 'h2', 28 | time: DAY_MILLISECONDS * 7 29 | }; 30 | case '1M': 31 | return { 32 | history: 'h12', 33 | time: DAY_MILLISECONDS * 30 34 | }; 35 | case '1Y': 36 | return { 37 | history: 'd1', 38 | time: DAY_MILLISECONDS * 365 39 | }; 40 | default: 41 | return { 42 | history: 'm15', 43 | time: DAY_MILLISECONDS / 2 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/Autocomplite/Autocomplite.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style/veriables.scss'; 2 | .wrapper { 3 | position: relative; 4 | height: 24px; 5 | } 6 | 7 | .input { 8 | border: 1px solid $color-light-grey; 9 | width: 168px; 10 | border-radius: 5px; 11 | height: 24px; 12 | padding: 0 8px 0 32px; 13 | vertical-align: top; 14 | font-family: $font; 15 | font-size: 12px; 16 | } 17 | 18 | .icon { 19 | position: absolute; 20 | left: 8px; 21 | top: 50%; 22 | transform: translateY(-50%); 23 | } 24 | 25 | .nosuggestions { 26 | position: absolute; 27 | color: rgb(244, 84, 65); 28 | font-size: 12px; 29 | bottom: -20px; 30 | left: 50%; 31 | transform: translateX(-50%); 32 | } 33 | 34 | .suggestions { 35 | position: absolute; 36 | top: 100%; 37 | left: 0; 38 | background: $color-white; 39 | border: 1px solid $color-light-grey; 40 | border-top-width: 0; 41 | list-style: none; 42 | margin-top: 0; 43 | max-height: 150px; 44 | overflow-y: auto; 45 | padding-left: 0; 46 | width: calc(150px + 1rem); 47 | z-index: 11; 48 | width: 100%; 49 | font-size: 12px; 50 | } 51 | 52 | .suggestions li { 53 | padding: 8px; 54 | } 55 | 56 | .suggestionActive, 57 | .suggestions li:hover { 58 | background-color: $color-blue; 59 | color: $color-white; 60 | cursor: pointer; 61 | font-weight: 700; 62 | } 63 | 64 | .suggestions li:not(:last-of-type) { 65 | border-bottom: 1px solid $color-light-grey; 66 | } 67 | -------------------------------------------------------------------------------- /src/components/Favorite/FavoriteItem/FavoriteItem.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../style/veriables.scss'; 2 | 3 | .item { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: space-between; 7 | padding: 16px; 8 | box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.05); 9 | border-radius: 15px; 10 | width: 184px !important; 11 | height: 112px !important; 12 | } 13 | 14 | .wrapper { 15 | position: relative; 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | 20 | .name { 21 | font-weight: 600; 22 | font-size: 14px; 23 | line-height: 17px; 24 | } 25 | 26 | .id { 27 | font-weight: 600; 28 | font-size: 14px; 29 | line-height: 17px; 30 | color: rgba(48, 48, 48, 0.4); 31 | } 32 | 33 | .price { 34 | font-weight: 500; 35 | font-size: 18px; 36 | line-height: 21px; 37 | } 38 | 39 | .percent { 40 | font-weight: 500; 41 | font-size: 14px; 42 | line-height: 17px; 43 | } 44 | 45 | .green { 46 | color: $color-green; 47 | } 48 | 49 | .red { 50 | color: $color-pink; 51 | } 52 | 53 | .checkbox { 54 | position: relative; 55 | left: 0; 56 | } 57 | 58 | .arrow { 59 | display: inline-block; 60 | border: solid #fff; 61 | border-width: 0 2px 2px 0; 62 | width: 6px; 63 | height: 6px; 64 | margin-left: 8px; 65 | margin-bottom: 2px; 66 | } 67 | 68 | .arrow.up { 69 | border-color: $color-green; 70 | transform: rotate(-135deg); 71 | padding-bottom: 2px; 72 | padding-right: 2px; 73 | } 74 | 75 | .arrow.down { 76 | border-color: $color-pink; 77 | transform: rotate(45deg); 78 | } 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.4.0", 8 | "@testing-library/user-event": "^7.2.1", 9 | "chart.js": "^2.9.3", 10 | "classnames": "^2.2.6", 11 | "moment": "^2.24.0", 12 | "node-sass": "^4.13.1", 13 | "prop-types": "^15.7.2", 14 | "react": "^16.12.0", 15 | "react-dom": "^16.12.0", 16 | "react-id-swiper": "^3.0.0", 17 | "react-router-dom": "^5.1.2", 18 | "react-scripts": "3.4.0", 19 | "swiper": "^6.8.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject", 26 | "lint": "eslint --ext .jsx,.js src/" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "devDependencies": { 44 | "eslint": "^6.8.0", 45 | "eslint-config-airbnb": "^18.0.1", 46 | "eslint-config-prettier": "^6.10.0", 47 | "eslint-plugin-import": "^2.20.1", 48 | "eslint-plugin-jsx-a11y": "^6.2.3", 49 | "eslint-plugin-prettier": "^3.1.1", 50 | "eslint-plugin-react": "^7.18.3", 51 | "eslint-plugin-react-hooks": "^2.5.0", 52 | "prettier": "^1.19.1" 53 | }, 54 | "lint-staged": { 55 | "src/**/*.{js,jsx,json,css,scss,md}": [ 56 | "prettier --write", 57 | "git add" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/Checkbox/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import styles from './Checkbox.module.scss'; 5 | 6 | const Checkbox = ({ click, coin, isChecked, addClass, func }) => { 7 | return ( 8 |
9 | 10 | 25 |
26 | ); 27 | }; 28 | 29 | Checkbox.defaultProps = { 30 | click: () => {}, 31 | coin: '', 32 | isChecked: false, 33 | addClass: '' 34 | }; 35 | 36 | Checkbox.propTypes = { 37 | click: PropTypes.func, 38 | coin: PropTypes.string, 39 | isChecked: PropTypes.bool, 40 | addClass: PropTypes.string 41 | }; 42 | 43 | export default Checkbox; 44 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/Favorite/FavoriteItem/FavoriteItem.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Checkbox from '../../Checkbox/Checkbox'; 3 | import style from './FavoriteItem.module.scss'; 4 | import PropTypes from 'prop-types'; 5 | 6 | import { cutValueAfterPoint } from '../../../utils'; 7 | 8 | const FavoriteItem = ({ 9 | id, 10 | name, 11 | symbol, 12 | number, 13 | priceUsd, 14 | changePercent24Hr, 15 | isFavorite, 16 | toogleFavorite 17 | }) => { 18 | const [price, setPrice] = useState(cutValueAfterPoint(priceUsd)); 19 | const [priceState, setPriceState] = useState(null); 20 | useEffect(() => { 21 | const pricesWs = new WebSocket(`wss://ws.coincap.io/prices?assets=${id}`); 22 | pricesWs.onmessage = function(msg) { 23 | const dataFromServer = cutValueAfterPoint(JSON.parse(msg.data)[id]); 24 | const priceStateClass = dataFromServer > price ? style.up : style.down; 25 | setPriceState(priceStateClass); 26 | setPrice(dataFromServer); 27 | }; 28 | return () => { 29 | pricesWs.close(); 30 | }; 31 | }, [id]); 32 | 33 | const percent = cutValueAfterPoint(changePercent24Hr, 'percent'); 34 | const percentClass = changePercent24Hr > 0 ? style.green : style.red; 35 | return ( 36 |
37 |
38 | 39 | {number}. {name} 40 | 41 | {symbol} 42 |
43 |
44 | $ {price} 45 | 46 |
47 |
48 | {percent}% 49 | { 53 | toogleFavorite(id); 54 | }} 55 | /> 56 |
57 |
58 | ); 59 | }; 60 | 61 | FavoriteItem.propTypes = { 62 | id: PropTypes.string, 63 | name: PropTypes.string, 64 | symbol: PropTypes.string, 65 | number: PropTypes.number, 66 | priceUsd: PropTypes.string, 67 | changePercent24Hr: PropTypes.string, 68 | isFavorite: PropTypes.bool, 69 | toogleFavorite: PropTypes.func 70 | }; 71 | 72 | export default FavoriteItem; 73 | -------------------------------------------------------------------------------- /src/components/CoinCard/CoinCard.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../style/veriables.scss'; 2 | 3 | .card { 4 | position: relative; 5 | display: flex; 6 | align-items: center; 7 | flex-direction: column; 8 | padding: 10px 32px; 9 | background-color: $color-white; 10 | font-weight: 500; 11 | font-size: 14px; 12 | box-shadow: 0px 4px 25px rgba(0, 0, 0, 0.05); 13 | & + .card { 14 | margin-top: 8px; 15 | } 16 | } 17 | 18 | .row { 19 | display: flex; 20 | align-items: center; 21 | min-height: 50px; 22 | width: 100%; 23 | cursor: pointer; 24 | } 25 | 26 | .image { 27 | width: 32px; 28 | 29 | margin-right: 16px; 30 | } 31 | .name { 32 | width: 35%; 33 | font-weight: 600; 34 | font-size: 16px; 35 | line-height: 19px; 36 | 37 | span { 38 | margin-top: 8px; 39 | font-weight: 500; 40 | font-size: 13px; 41 | line-height: 16px; 42 | color: $color-light-grey; 43 | } 44 | } 45 | .price { 46 | width: 30%; 47 | margin-left: auto; 48 | text-align: right; 49 | } 50 | .persent { 51 | width: 14%; 52 | text-align: right; 53 | } 54 | 55 | .green { 56 | color: $color-green; 57 | } 58 | 59 | .red { 60 | color: $color-pink; 61 | } 62 | 63 | .wrapper { 64 | display: flex; 65 | width: 100%; 66 | margin-top: 16px; 67 | } 68 | 69 | .buttons { 70 | display: flex; 71 | flex-direction: column; 72 | align-self: flex-start; 73 | width: 64px; 74 | border-radius: 10px; 75 | background-color: #faf9f9; 76 | margin-left: 17px; 77 | padding: 4px 0; 78 | } 79 | .button { 80 | font-weight: 600; 81 | font-size: 11px; 82 | line-height: 14px; 83 | width: 100%; 84 | padding: 4px 0; 85 | border-radius: 10px; 86 | border: transparent; 87 | background-color: #faf9f9; 88 | color: #87898d; 89 | transition: 0.3s; 90 | 91 | & + .button { 92 | margin-top: 4px; 93 | } 94 | 95 | &.active { 96 | background-color: #29c585; 97 | color: $color-white; 98 | } 99 | &:hover { 100 | background-color: #29c585; 101 | color: $color-white; 102 | } 103 | } 104 | 105 | .arrow { 106 | display: flex; 107 | justify-content: center; 108 | align-items: center; 109 | border-radius: 50%; 110 | width: 16px; 111 | height: 16px; 112 | margin-left: 2%; 113 | span { 114 | border: solid #fff; 115 | border-width: 0 2px 2px 0; 116 | padding: 2px; 117 | } 118 | } 119 | 120 | .arrow.up { 121 | background-color: $color-green; 122 | transform: rotate(-135deg); 123 | padding-bottom: 2px; 124 | padding-right: 2px; 125 | } 126 | 127 | .arrow.down { 128 | background-color: $color-pink; 129 | transform: rotate(45deg); 130 | } 131 | 132 | .checkbox { 133 | top: 25px; 134 | } 135 | -------------------------------------------------------------------------------- /src/components/Chart/Chart.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import Chart from 'chart.js'; 3 | 4 | import style from './Chart.module.scss'; 5 | 6 | const ChartCanvas = ({ data, rise, timeInterval }) => { 7 | const canvas = useRef(); 8 | const ctx = useRef(); 9 | useEffect(() => { 10 | ctx.current = canvas.current.getContext('2d'); 11 | const cfg = { 12 | data: { 13 | datasets: [ 14 | { 15 | label: '', 16 | backgroundColor: rise ? CHART_COLORS.lightGreen : CHART_COLORS.lightRed, 17 | borderColor: rise ? CHART_COLORS.green : CHART_COLORS.red, 18 | data: data 19 | .filter(item => Date.now() - timeInterval < item.time) 20 | .map(item => { 21 | return { 22 | t: item.time, 23 | y: item.priceUsd 24 | }; 25 | }), 26 | type: 'line', 27 | pointRadius: 0, 28 | lineTension: 0, 29 | borderWidth: 2 30 | } 31 | ] 32 | }, 33 | options: { 34 | animation: { 35 | duration: 0 36 | }, 37 | legend: { 38 | display: false 39 | }, 40 | scales: { 41 | padding: 0, 42 | xAxes: [ 43 | { 44 | type: 'time', 45 | gridLines: { 46 | display: false 47 | }, 48 | ticks: { 49 | display: false, 50 | padding: 10 51 | } 52 | } 53 | ], 54 | yAxes: [ 55 | { 56 | ticks: { 57 | display: false, 58 | padding: 0 59 | }, 60 | gridLines: { 61 | display: false 62 | }, 63 | scaleLabel: { 64 | display: false 65 | } 66 | } 67 | ] 68 | }, 69 | tooltips: { 70 | intersect: false, 71 | mode: 'index', 72 | custom: function(tooltip) { 73 | if (!tooltip) return; 74 | tooltip.displayColors = false; 75 | }, 76 | callbacks: { 77 | label: function(tooltipItem) { 78 | return `Price: $${parseFloat(tooltipItem.value).toFixed(2)}`; 79 | } 80 | } 81 | } 82 | } 83 | }; 84 | const newChart = new Chart(ctx.current, cfg); 85 | return () => { 86 | newChart.destroy(); 87 | }; 88 | }, [data, rise, timeInterval]); 89 | 90 | return ( 91 |
92 | 93 |
94 | ); 95 | }; 96 | 97 | const CHART_COLORS = { 98 | green: 'rgb(41, 197, 133)', 99 | lightGreen: 'rgba(183, 220, 175, 0.5)', 100 | red: 'rgb(244, 84, 65)', 101 | lightRed: 'rgba(244, 84, 65, 0.3)' 102 | }; 103 | 104 | export default ChartCanvas; 105 | -------------------------------------------------------------------------------- /src/containers/Main/Main.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import classes from './Main.module.scss'; 3 | import CoinCard from '../../components/CoinCard'; 4 | import CoinService from '../../services/index'; 5 | import Header from '../../components/Header'; 6 | import TableHeader from '../../components/TableHeader'; 7 | import Favorites from '../../components/Favorite'; 8 | 9 | const initialFavoritesState = { 10 | list: localStorage.getItem('favorites') ? localStorage.getItem('favorites').split(',') : [], 11 | data: [] 12 | }; 13 | 14 | const initialActiveCoins = 4; 15 | 16 | const CoinContainer = () => { 17 | const [data, setData] = useState([]); 18 | const [favorites, setFavorites] = useState(initialFavoritesState); 19 | const [activeCoins, setActiveCoins] = useState(initialActiveCoins); 20 | 21 | useEffect(() => { 22 | fetchCoins(); 23 | sortFavorits(); 24 | localStorage.setItem('favorites', favorites.list); 25 | }, [activeCoins, favorites.list]); 26 | 27 | const fetchCoins = () => { 28 | CoinService.limit(0, activeCoins, result => { 29 | setData( 30 | result.data.map(item => { 31 | return { ...item, isFavorite: favorites.list.includes(item.id) }; 32 | }) 33 | ); 34 | }); 35 | }; 36 | 37 | const findCoin = name => { 38 | CoinService.findCoin(name, result => setData([result.data])); 39 | }; 40 | 41 | const sortFavorits = () => { 42 | if (favorites.list.length > 0) { 43 | const names = favorites.list.join(','); 44 | CoinService.findCoins(names, result => { 45 | const favoriteData = result.data.map(item => { 46 | return { ...item, isFavorite: favorites.list.includes(item.id) }; 47 | }); 48 | setFavorites({ ...favorites, data: favoriteData }); 49 | }); 50 | } else { 51 | setFavorites({ ...favorites, data: [] }); 52 | } 53 | }; 54 | 55 | const toogleFavorite = id => { 56 | if (favorites.list.includes(id)) { 57 | const newFavoritesList = favorites.list.filter(item => item !== id); 58 | setFavorites({ ...favorites, list: newFavoritesList }); 59 | } else { 60 | const newFavoritesList = [...favorites.list, id]; 61 | setFavorites({ ...favorites, list: newFavoritesList }); 62 | } 63 | }; 64 | 65 | const showMoreCoinsClick = () => { 66 | const newValue = activeCoins + initialActiveCoins; 67 | setActiveCoins(newValue); 68 | }; 69 | 70 | return ( 71 |
72 |
73 | {favorites.data.length > 0 ? ( 74 | 75 | ) : null} 76 | 77 |
78 | {data.map((coin, index) => ( 79 | 86 | ))} 87 |
88 | 91 |
92 | ); 93 | }; 94 | 95 | export default CoinContainer; 96 | -------------------------------------------------------------------------------- /src/components/Autocomplite/Autocomplite.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import COIN_NAMES from './names'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import classes from './Autocomplite.module.scss'; 6 | import Icon from './images/search-icon.svg'; 7 | 8 | const initialState = { 9 | activeSuggestion: 0, 10 | filteredSuggestions: [], 11 | showSuggestions: false, 12 | userInput: '' 13 | }; 14 | 15 | function Autocomplete({ findCoin, getCoins }) { 16 | const [state, setState] = useState(initialState); 17 | 18 | const onChange = e => { 19 | const userInput = e.currentTarget.value; 20 | 21 | if (userInput.length === 0) { 22 | getCoins(); 23 | } 24 | 25 | const filteredSuggestions = COIN_NAMES.filter( 26 | suggestion => 27 | suggestion 28 | .toLowerCase() 29 | .slice(0, userInput.length) 30 | .indexOf(userInput.toLowerCase()) > -1 31 | ); 32 | 33 | setState({ 34 | ...state, 35 | filteredSuggestions, 36 | showSuggestions: true, 37 | userInput: e.currentTarget.value 38 | }); 39 | }; 40 | 41 | const onClick = e => { 42 | setState({ 43 | ...state, 44 | showSuggestions: false, 45 | userInput: e.currentTarget.innerText 46 | }); 47 | findCoin(e.currentTarget.innerText); 48 | }; 49 | 50 | const onKeyDown = e => { 51 | const { activeSuggestion, filteredSuggestions } = state; 52 | 53 | if (e.keyCode === 13) { 54 | setState({ 55 | activeSuggestion, 56 | showSuggestions: false, 57 | userInput: filteredSuggestions[activeSuggestion] 58 | }); 59 | 60 | findCoin(filteredSuggestions[activeSuggestion]); 61 | } 62 | if (e.keyCode === 38) { 63 | if (activeSuggestion === 0) { 64 | return; 65 | } 66 | setState({ ...state, activeSuggestion: activeSuggestion - 1 }); 67 | } 68 | if (e.keyCode === 40) { 69 | if (state.activeSuggestion - 1 === state.filteredSuggestions.length) { 70 | return; 71 | } 72 | setState({ ...state, activeSuggestion: activeSuggestion + 1 }); 73 | } 74 | }; 75 | 76 | let suggestionsListComponent; 77 | 78 | if (state.showSuggestions && state.userInput) { 79 | if (state.filteredSuggestions.length) { 80 | suggestionsListComponent = ( 81 | 96 | ); 97 | } else { 98 | suggestionsListComponent =
No matches!
; 99 | } 100 | } 101 | 102 | return ( 103 |
104 | search icon 105 | 112 | {suggestionsListComponent} 113 |
114 | ); 115 | } 116 | 117 | Autocomplete.defaultProps = { 118 | findCoin: () => {}, 119 | getCoins: () => {} 120 | }; 121 | 122 | Autocomplete.propTypes = { 123 | findCoin: PropTypes.func, 124 | getCoins: PropTypes.func 125 | }; 126 | 127 | export default Autocomplete; 128 | -------------------------------------------------------------------------------- /src/components/CoinCard/CoinCard.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from '../Chart/Chart'; 3 | import Checkbox from '../Checkbox/Checkbox'; 4 | import classes from './CoinCard.module.scss'; 5 | import PropTypes from 'prop-types'; 6 | 7 | import CoinService from '../../services/index'; 8 | import { cutValueAfterPoint, chartPeriod } from '../../utils'; 9 | 10 | const initialChartState = { 11 | isOpen: false, 12 | data: [], 13 | period: '12H' 14 | }; 15 | 16 | const CoinCard = ({ value, addFavorits, index, isFavorite }) => { 17 | const { id, symbol, name, priceUsd, changePercent24Hr } = value; 18 | const [price, setPrice] = useState(cutValueAfterPoint(priceUsd)); 19 | const [chartState, setChartState] = useState(initialChartState); 20 | const [priceState, setPriceState] = useState(null); 21 | useEffect(() => { 22 | const pricesWs = new WebSocket(`wss://ws.coincap.io/prices?assets=${id}`); 23 | pricesWs.onmessage = function(msg) { 24 | const dataFromServer = cutValueAfterPoint(JSON.parse(msg.data)[id]); 25 | const priceStateClass = dataFromServer > price ? classes.up : classes.down; 26 | setPriceState(priceStateClass); 27 | setPrice(dataFromServer); 28 | }; 29 | return () => { 30 | pricesWs.close(); 31 | }; 32 | }, [id]); 33 | 34 | const fetchPriceHistory = () => { 35 | if (!chartState.isOpen) { 36 | const timeInterval = chartPeriod(chartState.period).history; 37 | CoinService.history(id, timeInterval, result => { 38 | setChartState({ ...chartState, data: result.data, isOpen: true }); 39 | }); 40 | } else { 41 | setChartState({ ...chartState, isOpen: false }); 42 | } 43 | }; 44 | 45 | const changePeriod = period => { 46 | const timeInterval = chartPeriod(period).history; 47 | CoinService.history(id, timeInterval, result => { 48 | setChartState({ ...chartState, data: result.data, period }); 49 | }); 50 | }; 51 | 52 | const imageSrc = `https://static.coincap.io/assets/icons/${symbol.toLowerCase()}@2x.png`; 53 | 54 | const percent = cutValueAfterPoint(changePercent24Hr, 'percent'); 55 | const percentClass = changePercent24Hr > 0 ? classes.green : classes.red; 56 | return ( 57 |
58 | addFavorits(id)} 60 | coin={id} 61 | isChecked={isFavorite} 62 | addClass={classes.checkbox} 63 | /> 64 |
65 | icon 66 |
67 |

68 | {index}.{name} 69 |

70 | {symbol} 71 |
72 | ${price} 73 |
74 | 75 |
76 |
77 | {percent}% 78 |
79 |
80 | {chartState.isOpen ? ( 81 |
82 | 0} 85 | timeInterval={chartPeriod(chartState.period).time} 86 | /> 87 |
88 | {TIME_PERIODS.map(time => { 89 | const isActive = time === chartState.period ? classes.active : ''; 90 | return ( 91 | 98 | ); 99 | })} 100 |
101 |
102 | ) : null} 103 |
104 | ); 105 | }; 106 | 107 | const TIME_PERIODS = ['12H', '1D', '1W', '1M', '1Y']; 108 | 109 | CoinCard.propTypes = { 110 | value: PropTypes.object, 111 | addFavorits: PropTypes.func, 112 | index: PropTypes.number, 113 | isFavorite: PropTypes.bool 114 | }; 115 | 116 | export default CoinCard; 117 | -------------------------------------------------------------------------------- /src/components/Autocomplite/names.js: -------------------------------------------------------------------------------- 1 | const COIN_NAMES = ["bitcoin", "ethereum", "ripple", "bitcoin-cash", "bitcoin-sv", "litecoin", "tether", "eos", "binance-coin", "tezos", "chainlink", "cardano", "stellar", "monero", "tron", "cosmos", "huobi-token", "ethereum-classic", "neo", "dash", "iota", "maker", "zcash", "nem", "ontology", "basic-attention-token", "vechain", "dogecoin", "algorand", "paxos-standard-token", "qtum", "decred", "icon", "lisk", "0x", "bitcoin-gold", "ravencoin", "omisego", "augur", "waves", "bitcoin-diamond", "trueusd", "siacoin", "nano", "monacoin", "holo", "nexo", "enjin-coin", "dai", "kyber-network", "v-systems", "digixdao", "bytom", "theta-token", "zencash", "digibyte", "komodo", "bitshares", "crypto-com", "hypercash", "steem", "iostoken", "verge", "zilliqa", "ardor", "golem-network-tokens", "aelf", "seele", "status", "aeternity", "zcoin", "decentraland", "rlc", "wax", "power-ledger", "stratis", "loopring", "grin", "project-pai", "ethlend", "elastos", "dragon-coins", "truechain", "ripio-credit-network", "unibright", "waltonchain", "wanchain", "iotex", "nebulas-token", "ark", "funfair", "loom-network", "enigma-project", "nuls", "tierion", "populous", "numeraire", "bancor", "pivx", "storj", "metal", "cortex", "gnosis-gno", "civic", "syscoin", "gas", "qash", "revain", "bitkan", "bibox-token", "dent", "gochain", "eidoo", "groestlcoin", "singularitynet", "nxt", "arcblock", "libra-credit", "moeda-loyalty-points", "polymath-network", "cybermiles", "neblio", "request-network", "streamr-datacoin", "iot-chain", "high-performance-blockchain", "storm", "quarkchain", "nav-coin", "u-network", "gifto", "adx-net", "wabi", "tenx", "quantstamp", "raiden-network-token", "smartcash", "utrust", "achain", "everex", "poet", "mithril", "time-new-bank", "smartmesh", "swftcoin", "wepower", "nucleus-vision", "dentacoin", "matrix-ai-network", "cybervein", "bluzelle", "dock", "viacoin", "district0x", "appcoins", "vibe", "viberate", "monetha", "oax", "ethos", "measurable-data-token", "qlink", "litecoin-cash", "agrello-delta", "game", "airswap", "deepbrain-chain", "qunqun", "suncontract", "medishares", "blockmason", "etherparty", "odyssey", "aeron", "genaro-network", "amber", "whitecoin", "lunyr", "scryinfo", "skrumble-network", "linkeye", "cube", "digitalnote", "primas", "bitmark", "acute-angle-cloud", "unus-sed-leo", "usd-coin", "molecular-future", "dxchain-token", "centrality", "blockstack", "aion", "quant", "pundi-x", "maidsafecoin", "tomochain", "waykichain", "beam", "electroneum", "gxchain", "aragon", "crypterium", "reddcoin", "factom", "metaverse", "bread", "byteball", "orchid", "swissborg", "constellation", "you-coin", "nexus", "cindicator", "blocknet", "vertcoin", "bnktothefuture", "b2bx", "nkn", "skycoin", "mainframe", "ost", "polybius", "zrcoin", "egretia", "dmarket", "ruff", "presearch", "vite", "tokencard", "all-sports", "melon", "selfkey", "lockchain", "peercoin", "tripio", "genesis-vision", "incent", "gulden", "library-credit", "stakenet", "propy", "ttc-protocol", "hycon", "sonm", "particl", "tokenclub", "blox", "gemini-dollar", "contentbox", "sirin-labs-token", "pepe-cash", "emercoin", "dao-casino", "data", "poa-network", "veridocglobal", "xaurum", "lamden", "content-neutrality-network", "refereum", "oneroot-network", "okcash", "kcash", "insights-network", "remme", "aidoc", "zclassic", "origin-sport", "cononchain", "zip", "atc-coin", "hydro-protocol", "iht-real-estate-protocol", "penta", "yee", "daex", "kryll", "vericoin", "peerplays-ppy", "chatcoin", "eosdac", "friends", "medical-chain", "datum", "humaniq", "internet-node-token", "tokenpay", "bankex", "stk", "insurepal", "echolink", "aventus", "ulord", "internxt", "matryx", "playkey", "red", "zeusshield", "mercury", "memetic", "helbiz", "minexcoin", "ink-protocol", "selfsell", "rentberry", "micromoney", "patron", "soma", "intelligent-trading-foundation", "comet", "bytecoin-bcn", "havven", "bankera", "platincoin", "stasis-eurs", "dynamic-trading-rights", "c20", "ignis", "latoken", "boscoin", "thekey", "veritaseum", "unobtanium", "dragonchain", "telcoin", "robotina", "lightning-bitcoin", "einsteinium", "burst", "medibloc", "clams", "credits", "bitcore", "xinfin-network", "namecoin", "rocket-pool", "nimiq", "1world", "pillar", "hydrogen", "uttoken", "endor-protocol", "salus", "steem-dollars", "bitcapitalvendor", "metronome", "taas", "flo", "wagerr", "fusion", "kin", "firstblood", "quantum-resistant-ledger", "rialto", "zelcash", "monero-classic", "deeponion", "decent-bet", "box-token", "singulardtv", "invictus-hyperion-fund", "neumark", "babb", "blackmoon", "jibrel-network", "salt", "counterparty", "universa", "peculium", "html-coin", "morpheus-network", "ubiq", "get-protocol", "polyswarm", "blockv", "carvertical", "0chain", "grid", "gamecredits", "oneledger", "mobius", "uquid-coin", "commerceblock", "dacsee", "aeon", "te-food", "lympo", "pumapay", "potcoin", "the-abyss", "morpheus-labs", "bit-tube", "bloomtoken", "cryptaur", "winding-tree", "trueflip", "feathercoin", "lykke", "yoyow"] 2 | 3 | export default COIN_NAMES; -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/components/Header/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------