mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Merge branch 'main' of github.com:tom2drum/block-scout into sentri
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
API_HOST=blockscout.com
|
||||
API_BASE_PATH=/xdai/testnet/api
|
||||
+7
-1
@@ -1,3 +1,9 @@
|
||||
API_AUTHORIZATION_TOKEN=xxx
|
||||
SENTRY_DSN=xxx
|
||||
NEXT_PUBLIC_SENTRY_DSN=xxx
|
||||
NEXT_PUBLIC_SENTRY_DSN=xxx
|
||||
NEXT_PUBLIC_BLOCKSCOUT_VERSION=xxx
|
||||
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
|
||||
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
|
||||
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
|
||||
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
|
||||
NEXT_PUBLIC_SUPPORTED_NETWORKS=[{"name":"Gnosis Chain","type":"xdai","subType":"mainnet","group":"mainnets","isAccountSupported":true},{"name":"Optimism on Gnosis Chain","type":"xdai","subType":"optimism","group":"mainnets"},{"name":"Arbitrum on xDai","type":"xdai","subType":"aox","group":"mainnets"},{"name":"Ethereum","type":"eth","subType":"mainnet","group":"mainnets"},{"name":"Ethereum Classic","type":"etc","subType":"mainnet","group":"mainnets"},{"name":"POA","type":"poa","subType":"core","group":"mainnets"},{"name":"RSK","type":"rsk","subType":"mainnet","group":"mainnets"},{"name":"Gnosis Chain Testnet","type":"xdai","subType":"testnet","group":"testnets","isAccountSupported":true},{"name":"POA Sokol","type":"poa","subType":"sokol","group":"testnets"},{"name":"ARTIS Σ1","type":"artis","subType":"sigma1","group":"other"},{"name":"LUKSO L14","type":"lukso","subType":"l14","group":"other"}]
|
||||
@@ -1,2 +0,0 @@
|
||||
API_HOST=blockscout.com
|
||||
API_BASE_PATH=/xdai/testnet/api
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Publish Docker image on every push to main branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
|
||||
K8S_HOST: ${{ secrets.K8S_HOST }}
|
||||
BASTION_HOST: ${{ secrets.BASTION_HOST }}
|
||||
K8S_PORT: ${{ secrets.K8S_PORT }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/blockscout/frontend
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
deploy:
|
||||
name: Deploy frontend to k8s
|
||||
needs: push_to_registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Set Kubernetes Context
|
||||
uses: azure/k8s-set-context@v1
|
||||
with:
|
||||
method: kubeconfig
|
||||
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
||||
- name: Deploy to k8s
|
||||
shell: bash
|
||||
working-directory: charts
|
||||
# port forwarding works only inside the step, consider refactoring as TS action
|
||||
env:
|
||||
NAMESPACE_NAME: bs-frontend
|
||||
run: |
|
||||
mkdir ~/.ssh
|
||||
ssh-keyscan -H $BASTION_HOST >> ~/.ssh/known_hosts
|
||||
eval `ssh-agent -s`
|
||||
ssh-add - <<< "$BASTION_SSH_KEY"
|
||||
sudo echo "127.0.0.1 $K8S_HOST" | sudo tee -a /etc/hosts
|
||||
ssh -fN -v -L $K8S_LOCAL_PORT:$K8S_HOST:$K8S_PORT $USERNAME@$BASTION_HOST
|
||||
helm upgrade --install -n $NAMESPACE_NAME $NAMESPACE_NAME ./ -f values-frontend.yaml --create-namespace
|
||||
@@ -1,11 +1,48 @@
|
||||
[Design](https://www.figma.com/file/07zoJSAP7Vo655ertmlppA/My_Account?node-id=279%3A1006) | [API Doc](https://github.com/blockscout/blockscout-account/blob/account/apps/block_scout_web/API.md)
|
||||
[Design](https://www.figma.com/file/07zoJSAP7Vo655ertmlppA/My_Account?node-id=279%3A1006) | [API Doc](https://github.com/blockscout/blockscout-account/blob/account/apps/block_scout_web/API.md) | [Swagger](https://app.swaggerhub.com/apis/NIKITOSING4/blockscout-account-api/1.0)
|
||||
|
||||
-----
|
||||
## Technology stack
|
||||
|
||||
Core technologies what used in the project are
|
||||
- [yarn](https://yarnpkg.com/) as package manager
|
||||
- [React](https://reactjs.org/) as UI library
|
||||
- [Next.js](https://nextjs.org/) as application framework
|
||||
- [Chakra](https://chakra-ui.com/) as component library; our theme customization could be found in `/theme` folder
|
||||
- [css-modules](https://github.com/css-modules/css-modules) as lib for styling custom components
|
||||
|
||||
And of course our premier language is [Typescript](https://www.typescriptlang.org/)
|
||||
|
||||
-----
|
||||
## Local Development
|
||||
|
||||
For local development follow next steps:
|
||||
For local development please follow next steps:
|
||||
- clone repo
|
||||
- install dependencies with `yarn`
|
||||
- create local env file `env.local` according to `env.example`
|
||||
- `API_AUTHORIZATION_TOKEN` could be obtain [here](https://blockscout.com/xdai/testnet/auth/auth0_api)
|
||||
- create local env file `env.local` according to `env.example` snapshot (see list of used environment variables [below](#environment-variables))
|
||||
- run `yarn dev` to spin up local dev server and navigate to the host from logs output
|
||||
|
||||
## Environment variables
|
||||
### Variables list
|
||||
The app instance could be customized by passing following variables to NodeJS environment.
|
||||
|
||||
| Variable | Type | Description | Default value
|
||||
| --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_BLOCKSCOUT_VERSION | `string` | Current running version of Blockscout (used to display link to release in the footer) |
|
||||
| NEXT_PUBLIC_FOOTER_GITHUB_LINK | `string` | Link to Github in the footer | `https://github.com/blockscout/blockscout` |
|
||||
| NEXT_PUBLIC_FOOTER_TWITTER_LINK | `string` | Link to Twitter in the footer | `https://www.twitter.com/blockscoutcom` |
|
||||
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` | Link to Telegram in the footer | `https://t.me/poa_network` |
|
||||
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` |
|
||||
| NEXT_PUBLIC_SUPPORTED_NETWORKS | `Array<Network>` where `Network` can have following [properties](#network-configuration-properties) | Configuration of supported networks | `[{"name":"Gnosis Chain","type":"xdai","subType":"mainnet","group":"mainnets","isAccountSupported":true},{"name":"Optimism on Gnosis Chain","type":"xdai","subType":"optimism","group":"mainnets"},{"name":"Arbitrum on xDai","type":"xdai","subType":"aox","group":"mainnets"},{"name":"Ethereum","type":"eth","subType":"mainnet","group":"mainnets"},{"name":"Ethereum Classic","type":"etc","subType":"mainnet","group":"mainnets"},{"name":"POA","type":"poa","subType":"core","group":"mainnets"},{"name":"RSK","type":"rsk","subType":"mainnet","group":"mainnets"},{"name":"Gnosis Chain Testnet","type":"xdai","subType":"testnet","group":"testnets","isAccountSupported":true},{"name":"POA Sokol","type":"poa","subType":"sokol","group":"testnets"},{"name":"ARTIS Σ1","type":"artis","subType":"sigma1","group":"other"},{"name":"LUKSO L14","type":"lukso","subType":"l14","group":"other"}]` |
|
||||
|
||||
### Network configuration properties
|
||||
|
||||
| Property | Type | Description | Example value
|
||||
| --- | --- | --- | --- |
|
||||
| name | `string` | Displayed name of the network | `"Gnosis Chain"` |
|
||||
| type | `string` | Network type (used as first part of the base path) | `"xdai"` |
|
||||
| subType | `string` | Network subtype (used as second part of the base path) | `"mainnet"` |
|
||||
| group | `mainnets \| testnets \| other` | Indicates in which tab network appears in the menu | `"mainnets"` |
|
||||
| isAccountSupported | `boolean` *(optional)* | Set to true if network has account feature | `true` |
|
||||
| icon | `string` *(optional)* | Network icon; if not provided, will fallback to icon predefined in the project; if the project doesn't have icon for such network then the common placeholder will be shown; *Note* that icon size should be 30px by 30px | `"https://www.fillmurray.com/60/60"` |
|
||||
|
||||
*Note* the base path for the network is built up from its `type` and `subType` like so `https://blockscout.com/<type>/<subType>`
|
||||
@@ -0,0 +1 @@
|
||||
# values-frontend.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
appVersion: 0.0.1
|
||||
version: 0.0.1
|
||||
name: bs-frontend
|
||||
description: '''
|
||||
Helm chart for deploying blockscout frontend in K8S
|
||||
|
||||
Deploy command: `helm upgrade --install -n=<namespace> bs-frontend ./ -f values-<name>.yaml`
|
||||
'''
|
||||
@@ -0,0 +1,25 @@
|
||||
{{- define "app_env" }}
|
||||
{{- range $key, $value := .Values.environment }}
|
||||
{{- $item := get $.Values.environment $key }}
|
||||
{{- if or (kindIs "string" $item) (kindIs "int64" $item) (kindIs "bool" $item)}}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- else }}
|
||||
- name: {{ $key }}
|
||||
value: {{ pluck $.Values.global.env $item | first | default $item._default | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "node_env" }}
|
||||
{{- range $key, $value := .Values.node_environment }}
|
||||
{{- $item := get $.Values.node_environment $key }}
|
||||
{{- if or (kindIs "string" $item) (kindIs "int64" $item) (kindIs "bool" $item)}}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- else }}
|
||||
- name: {{ $key }}
|
||||
value: {{ pluck $.Values.global.env $item | first | default $item._default | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,62 @@
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "http-metrics"
|
||||
spec:
|
||||
replicas: {{ .Values.replicas.app }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
# serviceAccountName: vault-auth
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: {{ .Release.Name }}
|
||||
image: {{ .Values.image }}
|
||||
resources:
|
||||
{{- with .Values.resources }}
|
||||
limits:
|
||||
memory: {{ pluck $.Values.global.env .limits.memory | first | default .limits.memory._default | quote }}
|
||||
cpu: {{ pluck $.Values.global.env .limits.cpu | first | default .limits.cpu._default | quote }}
|
||||
requests:
|
||||
memory: {{ pluck $.Values.global.env .requests.memory | first | default .requests.memory._default | quote }}
|
||||
cpu: {{ pluck $.Values.global.env .requests.cpu | first | default .requests.cpu._default | quote }}
|
||||
{{- end }}
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: {{ .Values.docker.targetPort }}
|
||||
env:
|
||||
{{- include "app_env" . | indent 10 }}
|
||||
# volumeMounts:
|
||||
# - name: smweb-logs
|
||||
# mountPath: /usr/local/sm-web-server/log
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /appversion
|
||||
# port: {{ .Values.docker.port }}
|
||||
# scheme: HTTP
|
||||
# initialDelaySeconds: 60
|
||||
# periodSeconds: 10
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /appversion
|
||||
# port: {{ .Values.docker.port }}
|
||||
# scheme: HTTP
|
||||
# initialDelaySeconds: 100
|
||||
# periodSeconds: 100
|
||||
# volumes:
|
||||
# - name: smweb-logs
|
||||
# emptyDir: { }
|
||||
# - name: config
|
||||
# configMap:
|
||||
# name: {{ .Release.Name }}-promtail-configmap
|
||||
restartPolicy: Always
|
||||
---
|
||||
@@ -0,0 +1,29 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: internal-and-public
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: 500m
|
||||
nginx.ingress.kubernetes.io/client-max-body-size: "500M"
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "15m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "15m"
|
||||
# cert-manager.io/cluster-issuer: vault
|
||||
name: {{ .Release.Name }}-ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: "/"
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}-svc
|
||||
port:
|
||||
number: {{ .Values.docker.port }}
|
||||
# tls:
|
||||
# - hosts:
|
||||
# - {{ .Values.ingress.host }}
|
||||
# secretName: xcloud-cert-srv
|
||||
@@ -0,0 +1,14 @@
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-svc
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: {{ .Values.docker.port }}
|
||||
targetPort: {{ .Values.docker.targetPort }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: {{ .Release.Name }}
|
||||
---
|
||||
@@ -0,0 +1,29 @@
|
||||
image: ghcr.io/blockscout/frontend:main
|
||||
|
||||
replicas:
|
||||
app: 1
|
||||
docker:
|
||||
port: 80
|
||||
targetPort: 3000
|
||||
|
||||
ingress:
|
||||
host: blockscout-frontend.aws-k8s.blockscout.com
|
||||
|
||||
resources:
|
||||
limits:
|
||||
memory:
|
||||
_default: "0.3Gi"
|
||||
cpu:
|
||||
_default: "0.2"
|
||||
requests:
|
||||
memory:
|
||||
_default: "0.3Gi"
|
||||
cpu:
|
||||
_default: "0.2"
|
||||
|
||||
environment: {}
|
||||
# ENV_NAME:
|
||||
# _default: value
|
||||
|
||||
global:
|
||||
env: test
|
||||
+1
-1
@@ -9,7 +9,7 @@ export default function fetch(path: string, init?: RequestInit): Promise<Respons
|
||||
authorization: `Bearer ${ process.env.API_AUTHORIZATION_TOKEN }`,
|
||||
'content-type': 'application/json',
|
||||
};
|
||||
const url = `https://${ process.env.API_HOST }${ process.env.API_BASE_PATH }${ path }`;
|
||||
const url = `https://blockscout.com${ path }`;
|
||||
|
||||
return nodeFetch(url, {
|
||||
headers,
|
||||
|
||||
+11
-1
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
import fetch from 'lib/api/fetch';
|
||||
import * as cookies from 'lib/cookies';
|
||||
|
||||
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
|
||||
@@ -8,7 +9,16 @@ export default function handler<TRes>(getUrl: (_req: NextApiRequest) => string,
|
||||
return async(_req: NextApiRequest, res: NextApiResponse<TRes>) => {
|
||||
if (_req.method && allowedMethods.includes(_req.method as Methods)) {
|
||||
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
|
||||
const response = await fetch(getUrl(_req), {
|
||||
const networkType = _req.cookies[cookies.NAMES.NETWORK_TYPE];
|
||||
const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE];
|
||||
|
||||
if (!networkType || !networkSubType) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Incorrect network: NETWORK_TYPE=${ networkType } NETWORK_SUB_TYPE=${ networkSubType }`);
|
||||
}
|
||||
|
||||
const url = `/${ networkType }/${ networkSubType }/api${ getUrl(_req) }`;
|
||||
const response = await fetch(url, {
|
||||
method: _req.method,
|
||||
body: isBodyDisallowed ? undefined : _req.body,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export interface ErrorType<T> {
|
||||
error?: T;
|
||||
status: Response['status'];
|
||||
statusText: Response['statusText'];
|
||||
}
|
||||
|
||||
export default function clientFetch<Response, Error>(path: string, init: RequestInit): Promise<Response | ErrorType<Error>> {
|
||||
return fetch(path, init).then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(
|
||||
(jsonError) => Promise.reject({
|
||||
error: jsonError as Error,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
}),
|
||||
() => Promise.reject({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
}),
|
||||
);
|
||||
|
||||
} else {
|
||||
return response.json() as Promise<Response>;
|
||||
}
|
||||
});
|
||||
}
|
||||
+3
-1
@@ -4,7 +4,9 @@ import { Cookies } from 'typescript-cookie';
|
||||
import isBrowser from './isBrowser';
|
||||
|
||||
export enum NAMES {
|
||||
NAV_BAR_COLLAPSED='nav_bar_collapsed'
|
||||
NAV_BAR_COLLAPSED='nav_bar_collapsed',
|
||||
NETWORK_TYPE='network_type',
|
||||
NETWORK_SUB_TYPE='network_sub_type',
|
||||
}
|
||||
|
||||
export function get(name?: string | undefined | null) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useBreakpointValue } from '@chakra-ui/react';
|
||||
|
||||
export default function useIsMobile() {
|
||||
return useBreakpointValue({ base: true, lg: false });
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import Toast from 'ui/shared/Toast';
|
||||
const defaultOptions: UseToastOptions & { toastComponent?: React.FC<ToastProps> } = {
|
||||
toastComponent: Toast,
|
||||
position: 'top-right',
|
||||
duration: 10000000,
|
||||
isClosable: true,
|
||||
containerStyle: {
|
||||
margin: 8,
|
||||
},
|
||||
};
|
||||
|
||||
export default function useToastModified() {
|
||||
|
||||
+95
-82
@@ -10,88 +10,101 @@ import poaSokolIcon from 'icons/networks/poa-sokol.svg';
|
||||
import poaIcon from 'icons/networks/poa.svg';
|
||||
import rskIcon from 'icons/networks/rsk.svg';
|
||||
|
||||
export const NETWORKS: Array<Network> = [
|
||||
{
|
||||
name: 'Gnosis Chain',
|
||||
type: 'xdai',
|
||||
subType: 'mainnet',
|
||||
icon: gnosisIcon,
|
||||
group: 'mainnets',
|
||||
isAccountSupported: true,
|
||||
isNewUiSupported: true,
|
||||
},
|
||||
{
|
||||
name: 'Optimism on Gnosis Chain',
|
||||
type: 'xdai',
|
||||
subType: 'optimism',
|
||||
icon: optimismIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'Arbitrum on xDai',
|
||||
type: 'xdai',
|
||||
subType: 'aox',
|
||||
icon: arbitrumIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum',
|
||||
type: 'eth',
|
||||
subType: 'mainnet',
|
||||
icon: ethereumIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'Ethereum Classic',
|
||||
type: 'etc',
|
||||
subType: 'mainnet',
|
||||
icon: ethereumClassicIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'POA',
|
||||
type: 'poa',
|
||||
subType: 'core',
|
||||
icon: poaIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'RSK',
|
||||
type: 'rsk',
|
||||
subType: 'mainnet',
|
||||
icon: rskIcon,
|
||||
group: 'mainnets',
|
||||
},
|
||||
{
|
||||
name: 'Gnosis Chain Testnet',
|
||||
type: 'xdai',
|
||||
subType: 'testnet',
|
||||
icon: arbitrumIcon,
|
||||
group: 'testnets',
|
||||
isAccountSupported: true,
|
||||
isNewUiSupported: true,
|
||||
},
|
||||
{
|
||||
name: 'POA Sokol',
|
||||
type: 'poa',
|
||||
subType: 'sokol',
|
||||
icon: poaSokolIcon,
|
||||
group: 'testnets',
|
||||
},
|
||||
{
|
||||
name: 'ARTIS Σ1',
|
||||
type: 'artis',
|
||||
subType: 'sigma1',
|
||||
icon: artisIcon,
|
||||
group: 'other',
|
||||
},
|
||||
{
|
||||
name: 'LUKSO L14',
|
||||
type: 'lukso',
|
||||
subType: 'l14',
|
||||
group: 'other',
|
||||
},
|
||||
];
|
||||
// will change later when we agree how to host network icons
|
||||
const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
|
||||
'xdai/mainnet': gnosisIcon,
|
||||
'xdai/optimism': optimismIcon,
|
||||
'xdai/aox': arbitrumIcon,
|
||||
'eth/mainnet': ethereumIcon,
|
||||
'etc/mainnet': ethereumClassicIcon,
|
||||
'poa/core': poaIcon,
|
||||
'rsk/mainnet': rskIcon,
|
||||
'xdai/testnet': arbitrumIcon,
|
||||
'poa/sokol': poaSokolIcon,
|
||||
'artis/sigma1': artisIcon,
|
||||
};
|
||||
|
||||
export const NETWORKS: Array<Network> = (() => {
|
||||
try {
|
||||
const networksFromConfig: Array<Network> = JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]');
|
||||
return networksFromConfig.map((network) => ({ ...network, icon: network.icon || ICONS[`${ network.type }/${ network.subType }`] }));
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
})();
|
||||
|
||||
// for easy env creation
|
||||
// const FOR_CONFIG = [
|
||||
// {
|
||||
// name: 'Gnosis Chain',
|
||||
// type: 'xdai',
|
||||
// subType: 'mainnet',
|
||||
// group: 'mainnets',
|
||||
// isAccountSupported: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'Optimism on Gnosis Chain',
|
||||
// type: 'xdai',
|
||||
// subType: 'optimism',
|
||||
// group: 'mainnets',
|
||||
// icon: 'https://www.fillmurray.com/60/60'
|
||||
// },
|
||||
// {
|
||||
// name: 'Arbitrum on xDai',
|
||||
// type: 'xdai',
|
||||
// subType: 'aox',
|
||||
// group: 'mainnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'Ethereum',
|
||||
// type: 'eth',
|
||||
// subType: 'mainnet',
|
||||
// group: 'mainnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'Ethereum Classic',
|
||||
// type: 'etc',
|
||||
// subType: 'mainnet',
|
||||
// group: 'mainnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'POA',
|
||||
// type: 'poa',
|
||||
// subType: 'core',
|
||||
// group: 'mainnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'RSK',
|
||||
// type: 'rsk',
|
||||
// subType: 'mainnet',
|
||||
// group: 'mainnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'Gnosis Chain Testnet',
|
||||
// type: 'xdai',
|
||||
// subType: 'testnet',
|
||||
// group: 'testnets',
|
||||
// isAccountSupported: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'POA Sokol',
|
||||
// type: 'poa',
|
||||
// subType: 'sokol',
|
||||
// group: 'testnets',
|
||||
// },
|
||||
// {
|
||||
// name: 'ARTIS Σ1',
|
||||
// type: 'artis',
|
||||
// subType: 'sigma1',
|
||||
// group: 'other',
|
||||
// },
|
||||
// {
|
||||
// name: 'LUKSO L14',
|
||||
// type: 'lukso',
|
||||
// subType: 'l14',
|
||||
// group: 'other',
|
||||
// },
|
||||
// ];
|
||||
|
||||
export const ACCOUNT_ROUTES = [ '/watchlist', '/private-tags', '/public-tags', '/api-keys', '/custom-abi' ];
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const moduleExports = {
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
destination: '/xdai/mainnet',
|
||||
destination: '/xdai/testnet',
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
import type { AlertStatus } from '@chakra-ui/react';
|
||||
import { Center, Button, VStack, HStack, Box } from '@chakra-ui/react';
|
||||
import { Center, VStack, Box } from '@chakra-ui/react';
|
||||
import type { NextPage } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
const openToast = (status: AlertStatus) => () => {
|
||||
toast({
|
||||
title: 'Account created.',
|
||||
description: 'We\'ve created your account for you.',
|
||||
status,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Center h="100%">
|
||||
<Center h="100%" fontSize={{ base: 'sm', lg: 'xl' }}>
|
||||
<VStack gap={ 4 }>
|
||||
<Box>home page for { router.query.network_type } { router.query.network_sub_type } network</Box>
|
||||
<HStack>
|
||||
<Button onClick={ openToast('info') } > Show Info </Button>
|
||||
<Button onClick={ openToast('error') } > Show Error </Button>
|
||||
<Button onClick={ openToast('warning') } > Show Warning </Button>
|
||||
<Button onClick={ openToast('success') } > Show Success </Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
</Page>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const breakpoints = {
|
||||
// maybe we need them in future
|
||||
// sm: '320px',
|
||||
// md: '768px',
|
||||
lg: '1000px',
|
||||
// these breakpoints are needed just to make "lg" work
|
||||
xl: '2000px',
|
||||
'2xl': '3000px',
|
||||
};
|
||||
|
||||
export default breakpoints;
|
||||
@@ -3,6 +3,7 @@ import { extendTheme } from '@chakra-ui/react';
|
||||
import components from './components/index';
|
||||
import config from './config';
|
||||
import borders from './foundations/borders';
|
||||
import breakpoints from './foundations/breakpoints';
|
||||
import colors from './foundations/colors';
|
||||
import typography from './foundations/typography';
|
||||
import global from './global';
|
||||
@@ -16,6 +17,7 @@ const overrides = {
|
||||
styles: {
|
||||
global,
|
||||
},
|
||||
breakpoints,
|
||||
};
|
||||
|
||||
export default extendTheme(overrides);
|
||||
|
||||
@@ -80,13 +80,11 @@ export interface PublicTag {
|
||||
full_name: string;
|
||||
email: string;
|
||||
company: string;
|
||||
addresses: string; // address_1;<address_2;address_3 etc.
|
||||
addresses: Array<string>;
|
||||
additional_comment: string;
|
||||
}
|
||||
|
||||
export type PublicTagNew = Omit<PublicTag, 'addresses' | 'id'> & {
|
||||
addresses_array: Array<string>;
|
||||
}
|
||||
export type PublicTagNew = Omit<PublicTag, 'id'>
|
||||
|
||||
export type PublicTags = Array<PublicTag>;
|
||||
|
||||
|
||||
+1
-2
@@ -8,7 +8,6 @@ export interface Network {
|
||||
type: string;
|
||||
subType: string;
|
||||
group: 'mainnets' | 'testnets' | 'other';
|
||||
icon?: FunctionComponent<SVGAttributes<SVGElement>>;
|
||||
icon?: FunctionComponent<SVGAttributes<SVGElement>> | string;
|
||||
isAccountSupported?: boolean;
|
||||
isNewUiSupported?: boolean;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ import twIcon from 'icons/social/tweet.svg';
|
||||
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
|
||||
|
||||
const SOCIAL_LINKS = [
|
||||
{ link: '#gh', icon: ghIcon },
|
||||
{ link: '#tw', icon: twIcon },
|
||||
{ link: '#tg', icon: tgIcon },
|
||||
{ link: '#stats', icon: statsIcon },
|
||||
];
|
||||
{ link: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, icon: ghIcon },
|
||||
{ link: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK, icon: twIcon },
|
||||
{ link: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK, icon: tgIcon },
|
||||
{ link: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK, icon: statsIcon },
|
||||
].filter(({ link }) => link !== undefined);
|
||||
|
||||
const BLOCKSCOUT_VERSION = process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION;
|
||||
const VERSION_URL = `https://github.com/blockscout/blockscout/tree/${ BLOCKSCOUT_VERSION }`;
|
||||
|
||||
interface Props {
|
||||
isCollapsed: boolean;
|
||||
@@ -47,7 +50,7 @@ const NavFooter = ({ isCollapsed }: Props) => {
|
||||
<Text variant="secondary">
|
||||
Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.
|
||||
</Text>
|
||||
<Text variant="secondary">Version: <Link>v4.2.1-beta</Link></Text>
|
||||
<Text variant="secondary">Version: <Link href={ VERSION_URL } target="_blank">{ BLOCKSCOUT_VERSION }</Link></Text>
|
||||
</>
|
||||
) }
|
||||
</VStack>
|
||||
|
||||
@@ -35,11 +35,11 @@ const Navigation = () => {
|
||||
];
|
||||
|
||||
const accountNavItems = [
|
||||
{ text: 'Watchlist', pathname: basePath + '/watchlist', icon: watchlistIcon },
|
||||
{ text: 'Private tags', pathname: basePath + '/private-tags', icon: privateTagIcon },
|
||||
{ text: 'Public tags', pathname: basePath + '/public-tags', icon: publicTagIcon },
|
||||
{ text: 'API keys', pathname: basePath + '/api-keys', icon: apiKeysIcon },
|
||||
{ text: 'Custom ABI', pathname: basePath + '/custom-abi', icon: abiIcon },
|
||||
{ text: 'Watchlist', pathname: basePath + '/account/watchlist', icon: watchlistIcon },
|
||||
{ text: 'Private tags', pathname: basePath + '/account/private_tags', icon: privateTagIcon },
|
||||
{ text: 'Public tags', pathname: basePath + '/account/public_tags_request', icon: publicTagIcon },
|
||||
{ text: 'API keys', pathname: basePath + '/account/api_key', icon: apiKeysIcon },
|
||||
{ text: 'Custom ABI', pathname: basePath + '/account/custom_abi', icon: abiIcon },
|
||||
];
|
||||
|
||||
const [ isCollapsed, setCollapsedState ] = React.useState(cookies.get(cookies.NAMES.NAV_BAR_COLLAPSED) === 'true');
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Popover, PopoverTrigger, Icon, useColorModeValue, Button } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import networksIcon from 'icons/networks.svg';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
|
||||
|
||||
import NetworkMenuPopup from './NetworkMenuPopup';
|
||||
@@ -11,6 +13,19 @@ interface Props {
|
||||
}
|
||||
|
||||
const NetworkMenu = ({ isCollapsed }: Props) => {
|
||||
const router = useRouter();
|
||||
const networkType = router.query.network_type;
|
||||
const networkSubType = router.query.network_sub_type;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof networkType === 'string') {
|
||||
cookies.set(cookies.NAMES.NETWORK_TYPE, networkType);
|
||||
}
|
||||
if (typeof networkSubType === 'string') {
|
||||
cookies.set(cookies.NAMES.NETWORK_SUB_TYPE, networkSubType);
|
||||
}
|
||||
}, [ networkType, networkSubType ]);
|
||||
|
||||
return (
|
||||
<Popover openDelay={ 300 } placement="right-start" gutter={ 22 } isLazy>
|
||||
<PopoverTrigger>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Flex, Icon, Text } from '@chakra-ui/react';
|
||||
import { Box, Flex, Icon, Text, Image } from '@chakra-ui/react';
|
||||
import NextLink from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Props extends Network {
|
||||
routeName: string;
|
||||
}
|
||||
|
||||
const NetworkMenuLink = ({ name, type, subType, icon, isActive, routeName, isAccountSupported, isNewUiSupported }: Props) => {
|
||||
const NetworkMenuLink = ({ name, type, subType, icon, isActive, routeName, isAccountSupported }: Props) => {
|
||||
const isAccount = isAccountRoute(routeName);
|
||||
const localPath = (() => {
|
||||
if (isAccount && isAccountSupported) {
|
||||
@@ -32,10 +32,20 @@ const NetworkMenuLink = ({ name, type, subType, icon, isActive, routeName, isAcc
|
||||
const pathName = `/${ type }/${ subType }${ localPath }`;
|
||||
|
||||
// will fix later after we agree on CI/CD workflow
|
||||
const href = isNewUiSupported ? pathName : 'https://blockscout.com' + pathName;
|
||||
const href = type === 'xdai' && subType === 'testnet' ? pathName : 'https://blockscout.com' + pathName;
|
||||
const hasIcon = Boolean(icon);
|
||||
const colors = useColors({ hasIcon });
|
||||
|
||||
const iconEl = typeof icon === 'string' ? (
|
||||
<Image w="30px" h="30px" src={ icon } alt={ `${ type } ${ subType } network icon` }/>
|
||||
) : (
|
||||
<Icon
|
||||
as={ hasIcon ? icon : placeholderIcon }
|
||||
boxSize="30px"
|
||||
color={ isActive ? colors.icon.active : colors.icon.default }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box as="li" listStyleType="none">
|
||||
<NextLink href={ href } passHref>
|
||||
@@ -51,11 +61,7 @@ const NetworkMenuLink = ({ name, type, subType, icon, isActive, routeName, isAcc
|
||||
bgColor={ isActive ? colors.bg.active : colors.bg.default }
|
||||
_hover={{ color: isActive ? colors.text.active : colors.text.hover }}
|
||||
>
|
||||
<Icon
|
||||
as={ hasIcon ? icon : placeholderIcon }
|
||||
boxSize="30px"
|
||||
color={ isActive ? colors.icon.active : colors.icon.default }
|
||||
/>
|
||||
{ iconEl }
|
||||
<Text
|
||||
marginLeft={ 3 }
|
||||
fontWeight="500"
|
||||
|
||||
@@ -9,23 +9,26 @@ import { NETWORKS } from 'lib/networks';
|
||||
import NetworkMenuLink from './NetworkMenuLink';
|
||||
|
||||
const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ];
|
||||
const availableTabs = TABS.filter((tab) => NETWORKS.some(({ group }) => group === tab));
|
||||
|
||||
const NetworkMenuPopup = () => {
|
||||
const router = useRouter();
|
||||
const routeName = router.pathname.replace('/[network_type]/[network_sub_type]', '');
|
||||
const selectedNetwork = NETWORKS.find((network) => router.query.network_type === network.type && router.query.network_sub_type === network.subType);
|
||||
const selectedTab = TABS.findIndex((tab) => selectedNetwork?.group === tab);
|
||||
const selectedTab = availableTabs.findIndex((tab) => selectedNetwork?.group === tab);
|
||||
|
||||
return (
|
||||
<PopoverContent w="382px">
|
||||
<PopoverBody>
|
||||
<Text as="h4" fontSize="18px" lineHeight="30px" fontWeight="500">Networks</Text>
|
||||
<Tabs variant="soft-rounded" mt={ 4 } isLazy defaultIndex={ selectedTab !== -1 ? selectedTab : undefined }>
|
||||
<TabList>
|
||||
{ TABS.map((tab) => <Tab key={ tab } textTransform="capitalize">{ tab }</Tab>) }
|
||||
</TabList>
|
||||
{ availableTabs.length > 1 && (
|
||||
<TabList>
|
||||
{ availableTabs.map((tab) => <Tab key={ tab } textTransform="capitalize">{ tab }</Tab>) }
|
||||
</TabList>
|
||||
) }
|
||||
<TabPanels mt={ 8 }>
|
||||
{ TABS.map((tab) => (
|
||||
{ availableTabs.map((tab) => (
|
||||
<TabPanel key={ tab } p={ 0 }>
|
||||
<VStack as="ul" spacing={ 2 } alignItems="stretch" mt={ 4 }>
|
||||
{ NETWORKS
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
Box,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { animateScroll } from 'react-scroll';
|
||||
|
||||
import type { PublicTag } from 'types/api/account';
|
||||
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import PublicTagsData from 'ui/publicTags/PublicTagsData';
|
||||
import PublicTagsForm from 'ui/publicTags/PublicTagsForm/PublicTagsForm';
|
||||
import AccountPageHeader from 'ui/shared/AccountPageHeader';
|
||||
|
||||
@@ -36,7 +36,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
|
||||
<Tr alignItems="top" key={ item.id }>
|
||||
<Td>
|
||||
<VStack spacing={ 4 } alignItems="unset">
|
||||
{ item.addresses.split(';').map((address) => {
|
||||
{ item.addresses.map((address) => {
|
||||
return (
|
||||
<HStack spacing={ 4 } key={ address } overflow="hidden" alignItems="start">
|
||||
<AddressIcon address={ address }/>
|
||||
|
||||
@@ -60,7 +60,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
|
||||
companyName: data?.company || '',
|
||||
companyUrl: data?.website || '',
|
||||
tags: data?.tags.split(';').map((tag) => tag).join('; ') || '',
|
||||
addresses: data?.addresses.split(';').map((address, index: number) => ({ name: `address.${ index }.address`, address })) ||
|
||||
addresses: data?.addresses.map((address, index: number) => ({ name: `address.${ index }.address`, address })) ||
|
||||
[ { name: 'address.0.address', address: '' } ],
|
||||
comment: data?.additional_comment || '',
|
||||
action: data?.is_owner === undefined || data?.is_owner ? 'add' : 'report',
|
||||
@@ -84,7 +84,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
|
||||
company: formData.companyName || '',
|
||||
website: formData.companyUrl || '',
|
||||
is_owner: formData.action === 'add',
|
||||
addresses_array: formData.addresses?.map(({ address }) => address) || [],
|
||||
addresses: formData.addresses?.map(({ address }) => address) || [],
|
||||
tags: formData.tags?.split(';').map((s) => s.trim()).join(';') || '',
|
||||
additional_comment: formData.comment || '',
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro
|
||||
bgColor={ bgColor }
|
||||
textAlign="start"
|
||||
width="auto"
|
||||
maxWidth="400px"
|
||||
>
|
||||
<chakra.div flex="1" maxWidth="100%">
|
||||
{ title && <AlertTitle id={ ids?.title }>{ title }</AlertTitle> }
|
||||
|
||||
Reference in New Issue
Block a user