mirror of
https://github.com/oceanprotocol/status
synced 2024-11-22 01:36:59 +01:00
migrate to Next.js
This commit is contained in:
parent
7b54275822
commit
fa51b4ac1a
39
.eslintrc
39
.eslintrc
@ -1,20 +1,37 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["eslint:recommended", "prettier"],
|
||||||
"oceanprotocol",
|
|
||||||
"oceanprotocol/react",
|
|
||||||
"prettier/react",
|
|
||||||
"prettier/standard",
|
|
||||||
"plugin:prettier/recommended"
|
|
||||||
],
|
|
||||||
"plugins": ["prettier"],
|
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"jest": true
|
"node": true
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": "16.8"
|
"version": "detect"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": ["./tsconfig.json"]
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"oceanprotocol",
|
||||||
|
"oceanprotocol/react",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"prettier/react",
|
||||||
|
"prettier/standard",
|
||||||
|
"prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"plugins": ["@typescript-eslint", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -13,11 +13,9 @@
|
|||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.*
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
.next
|
19
.travis.yml
19
.travis.yml
@ -1,18 +1,17 @@
|
|||||||
dist: xenial
|
dist: xenial
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js: node
|
||||||
- "12"
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||||
- chmod +x ./cc-test-reporter
|
- chmod +x ./cc-test-reporter
|
||||||
- "./cc-test-reporter before-build"
|
- './cc-test-reporter before-build'
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# will run `npm ci` automatically here
|
# will run `npm ci` automatically here
|
||||||
- npm test
|
- npm test
|
||||||
- "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
|
- './cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT'
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
@ -82,4 +82,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
```
|
```
|
||||||
|
18
jest.config.js
Normal file
18
jest.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest/presets/js-with-babel',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest/setup.ts'],
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
tsConfig: 'jest.tsconfig.json'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moduleNameMapper: {
|
||||||
|
'.+\\.(css|styl|less|sass|scss)$': '<rootDir>/jest/__mocks__/styleMock.js',
|
||||||
|
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||||
|
'<rootDir>/jest/__mocks__/fileMock.js',
|
||||||
|
'\\.svg': '<rootDir>/jest/__mocks__/svgrMock.js'
|
||||||
|
},
|
||||||
|
testPathIgnorePatterns: ['.next/', 'node_modules/', 'build/', 'coverage/'],
|
||||||
|
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/@types/**/*'],
|
||||||
|
collectCoverage: true
|
||||||
|
}
|
12
jest.tsconfig.json
Normal file
12
jest.tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react",
|
||||||
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "es5"
|
||||||
|
}
|
||||||
|
}
|
1
jest/__mocks__/fileMock.js
Normal file
1
jest/__mocks__/fileMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = 'test-file-stub'
|
1
jest/__mocks__/styleMock.js
Normal file
1
jest/__mocks__/styleMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = {}
|
1
jest/__mocks__/svgrMock.js
Normal file
1
jest/__mocks__/svgrMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = 'svg'
|
2
next-env.d.ts
vendored
Normal file
2
next-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/types/global" />
|
41
next.config.js
Normal file
41
next.config.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const withCSS = require('@zeit/next-css')
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const withSvgr = (nextConfig = {}, nextComposePlugins = {}) => {
|
||||||
|
return Object.assign({}, nextConfig, {
|
||||||
|
webpack(config, options) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: '@svgr/webpack',
|
||||||
|
options: {
|
||||||
|
icon: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof nextConfig.webpack === 'function') {
|
||||||
|
return nextConfig.webpack(config, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||||
|
enabled: process.env.ANALYZE === 'true'
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = withBundleAnalyzer(
|
||||||
|
withSvgr(
|
||||||
|
withCSS({
|
||||||
|
cssModules: true,
|
||||||
|
cssLoaderOptions: {
|
||||||
|
localIdentName: '[local]___[hash:base64:5]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
13816
package-lock.json
generated
13816
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -4,32 +4,48 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "next dev",
|
||||||
"build": "react-scripts build",
|
"build": "next build",
|
||||||
"test": "npm run lint && react-scripts test --coverage --watchAll=false",
|
"serve": "next start",
|
||||||
"test:watch": "react-scripts test --coverage",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
"test": "npm run lint && NODE_ENV=test jest",
|
||||||
|
"test:watch": "npm run lint && NODE_ENV=test jest --watch",
|
||||||
"lint": "eslint --ignore-path .gitignore --ext .js .",
|
"lint": "eslint --ignore-path .gitignore --ext .js .",
|
||||||
"format": "prettier ./src/**/*.{js,scss,json} --write"
|
"format": "prettier --ignore-path .gitignore **/**/*.{css,yml,js,jsx,ts,tsx,json} --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethereum-navigator/atlas": "^0.5.1",
|
"@ethereum-navigator/atlas": "^0.5.1",
|
||||||
"@oceanprotocol/art": "^2.2.0",
|
"@oceanprotocol/art": "^2.2.0",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
|
"@zeit/next-css": "^1.0.1",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"next": "9.1.4",
|
||||||
|
"next-seo": "^3.1.0",
|
||||||
|
"next-svgr": "^0.0.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0"
|
||||||
"react-scripts": "^3.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@next/bundle-analyzer": "^9.1.4",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"eslint": "^6.7.1",
|
"@types/jest": "^24.0.23",
|
||||||
|
"@types/next-seo": "^1.10.0",
|
||||||
|
"@types/node": "^12.12.14",
|
||||||
|
"@types/react": "^16.9.15",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||||
|
"@typescript-eslint/parser": "^2.10.0",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-oceanprotocol": "^1.5.0",
|
"eslint-config-oceanprotocol": "^1.5.0",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"eslint-plugin-prettier": "^3.1.1",
|
"eslint-plugin-prettier": "^3.1.1",
|
||||||
"jest-mock-axios": "^3.1.2",
|
"jest": "^24.9.0",
|
||||||
"node-sass": "^4.13.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^1.19.1"
|
"prettier": "^1.19.1",
|
||||||
|
"ts-jest": "^24.2.0",
|
||||||
|
"typescript": "^3.7.3",
|
||||||
|
"webpack": "^4.41.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -47,11 +63,5 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"src/**/*.js",
|
|
||||||
"!src/serviceWorker.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
postcss.config.js
Normal file
8
postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
'postcss-preset-env': {
|
||||||
|
importFrom: './src/styles/_variables.css'
|
||||||
|
},
|
||||||
|
cssnano: {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/icons/icon-96x96.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#141414" />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="Overview and status checks of all Ocean Protocol RPC network connections."
|
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="icons/icon-256x256.png" />
|
|
||||||
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
|
|
||||||
<meta property="og:title" content="Ocean Protocol Status" />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Overview and status checks of all Ocean Protocol RPC network connections."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://status.oceanprotocol.com/share.png"
|
|
||||||
/>
|
|
||||||
<meta property="og:url" content="https://status.oceanprotocol.com" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:site" content="@oceanprotocol" />
|
|
||||||
|
|
||||||
<title>Ocean Protocol Status</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "Ocean Status",
|
|
||||||
"name": "Ocean Protocol Status",
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#141414",
|
|
||||||
"background_color": "#141414",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "icons/icon-48x48.png?v=a2156652544310e91b0703e484d8e51f",
|
|
||||||
"sizes": "48x48",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-72x72.png?v=a2156652544310e91b0703e484d8e51f",
|
|
||||||
"sizes": "72x72",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-96x96.png",
|
|
||||||
"sizes": "96x96",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-144x144.png",
|
|
||||||
"sizes": "144x144",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-256x256.png",
|
|
||||||
"sizes": "256x256",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-384x384.png",
|
|
||||||
"sizes": "384x384",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "icons/icon-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
5
site.config.js
Normal file
5
site.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
title: 'Ocean Protocol Status',
|
||||||
|
description: `Testing all RPC network connections from your browser, refreshed every 5 sec.`,
|
||||||
|
url: 'https://status.oceanprotocol.com'
|
||||||
|
}
|
13
src/@types/global.d.ts
vendored
Normal file
13
src/@types/global.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: { readonly [key: string]: string }
|
||||||
|
export default classes
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.svg' {
|
||||||
|
import * as React from 'react'
|
||||||
|
export const ReactComponent: React.FunctionComponent<React.SVGProps<
|
||||||
|
SVGSVGElement
|
||||||
|
>>
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
31
src/App.js
31
src/App.js
@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo-white.svg'
|
|
||||||
import styles from './App.module.scss'
|
|
||||||
import atlas from '@ethereum-navigator/atlas'
|
|
||||||
import Network from './Network'
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<div className={styles.app}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<Logo />
|
|
||||||
<h1>Ocean Protocol Status</h1>
|
|
||||||
<p>
|
|
||||||
Testing all RPC network connections from your browser, refreshed every
|
|
||||||
5 sec.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className={styles.networks}>
|
|
||||||
{atlas
|
|
||||||
.filter(
|
|
||||||
item => item.project === 'Ocean Protocol' && item.name !== 'Spree'
|
|
||||||
)
|
|
||||||
.reverse()
|
|
||||||
.map((network, i) => (
|
|
||||||
<Network key={i} network={network} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
@import './styles/variables';
|
|
||||||
|
|
||||||
.app {
|
|
||||||
text-align: center;
|
|
||||||
padding: $spacer / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
color: $brand-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: $spacer;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom: $spacer / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-size-large;
|
|
||||||
color: $brand-white;
|
|
||||||
margin: auto;
|
|
||||||
max-width: 35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 75px;
|
|
||||||
height: 75px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks {
|
|
||||||
display: grid;
|
|
||||||
gap: $spacer;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
margin: $spacer * 2 auto;
|
|
||||||
max-width: 80rem;
|
|
||||||
}
|
|
14
src/Layout.module.css
Normal file
14
src/Layout.module.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.app {
|
||||||
|
padding: var(--spacer);
|
||||||
|
background: var(--brand-black);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 640px) {
|
||||||
|
.app {
|
||||||
|
padding: calc(var(--spacer) * 2);
|
||||||
|
padding-bottom: var(--spacer);
|
||||||
|
height: auto;
|
||||||
|
min-height: calc(100vh - var(--page-frame) * 2);
|
||||||
|
}
|
||||||
|
}
|
44
src/Layout.tsx
Normal file
44
src/Layout.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { NextSeo } from 'next-seo'
|
||||||
|
import styles from './Layout.module.css'
|
||||||
|
import { title, description, url } from '../site.config'
|
||||||
|
|
||||||
|
export default function Layout({
|
||||||
|
children,
|
||||||
|
pageTitle = title
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
pageTitle?: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={styles.app}>
|
||||||
|
<Head>
|
||||||
|
<link rel="icon" href="/icons/icon-96x96.png" />
|
||||||
|
<link rel="apple-touch-icon" href="icons/icon-256x256.png" />
|
||||||
|
<meta name="theme-color" content="#141414" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<NextSeo
|
||||||
|
title={pageTitle}
|
||||||
|
description={description}
|
||||||
|
canonical={url}
|
||||||
|
openGraph={{
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
images: [{ url: `${url}/share.png` }],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||||
|
site_name: title
|
||||||
|
}}
|
||||||
|
twitter={{
|
||||||
|
handle: '@oceanprotocol',
|
||||||
|
site: '@oceanprotocol',
|
||||||
|
cardType: 'summary_large_image'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,91 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { axiosRpcRequest } from './rpc'
|
|
||||||
import styles from './Network.module.scss'
|
|
||||||
|
|
||||||
Network.propTypes = {
|
|
||||||
network: PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
networkId: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.string.isRequired,
|
|
||||||
rpcUrl: PropTypes.string.isRequired,
|
|
||||||
explorerUrl: PropTypes.string.isRequired
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Network({ network }) {
|
|
||||||
const [status, setStatus] = useState('')
|
|
||||||
const [block, setBlock] = useState('')
|
|
||||||
const [latency, setLatency] = useState('')
|
|
||||||
const [clientVersion, setClientVersion] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function getStatusAndBlock() {
|
|
||||||
const response = await axiosRpcRequest(network.rpcUrl, 'eth_blockNumber')
|
|
||||||
|
|
||||||
if (!response || response.status !== 200) {
|
|
||||||
setStatus('Offline')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setStatus('Online')
|
|
||||||
response.duration && setLatency(response.duration)
|
|
||||||
|
|
||||||
const blockNumber =
|
|
||||||
response && response.data && parseInt(response.data.result, 16)
|
|
||||||
|
|
||||||
setBlock(blockNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getClientVersion() {
|
|
||||||
const response = await axiosRpcRequest(
|
|
||||||
network.rpcUrl,
|
|
||||||
'web3_clientVersion'
|
|
||||||
)
|
|
||||||
response && response.data && setClientVersion(response.data.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
getStatusAndBlock()
|
|
||||||
getClientVersion()
|
|
||||||
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
getStatusAndBlock()
|
|
||||||
getClientVersion()
|
|
||||||
}, 5000) // run every 5 sec.
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer)
|
|
||||||
}
|
|
||||||
}, [network])
|
|
||||||
|
|
||||||
const isOnline = status === 'Online'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.network}>
|
|
||||||
<h2 className={styles.title}>
|
|
||||||
{network.name}
|
|
||||||
<code>{network.networkId}</code>
|
|
||||||
<span>{network.type}</span>
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
<code>{network.rpcUrl}</code>
|
|
||||||
</p>
|
|
||||||
<p className={styles.status}>
|
|
||||||
<span className={isOnline ? styles.success : styles.error}>
|
|
||||||
{status}
|
|
||||||
</span>
|
|
||||||
{latency && (
|
|
||||||
<span className={styles.latency} title="Latency">
|
|
||||||
{latency} ms
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{block && (
|
|
||||||
<p className={styles.block} title="Current block number">
|
|
||||||
At block #
|
|
||||||
<a href={`${network.explorerUrl}/blocks/${block}`}>{block}</a>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{clientVersion && <p className={styles.clientVersion}>{clientVersion}</p>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
@import './styles/variables';
|
|
||||||
|
|
||||||
.network {
|
|
||||||
border: 1px solid $brand-grey-dark;
|
|
||||||
padding: $spacer;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-bottom: $spacer / 4;
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: $font-size-h2;
|
|
||||||
|
|
||||||
span,
|
|
||||||
code {
|
|
||||||
color: $brand-grey-light;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: $spacer / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-weight: $font-weight-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
a {
|
|
||||||
color: $brand-grey-lighter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: $green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: $red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.latency {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: $spacer / 4;
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clientVersion {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render, wait } from '@testing-library/react'
|
|
||||||
import mockAxios from 'axios'
|
|
||||||
import Network from './Network'
|
|
||||||
|
|
||||||
const mockResponse = {
|
|
||||||
status: 200,
|
|
||||||
duration: 1000,
|
|
||||||
data: { result: '0x345' }
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockAxios.reset()
|
|
||||||
jest.clearAllTimers()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Network', () => {
|
|
||||||
const network = {
|
|
||||||
name: 'Pacific',
|
|
||||||
project: 'Ocean Protocol',
|
|
||||||
type: 'mainnet',
|
|
||||||
networkId: '0xCEA11',
|
|
||||||
rpcUrl: 'https://pacific.oceanprotocol.com',
|
|
||||||
explorerUrl: 'https://submarine.oceanprotocol.com'
|
|
||||||
}
|
|
||||||
|
|
||||||
it('renders without crashing', async () => {
|
|
||||||
mockAxios.post.mockResolvedValue(mockResponse)
|
|
||||||
const { container } = render(<Network network={network} />)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
|
||||||
await wait()
|
|
||||||
expect(mockAxios.post).toHaveBeenCalledTimes(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders without response', async () => {
|
|
||||||
mockAxios.post.mockResolvedValue(undefined)
|
|
||||||
const { container } = render(<Network network={network} />)
|
|
||||||
await wait()
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('re-fetches after 5 sec.', async () => {
|
|
||||||
jest.useFakeTimers()
|
|
||||||
mockAxios.post.mockResolvedValue(mockResponse)
|
|
||||||
render(<Network network={network} />)
|
|
||||||
jest.advanceTimersByTime(6000)
|
|
||||||
await wait()
|
|
||||||
// expect(setInterval).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,2 +0,0 @@
|
|||||||
import mockAxios from 'jest-mock-axios'
|
|
||||||
export default mockAxios
|
|
10
src/__tests__/Layout.test.tsx
Normal file
10
src/__tests__/Layout.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import Layout from '../Layout'
|
||||||
|
|
||||||
|
describe('Layout', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Layout pageTitle="Hello">Hello</Layout>)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
68
src/__tests__/Network.test.tsx
Normal file
68
src/__tests__/Network.test.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, wait, waitForElement } from '@testing-library/react'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Network from '../components/Network'
|
||||||
|
|
||||||
|
import { mocked } from 'ts-jest/dist/util/testing'
|
||||||
|
|
||||||
|
jest.mock('axios')
|
||||||
|
const axiosMock: any = mocked(axios)
|
||||||
|
|
||||||
|
const mockResponse = {
|
||||||
|
status: 200,
|
||||||
|
duration: 1000,
|
||||||
|
data: { result: '0x345' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = {
|
||||||
|
name: 'Pacific',
|
||||||
|
project: 'Ocean Protocol',
|
||||||
|
type: 'mainnet',
|
||||||
|
networkId: '0xCEA11',
|
||||||
|
rpcUrl: 'https://pacific.oceanprotocol.com',
|
||||||
|
explorerUrl: 'https://submarine.oceanprotocol.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkNoRpc = {
|
||||||
|
name: 'Pacific',
|
||||||
|
project: 'Ocean Protocol',
|
||||||
|
type: 'mainnet',
|
||||||
|
networkId: '0xCEA11',
|
||||||
|
explorerUrl: 'https://submarine.oceanprotocol.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Network', () => {
|
||||||
|
it('renders without crashing', async () => {
|
||||||
|
axiosMock.post.mockResolvedValue(mockResponse)
|
||||||
|
const { container, rerender, getByText } = render(
|
||||||
|
<Network network={network} />
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
await waitForElement(() => getByText('Online'))
|
||||||
|
expect(axiosMock.post).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
rerender(<Network network={networkNoRpc} />)
|
||||||
|
await waitForElement(() => getByText('Online'))
|
||||||
|
expect(axiosMock.post).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders without response', async () => {
|
||||||
|
axiosMock.post.mockResolvedValue(undefined)
|
||||||
|
const { container } = render(<Network network={network} />)
|
||||||
|
await wait()
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('re-fetches after 5 sec.', async () => {
|
||||||
|
jest.useFakeTimers()
|
||||||
|
axiosMock.post.mockResolvedValue(mockResponse)
|
||||||
|
const { getByText } = render(<Network network={network} />)
|
||||||
|
jest.advanceTimersByTime(6000)
|
||||||
|
await waitForElement(() => getByText('Online'))
|
||||||
|
// expect(setInterval).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
10
src/__tests__/Spinner.test.tsx
Normal file
10
src/__tests__/Spinner.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import Spinner from '../components/Spinner'
|
||||||
|
|
||||||
|
describe('Spinner', () => {
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const { container } = render(<Spinner message="Hello" />)
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import App from './App'
|
import Home from '../pages'
|
||||||
|
|
||||||
describe('App', () => {
|
describe('Home', () => {
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', () => {
|
||||||
const { container } = render(<App />)
|
const { container } = render(<Home />)
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
30
src/components/Network/Data.module.css
Normal file
30
src/components/Network/Data.module.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.networkData {
|
||||||
|
min-height: 66px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block a {
|
||||||
|
color: var(--brand-grey-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: calc(var(--spacer) / 4);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clientVersion {
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
89
src/components/Network/Data.tsx
Normal file
89
src/components/Network/Data.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { fetchRpc, AxiosResponseCustom } from '../../rpc'
|
||||||
|
import Spinner from '../Spinner'
|
||||||
|
import { NetworkProps } from '.'
|
||||||
|
import styles from './Data.module.css'
|
||||||
|
|
||||||
|
export default function Data({ network }: { network: NetworkProps }) {
|
||||||
|
const { rpcUrl, explorerUrl } = network
|
||||||
|
const [status, setStatus] = useState('')
|
||||||
|
const [block, setBlock] = useState(0)
|
||||||
|
const [latency, setLatency] = useState(0)
|
||||||
|
const [clientVersion, setClientVersion] = useState('')
|
||||||
|
|
||||||
|
async function getStatusAndBlock() {
|
||||||
|
if (!rpcUrl) return
|
||||||
|
|
||||||
|
const response: AxiosResponseCustom = await fetchRpc(
|
||||||
|
rpcUrl,
|
||||||
|
'eth_blockNumber'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response || response.status !== 200) {
|
||||||
|
setStatus('Offline')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('Online')
|
||||||
|
response.duration && setLatency(response.duration)
|
||||||
|
|
||||||
|
const blockNumber =
|
||||||
|
response && response.data && parseInt(response.data.result, 16)
|
||||||
|
setBlock(blockNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getClientVersion() {
|
||||||
|
if (!rpcUrl) return
|
||||||
|
|
||||||
|
const response: AxiosResponseCustom = await fetchRpc(
|
||||||
|
rpcUrl,
|
||||||
|
'web3_clientVersion'
|
||||||
|
)
|
||||||
|
|
||||||
|
response && response.data && setClientVersion(response.data.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getStatusAndBlock()
|
||||||
|
getClientVersion()
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
getStatusAndBlock()
|
||||||
|
getClientVersion()
|
||||||
|
}, 5000) // run every 5 sec.
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
}, [network])
|
||||||
|
|
||||||
|
const isOnline = status === 'Online'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.networkData}>
|
||||||
|
{block > 0 ? (
|
||||||
|
<>
|
||||||
|
<h2 className={styles.status}>
|
||||||
|
<span className={isOnline ? styles.success : styles.error}>
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
{latency && (
|
||||||
|
<span className={styles.latency} title="Latency">
|
||||||
|
{latency} ms
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
|
{block && (
|
||||||
|
<p className={styles.block} title="Current block number">
|
||||||
|
At block #<a href={`${explorerUrl}/blocks/${block}`}>{block}</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{clientVersion && (
|
||||||
|
<p className={styles.clientVersion}>{clientVersion}</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
32
src/components/Network/index.module.css
Normal file
32
src/components/Network/index.module.css
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.network {
|
||||||
|
border: 1px solid var(--brand-grey-dark);
|
||||||
|
padding: var(--spacer);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.networkHeader {
|
||||||
|
margin-bottom: var(--spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: calc(var(--spacer) / 8);
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: var(--font-size-h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title span,
|
||||||
|
.title code {
|
||||||
|
color: var(--brand-grey-light);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title code {
|
||||||
|
font-weight: var(--font-weight-base);
|
||||||
|
}
|
32
src/components/Network/index.tsx
Normal file
32
src/components/Network/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import Data from './Data'
|
||||||
|
|
||||||
|
export interface NetworkProps {
|
||||||
|
name: string
|
||||||
|
project?: string
|
||||||
|
networkId: string
|
||||||
|
chainId?: string
|
||||||
|
type: string
|
||||||
|
rpcUrl?: string
|
||||||
|
explorerUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Network({ network }: { network: NetworkProps }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.network}>
|
||||||
|
<header className={styles.networkHeader}>
|
||||||
|
<h2 className={styles.title}>
|
||||||
|
{network.name}
|
||||||
|
<code>{network.networkId}</code>
|
||||||
|
<span>{network.type}</span>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<code>{network.rpcUrl}</code>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<Data network={network} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
33
src/components/Spinner.module.css
Normal file
33
src/components/Spinner.module.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.spinner {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: calc(var(--spacer) * 2);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner:before {
|
||||||
|
content: '';
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-top: -20px;
|
||||||
|
margin-left: -10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--brand-purple);
|
||||||
|
border-top-color: var(--brand-violet);
|
||||||
|
animation: spinner 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinnerMessage {
|
||||||
|
color: var(--brand-grey-light);
|
||||||
|
padding-top: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
17
src/components/Spinner.tsx
Normal file
17
src/components/Spinner.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './Spinner.module.css'
|
||||||
|
|
||||||
|
const Spinner = ({ message }: { message?: string }) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.spinner}>
|
||||||
|
{message && (
|
||||||
|
<div
|
||||||
|
className={styles.spinnerMessage}
|
||||||
|
dangerouslySetInnerHTML={{ __html: message }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spinner
|
19
src/index.js
19
src/index.js
@ -1,19 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import './styles/global.scss'
|
|
||||||
import App from './App'
|
|
||||||
import * as serviceWorker from './serviceWorker'
|
|
||||||
|
|
||||||
function renderToDOM() {
|
|
||||||
const rootElement = document.getElementById('root')
|
|
||||||
|
|
||||||
if (rootElement !== null) {
|
|
||||||
ReactDOM.render(<App />, rootElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { renderToDOM }
|
|
||||||
|
|
||||||
renderToDOM()
|
|
||||||
|
|
||||||
serviceWorker.register()
|
|
@ -1,22 +0,0 @@
|
|||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { renderToDOM } from '.'
|
|
||||||
|
|
||||||
describe('test ReactDOM.render', () => {
|
|
||||||
const originalRender = ReactDOM.render
|
|
||||||
const originalGetElement = global.document.getElementById
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
global.document.getElementById = () => true
|
|
||||||
ReactDOM.render = jest.fn()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
global.document.getElementById = originalGetElement
|
|
||||||
ReactDOM.render = originalRender
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should call ReactDOM.render', () => {
|
|
||||||
renderToDOM()
|
|
||||||
expect(ReactDOM.render).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
12
src/pages/_app.tsx
Normal file
12
src/pages/_app.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import App from 'next/app'
|
||||||
|
|
||||||
|
import '@oceanprotocol/typographies/css/ocean-typo.css'
|
||||||
|
import '../styles/global.css'
|
||||||
|
|
||||||
|
export default class MyApp extends App {
|
||||||
|
render() {
|
||||||
|
const { Component, pageProps } = this.props
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
||||||
|
}
|
33
src/pages/index.module.css
Normal file
33
src/pages/index.module.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
color: var(--brand-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
color: var(--brand-white);
|
||||||
|
margin: auto;
|
||||||
|
max-width: 35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header svg {
|
||||||
|
width: 75px;
|
||||||
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.networks {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--spacer);
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||||
|
margin: calc(var(--spacer) * 2) auto;
|
||||||
|
max-width: 80rem;
|
||||||
|
}
|
31
src/pages/index.tsx
Normal file
31
src/pages/index.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import atlas from '@ethereum-navigator/atlas'
|
||||||
|
import Logo from '@oceanprotocol/art/logo/logo-white.svg'
|
||||||
|
import Layout from '../Layout'
|
||||||
|
import Network, { NetworkProps } from '../components/Network'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import { title, description } from '../../site.config'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Logo />
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<p>{description}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className={styles.networks}>
|
||||||
|
{atlas
|
||||||
|
.filter(
|
||||||
|
(item: NetworkProps) =>
|
||||||
|
item.project === 'Ocean Protocol' && item.name !== 'Spree'
|
||||||
|
)
|
||||||
|
.reverse()
|
||||||
|
.map((network: NetworkProps) => (
|
||||||
|
<Network key={network.name} network={network} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,18 @@
|
|||||||
import axios from 'axios'
|
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
async function axiosRpcRequest(url, method) {
|
export interface AxiosRequestConfigCustom extends AxiosRequestConfig {
|
||||||
|
metadata?: {
|
||||||
|
startTime: number | Date
|
||||||
|
endTime?: number | Date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AxiosResponseCustom extends AxiosResponse {
|
||||||
|
duration?: number
|
||||||
|
config: AxiosRequestConfigCustom
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRpc(url: string, method: string) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(url, {
|
const response = await axios.post(url, {
|
||||||
method,
|
method,
|
||||||
@ -15,11 +27,11 @@ async function axiosRpcRequest(url, method) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { axiosRpcRequest }
|
export { fetchRpc }
|
||||||
|
|
||||||
// Measure response time and deliver as `response.duration`
|
// Measure response time and deliver as `response.duration`
|
||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
config => {
|
(config: AxiosRequestConfigCustom) => {
|
||||||
config.metadata = { startTime: new Date() }
|
config.metadata = { startTime: new Date() }
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
@ -27,7 +39,7 @@ axios.interceptors.request.use(
|
|||||||
)
|
)
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
response => {
|
(response: any) => {
|
||||||
response.config.metadata.endTime = new Date()
|
response.config.metadata.endTime = new Date()
|
||||||
response.duration =
|
response.duration =
|
||||||
response.config.metadata.endTime - response.config.metadata.startTime
|
response.config.metadata.endTime - response.config.metadata.startTime
|
@ -1,135 +0,0 @@
|
|||||||
// This optional code is used to register a service worker.
|
|
||||||
// register() is not called by default.
|
|
||||||
|
|
||||||
// This lets the app load faster on subsequent visits in production, and gives
|
|
||||||
// it offline capabilities. However, it also means that developers (and users)
|
|
||||||
// will only see deployed updates on subsequent visits to a page, after all the
|
|
||||||
// existing tabs open on the page have been closed, since previously cached
|
|
||||||
// resources are updated in the background.
|
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
|
||||||
// opt-in, read https://bit.ly/CRA-PWA
|
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
|
||||||
window.location.hostname === 'localhost' ||
|
|
||||||
// [::1] is the IPv6 localhost address.
|
|
||||||
window.location.hostname === '[::1]' ||
|
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
|
||||||
window.location.hostname.match(
|
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function register(config) {
|
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
|
||||||
|
|
||||||
if (isLocalhost) {
|
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
|
||||||
checkValidServiceWorker(swUrl, config)
|
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||||
// service worker/PWA documentation.
|
|
||||||
navigator.serviceWorker.ready.then(() => {
|
|
||||||
console.log(
|
|
||||||
'This web app is being served cache-first by a service ' +
|
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Is not localhost. Just register service worker
|
|
||||||
registerValidSW(swUrl, config)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(swUrl)
|
|
||||||
.then(registration => {
|
|
||||||
registration.onupdatefound = () => {
|
|
||||||
const installingWorker = registration.installing
|
|
||||||
if (installingWorker == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
installingWorker.onstatechange = () => {
|
|
||||||
if (installingWorker.state === 'installed') {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
// At this point, the updated precached content has been fetched,
|
|
||||||
// but the previous service worker will still serve the older
|
|
||||||
// content until all client tabs are closed.
|
|
||||||
console.log(
|
|
||||||
'New content is available and will be used when all ' +
|
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onUpdate) {
|
|
||||||
config.onUpdate(registration)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, everything has been precached.
|
|
||||||
// It's the perfect time to display a
|
|
||||||
// "Content is cached for offline use." message.
|
|
||||||
console.log('Content is cached for offline use.')
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onSuccess) {
|
|
||||||
config.onSuccess(registration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error during service worker registration:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
|
||||||
fetch(swUrl)
|
|
||||||
.then(response => {
|
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
|
||||||
const contentType = response.headers.get('content-type')
|
|
||||||
if (
|
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
|
||||||
registration.unregister().then(() => {
|
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Service worker found. Proceed as normal.
|
|
||||||
registerValidSW(swUrl, config)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log(
|
|
||||||
'No internet connection found. App is running in offline mode.'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister() {
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
|
||||||
registration.unregister()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
51
src/styles/_variables.css
Normal file
51
src/styles/_variables.css
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
:root {
|
||||||
|
--brand-white: #fff;
|
||||||
|
--brand-black: #141414;
|
||||||
|
--brand-pink: #ff4092;
|
||||||
|
--brand-purple: #7b1173;
|
||||||
|
--brand-violet: #e000cf;
|
||||||
|
--brand-blue: #11597b;
|
||||||
|
|
||||||
|
--brand-grey: #41474e;
|
||||||
|
--brand-grey-light: #8b98a9;
|
||||||
|
--brand-grey-dark: #303030;
|
||||||
|
--brand-grey-lighter: #e2e2e2;
|
||||||
|
|
||||||
|
--green: #5fb359;
|
||||||
|
--red: #d80606;
|
||||||
|
--orange: #b35f36;
|
||||||
|
--yellow: #eac146;
|
||||||
|
|
||||||
|
--font-family-base: 'Sharp Sans', -apple-system, BlinkMacSystemFont,
|
||||||
|
'Segoe UI', Helvetica, Arial, sans-serif;
|
||||||
|
--font-family-title: 'Sharp Sans Display', -apple-system, BlinkMacSystemFont,
|
||||||
|
'Segoe UI', Helvetica, Arial, sans-serif;
|
||||||
|
--font-family-monospace: 'Fira Code', 'Fira Mono', Menlo, Monaco, Consolas,
|
||||||
|
'Courier New', monospace;
|
||||||
|
|
||||||
|
--font-size-root: 16px;
|
||||||
|
--font-size-base: 1rem;
|
||||||
|
--font-size-large: 1.2rem;
|
||||||
|
--font-size-small: 0.85rem;
|
||||||
|
--font-size-mini: 0.65rem;
|
||||||
|
--font-size-text: $font-size-base;
|
||||||
|
--font-size-label: $font-size-base;
|
||||||
|
--font-size-title: 1.4rem;
|
||||||
|
--font-size-h1: 3rem;
|
||||||
|
--font-size-h2: 1.7rem;
|
||||||
|
--font-size-h3: 1.4rem;
|
||||||
|
--font-size-h4: $font-size-large;
|
||||||
|
--font-size-h5: 1.1rem;
|
||||||
|
--font-weight-base: 500;
|
||||||
|
--font-weight-bold: 600;
|
||||||
|
--line-height: 1.6;
|
||||||
|
|
||||||
|
--spacer: 2rem;
|
||||||
|
--page-frame: 0.75rem;
|
||||||
|
|
||||||
|
--break-point--small: 640px;
|
||||||
|
--break-point--medium: 860px;
|
||||||
|
--break-point--large: 1140px;
|
||||||
|
--break-point--huge: 1400px;
|
||||||
|
--border-radius: 0.2rem;
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
// Colors
|
|
||||||
$brand-white: #fff;
|
|
||||||
$brand-black: #141414;
|
|
||||||
$brand-pink: #ff4092;
|
|
||||||
$brand-purple: #7b1173;
|
|
||||||
$brand-violet: #e000cf;
|
|
||||||
$brand-blue: #11597b;
|
|
||||||
|
|
||||||
$brand-grey: #41474e;
|
|
||||||
$brand-grey-light: #8b98a9;
|
|
||||||
$brand-grey-dark: #303030;
|
|
||||||
$brand-grey-lighter: #e2e2e2;
|
|
||||||
|
|
||||||
$green: #5fb359;
|
|
||||||
$red: #d80606;
|
|
||||||
$orange: #b35f36;
|
|
||||||
$yellow: #eac146;
|
|
||||||
|
|
||||||
$brand-gradient: linear-gradient(to right bottom, $brand-purple, $brand-pink);
|
|
||||||
|
|
||||||
$body-background: $brand-black;
|
|
||||||
|
|
||||||
// Fonts
|
|
||||||
$font-family-base: 'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
||||||
Helvetica, Arial, sans-serif;
|
|
||||||
$font-family-title: 'Sharp Sans Display', -apple-system, BlinkMacSystemFont,
|
|
||||||
'Segoe UI', Helvetica, Arial, sans-serif;
|
|
||||||
$font-family-monospace: 'Fira Code', 'Fira Mono', Menlo, Monaco, Consolas,
|
|
||||||
'Courier New', monospace;
|
|
||||||
|
|
||||||
$font-size-root: 16px;
|
|
||||||
$font-size-base: 1rem;
|
|
||||||
$font-size-large: 1.2rem;
|
|
||||||
$font-size-small: 0.85rem;
|
|
||||||
$font-size-mini: 0.65rem;
|
|
||||||
$font-size-text: $font-size-base;
|
|
||||||
$font-size-label: $font-size-base;
|
|
||||||
$font-size-title: 1.4rem;
|
|
||||||
$font-size-h1: 3rem;
|
|
||||||
$font-size-h2: 1.7rem;
|
|
||||||
$font-size-h3: 1.4rem;
|
|
||||||
$font-size-h4: $font-size-large;
|
|
||||||
$font-size-h5: 1.1rem;
|
|
||||||
|
|
||||||
$font-weight-base: 500;
|
|
||||||
$font-weight-bold: 600;
|
|
||||||
|
|
||||||
$line-height: 1.6;
|
|
||||||
|
|
||||||
// Sizes
|
|
||||||
$spacer: 2rem;
|
|
||||||
$page-frame: 0.75rem;
|
|
||||||
|
|
||||||
$break-point--small: 640px;
|
|
||||||
$break-point--medium: 860px;
|
|
||||||
$break-point--large: 1140px;
|
|
||||||
$break-point--huge: 1400px;
|
|
||||||
$brand-border-width: 1px;
|
|
||||||
|
|
||||||
$border-radius: 0.2rem;
|
|
||||||
|
|
||||||
$narrowWidth: 35rem;
|
|
126
src/styles/global.css
Normal file
126
src/styles/global.css
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
@import '_variables.css';
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: var(--font-size-root);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--brand-grey-light);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-family: var(--font-family-base);
|
||||||
|
font-weight: var(--font-weight-base);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 640px) {
|
||||||
|
body {
|
||||||
|
padding: var(--page-frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--brand-pink);
|
||||||
|
transition: 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: calc(var(--spacer) / var(--line-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
font-family: var(--font-family-title);
|
||||||
|
color: var(--brand-white);
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: var(--font-size-h1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--font-size-h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: var(--font-size-h3);
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: var(--font-size-h4);
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: var(--font-size-h5);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure,
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
embed,
|
||||||
|
canvas,
|
||||||
|
picture {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: calc(var(--spacer) / var(--line-height));
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: calc(var(--spacer) / var(--line-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li + li {
|
||||||
|
margin-top: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li:before {
|
||||||
|
content: ' \25AA';
|
||||||
|
top: -2px;
|
||||||
|
position: absolute;
|
||||||
|
left: -1rem;
|
||||||
|
color: var(--brand-grey-light);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
color: var(--brand-grey-light);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
@ -1,334 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
@import '../../node_modules/@oceanprotocol/typographies/sass/ocean-typo.scss';
|
|
||||||
|
|
||||||
*,
|
|
||||||
*:before,
|
|
||||||
*:after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-disable selector-max-id */
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-enable selector-max-id */
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: $font-size-root;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: $brand-grey-light;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
font-family: $font-family-base;
|
|
||||||
font-weight: $font-weight-base;
|
|
||||||
line-height: $line-height;
|
|
||||||
background: $body-background;
|
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
position: relative;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
@media screen and (min-width: $break-point--small) {
|
|
||||||
padding: $page-frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: $brand-pink;
|
|
||||||
transition: 0.2s ease-out;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: darken($brand-pink, 15%);
|
|
||||||
text-decoration: none;
|
|
||||||
transform: translate3d(0, -0.1rem, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: darken($brand-pink, 15%);
|
|
||||||
text-decoration: none;
|
|
||||||
transform: none;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: $spacer / $line-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lists
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
ul {
|
|
||||||
li {
|
|
||||||
&:before {
|
|
||||||
content: ' \25AA'; // Black Small Square: ▪ ▪
|
|
||||||
top: -2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
counter-reset: ol-counter;
|
|
||||||
|
|
||||||
li {
|
|
||||||
&:before {
|
|
||||||
content: counter(ol-counter) '.';
|
|
||||||
counter-increment: ol-counter;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li:before {
|
|
||||||
content: ' \25AA';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: $spacer;
|
|
||||||
padding-left: $spacer / $line-height;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
li {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
left: -($spacer / $line-height);
|
|
||||||
color: $brand-grey-light;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ li {
|
|
||||||
margin-top: $spacer / 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol,
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-top: $spacer / 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline typography
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong,
|
|
||||||
.bold {
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
em,
|
|
||||||
.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: $font-size-small;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr[title],
|
|
||||||
dfn {
|
|
||||||
text-transform: none;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: inherit;
|
|
||||||
border-bottom: 1px dashed $brand-grey-light;
|
|
||||||
cursor: help;
|
|
||||||
font-feature-settings: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5 {
|
|
||||||
font-family: $font-family-title;
|
|
||||||
color: inherit;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: $font-size-h1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: $font-size-h2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: $font-size-h3;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: $font-size-h4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: $font-size-h5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsive Media
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
figure,
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
audio,
|
|
||||||
embed,
|
|
||||||
canvas,
|
|
||||||
picture {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: $spacer 0;
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 0.1rem solid $brand-grey-lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quotes
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
q {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
cite {
|
|
||||||
font-style: normal;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
blockquote > p {
|
|
||||||
font-style: italic;
|
|
||||||
color: lighten($brand-grey, 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 $spacer;
|
|
||||||
padding-left: $spacer / 2;
|
|
||||||
border-left: 0.2rem solid $brand-grey-lighter;
|
|
||||||
|
|
||||||
@media screen and (min-width: $break-point--small) {
|
|
||||||
padding-left: $spacer / $line-height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tables
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: $spacer * $line-height;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: $spacer / 2;
|
|
||||||
border-bottom: 1px solid $brand-grey-lighter;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 90%;
|
|
||||||
|
|
||||||
&[align='center'] {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[align='right'] {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
pre,
|
|
||||||
samp {
|
|
||||||
font-family: $font-family-monospace;
|
|
||||||
font-size: $font-size-small;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
text-shadow: none;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
h1 &,
|
|
||||||
h2 &,
|
|
||||||
h3 &,
|
|
||||||
h4 &,
|
|
||||||
h5 & {
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(pre) > code {
|
|
||||||
color: inherit;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
a > code {
|
|
||||||
color: $brand-pink;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: $spacer;
|
|
||||||
padding: 0;
|
|
||||||
background: lighten($brand-grey-lighter, 5%);
|
|
||||||
|
|
||||||
// make 'em scrollable
|
|
||||||
overflow: auto;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: $spacer;
|
|
||||||
white-space: pre;
|
|
||||||
display: block;
|
|
||||||
color: $brand-grey-lighter;
|
|
||||||
overflow-wrap: normal;
|
|
||||||
word-wrap: normal;
|
|
||||||
word-break: normal;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection
|
|
||||||
/////////////////////////////////////
|
|
||||||
|
|
||||||
::-moz-selection {
|
|
||||||
background: $brand-grey-light;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: $brand-grey-light;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"lib": ["dom", "es2017"],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmit": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", ".next"],
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user