├── .editorconfig ├── .env.example ├── .gitignore ├── .gitlab-ci.yml ├── .nvmrc ├── LICENSE ├── README.md ├── craco.config.js ├── gulpfile.js ├── now.gitlab.json ├── now.json ├── package.json ├── patches └── web3+0.20.7.patch ├── public ├── favicon.ico ├── index.html ├── manifest.json └── static │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── dai-400px.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── og-image.png │ └── safari-pinned-tab.svg ├── src ├── App.test.js ├── abi │ ├── dsethtoken.json │ ├── dsproxy.json │ ├── dsproxyfactory.json │ ├── dstoken.json │ ├── dsvalue.json │ ├── faucet.json │ ├── medianizer.json │ ├── proxyregistry.json │ ├── saiProxyCreateAndExecute.json │ ├── saitap.json │ ├── saitop.json │ ├── saitub.json │ ├── saivaluesaggregator.json │ └── saivox.json ├── assets │ ├── fonts │ │ ├── slick.eot │ │ ├── slick.svg │ │ ├── slick.ttf │ │ └── slick.woff │ ├── images │ │ ├── 404.svg │ │ ├── cdp-created-icon-1.svg │ │ ├── cdp-created-icon-2.svg │ │ ├── cdp-created-icon-3.svg │ │ ├── cdp-created.svg │ │ ├── cdp-creating-1.svg │ │ ├── cdp-creating-2.svg │ │ ├── cdp-creating-3.svg │ │ ├── cdp-creating-4.svg │ │ ├── cdp-creating-5.svg │ │ ├── cdp-creating-6.svg │ │ ├── history-icon-borrow.svg │ │ ├── history-icon-give.svg │ │ ├── history-icon-liquidation.svg │ │ ├── history-icon-locked.svg │ │ ├── history-icon-open.svg │ │ ├── history-icon-payback.svg │ │ ├── history-icon-transfer.svg │ │ ├── history-icon-unknown.svg │ │ ├── icon-arrow-right.svg │ │ ├── icon-check.svg │ │ ├── icon-clock.svg │ │ ├── icon-close-dark.svg │ │ ├── icon-close.svg │ │ ├── icon-help.svg │ │ ├── icon-home.svg │ │ ├── icon-settings.svg │ │ ├── icon-stop.svg │ │ ├── icon-warning.svg │ │ ├── ledger-nano-logo.png │ │ ├── maker-logo-footer.svg │ │ ├── menu-icon-ledger.svg │ │ ├── menu-icon-trezor.svg │ │ ├── menu-icon-web.svg │ │ ├── metamask-logo.svg │ │ ├── parity-logo.png │ │ ├── send-icon.png │ │ ├── slick-loader.gif │ │ ├── trezor-logo.png │ │ ├── wallet-icon.png │ │ ├── welcome-hero.svg │ │ └── welcome-satellite.svg │ └── json │ │ └── terms.json ├── components │ ├── App.jsx │ ├── Confirm.jsx │ ├── ConfirmMobile.jsx │ ├── Cup.jsx │ ├── CupHistory.jsx │ ├── CupInfoMobile.jsx │ ├── CupMobile.jsx │ ├── Dashboard.jsx │ ├── Dialog.jsx │ ├── DropdownMenu.jsx │ ├── Footer.jsx │ ├── GeneralNotifications.jsx │ ├── Help.jsx │ ├── HelpItem.jsx │ ├── Home.jsx │ ├── InlineNotification.jsx │ ├── Landing.jsx │ ├── LegacyCups.jsx │ ├── LegacyCupsAlert.jsx │ ├── LoadingSpinner.jsx │ ├── McdAlert.jsx │ ├── Menu.jsx │ ├── Modal.jsx │ ├── NewCup.jsx │ ├── NewCupMobile.jsx │ ├── NotFound.jsx │ ├── Notify.jsx │ ├── NotifySetUp.jsx │ ├── OasisAlert.jsx │ ├── PriceModal.jsx │ ├── Routes.jsx │ ├── StabilityFeeAlert.jsx │ ├── SystemInfo.jsx │ ├── Terms.jsx │ ├── ToggleSwitch.jsx │ ├── TooltipHint.jsx │ ├── Wallet.jsx │ ├── WalletClientDownload.jsx │ ├── WalletClientSelector.jsx │ ├── WalletHWSelector.jsx │ ├── WalletIcons.jsx │ ├── WalletMobileClientDownload.jsx │ ├── WalletMobileConnect.jsx │ ├── WalletNoAccount.jsx │ ├── WalletSendToken.jsx │ ├── Welcome.jsx │ └── Wizard.jsx ├── index.js ├── scss │ ├── _breakpoints.scss │ ├── _buttons.scss │ ├── _colors.scss │ ├── _cp-charts.scss │ ├── _cp-cup-history.scss │ ├── _cp-cup.scss │ ├── _cp-dialog.scss │ ├── _cp-dropdown-menu.scss │ ├── _cp-help.scss │ ├── _cp-inline-notification.scss │ ├── _cp-landing.scss │ ├── _cp-loading-spinner.scss │ ├── _cp-markdown.scss │ ├── _cp-menu-bar.scss │ ├── _cp-migrate-legacy-cups.scss │ ├── _cp-modal.scss │ ├── _cp-not-found.scss │ ├── _cp-notify.scss │ ├── _cp-range-slider.scss │ ├── _cp-react-select.scss │ ├── _cp-right-column.scss │ ├── _cp-slider.scss │ ├── _cp-steps.scss │ ├── _cp-toggle-switch.scss │ ├── _cp-tooltip.scss │ ├── _cp-wizard.scss │ ├── _forecast-mode.scss │ ├── _forms.scss │ ├── _general.scss │ ├── _helpers.scss │ ├── _interactive.scss │ ├── _layout.scss │ ├── _mixins.scss │ ├── _normalize.scss │ ├── _text-styles.scss │ ├── _typography.scss │ └── styles.scss ├── settings.json ├── stores │ ├── Content.jsx │ ├── Dialog.jsx │ ├── Network.jsx │ ├── Profile.jsx │ ├── Root.jsx │ ├── System.jsx │ └── Transactions.jsx └── utils │ ├── address-generator.js │ ├── analytics.js │ ├── blockchain.js │ ├── dai-system.jsx │ ├── helpers.js │ ├── ledger-subprovider.js │ ├── trezor-subprovider.js │ └── web3.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | 8 | [*.{js,jsx}] 9 | indent_size = 2 10 | end_of_line = lf 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | AWS_ACCESS_KEY_ID= 2 | AWS_SECRET_ACCESS_KEY= 3 | AWS_DEPLOY_S3_BUCKET= 4 | AWS_DEPLOY_CLOUDFRONT_DISTRIBUTION_ID= 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # auto-generated 4 | src/scss/styles.css 5 | src/scss/styles.css.map 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | /build-* 16 | /.publish 17 | 18 | public/css/.sass-cache/ 19 | 20 | # misc 21 | .DS_Store 22 | .env 23 | .env.staging 24 | .env.production 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | package-lock.json 29 | 30 | # editors 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:10.16 2 | 3 | stages: 4 | - prepare 5 | - test 6 | - build 7 | - deploy 8 | 9 | cache: 10 | key: ${CI_COMMIT_REF_SLUG} 11 | paths: 12 | - node_modules/ 13 | - .yarn/ 14 | 15 | prepare: 16 | stage: prepare 17 | script: 18 | - npm install yarn --no-progress --global 19 | - yarn add now --cache-folder .yarn 20 | - yarn install --cache-folder .yarn 21 | only: 22 | - master 23 | - develop 24 | 25 | test: 26 | stage: test 27 | script: 28 | - echo "Running unit tests" 29 | # - npm run test 30 | only: 31 | - master 32 | - develop 33 | 34 | build development: 35 | stage: build 36 | script: 37 | - echo "Building project for development environment at $DEPLOYMENT_URL_DEVELOPMENT" 38 | - PUBLIC_URL=$DEPLOYMENT_URL_DEVELOPMENT yarn build 39 | only: 40 | - develop 41 | artifacts: 42 | name: "artifacts-$CI_JOB_ID-$CI_COMMIT_SHORT_SHA" 43 | paths: 44 | - build 45 | 46 | build: 47 | stage: build 48 | script: 49 | - echo "Building project for staging environment at $DEPLOYMENT_URL_STAGING" 50 | - DEPLOY_ENV=staging PUBLIC_URL=$DEPLOYMENT_URL_STAGING yarn build && mv build build-staging 51 | - echo "Building project for production environment at $DEPLOYMENT_URL_PRODUCTION" 52 | - DEPLOY_ENV=production PUBLIC_URL=$DEPLOYMENT_URL_PRODUCTION GENERATE_SOURCEMAP=false yarn build && mv build build-production 53 | only: 54 | - master 55 | artifacts: 56 | name: "artifacts-$CI_JOB_ID-$CI_COMMIT_SHORT_SHA" 57 | paths: 58 | - build-staging 59 | - build-production 60 | 61 | deploy to development: 62 | stage: deploy 63 | environment: 64 | name: development 65 | url: $DEPLOYMENT_URL_DEVELOPMENT 66 | script: 67 | - echo "Deploying to development at $DEPLOYMENT_URL_DEVELOPMENT" 68 | - node_modules/.bin/now ./build --token $NOW_TOKEN -A ../now.gitlab.json 69 | - node_modules/.bin/now alias --token $NOW_TOKEN $DEPLOYMENT_URL_DEVELOPMENT 70 | only: 71 | - develop 72 | 73 | deploy to staging: 74 | stage: deploy 75 | environment: 76 | name: staging 77 | url: $DEPLOYMENT_URL_STAGING 78 | script: 79 | - echo "Deploying to staging at $DEPLOYMENT_URL_STAGING" 80 | - DEPLOY_ENV=staging node_modules/.bin/gulp deploy-aws 81 | only: 82 | - master 83 | 84 | deploy to production: 85 | stage: deploy 86 | environment: 87 | name: production 88 | url: $DEPLOYMENT_URL_PRODUCTION 89 | script: 90 | - echo "Deploying to production at $DEPLOYMENT_URL_PRODUCTION" 91 | - DEPLOY_ENV=production node_modules/.bin/gulp deploy-aws 92 | when: manual 93 | allow_failure: false 94 | only: 95 | - master 96 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.16 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CDP Portal 2 | 3 | ### Prerequisites 4 | 5 | Have installed: 6 | 7 | - [Git](https://git-scm.com/downloads) 8 | - [Node](https://nodejs.org/en/download/) version 8 or higher 9 | - [Yarn](https://yarnpkg.com/lang/en/docs/install/) 10 | 11 | ### Installation 12 | 13 | 1) `git clone git@github.com:makerdao/scd-cdp-portal.git` 14 | 15 | 2) `cd scd-cdp-portal && yarn` 16 | 17 | ### Run project 18 | 19 | - `yarn start` 20 | - go to http://localhost:3000 (default port) 21 | 22 | For HW support: 23 | - `HTTPS=true yarn start` 24 | - go to https://localhost:3000 (default port) 25 | 26 | ### License 27 | 28 | The CDP Portal is free software: you can redistribute it and/or modify it under 29 | the terms of the GNU Affero General Public License as published by the Free 30 | Software Foundation, either version 3 of the License, or (at your option) any 31 | later version. 32 | 33 | The CDP Portal is distributed in the hope that it will be useful, but WITHOUT 34 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 35 | FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more 36 | details. 37 | 38 | You should have received a copy of the GNU Affero General Public License along 39 | with the CDP Portal. If not, see . 40 | 41 | This project's dependencies include code from caniuse.com. 42 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | webpack: { 5 | alias: { 6 | // Path aliases for fonts and images 7 | fonts: path.resolve(__dirname, './src/assets/fonts'), 8 | images: path.resolve(__dirname, './src/assets/images') 9 | } 10 | }, 11 | babel: { 12 | plugins: [ 13 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 14 | ] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | const fs = require('fs'); 3 | const gulp = require('gulp'); 4 | const s3 = require('gulp-s3-upload')({ useIAM: false }, { maxRetries: 5 }); 5 | const cloudfront = require('gulp-cloudfront-invalidate'); 6 | const log = require('fancy-log'); 7 | 8 | if (process.env.DEPLOY_ENV && !process.env.AWS_ACCESS_KEY_ID) require('dotenv').config({ path: `.env.${process.env.DEPLOY_ENV}` }); 9 | else require('dotenv').config(); 10 | 11 | const s3FilesChanged = []; 12 | 13 | gulp.task('aws-s3-upload', () => { 14 | if (!process.env.DEPLOY_ENV) throw new Error('Missing DEPLOY_ENV in env variables'); 15 | if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) throw new Error('Missing AWS auth credentials in env variables'); 16 | if (!process.env.AWS_DEPLOY_S3_BUCKET) throw new Error('No S3 bucket specified in env variable'); 17 | log(`Uploading to S3 bucket ${process.env.AWS_DEPLOY_S3_BUCKET}...`); 18 | return gulp.src('./build-' + process.env.DEPLOY_ENV + '/**', '!**/.DS_Store').pipe( 19 | s3( 20 | { 21 | Bucket: process.env.AWS_DEPLOY_S3_BUCKET, 22 | ACL: 'public-read', 23 | onChange: file => s3FilesChanged.push(`/${file}`) 24 | }, 25 | { 26 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 27 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY 28 | } 29 | ) 30 | ); 31 | }); 32 | 33 | gulp.task('aws-cloudfront-invalidate', cb => { 34 | if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) throw new Error('Missing AWS auth credentials in env variables'); 35 | if (!process.env.AWS_DEPLOY_CLOUDFRONT_DISTRIBUTION_ID) throw new Error('No CloudFront distribution id specified in env variable'); 36 | if (s3FilesChanged.length === 0) { 37 | log('Skipping CloudFront invalidation because no files have changed.'); 38 | return cb(); 39 | } 40 | log('Invalidating CloudFront cache for: ' + s3FilesChanged.join(', ')); 41 | return gulp.src('*').pipe( 42 | cloudfront({ 43 | distribution: process.env.AWS_DEPLOY_CLOUDFRONT_DISTRIBUTION_ID, 44 | paths: s3FilesChanged, 45 | wait: false, 46 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 47 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY 48 | }) 49 | ); 50 | }); 51 | 52 | gulp.task( 53 | 'deploy-aws', 54 | gulp.series('aws-s3-upload', 'aws-cloudfront-invalidate') 55 | ); 56 | -------------------------------------------------------------------------------- /now.gitlab.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "scd-cdp-portal", 4 | "builds": [ 5 | { 6 | "src": "**/*", "use": "@now/static" 7 | } 8 | ], 9 | "routes": [ 10 | { "src": "^/static/(.*)", "dest": "/static/$1" }, 11 | { "src": "^/favicon.ico", "dest": "/favicon.ico" }, 12 | { "src": "^/manifest.json", "dest": "/manifest.json" }, 13 | { "src": "^/asset-manifest.json", "dest": "/asset-manifest.json" }, 14 | { "src": "^/service-worker.js", "dest": "/service-worker.js" }, 15 | { "src": "^/(.*)", "dest": "/index.html" } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "scd-cdp-portal", 4 | "builds": [ 5 | { 6 | "src": "package.json", 7 | "use": "@now/static-build", 8 | "config": { "distDir": "build" } 9 | } 10 | ], 11 | "routes": [ 12 | { "src": "^/static/(.*)", "dest": "/static/$1" }, 13 | { "src": "^/favicon.ico", "dest": "/favicon.ico" }, 14 | { "src": "^/manifest.json", "dest": "/manifest.json" }, 15 | { "src": "^/asset-manifest.json", "dest": "/asset-manifest.json" }, 16 | { "src": "^/service-worker.js", "dest": "/service-worker.js" }, 17 | { "src": "^/(.*)", "dest": "/index.html" } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scd-cdp-portal", 3 | "license": "AGPL-3.0", 4 | "version": "0.1.0", 5 | "contributors": [ 6 | "Gonzalo Balabasquer ", 7 | "Michael Elliot " 8 | ], 9 | "description": "Collateralized Debt Position Portal for creating and managing Single-Collateral Dai CDPs in the Dai Stablecoin System.", 10 | "private": true, 11 | "homepage": "https://cdp.makerdao.com", 12 | "dependencies": { 13 | "@ledgerhq/hw-app-eth": "^4.7.3", 14 | "@ledgerhq/hw-transport-u2f": "^4.7.3", 15 | "axios": "^0.19.0", 16 | "bignumber.js": "^7.2.1", 17 | "create-react-class": "^15.6.3", 18 | "ethereumjs-tx": "^1.3.4", 19 | "hdkey": "^0.8.0", 20 | "ismobilejs": "^0.5.1", 21 | "jazzicon": "^1.5.0", 22 | "markdown-to-jsx": "^6.10.3", 23 | "mixpanel-browser": "^2.29.0", 24 | "mobx": "^5.13.0", 25 | "mobx-react": "^6.1.3", 26 | "rc-steps": "^3.5.0", 27 | "react": "^16.9.0", 28 | "react-document-title": "^2.0.3", 29 | "react-dom": "^16.9.0", 30 | "react-ga": "^2.6.0", 31 | "react-rangeslider": "^2.2.0", 32 | "react-router-dom": "^5.0.1", 33 | "react-scripts": "3.1.1", 34 | "react-select": "^1.2.1", 35 | "react-slick": "^0.25.2", 36 | "react-tooltip": "^3.10.0", 37 | "trezor-connect": "^7.0.5", 38 | "walletlink": "^1.0.0", 39 | "web3": "^0.20.6", 40 | "web3-provider-engine": "^14.0.4" 41 | }, 42 | "scripts": { 43 | "build-css": "node-sass-chokidar src/scss/ -o src/scss/ --recursive", 44 | "watch-css": "yarn build-css && node-sass-chokidar src/scss/ -o src/scss/ --recursive --watch", 45 | "start-js": "craco start", 46 | "start": "npm-run-all -p watch-css start-js", 47 | "build": "yarn build-css && craco build", 48 | "test": "craco test", 49 | "postinstall": "patch-package" 50 | }, 51 | "eslintConfig": { 52 | "extends": "react-app" 53 | }, 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | }, 66 | "devDependencies": { 67 | "@craco/craco": "^5.3.0", 68 | "fancy-log": "^1.3.3", 69 | "gulp": "^4.0.2", 70 | "gulp-cloudfront-invalidate": "^0.1.5", 71 | "gulp-s3-upload": "^1.7.3", 72 | "node-sass-chokidar": "^1.3.5", 73 | "npm-run-all": "^4.1.5", 74 | "patch-package": "^6.1.4", 75 | "postinstall-postinstall": "^2.0.0" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /patches/web3+0.20.7.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/web3/lib/utils/config.js b/node_modules/web3/lib/utils/config.js 2 | index 5c16262..984e760 100644 3 | --- a/node_modules/web3/lib/utils/config.js 4 | +++ b/node_modules/web3/lib/utils/config.js 5 | @@ -72,7 +72,7 @@ module.exports = { 6 | ETH_SIGNATURE_LENGTH: 4, 7 | ETH_UNITS: ETH_UNITS, 8 | ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, 9 | - ETH_POLLING_TIMEOUT: 1000/2, 10 | + ETH_POLLING_TIMEOUT: 10000, 11 | defaultBlock: 'latest', 12 | defaultAccount: undefined 13 | }; 14 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CDP Portal 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "CDP Portal", 3 | "name": "CDP Portal", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "./android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "./android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": "./index.html", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#202930" 25 | } 26 | -------------------------------------------------------------------------------- /public/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/apple-touch-icon.png -------------------------------------------------------------------------------- /public/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/static/dai-400px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/dai-400px.png -------------------------------------------------------------------------------- /public/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/favicon-16x16.png -------------------------------------------------------------------------------- /public/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/favicon-32x32.png -------------------------------------------------------------------------------- /public/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/mstile-150x150.png -------------------------------------------------------------------------------- /public/static/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/public/static/og-image.png -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/abi/proxyregistry.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [{"constant":false,"inputs":[],"name":"build","outputs":[{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"proxies","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"build","outputs":[{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"factory_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}], 3 | "bytecode": "" 4 | } -------------------------------------------------------------------------------- /src/abi/saiProxyCreateAndExecute.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"wad","type":"uint256"}],"name":"draw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"jam","type":"uint256"},{"name":"wad","type":"uint256"},{"name":"otc_","type":"address"}],"name":"wipeAndFree","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"wad","type":"uint256"}],"name":"lockAndDraw","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"wad","type":"uint256"}],"name":"lockAndDraw","outputs":[{"name":"cup","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"registry_","type":"address"},{"name":"tub_","type":"address"}],"name":"createAndOpen","outputs":[{"name":"proxy","type":"address"},{"name":"cup","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"otc_","type":"address"}],"name":"shut","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"wad","type":"uint256"},{"name":"otc_","type":"address"}],"name":"wipe","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"wad","type":"uint256"}],"name":"wipe","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"}],"name":"open","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"}],"name":"shut","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"}],"name":"lock","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"registry_","type":"address"},{"name":"tub_","type":"address"},{"name":"wad","type":"uint256"}],"name":"createOpenLockAndDraw","outputs":[{"name":"proxy","type":"address"},{"name":"cup","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"lad","type":"address"}],"name":"give","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"registry_","type":"address"},{"name":"tub_","type":"address"}],"name":"createOpenAndLock","outputs":[{"name":"proxy","type":"address"},{"name":"cup","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"jam","type":"uint256"}],"name":"free","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tub_","type":"address"},{"name":"cup","type":"bytes32"},{"name":"jam","type":"uint256"},{"name":"wad","type":"uint256"}],"name":"wipeAndFree","outputs":[],"payable":true,"stateMutability":"payable","type":"function"}], 3 | "bytecode": "" 4 | } -------------------------------------------------------------------------------- /src/abi/saivaluesaggregator.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [{"constant":true,"inputs":[],"name":"sin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"skr","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"gov","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tub","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"cup","type":"bytes32"}],"name":"aggregateCDPValues","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"lad","type":"address"},{"name":"safe","type":"bool"},{"name":"r","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vox","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"gem","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"sai","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"},{"name":"proxy","type":"address"}],"name":"aggregateValues","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"pipVal","type":"bytes32"},{"name":"pipSet","type":"bool"},{"name":"pepVal","type":"bytes32"},{"name":"pepSet","type":"bool"},{"name":"sStatus","type":"bool[]"},{"name":"sValues","type":"uint256[]"},{"name":"tValues","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proxyRegistry","type":"address"},{"name":"addr","type":"address"}],"name":"getContractsAddrs","outputs":[{"name":"blockNumber","type":"uint256"},{"name":"saiContracts","type":"address[]"},{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pep","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pip","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pit","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tap","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"top","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_top","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}], 3 | "bytecode": "" 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/fonts/slick.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/fonts/slick.eot -------------------------------------------------------------------------------- /src/assets/fonts/slick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/fonts/slick.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/fonts/slick.ttf -------------------------------------------------------------------------------- /src/assets/fonts/slick.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/fonts/slick.woff -------------------------------------------------------------------------------- /src/assets/images/cdp-created-icon-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/cdp-created-icon-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/assets/images/cdp-created.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/cdp-creating-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/cdp-creating-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/cdp-creating-3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/cdp-creating-6.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/history-icon-borrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Icon Borrow 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/assets/images/history-icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Icon Unknown 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/images/icon-arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/icon-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/icon-close-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/icon-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/icon-help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/images/icon-home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/images/icon-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/images/icon-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/images/icon-warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/ledger-nano-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/ledger-nano-logo.png -------------------------------------------------------------------------------- /src/assets/images/maker-logo-footer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/menu-icon-ledger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/menu-icon-trezor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/menu-icon-web.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | menu-icon-web 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/parity-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/parity-logo.png -------------------------------------------------------------------------------- /src/assets/images/send-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/send-icon.png -------------------------------------------------------------------------------- /src/assets/images/slick-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/slick-loader.gif -------------------------------------------------------------------------------- /src/assets/images/trezor-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/trezor-logo.png -------------------------------------------------------------------------------- /src/assets/images/wallet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makerdao/scd-cdp-portal/3a54112a0d5ea5a96680182dbabf088792b84176/src/assets/images/wallet-icon.png -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {observer, Provider} from "mobx-react"; 4 | import {BrowserRouter} from "react-router-dom"; 5 | import ReactGA from 'react-ga'; 6 | 7 | // Components 8 | import Modal from "./Modal"; 9 | import Notify from "./Notify"; 10 | import NotifySetUp from "./NotifySetUp"; 11 | import PriceModal from "./PriceModal"; 12 | import Routes from "./Routes"; 13 | 14 | // Stores 15 | import rootStore from "../stores/Root"; 16 | 17 | // Utils 18 | import * as blockchain from "../utils/blockchain"; 19 | import { gaInit, mixpanelInit } from '../utils/analytics'; 20 | 21 | // Styles 22 | import "../scss/styles.css"; 23 | 24 | // Convenient console access 25 | window.blockchain = blockchain; 26 | window.dialog = rootStore.dialog; 27 | window.network = rootStore.network; 28 | window.profile = rootStore.profile; 29 | window.system = rootStore.system; 30 | window.transactions = rootStore.transactions; 31 | window.content = rootStore.content; 32 | 33 | // Analytics 34 | mixpanelInit(); 35 | gaInit(); 36 | ReactGA.pageview(window.location.pathname + window.location.search); 37 | 38 | @observer 39 | class App extends React.Component { 40 | render() { 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | } 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /src/components/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import checkIsMobile from "ismobilejs"; 5 | 6 | // Components 7 | import Cup from "./Cup"; 8 | import LegacyCupsAlert from "./LegacyCupsAlert"; 9 | import CupMobile from "./CupMobile"; 10 | 11 | @inject("system") 12 | @observer 13 | class Dashboard extends React.Component { 14 | render() { 15 | const cupId = this.props.system.tub.cupId ? this.props.system.tub.cupId : Object.keys(this.props.system.tub.cups)[0]; 16 | return ( 17 |
18 | 19 | { 20 | checkIsMobile.any 21 | ? 22 | : 23 | } 24 |
25 | ) 26 | } 27 | } 28 | 29 | export default Dashboard; 30 | -------------------------------------------------------------------------------- /src/components/DropdownMenu.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | 4 | export function MenuItems(props) { 5 | return ( 6 |
7 | { props.children } 8 |
9 | ); 10 | } 11 | 12 | export function MenuItem(props) { 13 | return ( 14 | 15 | { 16 | props.iconsvg ? props.iconsvg : props.icon && > 17 | } 18 | { props.text } 19 | 20 | ); 21 | } 22 | 23 | export function MenuFooter(props) { 24 | return ( 25 |
26 | { props.children } 27 |
28 | ); 29 | } 30 | 31 | export class DropdownMenu extends React.Component { 32 | render() { 33 | return ( 34 |
35 | ˅ 36 | 37 |
38 | { this.props.children } 39 |
40 |
41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {Link} from "react-router-dom"; 4 | 5 | // Images 6 | import makerLogoFooter from "images/maker-logo-footer.svg"; 7 | 8 | class Footer extends React.Component { 9 | render() { 10 | return ( 11 | 12 |
13 |
14 |
15 |
MakerMaker
16 |
17 |

18 | The Sai Credit System was developed by Maker.
19 | Our team consists of developers, economists and designers from all over the world. Our decentralized autonomous organization is governed by our token holders. 20 |

21 | 26 |
27 | 28 | ) 29 | } 30 | } 31 | 32 | export default Footer; 33 | -------------------------------------------------------------------------------- /src/components/GeneralNotifications.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | 5 | // Components 6 | import InlineNotification from './InlineNotification'; 7 | import StabilityFeeAlert from './StabilityFeeAlert'; 8 | 9 | @inject('content') 10 | @inject('network') 11 | @observer 12 | class GeneralNotifications extends React.Component { 13 | render() { 14 | return ( 15 | (this.props.network.isConnected || this.props.network.defaultAccount) && 16 | (this.props.content.shouldShowGeneralNotifications() || this.props.content.shouldShowStabilityFeeAlert()) && ( 17 |
18 | {this.props.content.shouldShowGeneralNotifications() && 19 | Object.entries(this.props.content.getGeneralNotifications()).map( 20 | ([key, notification]) => 21 | notification.show && ( 22 | this.props.content.hideGeneralNotification(key)} 26 | /> 27 | ) 28 | )} 29 | 30 |
31 | ) 32 | ); 33 | } 34 | } 35 | 36 | export default GeneralNotifications; 37 | -------------------------------------------------------------------------------- /src/components/Help.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {Link} from "react-router-dom"; 4 | import DocumentTitle from "react-document-title"; 5 | import checkIsMobile from "ismobilejs"; 6 | 7 | // Components 8 | import Menu from "./Menu"; 9 | 10 | class Help extends React.Component { 11 | columnFormatting = () => { 12 | return checkIsMobile.any 13 | ? "col col-extra-padding" 14 | : "col col-2 col-extra-padding"; 15 | } 16 | 17 | columnStyle = () => { 18 | return checkIsMobile.any 19 | ? { marginTop: "-120px" } 20 | : { paddingLeft: "2.5em" } 21 | } 22 | 23 | render() { 24 | return ( 25 | 26 |
27 |
28 | { 29 | 30 | } 31 |
32 |
33 |
34 |

Help

35 |
36 |
37 |
38 |

FAQ

39 |

First steps

40 |
    41 |
  • What is a Collateralized Debt Position (CDP)?
  • 42 |
  • What is the best way to lower my risk of liquidation?
  • 43 |
  • What is the Stability Fee?
  • 44 |
  • When do I have to pay the Stability Fee?
  • 45 |
  • What is PETH?
  • 46 |
  • How do I get MKR tokens?
  • 47 |
48 |
49 |
50 |

 

51 |

Most asked questions

52 |
    53 |
  • How does the avatar work?
  • 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ) 63 | } 64 | } 65 | 66 | export default Help; 67 | -------------------------------------------------------------------------------- /src/components/HelpItem.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import {Link} from "react-router-dom"; 5 | import Markdown from "markdown-to-jsx"; 6 | import DocumentTitle from "react-document-title"; 7 | 8 | // Components 9 | import Menu from "./Menu"; 10 | import NotFound from "./NotFound"; 11 | 12 | @inject("content") 13 | @observer 14 | class HelpItem extends React.Component { 15 | render() { 16 | const helpId = this.props.match.params.helpId || null; 17 | const helpItem = this.props.content.getHelpItem(helpId); 18 | if (!helpId || (this.props.content.contentLoaded && !helpItem)) return ; 19 | 20 | return ( 21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 |

Help

29 |
30 |
31 |
32 |
FAQ{ helpItem ? helpItem.title : "Loading..." }
33 |
34 | { 35 | this.props.content.contentLoaded && 36 | 37 | } 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | ) 47 | } 48 | } 49 | 50 | export default HelpItem; 51 | -------------------------------------------------------------------------------- /src/components/InlineNotification.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | 4 | const icon = ( 5 | 6 | 7 | 8 | ); 9 | 10 | class InlineNotification extends React.Component { 11 | render() { 12 | return ( 13 |
14 | { 15 | //this.props.onCloseButtonClick && 16 | // { e.preventDefault(); this.props.onCloseButtonClick() } } className="close-button" width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> 17 | // 18 | // 19 | } 20 | { this.props.caption && icon } 21 | { 22 | this.props.caption && 23 |
24 | { this.props.caption } 25 |
26 | } 27 | { !this.props.caption && icon } 28 |
29 | { this.props.message || this.props.children } 30 | { 31 | this.props.onButtonClick && 32 | } 33 |
34 |
35 |
36 | ) 37 | } 38 | } 39 | 40 | export default InlineNotification; 41 | -------------------------------------------------------------------------------- /src/components/LegacyCupsAlert.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Components 6 | import InlineNotification from "./InlineNotification"; 7 | 8 | @inject("network") 9 | @inject("system") 10 | @observer 11 | class LegacyCupsAlert extends React.Component { 12 | constructor(props){ 13 | super(props); 14 | this.state = { show: true } 15 | } 16 | 17 | render() { 18 | return ( 19 | this.props.system.showLegacyAlert && this.state.show && !localStorage.getItem(`LegacyCDPsAlertClosed-${this.props.network.defaultAccount}`) && 20 | { localStorage.setItem(`LegacyCDPsAlertClosed-${this.props.network.defaultAccount}`, true); this.setState({show: false}); } } 26 | onButtonClick={ () => this.props.setOpenMigrate(true) } 27 | /> 28 | ) 29 | } 30 | } 31 | 32 | export default LegacyCupsAlert; 33 | -------------------------------------------------------------------------------- /src/components/LoadingSpinner.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | 4 | class LoadingSpinner extends React.Component { 5 | render() { 6 | return ( 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 | ) 15 | } 16 | } 17 | 18 | export default LoadingSpinner; 19 | -------------------------------------------------------------------------------- /src/components/McdAlert.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import { observer } from "mobx-react"; 4 | 5 | // Components 6 | import InlineNotification from "./InlineNotification"; 7 | 8 | @observer 9 | class McdAlert extends React.Component { 10 | constructor(props){ 11 | super(props); 12 | this.state = { show: true } 13 | } 14 | 15 | render() { 16 | return ( 17 | this.state.show && 18 | { localStorage.setItem('ScdAlertClosed', true); this.setState({show: false}); } } 23 | onButtonClick={ () => window.open("https://migrate.makerdao.com", "_blank") } 24 | > 25 | Single Collateral Dai (SCD) was shutdown at 16:00 UTC on Tuesday, May 12, 2020. From now on, 26 | it will only be possible to redeem Sai and CDPs from the official MakerDAO Migration Portal 27 | at migrate.makerdao.com. 28 | 29 | ) 30 | } 31 | } 32 | 33 | export default McdAlert; 34 | -------------------------------------------------------------------------------- /src/components/Modal.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {observer} from "mobx-react"; 4 | 5 | @observer 6 | class Modal extends React.Component { 7 | constructor() { 8 | super(); 9 | this.handleKeyDown = this.handleKeyDown.bind(this); 10 | } 11 | componentWillMount() { 12 | window.addEventListener("keydown", this.handleKeyDown.bind(this)); 13 | } 14 | componentWillUnmount() { 15 | window.removeEventListener("keydown", this.handleKeyDown.bind(this)); 16 | } 17 | handleKeyDown(event) { 18 | if (event.keyCode === 27) this.props.close(); 19 | } 20 | handleInnerClick(e) { 21 | e.stopPropagation(); 22 | } 23 | render() { 24 | return ( 25 | 26 | { 27 | this.props.show && 28 |
29 |
30 | { this.props.children } 31 |
32 |
33 | } 34 |
35 | ) 36 | } 37 | } 38 | 39 | export default Modal; 40 | -------------------------------------------------------------------------------- /src/components/NewCupMobile.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React, {Component} from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import mixpanel from 'mixpanel-browser'; 5 | 6 | // Components 7 | import InlineNotification from "./InlineNotification"; 8 | 9 | //Utils 10 | import {printNumber, formatAmount} from "../utils/helpers"; 11 | import CupInfoMobile from "./CupInfoMobile"; 12 | 13 | @inject("system") 14 | @observer 15 | class NewCupMobile extends Component { 16 | usdValue = eth => { 17 | const ethPrice = this.props.system.pip.val / 1000000000000000000; 18 | return eth * ethPrice; 19 | } 20 | 21 | render() { 22 | const { 23 | checkValues, 24 | daiText, 25 | error, 26 | dai, 27 | eth, 28 | ethText, 29 | liqPrice, 30 | maxDaiAvail, 31 | ratio, 32 | submitEnabled, 33 | warning 34 | } = this.props.newCupProps; 35 | 36 | return ( 37 |
38 |
39 |
40 | 41 |
42 | this.eth = input } type="number" id="inputETH" className="number-input" required step="0.000000000000000001" placeholder="0.000" value={ ethText } onChange={ e => { checkValues("eth", e.target.value) } } onKeyDown={ e => { if (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 189) e.preventDefault() } } style={{marginBottom: "5px"}} /> 43 | ETH 44 |
45 | Worth ${ printNumber(this.usdValue(eth)) } 46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 | this.dai = input } type="number" id="inputDAI" className="number-input" required step="0.000000000000000001" placeholder="0.000" value={ daiText } style={{marginBottom: "5px"}} onChange={ e => { checkValues("dai", e.target.value) } } onKeyDown={ e => { if (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 189) e.preventDefault() } } /> 56 | DAI 57 | { 58 | maxDaiAvail && 59 |

Max SAI available to generate: { printNumber(maxDaiAvail) } SAI

60 | } 61 |
62 |
63 |
64 | 65 | 66 | 67 |
68 | { warning &&
} 69 | { error &&
} 70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 |
78 | ); 79 | } 80 | } 81 | 82 | export default NewCupMobile; 83 | -------------------------------------------------------------------------------- /src/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | 4 | // Images 5 | import image404 from "images/404.svg"; 6 | 7 | class NotFound extends React.Component { 8 | render() { 9 | return ( 10 | 11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

Page not found – Error 404

22 |

You spotted a black hole.

23 |

Don’t go too close – it sucks up
pages and contents.

24 |
25 | You spotted a black hole 26 |
27 |
28 |
29 |
30 |

We are confident you will find what you were
looking for on the main page.

31 | 32 |
33 |
34 |
35 | ) 36 | } 37 | } 38 | 39 | export default NotFound; 40 | -------------------------------------------------------------------------------- /src/components/Notify.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | class Item extends React.Component { 6 | displayName = "Item"; 7 | 8 | hideNotification = () => { 9 | this.props.hideNotification(this.props.id); 10 | } 11 | 12 | render() { 13 | return ( 14 | React.createElement("div", { className: "col " + this.props.classNames.join(" ") }, 15 | React.createElement("button", { className: "close-box" , onClick: this.hideNotification}), 16 | React.createElement("h3", { className: "notification-headline" }, this.props.title), 17 | React.createElement("div", { className: "" }, this.props.msg) 18 | ) 19 | ) 20 | } 21 | }; 22 | 23 | @inject("network") 24 | @inject("transactions") 25 | @observer 26 | class Notify extends React.Component { 27 | displayName = "Notify"; 28 | key = 0; 29 | 30 | constructor() { 31 | super(); 32 | this.state = {} 33 | } 34 | 35 | componentDidMount = () => { 36 | this.props.transactions.notificator = this.props.network.notificator = this; 37 | } 38 | 39 | getInitialState = () => { 40 | return {}; 41 | } 42 | 43 | success = (key, title, msg, time, onClose = () => null) => { 44 | this.addNotify(key, title, msg, time, ["nf-success"], onClose); 45 | } 46 | 47 | error = (key, title, msg, time, onClose = () => null) => { 48 | this.addNotify(key, title, msg, time, ["nf-error"], onClose); 49 | } 50 | 51 | info = (key, title, msg, time, onClose = () => null) => { 52 | this.addNotify(key, title, msg, time, ["nf-info"], onClose); 53 | } 54 | 55 | notice = (key, title, msg, time, onClose = () => null) => { 56 | this.addNotify(key, title, msg, time, ["nf-notice", "bright-style"], onClose); 57 | } 58 | 59 | addNotify = (key, title, msg, time, classNames, onClose = () => null) => { 60 | const state = {...this.state} 61 | state[key] = { title, msg, time, classNames, onClose }; 62 | this.setState(state); 63 | this.countToHide(time, key); 64 | } 65 | 66 | countToHide = (duration, key) => { 67 | if (duration) { 68 | var that = this; 69 | setTimeout(function () { 70 | that.hideNotification(key); 71 | }, duration); 72 | } 73 | } 74 | 75 | hideNotification = key => { 76 | if (this.state[key]) { 77 | this.state[key].onClose(); 78 | } 79 | delete this.state[key]; 80 | this.setState(this.state); 81 | } 82 | 83 | render = () => { 84 | var keys = Object.keys(this.state); 85 | var state = this.state; 86 | var hide = this.hideNotification; 87 | var el = keys.map(function (key) { 88 | return React.createElement(Item, { 89 | id: key, 90 | key: key, 91 | classNames: state[key].classNames, 92 | hideNotification: hide, 93 | title: state[key].title, 94 | msg: state[key].msg 95 | } 96 | ) 97 | }); 98 | return (React.createElement("div", { className: "notifications-container" }, el)); 99 | } 100 | }; 101 | 102 | export default Notify; 103 | -------------------------------------------------------------------------------- /src/components/NotifySetUp.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Utils 6 | import {etherscanTx} from "../utils/helpers"; 7 | 8 | // Images 9 | import cdpCreated from "images/cdp-created.svg"; 10 | import cdpCreatedIcon1 from "images/cdp-created-icon-1.svg"; 11 | import cdpCreatedIcon2 from "images/cdp-created-icon-2.svg"; 12 | import cdpCreatedIcon3 from "images/cdp-created-icon-3.svg"; 13 | 14 | import cdpCreating1 from "images/cdp-creating-1.svg"; 15 | import cdpCreating2 from "images/cdp-creating-2.svg"; 16 | import cdpCreating3 from "images/cdp-creating-3.svg"; 17 | import cdpCreating4 from "images/cdp-creating-4.svg"; 18 | import cdpCreating5 from "images/cdp-creating-5.svg"; 19 | import cdpCreating6 from "images/cdp-creating-6.svg"; 20 | 21 | const cdpCreatingAnimation = [cdpCreating1, cdpCreating2, cdpCreating3, cdpCreating4, cdpCreating5, cdpCreating6]; 22 | 23 | class CreatingCDPAnimation extends React.Component { 24 | constructor(props){ 25 | super(props); 26 | this.state = { currentCount: 1 } 27 | } 28 | timer() { 29 | this.setState({ 30 | currentCount: this.state.currentCount === 6 ? 1 : this.state.currentCount + 1 31 | }) 32 | } 33 | componentDidMount() { 34 | this.intervalId = setInterval(this.timer.bind(this), 2000); 35 | } 36 | componentWillUnmount() { 37 | clearInterval(this.intervalId); 38 | } 39 | render() { 40 | return( 41 | 42 | Creating CDP 43 | 44 | ); 45 | } 46 | } 47 | 48 | @inject("transactions") 49 | @inject("system") 50 | @inject("network") 51 | @observer 52 | class NotifySetUp extends React.Component { 53 | render() { 54 | const txs = Object.keys(this.props.transactions.registry).filter(tx => this.props.transactions.registry[tx].cdpCreationTx); 55 | const txHash = txs[0] || null; 56 | 57 | return ( 58 | this.props.transactions.showCreatingCdpModal && 59 |
60 |
61 | { 62 | this.props.transactions.registry[txs[0]].pending || Object.keys(this.props.system.tub.cups).length === 0 63 | ? 64 | 65 |

Creating your CDP

66 | 67 |

68 | { 69 | txHash 70 | ? 71 | etherscanTx(this.props.network.network, "View transaction", txHash) 72 | : 73 | "Creating your new CDP..." 74 | } 75 |

76 |
77 | : 78 | 79 |

Congratulations on your new CDP

80 | CDP created 81 |

82 | Welcome to the CDP Portal where you can view and manage
your collateral and debt position on a decentralized system. 83 |

84 |
    85 |
  • 86 |
    *
    87 | Check current collateral
    to debt position 88 |
  • 89 |
  • 90 |
    *
    91 | Deposit or withdraw
    collateral 92 |
  • 93 |
  • 94 |
    *
    95 | Generate or pay
    back SAI 96 |
  • 97 |
98 |
99 | 100 |
101 |
102 | } 103 |
104 |
105 | ) 106 | } 107 | } 108 | 109 | export default NotifySetUp; 110 | -------------------------------------------------------------------------------- /src/components/OasisAlert.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import { observer } from "mobx-react"; 4 | 5 | // Components 6 | import InlineNotification from "./InlineNotification"; 7 | 8 | @observer 9 | class OasisAlert extends React.Component { 10 | constructor(props){ 11 | super(props); 12 | this.state = { show: true } 13 | } 14 | 15 | render() { 16 | return ( 17 | this.state.show && 18 | { localStorage.setItem('OasisAlertClosed', true); this.setState({show: false}); } } 23 | onButtonClick={ () => window.open("https://oasis.app/borrow", "_blank") } 24 | > 25 | New Multi Collateral Dai (MCD) Vaults can be created from the Oasis App 26 | at oasis.app. For more information please visit 27 | our Forum at forum.makerdao.com or 28 | our Chat at chat.makerdao.com. 29 | 30 | ) 31 | } 32 | } 33 | 34 | export default OasisAlert; 35 | -------------------------------------------------------------------------------- /src/components/PriceModal.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import Slider from "react-rangeslider"; 5 | 6 | import "react-rangeslider/lib/index.css"; 7 | 8 | @inject("transactions") 9 | @observer 10 | class PriceModal extends React.Component { 11 | constructor (props, context) { 12 | super(props, context); 13 | this.state = { 14 | gasPrice: props.transactions.standardGasPrice, 15 | timeEstimate: this.estimateTxTime(props.transactions.standardGasPrice) 16 | }; 17 | this.stdGasPrice = props.transactions.standardGasPrice; 18 | } 19 | 20 | setPriceAndSend = e => { 21 | e.preventDefault(); 22 | this.props.transactions.setPriceAndSend(this.state.gasPrice); 23 | }; 24 | 25 | // TODO: Use an external source to estimate tx time 26 | estimateTxTime = gasPrice => { 27 | return Math.round((99 - gasPrice) / 2) + " mins"; 28 | }; 29 | 30 | handleChange = value => { 31 | this.setState({ 32 | gasPrice: value, 33 | timeEstimate: this.estimateTxTime(value) 34 | }); 35 | }; 36 | 37 | render() { 38 | const { gasPrice } = this.state; 39 | return ( 40 | 41 |

Set your gas price

42 |

Gas is used to pay for transactions. A higher gas price results in faster confirmation times.

43 |

{ gasPrice } Gwei { gasPrice === this.stdGasPrice ? " (Standard)" : "" }

44 | {/*

~{ this.state.timeEstimate }

*/} 45 |
46 | 47 |
48 | 57 |
Slow
Fast
58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 | ) 66 | } 67 | } 68 | 69 | export default PriceModal; 70 | -------------------------------------------------------------------------------- /src/components/Routes.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {observer} from "mobx-react"; 4 | import {Route, Switch, withRouter} from "react-router-dom"; 5 | import ReactGA from 'react-ga'; 6 | import mixpanel from 'mixpanel-browser'; 7 | 8 | // Components 9 | import Help from "./Help"; 10 | import HelpItem from "./HelpItem"; 11 | import Home from "./Home"; 12 | import NotFound from "./NotFound"; 13 | import Terms from "./Terms"; 14 | 15 | @withRouter 16 | @observer 17 | class Routes extends React.Component { 18 | componentDidUpdate = prevProps => { 19 | if (this.props.location.pathname !== prevProps.location.pathname) { 20 | window.scrollTo(0, 0); 21 | } 22 | console.debug(`[Analytics] Tracked: ${this.props.location.pathname}`); 23 | ReactGA.pageview(this.props.location.pathname); 24 | mixpanel.track('Pageview', { product: 'scd-cdp-portal' }); 25 | } 26 | 27 | render() { 28 | return ( 29 | 30 | 31 | 32 | } /> 33 | 34 | 35 | 36 | ) 37 | } 38 | } 39 | 40 | export default Routes; 41 | -------------------------------------------------------------------------------- /src/components/StabilityFeeAlert.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Components 6 | import InlineNotification from "./InlineNotification"; 7 | 8 | @inject("content") 9 | @inject("network") 10 | @observer 11 | class StabilityFeeAlert extends React.Component { 12 | render() { 13 | return ( 14 | this.props.content.shouldShowStabilityFeeAlert() && 15 | (this.props.network.isConnected || 16 | this.props.network.defaultAccount) && 17 | this.props.content.hideStabilityFeeContent()} 20 | /> 21 | ) 22 | } 23 | } 24 | 25 | export default StabilityFeeAlert; 26 | -------------------------------------------------------------------------------- /src/components/SystemInfo.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Utils 6 | import {printNumber, wdiv, wmul, capitalize} from "../utils/helpers"; 7 | 8 | @inject("network") 9 | @inject("system") 10 | @observer 11 | class SystemInfo extends React.Component { 12 | render() { 13 | return ( 14 | 15 |
16 |

{ this.props.network.network === "main" ? "Main Ethereum" : capitalize(this.props.network.network) + " Test" } Network

17 |
18 |
19 |
20 |

Price Info

21 |

ETH/USD

22 |
23 | { 24 | this.props.system.pip.val && this.props.system.pip.val.gt(0) 25 | ? 26 | { printNumber(this.props.system.pip.val) }USD 27 | : 28 | Loading... 29 | } 30 |
31 |

PETH/ETH

32 |
33 | { 34 | this.props.system.tub.per.gte(0) 35 | ? 36 | { printNumber(this.props.system.tub.per) }ETH 37 | : 38 | Loading... 39 | } 40 |
41 |

SAI/USD

42 |
43 | { 44 | this.props.system.vox.par.gte(0) 45 | ? 46 | { printNumber(this.props.system.vox.par) }USD 47 | : 48 | Loading... 49 | } 50 |
51 |

MKR/USD

52 |
53 | { 54 | this.props.system.pep.val && this.props.system.pep.val.gt(0) 55 | ? 56 | { printNumber(this.props.system.pep.val) }USD 57 | : 58 | Loading... 59 | } 60 |
61 |
62 |
63 |
64 |

Global CDP Info

65 |

Global CDP Collateralization

66 |
67 | { 68 | this.props.system.gem.tubBalance.gte(0) && this.props.system.pip.val.gte(0) && this.props.system.dai.totalSupply.gte(0) && this.props.system.vox.par.gte(0) 69 | ? 70 | 71 | { 72 | printNumber( 73 | this.props.system.dai.totalSupply.eq(0) 74 | ? 0 75 | : wdiv(wmul(this.props.system.gem.tubBalance, this.props.system.pip.val), wmul(this.props.system.dai.totalSupply, this.props.system.vox.par)).times(100) 76 | ) 77 | } 78 | % 79 | 80 | : 81 | "Loading..." 82 | } 83 |
84 |

Maximum Global SAI Available

85 |
86 | { 87 | this.props.system.dai.totalSupply && this.props.system.dai.totalSupply.gt(0) 88 | ? 89 | { printNumber(this.props.system.dai.totalSupply) }$ 90 | : 91 | Loading... 92 | } 93 |
94 | { 95 | this.props.network.network === "main" && 96 |

See all CDPs

97 | } 98 |
99 |
100 |
101 |
102 | ) 103 | } 104 | } 105 | 106 | export default SystemInfo; 107 | -------------------------------------------------------------------------------- /src/components/Terms.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import Markdown from "markdown-to-jsx"; 5 | import DocumentTitle from "react-document-title"; 6 | 7 | // Components 8 | import Menu from "./Menu"; 9 | 10 | @inject("content") 11 | @observer 12 | class Terms extends React.Component { 13 | render() { 14 | return ( 15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 |
25 | ) 26 | } 27 | } 28 | 29 | export default Terms; 30 | -------------------------------------------------------------------------------- /src/components/ToggleSwitch.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import PropTypes from "prop-types"; 3 | import React from "react"; 4 | 5 | const tickIcon = 6 | 7 | const lockIcon = 8 | 9 | function ToggleSwitch({on, pending, onClick, onDisabledClick, enabled, className, children}) { 10 | const classes = ["switch", className, (on ? "on " : ""), (enabled ? "" : "disabled ")].join(" "); 11 | return ( 12 |
enabled ? onClick(e) : onDisabledClick(e)}> 13 |
14 | { 15 | on ? 16 | pending ?
: tickIcon 17 | : 18 | pending ?
: lockIcon 19 | } 20 |
21 |
22 | ); 23 | } 24 | 25 | ToggleSwitch.propTypes = { 26 | on: PropTypes.bool.isRequired, 27 | onClick: PropTypes.func.isRequired, 28 | enabled: PropTypes.bool, 29 | className: PropTypes.string 30 | }; 31 | 32 | ToggleSwitch.defaultProps = { 33 | enabled: true, 34 | className: "", 35 | onDisabledClick: () => {} 36 | }; 37 | 38 | export default ToggleSwitch; 39 | -------------------------------------------------------------------------------- /src/components/TooltipHint.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import ReactTooltip from "react-tooltip"; 4 | import {inject, observer} from "mobx-react"; 5 | 6 | @inject("content") 7 | @observer 8 | class TooltipHint extends React.Component { 9 | static rebuildTooltips() { 10 | ReactTooltip.rebuild(); 11 | } 12 | render() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | } 22 | 23 | export default TooltipHint; 24 | -------------------------------------------------------------------------------- /src/components/WalletClientDownload.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | import walletIcons from './WalletIcons'; 5 | 6 | 7 | @inject("network") 8 | @observer 9 | class WalletClientDownload extends React.Component { 10 | render() { 11 | return ( 12 |
13 |
14 |
{ this.props.network.downloadClient = false } }> 15 | 16 | 17 | 18 |
19 |

Get a Wallet

20 |
21 |
22 |
Select a web wallet to see more information
23 | 24 |
{ walletIcons["metamask"] }
25 | MetaMask 26 |
27 | 28 |
{ walletIcons["parity"] }
29 | Parity 30 |
31 | 32 |
{ walletIcons["mist"] }
33 | Mist 34 |
35 |
36 |
37 | ) 38 | } 39 | } 40 | 41 | export default WalletClientDownload; 42 | -------------------------------------------------------------------------------- /src/components/WalletClientSelector.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Utils 6 | import {getWebClientProviderName} from "../utils/blockchain"; 7 | import walletIcons from "./WalletIcons"; 8 | 9 | @inject("network") 10 | @observer 11 | class WalletClientSelector extends React.Component { 12 | render() { 13 | const providerName = getWebClientProviderName(); 14 | return ( 15 |
16 |
17 |

Connect a Wallet

18 |
19 |
20 |
Get started by connecting one of the wallets below
21 | { e.preventDefault(); this.props.network.setWeb3WebClient() } } className="web-wallet"> 22 | { 23 | providerName ? 24 | 25 |
{ walletIcons.hasOwnProperty(providerName) ? walletIcons[providerName] : walletIcons["web"] }
26 | { this.props.formatClientName(providerName) } 27 |
28 | : 29 | 30 |
{ walletIcons["web"] }
31 | {this.props.network.isMobile ? "Mobile" : "Web"} Wallet 32 |
33 | } 34 |
35 | { 36 | navigator.userAgent.toLowerCase().indexOf("firefox") === -1 && 37 | { e.preventDefault(); this.props.network.showHW("ledger") } }> 38 |
{ walletIcons["ledger"] }
39 | Ledger Nano S 40 |
41 | } 42 | { e.preventDefault(); this.props.network.showHW("trezor") } }> 43 |
{ walletIcons["trezor"] }
44 | Trezor 45 |
46 | { e.preventDefault(); this.props.network.startWalletLink() } }> 47 |
{ walletIcons["walletlink"] }
48 | Coinbase Wallet 49 |
50 |
51 |
52 | ) 53 | } 54 | } 55 | 56 | export default WalletClientSelector; 57 | -------------------------------------------------------------------------------- /src/components/WalletMobileClientDownload.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from 'react'; 3 | import { inject, observer } from 'mobx-react'; 4 | import walletIcons from './WalletIcons' 5 | 6 | @inject('network') 7 | @observer 8 | class WalletMobileClientDownload extends React.Component { 9 | render() { 10 | return ( 11 |
12 |
13 |
{ 16 | this.props.network.downloadClient = false; 17 | }} 18 | > 19 | 25 | 30 | 31 |
32 |

Get a Mobile Wallet

33 |
34 |
35 |
36 | Select a mobile wallet to see more information 37 |
38 | 44 |
{walletIcons['coinbase']}
45 | Coinbase 46 |
47 | 53 |
{walletIcons['trust']}
54 | Trust 55 |
56 | 62 |
{walletIcons['imtoken']}
63 | ImToken 64 |
65 |
66 |
67 | ); 68 | } 69 | } 70 | 71 | export default WalletMobileClientDownload; 72 | -------------------------------------------------------------------------------- /src/components/WalletMobileConnect.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from 'react'; 3 | import { inject } from 'mobx-react'; 4 | 5 | @inject('network') 6 | class WalletMobileConnect extends React.Component { 7 | render() { 8 | return ( 9 |
10 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default WalletMobileConnect; 26 | -------------------------------------------------------------------------------- /src/components/WalletNoAccount.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject} from "mobx-react"; 4 | 5 | // Components 6 | import LoadingSpinner from "./LoadingSpinner"; 7 | 8 | // Utils 9 | import {getCurrentProviderName} from "../utils/blockchain"; 10 | 11 | @inject("network") 12 | class NoAccount extends React.Component { 13 | render() { 14 | return ( 15 |
16 |

Log In to { this.props.formatClientName(getCurrentProviderName()) }

17 |

Please unlock your { this.props.formatClientName(getCurrentProviderName()) } account to continue.

18 | 19 |
20 | 21 |
22 |
23 | ) 24 | } 25 | } 26 | 27 | export default NoAccount; 28 | -------------------------------------------------------------------------------- /src/components/WalletSendToken.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | import {inject, observer} from "mobx-react"; 4 | 5 | // Utils 6 | import {printNumber, formatNumber, isAddress, toWei} from "../utils/helpers"; 7 | 8 | @inject("profile") 9 | @inject("system") 10 | @observer 11 | class WalletSendToken extends React.Component { 12 | constructor() { 13 | super(); 14 | this.state = { 15 | fieldErrors: {} 16 | } 17 | } 18 | 19 | setAmount = () => { 20 | this.amount.value = formatNumber(this.props.system[this.props.sendToken].myBalance, false); 21 | } 22 | 23 | transfer = e => { 24 | e.preventDefault(); 25 | const token = this.props.sendToken; 26 | const amount = this.amount.value; 27 | const destination = this.destination.value; 28 | this.setState({ fieldErrors: {} }); 29 | 30 | if (!destination || !isAddress(destination)) { 31 | this.setState({ fieldErrors: { address: "Please enter a valid address" } }); 32 | } else if (!amount) { 33 | this.setState({ fieldErrors: { amount: "Please enter a valid amount" } }); 34 | } else if (this.props.system[token].myBalance.lt(toWei(amount))) { 35 | this.setState({ fieldErrors: { amount: `Not enough balance to transfer ${amount} ${this.props.tokenName(token)}` } }); 36 | } else { 37 | this.props.system.transferToken(token, destination, amount); 38 | this.amount.value = ""; 39 | this.destination.value = ""; 40 | this.props.closeSendBox(); 41 | } 42 | } 43 | 44 | render() { 45 | return ( 46 | 47 |
48 |
49 |
50 | 51 | 52 | 53 |
54 |

Send { this.props.tokenName(this.props.sendToken) }

55 |
56 |
57 |
58 |
59 | 63 |
{ printNumber(this.props.system[this.props.sendToken].myBalance) } { ` ${ this.props.tokenName(this.props.sendToken) } available` }
64 |
65 | { this.state.fieldErrors.amount &&

{ this.state.fieldErrors.amount }

} 66 | 67 |
68 | 72 |
73 | { this.state.fieldErrors.address &&

{ this.state.fieldErrors.address }

} 74 | 75 |
76 | 77 | 78 |
79 |
80 |
81 |
82 |
83 | ) 84 | } 85 | } 86 | 87 | export default WalletSendToken; 88 | -------------------------------------------------------------------------------- /src/components/Welcome.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import React from "react"; 3 | 4 | // Components 5 | import LegacyCupsAlert from "./LegacyCupsAlert"; 6 | 7 | // Images 8 | import welcomeSatellite from "images/welcome-satellite.svg"; 9 | 10 | // Utils 11 | import { mobileToggle } from "../utils/helpers"; 12 | 13 | class Welcome extends React.Component { 14 | render() { 15 | return ( 16 |
17 | 18 |
19 |

CDP Portal

20 |
21 |
22 |
23 | You have no CDPs open at this time. 24 |
25 |
26 | 27 |
28 |
29 | Welcome 30 |
31 |
32 |
33 | ) 34 | } 35 | } 36 | 37 | export default Welcome; 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import App from "./components/App"; 5 | 6 | ReactDOM.render(( 7 | 8 | ), document.getElementById("root")); 9 | -------------------------------------------------------------------------------- /src/scss/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // BREAKPOINTS 5 | @mixin respond-to($breakpoint) { 6 | @if $breakpoint == "s" { // small size screens = iphones, smartphones, phablet 7 | @media only screen and (max-width: 480px) { 8 | @content; 9 | } 10 | } 11 | @if $breakpoint == "s+" { // small size screens 768px and under 12 | @media only screen and (max-width: 768px) { 13 | @content; 14 | } 15 | } 16 | @if $breakpoint == "m" { // medium size screens 17 | @media only screen and (max-width: 1024px) { 18 | @content; 19 | } 20 | } 21 | @if $breakpoint == "l" { // large size screens = ipad portrait, laptops and above 22 | @media only screen and (max-width: 1280px) { 23 | @content; 24 | } 25 | } 26 | // screen sizes in between 1281 and 1599 px are defined with no media queries 27 | 28 | 29 | @if $breakpoint == " *, .bright-style { 44 | color: $black; 45 | } 46 | 47 | // CSS Font Color Classes 48 | .typo-white { color: $white; } 49 | .typo-mid-grey { color: $mid-grey; } 50 | .typo-grid-grey { color: $grid-grey; } 51 | 52 | // Background CSS-Classes 53 | // border CSS-Classes 54 | -------------------------------------------------------------------------------- /src/scss/_cp-cup-history.scss: -------------------------------------------------------------------------------- 1 | .cup-history-items { 2 | overflow: hidden; 3 | max-height: 0; 4 | min-height: 435px; 5 | transition: max-height 0.15s ease-out; 6 | 7 | &.expanded { 8 | max-height: 10000px; 9 | transition: max-height 0.25s ease-in; 10 | } 11 | } 12 | 13 | .history-cointainer { 14 | clear: both; 15 | margin-top: 35px; 16 | position: relative; 17 | .history-icon { 18 | float: left; 19 | height: 100px; 20 | width: 80px; 21 | img { 22 | position: absolute; 23 | display: block; 24 | width: auto; 25 | height: 48px; 26 | } 27 | .vertical-line { 28 | position: relative; 29 | width: 1px; 30 | height: calc(100% - 65px); 31 | left: 23px; 32 | top: 57px; 33 | border-left: 1px solid #313d47; 34 | } 35 | } 36 | .history-details { 37 | position: relative; 38 | top: 6px; 39 | @include respond-to(xl) { 40 | top: 2px; 41 | } 42 | line-height: 1.5; 43 | } 44 | .history-date { 45 | font-size: 0.85em; 46 | } 47 | .history-tx-links { 48 | margin-left: 10px; 49 | position: relative; 50 | top: -1px; 51 | .pipe-separator { 52 | font-size: 0.9em; 53 | padding: 0 1px; 54 | color: rgba(154, 163, 173, 0.65); 55 | } 56 | a { 57 | text-decoration: none; 58 | border-bottom: 1px solid rgba(154, 163, 173, 0.65); 59 | padding-bottom: 2px; 60 | position: relative; 61 | font-size: 0.85em; 62 | display: inline-block; 63 | line-height: 1; 64 | } 65 | } 66 | } 67 | .cup-history-items.hide-expander .history-cointainer:last-child .vertical-line { 68 | display: none; 69 | } 70 | 71 | .history-expand { 72 | clear: both; 73 | cursor: pointer; 74 | user-select: none; 75 | &.hide-expander { 76 | display: none; 77 | } 78 | .history-icon { 79 | float: left; 80 | height: 32px; 81 | width: 80px; 82 | svg { 83 | position: absolute; 84 | display: block; 85 | width: auto; 86 | height: 32px; 87 | margin-left: 7px; 88 | z-index: 1; 89 | } 90 | } 91 | &:hover .history-icon svg path { 92 | fill: #ced0d2; 93 | } 94 | .history-details { 95 | padding-top: 5px; 96 | @include respond-to(xl) { 97 | padding-top: 2px; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/scss/_cp-cup.scss: -------------------------------------------------------------------------------- 1 | 2 | h1.dashboard-headline span { 3 | color: rgba(154, 163, 173, 0.75); 4 | padding-left: 0.5rem; 5 | @include respond-to(s) { 6 | display: block; 7 | margin-top: 5px; 8 | padding-left: 0; 9 | } 10 | } 11 | 12 | .cup-top-right-buttons { 13 | float: right; 14 | height: 0; 15 | position: relative; 16 | top: -47px; 17 | @include respond-to(xl) { 18 | top: -58px; 19 | } 20 | 21 | a { 22 | display: inline-block; 23 | text-align: center; 24 | text-decoration: none; 25 | font-size: 0.8rem; 26 | margin-right: 1.5rem; 27 | &:last-child { 28 | margin-right: 0.5rem; 29 | } 30 | svg { 31 | width: 1.4rem; 32 | height: 1.4rem; 33 | path { 34 | stroke: #9aa3ad; 35 | transition: stroke .2s ease-in; 36 | } 37 | } 38 | &:hover svg path { 39 | stroke: #fff; 40 | } 41 | span { 42 | display: block; 43 | padding-top: 0.2rem; 44 | } 45 | } 46 | } 47 | 48 | .row.cup-price-information { 49 | .text-yellow { 50 | &, & * { 51 | color: #FBAE17; 52 | } 53 | } 54 | .text-red { 55 | &, & * { 56 | color: #C0392B; 57 | } 58 | } 59 | .strong-text { 60 | &, * { 61 | font-weight: 500; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/scss/_cp-dropdown-menu.scss: -------------------------------------------------------------------------------- 1 | // Dropdown menu 2 | .dropdown { 3 | position: relative; 4 | cursor: pointer; 5 | user-select: none; 6 | z-index: 1; 7 | 8 | // When dropdown-button becomes an SVG, use this to change 'fill' style on hover 9 | // .dropdown-button { 10 | // :hover > & { 11 | // } 12 | // } 13 | 14 | // Arrow 15 | svg.dropdown-arrow { 16 | display: none; 17 | position: absolute; 18 | right: -3px; 19 | bottom: -22px; 20 | } 21 | // Show arrow on hover 22 | &:hover svg.dropdown-arrow { 23 | display: block; 24 | } 25 | // Dropdown content (hidden by default) 26 | .dropdown-content { 27 | display: none; 28 | border-radius: 2px; 29 | right: -19px; 30 | // This padding allows the element to be up higher so that the mouse hover remains 31 | padding-top: 21px; 32 | top: 16px; 33 | overflow: hidden; 34 | box-shadow: 0 4px 12px 0 rgba(0,0,0,0.50); 35 | position: absolute; 36 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 37 | 38 | .dropdown-item-icon { 39 | float: left; 40 | width: 18px; 41 | height: 18px; 42 | } 43 | .dropdown-item:first-child { 44 | border-top-left-radius: 2px; 45 | border-top-right-radius: 2px; 46 | } 47 | .dropdown-item { 48 | color: black; 49 | font-size: 1.05rem; 50 | padding: 0.8rem 0 0.8rem 1.1rem; 51 | &:first-child { 52 | padding-top: 0.9rem; 53 | } 54 | &:last-child { 55 | padding-bottom: 0.9rem; 56 | } 57 | text-decoration: none; 58 | display: block; 59 | font-weight: 400; 60 | white-space: nowrap; 61 | &.has-icon { 62 | padding-right: 18px; 63 | } 64 | background-color: #d8d8d8; 65 | &:hover { 66 | background-color: #e1e1e1; 67 | } 68 | span { 69 | padding-right: 1.5rem; 70 | padding-left: 1.1rem; 71 | } 72 | } 73 | } 74 | // Show dropdown on hover 75 | &:hover .dropdown-content { 76 | display: block; 77 | } 78 | } 79 | 80 | // Implementation-specific styles 81 | .dropdown { 82 | float: right; 83 | width: 17px; 84 | height: 17px; 85 | 86 | img.dropdown-button { 87 | width: 17px; 88 | height: 17px; 89 | } 90 | .dropdown-footer { 91 | padding: 0; 92 | background-color: transparent; 93 | a { 94 | width: 50%; 95 | display: inline-block; 96 | background-color: #2D3337; 97 | &:hover { 98 | background-color: #4F565B; 99 | } 100 | padding-top: 0.8rem; 101 | padding-bottom: 0.8rem; 102 | text-align: center; 103 | font-size: 1rem; 104 | font-weight: 200; 105 | text-decoration: none; 106 | color: #fff; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/scss/_cp-help.scss: -------------------------------------------------------------------------------- 1 | .help-page { 2 | 3 | .breadcrumbs { 4 | a.breadcrumb-root { 5 | font-size: 24px; 6 | line-height: 1.3; 7 | text-decoration: none; 8 | &::after { 9 | display: inline-block; 10 | content: '\00bb'; 11 | margin: 0 .6em; 12 | color: rgba(154, 163, 173, 0.8); 13 | } 14 | } 15 | .breadcrumb-page { 16 | display: inline-block; 17 | font-size: 21px; 18 | line-height: 1.3; 19 | color: #bec4cc; 20 | } 21 | } 22 | 23 | .help-faq-item-markdown-container { 24 | margin-right: 3rem; 25 | 26 | span.help-faq-item-markdown, p.help-faq-item-markdown { 27 | display: block; 28 | max-width: 650px; 29 | font-size: 1.2rem; 30 | font-weight: 200; 31 | color: #9aa3ad; 32 | } 33 | 34 | .help-faq-item-markdown { 35 | padding: 1.5rem 0; 36 | font-size: 1.2rem; 37 | 38 | ul { 39 | list-style-type: disc; 40 | padding-left: 2.3rem; 41 | } 42 | 43 | & * { 44 | user-select: text !important; 45 | } 46 | 47 | h1 { 48 | letter-spacing: 0.1px; 49 | font-size: 1.4rem; 50 | line-height: 1.3; 51 | color: #fff; 52 | font-weight: 300; 53 | } 54 | 55 | h2 { 56 | font-size: 1.5rem; 57 | line-height: 1.3; 58 | margin-bottom: 1.8rem; 59 | font-weight: 200; 60 | color: #fff; 61 | letter-spacing: 0.02rem; 62 | margin-top: 4.5rem; 63 | 64 | &:first-of-type { 65 | margin-top: 3rem; 66 | } 67 | } 68 | 69 | h3 { 70 | font-size: 1.5rem; 71 | line-height: 1.3; 72 | margin-bottom: 1.5rem; 73 | margin-top: 3rem; 74 | font-weight: 200; 75 | color: #dedede; 76 | letter-spacing: 0.02rem; 77 | } 78 | 79 | h2 + h3 { 80 | margin-top: 2rem; 81 | } 82 | 83 | p { 84 | max-width: 650px; 85 | margin-top: 0; 86 | font-size: 1.2rem; 87 | font-weight: 200; 88 | color: #9aa3ad; 89 | } 90 | 91 | b, strong { 92 | color: #a2aab3; 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/scss/_cp-inline-notification.scss: -------------------------------------------------------------------------------- 1 | .inline-notification { 2 | display: inline-block; 3 | position: relative; 4 | clear: both; 5 | background-color: $white-two; 6 | color: #26323a; 7 | padding: 1rem 1.3rem !important; 8 | margin: 2rem 0 1rem; 9 | border-radius: 2px; 10 | min-width: 250px; 11 | .caption { 12 | position: relative; 13 | font-size: 1.3rem; 14 | font-weight: 300; 15 | margin-bottom: 0.5rem; 16 | margin-left: 2.4rem; 17 | } 18 | .message { 19 | position: relative; 20 | font-size: 1.1rem; 21 | @include respond-to(xl) { 22 | font-size: 1rem; 23 | } 24 | font-weight: 200; 25 | &.has-button { 26 | padding-right: 13rem; 27 | @include respond-to('s+') { 28 | padding-right: 0; 29 | padding-bottom: 4rem; 30 | } 31 | } 32 | &.no-caption { 33 | margin-left: 2.7rem; 34 | @include respond-to(xl) { 35 | margin-left: 2.5rem; 36 | } 37 | } 38 | } 39 | button { 40 | min-width: 10rem; 41 | position: absolute; 42 | bottom: 0; 43 | right: 0; 44 | margin-bottom: 0; 45 | } 46 | // Close button 47 | svg.close-button { 48 | position: relative; 49 | float: right; 50 | cursor: pointer; 51 | overflow: hidden; 52 | width: 20px; 53 | height: 20px; 54 | z-index: 1; 55 | path + g { 56 | stroke: #2B3943; 57 | } 58 | &:hover path + g { 59 | stroke: lighten(#2B3943, 20%); 60 | } 61 | } 62 | // Icon 63 | svg.notification-icon { 64 | float: left; 65 | position: absolute; 66 | width: 21px; 67 | height: 19px; 68 | top: 1.1rem; 69 | @include respond-to(xl) { 70 | top: 1.2rem; 71 | } 72 | path { 73 | fill: #26323A; 74 | } 75 | } 76 | &.is-warning { 77 | svg.notification-icon path { 78 | fill: #202930; 79 | } 80 | & { 81 | background-color: #FBAE17; 82 | color: #2B3943; 83 | } 84 | } 85 | &.is-error { 86 | svg.notification-icon path { 87 | fill: $white; 88 | } 89 | & { 90 | background-color: #C0392B; 91 | color: $white; 92 | } 93 | } 94 | &.is-stability-fee-warning { 95 | border-radius: 5px; 96 | padding: unset !important; 97 | padding-left: 15px !important; 98 | padding-right: 15px !important; 99 | } 100 | } 101 | 102 | // General notifications 103 | .general-notifications .inline-notification { 104 | margin: 0.5rem 0 0.7rem; 105 | min-width: 100%; 106 | } 107 | .main-column > div.row.general-notifications { 108 | padding: 1.7em 3.4em 1.5em; 109 | } 110 | 111 | // McdAlert notification 112 | .inline-notification.mcd-alert { 113 | display: block; 114 | width: 90%; 115 | background-color: #FBAE17; 116 | padding: 1rem 1.5rem 1.1rem !important; 117 | margin: 1rem auto; 118 | background-color: #FBAE17; 119 | .message { 120 | font-weight: 300; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/scss/_cp-loading-spinner.scss: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | to { 3 | transform: rotate(360deg); 4 | } 5 | } 6 | 7 | .spinner { 8 | position: relative; 9 | width: 120px; 10 | height: 120px; 11 | margin: 2rem auto; 12 | &:before { 13 | content: ''; 14 | box-sizing: border-box; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | border-radius: 50%; 21 | border: 1px solid #ccc; 22 | border-top-color: #1bc4a6; 23 | animation: spinner .6s linear infinite; 24 | } 25 | .maker-logo { 26 | position: relative; 27 | margin: 0 auto; 28 | width: 45%; 29 | height: 32.5%; 30 | top: 50%; 31 | svg { 32 | position: relative; 33 | margin: 0 auto; 34 | width: 100%; 35 | height: 100%; 36 | top: -50%; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/scss/_cp-markdown.scss: -------------------------------------------------------------------------------- 1 | .markdown { 2 | padding: 1.5rem 0; 3 | 4 | & * { 5 | user-select: text !important; 6 | } 7 | 8 | h1 { 9 | font-size: 2.5rem !important; 10 | border-color: #313d47; 11 | border-style: solid; 12 | border-width: 0 0 1px 0; 13 | padding-bottom: 1.7rem; 14 | } 15 | 16 | h2 { 17 | font-size: 1.85rem; 18 | line-height: 1.3; 19 | margin-bottom: 1.8rem; 20 | font-weight: 200; 21 | color: #fff; 22 | letter-spacing: 0.02rem; 23 | margin-top: 4.5rem; 24 | 25 | &:first-of-type { 26 | margin-top: 3rem; 27 | } 28 | } 29 | 30 | h3 { 31 | font-size: 1.5rem; 32 | line-height: 1.3; 33 | margin-bottom: 1.5rem; 34 | margin-top: 3rem; 35 | font-weight: 200; 36 | color: #dedede; 37 | letter-spacing: 0.02rem; 38 | } 39 | 40 | h2 + h3 { 41 | margin-top: 2rem; 42 | } 43 | 44 | p { 45 | max-width: 75%; 46 | font-size: 1.2rem; 47 | font-weight: 200; 48 | color: #9aa3ad; 49 | } 50 | 51 | b, strong { 52 | color: #a2aab3; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/scss/_cp-menu-bar.scss: -------------------------------------------------------------------------------- 1 | .menu-bar { 2 | background-color: $black; 3 | margin: 0; 4 | padding: 0; 5 | width: 65px; 6 | width: 5rem; 7 | height: 100%; 8 | display: block; 9 | position: fixed; 10 | left: 0; 11 | top: 0; 12 | z-index: 1; 13 | overflow: hidden; 14 | user-select: none; 15 | 16 | .logo { 17 | cursor: pointer; 18 | position: absolute; 19 | top:0; 20 | left:0; 21 | z-index: 3; 22 | display: block; 23 | background: $white; 24 | text-align: center; 25 | height: 65px; 26 | height: 5rem; 27 | width: 100%; 28 | padding: 26px 13px; 29 | padding: 1.53846rem 1rem; 30 | overflow: hidden; 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | img, svg { 35 | display: inline-block; 36 | width: 100%; 37 | height: auto; 38 | min-width: 0; 39 | min-height: 0; 40 | } 41 | } 42 | .menu-label { 43 | display: none; 44 | } 45 | nav { 46 | position: absolute; 47 | top:0; 48 | left: 0; 49 | display: inline; 50 | padding: 0; 51 | margin: 0; 52 | height: 100%; 53 | width: 100%; 54 | } 55 | .menu { 56 | z-index: 2; 57 | text-indent: 0; 58 | padding: 65px 0 0; 59 | padding: 5rem 0 0; 60 | margin: 0 0 0; 61 | position: relative; 62 | height: 100%; 63 | width: 100%; 64 | 65 | li.cdp-id-item { 66 | a { 67 | text-decoration: none; 68 | padding: 1rem; 69 | @include respond-to(m) { 70 | padding: 0; 71 | } 72 | } 73 | @include respond-to(m) { 74 | padding: 10px 0; 75 | font-size: 0.8rem; 76 | } 77 | @include respond-to(s) { 78 | padding: 13px 0; 79 | font-size: 0.8rem; 80 | } 81 | } 82 | li a { 83 | padding: 1.76rem; 84 | @include respond-to(m) { 85 | padding: 0; 86 | } 87 | width: 100%; 88 | height: 100%; 89 | } 90 | li, &>* { 91 | cursor: pointer; 92 | list-style-type: none; 93 | padding: 0; 94 | margin: 0; 95 | text-indent: 0; 96 | width: 100%; 97 | height: 65px; 98 | height: 5rem; 99 | text-align: center; 100 | display: inline-block; 101 | float: left; 102 | display: flex; 103 | flex-direction: column; 104 | justify-content: center; 105 | transition: background-color .2s linear, color .2s linear; 106 | 107 | &:hover, &.active { 108 | background-color: $bg-darkgrey; 109 | color: $white; 110 | img { opacity: 1; } 111 | } 112 | &:active { 113 | img { opacity: 0.5; } 114 | } 115 | &[value="settings"]{ 116 | position: absolute; 117 | transform: translateY(-1px); 118 | bottom: 65px; 119 | bottom: 5rem; 120 | } 121 | &[value="help"]{ 122 | position: absolute; 123 | bottom: 0; 124 | } 125 | 126 | img { 127 | width: 100%; 128 | height: auto; 129 | max-width: 100%; 130 | opacity: 0.5; 131 | transition: opacity .2s ease-in; 132 | } 133 | } 134 | } 135 | 136 | @include respond-to(m) { 137 | 138 | width: 100%; 139 | height: 50px; 140 | position: relative; 141 | padding: 0; 142 | 143 | .logo { 144 | height: 50px; 145 | width: 50px; 146 | padding: 10px 12px; 147 | } 148 | .menu { 149 | padding: 0 0 0 50px; 150 | 151 | li, &>* { 152 | height: 100%; 153 | width: 50px; 154 | padding: 16px; 155 | display: inline-block; 156 | &[value="help"], &[value="settings"]{ 157 | transform: none; 158 | position: relative; 159 | bottom: auto; 160 | float: right; 161 | margin-left: 1px; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/scss/_cp-migrate-legacy-cups.scss: -------------------------------------------------------------------------------- 1 | // Customized inline notification 2 | .inline-notification.migrate-cups { 3 | padding: 1rem 1.5rem 1.1rem !important; 4 | .message { 5 | font-weight: 300; 6 | } 7 | } 8 | 9 | // Migrate cups menu item 10 | ul.menu { 11 | li.migrate-cups { 12 | padding-top: 12px; 13 | padding-bottom: 10px; 14 | height: auto; 15 | 16 | color: #dedede; 17 | &.active, &:hover { 18 | color: #fff; 19 | svg path { 20 | fill: #fff; 21 | } 22 | } 23 | 24 | svg { 25 | display: block; 26 | margin: 0 auto 5px; 27 | width: 20px; 28 | height: auto; 29 | path { 30 | transition: fill 0.2s linear; 31 | } 32 | } 33 | 34 | @include respond-to(m) { 35 | height: 100%; 36 | padding: 5px 10px; 37 | font-size: 0.8rem; 38 | width: auto; 39 | svg { 40 | margin: 2px auto 4px; 41 | width: 18px; 42 | } 43 | } 44 | } 45 | } 46 | 47 | // Migrate cups page 48 | .migrate-cups-section { 49 | 50 | .number-of-cdps-to-migrate { 51 | font-size: 1.1rem; 52 | font-weight: 200; 53 | letter-spacing: 0.02rem; 54 | clear: both; 55 | padding-top: 0.5rem; 56 | } 57 | 58 | .cup-to-migrate { 59 | 60 | .cdp-id-heading { 61 | margin-top: 3rem; 62 | margin-bottom: 0.4rem 63 | } 64 | 65 | table { 66 | float: left; 67 | border: 1px solid #728391; 68 | padding: 0.7rem 1rem 0.6rem; 69 | width: calc(100% - 200px); 70 | @include respond-to(xl) { 71 | width: calc(100% - 230px); 72 | } 73 | th, tr { 74 | text-align: left; 75 | color: #fff; 76 | letter-spacing: 0.01rem; 77 | } 78 | th { 79 | text-transform: uppercase; 80 | font-weight: 300; 81 | font-size: 0.9rem; 82 | white-space: nowrap; 83 | &.status-column { 84 | width: 75px; 85 | } 86 | } 87 | tr { 88 | font-weight: 200; 89 | font-size: 1.2rem; 90 | color: rgba(255, 255, 255, 0.85); 91 | td { 92 | padding-top: 3px; 93 | &.cdp-status { 94 | svg { 95 | margin-right: 8px; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | .migrate-button { 102 | float: right; 103 | margin-top: 1.2rem; 104 | button { 105 | padding-left: 1.5rem; 106 | padding-right: 1.5rem; 107 | } 108 | } 109 | 110 | .migrate-success { 111 | display: inline-block; 112 | min-width: 12rem; 113 | background: #27AE60; 114 | padding: 12px 1.5rem 12px 1.5rem; 115 | margin-bottom: 1em; 116 | color: #fff; 117 | text-align: center; 118 | font-weight: normal; 119 | letter-spacing: 0.0025em; 120 | outline: 0; 121 | margin: 0; 122 | border-radius: 2px; 123 | user-select: none; 124 | font-size: 100%; 125 | line-height: 1.15; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/scss/_cp-modal.scss: -------------------------------------------------------------------------------- 1 | #root > .modal-open { 2 | @include filter(blur, 3px); 3 | } 4 | .modal { 5 | position: fixed; 6 | left: 0px; 7 | right: 0px; 8 | top: 0px; 9 | bottom: 0px; 10 | background: rgba(0, 0, 0, 0.25); 11 | z-index: 997; 12 | .modal-inner { 13 | position: relative; 14 | width: 29.2rem; 15 | background: #202930; 16 | z-index: 998; 17 | margin: 175px auto 0; 18 | box-shadow: 0 4px 12px 0 rgba(0,0,0,0.50); 19 | border-radius: 3px; 20 | 21 | h2 { 22 | color: #fff; 23 | text-align: center; 24 | padding-top: 2.4rem; 25 | letter-spacing: 0.02rem; 26 | margin-bottom: 1rem; 27 | } 28 | h3 { 29 | font-size: 1.4rem; 30 | line-height: 1; 31 | color: #fff; 32 | text-align: center; 33 | letter-spacing: 0.02rem; 34 | margin: 1rem 0; 35 | } 36 | p { 37 | color: #A3A3A3; 38 | font-size: 0.9rem; 39 | line-height: 1.75; 40 | text-align: center; 41 | width: 65%; 42 | margin: 0 auto 2.3rem; 43 | user-select: none; 44 | } 45 | } 46 | } 47 | 48 | .modal.create-cdp { 49 | @include respond-to('s+') { 50 | position: absolute; 51 | } 52 | } 53 | 54 | .modal.create-cdp .modal-inner { 55 | width: 54rem; 56 | max-width: 90%; 57 | margin-top: 10vh; 58 | @include respond-to('s+') { 59 | margin-top: 2rem; 60 | } 61 | h2 { 62 | color: #fff; 63 | text-align: center; 64 | padding: 0; 65 | font-size: 1.4rem; 66 | letter-spacing: 0.02rem; 67 | font-weight: 200; 68 | padding: 1.5rem 0; 69 | border-bottom: 1px solid #313D47; 70 | } 71 | img.main { 72 | display: block; 73 | margin: 0 auto; 74 | margin-top: 4rem; 75 | width: 488px; 76 | height: 176px; 77 | max-width: 100%; 78 | } 79 | p { 80 | color: #879099; 81 | font-size: 1.1rem; 82 | line-height: 1.4; 83 | letter-spacing: 0.02rem; 84 | margin: 2rem auto 1.5rem; 85 | font-weight: 200; 86 | } 87 | ul { 88 | display: block; 89 | font-size: 1rem; 90 | font-weight: 300; 91 | color: #fff; 92 | letter-spacing: 0.02rem; 93 | text-align: center; 94 | margin: 0; 95 | padding-bottom: 3.7rem; 96 | li { 97 | display: inline-block; 98 | margin: 1rem 3rem 0; 99 | @include respond-to('s+') { 100 | margin: 1rem 0.5rem 0; 101 | min-width: 5rem; 102 | } 103 | min-width: 10rem; 104 | &:first-child { 105 | margin-left: 0; 106 | } 107 | &:last-child { 108 | margin-right: 0; 109 | } 110 | div.icon { 111 | height: 30px; 112 | margin-bottom: 0.7rem; 113 | img { 114 | width: auto; 115 | height: 24px; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/scss/_cp-not-found.scss: -------------------------------------------------------------------------------- 1 | .not-found { 2 | width: 100%; 3 | height: 588px; 4 | background: linear-gradient(to bottom, #202124, #303B41); 5 | img { 6 | width: auto; 7 | height: 546px; 8 | margin-top: 100px; 9 | } 10 | .not-found-text-container-1 { 11 | width: 80%; 12 | margin: 0 auto; 13 | white-space: nowrap; 14 | } 15 | .not-found-text-container-2 { 16 | float: left; 17 | } 18 | h1 { 19 | color: #50E3C2; 20 | font-size: 1rem; 21 | margin: 2rem 0 1rem; 22 | } 23 | h2 { 24 | font-size: 2.3rem; 25 | margin: 1rem 0 0.5rem; 26 | color: #fff; 27 | } 28 | h3 { 29 | font-size: 2.3rem; 30 | margin: 1rem 0 0.5rem; 31 | color: #fff; 32 | } 33 | p { 34 | color: #808B97; 35 | font-size: 1.3rem; 36 | margin: 0.5rem 0; 37 | user-select: none; 38 | } 39 | } 40 | .not-found-2 { 41 | .not-found-text-container-1 { 42 | width: 80%; 43 | margin: 0 auto; 44 | } 45 | p { 46 | color: #808B97; 47 | font-size: 1.5rem; 48 | margin: 3rem 0 1rem; 49 | user-select: none; 50 | } 51 | button { 52 | margin: 1rem 0; 53 | padding-left: 2rem; 54 | padding-right: 2rem; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/scss/_cp-notify.scss: -------------------------------------------------------------------------------- 1 | // boxes in alerts 2 | .notifications-container { 3 | position: fixed; 4 | width: 340px; 5 | z-index: 999; 6 | right: 0; 7 | top: 0; 8 | 9 | & > * { 10 | padding: 32px 44px; 11 | position: relative; 12 | border: 0; 13 | border-radius: 2px; 14 | margin-top: 0.7rem; 15 | right: 0.7rem; 16 | p { 17 | max-width: 30em; 18 | } 19 | } 20 | h3 { 21 | margin-top: 0px; 22 | } 23 | .nf-error { 24 | background-color: rgba($error-red, 0.85); 25 | color: $white; 26 | .notification-headline { 27 | &:after { 28 | content: url('"~images/icon-stop.svg'); 29 | position: absolute; 30 | left: -1.5em; 31 | top: auto; 32 | margin-top: 0.05em; 33 | } 34 | } 35 | } 36 | .nf-info { 37 | background-color: rgba($neutral-blue, 0.85); 38 | color: $white; 39 | .notification-headline { 40 | &:after { 41 | content: url('"~images/icon-clock.svg'); 42 | position: absolute; 43 | left: -1.5em; 44 | top: auto; 45 | margin-top: 0.05em; 46 | } 47 | } 48 | } 49 | .nf-success { 50 | background-color: rgba($success-green, 0.85); 51 | color: $white; 52 | .notification-headline { 53 | &:after { 54 | content: url('"~images/icon-check.svg'); 55 | position: absolute; 56 | left: -1.5em; 57 | top: auto; 58 | margin-top: 0.05em; 59 | } 60 | } 61 | } 62 | .nf-notice { 63 | background-color: rgba(#EAEAEA, 0.85); 64 | .notification-headline { 65 | color: #C0392B; 66 | &:after { 67 | content: url("~images/icon-warning.svg"); 68 | position: absolute; 69 | left: -30px; 70 | top: auto; 71 | margin-top: 3px; 72 | } 73 | } 74 | div.grouped-section { 75 | color: #C0392B; 76 | font-size: 1.05rem; 77 | letter-spacing: 0.01rem; 78 | margin-top: 1em; 79 | margin-bottom: 1em; 80 | } 81 | .dark-text { 82 | color: #202930; 83 | } 84 | .indented-section { 85 | position: relative; 86 | padding-left: 2.5rem; 87 | } 88 | .line-indent { 89 | width: 7px; 90 | height: 94%; 91 | top: 3%; 92 | position: absolute; 93 | left: 10px; 94 | border-bottom: 1px solid #313D47; 95 | &:after { 96 | border-left: 1px solid #313D47; 97 | width: 1px; 98 | height: 100%; 99 | content: ' '; 100 | position: absolute; 101 | left: 3px; 102 | } 103 | } 104 | } 105 | .close-box { 106 | position: absolute; 107 | top: 0.785em; 108 | right: 0.785em; 109 | } 110 | .notification-headline { 111 | @include fontweight(bold); 112 | position: relative; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/scss/_cp-range-slider.scss: -------------------------------------------------------------------------------- 1 | .horizontal-slider { 2 | width: 225px; 3 | margin: 0 auto; 4 | cursor: pointer; 5 | 6 | .rangeslider.rangeslider-horizontal { 7 | height: 4px; 8 | border-radius: 0; 9 | background-color: #9AA3AD; 10 | width: 100%; 11 | -webkit-box-shadow: none; 12 | box-shadow: none; 13 | margin: 20px 0 16px; 14 | 15 | .rangeslider__handle { 16 | outline: none; 17 | width: 18px; 18 | height: 18px; 19 | border-radius: 50%; 20 | -webkit-box-shadow: none; 21 | box-shadow: none; 22 | background-color: #1ABC9C; 23 | border: none; 24 | &:after { 25 | display: none; 26 | } 27 | } 28 | 29 | .rangeslider__fill { 30 | -webkit-box-shadow: none; 31 | box-shadow: none; 32 | border-radius: 0; 33 | background-color: #20af93; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/scss/_cp-steps.scss: -------------------------------------------------------------------------------- 1 | header { 2 | .rc-steps { 3 | @include inline-flex; 4 | width: unset; 5 | } 6 | 7 | .rc-steps-item-custom { 8 | -ms-flex: unset; 9 | flex: unset; 10 | user-select: none; 11 | white-space: nowrap; 12 | .rc-steps-item-icon { 13 | font-size: 1rem; 14 | background: initial; 15 | height: unset; 16 | width: unset; 17 | margin-right: 0.6rem; 18 | border-radius: 50%; 19 | background-color: $bg-grey; 20 | border: solid 1px $line-grey; 21 | @include transition(background-color 0.5s, border-color 0.5s); 22 | 23 | & > .rc-steps-icon { 24 | color: $text-grey; 25 | font-size: unset; 26 | top: unset; 27 | width: unset; 28 | height: unset; 29 | @include transition(color 0.5s); 30 | } 31 | } 32 | .rc-steps-item-icon-inner { 33 | border: 2px solid $bg-grey; 34 | background-color: $line-grey; 35 | width: 2em; 36 | height: 2em; 37 | line-height: calc(2em - 4px); 38 | text-align: center; 39 | border-radius: 50%; 40 | font-weight: normal; 41 | font-size: 1em; 42 | } 43 | .rc-steps-item-content { 44 | margin-top: 2px; 45 | // Step text 46 | .rc-steps-item-title { 47 | color: $text-grey; 48 | font-weight: 300; 49 | font-size: 1.3rem; 50 | } 51 | // Line separator 52 | .rc-steps-item-title:after { 53 | top: 50%; 54 | background-color: $text-grey; 55 | } 56 | } 57 | // Line separator width 58 | &:not(:last-child) .rc-steps-item-title { 59 | margin-right: 35px; 60 | } 61 | 62 | // Active step element styles 63 | &.rc-steps-item-process { 64 | .rc-steps-item-icon { 65 | background-color: $bg-grey; 66 | border-color: darken($white, 5%); 67 | & > .rc-steps-icon { 68 | color: $white; 69 | } 70 | } 71 | // Step text 72 | .rc-steps-item-title { 73 | color: $white; 74 | } 75 | // Step separator (line) 76 | .rc-steps-item-title:after { 77 | background-color: $white; 78 | } 79 | } 80 | 81 | // Finished step element styles 82 | &.rc-steps-item-finish { 83 | // Step separator (line) 84 | .rc-steps-item-title:after { 85 | background-color: $text-grey; 86 | } 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/scss/_cp-toggle-switch.scss: -------------------------------------------------------------------------------- 1 | .switch { 2 | position: relative; 3 | width: 44px; 4 | height: 22px; 5 | border-radius: 11px; 6 | cursor: pointer; 7 | display: inline-block; 8 | background-color: #4f565b; 9 | transition: background-color 0.2s ease-in-out; 10 | line-height: 1; 11 | font-size: 14px; 12 | 13 | &.on { 14 | background-color: #1abc9c; 15 | 16 | & .toggle-switch { 17 | background-color: #fff; 18 | left: calc(100% - 20px); 19 | 20 | svg { 21 | top: 2px; 22 | width: 9px; 23 | height: 10px; 24 | } 25 | } 26 | 27 | .switch-spinner:before { 28 | border-top-color: #16927a; 29 | } 30 | } 31 | 32 | &.disabled { 33 | cursor: not-allowed; 34 | } 35 | 36 | .toggle-switch { 37 | position: absolute; 38 | left: 3px; 39 | top: 2px; 40 | width: 18px; 41 | height: 18px; 42 | border-radius: 50%; 43 | background-color: #152128; 44 | transition: left 0.2s ease-in-out, background-color 0.2s ease-in-out; 45 | text-align: center; 46 | 47 | svg { 48 | position: relative; 49 | top: 1px; 50 | width: 10px; 51 | height: 10px; 52 | } 53 | } 54 | @keyframes switch-spinner { 55 | to { 56 | transform: rotate(360deg); 57 | } 58 | } 59 | 60 | .switch-spinner { 61 | position: relative; 62 | width: 100%; 63 | height: 100%; 64 | 65 | &:before { 66 | content: ''; 67 | box-sizing: border-box; 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | width: 100%; 72 | height: 100%; 73 | border-radius: 50%; 74 | border: 1px solid transparent; 75 | border-top-color: #1bc4a6; 76 | animation: switch-spinner 0.6s ease-in-out infinite; 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/scss/_cp-tooltip.scss: -------------------------------------------------------------------------------- 1 | .__react_component_tooltip { 2 | background-color: #ffffff; 3 | box-shadow: 0 -2px 8px 0 rgba(0, 0, 0, 0.4); 4 | opacity: 1 !important; 5 | z-index: 10000; 6 | pointer-events: all; 7 | max-width: 410px !important; 8 | @include respond-to(s) { 9 | max-width: 200px !important; 10 | } 11 | a:hover { 12 | color: $maker-green; 13 | } 14 | a.more-info { 15 | // margin-left: 5px; 16 | } 17 | } 18 | 19 | .tooltip-hint { 20 | display: inline-block; 21 | position: relative; 22 | cursor: pointer; 23 | margin-left: 0.4rem; 24 | top: 0.15rem; 25 | width: 12px; 26 | height: 12px; 27 | @include respond-to(xl) { 28 | width: 13px; 29 | height: 13px; 30 | top: 1px; 31 | } 32 | 33 | path { 34 | fill: $text-grey; 35 | } 36 | &:hover path { 37 | fill: $silver; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/scss/_cp-wizard.scss: -------------------------------------------------------------------------------- 1 | svg.wizard.expand-section-btn { 2 | float: right; 3 | } 4 | 5 | .wizard-section { 6 | .input-values-container { 7 | display: inline-block; 8 | input.number-input { 9 | min-width: 15rem; 10 | } 11 | @include respond-to('s+') { 12 | input.number-input { 13 | font-size: 1.5em; 14 | max-width: 77%; 15 | min-width: unset; 16 | width: 29.25rem; 17 | height: 39px; 18 | & + .unit { 19 | font-size: 1.2em; 20 | width: 20%; 21 | max-width: 4em; 22 | min-width: unset; 23 | padding: 0; 24 | height: 39px; 25 | line-height: 3.1em; 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | .wizard-automated-transactions { 33 | user-select: none; 34 | overflow: hidden; 35 | max-height: 0; 36 | transition: max-height 0.15s ease-out; 37 | 38 | &.expanded { 39 | max-height: 1000px; 40 | transition: max-height 0.25s ease-in; 41 | } 42 | 43 | .step-cointainer { 44 | clear: both; 45 | &:first-child { 46 | padding-top: 2.5rem; 47 | } 48 | &:last-child { 49 | padding-bottom: 3.5rem; 50 | } 51 | 52 | .step-icon { 53 | float: left; 54 | height: 5.6rem; 55 | width: 4.5rem; 56 | 57 | .vertical-line { 58 | position: relative; 59 | width: 1px; 60 | height: calc(100% - 3.8rem); 61 | left: 1rem; 62 | top: 0.85rem; 63 | border-left: 1px solid $line-grey; 64 | } 65 | } 66 | 67 | &:last-child .step-icon { 68 | height: 1px; 69 | .vertical-line { 70 | display: none; 71 | } 72 | } 73 | 74 | .step-message { 75 | float: left; 76 | line-height: 1.5; 77 | margin-top: 3px; 78 | } 79 | 80 | .steps-item { 81 | display: inline-block; 82 | font-size: 1rem; 83 | margin-right: 0.6rem; 84 | border-radius: 50%; 85 | background-color: $bg-grey; 86 | border: solid 1px darken($white, 5%); 87 | 88 | .steps-item-inner { 89 | color: darken($white, 5%); 90 | border: 2px solid $bg-grey; 91 | background-color: $line-grey; 92 | width: 2em; 93 | height: 2em; 94 | line-height: calc(2em - 4px); 95 | text-align: center; 96 | border-radius: 50%; 97 | font-weight: normal; 98 | font-size: 1em; 99 | } 100 | } 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/scss/_forecast-mode.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .forecast-mode { 4 | 5 | .value { 6 | 7 | &:before { 8 | content: 'forecast *'; 9 | white-space: nowrap; 10 | position: absolute; 11 | right: 100%; 12 | padding-right: 0.5em; 13 | top: -0.5em; 14 | @include fontsize(cxs); 15 | color: $maker-green; 16 | } 17 | } 18 | 19 | 20 | .chart-container .chart .line-chart .x.axis g.tick:last-child { 21 | text { 22 | fill: $maker-green; 23 | } 24 | line { 25 | stroke: $maker-green-alpha; 26 | stroke-width: 4em; 27 | stroke-linecap: butt; 28 | stroke-dasharray: 0.1% 0.1%; 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | // LABELS 2 | 3 | label, .form-label { 4 | margin-bottom: 0.5em; 5 | display: block; 6 | 7 | input + & { 8 | margin-top: 2em; 9 | } 10 | } 11 | 12 | // FIELDSET 13 | 14 | fieldset { 15 | border: 0; 16 | padding-left: 0; 17 | padding-right: 0; 18 | } 19 | 20 | // TEXT/NUMBER INPUTS 21 | 22 | input[type="text"], input[type="number"] { 23 | border: 0; 24 | outline: 0; 25 | padding: 0 .5em; 26 | @include interactive("pushInAndCrush"); 27 | @include fontweight(bold); 28 | line-height: 2.5em; 29 | height: 2.5em; 30 | } 31 | 32 | input[type="text"].number-input, input[type="number"].number-input { 33 | float: left; 34 | text-align: right; 35 | border-radius: 2px 0px 0px 2px; 36 | position: relative; 37 | z-index: 1; 38 | & + .unit { 39 | color: lighten($black, 5%); 40 | position: relative; 41 | z-index: 0; 42 | display: inline-block; 43 | float: left; 44 | height: 2.5em; 45 | line-height: 2.5em; 46 | vertical-align: baseline; 47 | padding: 0 1em; 48 | background-color: $white-two; 49 | min-width: 4em; 50 | text-align: center; 51 | border-radius: 0 2px 2px 0; 52 | } 53 | } 54 | 55 | 56 | // CHECKBOXES 57 | 58 | input[type='checkbox']:checked, 59 | input[type='checkbox']:not(:checked), 60 | input[type='radio']:checked, 61 | input[type='radio']:not(:checked) { 62 | background: transparent; 63 | position: relative; 64 | visibility: hidden; 65 | margin: 0; 66 | padding: 0; 67 | } 68 | 69 | input[type='checkbox']+label, 70 | input[type='radio']+label { 71 | cursor: pointer; 72 | } 73 | 74 | input[type="radio"] { 75 | // Remove standard styles 76 | -webkit-appearance: none; 77 | -moz-appearance: none; 78 | appearance: none; 79 | border: none; 80 | border-radius: 0; 81 | font-size: 1em; 82 | 83 | // Graceful degradation for IE8 84 | width: auto; 85 | float: left; 86 | margin-right: .75em; 87 | 88 | // Hide native radio button 89 | background: transparent; 90 | border: none; 91 | display: inline-block; 92 | line-height: inherit; 93 | vertical-align: baseline; 94 | 95 | // Add custom styles 96 | &:checked + label::before, 97 | &:not(:checked) + label::before { 98 | content: ' '; 99 | display: inline-block; 100 | position: relative; 101 | top: 5px; 102 | width: 15px; 103 | height: 15px; 104 | @include respond-to(xl) { 105 | width: 16px; 106 | height: 16px; 107 | margin-right: 7px; 108 | } 109 | border: 2px solid rgba(49, 61, 71, 0.8); 110 | margin-right: 6px; 111 | border-radius: 50%; 112 | transition-property: border-color, background; 113 | transition-duration: 0.2s; 114 | transition-timing-function: ease-in; 115 | } 116 | & + label:hover::before { 117 | border-color: rgba(49, 61, 71, .8); 118 | background: rgba(18, 187, 155, 0.3); 119 | box-shadow: inset 0px 0px 0px 2px #ced0d2; 120 | } 121 | &:checked + label::before { 122 | border-color: rgba(49, 61, 71, .8); 123 | background: #12BB9B; 124 | box-shadow: inset 0px 0px 0px 2px #ced0d2; 125 | } 126 | 127 | // Layout for labels 128 | & + label { 129 | display: inline-block; 130 | margin:0; 131 | user-select: none; 132 | } 133 | & + label + & + label { 134 | margin-left: 3em; 135 | } 136 | } 137 | 138 | // Custom checkbox 139 | label.checkbox-container { 140 | // Customize the label container 141 | display: block; 142 | position: relative; 143 | padding-left: 26px; 144 | cursor: pointer; 145 | font-size: 1.1rem; 146 | line-height: 1.3; 147 | -webkit-user-select: none; 148 | -moz-user-select: none; 149 | -ms-user-select: none; 150 | user-select: none; 151 | // Hide the browser's default checkbox 152 | input { 153 | position: absolute; 154 | opacity: 0; 155 | cursor: pointer; 156 | } 157 | // Create a custom checkbox 158 | .checkmark { 159 | position: absolute; 160 | top: 0; 161 | left: 0; 162 | height: 18px; 163 | width: 18px; 164 | border-radius: 2px; 165 | background-color: #eee; 166 | } 167 | // On mouse-over, darken bg 168 | &:hover input ~ .checkmark { 169 | background-color: darken(#eee, 3%); 170 | } 171 | // When the checkbox is checked 172 | input:checked ~ .checkmark { 173 | background-color: #1BC4A6; 174 | } 175 | // On mouse-over, lighten bg 176 | &:hover input:checked ~ .checkmark { 177 | background-color: lighten(#1BC4A6, 3%); 178 | } 179 | // Create the checkmark/indicator (hidden when not checked) 180 | .checkmark:after { 181 | content: ""; 182 | position: absolute; 183 | display: none; 184 | } 185 | // Show the checkmark when checked 186 | input:checked ~ .checkmark:after { 187 | display: block; 188 | } 189 | // Style the checkmark/indicator 190 | .checkmark:after { 191 | left: 6px; 192 | top: 2px; 193 | width: 3px; 194 | height: 9px; 195 | border: solid #0b5447; 196 | border-width: 0 3px 3px 0; 197 | -webkit-transform: rotate(45deg); 198 | -ms-transform: rotate(45deg); 199 | transform: rotate(45deg); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/scss/_general.scss: -------------------------------------------------------------------------------- 1 | .printedNumber { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /src/scss/_helpers.scss: -------------------------------------------------------------------------------- 1 | // Helper classes 2 | 3 | .align-right { 4 | text-align: right !important; 5 | } 6 | 7 | .align-center { 8 | text-align: center !important; 9 | } 10 | 11 | .no-select { 12 | user-select: none !important; 13 | } 14 | 15 | .no-margin { 16 | margin: 0 !important; 17 | } 18 | 19 | .clear-left { 20 | clear: left !important; 21 | } 22 | 23 | .clearfix { 24 | clear: both !important; 25 | } 26 | 27 | .no-wrap { 28 | white-space: nowrap !important; 29 | } 30 | -------------------------------------------------------------------------------- /src/scss/_interactive.scss: -------------------------------------------------------------------------------- 1 | // Interation Styles 2 | 3 | @mixin interactive($style) { 4 | 5 | @if $style == "liftAndPushIn" { 6 | box-shadow: 0px 0px 0px 0px transparent; 7 | transition-duration: .18s; 8 | transition-property: box-shadow, background-color, opacity, border, color, margin-left, margin-right, width, min-width, max-width; 9 | transition-timing-function: ease-out; 10 | z-index: 0; 11 | 12 | &:hover { 13 | z-index: 1; 14 | box-shadow: 0px 1px 6px 0px rgba(0, 0, 0, 0.9); 15 | } 16 | &:active, &:focus { 17 | transition-duration: 0.18s; 18 | box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.9); 19 | } 20 | &:focus, &:hover, &:active { 21 | outline: 0; 22 | } 23 | 24 | .bright-style &, &.bright-style { 25 | &:enabled:hover { 26 | box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); 27 | } 28 | &:active, &:focus { 29 | box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.2); 30 | } 31 | } 32 | } 33 | @if $style == "pushInAndCrush" { 34 | box-shadow: 0px 0px 0px 0px transparent, inset 0px 0px 0px 0px transparent; 35 | transition-duration: .18s; 36 | transition-property: box-shadow, text-shadow, transform, border; 37 | transition-timing-function: ease-out; 38 | border: 0; 39 | outline: 0; 40 | 41 | &:hover { 42 | box-shadow: inset 0px 0px 0px 1px #B3B3B3, inset 0px 2px 2px 0px rgba(0, 0, 0, 0.2); 43 | } 44 | &:active, &:focus { 45 | transition-duration: 0.18s; 46 | box-shadow: inset 0 0 0 1px $maker-green, inset 0 2px 2px 0 rgba(0, 0, 0, 0.4) 47 | } 48 | &:focus, &:hover, &:active { 49 | outline: 0; 50 | } 51 | 52 | .bright-style &, &.bright-style { 53 | box-shadow: 0px 0px 0px 0px transparent, inset 0px 0px 0px 0px transparent; 54 | &:hover { 55 | box-shadow: 0px 0px 0px 1px #B3B3B3, inset 0px 2px 2px 0px rgba(0, 0, 0, 0.2); 56 | } 57 | &:active, &:focus { 58 | box-shadow: 0px 0px 0px 1px $maker-green, inset 0px 2px 2px 0px rgba(0, 0, 0, 0.2); 59 | } 60 | } 61 | } 62 | @if $style == "enlightAndSrink" { 63 | background-color: rgba(255, 255, 255, 0); 64 | transition-duration: .18s; 65 | transition-property: background-color, transform, box-shadow; 66 | transition-timing-function: ease-out; 67 | 68 | &:hover { 69 | box-shadow: none; 70 | background-color: rgba(255, 255, 255, 0.15); 71 | } 72 | &:active { 73 | background-color: rgba(255, 255, 255, 0.25); 74 | } 75 | &:focus, &:hover, &:active { 76 | outline: 0; 77 | } 78 | 79 | 80 | .bright-style &, &.bright-style { 81 | background-color: rgba(0, 0, 0, 0); 82 | &:hover { 83 | background-color: rgba(0, 0, 0, 0.10); 84 | box-shadow: none; 85 | } 86 | &:active { 87 | background-color: rgba(0, 0, 0, 0.2); 88 | } 89 | &::after { 90 | transition-duration: .18s; 91 | transition-property: transform; 92 | transition-timing-function: ease-out; 93 | } 94 | &:active::after { 95 | transform: scale(0.7); 96 | } 97 | } 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | background-color: $bg-grey; 7 | } 8 | 9 | body { 10 | // min-width: 768px; 11 | margin: 0; 12 | overflow: auto; 13 | overflow-x: hidden; 14 | 15 | @media only screen and (max-width: 767px) { 16 | overflow-x: auto; 17 | } 18 | } 19 | 20 | #root { 21 | padding: 0; 22 | margin: 0; 23 | display: inline; 24 | } 25 | 26 | .wrapper { 27 | width: 100%; 28 | min-height: 100vh; 29 | position: relative; 30 | padding-left: 65px; 31 | padding-right: 340px; 32 | background-color: $bg-grey; 33 | transition: padding-right .3s ease-in; 34 | 35 | @include respond-to(m) { 36 | padding-left: 0; 37 | padding-right: 0; 38 | } 39 | @include respond-to(xl) { 40 | padding-left: 80px; 41 | padding-right: 0; 42 | } 43 | 44 | .full-width-page &, &.full-width-page { 45 | padding-right: 0; 46 | .right-column { 47 | right: -50%; 48 | @include respond-to(m) { 49 | right: 0px; 50 | bottom: -100%; 51 | } 52 | border-left: none; 53 | } 54 | .main-column { 55 | padding-right: 0; 56 | margin-right: 0; 57 | width: 100%; 58 | } 59 | } 60 | } 61 | 62 | 63 | 64 | .main-column { 65 | z-index: 1; 66 | background-color: $bg-grey; 67 | min-height: 100%; 68 | width: 100%; 69 | display: block; 70 | overflow: hidden; 71 | 72 | & > div { 73 | width: 100%; 74 | padding: 23px 44px 38px; 75 | padding: 1.7em 3.3846153em 3em; 76 | } 77 | 78 | // large Screen layout 79 | @include respond-to(xl) { 80 | width: calc(75% - 20px); 81 | .is-not-connected & { 82 | width: 75%; 83 | } 84 | } 85 | // medium Screen layout 86 | @include respond-to(m) { 87 | min-height: 0; 88 | } 89 | } 90 | 91 | // ALIGNMENT 92 | 93 | .right { 94 | float: right; 95 | } 96 | 97 | 98 | // GRID 99 | 100 | .col, 101 | .row { 102 | border-color: $line-grey; 103 | border-style: solid; 104 | border-width: 0 0 1px 0; 105 | display: inline-block; 106 | width: 100%; 107 | float: left; 108 | 109 | &.row-no-border { 110 | border: 0; 111 | } 112 | } 113 | 114 | .row .col { 115 | border-width: 0; 116 | } 117 | 118 | .col { 119 | padding: 1em 0; 120 | 121 | @include respond-to(xl) { 122 | padding: 1.5em 0; 123 | } 124 | 125 | &.col-extra-padding { 126 | padding-top: 4em; 127 | padding-bottom: 4em; 128 | 129 | @include respond-to(xl) { 130 | padding-top: 5em; 131 | padding-bottom: 5em; 132 | } 133 | } 134 | &.col-no-border { 135 | border: 0; 136 | } 137 | } 138 | 139 | .col-2 { 140 | width: 50%; 141 | float: left; 142 | padding-left: 1em; 143 | 144 | &:first-child { 145 | border-right-width: 1px; 146 | padding-left: 0px; 147 | padding-right: 1em; 148 | } 149 | 150 | @include respond-to(xl) { 151 | padding-left: 1.5em; 152 | &:first-child {; 153 | padding-left: 0px; 154 | padding-right: 1.5em; 155 | } 156 | } 157 | } 158 | 159 | // responsive columns 160 | 161 | @include respond-to(m) { 162 | [foo], .row-2col-m { 163 | display: flex; 164 | flex-direction: row; 165 | 166 | & > * { 167 | padding-left: 1em; 168 | border-bottom-width: 0px; 169 | } 170 | 171 | & > *:first-child { 172 | border-right-width: 1px; 173 | padding-left: 0px; 174 | padding-right: 0; 175 | } 176 | } 177 | } 178 | 179 | .inner-row { 180 | clear: both; 181 | display: block; 182 | margin-top: 1em; 183 | line-height: 3em; 184 | vertical-align: baseline; 185 | } 186 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin flexbox { 2 | display: -webkit-box; 3 | display: -moz-box; 4 | display: -webkit-flex; 5 | display: -ms-flexbox; 6 | display: flex; 7 | } 8 | 9 | @mixin inline-flex { 10 | display: -webkit-inline-box; 11 | display: -moz-inline-box; 12 | display: -webkit-inline-flex; 13 | display: -ms-inline-flexbox; 14 | display: inline-flex; 15 | } 16 | 17 | @mixin transition($transition...) { 18 | -moz-transition: $transition; 19 | -o-transition: $transition; 20 | -webkit-transition: $transition; 21 | transition: $transition; 22 | } 23 | 24 | @mixin filter($filter-type, $filter-amount) { 25 | -webkit-filter: $filter-type+unquote('(#{$filter-amount})'); 26 | -moz-filter: $filter-type+unquote('(#{$filter-amount})'); 27 | -ms-filter: $filter-type+unquote('(#{$filter-amount})'); 28 | -o-filter: $filter-type+unquote('(#{$filter-amount})'); 29 | filter: $filter-type+unquote('(#{$filter-amount})'); 30 | } 31 | -------------------------------------------------------------------------------- /src/scss/_text-styles.scss: -------------------------------------------------------------------------------- 1 | p.error { 2 | clear: left; 3 | color: $error-red; 4 | display: block; 5 | margin-top: 5px; 6 | margin-bottom: 0px; 7 | } 8 | 9 | .value { 10 | display: inline-block; 11 | color: $white; 12 | user-select: text; 13 | position: relative; 14 | 15 | &.block { 16 | display: block; 17 | } 18 | 19 | .unit { 20 | margin-left: 0.4em; 21 | font-weight: 300; 22 | color: $text-grey; 23 | user-select: none; 24 | } 25 | } 26 | 27 | 28 | ul { 29 | margin: 1em 0; 30 | padding: 0; 31 | list-style-type: none; 32 | line-height: inherit; 33 | 34 | &.expandable { 35 | line-height: 2; 36 | li { 37 | display: block; 38 | a { 39 | margin-left: 0.5em; 40 | } 41 | &:before { 42 | content: '+'; 43 | display: inline-block; 44 | font-weight: 600; 45 | } 46 | } 47 | } 48 | 49 | &.bullets { 50 | line-height: 2; 51 | li { 52 | display: block; 53 | a { 54 | margin-left: 0.5em; 55 | } 56 | &:before { 57 | content: '\2219'; 58 | display: inline-block; 59 | font-weight: 600; 60 | } 61 | } 62 | } 63 | 64 | 65 | } 66 | 67 | .separator { 68 | font-weight: 300; 69 | color: $text-grey; 70 | } 71 | -------------------------------------------------------------------------------- /src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | // Load CSS presets 2 | @import '_normalize'; 3 | 4 | // Libraries 5 | @import '_colors'; 6 | @import '_interactive'; 7 | @import '_breakpoints'; 8 | @import '_typography'; 9 | @import '_text-styles'; 10 | @import '_buttons'; 11 | @import '_forms'; 12 | @import '_layout'; 13 | @import '_mixins'; 14 | @import '_helpers'; 15 | @import '_general'; 16 | 17 | // Components 18 | @import '_cp-menu-bar'; 19 | @import '_cp-right-column'; 20 | @import '_cp-dialog'; 21 | @import '_cp-charts'; 22 | @import '_cp-notify'; 23 | @import '_cp-cup-history'; 24 | @import '_cp-steps'; 25 | @import '_cp-wizard'; 26 | @import '_cp-tooltip'; 27 | @import '_cp-inline-notification'; 28 | @import '_cp-slider'; 29 | @import '_cp-landing'; 30 | @import '_cp-dropdown-menu'; 31 | @import '_cp-loading-spinner'; 32 | @import '_cp-range-slider'; 33 | @import '_cp-modal'; 34 | @import '_cp-toggle-switch'; 35 | @import '_cp-react-select'; 36 | @import '_cp-markdown'; 37 | @import '_cp-cup'; 38 | @import '_cp-migrate-legacy-cups'; 39 | @import '_cp-not-found'; 40 | @import '_cp-help'; 41 | 42 | // States 43 | @import '_forecast-mode'; 44 | -------------------------------------------------------------------------------- /src/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentUrl": "https://content.makerfoundation.com/content/dai-dashboard", 3 | "chain": { 4 | "kovan": { 5 | "nodeURL": "https://kovan.infura.io/v3/078596535bf243c6996d2ac196563d49", 6 | "saiValuesAggregator": "0x040abcb09a5b46f9a5ebed320abe074e6e626cc5", 7 | "otc": "0xe325acb9765b02b8b418199bf9650972299235f4", 8 | "fromBlock": 5216602, 9 | "service": "https://sai-kovan.makerfoundation.com/v1", 10 | "serviceTimeout": 5000, 11 | "chart": false, 12 | "proxyRegistry": "0x64a436ae831c1672ae81f674cab8b6775df3475c", 13 | "saiProxyCreateAndExecute": "0x96fc005a8ba82b84b11e0ff211a2a1362f107ef0", 14 | "tub": "0xa71937147b55deb8a530c7229c442fd3f31b7db2" 15 | }, 16 | "main": { 17 | "nodeURL": "https://mainnet.infura.io/v3/078596535bf243c6996d2ac196563d49", 18 | "saiValuesAggregator": "0x83f6ed3d377674186d8898a89d9032216e07e659", 19 | "otc": "0x794e6e91555438afc3ccf1c5076a74f42133d08d", 20 | "fromBlock": 4752013, 21 | "service": "https://sai-mainnet.makerfoundation.com/v1", 22 | "serviceTimeout": 5000, 23 | "chart": false, 24 | "proxyRegistry": "0x4678f0a6958e4d2bc4f1baf7bc52e8f3564f3fe4", 25 | "saiProxyCreateAndExecute": "0x526af336d614ade5cc252a407062b8861af998f5", 26 | "tub": "0x448a5065aebb8e423f0896e6c5d525c040f59af3" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/stores/Content.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import {observable} from "mobx"; 3 | import axios from "axios"; 4 | import ReactTooltip from "react-tooltip"; 5 | import { compiler } from 'markdown-to-jsx'; 6 | 7 | // Utils 8 | import {WAD, formatNumber, fromWei, toWei} from "../utils/helpers"; 9 | 10 | // Settings 11 | import * as settings from "../settings"; 12 | 13 | // JSON Content 14 | import contentTerms from "../assets/json/terms.json"; 15 | 16 | export default class ContentStore { 17 | @observable content = { faq: {}, tooltips: {}, notifications: {} } 18 | @observable contentLoaded = false 19 | @observable showNotification = false 20 | 21 | constructor(rootStore) { 22 | this.rootStore = rootStore; 23 | axios.get(settings.contentUrl) 24 | .then(res => { 25 | this.content = res.data || null; 26 | this.contentLoaded = true; 27 | this.showNotification = false;//!localStorage.getItem(`StabilityFeeChangeAlertClosed-${this.stabilityFeeMarkdown()}`); 28 | 29 | // General notifications 30 | this.content.notifications = {}; 31 | for (let key in this.content.faq) { 32 | if (key.substr(0, 13) === 'notification-' && key.substr(-3) === '-en') 33 | this.content.notifications[key] = { 34 | markdown: this.content.faq[key].markdown, 35 | content: compiler(this.content.faq[key].markdown), 36 | show: !localStorage.getItem(`NotificationClosed-${key}-${this.content.faq[key].markdown}`) 37 | }; 38 | } 39 | 40 | ReactTooltip.rebuild(); 41 | }); 42 | } 43 | 44 | replaceVariables = text => { 45 | return text.replace(/\$\{(.+?)\}/g, (match, capture) => { 46 | switch(capture) { 47 | case "stability_fee": 48 | return formatNumber(toWei(fromWei(this.rootStore.system.tub.fee).pow(60 * 60 * 24 * 365)).times(100).minus(toWei(100)), 1) + "%"; 49 | case "liquidation_penalty": 50 | return formatNumber(this.rootStore.system.tub.axe.minus(WAD).times(100)) + "%"; 51 | case "min_collateralization_ratio": 52 | return formatNumber(this.rootStore.system.tub.mat.times(100)) + "%"; 53 | default: 54 | return "?"; 55 | } 56 | }); 57 | } 58 | 59 | getTooltip = tipKey => { 60 | if (this.content.tooltips && this.content.tooltips.hasOwnProperty(tipKey)) { 61 | let tipText = this.content.tooltips[tipKey]["text"].replace(/\n|\\n/g, "
"); 62 | // Does the tooltip text contain variables that need replacing? 63 | if (/\$\{.+?\}/.test(tipText)) tipText = this.replaceVariables(tipText); 64 | return tipText; 65 | } 66 | else return ""; 67 | // Solution for having a More Info link rendered using JSX 68 | // Need to submit a PR to react-tooltip or create a fork to add support for this 69 | // if (contentTooltips.hasOwnProperty(tipKey)) { 70 | // const textLines = contentTooltips[tipKey]["text"].split("\n"); 71 | // return ( 72 | // 73 | // { textLines.map((item, i) => { return {item}{ i !== textLines.length - 1 &&
}
})} 74 | // { " " } 75 | // { contentTooltips[tipKey]["more-info"] && More Info } 76 | //
77 | // ); 78 | // } 79 | } 80 | 81 | stabilityFeeMarkdown = () => (this.getHelpItem('stability-fee-information') || {}).markdown 82 | stabilityFeeContent = () => this.stabilityFeeMarkdown() && compiler(this.stabilityFeeMarkdown()); 83 | shouldShowStabilityFeeAlert = () => { 84 | return this.showNotification && !!this.stabilityFeeMarkdown 85 | } 86 | hideStabilityFeeContent = () => { 87 | localStorage.setItem(`StabilityFeeChangeAlertClosed-${this.stabilityFeeMarkdown()}`, true); 88 | this.showNotification = false; 89 | } 90 | 91 | getGeneralNotifications = () => this.content.notifications; 92 | hideGeneralNotification = key => { 93 | localStorage.setItem(`NotificationClosed-${key}-${this.content.notifications[key].markdown}`, true); 94 | this.content.notifications[key].show = false; 95 | } 96 | shouldShowGeneralNotifications = () => { 97 | for(let key in this.content.notifications) { 98 | if (this.content.notifications[key].show) return true; 99 | } 100 | return false; 101 | } 102 | 103 | getHelpItem = helpId => { 104 | return this.content.faq[helpId] || null; 105 | } 106 | 107 | getTerms = () => { 108 | return contentTerms.markdown || null; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/stores/Dialog.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import {observable} from "mobx"; 3 | 4 | export default class DialogStore { 5 | @observable show = false; 6 | @observable method = null; 7 | @observable cupId = false; 8 | @observable error = ""; 9 | @observable warning = ""; 10 | 11 | constructor(rootStore) { 12 | this.rootStore = rootStore; 13 | } 14 | 15 | reset = () => { 16 | this.show = false; 17 | this.method = null; 18 | this.cupId = false; 19 | this.error = ""; 20 | this.warning = ""; 21 | } 22 | 23 | handleOpenDialog = e => { 24 | e.preventDefault(); 25 | this.method = e.currentTarget.getAttribute("data-method"); 26 | this.cupId = e.currentTarget.getAttribute("data-cup") ? e.currentTarget.getAttribute("data-cup") : false; 27 | this.show = true; 28 | } 29 | 30 | handleCloseDialog = e => { 31 | e.preventDefault(); 32 | this.reset(); 33 | } 34 | 35 | setError = e => { 36 | this.error = e; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/stores/Profile.jsx: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import {observable} from "mobx"; 3 | 4 | // Utils 5 | import * as blockchain from "../utils/blockchain"; 6 | import * as daisystem from "../utils/dai-system"; 7 | 8 | // Settings 9 | import * as settings from "../settings"; 10 | 11 | export default class ProfileStore { 12 | @observable proxy = -1; 13 | 14 | constructor(rootStore) { 15 | this.rootStore = rootStore; 16 | } 17 | 18 | setProxyFromChain = (callbacks = null) => { 19 | return new Promise((resolve, reject) => { 20 | console.debug("Checking proxy...") 21 | daisystem.getContracts(settings.chain[this.rootStore.network.network].proxyRegistry, this.rootStore.network.defaultAccount).then(r => { 22 | if (r && r[2] && this.rootStore.transactions.setLatestBlock(r[0].toNumber())) { 23 | this.setProxy(r[2]); 24 | callbacks && this.rootStore.transactions.executeCallbacks(callbacks); 25 | resolve(r[2]); 26 | } else { 27 | // We force to check again until we get the result 28 | console.debug("Proxy still not found, trying again in 3 seconds..."); 29 | setTimeout(() => this.setProxyFromChain(callbacks), 3000); 30 | reject(false); 31 | } 32 | }, () => { 33 | console.debug("Error occurred, trying again in 3 seconds..."); 34 | setTimeout(() => this.setProxyFromChain(callbacks), 3000); 35 | reject(false); 36 | }); 37 | }); 38 | } 39 | 40 | setProxy = proxy => { 41 | this.proxy = proxy !== "0x0000000000000000000000000000000000000000" ? proxy : null; 42 | blockchain.loadObject("dsproxy", this.proxy, "proxy"); 43 | console.debug("Found proxy:", this.proxy); 44 | } 45 | 46 | checkProxy = callbacks => { 47 | if (this.proxy) { 48 | this.rootStore.transactions.executeCallbacks(callbacks); 49 | } else { 50 | const title = "Create Proxy"; 51 | const params = {value: 0}; 52 | if (this.rootStore.network.hw.active) { 53 | params.gas = 1000000; 54 | } 55 | this.rootStore.transactions.askPriceAndSend(title, blockchain.objects.proxyRegistry.build, [], params, [["profile/setProxyFromChain", callbacks]]); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/stores/Root.jsx: -------------------------------------------------------------------------------- 1 | // Stores 2 | import DialogStore from "./Dialog"; 3 | import NetworkStore from "./Network"; 4 | import ProfileStore from "./Profile"; 5 | import SystemStore from "./System"; 6 | import TransactionsStore from "./Transactions"; 7 | import ContentStore from "./Content"; 8 | 9 | // Utils 10 | import * as blockchain from "../utils/blockchain"; 11 | import * as daisystem from "../utils/dai-system"; 12 | import {isAddress} from "../utils/helpers"; 13 | 14 | // Settings 15 | import * as settings from "../settings"; 16 | 17 | class RootStore { 18 | constructor() { 19 | this.dialog = new DialogStore(this); 20 | this.network = new NetworkStore(this); 21 | this.profile = new ProfileStore(this); 22 | this.system = new SystemStore(this); 23 | this.transactions = new TransactionsStore(this); 24 | this.content = new ContentStore(this); 25 | 26 | this.interval = null; 27 | this.intervalAggregatedValues = null; 28 | } 29 | 30 | setVariablesInterval = () => { 31 | if (!this.interval) { 32 | this.interval = setInterval(() => { 33 | console.debug("Running variables interval"); 34 | this.transactions.setStandardGasPrice(); 35 | this.transactions.checkPendingTransactions(); 36 | }, 10000); 37 | } 38 | 39 | if (!this.intervalAggregatedValues) { 40 | this.intervalAggregatedValues = setInterval(() => { 41 | console.debug("Running setAggregatedValues interval"); 42 | this.system.setAggregatedValues(); 43 | }, 5000); 44 | } 45 | } 46 | 47 | _loadContracts = () => { 48 | daisystem.getContracts(settings.chain[this.network.network].proxyRegistry, this.network.defaultAccount).then(r => { 49 | if (r && r[0] && r[1] && isAddress(r[1][0]) && isAddress(r[1][1])) { 50 | const block = r[0].toNumber(); 51 | // Make the contracts addresses load a bit more flexible, just checking the node request is bringing data no older than 5 blocks 52 | if (block > this.transactions.latestBlock - 5) { 53 | this.transactions.setLatestBlock(block); 54 | // Set profile proxy and system contracts 55 | this.profile.setProxy(r[2]); 56 | this.system.init(r[1][0], r[1][1], r[1][2], r[1][3], r[1][4], r[1][5], r[1][6], r[1][7], r[1][8], r[1][9], r[1][10], r[1][11]); 57 | this.network.stopLoadingAddress(); 58 | this.transactions.setStandardGasPrice(); 59 | 60 | this.setVariablesInterval(); 61 | } else { 62 | console.debug(`Error loading contracts (latest block ${this.transactions.latestBlock}, request one: ${block}, trying again...`); 63 | this.transactions.addAmountCheck(); 64 | setTimeout(this._loadContracts, 2000); 65 | } 66 | } else { 67 | console.debug("Error loading contracts, trying again..."); 68 | this.transactions.addAmountCheck(); 69 | setTimeout(this._loadContracts, 2000); 70 | } 71 | }, () => { 72 | console.debug("Error loading contracts, trying again..."); 73 | setTimeout(this._loadContracts, 2000); 74 | }); 75 | } 76 | 77 | loadContracts = () => { 78 | if (this.network.network && !this.network.stopIntervals) { 79 | blockchain.resetFilters(true); 80 | if (typeof this.interval !== "undefined") clearInterval(this.interval); 81 | this.dialog.reset(); 82 | this.system.reset(); 83 | this.transactions.reset(); 84 | 85 | // Check actual block number from 3 different requests (workaround to try to avoid outdated nodes behind load balancer) 86 | const blockPromises = []; 87 | for (let i = 0; i < 3; i++) { 88 | blockPromises.push(blockchain.getBlockNumber()); 89 | } 90 | 91 | Promise.all(blockPromises).then(r => { 92 | r.forEach(block => this.transactions.setLatestBlock(block)); // Will set the maximum value 93 | 94 | blockchain.loadObject("proxyregistry", settings.chain[this.network.network].proxyRegistry, "proxyRegistry"); 95 | blockchain.loadObject("saivaluesaggregator", settings.chain[this.network.network].saiValuesAggregator, "saiValuesAggregator"); 96 | 97 | this._loadContracts(); 98 | }) 99 | } 100 | } 101 | } 102 | 103 | const store = new RootStore(); 104 | export default store; 105 | -------------------------------------------------------------------------------- /src/utils/address-generator.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import ethUtil from "ethereumjs-util"; 3 | import HDKey from "hdkey"; 4 | 5 | export default class AddressGenerator { 6 | constructor(data) { 7 | this.hdk = new HDKey(); 8 | this.hdk.publicKey = new Buffer(data.publicKey, "hex"); 9 | this.hdk.chainCode = new Buffer(data.chainCode, "hex"); 10 | } 11 | 12 | getAddressString = index => { 13 | let derivedKey = this.hdk.derive(`m/${index}`); 14 | let address = ethUtil.publicToAddress(derivedKey.publicKey, true); 15 | let addressString = "0x" + address.toString("hex"); 16 | return addressString; 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/analytics.js: -------------------------------------------------------------------------------- 1 | import mixpanel from 'mixpanel-browser'; 2 | import ReactGA from 'react-ga'; 3 | 4 | const env = process.env.NODE_ENV === 'production' ? 'prod' : 'test'; 5 | const config = { 6 | test: { 7 | userSnap: { 8 | token: 'def2ae23-a9a8-4f11-85e6-5346cb86d4f2', 9 | config: { 10 | fields: { 11 | email: null 12 | } 13 | } 14 | }, 15 | mixpanel: { 16 | token: '4ff3f85397ffc3c6b6f0d4120a4ea40a', 17 | config: { debug: true, ip: false } 18 | }, 19 | gaTrackingId: 'UA-128164213-2' 20 | }, 21 | prod: { 22 | mixpanel: { 23 | token: 'a030d8845e34bfdc11be3d9f3054ad67', 24 | config: { ip: false } 25 | }, 26 | gaTrackingId: 'UA-128164213-1', 27 | userSnap: { 28 | token: 'def2ae23-a9a8-4f11-85e6-5346cb86d4f2', 29 | config: { 30 | fields: { 31 | email: null 32 | } 33 | } 34 | } 35 | } 36 | }[env]; 37 | 38 | export const mixpanelInit = () => { 39 | console.debug( 40 | `[Mixpanel] Tracking initialized for ${env} env using ${ 41 | config.mixpanel.token 42 | }` 43 | ); 44 | mixpanel.init(config.mixpanel.token, config.mixpanel.config); 45 | mixpanel.track('Pageview', { product: 'scd-cdp-portal' }); 46 | }; 47 | 48 | export const mixpanelIdentify = (id, props = null) => { 49 | if (typeof mixpanel.config === 'undefined') return; 50 | console.debug( 51 | `[Mixpanel] Identifying as ${id} ${props && props.wallet ? `using wallet ${props.wallet}` : ''}` 52 | ); 53 | mixpanel.identify(id); 54 | if (props) mixpanel.people.set(props); 55 | }; 56 | 57 | export const userSnapInit = () => { 58 | // already injected 59 | if (document.getElementById('usersnap-script')) return; 60 | 61 | window.onUsersnapLoad = function(api) { 62 | api.init(config.userSnap.config); 63 | window.Usersnap = api; 64 | }; 65 | 66 | const scriptUrl = `//api.usersnap.com/load/${ 67 | config.userSnap.token 68 | }.js?onload=onUsersnapLoad`; 69 | const script = document.createElement('script'); 70 | script.id = 'usersnap-script'; 71 | script.src = scriptUrl; 72 | script.async = true; 73 | 74 | document.getElementsByTagName('head')[0].appendChild(script); 75 | }; 76 | 77 | export const gaInit = () => { 78 | console.debug( 79 | `[GA] Tracking initialized for ${env} env using ${config.gaTrackingId}` 80 | ); 81 | ReactGA.initialize(config.gaTrackingId); 82 | }; 83 | -------------------------------------------------------------------------------- /src/utils/web3.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | import Web3 from "web3"; 3 | import * as Web3ProviderEngine from "web3-provider-engine/dist/es5"; 4 | import * as RpcSource from "web3-provider-engine/dist/es5/subproviders/rpc"; 5 | import Transport from "@ledgerhq/hw-transport-u2f"; 6 | import checkIsMobile from "ismobilejs"; 7 | 8 | // Utils 9 | import LedgerSubProvider from "./ledger-subprovider"; 10 | import TrezorSubProvider from "./trezor-subprovider"; 11 | 12 | // Settings 13 | import * as settings from "../settings"; 14 | 15 | export const getWebClientProviderName = () => { 16 | if (window.imToken) 17 | return "imtoken"; 18 | 19 | if (window.ethereum && window.ethereum.isStatus) 20 | return "status"; 21 | 22 | if (!window.web3 || typeof window.web3.currentProvider === "undefined") 23 | return ""; 24 | 25 | if (window.web3.currentProvider.isAlphaWallet) 26 | return "alphawallet"; 27 | 28 | if (window.activeProvider && window.activeProvider.isWalletLink) 29 | return "walletlink"; 30 | 31 | if (window.web3.currentProvider.isMetaMask && checkIsMobile.any) 32 | return "metamask-mobile"; 33 | 34 | if (window.web3.currentProvider.isMetaMask) 35 | return "metamask"; 36 | 37 | if (window.web3.currentProvider.isTrust) 38 | return "trust"; 39 | 40 | if (window.web3.currentProvider.isQbao) 41 | return "qbao"; 42 | 43 | if (window.web3.currentProvider.isBitpie) 44 | return "bitpie"; 45 | 46 | if (typeof window.SOFA !== "undefined") 47 | return "coinbase"; 48 | 49 | if (typeof window.__CIPHER__ !== "undefined") 50 | return "cipher"; 51 | 52 | if (window.web3.currentProvider.constructor.name === "EthereumProvider") 53 | return "mist"; 54 | 55 | if (window.web3.currentProvider.constructor.name === "Web3FrameProvider") 56 | return "parity"; 57 | 58 | if (window.web3.currentProvider.host && window.web3.currentProvider.host.indexOf("infura") !== -1) 59 | return "infura"; 60 | 61 | if (window.web3.currentProvider.host && window.web3.currentProvider.host.indexOf("localhost") !== -1) 62 | return "localhost"; 63 | 64 | return "other"; 65 | }; 66 | 67 | class Web3Extended extends Web3 { 68 | stop = () => { 69 | this.reset(); 70 | if (this.currentProvider && typeof this.currentProvider.stop === "function") { 71 | this.currentProvider.stop(); 72 | } 73 | } 74 | 75 | setHWProvider = (device, network, path, accountsOffset = 0, accountsLength = 1) => { 76 | this.stop(); 77 | return new Promise(async (resolve, reject) => { 78 | try { 79 | const networkId = network === "main" ? 1 : (network === "kovan" ? 42 : ""); 80 | this.setProvider(new Web3ProviderEngine()); 81 | const hwWalletSubProvider = device === "ledger" 82 | ? LedgerSubProvider(async () => await Transport.create(), {networkId, path, accountsOffset, accountsLength}) 83 | : TrezorSubProvider({networkId, path, accountsOffset, accountsLength}); 84 | this.currentProvider.name = device; 85 | this.currentProvider.addProvider(hwWalletSubProvider); 86 | this.currentProvider.addProvider(new RpcSource({rpcUrl: settings.chain[network].nodeURL})); 87 | this.currentProvider.start(); 88 | this.useLogs = false; 89 | resolve(true); 90 | } catch(e) { 91 | reject(e); 92 | } 93 | }); 94 | } 95 | 96 | setWebClientWeb3 = (specificProvider = null) => { 97 | this.stop(); 98 | return new Promise(async (resolve, reject) => { 99 | try { 100 | if (specificProvider) { 101 | try { 102 | if (typeof specificProvider.enable === 'function') { 103 | await specificProvider.enable(); 104 | } 105 | resolve(specificProvider); 106 | } catch (error) { 107 | reject(new Error("User denied account access")); 108 | } 109 | } else if (window.web3 || window.ethereum) { 110 | try { 111 | let provider; 112 | if (window.ethereum) { 113 | await window.ethereum.enable(); 114 | provider = window.ethereum; 115 | } else { 116 | provider = window.web3.currentProvider; 117 | } 118 | resolve(provider); 119 | } catch (error) { 120 | reject(new Error("User denied account access")); 121 | } 122 | } else { 123 | reject(new Error("No client")); 124 | } 125 | } catch(e) { 126 | reject(e); 127 | } 128 | }); 129 | } 130 | 131 | setWebClientProvider = provider => { 132 | return new Promise(async (resolve, reject) => { 133 | try { 134 | this.setProvider(provider); 135 | this.useLogs = false; 136 | window.activeProvider = provider; 137 | this.currentProvider.name = getWebClientProviderName(); 138 | resolve(true); 139 | } catch (error) { 140 | reject(new Error("Error setting provider")); 141 | } 142 | }); 143 | } 144 | } 145 | 146 | const web3 = new Web3Extended(); 147 | window.web3Provider = web3; 148 | 149 | export default web3; 150 | --------------------------------------------------------------------------------