├── .editorconfig ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── env.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── images.d.ts ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json ├── og_image.png └── og_image_corgi.png ├── scripts ├── build-preset │ ├── corgi.sh │ └── mainnet.sh ├── build.js └── start.js ├── src ├── _globals.scss ├── _mixins.scss ├── _variables.scss ├── components │ ├── asset │ │ ├── AssetDetails │ │ │ ├── AssetDetails.scss │ │ │ └── AssetDetails.tsx │ │ ├── AssetList │ │ │ ├── AssetList.scss │ │ │ └── AssetList.tsx │ │ └── AssetOwners │ │ │ ├── AssetOwners.scss │ │ │ ├── AssetOwners.tsx │ │ │ └── AssetOwnersChart.tsx │ ├── block │ │ ├── BlockDetails │ │ │ ├── BlockDetails.scss │ │ │ └── BlockDetails.tsx │ │ └── BlockList │ │ │ ├── BlockList.scss │ │ │ └── BlockList.tsx │ ├── error │ │ └── Error │ │ │ ├── Error.scss │ │ │ ├── Error.tsx │ │ │ └── img │ │ │ └── error-monster.png │ ├── footer │ │ ├── Footer.scss │ │ ├── Footer.tsx │ │ └── img │ │ │ ├── codechain-icon.png │ │ │ ├── codechain-icon.svg │ │ │ └── logo.png │ ├── header │ │ ├── Header │ │ │ ├── Header.scss │ │ │ ├── Header.tsx │ │ │ └── img │ │ │ │ ├── logo-explorer.png │ │ │ │ └── logo.png │ │ └── Search │ │ │ ├── Search.scss │ │ │ └── Search.tsx │ ├── home │ │ ├── LatestBlocks │ │ │ ├── LatestBlocks.scss │ │ │ └── LatestBlocks.tsx │ │ ├── LatestTransactions │ │ │ ├── LatestTransactions.scss │ │ │ └── LatestTransactions.tsx │ │ └── Summary │ │ │ ├── Summary.scss │ │ │ ├── Summary.tsx │ │ │ ├── WeeklyChart.tsx │ │ │ └── img │ │ │ └── empty.png │ ├── platformAddress │ │ └── AccountDetails │ │ │ ├── AccountDetails.scss │ │ │ └── AccountDetails.tsx │ ├── status │ │ ├── ChainInfo │ │ │ ├── ChainInfo.scss │ │ │ └── ChainInfo.tsx │ │ ├── ExplorerInfo │ │ │ ├── ExplorerInfo.scss │ │ │ └── ExplorerInfo.tsx │ │ ├── NodeStatus │ │ │ ├── NodeStatus.scss │ │ │ └── NodeStatus.tsx │ │ ├── SyncStatus │ │ │ ├── SyncStatus.scss │ │ │ └── SyncStatus.tsx │ │ └── TransactionChart │ │ │ └── TransactionChart.tsx │ ├── transaction │ │ ├── BlockTransactionList │ │ │ ├── BlockTransactionList.scss │ │ │ ├── BlockTransactionList.tsx │ │ │ └── BlockTransactionListPage.tsx │ │ ├── TransactionDetails │ │ │ ├── CommonDetails │ │ │ │ └── CommonDetails.tsx │ │ │ ├── DetailsByType │ │ │ │ ├── AssetMintDetails │ │ │ │ │ └── AssetMintDetails.tsx │ │ │ │ ├── AssetTransferDetails │ │ │ │ │ └── AssetTransferDetails.tsx │ │ │ │ ├── ChangeAssetSchemeDetails │ │ │ │ │ └── ChangeAssetSchemeDetails.tsx │ │ │ │ ├── CreateShardDetails │ │ │ │ │ └── CreateShardDetails.tsx │ │ │ │ ├── CustomDetails │ │ │ │ │ └── CustomDetails.tsx │ │ │ │ ├── DetailsByType.tsx │ │ │ │ ├── IncreaseAssetSupplyDetails │ │ │ │ │ └── IncreaseAssetSupplyDetails.tsx │ │ │ │ ├── PayDetails │ │ │ │ │ └── PayDetails.tsx │ │ │ │ ├── RemoveDetails │ │ │ │ │ └── RemoveDetails.tsx │ │ │ │ ├── SetRegularKeyDetails │ │ │ │ │ └── SetRegularKeyDetails.tsx │ │ │ │ ├── SetShardOwnersDetails │ │ │ │ │ └── SetShardOwnersDetails.tsx │ │ │ │ ├── SetShardUsersDetails │ │ │ │ │ └── SetShardUsersDetails.tsx │ │ │ │ ├── StoreDetails │ │ │ │ │ └── StoreDetails.tsx │ │ │ │ ├── UnwrapCCCDetails │ │ │ │ │ └── UnwrapCCCDetails.tsx │ │ │ │ └── WrapCCCDetails │ │ │ │ │ └── WrapCCCDetails.tsx │ │ │ ├── MoreInfo │ │ │ │ ├── AssetOutput │ │ │ │ │ └── AssetOutput.tsx │ │ │ │ ├── AssetTransferInputs │ │ │ │ │ └── AssetTransferInputs.tsx │ │ │ │ ├── AssetTransferOrders │ │ │ │ │ └── AssetTransferOrders.tsx │ │ │ │ ├── AssetTransferOutputs │ │ │ │ │ └── AssetTransferOutputs.tsx │ │ │ │ └── MoreInfo.tsx │ │ │ ├── TransactionDetails.scss │ │ │ └── TransactionDetails.tsx │ │ ├── TransactionList │ │ │ ├── TransactionList.scss │ │ │ ├── TransactionList.tsx │ │ │ └── img │ │ │ │ └── arrow.png │ │ ├── TransactionListItems │ │ │ └── TransactionListItems.tsx │ │ └── TransactionSummary │ │ │ ├── AssetIcon.tsx │ │ │ ├── TransactionSummary.scss │ │ │ └── TransactionSummary.tsx │ └── util │ │ ├── CommaNumberString │ │ └── CommaNumberString.tsx │ │ ├── CopyButton │ │ ├── CopyButton.scss │ │ └── CopyButton.tsx │ │ ├── DataSet │ │ └── DataSet.tsx │ │ ├── DataTable │ │ └── DataTable.tsx │ │ ├── HealthChecker │ │ ├── HealthChecker.scss │ │ └── HealthChecker.tsx │ │ ├── HexString │ │ └── HexString.tsx │ │ ├── ImageLoader │ │ └── ImageLoader.tsx │ │ ├── ScrollToTop │ │ └── ScrollToTop.tsx │ │ ├── StatusBadge │ │ ├── StatusBadge.scss │ │ └── StatusBadge.tsx │ │ └── TypeBadge │ │ ├── TypeBadge.scss │ │ └── TypeBadge.tsx ├── index.scss ├── index.tsx ├── logo.svg ├── pages │ ├── Asset │ │ ├── Asset.scss │ │ └── Asset.tsx │ ├── AssetTransferAddress │ │ ├── AssetTransferAddress.scss │ │ └── AssetTransferAddress.tsx │ ├── Block │ │ ├── Block.scss │ │ └── Block.tsx │ ├── Blocks │ │ ├── Blocks.scss │ │ └── Blocks.tsx │ ├── Home │ │ ├── BlockCapacityUsageChart.tsx │ │ ├── BlockCreationTimeChart.tsx │ │ ├── FeeStatusChart.tsx │ │ ├── Home.scss │ │ ├── Home.tsx │ │ └── TransactionsCountByTypeChart.tsx │ ├── NotFound │ │ ├── NotFound.scss │ │ └── NotFound.tsx │ ├── PendingTransactions │ │ ├── PendingTransactions.scss │ │ └── PendingTransactions.tsx │ ├── PlatformAddress │ │ ├── PlatformAddress.scss │ │ └── PlatformAddress.tsx │ ├── Status │ │ ├── Status.scss │ │ └── Status.tsx │ ├── Transaction │ │ ├── Transaction.scss │ │ └── Transaction.tsx │ └── Transactions │ │ ├── Transactions.scss │ │ └── Transactions.tsx ├── redux │ ├── actions.ts │ └── store.ts ├── register_service_worker.ts ├── request │ ├── ApiRequest.tsx │ ├── RequestAssetInfosByName.tsx │ ├── RequestAssetScheme.tsx │ ├── RequestAssetTransferAddressUTXO.tsx │ ├── RequestAssetTypeUTXO.tsx │ ├── RequestBlock.tsx │ ├── RequestBlockNumber.tsx │ ├── RequestBlockTransactions.tsx │ ├── RequestBlocks.tsx │ ├── RequestCodeChainStatus.tsx │ ├── RequestDailyLogs.ts │ ├── RequestFeeStats.tsx │ ├── RequestIndexerVersion.tsx │ ├── RequestNodeStatus.tsx │ ├── RequestPendingTransactions.tsx │ ├── RequestPing.tsx │ ├── RequestPlatformAddressAccount.tsx │ ├── RequestServerTime.tsx │ ├── RequestSyncStatus.tsx │ ├── RequestTotalBlockCount.tsx │ ├── RequestTotalPlatformBlockCount.tsx │ ├── RequestTransaction.tsx │ ├── RequestTransactions.tsx │ ├── RequestWeeklyLogs.ts │ └── index.tsx └── utils │ ├── Address.ts │ ├── Asset.ts │ ├── BalanceHistory.ts │ ├── Metadata.ts │ ├── Time.ts │ └── Transactions.ts ├── tsconfig.json ├── tsconfig.test.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | insert_final_newline = true 7 | 8 | [*.yml] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [{src,test}/**.{ts,tsx,json,js,jsx}] 13 | trim_trailing_whitespace = true 14 | indent_style = space 15 | indent_size = 4 16 | 17 | # npm inserts a final newline. 18 | [package.json] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # dotenv 24 | .env 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | before_install: 5 | - npm install -g yarn 6 | install: 7 | - yarn install 8 | script: 9 | - yarn run lint 10 | - yarn run build 11 | cache: yarn 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeChain Explorer [![Gitter](https://badges.gitter.im/CodeChain-io.svg)](https://gitter.im/CodeChain-io/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/CodeChain-io/codechain-explorer.svg?branch=master)](https://travis-ci.org/CodeChain-io/codechain-explorer) 2 | 3 | CodeChain explorer is a simple, easy to use, open-source visualization tool for viewing activity on the underlying blockchain network. 4 | 5 | ## Table of Contents 6 | 7 | - [Install](https://github.com/CodeChain-io/codechain-explorer#install) 8 | - [Running for development](https://github.com/CodeChain-io/codechain-explorer#running-for-development) 9 | - [Running for production](https://github.com/CodeChain-io/codechain-explorer#running-for-production) 10 | 11 | ## Install 12 | 13 | ### Download 14 | 15 | Download CodeChain-explorer's code from the GitHub repository 16 | 17 | ``` 18 | # git clone git@github.com:kodebox-io/codechain-explorer.git 19 | # cd codechain-explorer 20 | ``` 21 | 22 | ### Install package 23 | 24 | Use yarn package manager to install packages 25 | 26 | ``` 27 | # yarn install 28 | ``` 29 | 30 | ## Before starting 31 | 32 | ### Dependency 33 | 34 | - Get [CodeChain-indexer](https://github.com/CodeChain-io/codechain-indexer) ready for indexing block data and running the API server 35 | 36 | ### Running for development 37 | 38 | -The explorer will run at http://localhost:3000 39 | 40 | ``` 41 | # yarn run start 42 | 43 | // You can chage the port of test server and the host URL with environment variables. 44 | # PORT=3000 REACT_APP_SERVER_HOST=http://127.0.0.1:8081 yarn run start 45 | ``` 46 | 47 | ### Running for production 48 | 49 | #### Build 50 | 51 | Build CodeChain-explorer with the following script. You can get optimized and uglified build code. It will be located in the "/build" directory 52 | 53 | ``` 54 | # yarn run build 55 | ``` 56 | 57 | - You can change the server host using an environment variable 58 | 59 | ``` 60 | # REACT_APP_SERVER_HOST=http://127.0.0.1:8080 yarn run build 61 | ``` 62 | 63 | ## Custom Configuration 64 | 65 | ### Build 66 | 67 | | | Default | Options | Description | 68 | | ----------------------------- | --------------------- | ------- | ------------------------------- | 69 | | REACT_APP_SERVER_HOST | http://127.0.0.1:9001 | | | 70 | | REACT_APP_URL | | | This is used for the open graph | 71 | | REACT_APP_HEADER_TITLE | | | | 72 | | REACT_APP_HEADER_SHORT_TITLE | | | | 73 | | REACT_APP_OG_TITLE | | | | 74 | | REACT_APP_OG_DESC | | | | 75 | | REACT_APP_OG_IMAGE | | | | 76 | | REACT_APP_GOOGLE_ANALYTICS_ID | | | | 77 | | PUBLIC_URL | | | | 78 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebookincubator/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce( 85 | (env, key) => { 86 | env[key] = JSON.stringify(raw[key]); 87 | return env; 88 | }, 89 | {} 90 | ), 91 | }; 92 | 93 | return { raw, stringified }; 94 | } 95 | 96 | module.exports = getClientEnvironment; 97 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const url = require("url"); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith("/"); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => envPublicUrl || require(appPackageJson).homepage; 26 | 27 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 28 | // "public path" at which the app is served. 29 | // Webpack needs to know it to put the right 45 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/og_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/public/og_image.png -------------------------------------------------------------------------------- /public/og_image_corgi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/public/og_image_corgi.png -------------------------------------------------------------------------------- /scripts/build-preset/corgi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PUBLIC_URL="/explorer" \ 4 | REACT_APP_SERVER_HOST="https://corgi.codechain.io/explorer" \ 5 | REACT_APP_URL="https://corgi.codechain.io/explorer" \ 6 | REACT_APP_HEADER_TITLE="CodeChain Explorer - Corgi" \ 7 | REACT_APP_HEADER_SHORT_TITLE="Explorer - Corgi" \ 8 | REACT_APP_OG_TITLE="CodeChain Explorer For Corgi" \ 9 | REACT_APP_OG_DESC="View activity on the underlying Corgi test network" \ 10 | REACT_APP_OG_IMAGE="og_image_corgi.png" \ 11 | yarn build 12 | -------------------------------------------------------------------------------- /scripts/build-preset/mainnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PUBLIC_URL="" \ 4 | REACT_APP_SERVER_HOST="https://explorer.codechain.io" \ 5 | REACT_APP_URL="https://explorer.codechain.io" \ 6 | REACT_APP_HEADER_TITLE="CodeChain Explorer" \ 7 | REACT_APP_HEADER_SHORT_TITLE="Explorer" \ 8 | REACT_APP_OG_TITLE="CodeChain Explorer" \ 9 | REACT_APP_OG_DESC="View activity on the underlying CodeChain network" \ 10 | REACT_APP_OG_IMAGE="og_image.png" \ 11 | yarn build 12 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | if (process.env.HOST) { 47 | console.log( 48 | chalk.cyan( 49 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 50 | chalk.bold(process.env.HOST) 51 | )}` 52 | ) 53 | ); 54 | console.log( 55 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 56 | ); 57 | console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); 58 | console.log(); 59 | } 60 | 61 | // We attempt to use the default port but if it is busy, we offer the user to 62 | // run on a different port. `choosePort()` Promise resolves to the next free port. 63 | choosePort(HOST, DEFAULT_PORT) 64 | .then(port => { 65 | if (port == null) { 66 | // We have not found a port. 67 | return; 68 | } 69 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 70 | const appName = require(paths.appPackageJson).name; 71 | const urls = prepareUrls(protocol, HOST, port); 72 | // Create a webpack compiler that is configured with custom messages. 73 | const compiler = createCompiler(webpack, config, appName, urls, useYarn); 74 | // Load proxy config 75 | const proxySetting = require(paths.appPackageJson).proxy; 76 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 77 | // Serve webpack assets generated by the compiler over a web sever. 78 | const serverConfig = createDevServerConfig( 79 | proxyConfig, 80 | urls.lanUrlForConfig 81 | ); 82 | const devServer = new WebpackDevServer(compiler, serverConfig); 83 | // Launch WebpackDevServer. 84 | devServer.listen(port, HOST, err => { 85 | if (err) { 86 | return console.log(err); 87 | } 88 | if (isInteractive) { 89 | clearConsole(); 90 | } 91 | console.log(chalk.cyan('Starting the development server...\n')); 92 | openBrowser(urls.localUrlForBrowser); 93 | }); 94 | 95 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 96 | process.on(sig, function() { 97 | devServer.close(); 98 | process.exit(); 99 | }); 100 | }); 101 | }) 102 | .catch(err => { 103 | if (err && err.message) { 104 | console.log(err.message); 105 | } 106 | process.exit(1); 107 | }); 108 | -------------------------------------------------------------------------------- /src/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin heading-font() { 2 | font-weight: 600; 3 | } 4 | 5 | @mixin data-font() { 6 | font-family: "Roboto Mono", monospace; 7 | font-weight: 300; 8 | } 9 | 10 | @mixin small-text() { 11 | font-size: 0.8rem; 12 | } 13 | 14 | @mixin ellipsis-block() { 15 | overflow: hidden; 16 | text-overflow: ellipsis; 17 | white-space: nowrap; 18 | } 19 | 20 | $breakpoints: ( 21 | xs: 576px, 22 | sm: 768px, 23 | md: 992px, 24 | lg: 1200px 25 | ); 26 | @mixin respond-above($breakpoint) { 27 | @if map-has-key($breakpoints, $breakpoint) { 28 | $breakpoint-value: map-get($breakpoints, $breakpoint); 29 | @media (min-width: $breakpoint-value) { 30 | @content; 31 | } 32 | } @else { 33 | @warn 'Invalid breakpoint: #{$breakpoint}.'; 34 | } 35 | } 36 | 37 | @mixin respond-below($breakpoint) { 38 | @if map-has-key($breakpoints, $breakpoint) { 39 | $breakpoint-value: map-get($breakpoints, $breakpoint); 40 | @media (max-width: ($breakpoint-value - 1)) { 41 | @content; 42 | } 43 | } @else { 44 | @warn 'Invalid breakpoint: #{$breakpoint}.'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/_variables.scss: -------------------------------------------------------------------------------- 1 | // font color 2 | $dark-color: #4f4f4f; 3 | $header-text-color: #eff2f4; 4 | // background color 5 | $background-gray: #f2f2f2; 6 | // line color 7 | $line-color: #bdbdbd; 8 | // Color set 9 | $primary-color: #05aad1; 10 | // Status 11 | $pending-color: #f2c94c; 12 | $confirmed-color: #1ccf68; 13 | $dead-color: #eb5757; 14 | -------------------------------------------------------------------------------- /src/components/asset/AssetDetails/AssetDetails.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/asset/AssetDetails/AssetDetails.scss -------------------------------------------------------------------------------- /src/components/asset/AssetDetails/AssetDetails.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { AssetSchemeDoc } from "codechain-indexer-types"; 4 | import { Link } from "react-router-dom"; 5 | import { Col, Row } from "reactstrap"; 6 | import * as Metadata from "../../../utils/Metadata"; 7 | import DataSet from "../../util/DataSet/DataSet"; 8 | import "./AssetDetails.scss"; 9 | 10 | interface OwnProps { 11 | assetType: string; 12 | assetScheme: AssetSchemeDoc; 13 | } 14 | 15 | const AssetDetails = (prop: OwnProps) => { 16 | const metadata = Metadata.parseMetadata(prop.assetScheme.metadata); 17 | return ( 18 |
19 | 20 | 21 |

Details

22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | Name 30 | {metadata.name ? metadata.name : "None"} 31 | 32 |
33 | 34 | Description 35 | 36 |
{metadata.description ? metadata.description : "None"}
37 | 38 |
39 |
40 | 41 | Icon 42 | 43 |
{metadata.icon_url ? metadata.icon_url : "None"}
44 | 45 |
46 |
47 | 48 | Raw metadata 49 | 50 |
{prop.assetScheme.metadata}
51 | 52 |
53 |
54 | 55 | Approver 56 | 57 | {prop.assetScheme.approver ? ( 58 | 59 | {prop.assetScheme.approver} 60 | 61 | ) : ( 62 | "None" 63 | )} 64 | 65 | 66 |
67 | 68 | Registrar 69 | 70 | {prop.assetScheme.registrar ? ( 71 | 72 | {prop.assetScheme.registrar} 73 | 74 | ) : ( 75 | "None" 76 | )} 77 | 78 | 79 |
80 | 81 | Total Supply 82 | {prop.assetScheme.supply ? prop.assetScheme.supply.toLocaleString() : 0} 83 | 84 |
85 |
86 | 87 |
88 |
89 | ); 90 | }; 91 | 92 | export default AssetDetails; 93 | -------------------------------------------------------------------------------- /src/components/asset/AssetList/AssetList.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .asset-list { 4 | .asset-item { 5 | background-color: #f2f2f2; 6 | width: 100%; 7 | height: 66px; 8 | padding: 8px; 9 | .icon { 10 | margin-right: 8px; 11 | } 12 | .asset-text-container { 13 | flex-grow: 1; 14 | overflow: hidden; 15 | & > div { 16 | overflow: hidden; 17 | } 18 | .asset-name { 19 | @include ellipsis-block; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/asset/AssetOwners/AssetOwners.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .parcel-transaction-list { 4 | .input-output-container { 5 | background-color: $background-gray; 6 | padding-left: 8px; 7 | padding-right: 8px; 8 | margin-top: 6px; 9 | margin-bottom: 6px; 10 | } 11 | .view-more-transfer-btn { 12 | margin-bottom: 6px; 13 | } 14 | .input-highlight { 15 | background-color: #f9b7a9; 16 | } 17 | .output-highlight { 18 | background-color: #c1eed4; 19 | } 20 | .arrow-icon { 21 | color: #bdbdbd; 22 | } 23 | } 24 | 25 | .pie-chart-container { 26 | background-color: #ffffff; 27 | width: 100%; 28 | height: 400px; 29 | position: relative; 30 | .chart { 31 | position: absolute; 32 | top: 70px; 33 | left: 0; 34 | bottom: 0; 35 | right: 0; 36 | 37 | .chart-item { 38 | height: 100%; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/asset/AssetOwners/AssetOwners.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { AggsUTXODoc } from "codechain-indexer-types"; 4 | import { U64 } from "codechain-primitives/lib"; 5 | import { Link } from "react-router-dom"; 6 | import { Col, Row } from "reactstrap"; 7 | import { CommaNumberString } from "src/components/util/CommaNumberString/CommaNumberString"; 8 | import DataTable from "src/components/util/DataTable/DataTable"; 9 | import { ImageLoader } from "src/components/util/ImageLoader/ImageLoader"; 10 | import "./AssetOwners.scss"; 11 | import AssetOwnersChart from "./AssetOwnersChart"; 12 | 13 | interface OwnProps { 14 | aggsUTXO: AggsUTXODoc[]; 15 | } 16 | 17 | const AssetOwners = (prop: OwnProps) => { 18 | const n = prop.aggsUTXO.length === 11 ? 11 : 10; 19 | const topOwners = prop.aggsUTXO.slice(0, n); 20 | const others = prop.aggsUTXO.slice(n); 21 | return ( 22 |
23 | 24 | 25 |

Asset Owners

26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | Address 35 | 36 | Quantity 37 | 38 | 39 | 40 | 41 | {topOwners.map(item => { 42 | return ( 43 | 44 | 45 | 51 | {item.address} 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | })} 59 | {others.length > 0 && ( 60 | 61 | Others 62 | 63 | sum.plus(owner.totalAssetQuantity), new U64(0)) 66 | .toString()} 67 | /> 68 | 69 | 70 | )} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 | ); 80 | }; 81 | 82 | export default AssetOwners; 83 | -------------------------------------------------------------------------------- /src/components/asset/AssetOwners/AssetOwnersChart.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | const { ResponsivePie } = require("@nivo/pie"); 3 | 4 | import { AggsUTXODoc } from "codechain-indexer-types"; 5 | import { U64 } from "codechain-primitives/lib"; 6 | 7 | interface OwnProps { 8 | aggsUTXO: AggsUTXODoc[]; 9 | } 10 | 11 | type Props = OwnProps; 12 | 13 | class AssetOwnersChart extends React.Component { 14 | constructor(props: Props) { 15 | super(props); 16 | } 17 | 18 | public render() { 19 | return ( 20 |
21 |
22 |
{this.renderPieChart()}
23 |
24 |
25 | ); 26 | } 27 | 28 | public renderPieChart() { 29 | const { aggsUTXO } = this.props; 30 | 31 | const n = aggsUTXO.length === 11 ? 11 : 10; 32 | const topOwners = aggsUTXO.slice(0, n); 33 | const others = aggsUTXO.slice(n); 34 | 35 | const data = topOwners.map(i => { 36 | return { 37 | id: `${i.address.slice(0, 10)}...`, 38 | label: `${i.address.slice(0, 10)}...`, 39 | value: new U64(i.totalAssetQuantity).value.toNumber() 40 | }; 41 | }); 42 | data.push({ 43 | id: "Others", 44 | label: "Others", 45 | value: others.reduce((sum, owner) => sum.plus(owner.totalAssetQuantity), new U64(0)).value.toNumber() 46 | }); 47 | return ( 48 | `${d.id}`} 80 | slicesLabelsSkipAngle={20} 81 | slicesLabelsTextColor="#000000" 82 | /> 83 | ); 84 | } 85 | } 86 | 87 | export default AssetOwnersChart; 88 | -------------------------------------------------------------------------------- /src/components/block/BlockDetails/BlockDetails.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/block/BlockDetails/BlockDetails.scss -------------------------------------------------------------------------------- /src/components/block/BlockDetails/BlockDetails.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | 4 | import { Col, Row } from "reactstrap"; 5 | import DataSet from "../../util/DataSet/DataSet"; 6 | 7 | import { BlockDoc } from "codechain-indexer-types"; 8 | import { Link } from "react-router-dom"; 9 | import { CommaNumberString } from "../../util/CommaNumberString/CommaNumberString"; 10 | import HexString from "../../util/HexString/HexString"; 11 | import "./BlockDetails.scss"; 12 | 13 | interface OwnProps { 14 | block: BlockDoc; 15 | } 16 | 17 | class BlockDetails extends React.Component { 18 | public render() { 19 | const { block } = this.props; 20 | return ( 21 |
22 | 23 | 24 |

Details

25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | Parent Block Hash 33 | 34 | 35 | 36 | 37 |
38 | 39 | Transactions Root 40 | 41 | 42 | 43 | 44 |
45 | 46 | State Root 47 | 48 | 49 | 50 | 51 |
52 | 53 | Author 54 | 55 | {block.author} 56 | 57 | 58 |
59 | 60 | Score 61 | 62 | 63 | 64 | 65 |
66 | 67 | Seal 68 | 69 |
70 | {_.map(block.seal, s => Buffer.from(s).toString("hex")).join(" ")} 71 |
72 | 73 |
74 |
75 | 76 | Extra Data 77 | 78 |
{Buffer.from(block.extraData)}
79 | 80 |
81 |
82 | 83 | # of Transactions 84 | {block.transactionsCount.toLocaleString()} 85 | 86 |
87 |
88 | 89 |
90 |
91 | ); 92 | } 93 | } 94 | 95 | export default BlockDetails; 96 | -------------------------------------------------------------------------------- /src/components/block/BlockList/BlockList.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/block/BlockList/BlockList.scss -------------------------------------------------------------------------------- /src/components/error/Error/Error.scss: -------------------------------------------------------------------------------- 1 | @import "../../../variables.scss"; 2 | .error { 3 | margin-top: 60px; 4 | margin-bottom: 100px; 5 | .error-monster { 6 | width: 60%; 7 | height: auto; 8 | } 9 | .sub-title { 10 | color: $dark-color; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/error/Error/Error.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Col, Container, Row } from "reactstrap"; 3 | import "./Error.scss"; 4 | import * as errorMonster from "./img/error-monster.png"; 5 | 6 | interface Props { 7 | title: string; 8 | content: string; 9 | className?: string; 10 | } 11 | 12 | export const Error = (props: Props) => { 13 | const { className, title, content } = props; 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

Error!

23 |

We can’t find the page you’re looking for.

24 |

{title}

25 |

{content}

26 |
27 | 28 |
29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/error/Error/img/error-monster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/error/Error/img/error-monster.png -------------------------------------------------------------------------------- /src/components/footer/Footer.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .footer { 4 | background-color: $background-gray; 5 | padding-top: 25px; 6 | padding-bottom: 100px; 7 | .logo { 8 | width: 130px; 9 | height: 40px; 10 | } 11 | .codechain-icon { 12 | filter: opacity(0.68); 13 | } 14 | .gitter-icon { 15 | width: 17px; 16 | height: 25px; 17 | } 18 | .home-link-list { 19 | padding: 0; 20 | list-style-type: none; 21 | margin: 0; 22 | li { 23 | @include small-text; 24 | margin-bottom: 5px; 25 | a { 26 | color: $dark-color; 27 | &:hover { 28 | color: black; 29 | } 30 | } 31 | } 32 | } 33 | .copyright { 34 | margin-top: 5px; 35 | @include small-text; 36 | } 37 | .link-list { 38 | @include respond-above(lg) { 39 | float: right !important; 40 | } 41 | li { 42 | font-size: 2rem; 43 | margin-left: 10px; 44 | a { 45 | color: $dark-color; 46 | &:hover { 47 | color: black; 48 | } 49 | .link-icon { 50 | width: 27px; 51 | height: 27px; 52 | margin-bottom: 3px; 53 | &:hover { 54 | filter: brightness(50%); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { faGithub, faGitter, faMedium, faTwitter } from "@fortawesome/free-brands-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import * as React from "react"; 4 | import { Col, Container, Row } from "reactstrap"; 5 | import "./Footer.scss"; 6 | import * as codechainIcon from "./img/codechain-icon.png"; 7 | 8 | const Footer = () => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 27 | 28 | 29 | 56 | 57 | 58 | 59 |
60 | ); 61 | }; 62 | 63 | export default Footer; 64 | -------------------------------------------------------------------------------- /src/components/footer/img/codechain-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/footer/img/codechain-icon.png -------------------------------------------------------------------------------- /src/components/footer/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/footer/img/logo.png -------------------------------------------------------------------------------- /src/components/header/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .header { 4 | background-color: $primary-color; 5 | position: fixed; 6 | top: 0; 7 | width: 100%; 8 | z-index: 10000; 9 | .header-title { 10 | font-weight: 600; 11 | } 12 | .header-title-container { 13 | margin-right: calc(50% - 870px); 14 | } 15 | @media (max-width: 1740px) { 16 | .header-title-container { 17 | margin-right: 0; 18 | } 19 | } 20 | 21 | .menu-container { 22 | max-width: 1140px; 23 | } 24 | .logo { 25 | height: 35px; 26 | width: auto; 27 | margin-right: 5px; 28 | } 29 | .nav-link { 30 | color: white !important; 31 | cursor: pointer; 32 | &:hover { 33 | color: $header-text-color !important; 34 | } 35 | } 36 | .dropdown-menu { 37 | .dropdown-item { 38 | color: $dark-color; 39 | &:hover { 40 | background-color: $background-gray; 41 | } 42 | } 43 | a.active { 44 | background-color: white; 45 | } 46 | } 47 | .header-small { 48 | display: none !important; 49 | vertical-align: middle; 50 | } 51 | .header-big { 52 | vertical-align: middle; 53 | } 54 | @include respond-below(sm) { 55 | .header-big { 56 | display: none !important; 57 | } 58 | .header-small { 59 | display: inline-block !important; 60 | } 61 | } 62 | @include respond-below(md) { 63 | .dropdown-menu { 64 | background-color: $primary-color; 65 | border: none; 66 | .dropdown-item { 67 | color: white !important; 68 | &:hover { 69 | background-color: inherit; 70 | color: $header-text-color !important; 71 | } 72 | } 73 | a.active { 74 | background-color: $primary-color; 75 | } 76 | } 77 | } 78 | .loading-bar { 79 | background-color: #4f50a2; 80 | height: 3px; 81 | position: absolute; 82 | } 83 | .search-for-small-screen { 84 | display: none; 85 | padding-bottom: 16px; 86 | } 87 | @include respond-below(md) { 88 | .search-for-small-screen { 89 | display: flex; 90 | } 91 | .search-for-large-screen { 92 | display: none; 93 | } 94 | } 95 | } 96 | 97 | .network-description-popover { 98 | z-index: 10001; 99 | } 100 | -------------------------------------------------------------------------------- /src/components/header/Header/img/logo-explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/header/Header/img/logo-explorer.png -------------------------------------------------------------------------------- /src/components/header/Header/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/header/Header/img/logo.png -------------------------------------------------------------------------------- /src/components/header/Search/Search.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .search-form { 4 | width: 400px; 5 | @media (max-width: 400px) { 6 | & { 7 | width: 100%; 8 | } 9 | } 10 | .search-form-group { 11 | flex-grow: 1; 12 | margin-right: 8px; 13 | } 14 | .not-found { 15 | font-size: 1rem; 16 | padding: 0; 17 | margin-right: 8px; 18 | line-height: 34px; 19 | } 20 | .search-input { 21 | flex-grow: 1; 22 | } 23 | .react-autosuggest__input { 24 | width: 100%; 25 | height: 34px; 26 | margin-right: 5px; 27 | padding-left: 10px; 28 | padding-right: 10px; 29 | border: none; 30 | } 31 | .react-autosuggest__container { 32 | width: 100%; 33 | position: relative; 34 | } 35 | .react-autosuggest__suggestions-container { 36 | display: none; 37 | } 38 | .react-autosuggest__suggestions-container--open { 39 | display: block; 40 | position: absolute; 41 | top: 34px; 42 | width: 100%; 43 | border: 1px solid #aaa; 44 | background-color: #fff; 45 | border-bottom-left-radius: 4px; 46 | border-bottom-right-radius: 4px; 47 | z-index: 2; 48 | } 49 | .react-autosuggest__suggestions-list { 50 | margin: 0; 51 | padding: 0; 52 | list-style-type: none; 53 | @include ellipsis-block; 54 | .icon { 55 | vertical-align: top; 56 | margin-right: 10px; 57 | } 58 | .name { 59 | vertical-align: top; 60 | } 61 | } 62 | .react-autosuggest__suggestion { 63 | cursor: pointer; 64 | padding: 10px 20px; 65 | } 66 | .react-autosuggest__suggestion--highlighted { 67 | background-color: #ddd; 68 | } 69 | .search-loading-bar { 70 | background-color: #f2b04c; 71 | height: 3px; 72 | width: 100%; 73 | } 74 | .search-summit { 75 | height: 34px; 76 | width: 100px; 77 | color: white; 78 | border: 1px solid white; 79 | background-color: $primary-color; 80 | font-weight: 600; 81 | &:hover { 82 | background-color: #0a8faf; 83 | } 84 | @media (max-width: 400px) { 85 | & { 86 | width: 50px; 87 | } 88 | } 89 | } 90 | @media (max-width: 400px) { 91 | .search-small { 92 | display: inline; 93 | } 94 | .search-big { 95 | display: none; 96 | } 97 | } 98 | @media (min-width: 401px) { 99 | .search-small { 100 | display: none; 101 | } 102 | .search-big { 103 | display: inline; 104 | } 105 | } 106 | } 107 | 108 | .search-error-pop { 109 | z-index: 10001; 110 | .popover-body { 111 | color: red !important; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/components/home/LatestBlocks/LatestBlocks.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/home/LatestBlocks/LatestBlocks.scss -------------------------------------------------------------------------------- /src/components/home/LatestBlocks/LatestBlocks.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as moment from "moment"; 3 | import * as React from "react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | import { BlockDoc } from "codechain-indexer-types"; 7 | import { connect } from "react-redux"; 8 | import { getUnixTimeLocaleString } from "src/utils/Time"; 9 | import DataTable from "../../util/DataTable/DataTable"; 10 | import "./LatestBlocks.scss"; 11 | 12 | interface OwnProps { 13 | blocks: BlockDoc[]; 14 | } 15 | type Props = OwnProps; 16 | 17 | const LatestBlocks = (props: Props) => { 18 | const { blocks } = props; 19 | const offset = blocks.length === 0 ? 0 : blocks[0].timestamp - moment().unix(); 20 | return ( 21 |
22 |

Latest Blocks

23 |
24 | 25 | 26 | 27 | No. 28 | Hash 29 | 30 | Tx 31 | 32 | 33 | Time 34 | 35 | 36 | 37 | 38 | {_.map(blocks.slice(0, 10), (block: BlockDoc) => { 39 | return ( 40 | 41 | 42 | {block.number.toLocaleString()} 43 | 44 | 45 | {`0x${block.hash}`} 46 | 47 | {block.transactionsCount.toLocaleString()} 48 | 49 | {block.timestamp 50 | ? getUnixTimeLocaleString(block.timestamp - offset, offset) 51 | : "Genesis"} 52 | 53 | 54 | ); 55 | })} 56 | 57 | 58 | { 59 |
60 | 61 | 64 | 65 |
66 | } 67 |
68 |
69 | ); 70 | }; 71 | 72 | export default connect()(LatestBlocks); 73 | -------------------------------------------------------------------------------- /src/components/home/LatestTransactions/LatestTransactions.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .latest-transactions { 4 | .latest-container { 5 | .transaction-type { 6 | color: white; 7 | width: 160px; 8 | border-radius: 11px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/home/LatestTransactions/LatestTransactions.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | 4 | import { faSpinner } from "@fortawesome/free-solid-svg-icons"; 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { TransactionDoc } from "codechain-indexer-types"; 7 | import { connect } from "react-redux"; 8 | import { Link } from "react-router-dom"; 9 | import { ImageLoader } from "src/components/util/ImageLoader/ImageLoader"; 10 | import DataTable from "../../util/DataTable/DataTable"; 11 | import HexString from "../../util/HexString/HexString"; 12 | import { TypeBadge } from "../../util/TypeBadge/TypeBadge"; 13 | import "./LatestTransactions.scss"; 14 | 15 | interface OwnProps { 16 | transactions: TransactionDoc[]; 17 | } 18 | 19 | type Props = OwnProps; 20 | 21 | const LatestTransactions = (props: Props) => { 22 | const { transactions } = props; 23 | 24 | const renderTableBody = () => { 25 | if (transactions.length === 0) { 26 | return ( 27 | 28 | 29 |
30 | 31 |
32 | 33 | 34 | ); 35 | } 36 | 37 | return _.map(transactions.slice(0, 10), (transaction: TransactionDoc) => ( 38 | 39 | 40 | {" "} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {transaction.signer} 49 | 50 | 51 | 52 | )); 53 | }; 54 | 55 | return ( 56 |
57 |

Latest Transactions

58 |
59 | 60 | 61 | 62 | Type 63 | Hash 64 | Signer 65 | 66 | 67 | {renderTableBody()} 68 | 69 | { 70 |
71 | 72 | 75 | 76 |
77 | } 78 |
79 |
80 | ); 81 | }; 82 | 83 | export default connect()(LatestTransactions); 84 | -------------------------------------------------------------------------------- /src/components/home/Summary/Summary.scss: -------------------------------------------------------------------------------- 1 | .summary { 2 | .empty-container { 3 | height: 100%; 4 | padding-top: 30px; 5 | display: flex; 6 | .empty-icon { 7 | width: 130px; 8 | height: 130px; 9 | } 10 | } 11 | g > rect { 12 | cursor: pointer; 13 | } 14 | .daily-log-item.block { 15 | g > path { 16 | cursor: pointer; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/home/Summary/Summary.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { Col, Row } from "reactstrap"; 4 | 5 | import { connect } from "react-redux"; 6 | import { RootState } from "../../../redux/actions"; 7 | import { WeeklyLogType } from "../../../request/RequestWeeklyLogs"; 8 | import "./Summary.scss"; 9 | import WeeklyChart from "./WeeklyChart"; 10 | 11 | interface State { 12 | latestBestBlockNumber?: number; 13 | } 14 | 15 | interface StateProps { 16 | bestBlockNumber?: number; 17 | } 18 | 19 | type Props = StateProps; 20 | 21 | class Summary extends React.Component { 22 | private refresher: any; 23 | constructor(props: {}) { 24 | super(props); 25 | this.state = { 26 | latestBestBlockNumber: undefined 27 | }; 28 | } 29 | public componentWillUnmount() { 30 | if (this.refresher) { 31 | clearInterval(this.refresher); 32 | } 33 | } 34 | public componentDidMount() { 35 | this.refresher = setInterval(this.refreshSummary, 5000); 36 | } 37 | public render() { 38 | const { bestBlockNumber } = this.props; 39 | return ( 40 |
41 | 42 | 43 | 50 | 51 | 52 | 59 | 60 | 61 |
62 | ); 63 | } 64 | 65 | private refreshSummary = () => { 66 | const { bestBlockNumber } = this.props; 67 | this.setState({ latestBestBlockNumber: bestBlockNumber }); 68 | }; 69 | 70 | private onError = (error: any) => { 71 | console.log(error); 72 | }; 73 | } 74 | 75 | export default connect((state: RootState) => { 76 | return { 77 | bestBlockNumber: state.appReducer.bestBlockNumber 78 | }; 79 | })(Summary); 80 | -------------------------------------------------------------------------------- /src/components/home/Summary/img/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/home/Summary/img/empty.png -------------------------------------------------------------------------------- /src/components/platformAddress/AccountDetails/AccountDetails.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/platformAddress/AccountDetails/AccountDetails.scss -------------------------------------------------------------------------------- /src/components/platformAddress/AccountDetails/AccountDetails.tsx: -------------------------------------------------------------------------------- 1 | import { U64 } from "codechain-sdk/lib/core/classes"; 2 | import * as React from "react"; 3 | import { Col, Row } from "reactstrap"; 4 | 5 | import { CommaNumberString } from "../../util/CommaNumberString/CommaNumberString"; 6 | import DataSet from "../../util/DataSet/DataSet"; 7 | import "./AccountDetails.scss"; 8 | 9 | interface OwnProps { 10 | account: { 11 | seq: U64; 12 | balance: U64; 13 | }; 14 | } 15 | 16 | const AccountDetails = (prop: OwnProps) => { 17 | const { account } = prop; 18 | return ( 19 |
20 | 21 | 22 |

Details

23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 | Balance 31 | 32 | 33 | CCC 34 | 35 | 36 |
37 | 38 | Sequence 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 |
47 |
48 | ); 49 | }; 50 | 51 | export default AccountDetails; 52 | -------------------------------------------------------------------------------- /src/components/status/ChainInfo/ChainInfo.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/status/ChainInfo/ChainInfo.scss -------------------------------------------------------------------------------- /src/components/status/ChainInfo/ChainInfo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { CodeChainData } from "../../../request/RequestCodeChainStatus"; 4 | import DataSet from "../../util/DataSet/DataSet"; 5 | import "./ChainInfo.scss"; 6 | 7 | interface Props { 8 | chainInfo: CodeChainData; 9 | } 10 | 11 | class ChainInfo extends React.Component { 12 | constructor(props: Props) { 13 | super(props); 14 | } 15 | 16 | public render() { 17 | const { chainInfo } = this.props; 18 | return ( 19 |
20 |
21 |

CodeChain Information

22 |
23 |
24 | 25 |
26 |
Network ID
27 |
{chainInfo.networkId}
28 |
29 |
30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default ChainInfo; 37 | -------------------------------------------------------------------------------- /src/components/status/ExplorerInfo/ExplorerInfo.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/status/ExplorerInfo/ExplorerInfo.scss -------------------------------------------------------------------------------- /src/components/status/ExplorerInfo/ExplorerInfo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import DataSet from "../../util/DataSet/DataSet"; 4 | 5 | import RequestIndexerVersion from "src/request/RequestIndexerVersion"; 6 | import "./ExplorerInfo.scss"; 7 | 8 | const { version } = require("../../../../package.json"); 9 | 10 | interface State { 11 | indexerVersion?: string; 12 | indexerVersionRequestError?: any; 13 | } 14 | 15 | class ExplorerInfo extends React.Component<{}, State> { 16 | constructor(props: {}) { 17 | super(props); 18 | this.state = {}; 19 | } 20 | 21 | public render() { 22 | return ( 23 |
24 |
25 |

Explorer Information

26 |
27 |
28 | 29 |
30 |
Explorer version
31 |
{version}
32 |
33 |
34 |
35 |
Indexer version
36 |
{this.renderIndexerVersion()}
37 |
38 |
39 |
40 |
41 | ); 42 | } 43 | 44 | private renderIndexerVersion() { 45 | const { indexerVersion, indexerVersionRequestError } = this.state; 46 | if (indexerVersion) { 47 | return <>{indexerVersion}; 48 | } else if (indexerVersionRequestError === true) { 49 | return <>unavailable; 50 | } else { 51 | // FIXME: Progressbar 52 | return ( 53 | <> 54 | loading... 55 | 56 | 57 | ); 58 | } 59 | } 60 | 61 | private onIndexerVersion = (indexerVersion: string) => { 62 | this.setState({ indexerVersion }); 63 | }; 64 | 65 | private onError = () => { 66 | this.setState({ indexerVersionRequestError: true }); 67 | }; 68 | } 69 | 70 | export default ExplorerInfo; 71 | -------------------------------------------------------------------------------- /src/components/status/NodeStatus/NodeStatus.scss: -------------------------------------------------------------------------------- 1 | .node-status { 2 | .running { 3 | color: #1ccf68; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/status/NodeStatus/NodeStatus.tsx: -------------------------------------------------------------------------------- 1 | import { faCircle } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import * as React from "react"; 4 | 5 | import DataSet from "../../util/DataSet/DataSet"; 6 | import "./NodeStatus.scss"; 7 | 8 | interface Props { 9 | nodeStatus: boolean; 10 | } 11 | 12 | class NodeStatus extends React.Component { 13 | constructor(props: Props) { 14 | super(props); 15 | } 16 | 17 | public render() { 18 | const { nodeStatus } = this.props; 19 | return ( 20 |
21 |
22 |

Node Status

23 |
24 |
25 | 26 |
27 |
Status
28 |
29 | {nodeStatus ? ( 30 | 31 | Running 32 | 33 | ) : ( 34 | 35 | Stopped 36 | 37 | )} 38 |
39 |
40 |
41 |
42 |
43 | ); 44 | } 45 | } 46 | 47 | export default NodeStatus; 48 | -------------------------------------------------------------------------------- /src/components/status/SyncStatus/SyncStatus.scss: -------------------------------------------------------------------------------- 1 | .sync-status { 2 | .custom-progress { 3 | height: 21px; 4 | .bg-success { 5 | background-color: #31f3d0 !important; 6 | } 7 | } 8 | .progress-value { 9 | position: absolute; 10 | top: 0px; 11 | right: 20px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/status/SyncStatus/SyncStatus.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Col, Row } from "reactstrap"; 3 | import { Progress } from "reactstrap"; 4 | 5 | import { Link } from "react-router-dom"; 6 | import { SyncData } from "../../../request/RequestSyncStatus"; 7 | import DataSet from "../../util/DataSet/DataSet"; 8 | import HexString from "../../util/HexString/HexString"; 9 | import "./SyncStatus.scss"; 10 | 11 | interface Props { 12 | syncStatus: SyncData; 13 | } 14 | 15 | class SyncStatus extends React.Component { 16 | constructor(props: Props) { 17 | super(props); 18 | } 19 | 20 | public render() { 21 | const { syncStatus } = this.props; 22 | return ( 23 |
24 |
25 |

Sync Status

26 |
27 |
28 | 29 |
30 |
Best block number (CodeChain)
31 |
{syncStatus.codechainBestBlockNumber.toLocaleString()}
32 |
33 |
34 | 35 | Best block hash (CodeChain) 36 | 37 | 0x 38 | {syncStatus.codechainBestBlockHash} 39 | 40 | 41 |
42 |
43 |
Last block number (Explorer)
44 |
45 | 46 | {syncStatus.indexedBlockNumber.toLocaleString()} 47 | 48 |
49 |
50 |
51 | 52 | Last block hash (Explorer) 53 | 54 | 58 | 59 | 60 |
61 | 62 | Sync progress 63 | 64 | 73 | 74 | {Math.floor( 75 | (syncStatus.indexedBlockNumber / syncStatus.codechainBestBlockNumber) * 1000 76 | ) / 10} 77 | % 78 | 79 | 80 | 81 |
82 |
83 |
84 | ); 85 | } 86 | } 87 | 88 | export default SyncStatus; 89 | -------------------------------------------------------------------------------- /src/components/status/TransactionChart/TransactionChart.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | const { ResponsiveLine } = require("@nivo/line"); 4 | 5 | interface Props { 6 | transactions: Array<{ 7 | x: string; 8 | y: string; 9 | }>; 10 | } 11 | 12 | class TransactionChart extends React.Component { 13 | constructor(props: Props) { 14 | super(props); 15 | } 16 | 17 | public render() { 18 | const { transactions } = this.props; 19 | const first = _.first(transactions); 20 | const last = _.last(transactions); 21 | let tickValues: any = []; 22 | if (first && last) { 23 | tickValues = [first.x, last.x]; 24 | } 25 | return ( 26 |
27 |
28 |
29 |
30 |

Block transactions

31 |

Last 50 blocks

32 |
33 |
34 | e.color} 67 | dotSize={10} 68 | dotColor="inherit:darker(0.3)" 69 | dotBorderWidth={2} 70 | enableGridX={false} 71 | dotBorderColor="#ffffff" 72 | enableDotLabel={false} 73 | animate={true} 74 | motionStiffness={90} 75 | motionDamping={15} 76 | /> 77 |
78 |
79 |
80 |
81 | ); 82 | } 83 | 84 | private getAxisLeftTickValues = (): number[] => { 85 | const { transactions } = this.props; 86 | const maxY = _.max(transactions.map(t => parseInt(t.y, 10))) || 0; 87 | if (maxY < 10) { 88 | return _.range(0, maxY + 1, 1); 89 | } 90 | return _.range(0, Math.ceil(maxY / 10) * 10 + 1, Math.ceil(maxY / 10)); 91 | }; 92 | } 93 | 94 | export default TransactionChart; 95 | -------------------------------------------------------------------------------- /src/components/transaction/BlockTransactionList/BlockTransactionList.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .parcel-transaction-list { 4 | .input-output-container { 5 | background-color: $background-gray; 6 | padding-left: 8px; 7 | padding-right: 8px; 8 | margin-top: 6px; 9 | margin-bottom: 6px; 10 | } 11 | .view-more-transfer-btn { 12 | margin-bottom: 6px; 13 | } 14 | .input-highlight { 15 | background-color: #f9b7a9; 16 | } 17 | .output-highlight { 18 | background-color: #c1eed4; 19 | } 20 | .arrow-icon { 21 | color: #bdbdbd; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/transaction/BlockTransactionList/BlockTransactionList.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { Col, Row } from "reactstrap"; 4 | 5 | import { H160 } from "codechain-sdk/lib/core/classes"; 6 | 7 | import "./BlockTransactionList.scss"; 8 | import BlockTransactionListPage from "./BlockTransactionListPage"; 9 | 10 | interface OwnProps { 11 | blockId: number | string; 12 | owner?: string; 13 | assetType?: H160; 14 | totalCount: number; 15 | } 16 | 17 | interface State { 18 | pages: [undefined, ...string[]]; 19 | hasNextPage?: boolean; 20 | lastEvaluatedKey?: string; 21 | } 22 | 23 | type Props = OwnProps; 24 | 25 | class BlockTransactionList extends React.Component { 26 | private itemsPerPage = 6; 27 | constructor(props: Props) { 28 | super(props); 29 | this.state = { 30 | pages: [undefined] 31 | }; 32 | } 33 | 34 | public render() { 35 | const { pages, hasNextPage } = this.state; 36 | const { blockId } = this.props; 37 | return ( 38 |
39 | {this.renderTransactionListTitle()} 40 | {pages.map(page => ( 41 | 47 | ))} 48 | {hasNextPage && ( 49 | 50 | 51 |
52 | 55 |
56 | 57 |
58 | )} 59 |
60 | ); 61 | } 62 | 63 | private renderTransactionListTitle = () => { 64 | const { totalCount } = this.props; 65 | return ( 66 | 67 | 68 |
69 | {totalCount === 1 ?

Transaction

:

Transactions

} 70 | {totalCount !== 1 && Total {totalCount} transactions} 71 |
72 |
73 | 74 |
75 | ); 76 | }; 77 | 78 | private loadMore = () => { 79 | const { pages, lastEvaluatedKey } = this.state; 80 | this.setState({ pages: [...pages, lastEvaluatedKey] as [undefined, ...string[]] }); 81 | }; 82 | 83 | private handlePageLoad = (params: { 84 | requestedKey: string | undefined; 85 | requestedItemsPerPage: number; 86 | hasNextPage: boolean; 87 | lastEvaluatedKey: string; 88 | }) => { 89 | const { requestedKey, hasNextPage, lastEvaluatedKey } = params; 90 | if (_.last(this.state.pages) === requestedKey) { 91 | this.setState({ 92 | hasNextPage, 93 | lastEvaluatedKey 94 | }); 95 | } 96 | }; 97 | } 98 | 99 | export default BlockTransactionList; 100 | -------------------------------------------------------------------------------- /src/components/transaction/BlockTransactionList/BlockTransactionListPage.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | 4 | import { TransactionDoc } from "codechain-indexer-types"; 5 | import { H160 } from "codechain-sdk/lib/core/classes"; 6 | 7 | import { faSpinner } from "@fortawesome/free-solid-svg-icons"; 8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 9 | import { TransactionsResponse } from "src/request/RequestTransactions"; 10 | import RequestBlockTransactions from "../../../request/RequestBlockTransactions"; 11 | import TransactionListItems from "../TransactionListItems/TransactionListItems"; 12 | 13 | interface OwnProps { 14 | lastEvaluatedKey?: string; 15 | itemsPerPage: number; 16 | blockId: number | string; 17 | assetType?: H160; 18 | owner?: string; 19 | onLoad: ( 20 | params: { 21 | requestedKey: string | undefined; 22 | requestedItemsPerPage: number; 23 | hasNextPage: boolean; 24 | lastEvaluatedKey: string; 25 | } 26 | ) => void; 27 | } 28 | 29 | interface State { 30 | transactions?: TransactionDoc[]; 31 | } 32 | 33 | type Props = OwnProps; 34 | 35 | class BlockTransactionListPage extends React.Component { 36 | constructor(props: Props) { 37 | super(props); 38 | this.state = {}; 39 | } 40 | 41 | public render() { 42 | const { transactions } = this.state; 43 | if (transactions == null) { 44 | const { blockId, lastEvaluatedKey, itemsPerPage } = this.props; 45 | return ( 46 |
47 |
48 | 49 |
50 | 57 |
58 | ); 59 | } 60 | const { assetType, owner } = this.props; 61 | return ; 62 | } 63 | 64 | private onLoad = (response: TransactionsResponse) => { 65 | const { data: transactions, hasNextPage, lastEvaluatedKey } = response; 66 | this.setState({ transactions }); 67 | const { lastEvaluatedKey: requestedKey, itemsPerPage: requestedItemsPerPage } = this.props; 68 | this.props.onLoad({ requestedKey, requestedItemsPerPage, hasNextPage, lastEvaluatedKey }); 69 | }; 70 | } 71 | 72 | export default BlockTransactionListPage; 73 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/CommonDetails/CommonDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import { Link } from "react-router-dom"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | import { CommaNumberString } from "src/components/util/CommaNumberString/CommaNumberString"; 7 | import { StatusBadge } from "../../../util/StatusBadge/StatusBadge"; 8 | import { TypeBadge } from "../../../util/TypeBadge/TypeBadge"; 9 | 10 | interface Props { 11 | tx: TransactionDoc; 12 | } 13 | 14 | export default class CommonDetails extends React.Component { 15 | public render() { 16 | const { tx: transaction } = this.props; 17 | return [ 18 | 19 | Type 20 | 21 | 22 | 23 | , 24 |
, 25 | 26 | Block 27 | 28 | {transaction.blockNumber} 29 | 30 | , 31 |
, 32 | !transaction.isPending && [ 33 | 34 | Transaction Index 35 | {transaction.transactionIndex!.toLocaleString()} 36 | , 37 |
38 | ], 39 | 40 | Sequence 41 | {transaction.seq} 42 | , 43 |
, 44 | 45 | Fee 46 | 47 | 48 | CCC 49 | 50 | , 51 |
, 52 | 53 | Signer 54 | 55 | {transaction.signer} 56 | 57 | , 58 |
, 59 | 60 | NetworkID 61 | {transaction.networkId} 62 | , 63 |
, 64 | 65 | Status 66 | 67 | 68 | 69 | , 70 |
71 | ]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/AssetTransferDetails/AssetTransferDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc, TransferAssetTransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as moment from "moment"; 4 | import * as React from "react"; 5 | import Col from "reactstrap/lib/Col"; 6 | import Row from "reactstrap/lib/Row"; 7 | 8 | interface Props { 9 | tx: TransactionDoc; 10 | } 11 | 12 | export default class AssetTransferDetails extends React.Component { 13 | public render() { 14 | const { tx } = this.props; 15 | const transaction = tx as TransferAssetTransactionDoc; 16 | return [ 17 | 18 | Metadata 19 | 20 |
{transaction.transferAsset.metadata}
21 | 22 |
, 23 |
, 24 | 25 | Approvals 26 | 27 | {transaction.transferAsset.approvals.length !== 0 ? ( 28 |
29 | {_.map(transaction.transferAsset.approvals, (approval, i) => { 30 | return
{approval}
; 31 | })} 32 |
33 | ) : ( 34 | "None" 35 | )} 36 | 37 |
, 38 |
, 39 | 40 | Expiration 41 | 42 | {transaction.transferAsset.expiration 43 | ? moment.unix(parseInt(transaction.transferAsset.expiration, 10)).format("LLL") 44 | : "None"} 45 | 46 | , 47 |
, 48 | 49 | # of Input 50 | {transaction.transferAsset.inputs.length.toLocaleString()} 51 | , 52 |
, 53 | 54 | # of Output 55 | {transaction.transferAsset.outputs.length.toLocaleString()} 56 | , 57 |
, 58 | 59 | # of Burn 60 | {transaction.transferAsset.burns.length.toLocaleString()} 61 | , 62 |
63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/ChangeAssetSchemeDetails/ChangeAssetSchemeDetails.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeAssetSchemeTransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | import HexString from "../../../../util/HexString/HexString"; 7 | import { ImageLoader } from "../../../../util/ImageLoader/ImageLoader"; 8 | 9 | interface Props { 10 | tx: ChangeAssetSchemeTransactionDoc; 11 | } 12 | 13 | export default class ChangeAssetSchemeDetails extends React.Component { 14 | public render() { 15 | const { tx } = this.props; 16 | return [ 17 | 18 | ShardId 19 | {tx.changeAssetScheme.shardId} 20 | , 21 |
, 22 | 23 | Registrar 24 | {tx.changeAssetScheme.registrar ? tx.changeAssetScheme.registrar : "None"} 25 | , 26 |
, 27 | 28 | AllowedScriptHashes 29 | 30 | {tx.changeAssetScheme.allowedScriptHashes.length !== 0 ? ( 31 |
32 | {_.map(tx.changeAssetScheme.allowedScriptHashes, (allowedScriptHash, i) => { 33 | return
{allowedScriptHash}
; 34 | })} 35 |
36 | ) : ( 37 | "None" 38 | )} 39 | 40 |
, 41 |
, 42 | 43 | Approvals 44 | 45 | {tx.changeAssetScheme.approvals.length !== 0 ? ( 46 |
47 | {_.map(tx.changeAssetScheme.approvals, (approval, i) => { 48 | return
{approval}
; 49 | })} 50 |
51 | ) : ( 52 | "None" 53 | )} 54 | 55 |
, 56 |
, 57 | 58 | AssetType 59 | 60 | 61 | 65 | 66 | , 67 |
, 68 | 69 | Metadata 70 | 71 |
{tx.changeAssetScheme.metadata}
72 | 73 |
, 74 |
75 | ]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/CreateShardDetails/CreateShardDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | 4 | interface Props { 5 | tx: TransactionDoc; 6 | } 7 | 8 | export default class CreateShardDetails extends React.Component { 9 | public render() { 10 | return null; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/CustomDetails/CustomDetails.tsx: -------------------------------------------------------------------------------- 1 | import { CustomTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import Col from "reactstrap/lib/Col"; 4 | import Row from "reactstrap/lib/Row"; 5 | 6 | interface Props { 7 | tx: TransactionDoc; 8 | } 9 | 10 | export default class CustomDetails extends React.Component { 11 | public render() { 12 | const { tx } = this.props; 13 | const transaction = tx as CustomTransactionDoc; 14 | return [ 15 | 16 | Content 17 | 18 |
{transaction.custom.content}
19 | 20 |
, 21 |
, 22 | 23 | Handler id 24 | {transaction.custom.handlerId} 25 | , 26 |
27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/DetailsByType.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import AssetMintDetails from "./AssetMintDetails/AssetMintDetails"; 4 | import AssetTransferDetails from "./AssetTransferDetails/AssetTransferDetails"; 5 | import ChangeAssetSchemeDetails from "./ChangeAssetSchemeDetails/ChangeAssetSchemeDetails"; 6 | import CreateShardDetails from "./CreateShardDetails/CreateShardDetails"; 7 | import CustomDetails from "./CustomDetails/CustomDetails"; 8 | import IncreaseAssetSupplyDetails from "./IncreaseAssetSupplyDetails/IncreaseAssetSupplyDetails"; 9 | import PayDetails from "./PayDetails/PayDetails"; 10 | import RemoveDetails from "./RemoveDetails/RemoveDetails"; 11 | import SetRegularKeyDetails from "./SetRegularKeyDetails/SetRegularKeyDetails"; 12 | import SetShardOwnersDetails from "./SetShardOwnersDetails/SetShardOwnersDetails"; 13 | import SetShardUsersDetails from "./SetShardUsersDetails/SetShardUsersDetails"; 14 | import StoreDetails from "./StoreDetails/StoreDetails"; 15 | import UnwrapCCCDetails from "./UnwrapCCCDetails/UnwrapCCCDetails"; 16 | import WrapCCCDetails from "./WrapCCCDetails/WrapCCCDetails"; 17 | 18 | export interface Props { 19 | tx: TransactionDoc; 20 | } 21 | 22 | export default class DetailsByType extends React.Component { 23 | public render() { 24 | const { tx: transaction } = this.props; 25 | if (transaction.type === "transferAsset") { 26 | return ; 27 | } else if (transaction.type === "mintAsset") { 28 | return ; 29 | } else if (transaction.type === "pay") { 30 | return ; 31 | } else if (transaction.type === "setRegularKey") { 32 | return ; 33 | } else if (transaction.type === "store") { 34 | return ; 35 | } else if (transaction.type === "createShard") { 36 | return ; 37 | } else if (transaction.type === "setShardOwners") { 38 | return ; 39 | } else if (transaction.type === "setShardUsers") { 40 | return ; 41 | } else if (transaction.type === "remove") { 42 | return ; 43 | } else if (transaction.type === "unwrapCCC") { 44 | return ; 45 | } else if (transaction.type === "wrapCCC") { 46 | return ; 47 | } else if (transaction.type === "custom") { 48 | return ; 49 | } else if (transaction.type === "changeAssetScheme") { 50 | return ; 51 | } else if (transaction.type === "increaseAssetSupply") { 52 | return ; 53 | } 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/IncreaseAssetSupplyDetails/IncreaseAssetSupplyDetails.tsx: -------------------------------------------------------------------------------- 1 | import { IncreaseAssetSupplyTransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import { Link } from "react-router-dom"; 5 | import Col from "reactstrap/lib/Col"; 6 | import Row from "reactstrap/lib/Row"; 7 | import { getLockScriptName } from "src/utils/Transactions"; 8 | import HexString from "../../../../util/HexString/HexString"; 9 | import { ImageLoader } from "../../../../util/ImageLoader/ImageLoader"; 10 | 11 | interface Props { 12 | tx: IncreaseAssetSupplyTransactionDoc; 13 | } 14 | 15 | export default class IncreaseAssetSupplyDetails extends React.Component { 16 | public render() { 17 | const { tx } = this.props; 18 | return [ 19 | 20 | ShardId 21 | {tx.increaseAssetSupply.shardId} 22 | , 23 |
, 24 | 25 | Parameters 26 | 27 |
28 | {_.map(tx.increaseAssetSupply.parameters, (parameter, i) => { 29 | return
{parameter}
; 30 | })} 31 |
32 | 33 |
, 34 |
, 35 | 36 | Recipient 37 | 38 | {tx.increaseAssetSupply.recipient ? ( 39 | 40 | {tx.increaseAssetSupply.recipient} 41 | 42 | ) : ( 43 | "Unknown" 44 | )} 45 | 46 | , 47 |
, 48 | 49 | LockScriptHash 50 | {getLockScriptName(tx.increaseAssetSupply.lockScriptHash)} 51 | , 52 |
, 53 | 54 | Approvals 55 | 56 | {tx.increaseAssetSupply.approvals.length !== 0 ? ( 57 |
58 | {_.map(tx.increaseAssetSupply.approvals, (approval, i) => { 59 | return
{approval}
; 60 | })} 61 |
62 | ) : ( 63 | "None" 64 | )} 65 | 66 |
, 67 |
, 68 | 69 | AssetType 70 | 71 | 77 | 81 | 82 | , 83 |
, 84 | 85 | Supply 86 | {tx.increaseAssetSupply.supply} 87 | , 88 |
89 | ]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/PayDetails/PayDetails.tsx: -------------------------------------------------------------------------------- 1 | import { PayTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import { Link } from "react-router-dom"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | 7 | interface Props { 8 | tx: TransactionDoc; 9 | } 10 | 11 | export default class PayDetails extends React.Component { 12 | public render() { 13 | const { tx } = this.props; 14 | const transaction = tx as PayTransactionDoc; 15 | return [ 16 | 17 | Quantity 18 | 19 | {transaction.pay.quantity} 20 | CCC 21 | 22 | , 23 |
, 24 | 25 | Receiver 26 | 27 | {transaction.pay.receiver} 28 | 29 | , 30 |
31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/RemoveDetails/RemoveDetails.tsx: -------------------------------------------------------------------------------- 1 | import { RemoveTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import Col from "reactstrap/lib/Col"; 4 | import Row from "reactstrap/lib/Row"; 5 | 6 | interface Props { 7 | tx: TransactionDoc; 8 | } 9 | 10 | export default class RemoveDetails extends React.Component { 11 | public render() { 12 | const { tx } = this.props; 13 | const transaction = tx as RemoveTransactionDoc; 14 | return [ 15 | 16 | Signature 17 | 18 |
{transaction.remove.signature}
19 | 20 |
, 21 |
, 22 | 23 | Text hash 24 | {transaction.remove.textHash} 25 | , 26 |
27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/SetRegularKeyDetails/SetRegularKeyDetails.tsx: -------------------------------------------------------------------------------- 1 | import { SetRegularKeyTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import Col from "reactstrap/lib/Col"; 4 | import Row from "reactstrap/lib/Row"; 5 | 6 | interface Props { 7 | tx: TransactionDoc; 8 | } 9 | 10 | export default class SetRegularKeyDetails extends React.Component { 11 | public render() { 12 | const { tx } = this.props; 13 | const transaction = tx as SetRegularKeyTransactionDoc; 14 | return [ 15 | 16 | Key 17 | {transaction.setRegularKey.key} 18 | , 19 |
20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/SetShardOwnersDetails/SetShardOwnersDetails.tsx: -------------------------------------------------------------------------------- 1 | import { SetShardOwnersTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | 7 | interface Props { 8 | tx: TransactionDoc; 9 | } 10 | 11 | export default class SetShardOwnersDetails extends React.Component { 12 | public render() { 13 | const { tx } = this.props; 14 | const transaction = tx as SetShardOwnersTransactionDoc; 15 | return [ 16 | 17 | ShardId 18 | {transaction.setShardOwners.shardId} 19 | , 20 |
, 21 | 22 | Owners 23 | 24 | {transaction.setShardOwners.owners.length !== 0 ? ( 25 |
26 | {_.map(transaction.setShardOwners.owners, (owner, i) => { 27 | return
{owner}
; 28 | })} 29 |
30 | ) : ( 31 | "None" 32 | )} 33 | 34 |
, 35 |
36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/SetShardUsersDetails/SetShardUsersDetails.tsx: -------------------------------------------------------------------------------- 1 | import { SetShardUsersTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | 7 | interface Props { 8 | tx: TransactionDoc; 9 | } 10 | 11 | export default class SetShardUsersDetails extends React.Component { 12 | public render() { 13 | const { tx } = this.props; 14 | const transaction = tx as SetShardUsersTransactionDoc; 15 | return [ 16 | 17 | ShardId 18 | {transaction.setShardUsers.shardId} 19 | , 20 |
, 21 | 22 | Users 23 | 24 | {transaction.setShardUsers.users.length !== 0 ? ( 25 |
26 | {_.map(transaction.setShardUsers.users, (user, i) => { 27 | return
{user}
; 28 | })} 29 |
30 | ) : ( 31 | "None" 32 | )} 33 | 34 |
, 35 |
36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/StoreDetails/StoreDetails.tsx: -------------------------------------------------------------------------------- 1 | import { StoreTransactionDoc, TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import { Link } from "react-router-dom"; 4 | import Col from "reactstrap/lib/Col"; 5 | import Row from "reactstrap/lib/Row"; 6 | 7 | interface Props { 8 | tx: TransactionDoc; 9 | } 10 | 11 | export default class StoreDetails extends React.Component { 12 | public render() { 13 | const { tx } = this.props; 14 | const transaction = tx as StoreTransactionDoc; 15 | return [ 16 | 17 | Content 18 | 19 |
{transaction.store.content}
20 | 21 |
, 22 |
, 23 | 24 | Signature 25 | 26 |
{transaction.store.signature}
27 | 28 |
, 29 |
, 30 | 31 | Certifier 32 | 33 | {transaction.store.certifier} 34 | 35 | , 36 |
37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/UnwrapCCCDetails/UnwrapCCCDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc, UnwrapCCCTransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import Col from "reactstrap/lib/Col"; 4 | import Row from "reactstrap/lib/Row"; 5 | import { CommaNumberString } from "src/components/util/CommaNumberString/CommaNumberString"; 6 | 7 | interface Props { 8 | tx: TransactionDoc; 9 | } 10 | 11 | export default class UnwrapCCCDetails extends React.Component { 12 | public render() { 13 | const { tx } = this.props; 14 | const transaction = tx as UnwrapCCCTransactionDoc; 15 | return [ 16 | 17 | Receiver 18 | {transaction.unwrapCCC.receiver} 19 | , 20 |
, 21 | 22 | Quantity 23 | 24 | 25 | CCC 26 | 27 | , 28 |
29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/DetailsByType/WrapCCCDetails/WrapCCCDetails.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc, WrapCCCTransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import { Link } from "react-router-dom"; 5 | import Col from "reactstrap/lib/Col"; 6 | import Row from "reactstrap/lib/Row"; 7 | import { CommaNumberString } from "src/components/util/CommaNumberString/CommaNumberString"; 8 | import { getLockScriptName } from "src/utils/Transactions"; 9 | 10 | interface Props { 11 | tx: TransactionDoc; 12 | } 13 | 14 | export default class WrapCCCDetails extends React.Component { 15 | public render() { 16 | const { tx } = this.props; 17 | const transaction = tx as WrapCCCTransactionDoc; 18 | return [ 19 | 20 | ShardId 21 | {transaction.wrapCCC.shardId} 22 | , 23 |
, 24 | 25 | LockScriptHash 26 | {getLockScriptName(transaction.wrapCCC.lockScriptHash)} 27 | , 28 |
, 29 | 30 | Parameters 31 | 32 |
33 | {_.map(transaction.wrapCCC.parameters, (parameter, i) => { 34 | return
{parameter}
; 35 | })} 36 |
37 | 38 |
, 39 |
, 40 | 41 | Recipient 42 | 43 | {transaction.wrapCCC.recipient ? ( 44 | {transaction.wrapCCC.recipient} 45 | ) : ( 46 | "Unknown" 47 | )} 48 | 49 | , 50 |
, 51 | 52 | Quantity 53 | 54 | 55 | CCC 56 | 57 | , 58 |
59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/MoreInfo/AssetOutput/AssetOutput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Col from "reactstrap/lib/Col"; 3 | import Row from "reactstrap/lib/Row"; 4 | import * as Metadata from "../../../../../utils/Metadata"; 5 | import DataSet from "../../../../util/DataSet/DataSet"; 6 | 7 | interface Props { 8 | asset: Asset; 9 | } 10 | 11 | export interface Asset { 12 | networkId: string; 13 | shardId: number; 14 | metadata: string; 15 | approver: string | null; 16 | registrar: string | null; 17 | allowedScriptHashes: string[]; 18 | approvals: string[]; 19 | lockScriptHash: string; 20 | parameters: string[]; 21 | supply: string; 22 | assetName?: string; 23 | assetType: string; 24 | recipient: string; 25 | } 26 | 27 | export default class AssetOutput extends React.Component { 28 | public render() { 29 | const { asset } = this.props; 30 | const metadata = Metadata.parseMetadata(asset.metadata); 31 | return [ 32 | 33 | 34 |

Metadata

35 |
36 | 37 |
, 38 | 39 | 40 | 41 | 42 | Name 43 | {metadata.name ? metadata.name : "None"} 44 | 45 |
46 | 47 | Description 48 | 49 |
{metadata.description ? metadata.description : "None"}
50 | 51 |
52 |
53 | 54 | Icon 55 | 56 |
{metadata.icon_url ? metadata.icon_url : "None"}
57 | 58 |
59 |
60 | 61 | Raw data 62 | 63 |
{asset.metadata}
64 | 65 |
66 |
67 |
68 | 69 |
70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/MoreInfo/MoreInfo.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as _ from "lodash"; 3 | import * as React from "react"; 4 | import AssetOutput from "./AssetOutput/AssetOutput"; 5 | import AssetTransferInputs from "./AssetTransferInputs/AssetTransferInputs"; 6 | import AssetTransferOrders from "./AssetTransferOrders/AssetTransferOrders"; 7 | import AssetTransferOutputs from "./AssetTransferOutputs/AssetTransferOutputs"; 8 | 9 | interface Props { 10 | tx: TransactionDoc; 11 | } 12 | 13 | interface State { 14 | pageForInput: number; 15 | pageForOutput: number; 16 | pageForBurn: number; 17 | } 18 | 19 | export default class MoreInfo extends React.Component { 20 | constructor(props: Props) { 21 | super(props); 22 | this.state = { 23 | pageForInput: 1, 24 | pageForOutput: 1, 25 | pageForBurn: 1 26 | }; 27 | } 28 | 29 | public render() { 30 | const { tx: transaction } = this.props; 31 | if (transaction.type === "transferAsset") { 32 | return [ 33 | , 34 | , 35 | , 36 | 37 | ]; 38 | } else if (transaction.type === "mintAsset") { 39 | return []; 40 | } else if (transaction.type === "unwrapCCC") { 41 | return []; 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/TransactionDetails.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/transaction/TransactionDetails/TransactionDetails.scss -------------------------------------------------------------------------------- /src/components/transaction/TransactionDetails/TransactionDetails.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | 4 | import { Col, Row } from "reactstrap"; 5 | 6 | import { TransactionDoc } from "codechain-indexer-types"; 7 | import DataSet from "../../util/DataSet/DataSet"; 8 | import CommonDetails from "./CommonDetails/CommonDetails"; 9 | import DetailsByType from "./DetailsByType/DetailsByType"; 10 | import MoreInfo from "./MoreInfo/MoreInfo"; 11 | import "./TransactionDetails.scss"; 12 | 13 | interface Props { 14 | transaction: TransactionDoc; 15 | } 16 | 17 | export default class TransactionDetails extends React.Component { 18 | public render() { 19 | const { transaction } = this.props; 20 | return ( 21 |
22 | 23 | 24 |

Details

25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionList/TransactionList.scss: -------------------------------------------------------------------------------- 1 | @import "../../../mixins.scss"; 2 | @import "../../../variables.scss"; 3 | .parcel-transaction-list { 4 | .input-output-container { 5 | background-color: $background-gray; 6 | padding-left: 8px; 7 | padding-right: 8px; 8 | margin-top: 6px; 9 | margin-bottom: 6px; 10 | } 11 | .view-more-transfer-btn { 12 | margin-bottom: 6px; 13 | } 14 | .input-highlight { 15 | background-color: #f9b7a9; 16 | } 17 | .output-highlight { 18 | background-color: #c1eed4; 19 | } 20 | .arrow-icon { 21 | color: #bdbdbd; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionList/TransactionList.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { Col, Row } from "reactstrap"; 4 | 5 | import { faSpinner } from "@fortawesome/free-solid-svg-icons"; 6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 7 | import { TransactionDoc } from "codechain-indexer-types"; 8 | import { H160 } from "codechain-sdk/lib/core/classes"; 9 | 10 | import TransactionListItems from "../TransactionListItems/TransactionListItems"; 11 | 12 | import "./TransactionList.scss"; 13 | 14 | interface OwnProps { 15 | owner?: string; 16 | assetType?: H160; 17 | transactions: TransactionDoc[]; 18 | loadMoreAction?: () => void; 19 | hideMoreButton?: boolean; 20 | } 21 | 22 | interface State { 23 | page: number; 24 | } 25 | 26 | type Props = OwnProps; 27 | 28 | class TransactionList extends React.Component { 29 | private itemPerPage = 6; 30 | constructor(props: Props) { 31 | super(props); 32 | this.state = { 33 | page: 1 34 | }; 35 | } 36 | 37 | public render() { 38 | const { page } = this.state; 39 | const { transactions, loadMoreAction, hideMoreButton, owner, assetType } = this.props; 40 | let loadedTransactions; 41 | if (loadMoreAction) { 42 | loadedTransactions = transactions; 43 | } else { 44 | loadedTransactions = transactions.slice(0, this.itemPerPage * page); 45 | } 46 | return ( 47 |
48 | {this.renderTransactionListTitle()} 49 | {transactions.length === 0 ? ( 50 |
51 | 52 |
53 | ) : null} 54 | 55 | {!hideMoreButton && (loadMoreAction || this.itemPerPage * page < transactions.length) ? ( 56 | 57 | 58 |
59 | 62 |
63 | 64 |
65 | ) : null} 66 |
67 | ); 68 | } 69 | 70 | private renderTransactionListTitle = () => { 71 | const { transactions } = this.props; 72 | return ( 73 | 74 | 75 |
76 | {transactions.length === 1 ?

Transaction

:

Transactions

} 77 |
78 |
79 | 80 |
81 | ); 82 | }; 83 | 84 | private loadMore = (e: any) => { 85 | e.preventDefault(); 86 | if (this.props.loadMoreAction) { 87 | this.props.loadMoreAction(); 88 | } else { 89 | this.setState({ page: this.state.page + 1 }); 90 | } 91 | }; 92 | } 93 | 94 | export default TransactionList; 95 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionList/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/transaction/TransactionList/img/arrow.png -------------------------------------------------------------------------------- /src/components/transaction/TransactionSummary/AssetIcon.tsx: -------------------------------------------------------------------------------- 1 | import { AssetSchemeDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | 4 | import { ImageLoader } from "src/components/util/ImageLoader/ImageLoader"; 5 | import { RequestAssetScheme } from "src/request"; 6 | import * as Metadata from "../../../utils/Metadata"; 7 | 8 | interface OwnProps { 9 | assetType: string; 10 | index: number; 11 | amount: string; 12 | owner: string; 13 | type: "input" | "output" | "burn"; 14 | onClick: () => void; 15 | onMouseEnter: (target: string, name: string, amount: string, owner: string) => void; 16 | onMouseLeave: () => void; 17 | } 18 | 19 | interface State { 20 | name?: string; 21 | err?: string; 22 | } 23 | 24 | class AssetIcon extends React.Component { 25 | constructor(props: OwnProps) { 26 | super(props); 27 | this.state = {}; 28 | } 29 | 30 | public render() { 31 | const { name, err } = this.state; 32 | const { index, assetType, type } = this.props; 33 | if (name == null && err == null) { 34 | return ( 35 | 41 | ); 42 | } 43 | const { onClick, onMouseLeave } = this.props; 44 | const targetId = `icon-${index}-${assetType}-${type}`; 45 | return ( 46 |
54 | 55 |
56 | ); 57 | } 58 | 59 | private onMouseEnter = () => { 60 | const { index, assetType, type, amount, owner } = this.props; 61 | const { name } = this.state; 62 | if (name) { 63 | const targetId = `icon-${index}-${assetType}-${type}`; 64 | this.props.onMouseEnter(targetId, name, amount, owner); 65 | } 66 | }; 67 | 68 | private onAssetScheme = (assetScheme: AssetSchemeDoc) => { 69 | const { name = `0x${this.props.assetType}` } = Metadata.parseMetadata(assetScheme.metadata); 70 | this.setState({ 71 | name 72 | }); 73 | }; 74 | 75 | private onAssetSchemeNotExist = () => { 76 | this.setState({ 77 | name: "Invalid AssetType" 78 | }); 79 | }; 80 | 81 | private onError = () => { 82 | this.setState({ 83 | err: "" 84 | }); 85 | }; 86 | } 87 | 88 | export default AssetIcon; 89 | -------------------------------------------------------------------------------- /src/components/transaction/TransactionSummary/TransactionSummary.scss: -------------------------------------------------------------------------------- 1 | @import "../../../variables.scss"; 2 | @import "../../../mixins.scss"; 3 | 4 | .transaction-summary { 5 | .arrow-icon { 6 | color: #bdbdbd; 7 | } 8 | 9 | .summary-item { 10 | border: 1px solid $line-color; 11 | 12 | .title-panel { 13 | border-bottom: 1px solid $line-color; 14 | padding: 6px; 15 | background-color: $background-gray; 16 | 17 | .burn-title { 18 | color: #9f3636; 19 | } 20 | } 21 | 22 | .item-panel { 23 | padding-top: 20px; 24 | padding-left: 20px; 25 | padding-right: 20px; 26 | padding-bottom: 10px; 27 | 28 | .icon { 29 | margin-right: 20px; 30 | margin-bottom: 10px; 31 | width: 42px; 32 | height: 42px; 33 | } 34 | } 35 | 36 | .content-panel { 37 | padding: 20px; 38 | 39 | .content-item { 40 | .content-title { 41 | max-width: 200px; 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | } 45 | 46 | .content-description { 47 | margin-top: 5px; 48 | @include small-text; 49 | } 50 | } 51 | } 52 | 53 | .registrar-panel { 54 | border-top: 1px solid $line-color; 55 | padding: 6px; 56 | 57 | .registrar-text { 58 | flex-grow: 1; 59 | text-overflow: ellipsis; 60 | white-space: nowrap; 61 | text-align: right; 62 | } 63 | } 64 | } 65 | } 66 | 67 | .popover { 68 | max-width: 400px; 69 | } 70 | -------------------------------------------------------------------------------- /src/components/util/CommaNumberString/CommaNumberString.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface Props { 4 | text: string; 5 | className?: string; 6 | } 7 | 8 | const numberWithCommas = (x: string) => { 9 | return x.replace(/\B(?=(\d{3})+(?!\d))/g, ","); 10 | }; 11 | 12 | export const CommaNumberString = (props: Props) => { 13 | const { className, text } = props; 14 | const splitedText = text.split("."); 15 | if (splitedText.length === 1) { 16 | return {numberWithCommas(text)}; 17 | } else { 18 | return {`${numberWithCommas(splitedText[0])}.${splitedText[1]}`}; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/util/CopyButton/CopyButton.scss: -------------------------------------------------------------------------------- 1 | @import "../../../variables.scss"; 2 | .copy-button { 3 | .copy { 4 | vertical-align: top; 5 | height: 30px; 6 | margin-right: 3px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/util/CopyButton/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import { faCopy } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import * as React from "react"; 4 | import * as CopyToClipboard from "react-copy-to-clipboard"; 5 | import { Popover, PopoverBody } from "reactstrap"; 6 | import "./CopyButton.scss"; 7 | 8 | interface Props { 9 | copyString: string; 10 | className?: string; 11 | } 12 | 13 | interface State { 14 | copyPopoverOpen: boolean; 15 | } 16 | 17 | class CopyButton extends React.Component { 18 | constructor(props: Props) { 19 | super(props); 20 | this.state = { 21 | copyPopoverOpen: false 22 | }; 23 | } 24 | 25 | public render() { 26 | const { copyString, className } = this.props; 27 | const { copyPopoverOpen } = this.state; 28 | return ( 29 |
30 | 31 | 34 | 35 | 36 | Copied to clipboard 37 | 38 |
39 | ); 40 | } 41 | 42 | private toggleCopyPopover = () => { 43 | this.setState({ 44 | copyPopoverOpen: !this.state.copyPopoverOpen 45 | }); 46 | }; 47 | } 48 | 49 | export default CopyButton; 50 | -------------------------------------------------------------------------------- /src/components/util/DataSet/DataSet.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | 4 | interface Props { 5 | className?: string; 6 | isStatus?: boolean; 7 | } 8 | 9 | class DataSet extends React.Component { 10 | private refList: Array>; 11 | constructor(props: Props) { 12 | super(props); 13 | } 14 | 15 | public componentDidMount() { 16 | _.each(this.refList, ref => { 17 | if (ref.current) { 18 | ref.current.setAttribute("title", ref.current.innerText); 19 | } 20 | }); 21 | } 22 | 23 | public componentDidUpdate() { 24 | _.each(this.refList, ref => { 25 | if (ref.current) { 26 | ref.current.setAttribute("title", ref.current.innerText); 27 | } 28 | }); 29 | } 30 | 31 | public render() { 32 | const { children, isStatus, className } = this.props; 33 | if (!children) { 34 | return null; 35 | } 36 | this.refList = []; 37 | const childrenWithTitle = React.Children.map(children, (child: React.ReactElement) => { 38 | if (!child) { 39 | return null; 40 | } 41 | if (child.type === "hr") { 42 | return child; 43 | } else { 44 | const childList = React.Children.map( 45 | child.props.children, 46 | (childInner: React.ReactElement, index: number) => { 47 | if (index === 0) { 48 | return childInner; 49 | } else { 50 | if (!childInner || !childInner.props.children) { 51 | return null; 52 | } 53 | if ( 54 | childInner.props.children.type === "div" && 55 | childInner.props.children.props.className === "text-area" 56 | ) { 57 | return childInner; 58 | } 59 | const newRef = React.createRef(); 60 | this.refList.push(newRef); 61 | const wrapper = React.createElement( 62 | "div", 63 | { ref: newRef, className: "wrapper-for-title" }, 64 | childInner.props.children 65 | ); 66 | return React.cloneElement(childInner, {}, wrapper); 67 | } 68 | } 69 | ); 70 | return React.cloneElement(child, {}, childList); 71 | } 72 | }); 73 | return ( 74 |
{childrenWithTitle}
75 | ); 76 | } 77 | } 78 | 79 | export default DataSet; 80 | -------------------------------------------------------------------------------- /src/components/util/DataTable/DataTable.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { Table } from "reactstrap"; 4 | 5 | interface Props { 6 | className?: string; 7 | } 8 | 9 | class DataTable extends React.Component { 10 | private refList: Array>; 11 | constructor(props: {}) { 12 | super(props); 13 | } 14 | 15 | public componentDidMount() { 16 | _.each(this.refList, ref => { 17 | if (ref.current) { 18 | ref.current.setAttribute("title", ref.current.innerText); 19 | } 20 | }); 21 | } 22 | 23 | public componentDidUpdate() { 24 | _.each(this.refList, ref => { 25 | if (ref.current) { 26 | ref.current.setAttribute("title", ref.current.innerText); 27 | } 28 | }); 29 | } 30 | 31 | public render() { 32 | const { className, children } = this.props; 33 | if (!children) { 34 | return null; 35 | } 36 | this.refList = []; 37 | const tbody = children[1]; 38 | let cloneTbody; 39 | if (tbody) { 40 | cloneTbody = React.Children.map(tbody.props.children, (tr: any) => { 41 | if (tr == null) { 42 | return; 43 | } 44 | const tdList = React.Children.map(tr.props.children, (td: any) => { 45 | const newRef = React.createRef(); 46 | this.refList.push(newRef); 47 | return React.cloneElement(td, { 48 | ref: newRef 49 | }); 50 | }); 51 | return React.cloneElement(tr, {}, tdList); 52 | }); 53 | } 54 | return ( 55 | 56 | {children[0]} 57 | {tbody ? {cloneTbody} : null} 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default DataTable; 64 | -------------------------------------------------------------------------------- /src/components/util/HealthChecker/HealthChecker.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeChain-io/codechain-explorer/249752d2a853dcbc783053884384b90a74d3c6c3/src/components/util/HealthChecker/HealthChecker.scss -------------------------------------------------------------------------------- /src/components/util/HealthChecker/HealthChecker.tsx: -------------------------------------------------------------------------------- 1 | import { faCircle } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import * as React from "react"; 4 | 5 | import { RequestPing } from "../../../request"; 6 | 7 | import "./HealthChecker.scss"; 8 | 9 | interface State { 10 | isNodeAlive?: boolean; 11 | isDead: boolean; 12 | } 13 | 14 | interface Props { 15 | isSimple?: boolean; 16 | } 17 | 18 | class HealthChecker extends React.Component { 19 | constructor(props: {}) { 20 | super(props); 21 | this.state = { 22 | isDead: false 23 | }; 24 | } 25 | public render() { 26 | const { isSimple } = this.props; 27 | const { isNodeAlive, isDead } = this.state; 28 | if (isNodeAlive === undefined) { 29 | return ( 30 |
31 | {isSimple ? "" : "Status"}{" "} 32 | 33 | 34 | 35 | 36 |
37 | ); 38 | } 39 | return ( 40 |
41 | {isNodeAlive ? ( 42 |
43 | {isSimple ? "" : "Status"}{" "} 44 | 45 | 46 | 47 |
48 | ) : ( 49 |
50 | {isSimple ? "" : "Status"}{" "} 51 | 52 | 53 | 54 |
55 | )} 56 | {!isDead ? ( 57 | 63 | ) : null} 64 |
65 | ); 66 | } 67 | 68 | private onPong = () => { 69 | this.setState({ isNodeAlive: true }); 70 | }; 71 | 72 | private onError = () => { 73 | this.setState({ isNodeAlive: false }); 74 | }; 75 | 76 | private serverDeadNotify = () => { 77 | this.setState({ isDead: true }); 78 | }; 79 | } 80 | 81 | export default HealthChecker; 82 | -------------------------------------------------------------------------------- /src/components/util/HexString/HexString.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | interface Props { 5 | text: string; 6 | link?: string; 7 | length?: number; 8 | } 9 | 10 | const HexString = (props: Props) => { 11 | const { link, length, text } = props; 12 | let sliceLength = text.length; 13 | if (length) { 14 | sliceLength = length; 15 | } 16 | if (link) { 17 | return ( 18 | 19 | {`0x${length ? text.slice(0, sliceLength) + "..." : text}`} 20 | 21 | ); 22 | } else { 23 | return {`0x${length ? text.slice(0, sliceLength) + "..." : text}`}; 24 | } 25 | }; 26 | 27 | export default HexString; 28 | -------------------------------------------------------------------------------- /src/components/util/ImageLoader/ImageLoader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | const Identicon = require("identicon.js"); 3 | const sha256 = require("js-sha256"); 4 | 5 | interface Props { 6 | data: string; 7 | className?: string; 8 | size: number; 9 | isAssetImage: boolean; 10 | } 11 | interface State { 12 | requestUrl?: string; 13 | } 14 | 15 | export class ImageLoader extends React.Component { 16 | private host = process.env.REACT_APP_SERVER_HOST ? process.env.REACT_APP_SERVER_HOST : "http://localhost:9001"; 17 | constructor(prop: Props) { 18 | super(prop); 19 | let requestUrl; 20 | if (prop.isAssetImage) { 21 | requestUrl = `${this.host}/api/asset-image/${prop.data}`; 22 | } else { 23 | requestUrl = this.getDefaultImage(); 24 | } 25 | this.state = { 26 | requestUrl 27 | }; 28 | } 29 | 30 | public render() { 31 | const { className, size } = this.props; 32 | const { requestUrl } = this.state; 33 | 34 | return ( 35 | 41 | ); 42 | } 43 | 44 | private getDefaultImage = () => { 45 | const hash = sha256.create(); 46 | hash.update(this.props.data); 47 | const identiconData = new Identicon(hash.hex(), this.props.size).toString(); 48 | return `data:image/png;base64,${identiconData}`; 49 | }; 50 | 51 | private fallback = () => { 52 | this.setState({ requestUrl: this.getDefaultImage() }); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/components/util/ScrollToTop/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | 4 | class ScrollToTop extends React.Component { 5 | public componentDidUpdate(prevProps: any) { 6 | if (this.props.location !== prevProps.location) { 7 | window.scrollTo(0, 0); 8 | } 9 | } 10 | 11 | public render() { 12 | return this.props.children; 13 | } 14 | } 15 | 16 | export default withRouter(ScrollToTop); 17 | -------------------------------------------------------------------------------- /src/components/util/StatusBadge/StatusBadge.scss: -------------------------------------------------------------------------------- 1 | .status-badge { 2 | @-moz-keyframes spin { 3 | to { 4 | -moz-transform: rotate(360deg); 5 | } 6 | } 7 | @-webkit-keyframes spin { 8 | to { 9 | -webkit-transform: rotate(360deg); 10 | } 11 | } 12 | @keyframes spin { 13 | to { 14 | transform: rotate(360deg); 15 | } 16 | } 17 | 18 | .spin { 19 | animation: spin 2000ms linear infinite; 20 | margin-right: 5px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/util/StatusBadge/StatusBadge.tsx: -------------------------------------------------------------------------------- 1 | import { faCircle, faSpinner } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import { TransactionDoc } from "codechain-indexer-types"; 4 | import * as moment from "moment"; 5 | import * as React from "react"; 6 | import "./StatusBadge.scss"; 7 | 8 | interface Props { 9 | tx: TransactionDoc; 10 | className?: string; 11 | } 12 | 13 | const getBadgeBackgroundColorClassByStatus = (tx: TransactionDoc) => { 14 | if (tx.isPending) { 15 | return "text-warning"; 16 | } else { 17 | return "text-success"; 18 | } 19 | }; 20 | 21 | const getStatusString = (tx: TransactionDoc) => { 22 | if (tx.isPending) { 23 | return ( 24 | 25 | Pending( 26 | 27 | {moment.unix(tx.pendingTimestamp!).fromNow()}) 28 | 29 | ); 30 | } else { 31 | return "Confirmed"; 32 | } 33 | }; 34 | 35 | export const StatusBadge = (props: Props) => { 36 | const { className, tx } = props; 37 | return ( 38 | 39 | {" "} 40 | {getStatusString(tx)} 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/components/util/TypeBadge/TypeBadge.scss: -------------------------------------------------------------------------------- 1 | @import "../../../variables.scss"; 2 | 3 | .type-badge { 4 | color: $primary-color; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/util/TypeBadge/TypeBadge.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import "./TypeBadge.scss"; 4 | 5 | interface Props { 6 | transaction: TransactionDoc; 7 | className?: string; 8 | } 9 | 10 | function capitalizeFirstLetter(str: string) { 11 | return str[0].toUpperCase() + str.slice(1); 12 | } 13 | 14 | export const TypeBadge = (props: Props) => { 15 | const { className, transaction } = props; 16 | return {capitalizeFirstLetter(transaction.type)}; 17 | }; 18 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Montserrat:300,400,600,700|Roboto+Mono:300,400"); 2 | @import "mixins.scss"; 3 | @import "variables.scss"; 4 | @import "globals.scss"; 5 | @import "~animate.css/animate.css"; 6 | 7 | html { 8 | font-size: 14px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | font-family: "Montserrat", sans-serif; 15 | color: $dark-color; 16 | } 17 | 18 | a { 19 | color: #2d9cdb; 20 | &:hover { 21 | text-decoration: none; 22 | } 23 | } 24 | 25 | #page { 26 | min-height: 100vh; 27 | #content { 28 | padding-top: 53px; 29 | min-height: calc(100vh - 100px); 30 | overflow: auto; 31 | @include respond-below(md) { 32 | & { 33 | padding-top: 104px; 34 | } 35 | } 36 | } 37 | } 38 | 39 | .table-striped > tbody > tr:nth-child(odd) > td, 40 | .table-striped > tbody > tr:nth-child(odd) > th { 41 | background-color: white; 42 | } 43 | 44 | .table-striped > tbody > tr:nth-child(even) > td, 45 | .table-striped > tbody > tr:nth-child(even) > th { 46 | background-color: #f2f2f2; 47 | } 48 | 49 | // Typography 50 | h1 { 51 | font-family: "Montserrat", sans-serif; 52 | font-weight: 700; 53 | margin-bottom: 0; 54 | } 55 | 56 | h2, 57 | h3 { 58 | font-family: "Montserrat", sans-serif; 59 | font-weight: 600; 60 | margin-bottom: 0; 61 | } 62 | 63 | h1, 64 | h2, 65 | h3 { 66 | color: $primary-color; 67 | margin-bottom: 5px; 68 | } 69 | 70 | h1 { 71 | font-size: 1.71rem; 72 | } 73 | 74 | h2 { 75 | font-size: 1.28rem; 76 | } 77 | 78 | h3 { 79 | font-size: 1rem; 80 | } 81 | 82 | @include respond-below(md) { 83 | h1 { 84 | font-size: 1.5rem; 85 | } 86 | h2 { 87 | font-size: 1.2rem; 88 | } 89 | h3 { 90 | font-size: 1rem; 91 | } 92 | } 93 | 94 | .ccc { 95 | margin-left: 5px; 96 | font-size: 100%; 97 | } 98 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 5 | import { ToastContainer } from "react-toastify"; 6 | import "react-toastify/dist/ReactToastify.css"; 7 | 8 | import "bootstrap/dist/css/bootstrap.min.css"; 9 | import "./index.scss"; 10 | 11 | import Footer from "./components/footer/Footer"; 12 | import Header from "./components/header/Header/Header"; 13 | import ScrollToTop from "./components/util/ScrollToTop/ScrollToTop"; 14 | import Asset from "./pages/Asset/Asset"; 15 | import AssetTransferAddress from "./pages/AssetTransferAddress/AssetTransferAddress"; 16 | import Block from "./pages/Block/Block"; 17 | import Blocks from "./pages/Blocks/Blocks"; 18 | import Home from "./pages/Home/Home"; 19 | import NotFound from "./pages/NotFound/NotFound"; 20 | import PendingTransactions from "./pages/PendingTransactions/PendingTransactions"; 21 | import PlatformAddress from "./pages/PlatformAddress/PlatformAddress"; 22 | import Status from "./pages/Status/Status"; 23 | import Transaction from "./pages/Transaction/Transaction"; 24 | import Transactions from "./pages/Transactions/Transactions"; 25 | import { store } from "./redux/store"; 26 | // import RegisterServiceWorker from "./register_service_worker"; 27 | 28 | ReactDOM.render( 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 |
54 |
, 55 | document.getElementById("root") as HTMLElement 56 | ); 57 | 58 | // RegisterServiceWorker(); 59 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/Asset/Asset.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .asset { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | .title-container { 7 | h1 { 8 | margin-bottom: 5px; 9 | } 10 | .left-container { 11 | vertical-align: top; 12 | margin-right: 10px; 13 | } 14 | .right-container { 15 | max-width: 700px; 16 | flex-grow: 1; 17 | overflow: hidden; 18 | vertical-align: top; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/AssetTransferAddress/AssetTransferAddress.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .asset-transfer-address { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | .title-container { 7 | h1 { 8 | margin-bottom: 5px; 9 | } 10 | .left-container { 11 | vertical-align: top; 12 | margin-right: 10px; 13 | } 14 | .right-container { 15 | max-width: 680px; 16 | flex-grow: 1; 17 | overflow: hidden; 18 | vertical-align: top; 19 | } 20 | .qrcode-container { 21 | margin-left: 5px; 22 | vertical-align: top; 23 | } 24 | } 25 | .big-size-qr { 26 | padding-top: 30px; 27 | display: none; 28 | } 29 | @include respond-below(sm) { 30 | .big-size-qr { 31 | display: block; 32 | } 33 | .qrcode-container { 34 | display: none !important; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/Block/Block.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .block { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | .title-container { 7 | h1 { 8 | .block-number { 9 | @include data-font; 10 | color: $dark-color; 11 | } 12 | } 13 | .timestamp { 14 | @include small-text; 15 | } 16 | } 17 | .square { 18 | color: $primary-color; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/Blocks/Blocks.scss: -------------------------------------------------------------------------------- 1 | .blocks { 2 | padding-top: 60px; 3 | padding-bottom: 108px; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/Home/BlockCapacityUsageChart.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | const { ResponsiveBar } = require("@nivo/bar"); 4 | 5 | import { BlockDoc } from "codechain-indexer-types"; 6 | 7 | interface OwnProps { 8 | blocks: BlockDoc[]; 9 | } 10 | 11 | type Props = OwnProps; 12 | class BlockCapacityUsageChart extends React.Component { 13 | constructor(props: Props) { 14 | super(props); 15 | } 16 | 17 | public render() { 18 | return ( 19 |
20 |
21 |
22 |

Block size

23 |
24 |
{this.renderBarChart()}
25 |
26 |
27 | ); 28 | } 29 | 30 | private renderBarChart() { 31 | const { blocks } = this.props; 32 | const data = blocks 33 | .map(block => { 34 | return { 35 | size: block.size, 36 | blockNumber: block.number.toString(), 37 | color: "hsl(191, 95%, 42%)" 38 | }; 39 | }) 40 | .reverse(); 41 | return ( 42 | e.data.color} 56 | borderColor="inherit:darker(1.6)" 57 | enableLabel={false} 58 | labelSkipWidth={12} 59 | labelSkipHeight={12} 60 | labelTextColor="inherit:darker(1.6)" 61 | axisLeft={{ 62 | format: (tick: string) => (parseInt(tick, 10) % 1000 !== 0 ? "" : parseInt(tick, 10) / 1000 + " KB") 63 | }} 64 | axisBottom={{ 65 | tickValues: this.bottomTickValues(data), 66 | tickPadding: 10 67 | }} 68 | animate={true} 69 | motionStiffness={90} 70 | motionDamping={15} 71 | theme={{ 72 | tooltip: { 73 | container: { 74 | fontSize: "13px" 75 | } 76 | }, 77 | labels: { 78 | textColor: "#555" 79 | } 80 | }} 81 | /> 82 | ); 83 | } 84 | 85 | private bottomTickValues = (data: { blockNumber: string }[]) => { 86 | if (data.length >= 2) { 87 | return [_.first(data)!.blockNumber, _.last(data)!.blockNumber]; 88 | } else { 89 | return undefined; 90 | } 91 | }; 92 | } 93 | 94 | export default BlockCapacityUsageChart; 95 | -------------------------------------------------------------------------------- /src/pages/Home/BlockCreationTimeChart.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | const { ResponsiveBar } = require("@nivo/bar"); 4 | 5 | import { BlockDoc } from "codechain-indexer-types"; 6 | 7 | interface OwnProps { 8 | blocks: BlockDoc[]; 9 | } 10 | 11 | type Props = OwnProps; 12 | class BlockCreationTimeChart extends React.Component { 13 | constructor(props: Props) { 14 | super(props); 15 | } 16 | 17 | public render() { 18 | return ( 19 |
20 |
21 |
22 |

Block creation time

23 |
24 |
{this.renderBarChart()}
25 |
26 |
27 | ); 28 | } 29 | 30 | private renderBarChart() { 31 | const { blocks } = this.props; 32 | const data = 33 | blocks.length < 2 34 | ? [] 35 | : _.range(0, blocks.length - 1) 36 | .map(i => { 37 | return { 38 | creationTime: blocks[i].timestamp - blocks[i + 1].timestamp, 39 | blockNumber: blocks[i].number.toString(), 40 | color: "hsl(191, 95%, 42%)" 41 | }; 42 | }) 43 | .reverse(); 44 | return ( 45 | e.data.color} 59 | borderColor="inherit:darker(1.6)" 60 | enableLabel={false} 61 | labelSkipWidth={12} 62 | labelSkipHeight={12} 63 | labelTextColor="inherit:darker(1.6)" 64 | axisLeft={{ 65 | format: (tick: string) => (tick.indexOf(".") === -1 ? tick : ""), 66 | legend: "seconds", 67 | legendOffset: -40, 68 | legendPosition: "center" 69 | }} 70 | axisBottom={{ 71 | tickValues: this.bottomTickValues(data), 72 | tickPadding: 10 73 | }} 74 | animate={true} 75 | motionStiffness={90} 76 | motionDamping={15} 77 | theme={{ 78 | tooltip: { 79 | container: { 80 | fontSize: "13px" 81 | } 82 | }, 83 | labels: { 84 | textColor: "#555" 85 | } 86 | }} 87 | /> 88 | ); 89 | } 90 | 91 | private bottomTickValues = (data: { blockNumber: string }[]) => { 92 | if (data.length >= 2) { 93 | return [_.first(data)!.blockNumber, _.last(data)!.blockNumber]; 94 | } else { 95 | return undefined; 96 | } 97 | }; 98 | } 99 | 100 | export default BlockCreationTimeChart; 101 | -------------------------------------------------------------------------------- /src/pages/Home/Home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | .home-element-container { 3 | margin-top: 20px; 4 | margin-bottom: 20px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/Home/TransactionsCountByTypeChart.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | const { ResponsiveBar } = require("@nivo/bar"); 4 | 5 | import { BlockDoc } from "codechain-indexer-types"; 6 | 7 | interface OwnProps { 8 | blocks: BlockDoc[]; 9 | } 10 | 11 | type Props = OwnProps; 12 | class TransactionsCountByTypeChart extends React.Component { 13 | constructor(props: Props) { 14 | super(props); 15 | } 16 | 17 | public render() { 18 | return ( 19 |
20 |
21 |
22 |

Transactions

23 |
24 |
{this.renderBarChart()}
25 |
26 |
27 | ); 28 | } 29 | 30 | private renderBarChart() { 31 | const { blocks } = this.props; 32 | const types = Object.keys(blocks.reduce((prev, block) => ({ ...prev, ...block.transactionsCountByType }), {})); 33 | const max = _.max(blocks.map(b => _.sum(_.values(b.transactionsCountByType)))); 34 | const data = blocks 35 | .map(block => { 36 | return { 37 | ...block.transactionsCountByType, 38 | blockNumber: block.number.toString() 39 | }; 40 | }) 41 | .reverse(); 42 | return ( 43 | (tick.indexOf(".") === -1 ? tick : "") 63 | }} 64 | axisBottom={{ 65 | tickValues: this.bottomTickValues(data), 66 | tickPadding: 10 67 | }} 68 | animate={true} 69 | motionStiffness={90} 70 | motionDamping={15} 71 | theme={{ 72 | tooltip: { 73 | container: { 74 | fontSize: "13px" 75 | } 76 | }, 77 | labels: { 78 | textColor: "#555" 79 | } 80 | }} 81 | /> 82 | ); 83 | } 84 | 85 | private bottomTickValues = (data: { blockNumber: string }[]) => { 86 | if (data.length >= 2) { 87 | return [_.first(data)!.blockNumber, _.last(data)!.blockNumber]; 88 | } else { 89 | return undefined; 90 | } 91 | }; 92 | } 93 | 94 | export default TransactionsCountByTypeChart; 95 | -------------------------------------------------------------------------------- /src/pages/NotFound/NotFound.scss: -------------------------------------------------------------------------------- 1 | .not-found { 2 | padding-top: 60px; 3 | padding-bottom: 108px; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Error } from "../../components/error/Error/Error"; 3 | 4 | import "./NotFound.scss"; 5 | 6 | interface Props { 7 | location: any; 8 | } 9 | 10 | class NotFound extends React.Component { 11 | constructor(props: Props) { 12 | super(props); 13 | } 14 | 15 | public render() { 16 | const { location } = this.props; 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default NotFound; 26 | -------------------------------------------------------------------------------- /src/pages/PendingTransactions/PendingTransactions.scss: -------------------------------------------------------------------------------- 1 | .Pending-transactions { 2 | padding-top: 60px; 3 | padding-bottom: 108px; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/PlatformAddress/PlatformAddress.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .platform-address { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | .title-container { 7 | h1 { 8 | margin-bottom: 5px; 9 | } 10 | .left-container { 11 | vertical-align: top; 12 | margin-right: 10px; 13 | } 14 | .right-container { 15 | max-width: 680px; 16 | flex-grow: 1; 17 | overflow: hidden; 18 | vertical-align: top; 19 | } 20 | .qrcode-container { 21 | margin-left: 5px; 22 | vertical-align: top; 23 | } 24 | } 25 | .big-size-qr { 26 | padding-top: 30px; 27 | display: none; 28 | } 29 | .reason-filter-container { 30 | margin-top: 18px; 31 | margin-bottom: 20px; 32 | border: 1px solid $primary-color; 33 | padding: 18px; 34 | 35 | .filter-title { 36 | margin-bottom: 18px; 37 | span { 38 | font-weight: bold; 39 | } 40 | } 41 | 42 | .hide-reason-filter-container { 43 | margin-top: 20px; 44 | } 45 | } 46 | 47 | .show-reason-filter-container { 48 | margin-top: 20px; 49 | margin-bottom: 20px; 50 | } 51 | 52 | .filter-btn { 53 | cursor: pointer; 54 | text-decoration: underline; 55 | } 56 | @include respond-below(sm) { 57 | .big-size-qr { 58 | display: block; 59 | } 60 | .qrcode-container { 61 | display: none !important; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/pages/Status/Status.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .status { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/Transaction/Transaction.scss: -------------------------------------------------------------------------------- 1 | @import "../../mixins.scss"; 2 | @import "../../variables.scss"; 3 | .transaction { 4 | padding-top: 60px; 5 | padding-bottom: 100px; 6 | .title-container { 7 | .timestamp { 8 | @include small-text; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/Transactions/Transactions.scss: -------------------------------------------------------------------------------- 1 | @import "../../variables.scss"; 2 | 3 | .transactions { 4 | padding-top: 60px; 5 | padding-bottom: 108px; 6 | 7 | .type-filter-container { 8 | margin-top: 18px; 9 | border: 1px solid $primary-color; 10 | padding: 18px; 11 | 12 | .filter-title { 13 | margin-bottom: 18px; 14 | span { 15 | font-weight: bold; 16 | } 17 | } 18 | 19 | .hide-type-filter-container { 20 | margin-top: 20px; 21 | } 22 | } 23 | 24 | .show-type-filter-container { 25 | margin-top: 20px; 26 | } 27 | 28 | .filter-btn { 29 | cursor: pointer; 30 | text-decoration: underline; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { loadingBarMiddleware } from "react-redux-loading-bar"; 2 | import { applyMiddleware, createStore } from "redux"; 3 | import { rootReducer } from "./actions"; 4 | 5 | export const store = createStore( 6 | rootReducer, 7 | applyMiddleware( 8 | loadingBarMiddleware({ 9 | scope: "searchBar" 10 | }) 11 | ) 12 | ); 13 | -------------------------------------------------------------------------------- /src/request/ApiRequest.tsx: -------------------------------------------------------------------------------- 1 | import { hideLoading, showLoading } from "react-redux-loading-bar"; 2 | 3 | interface ApiRequestData { 4 | path: string; 5 | body?: any | null; 6 | dispatch: any; 7 | showProgressBar: boolean; 8 | progressBarTarget?: string; 9 | } 10 | 11 | export interface ApiError { 12 | message: string; 13 | } 14 | 15 | export const apiRequest = ({ path, body, dispatch, progressBarTarget, showProgressBar }: ApiRequestData) => { 16 | const host = process.env.REACT_APP_SERVER_HOST || "http://localhost:9001"; 17 | 18 | const showLoadingBar = () => { 19 | if (showProgressBar) { 20 | dispatch(showLoading(progressBarTarget)); 21 | } 22 | }; 23 | 24 | const hideLoadingBar = () => { 25 | if (showProgressBar) { 26 | dispatch(hideLoading(progressBarTarget)); 27 | } 28 | }; 29 | 30 | return new Promise((resolve, reject) => { 31 | showLoadingBar(); 32 | const timeout = setTimeout(() => { 33 | hideLoadingBar(); 34 | reject(new Error("Request timed out")); 35 | }, 20000); 36 | 37 | fetch( 38 | `${host}/api/${path}`, 39 | body && { 40 | body: JSON.stringify(body), 41 | headers: { "Content-Type": "application/json" }, 42 | method: "POST" 43 | } 44 | ) 45 | .then(async res => { 46 | clearTimeout(timeout); 47 | hideLoadingBar(); 48 | if (res.status < 300) { 49 | resolve(await res.json()); 50 | } else if (res.status < 500) { 51 | throw { message: await res.text() }; 52 | } else { 53 | throw { message: res.statusText }; 54 | } 55 | }) 56 | .catch(err => { 57 | clearTimeout(timeout); 58 | hideLoadingBar(); 59 | reject(err); 60 | }); 61 | }); 62 | }; 63 | -------------------------------------------------------------------------------- /src/request/RequestAssetInfosByName.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { AssetSchemeDoc } from "codechain-indexer-types"; 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | 7 | interface OwnProps { 8 | assetName: string; 9 | onSearchResponse: (assetInfo: { assetType: string; assetScheme: AssetSchemeDoc }[]) => void; 10 | onError: (e: ApiError) => void; 11 | } 12 | 13 | type Props = OwnProps & DispatchProp; 14 | 15 | class RequestAssetInfosByName extends React.Component { 16 | public componentWillMount() { 17 | const { assetName, onSearchResponse, onError, dispatch } = this.props; 18 | apiRequest({ 19 | path: `search/asset/${assetName}`, 20 | dispatch, 21 | showProgressBar: false 22 | }) 23 | .then((response: { assetType: string; assetScheme: AssetSchemeDoc }[]) => { 24 | onSearchResponse(response); 25 | }) 26 | .catch(onError); 27 | } 28 | 29 | public render() { 30 | return null; 31 | } 32 | } 33 | 34 | export default connect()(RequestAssetInfosByName); 35 | -------------------------------------------------------------------------------- /src/request/RequestAssetScheme.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { H160 } from "codechain-sdk/lib/core/classes"; 5 | 6 | import { AssetSchemeDoc } from "codechain-indexer-types"; 7 | import { RootState } from "../redux/actions"; 8 | import { getCurrentTimestamp } from "../utils/Time"; 9 | import { ApiError, apiRequest } from "./ApiRequest"; 10 | 11 | interface OwnProps { 12 | assetType: string; 13 | onAssetScheme: (assetScheme: AssetSchemeDoc, assetType: string) => void; 14 | onAssetSchemeNotExist: () => void; 15 | onError: (e: ApiError) => void; 16 | progressBarTarget?: string; 17 | } 18 | 19 | interface StateProps { 20 | cached: { 21 | data: AssetSchemeDoc; 22 | updatedAt: number; 23 | }; 24 | } 25 | 26 | type Props = OwnProps & StateProps & DispatchProp; 27 | 28 | class RequestAssetScheme extends React.Component { 29 | public componentWillMount() { 30 | const { 31 | cached, 32 | dispatch, 33 | assetType, 34 | onAssetScheme, 35 | onAssetSchemeNotExist, 36 | onError, 37 | progressBarTarget 38 | } = this.props; 39 | if (cached && getCurrentTimestamp() - cached.updatedAt < 10) { 40 | setTimeout(() => onAssetScheme(cached.data, assetType)); 41 | return; 42 | } 43 | apiRequest({ 44 | path: `asset-scheme/${assetType}`, 45 | dispatch, 46 | progressBarTarget, 47 | showProgressBar: false 48 | }) 49 | .then((response: AssetSchemeDoc) => { 50 | if (response === null) { 51 | return onAssetSchemeNotExist(); 52 | } 53 | const assetScheme = response; 54 | const cacheKey = new H160(assetType).value; 55 | dispatch({ 56 | type: "CACHE_ASSET_SCHEME", 57 | data: { 58 | assetType: cacheKey, 59 | assetScheme 60 | } 61 | }); 62 | onAssetScheme(assetScheme, assetType); 63 | }) 64 | .catch(onError); 65 | } 66 | 67 | public render() { 68 | return null; 69 | } 70 | } 71 | 72 | export default connect((state: RootState, props: OwnProps) => { 73 | let cachedAssetScheme; 74 | try { 75 | const cacheKey = new H160(props.assetType).value; 76 | cachedAssetScheme = state.appReducer.assetSchemeByAssetType[cacheKey]; 77 | } catch (e) { 78 | // 79 | } 80 | return { 81 | cached: cachedAssetScheme && { 82 | data: cachedAssetScheme.data, 83 | updatedAt: cachedAssetScheme.updatedAt 84 | } 85 | }; 86 | })(RequestAssetScheme); 87 | -------------------------------------------------------------------------------- /src/request/RequestAssetTransferAddressUTXO.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { AggsUTXODoc } from "codechain-indexer-types"; 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | import { AggsUTXOResponse } from "./RequestAssetTypeUTXO"; 7 | 8 | // FIXME: Support pagination 9 | interface OwnProps { 10 | address: string; 11 | onAggsUTXO: (aggsUTXO: AggsUTXODoc[]) => void; 12 | onError: (e: ApiError) => void; 13 | } 14 | 15 | type Props = OwnProps & DispatchProp; 16 | 17 | class RequestAssetTransferAddressUTXO extends React.Component { 18 | public componentWillMount() { 19 | const { address, onAggsUTXO, onError, dispatch } = this.props; 20 | const path = `aggs-utxo?address=${address}`; 21 | apiRequest({ path, dispatch, showProgressBar: true }) 22 | .then((response: AggsUTXOResponse) => { 23 | onAggsUTXO(response.data); 24 | }) 25 | .catch(onError); 26 | } 27 | 28 | public render() { 29 | return null; 30 | } 31 | } 32 | 33 | export default connect()(RequestAssetTransferAddressUTXO); 34 | -------------------------------------------------------------------------------- /src/request/RequestAssetTypeUTXO.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { AggsUTXODoc } from "codechain-indexer-types"; 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | 7 | export interface AggsUTXOResponse { 8 | data: AggsUTXODoc[]; 9 | } 10 | 11 | // FIXME: Support pagination 12 | interface OwnProps { 13 | assetType: string; 14 | onAggsUTXOs: (aggsUTXOs: AggsUTXODoc[]) => void; 15 | onError: (e: ApiError) => void; 16 | } 17 | 18 | type Props = OwnProps & DispatchProp; 19 | 20 | class RequestAssetTypeUTXO extends React.Component { 21 | public componentWillMount() { 22 | const { assetType, onAggsUTXOs, onError, dispatch } = this.props; 23 | const path = `aggs-utxo?assetType=${assetType}&itemsPerPage=100`; 24 | apiRequest({ path, dispatch, showProgressBar: false }) 25 | .then((response: AggsUTXOResponse) => { 26 | onAggsUTXOs(response.data); 27 | }) 28 | .catch(onError); 29 | } 30 | 31 | public render() { 32 | return null; 33 | } 34 | } 35 | 36 | export default connect()(RequestAssetTypeUTXO); 37 | -------------------------------------------------------------------------------- /src/request/RequestBlock.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { connect, DispatchProp } from "react-redux"; 4 | 5 | import { H256 } from "codechain-sdk/lib/core/classes"; 6 | 7 | import { BlockDoc } from "codechain-indexer-types"; 8 | import { RootState } from "../redux/actions"; 9 | import { getCurrentTimestamp } from "../utils/Time"; 10 | import { apiRequest } from "./ApiRequest"; 11 | 12 | interface OwnProps { 13 | id: number | string; 14 | onBlock: (b: BlockDoc) => void; 15 | onError: (e: any) => void; 16 | onBlockNotExist: () => void; 17 | progressBarTarget?: string; 18 | } 19 | 20 | interface StateProps { 21 | cached?: { data: BlockDoc; updatedAt: number }; 22 | } 23 | 24 | type Props = OwnProps & StateProps & DispatchProp; 25 | 26 | class RequestBlock extends React.Component { 27 | public componentWillMount() { 28 | const { cached, dispatch, onError, onBlock, id, progressBarTarget, onBlockNotExist } = this.props; 29 | 30 | if (cached && getCurrentTimestamp() - cached.updatedAt < 10) { 31 | setTimeout(() => onBlock(cached.data)); 32 | return; 33 | } 34 | apiRequest({ 35 | path: `block/${id}`, 36 | dispatch, 37 | progressBarTarget, 38 | showProgressBar: false 39 | }) 40 | .then((response: BlockDoc) => { 41 | if (response === null) { 42 | return onBlockNotExist(); 43 | } 44 | const block = response; 45 | dispatch({ 46 | type: "CACHE_BLOCK", 47 | data: block 48 | }); 49 | onBlock(block); 50 | }) 51 | .catch(onError); 52 | } 53 | 54 | public render() { 55 | return null; 56 | } 57 | } 58 | 59 | export default connect((state: RootState, props: OwnProps) => { 60 | const { blocksByHash, blocksByNumber } = state.appReducer; 61 | const { id } = props; 62 | if (H256.check(id)) { 63 | const strId = String(id); 64 | return { 65 | cached: blocksByHash[new H256(strId).value] && { 66 | data: blocksByHash[new H256(strId).value].data, 67 | updatedAt: blocksByHash[new H256(strId).value].updatedAt 68 | } 69 | }; 70 | } else { 71 | return { 72 | cached: blocksByNumber[id] && { 73 | data: blocksByNumber[id].data, 74 | updatedAt: blocksByNumber[id].updatedAt 75 | } 76 | }; 77 | } 78 | return {}; 79 | })(RequestBlock); 80 | -------------------------------------------------------------------------------- /src/request/RequestBlockNumber.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { connect, DispatchProp } from "react-redux"; 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | onBlockNumber?: (n: number) => void; 8 | onError?: (e: any) => void; 9 | repeat?: number; 10 | } 11 | 12 | type Props = OwnProps & DispatchProp; 13 | 14 | interface State { 15 | // FIXME: | number 16 | timer?: NodeJS.Timer; 17 | } 18 | 19 | class RequestBlockNumber extends React.Component { 20 | public componentWillMount() { 21 | this.setState({}); 22 | const { repeat } = this.props; 23 | if (repeat) { 24 | const timer = setInterval(() => this.request(), repeat); 25 | this.setState({ timer }); 26 | } 27 | this.request(); 28 | } 29 | 30 | public componentWillReceiveProps(props: Props) { 31 | if (props.repeat === this.props.repeat) { 32 | return; 33 | } 34 | const { timer } = this.state; 35 | if (timer) { 36 | clearInterval(timer); 37 | } 38 | if (props.repeat) { 39 | const newTimer = setInterval(() => this.request(), props.repeat); 40 | this.setState({ timer: newTimer }); 41 | } else { 42 | this.setState({ timer: undefined }); 43 | } 44 | } 45 | 46 | public componentWillUnmount() { 47 | const { timer } = this.state; 48 | if (timer) { 49 | clearInterval(timer); 50 | } 51 | } 52 | 53 | public render() { 54 | return null; 55 | } 56 | 57 | private request() { 58 | const { dispatch, onBlockNumber, onError } = this.props; 59 | apiRequest({ path: "block/count", showProgressBar: false, dispatch }) 60 | .then((response: string) => { 61 | // NOTE: /block/count returns the bestBlockNumber + 1 because it counts the block 0. 62 | const num = Number.parseInt(response, 10) - 1; 63 | dispatch({ 64 | type: "BEST_BLOCK_NUMBER_ACTION", 65 | data: num 66 | }); 67 | if (onBlockNumber) { 68 | onBlockNumber(num); 69 | } 70 | }) 71 | .catch((error: ApiError) => { 72 | if (onError) { 73 | onError(error); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | export default connect()(RequestBlockNumber); 80 | -------------------------------------------------------------------------------- /src/request/RequestBlockTransactions.tsx: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as React from "react"; 3 | import { connect, DispatchProp } from "react-redux"; 4 | 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | import { TransactionsResponse } from "./RequestTransactions"; 7 | 8 | interface OwnProps { 9 | id: number | string; 10 | firstEvaluatedKey?: string; 11 | lastEvaluatedKey?: string; 12 | itemsPerPage: number; 13 | onTransactions: (response: TransactionsResponse) => void; 14 | onError: (e: ApiError) => void; 15 | } 16 | 17 | type Props = OwnProps & DispatchProp; 18 | 19 | class RequestBlockTransactions extends React.Component { 20 | public componentWillMount() { 21 | const { id, lastEvaluatedKey, firstEvaluatedKey, itemsPerPage, dispatch } = this.props; 22 | const { onError, onTransactions } = this.props; 23 | apiRequest({ 24 | path: `block/${id}/tx?itemsPerPage=${itemsPerPage}${ 25 | lastEvaluatedKey ? `&lastEvaluatedKey=${lastEvaluatedKey}` : "" 26 | }${firstEvaluatedKey ? `&firstEvaluatedKey=${firstEvaluatedKey}` : ""}`, 27 | dispatch, 28 | showProgressBar: false 29 | }) 30 | .then(onTransactions) 31 | .catch(onError); 32 | } 33 | 34 | public render() { 35 | return null; 36 | } 37 | } 38 | 39 | export default connect()(RequestBlockTransactions); 40 | -------------------------------------------------------------------------------- /src/request/RequestBlocks.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { BlockDoc } from "codechain-indexer-types"; 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | 7 | export interface BlocksResponse { 8 | data: BlockDoc[]; 9 | hasNextPage: boolean; 10 | hasPreviousPage: boolean; 11 | firstEvaluatedKey: string; 12 | lastEvaluatedKey: string; 13 | } 14 | 15 | interface OwnProps { 16 | firstEvaluatedKey?: string; 17 | lastEvaluatedKey?: string; 18 | itemsPerPage: number; 19 | showProgressBar: boolean; 20 | onBlocks: (blocks: BlocksResponse) => void; 21 | onError: (e: ApiError) => void; 22 | } 23 | 24 | type Props = OwnProps & DispatchProp; 25 | 26 | class RequestBlocks extends React.Component { 27 | public componentWillMount() { 28 | const { 29 | onError, 30 | onBlocks, 31 | dispatch, 32 | lastEvaluatedKey, 33 | firstEvaluatedKey, 34 | itemsPerPage, 35 | showProgressBar 36 | } = this.props; 37 | const path = `block?itemsPerPage=${itemsPerPage}${ 38 | lastEvaluatedKey ? `&lastEvaluatedKey=${lastEvaluatedKey}` : "" 39 | }${firstEvaluatedKey ? `&firstEvaluatedKey=${firstEvaluatedKey}` : ""}`; 40 | apiRequest({ 41 | path, 42 | dispatch, 43 | showProgressBar 44 | }) 45 | .then((response: any) => { 46 | onBlocks(response); 47 | }) 48 | .catch(onError); 49 | } 50 | 51 | public render() { 52 | return null; 53 | } 54 | } 55 | 56 | export default connect()(RequestBlocks); 57 | -------------------------------------------------------------------------------- /src/request/RequestCodeChainStatus.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | export interface CodeChainData { 7 | networkId: string; 8 | } 9 | 10 | interface OwnProps { 11 | onCodeChain: (codechain: CodeChainData) => void; 12 | onError: (e: ApiError) => void; 13 | } 14 | 15 | type Props = OwnProps & DispatchProp; 16 | 17 | class RequestCodeChainStatus extends React.Component { 18 | public componentWillMount() { 19 | const { onError, dispatch, onCodeChain } = this.props; 20 | apiRequest({ 21 | path: `status/codechain`, 22 | dispatch, 23 | showProgressBar: true 24 | }) 25 | .then((response: any) => { 26 | onCodeChain(response); 27 | }) 28 | .catch(onError); 29 | } 30 | 31 | public render() { 32 | return null; 33 | } 34 | } 35 | 36 | export default connect()(RequestCodeChainStatus); 37 | -------------------------------------------------------------------------------- /src/request/RequestFeeStats.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | export interface FeeStatus { 7 | pay?: string[]; 8 | transferAsset?: string[]; 9 | } 10 | 11 | interface OwnProps { 12 | onData: (data: FeeStatus) => void; 13 | onError: (e: ApiError) => void; 14 | } 15 | 16 | type Props = OwnProps & DispatchProp; 17 | 18 | class RequestFeeStats extends React.Component { 19 | public componentWillMount() { 20 | const { onError, dispatch, onData } = this.props; 21 | apiRequest({ 22 | path: `tx/fee-stats`, 23 | dispatch, 24 | showProgressBar: false 25 | }) 26 | .then((response: FeeStatus) => { 27 | onData(response); 28 | }) 29 | .catch(onError); 30 | } 31 | 32 | public render() { 33 | return null; 34 | } 35 | } 36 | 37 | export default connect()(RequestFeeStats); 38 | -------------------------------------------------------------------------------- /src/request/RequestIndexerVersion.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | onVersion: (version: string) => void; 8 | onError: (e: ApiError) => void; 9 | progressBarTarget?: string; 10 | } 11 | 12 | type Props = OwnProps & DispatchProp; 13 | 14 | class RequestIndexerVersion extends React.Component { 15 | public componentWillMount() { 16 | const { dispatch, progressBarTarget } = this.props; 17 | apiRequest({ 18 | path: `/version`, 19 | dispatch, 20 | progressBarTarget, 21 | showProgressBar: true 22 | }) 23 | .then((response: unknown) => { 24 | this.props.onVersion(response as string); 25 | }) 26 | .catch(this.props.onError); 27 | } 28 | 29 | public render() { 30 | return null; 31 | } 32 | } 33 | 34 | export default connect()(RequestIndexerVersion); 35 | -------------------------------------------------------------------------------- /src/request/RequestNodeStatus.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | onNodeStatus: (status: boolean) => void; 8 | onError: (e: ApiError) => void; 9 | } 10 | 11 | type Props = OwnProps & DispatchProp; 12 | 13 | class RequestNodeStatus extends React.Component { 14 | public componentWillMount() { 15 | const { onError } = this.props; 16 | try { 17 | this.requestNodeStat(); 18 | } catch (e) { 19 | onError(e); 20 | } 21 | } 22 | 23 | public render() { 24 | return null; 25 | } 26 | 27 | private requestNodeStat = async () => { 28 | const { onNodeStatus, dispatch } = this.props; 29 | const serverPing = await apiRequest({ 30 | path: `ping`, 31 | dispatch, 32 | showProgressBar: true 33 | }); 34 | 35 | onNodeStatus(serverPing === "pong"); 36 | }; 37 | } 38 | 39 | export default connect()(RequestNodeStatus); 40 | -------------------------------------------------------------------------------- /src/request/RequestPendingTransactions.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | import { ApiError, apiRequest } from "./ApiRequest"; 4 | import { TransactionsResponse } from "./RequestTransactions"; 5 | 6 | interface OwnProps { 7 | firstEvaluatedKey?: string; 8 | lastEvaluatedKey?: string; 9 | itemsPerPage: number; 10 | onTransactions: (transactions: TransactionsResponse) => void; 11 | onError: (e: ApiError) => void; 12 | } 13 | 14 | type Props = OwnProps & DispatchProp; 15 | 16 | class RequestPendingTransactions extends React.Component { 17 | public componentWillMount() { 18 | const { onError, onTransactions, dispatch, lastEvaluatedKey, firstEvaluatedKey, itemsPerPage } = this.props; 19 | const path = `pending-tx?itemsPerPage=${itemsPerPage}${ 20 | lastEvaluatedKey ? `&lastEvaluatedKey=${lastEvaluatedKey}` : "" 21 | }${firstEvaluatedKey ? `&firstEvaluatedKey=${firstEvaluatedKey}` : ""}`; 22 | apiRequest({ 23 | path, 24 | dispatch, 25 | showProgressBar: false 26 | }) 27 | .then((response: any) => { 28 | onTransactions(response); 29 | }) 30 | .catch(onError); 31 | } 32 | 33 | public render() { 34 | return null; 35 | } 36 | } 37 | 38 | export default connect()(RequestPendingTransactions); 39 | -------------------------------------------------------------------------------- /src/request/RequestPing.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | onPong: () => void; 8 | onError: (e: ApiError) => void; 9 | onDead: () => void; 10 | repeat?: number; 11 | } 12 | 13 | interface State { 14 | // FIXME: | number 15 | timer?: NodeJS.Timer; 16 | } 17 | 18 | type Props = OwnProps & DispatchProp; 19 | 20 | class RequestPing extends React.Component { 21 | public componentWillMount() { 22 | this.setState({}); 23 | const { repeat } = this.props; 24 | if (repeat) { 25 | const timer = setInterval(() => this.request(), repeat); 26 | this.setState({ timer }); 27 | } 28 | this.request(); 29 | } 30 | 31 | public componentWillReceiveProps(props: Props) { 32 | if (props.repeat === this.props.repeat) { 33 | return; 34 | } 35 | const { timer } = this.state; 36 | if (timer) { 37 | clearInterval(timer); 38 | } 39 | if (props.repeat) { 40 | const newTimer = setInterval(() => this.request(), props.repeat); 41 | this.setState({ timer: newTimer }); 42 | } else { 43 | this.setState({ timer: undefined }); 44 | } 45 | } 46 | 47 | public render() { 48 | return null; 49 | } 50 | 51 | public componentWillUnmount() { 52 | const { timer } = this.state; 53 | if (timer) { 54 | clearInterval(timer); 55 | } 56 | } 57 | 58 | private request = () => { 59 | const { onPong, onError, dispatch, onDead } = this.props; 60 | apiRequest({ path: `ping`, showProgressBar: false, dispatch }) 61 | .then((response: string) => { 62 | if (response === "pong") { 63 | onPong(); 64 | } else { 65 | onError({ message: `Expected "pong" but "${response}"` }); 66 | } 67 | }) 68 | .catch(onDead); 69 | }; 70 | } 71 | 72 | export default connect()(RequestPing); 73 | -------------------------------------------------------------------------------- /src/request/RequestPlatformAddressAccount.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { U256 } from "codechain-sdk/lib/core/classes"; 5 | 6 | import { ApiError, apiRequest } from "./ApiRequest"; 7 | 8 | interface OwnProps { 9 | address: string; 10 | onAccount: (account: { seq: U256; balance: U256 }, address: string) => void; 11 | onError: (e: ApiError) => void; 12 | onAccountNotExist: () => void; 13 | progressBarTarget?: string; 14 | showProgressBar: boolean; 15 | } 16 | 17 | type Props = OwnProps & DispatchProp; 18 | 19 | class RequestPlatformAddressAccount extends React.Component { 20 | public componentWillMount() { 21 | const { 22 | address, 23 | onAccount, 24 | onError, 25 | dispatch, 26 | progressBarTarget, 27 | showProgressBar, 28 | onAccountNotExist 29 | } = this.props; 30 | apiRequest({ 31 | path: `account/${address}`, 32 | dispatch, 33 | progressBarTarget, 34 | showProgressBar 35 | }) 36 | .then((response: any) => { 37 | if (response === null) { 38 | return onAccountNotExist(); 39 | } 40 | const { seq, balance } = response; 41 | onAccount( 42 | { 43 | seq: new U256(seq), 44 | balance: new U256(balance) 45 | }, 46 | address 47 | ); 48 | }) 49 | .catch(onError); 50 | } 51 | 52 | public render() { 53 | return null; 54 | } 55 | } 56 | export default connect()(RequestPlatformAddressAccount); 57 | -------------------------------------------------------------------------------- /src/request/RequestServerTime.tsx: -------------------------------------------------------------------------------- 1 | import * as moment from "moment"; 2 | import * as React from "react"; 3 | import { connect, DispatchProp } from "react-redux"; 4 | import { RootState } from "src/redux/actions"; 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | 7 | interface StateProps { 8 | waitingServerTimeResponse: boolean; 9 | } 10 | 11 | type Props = DispatchProp & StateProps; 12 | 13 | class RequestServerTime extends React.Component { 14 | public componentWillMount() { 15 | const { dispatch, waitingServerTimeResponse } = this.props; 16 | 17 | if (waitingServerTimeResponse) { 18 | return; 19 | } 20 | 21 | dispatch({ 22 | type: "SERVER_TIME_REQUEST_ACTION", 23 | data: true 24 | }); 25 | 26 | apiRequest({ path: `status/server-time`, dispatch, showProgressBar: false }) 27 | .then((response: any) => { 28 | dispatch({ 29 | type: "SERVER_TIME_RESPONSE_ACTION", 30 | data: response - moment().unix() 31 | }); 32 | }) 33 | .catch(error => { 34 | const e = error as ApiError; 35 | console.log(e); 36 | }); 37 | } 38 | 39 | public render() { 40 | return null; 41 | } 42 | } 43 | 44 | export default connect((state: RootState) => { 45 | return { 46 | waitingServerTimeResponse: state.appReducer.waitingServerTimeResponse 47 | }; 48 | })(RequestServerTime); 49 | -------------------------------------------------------------------------------- /src/request/RequestSyncStatus.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | export interface SyncData { 7 | codechainBestBlockNumber: number; 8 | codechainBestBlockHash: string; 9 | indexedBlockNumber: number; 10 | indexedBlockHash: string; 11 | } 12 | 13 | interface OwnProps { 14 | onSync: (sync: SyncData) => void; 15 | onError: (e: ApiError) => void; 16 | } 17 | 18 | type Props = OwnProps & DispatchProp; 19 | 20 | class RequestSyncStatus extends React.Component { 21 | public componentWillMount() { 22 | const { onError, dispatch, onSync } = this.props; 23 | apiRequest({ path: `status/sync`, dispatch, showProgressBar: true }) 24 | .then((response: any) => { 25 | onSync(response); 26 | }) 27 | .catch(onError); 28 | } 29 | 30 | public render() { 31 | return null; 32 | } 33 | } 34 | 35 | export default connect()(RequestSyncStatus); 36 | -------------------------------------------------------------------------------- /src/request/RequestTotalBlockCount.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | onBlockTotalCount: (blockTotalCount: number) => void; 8 | onError: (e: ApiError) => void; 9 | } 10 | 11 | type Props = OwnProps & DispatchProp; 12 | 13 | class RequestTotalBlockCount extends React.Component { 14 | public componentWillMount() { 15 | const { onError, onBlockTotalCount, dispatch } = this.props; 16 | apiRequest({ 17 | path: `block/count`, 18 | dispatch, 19 | showProgressBar: false 20 | }) 21 | .then((response: any) => { 22 | onBlockTotalCount(response); 23 | }) 24 | .catch(onError); 25 | } 26 | 27 | public render() { 28 | return null; 29 | } 30 | } 31 | 32 | export default connect()(RequestTotalBlockCount); 33 | -------------------------------------------------------------------------------- /src/request/RequestTotalPlatformBlockCount.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { connect, DispatchProp } from "react-redux"; 3 | 4 | import { ApiError, apiRequest } from "./ApiRequest"; 5 | 6 | interface OwnProps { 7 | address: string; 8 | onTotalCount: (totalCount: number) => void; 9 | onError: (e: ApiError) => void; 10 | } 11 | 12 | type Props = OwnProps & DispatchProp; 13 | 14 | class RequestTotalPlatfromBlockCount extends React.Component { 15 | public componentWillMount() { 16 | const { onError, onTotalCount, dispatch, address } = this.props; 17 | apiRequest({ 18 | path: `block/count?address=${address}`, 19 | dispatch, 20 | showProgressBar: true 21 | }) 22 | .then((response: any) => { 23 | onTotalCount(response); 24 | }) 25 | .catch(onError); 26 | } 27 | 28 | public render() { 29 | return null; 30 | } 31 | } 32 | 33 | export default connect()(RequestTotalPlatfromBlockCount); 34 | -------------------------------------------------------------------------------- /src/request/RequestTransaction.tsx: -------------------------------------------------------------------------------- 1 | import { H256 } from "codechain-sdk/lib/core/classes"; 2 | import * as React from "react"; 3 | import { connect, DispatchProp } from "react-redux"; 4 | 5 | import { TransactionDoc } from "codechain-indexer-types"; 6 | import { RootState } from "../redux/actions"; 7 | import { getCurrentTimestamp } from "../utils/Time"; 8 | import { ApiError, apiRequest } from "./ApiRequest"; 9 | 10 | interface OwnProps { 11 | hash: string; 12 | onTransaction: (transaction: TransactionDoc) => void; 13 | onTransactionNotExist: () => void; 14 | onError: (e: ApiError) => void; 15 | progressBarTarget?: string; 16 | } 17 | 18 | interface StateProps { 19 | cached?: { data: TransactionDoc; updatedAt: number }; 20 | } 21 | 22 | type Props = OwnProps & StateProps & DispatchProp; 23 | 24 | class RequestTransaction extends React.Component { 25 | public componentWillMount() { 26 | const { cached, dispatch, hash, onTransaction, onTransactionNotExist, onError, progressBarTarget } = this.props; 27 | if (cached && getCurrentTimestamp() - cached.updatedAt < 10) { 28 | setTimeout(() => onTransaction(cached.data)); 29 | return; 30 | } 31 | apiRequest({ 32 | path: `tx/${hash}`, 33 | dispatch, 34 | progressBarTarget, 35 | showProgressBar: true 36 | }) 37 | .then(async (response: TransactionDoc) => { 38 | if (response === null) { 39 | return onTransactionNotExist(); 40 | } 41 | const transaction = response; 42 | 43 | // FIXME: This is temporary code. https://github.com/CodeChain-io/codechain-indexer/issues/57 44 | if (transaction.type === "transferAsset") { 45 | await Promise.all( 46 | transaction.transferAsset.outputs.map(async output => { 47 | const assetScheme: any = await apiRequest({ 48 | path: `asset-scheme/${output.assetType}`, 49 | dispatch, 50 | showProgressBar: false 51 | }); 52 | output.assetScheme = assetScheme; 53 | }) 54 | ); 55 | } 56 | 57 | dispatch({ 58 | type: "CACHE_TRANSACTION", 59 | data: transaction 60 | }); 61 | 62 | if (transaction.type === "mintAsset") { 63 | dispatch({ 64 | type: "CACHE_ASSET_SCHEME", 65 | data: { 66 | assetType: transaction.mintAsset.assetType, 67 | assetScheme: transaction.mintAsset 68 | } 69 | }); 70 | } 71 | onTransaction(transaction); 72 | }) 73 | .catch(onError); 74 | } 75 | 76 | public render() { 77 | return null; 78 | } 79 | } 80 | export default connect((state: RootState, props: OwnProps) => { 81 | let cachedTx; 82 | try { 83 | const cacheKey = new H256(props.hash).value; 84 | cachedTx = state.appReducer.transactionByHash[cacheKey]; 85 | } catch (e) { 86 | // 87 | } 88 | return { 89 | cached: cachedTx && { 90 | data: cachedTx.data, 91 | updatedAt: cachedTx.updatedAt 92 | } 93 | }; 94 | })(RequestTransaction); 95 | -------------------------------------------------------------------------------- /src/request/RequestTransactions.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import * as React from "react"; 3 | import { connect, DispatchProp } from "react-redux"; 4 | 5 | import { ApiError, apiRequest } from "./ApiRequest"; 6 | 7 | export interface TransactionsResponse { 8 | data: TransactionDoc[]; 9 | hasNextPage: boolean; 10 | hasPreviousPage: boolean; 11 | firstEvaluatedKey: string; 12 | lastEvaluatedKey: string; 13 | } 14 | 15 | interface OwnProps { 16 | firstEvaluatedKey?: string; 17 | lastEvaluatedKey?: string; 18 | itemsPerPage: number; 19 | showProgressBar: boolean; 20 | onTransactions: (transactions: TransactionsResponse) => void; 21 | onError: (e: ApiError) => void; 22 | selectedTypes?: string[]; 23 | progressBarTarget?: string; 24 | address?: string; 25 | assetType?: string; 26 | } 27 | 28 | type Props = OwnProps & DispatchProp; 29 | 30 | class RequestTransactions extends React.Component { 31 | public componentWillMount() { 32 | const { 33 | onError, 34 | onTransactions, 35 | dispatch, 36 | lastEvaluatedKey, 37 | firstEvaluatedKey, 38 | itemsPerPage, 39 | showProgressBar, 40 | selectedTypes, 41 | progressBarTarget, 42 | address, 43 | assetType 44 | } = this.props; 45 | let path = `tx?itemsPerPage=${itemsPerPage}${lastEvaluatedKey ? `&lastEvaluatedKey=${lastEvaluatedKey}` : ""}${ 46 | firstEvaluatedKey ? `&firstEvaluatedKey=${firstEvaluatedKey}` : "" 47 | }`; 48 | if (selectedTypes && selectedTypes.length > 0) { 49 | path += `&type=${selectedTypes.join(",")}`; 50 | } 51 | if (address) { 52 | path += `&address=${address}`; 53 | } 54 | if (assetType) { 55 | path += `&assetType=${assetType}`; 56 | } 57 | apiRequest({ 58 | path, 59 | dispatch, 60 | showProgressBar, 61 | progressBarTarget 62 | }) 63 | .then((response: any) => { 64 | onTransactions(response); 65 | }) 66 | .catch(onError); 67 | } 68 | 69 | public render() { 70 | return null; 71 | } 72 | } 73 | 74 | export default connect()(RequestTransactions); 75 | -------------------------------------------------------------------------------- /src/request/RequestWeeklyLogs.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import * as moment from "moment"; 3 | import * as React from "react"; 4 | 5 | import { connect, DispatchProp } from "react-redux"; 6 | import { ApiError, apiRequest } from "./ApiRequest"; 7 | 8 | export enum WeeklyLogType { 9 | BLOCK_COUNT = "BLOCK_COUNT", 10 | TX_COUNT = "TX_COUNT" 11 | } 12 | 13 | interface OwnProps { 14 | type: WeeklyLogType; 15 | onData: ( 16 | weeklyLog: Array<{ 17 | date: string; 18 | "#": string; 19 | color: string; 20 | fullDate: string; 21 | }> 22 | ) => void; 23 | onError: (e: ApiError) => void; 24 | } 25 | 26 | type Props = OwnProps & DispatchProp; 27 | 28 | class RequestWeeklyLogs extends React.Component { 29 | public componentWillMount() { 30 | this.queryWeekLog(); 31 | } 32 | 33 | public render() { 34 | return null; 35 | } 36 | 37 | private queryWeekLog = async () => { 38 | const { onData, onError, dispatch, type } = this.props; 39 | let query = ""; 40 | switch (type) { 41 | case WeeklyLogType.BLOCK_COUNT: 42 | query = "log/count?filter=block"; 43 | break; 44 | case WeeklyLogType.TX_COUNT: 45 | query = "log/count?filter=tx"; 46 | break; 47 | } 48 | try { 49 | const asyncJobs = _.map(_.reverse(_.range(7)), async index => { 50 | const momentObject = moment() 51 | .utc() 52 | .subtract(index, "days"); 53 | const dateString = momentObject.format("YYYY-MM-DD"); 54 | const resultDateString = momentObject.format("MM-DD"); 55 | const count = (await apiRequest({ 56 | path: `${query}&date=${dateString}`, 57 | dispatch, 58 | showProgressBar: true 59 | })) as string; 60 | return { 61 | date: resultDateString, 62 | "#": count, 63 | color: "hsl(191, 95%, 42%)", 64 | fullDate: dateString 65 | }; 66 | }); 67 | const results = await Promise.all(asyncJobs); 68 | onData(results); 69 | } catch (e) { 70 | onError(e); 71 | } 72 | }; 73 | } 74 | 75 | export default connect()(RequestWeeklyLogs); 76 | -------------------------------------------------------------------------------- /src/request/index.tsx: -------------------------------------------------------------------------------- 1 | import RequestAssetInfosByName from "./RequestAssetInfosByName"; 2 | import RequestAssetScheme from "./RequestAssetScheme"; 3 | import RequestAssetTransferAddressUTXO from "./RequestAssetTransferAddressUTXO"; 4 | import RequestBlock from "./RequestBlock"; 5 | import RequestBlockNumber from "./RequestBlockNumber"; 6 | import RequestBlocks from "./RequestBlocks"; 7 | import RequestDailyLogs from "./RequestDailyLogs"; 8 | import RequestPendingTransactions from "./RequestPendingTransactions"; 9 | import RequestPing from "./RequestPing"; 10 | import RequestPlatformAddressAccount from "./RequestPlatformAddressAccount"; 11 | import RequestTotalBlockCount from "./RequestTotalBlockCount"; 12 | import RequestTotalPlatformBlockCount from "./RequestTotalPlatformBlockCount"; 13 | import RequestTransaction from "./RequestTransaction"; 14 | import RequestTransactions from "./RequestTransactions"; 15 | import RequestWeeklyLogs from "./RequestWeeklyLogs"; 16 | 17 | export { RequestPing }; 18 | export { RequestBlockNumber }; 19 | export { RequestBlock }; 20 | export { RequestAssetScheme }; 21 | export { RequestTransaction }; 22 | export { RequestAssetTransferAddressUTXO }; 23 | export { RequestPlatformAddressAccount }; 24 | export { RequestBlocks }; 25 | export { RequestTransactions }; 26 | export { RequestAssetInfosByName }; 27 | export { RequestTotalBlockCount }; 28 | export { RequestTotalPlatformBlockCount }; 29 | export { RequestWeeklyLogs }; 30 | export { RequestDailyLogs }; 31 | export { RequestPendingTransactions }; 32 | -------------------------------------------------------------------------------- /src/utils/Address.ts: -------------------------------------------------------------------------------- 1 | export function isAssetAddress(address: string) { 2 | return address.slice(2, 3) === "a"; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/Asset.ts: -------------------------------------------------------------------------------- 1 | import { TransactionDoc } from "codechain-indexer-types"; 2 | import { U256 } from "codechain-sdk/lib/core/classes"; 3 | import * as _ from "lodash"; 4 | 5 | export function getTotalAssetCount(transaction: TransactionDoc) { 6 | if (transaction.type === "mintAsset") { 7 | return transaction.mintAsset.supply || "0"; 8 | } else if (transaction.type === "transferAsset") { 9 | return _.reduce( 10 | transaction.transferAsset.inputs, 11 | (memo, input) => U256.plus(memo, new U256(input.prevOut.quantity)), 12 | new U256("0") 13 | ).toString(10); 14 | } 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/BalanceHistory.ts: -------------------------------------------------------------------------------- 1 | export declare type balanceHistoryReasons = 2 | | "fee" 3 | | "author" 4 | | "stake" 5 | | "tx" 6 | | "initial_distribution" 7 | | "deposit" 8 | | "validator" 9 | | "report"; 10 | 11 | const x: { [T in balanceHistoryReasons]: null } = { 12 | fee: null, 13 | author: null, 14 | stake: null, 15 | tx: null, 16 | initial_distribution: null, 17 | deposit: null, 18 | validator: null, 19 | report: null 20 | }; 21 | 22 | export const historyReasonTypes = Object.keys(x); 23 | -------------------------------------------------------------------------------- /src/utils/Metadata.ts: -------------------------------------------------------------------------------- 1 | export const parseMetadata = (metadata: string): Metadata => { 2 | try { 3 | return JSON.parse(metadata); 4 | } catch { 5 | return {}; 6 | } 7 | }; 8 | 9 | export interface Metadata { 10 | name?: string; 11 | description?: string; 12 | icon_url?: string; 13 | gateway?: { url?: string }; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/Time.ts: -------------------------------------------------------------------------------- 1 | import * as moment from "moment"; 2 | 3 | export const getCurrentTimestamp = () => { 4 | return Math.floor(new Date().getTime() / 1000); 5 | }; 6 | 7 | export const getUnixTimeLocaleString = (timeStamp: number, serverTimeOffset?: number) => { 8 | const time = moment.unix(timeStamp); 9 | const serverTime = moment().add(serverTimeOffset, "seconds"); 10 | moment.relativeTimeThreshold("ss", -1); 11 | moment.relativeTimeThreshold("m", 60); 12 | 13 | if (time.isAfter(serverTime.subtract(1, "hours"))) { 14 | return time.fromNow(); 15 | } else if (serverTime.startOf("day").isBefore(time)) { 16 | return `${time.format("HH:mm:ss").toLocaleString()} Today`; 17 | } else if (serverTime.startOf("year").isBefore(time)) { 18 | return time.format("MMM DD, HH:mm:ss").toLocaleString(); 19 | } else { 20 | return time.format("MMM DD YYYY, HH:mm:ss").toLocaleString(); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/Transactions.ts: -------------------------------------------------------------------------------- 1 | const x: { [index: string]: null } = { 2 | pay: null, 3 | mintAsset: null, 4 | transferAsset: null, 5 | wrapCCC: null, 6 | unwrapCCC: null, 7 | setRegularKey: null, 8 | createShard: null, 9 | setShardOwners: null, 10 | setShardUsers: null, 11 | store: null, 12 | remove: null, 13 | custom: null, 14 | increaseAssetSupply: null, 15 | changeAssetScheme: null 16 | }; 17 | 18 | export const TransactionTypes = Object.keys(x); 19 | 20 | export const getLockScriptName = (lockScriptHash: string) => { 21 | switch (lockScriptHash) { 22 | case "0x5f5960a7bca6ceeeb0c97bc717562914e7a1de04": 23 | return "P2PKH(0x5f5960a7bca6ceeeb0c97bc717562914e7a1de04)"; 24 | case "0x37572bdcc22d39a59c0d12d301f6271ba3fdd451": 25 | return "P2PKHBurn(0x37572bdcc22d39a59c0d12d301f6271ba3fdd451)"; 26 | } 27 | return `${lockScriptHash}`; 28 | }; 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es5", 5 | "lib": ["es6", "dom"], 6 | "baseUrl": ".", 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "jsx": "react", 10 | "moduleResolution": "node", 11 | "forceConsistentCasingInFileNames": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noImplicitAny": true, 15 | "strictNullChecks": true, 16 | "suppressImplicitAnyIndexErrors": true, 17 | "noUnusedLocals": true, 18 | "outDir": "build/dist", 19 | "rootDir": "src" 20 | }, 21 | "exclude": ["node_modules", "build", "scripts", "acceptance-tests", "webpack", "config"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "rules": { 4 | "interface-name": false, 5 | "no-console": false, 6 | "object-literal-sort-keys": false, 7 | "no-var-requires": false, 8 | "array-type": false 9 | }, 10 | "jsRules": { 11 | "no-console": false, 12 | "object-literal-sort-keys": false 13 | }, 14 | "linterOptions": { 15 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 16 | } 17 | } 18 | --------------------------------------------------------------------------------