From 937dff52b5d3c80ef4f240e9b861c8565b6789da Mon Sep 17 00:00:00 2001 From: painterpuppets Date: Mon, 11 Mar 2024 05:07:36 +0800 Subject: [PATCH 1/2] feat: add ckb explorer page --- packages/explorer/.env.example | 21 ++ packages/explorer/.eslintrc.cjs | 7 + packages/explorer/.lintstagedrc.js | 9 + packages/explorer/.stylelintrc.json | 52 ++++ packages/explorer/i18next-parser.config.ts | 120 ++++++++ packages/explorer/next-i18next.config.js | 21 ++ packages/explorer/next.config.mjs | 107 ++++++++ packages/explorer/package.json | 77 ++++++ packages/explorer/public/favicon.ico | Bin 0 -> 4286 bytes packages/explorer/public/favicon.svg | 6 + packages/explorer/public/locales/en/app.json | 3 + .../explorer/public/locales/en/changelog.json | 4 + .../explorer/public/locales/en/common.json | 37 +++ packages/explorer/public/locales/en/home.json | 12 + packages/explorer/public/locales/zh/app.json | 3 + .../explorer/public/locales/zh/changelog.json | 4 + .../explorer/public/locales/zh/common.json | 37 +++ packages/explorer/public/locales/zh/home.json | 12 + .../src/components/Button/index.module.scss | 76 +++++ .../explorer/src/components/Button/index.tsx | 38 +++ .../src/components/Page/index.module.scss | 19 ++ .../explorer/src/components/Page/index.tsx | 71 +++++ .../src/components/TableOfContents/index.tsx | 173 ++++++++++++ .../src/components/Tooltip/index.module.scss | 30 ++ .../explorer/src/components/Tooltip/index.tsx | 39 +++ packages/explorer/src/env.mjs | 71 +++++ packages/explorer/src/hooks/index.ts | 1 + .../explorer/src/hooks/useMarkdownProps.tsx | 81 ++++++ packages/explorer/src/pages/_app.page.tsx | 69 +++++ .../explorer/src/pages/_document.page.tsx | 73 +++++ .../src/pages/api/trpc/[trpc].page.ts | 17 ++ .../src/pages/changelog/[[...slug]].page.tsx | 147 ++++++++++ .../src/pages/changelog/index.module.scss | 258 +++++++++++++++++ .../explorer/src/pages/changelog/logo.png | Bin 0 -> 4704 bytes .../explorer/src/pages/changelog/more.svg | 3 + packages/explorer/src/pages/changelog/top.svg | 4 + packages/explorer/src/pages/home/analysis.png | Bin 0 -> 77870 bytes .../explorer/src/pages/home/analysisMb.png | Bin 0 -> 29481 bytes .../explorer/src/pages/home/analysisMb2.png | Bin 0 -> 21703 bytes .../explorer/src/pages/home/bottom-shadow.svg | 22 ++ .../explorer/src/pages/home/data-overview.png | Bin 0 -> 152552 bytes packages/explorer/src/pages/home/dataMb.png | Bin 0 -> 65214 bytes .../explorer/src/pages/home/experience.png | Bin 0 -> 243543 bytes .../explorer/src/pages/home/experienceMb.png | Bin 0 -> 51720 bytes packages/explorer/src/pages/home/github.svg | 3 + .../explorer/src/pages/home/index.module.scss | 188 +++++++++++++ .../explorer/src/pages/home/index.page.tsx | 184 +++++++++++++ packages/explorer/src/pages/home/oval.svg | 3 + packages/explorer/src/pages/home/overview.png | Bin 0 -> 458944 bytes .../explorer/src/pages/home/overviewMb.png | Bin 0 -> 92200 bytes packages/explorer/src/pages/home/summary.png | Bin 0 -> 69628 bytes .../explorer/src/pages/home/summaryMb1.png | Bin 0 -> 25457 bytes .../explorer/src/pages/home/summaryMb2.png | Bin 0 -> 17227 bytes .../explorer/src/pages/home/summaryMb3.png | Bin 0 -> 18951 bytes .../explorer/src/pages/home/title-shadow.svg | 3 + .../explorer/src/pages/home/top-shadow.svg | 22 ++ packages/explorer/src/pages/index.page.tsx | 1 + packages/explorer/src/server/api/root.ts | 17 ++ .../explorer/src/server/api/routers/uptime.ts | 28 ++ packages/explorer/src/server/api/trpc.ts | 82 ++++++ .../src/services/AppSettings/index.ts | 28 ++ .../src/services/PersistenceService/index.ts | 19 ++ .../src/styles/fonts/ProximaNova-Bold.otf | Bin 0 -> 63808 bytes .../src/styles/fonts/ProximaNova-Regular.otf | Bin 0 -> 62892 bytes .../src/styles/fonts/ProximaNova-Semibold.otf | Bin 0 -> 63116 bytes packages/explorer/src/styles/globals.scss | 67 +++++ .../explorer/src/styles/presets.module.scss | 55 ++++ .../explorer/src/styles/variables.module.scss | 6 + packages/explorer/src/typings/chunk-text.d.ts | 11 + packages/explorer/src/typings/react.d.ts | 8 + packages/explorer/src/utils/api.ts | 67 +++++ packages/explorer/src/utils/env.ts | 4 + packages/explorer/src/utils/github.ts | 259 ++++++++++++++++++ packages/explorer/src/utils/i18n.ts | 6 + packages/explorer/src/utils/index.ts | 8 + packages/explorer/src/utils/route.ts | 14 + packages/explorer/tsconfig.json | 5 + yarn.lock | 65 +++++ 78 files changed, 2877 insertions(+) create mode 100644 packages/explorer/.env.example create mode 100644 packages/explorer/.eslintrc.cjs create mode 100644 packages/explorer/.lintstagedrc.js create mode 100644 packages/explorer/.stylelintrc.json create mode 100644 packages/explorer/i18next-parser.config.ts create mode 100644 packages/explorer/next-i18next.config.js create mode 100644 packages/explorer/next.config.mjs create mode 100644 packages/explorer/package.json create mode 100644 packages/explorer/public/favicon.ico create mode 100644 packages/explorer/public/favicon.svg create mode 100644 packages/explorer/public/locales/en/app.json create mode 100644 packages/explorer/public/locales/en/changelog.json create mode 100644 packages/explorer/public/locales/en/common.json create mode 100644 packages/explorer/public/locales/en/home.json create mode 100644 packages/explorer/public/locales/zh/app.json create mode 100644 packages/explorer/public/locales/zh/changelog.json create mode 100644 packages/explorer/public/locales/zh/common.json create mode 100644 packages/explorer/public/locales/zh/home.json create mode 100644 packages/explorer/src/components/Button/index.module.scss create mode 100644 packages/explorer/src/components/Button/index.tsx create mode 100644 packages/explorer/src/components/Page/index.module.scss create mode 100644 packages/explorer/src/components/Page/index.tsx create mode 100644 packages/explorer/src/components/TableOfContents/index.tsx create mode 100644 packages/explorer/src/components/Tooltip/index.module.scss create mode 100644 packages/explorer/src/components/Tooltip/index.tsx create mode 100644 packages/explorer/src/env.mjs create mode 100644 packages/explorer/src/hooks/index.ts create mode 100644 packages/explorer/src/hooks/useMarkdownProps.tsx create mode 100644 packages/explorer/src/pages/_app.page.tsx create mode 100644 packages/explorer/src/pages/_document.page.tsx create mode 100644 packages/explorer/src/pages/api/trpc/[trpc].page.ts create mode 100644 packages/explorer/src/pages/changelog/[[...slug]].page.tsx create mode 100644 packages/explorer/src/pages/changelog/index.module.scss create mode 100644 packages/explorer/src/pages/changelog/logo.png create mode 100644 packages/explorer/src/pages/changelog/more.svg create mode 100644 packages/explorer/src/pages/changelog/top.svg create mode 100644 packages/explorer/src/pages/home/analysis.png create mode 100644 packages/explorer/src/pages/home/analysisMb.png create mode 100644 packages/explorer/src/pages/home/analysisMb2.png create mode 100644 packages/explorer/src/pages/home/bottom-shadow.svg create mode 100644 packages/explorer/src/pages/home/data-overview.png create mode 100644 packages/explorer/src/pages/home/dataMb.png create mode 100644 packages/explorer/src/pages/home/experience.png create mode 100644 packages/explorer/src/pages/home/experienceMb.png create mode 100644 packages/explorer/src/pages/home/github.svg create mode 100644 packages/explorer/src/pages/home/index.module.scss create mode 100644 packages/explorer/src/pages/home/index.page.tsx create mode 100644 packages/explorer/src/pages/home/oval.svg create mode 100644 packages/explorer/src/pages/home/overview.png create mode 100644 packages/explorer/src/pages/home/overviewMb.png create mode 100644 packages/explorer/src/pages/home/summary.png create mode 100644 packages/explorer/src/pages/home/summaryMb1.png create mode 100644 packages/explorer/src/pages/home/summaryMb2.png create mode 100644 packages/explorer/src/pages/home/summaryMb3.png create mode 100644 packages/explorer/src/pages/home/title-shadow.svg create mode 100644 packages/explorer/src/pages/home/top-shadow.svg create mode 100644 packages/explorer/src/pages/index.page.tsx create mode 100644 packages/explorer/src/server/api/root.ts create mode 100644 packages/explorer/src/server/api/routers/uptime.ts create mode 100644 packages/explorer/src/server/api/trpc.ts create mode 100644 packages/explorer/src/services/AppSettings/index.ts create mode 100644 packages/explorer/src/services/PersistenceService/index.ts create mode 100644 packages/explorer/src/styles/fonts/ProximaNova-Bold.otf create mode 100644 packages/explorer/src/styles/fonts/ProximaNova-Regular.otf create mode 100644 packages/explorer/src/styles/fonts/ProximaNova-Semibold.otf create mode 100644 packages/explorer/src/styles/globals.scss create mode 100644 packages/explorer/src/styles/presets.module.scss create mode 100644 packages/explorer/src/styles/variables.module.scss create mode 100644 packages/explorer/src/typings/chunk-text.d.ts create mode 100644 packages/explorer/src/typings/react.d.ts create mode 100644 packages/explorer/src/utils/api.ts create mode 100644 packages/explorer/src/utils/env.ts create mode 100644 packages/explorer/src/utils/github.ts create mode 100644 packages/explorer/src/utils/i18n.ts create mode 100644 packages/explorer/src/utils/index.ts create mode 100644 packages/explorer/src/utils/route.ts create mode 100644 packages/explorer/tsconfig.json diff --git a/packages/explorer/.env.example b/packages/explorer/.env.example new file mode 100644 index 0000000..0961fc9 --- /dev/null +++ b/packages/explorer/.env.example @@ -0,0 +1,21 @@ +# Since the ".env" file is gitignored, you can use the ".env.example" file to +# build a new ".env" file when you clone the repo. Keep this file up-to-date +# when you add new variables to `.env`. + +# This file will be committed to version control, so make sure not to have any +# secrets in it. If you are cloning this repo, create a copy of this file named +# ".env" and populate it with your secrets. + +# When adding additional environment variables, the schema in "/src/env.mjs" +# should be updated accordingly. + +# Example: +# SERVERVAR="foo" +# NEXT_PUBLIC_CLIENTVAR="bar" +NEXT_PUBLIC_REPO=nervosnetwork/ckb-explorer + +# For accessing api.github.com +# https://docs.github.com/en/rest/overview/authenticating-to-the-rest-api?apiVersion=2022-11-28 +GITHUB_TOKEN= + +UPTIME_KEY= diff --git a/packages/explorer/.eslintrc.cjs b/packages/explorer/.eslintrc.cjs new file mode 100644 index 0000000..d043dbd --- /dev/null +++ b/packages/explorer/.eslintrc.cjs @@ -0,0 +1,7 @@ +/** @type {import("eslint").Linter.Config} */ +const config = { + root: true, + extends: ['@magickbase-website/eslint-config/next.js'], +} + +module.exports = config diff --git a/packages/explorer/.lintstagedrc.js b/packages/explorer/.lintstagedrc.js new file mode 100644 index 0000000..903e0e1 --- /dev/null +++ b/packages/explorer/.lintstagedrc.js @@ -0,0 +1,9 @@ +const path = require('path') + +const buildEslintCommand = filenames => + `next lint --fix --file ${filenames.map(f => path.relative(process.cwd(), f)).join(' --file ')}` + +module.exports = { + '*.{js,cjs,mjs,jsx,ts,tsx}': ['prettier --write', buildEslintCommand], + '*.{css,scss}': ['prettier --write', 'stylelint --fix'], +} diff --git a/packages/explorer/.stylelintrc.json b/packages/explorer/.stylelintrc.json new file mode 100644 index 0000000..5558cc3 --- /dev/null +++ b/packages/explorer/.stylelintrc.json @@ -0,0 +1,52 @@ +{ + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss" + } + ], + "extends": [ + "stylelint-config-standard-scss", + "stylelint-config-rational-order", + "stylelint-config-css-modules", + "stylelint-config-prettier-scss" + ], + "rules": { + "selector-class-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected class selector to be lowerCamelCase" + } + ], + "custom-property-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected custom property name to be lowerCamelCase" + } + ], + "keyframes-name-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected class selector to be lowerCamelCase" + } + ], + "scss/dollar-variable-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected variable to be lowerCamelCase" + } + ], + "scss/percent-placeholder-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected placeholder to be lowerCamelCase" + } + ], + "scss/at-mixin-pattern": [ + "^[a-z][a-zA-Z0-9]*$", + { + "message": "Expected mixin name to be lowerCamelCase" + } + ] + } +} diff --git a/packages/explorer/i18next-parser.config.ts b/packages/explorer/i18next-parser.config.ts new file mode 100644 index 0000000..f813134 --- /dev/null +++ b/packages/explorer/i18next-parser.config.ts @@ -0,0 +1,120 @@ +import { UserConfig } from 'i18next-parser' + +const config: UserConfig = { + // Key separator used in your translation keys + contextSeparator: '_', + + // Save the \_old files + createOldCatalogs: false, + + // Default namespace used in your i18next config + defaultNamespace: 'translation', + + defaultValue(locale, namespace, key) { + return key ?? '' + }, + + // Indentation of the catalog files + indentation: 2, + + // Keep keys from the catalog that are no longer in code + // You may either specify a boolean to keep or discard all removed keys. + // You may also specify an array of patterns: the keys from the catalog that are no long in the code but match one of the patterns will be kept. + // The patterns are applied to the full key including the namespace, the parent keys and the separators. + keepRemoved: false, + + // Key separator used in your translation keys + // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance. + keySeparator: false, + + // see below for more details + lexers: { + hbs: ['HandlebarsLexer'], + handlebars: ['HandlebarsLexer'], + + htm: ['HTMLLexer'], + html: ['HTMLLexer'], + + mjs: ['JavascriptLexer'], + js: ['JavascriptLexer'], + ts: [ + { + lexer: 'JavascriptLexer', + functions: ['t', 'addI18nKey'], + namespaceFunctions: ['useTranslation', 'withTranslation', 'createI18nKeyAdder'], + }, + ], + jsx: ['JsxLexer'], + tsx: ['JsxLexer'], + + default: ['JavascriptLexer'], + }, + + // Control the line ending. See options at https://github.com/ryanve/eol + lineEnding: 'lf', + + // An array of the locales in your applications + locales: ['en', 'zh'], + + // Namespace separator used in your translation keys + // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance. + namespaceSeparator: false, + + // Supports $LOCALE and $NAMESPACE injection + // Supports JSON (.json) and YAML (.yml) file formats + // Where to write the locale files relative to process.cwd() + output: 'public/locales/$LOCALE/$NAMESPACE.json', + + // Plural separator used in your translation keys + // If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys. + // If you don't want to generate keys for plurals (for example, in case you are using ICU format), set `pluralSeparator: false`. + pluralSeparator: '_', + + // An array of globs that describe where to look for source files + // relative to the location of the configuration file + input: ['src/**/*.{ts,tsx}', '../shared/src/**/*.{ts,tsx}'], + + // Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters) + sort: true, + + // Display info about the parsing including some stats + verbose: false, + + // Exit with an exit code of 1 on warnings + failOnWarnings: false, + + // Exit with an exit code of 1 when translations are updated (for CI purpose) + failOnUpdate: false, + + // If you wish to customize the value output the value as an object, you can set your own format. + // ${defaultValue} is the default value you set in your translation function. + // Any other custom property will be automatically extracted. + // + // Example: + // { + // message: "${defaultValue}", + // description: "${maxLength}", // t('my-key', {maxLength: 150}) + // } + customValueTemplate: null, + + // The locale to compare with default values to determine whether a default value has been changed. + // If this is set and a default value differs from a translation in the specified locale, all entries + // for that key across locales are reset to the default value, and existing translations are moved to + // the `_old` file. + resetDefaultValueLocale: null, + + // If you wish to customize options in internally used i18next instance, you can define an object with any + // configuration property supported by i18next (https://www.i18next.com/overview/configuration-options). + // { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals. + i18nextOptions: null, + + // If you wish to customize options for yaml output, you can define an object here. + // Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-). + // Example: + // { + // lineWidth: -1, + // } + yamlOptions: null, +} + +export default config diff --git a/packages/explorer/next-i18next.config.js b/packages/explorer/next-i18next.config.js new file mode 100644 index 0000000..f9aceeb --- /dev/null +++ b/packages/explorer/next-i18next.config.js @@ -0,0 +1,21 @@ +/** + * next-i18next not support mjs config + */ +/* eslint-disable @typescript-eslint/no-var-requires */ + +const path = require('path') + +/** + * @type {import('next-i18next').UserConfig} + */ +module.exports = { + i18n: { + defaultLocale: 'en', + locales: ['en', 'zh'], + localeDetection: true, + }, + fallbackNS: 'common', + localePath: typeof window === 'undefined' ? path.resolve('./public/locales') : '/locales', + + reloadOnPrerender: process.env.NODE_ENV === 'development', +} diff --git a/packages/explorer/next.config.mjs b/packages/explorer/next.config.mjs new file mode 100644 index 0000000..415cd34 --- /dev/null +++ b/packages/explorer/next.config.mjs @@ -0,0 +1,107 @@ +const i18nConfig = (await import('./next-i18next.config.js')).default + +/** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. + * This is especially useful for Docker builds. + */ +!process.env.SKIP_ENV_VALIDATION && (await import('./src/env.mjs')) + +/** @type {import("next").NextConfig} */ +const config = { + reactStrictMode: true, + + pageExtensions: ['tsx', 'ts', 'jsx', 'js'].map(suffix => `page.${suffix}`), + + /** + * If you have the "experimental: { appDir: true }" setting enabled, then you + * must comment the below `i18n` config out. + * + * @see https://github.com/vercel/next.js/issues/41980 + */ + i18n: i18nConfig.i18n, + + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'avatars.githubusercontent.com', + }, + ], + }, + + transpilePackages: ['@magickbase-website/shared'], + + sassOptions: { + logger: { + warn: function (message) { + console.warn(message) + }, + debug: function (message) { + console.log(message) + }, + }, + }, + + webpack(config) { + config.module.rules.push({ + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: [ + { + loader: '@svgr/webpack', + options: { + svgoConfig: { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, + }, + ], + }, + }, + }, + ], + }) + + // https://dhanrajsp.me/snippets/customize-css-loader-options-in-nextjs + const oneOf = config.module.rules.find(rule => typeof rule.oneOf === 'object') + if (oneOf) { + const moduleSassRule = oneOf.oneOf.find(rule => regexEqual(rule.test, /\.module\.(scss|sass)$/)) + if (moduleSassRule) { + // Get the config object for css-loader plugin + const cssLoader = moduleSassRule.use.find(({ loader }) => loader.includes('css-loader')) + if (cssLoader) { + cssLoader.options = { + ...cssLoader.options, + modules: { + ...cssLoader.options.modules, + mode: 'local', + }, + } + } + } + } + + return config + }, +} + +/** + * Stolen from https://stackoverflow.com/questions/10776600/testing-for-equality-of-regular-expressions + */ +function regexEqual(x, y) { + return ( + x instanceof RegExp && + y instanceof RegExp && + x.source === y.source && + x.global === y.global && + x.ignoreCase === y.ignoreCase && + x.multiline === y.multiline + ) +} + +export default config diff --git a/packages/explorer/package.json b/packages/explorer/package.json new file mode 100644 index 0000000..ba70c99 --- /dev/null +++ b/packages/explorer/package.json @@ -0,0 +1,77 @@ +{ + "name": "@magickbase-website/ckb-explorer", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev", + "lint": "next lint", + "start": "next start", + "update:locales": "i18next" + }, + "dependencies": { + "@docsearch/css": "3", + "@docsearch/react": "3", + "@magickbase-website/shared": "workspace:^", + "@octokit/graphql-schema": "^14.12.0", + "@octokit/openapi-types": "^17.2.0", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/rest": "^19.0.11", + "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-dialog": "^1.0.4", + "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-popover": "^1.0.6", + "@radix-ui/react-tooltip": "^1.0.6", + "@tanstack/react-query": "^4.29.12", + "@trpc/client": "^10.28.2", + "@trpc/next": "^10.28.2", + "@trpc/react-query": "^10.28.2", + "@trpc/server": "^10.28.2", + "chunk-text": "^2.0.1", + "clsx": "^1.2.1", + "i18next": "^22.5.0", + "next": "^13.4.4", + "next-i18next": "^13.2.2", + "observable-hooks": "^4.2.2", + "overlayscrollbars": "^2.4.5", + "overlayscrollbars-react": "^0.5.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^12.3.1", + "react-markdown": "^8.0.7", + "react-resize-detector": "^9.1.0", + "rehype-raw": "^6.1.1", + "rehype-sanitize": "^5.0.1", + "remark-gfm": "^3.0.1", + "rxjs": "^7.8.1", + "sass": "^1.62.1", + "superjson": "^1.12.3", + "zod": "^3.21.4" + }, + "devDependencies": { + "@magickbase-website/config": "workspace:^", + "@magickbase-website/eslint-config": "workspace:^", + "@svgr/webpack": "^8.0.1", + "@types/eslint": "^8.56.0", + "@types/node": "^20.2.5", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.4", + "eslint": "^8.56.0", + "i18next-parser": "^8.7.0", + "postcss": "^8.4.24", + "postcss-scss": "^4.0.6", + "stylelint": "^15.6.2", + "stylelint-config-css-modules": "^4.2.0", + "stylelint-config-prettier-scss": "^1.0.0", + "stylelint-config-rational-order": "^0.1.2", + "stylelint-config-standard-scss": "^9.0.0", + "stylelint-order": "^6.0.3", + "typescript": "^5.1.3" + }, + "ct3aMetadata": { + "initVersion": "7.5.9" + }, + "peerDependencies": { + "@octokit/request-error": "*" + } +} diff --git a/packages/explorer/public/favicon.ico b/packages/explorer/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a9de964f7ecb963ed2b47761370abb8154cf1996 GIT binary patch literal 4286 zcmc(iS&G6y5QcleU6DM^voJ67G-sGA_~ao(d=PisS42F=T*9;H5k%Yt%ls_{jb_YJ zi6y0NxA^6+u2ffq_)uSoguuJ_Q-t^wLVT;PPKjUDF)vMB`}(=~WkQHhC`9V`sbg(A zost{2SS+YmEYdsptJR8fxg5pganJed^_ueeJo){8$@Wq|*ob)`5Rm%~wpaYkW<%w2 zSz{yTyyAmhtyb^YUh%ivE!Ao@3I>A@_n6!I!EQ7fPuXtk-|cqPY&Ku!mmB#9cDvpF zm+hAR{eDl~ZkNL0@WXzCdF$N!4|c!bH^t`G5B6|4q(~%kr}0*=e$1sG`)x({^*Xrt z^ZA@wt(L6og+hVgrBaDfsTBEqKErr$@r}-BoZVnt_3A&J@K2|c5muXb%17*y$s}d7 zS;}NG($ncQRVo#l&1M?MXyBBuj~BipzsKV-A>UsaH$N7O(PT2AvdW#mz=V-|1zUrJ=5N5XRsP3<^TWy literal 0 HcmV?d00001 diff --git a/packages/explorer/public/favicon.svg b/packages/explorer/public/favicon.svg new file mode 100644 index 0000000..3f22df8 --- /dev/null +++ b/packages/explorer/public/favicon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/explorer/public/locales/en/app.json b/packages/explorer/public/locales/en/app.json new file mode 100644 index 0000000..efef8a9 --- /dev/null +++ b/packages/explorer/public/locales/en/app.json @@ -0,0 +1,3 @@ +{ + "CKB Explorer Troubleshooting": "CKB Explorer Troubleshooting" +} diff --git a/packages/explorer/public/locales/en/changelog.json b/packages/explorer/public/locales/en/changelog.json new file mode 100644 index 0000000..08ae1ee --- /dev/null +++ b/packages/explorer/public/locales/en/changelog.json @@ -0,0 +1,4 @@ +{ + "Changelog": "Changelog", + "CKB Explorer new features and updates summary, join Github to learn more about the project progress.": "CKB Explorer new features and updates summary, join Github to learn more about the project progress." +} diff --git a/packages/explorer/public/locales/en/common.json b/packages/explorer/public/locales/en/common.json new file mode 100644 index 0000000..124ea45 --- /dev/null +++ b/packages/explorer/public/locales/en/common.json @@ -0,0 +1,37 @@ +{ + "About Neuron": "About Neuron", + "Advanced Features": "Advanced Features", + "All services are online": "All services are online", + "Announcement": "Announcement", + "Assets": "Assets", + "Axon Explorer": "Axon Explorer", + "Beginner's Guide": "Beginner's Guide", + "Change log": "Change log", + "Changelog": "Changelog", + "CKB Explorer": "CKB Explorer", + "Copyright © 2023 Magickbase All Rights Reserved.": "Copyright © 2023 Magickbase All Rights Reserved.", + "Develop guide": "Develop guide", + "Download Neuron": "Download Neuron", + "Foundation": "Foundation", + "Frequently Asked Questions": "Frequently Asked Questions", + "Godwoke Explorer": "Godwoke Explorer", + "Help Center": "Help Center", + "Home": "Home", + "Kuai": "Kuai", + "Language": "Language", + "Nervos": "Nervos", + "Neuron": "Neuron", + "Neuron Wallet": "Neuron Wallet", + "Public Node": "Public Node", + "Report": "Report", + "Safety": "Safety", + "Service Monitor": "Service Monitor", + "Services": "Services", + "Services status loading...": "Services status loading...", + "Some services are offline": "Some services are offline", + "Status": "Status", + "Sync": "Sync", + "Transaction": "Transaction", + "Usage Tutorial": "Usage Tutorial", + "Go to CKB Explorer": "Go to CKB Explorer" +} diff --git a/packages/explorer/public/locales/en/home.json b/packages/explorer/public/locales/en/home.json new file mode 100644 index 0000000..9a0c10d --- /dev/null +++ b/packages/explorer/public/locales/en/home.json @@ -0,0 +1,12 @@ +{ + "Unlocking the Potential of the Nervos Blockchain": "Unlocking the Potential of the Nervos Blockchain", + "CKB Explorer is a blockchain explorer that provides users with a real-time view of the Nervos CKB blockchain. It allows users to search for specific transactions, blocks, and addresses, and provides detailed information on each transaction and block, including the status, timestamp, and fees.": "CKB Explorer is a blockchain explorer that provides users with a real-time view of the Nervos CKB blockchain. It allows users to search for specific transactions, blocks, and addresses, and provides detailed information on each transaction and block, including the status, timestamp, and fees.", + "Real-time parsing of data on the chain": "Real-time parsing of data on the chain", + "Main chain information, block information, transaction information, contract information and address information all in one place to help you keep track of what's happening on the chain.": "Main chain information, block information, transaction information, contract information and address information all in one place to help you keep track of what's happening on the chain.", + "Chain assets summary display": "Chain assets summary display", + "Nervos DAO, Tokens, and NFT collection assets at a glance to help you understand the status of your on-chain projects right away.": "Nervos DAO, Tokens, and NFT collection assets at a glance to help you understand the status of your on-chain projects right away.", + "Multidimensional analysis of data charts": "Multidimensional analysis of data charts", + "Provide multi-dimensional, multi-type data chart display, is a good helper for you to analyze data and make decisions.": "Provide multi-dimensional, multi-type data chart display, is a good helper for you to analyze data and make decisions.", + "Experience CKB Explorer Now": "Experience CKB Explorer Now", + "Easy to grasp information on the chain to help you start your journey to the world of CKB": "Easy to grasp information on the chain to help you start your journey to the world of CKB" +} diff --git a/packages/explorer/public/locales/zh/app.json b/packages/explorer/public/locales/zh/app.json new file mode 100644 index 0000000..2403982 --- /dev/null +++ b/packages/explorer/public/locales/zh/app.json @@ -0,0 +1,3 @@ +{ + "CKB Explorer Troubleshooting": "CKB 浏览器帮助中心" +} diff --git a/packages/explorer/public/locales/zh/changelog.json b/packages/explorer/public/locales/zh/changelog.json new file mode 100644 index 0000000..af55498 --- /dev/null +++ b/packages/explorer/public/locales/zh/changelog.json @@ -0,0 +1,4 @@ +{ + "Changelog": "更新日志", + "CKB Explorer new features and updates summary, join Github to learn more about the project progress.": "CKB 浏览器新功能和更新摘要,加入 Github 了解更多项目进展。" +} diff --git a/packages/explorer/public/locales/zh/common.json b/packages/explorer/public/locales/zh/common.json new file mode 100644 index 0000000..d9922e6 --- /dev/null +++ b/packages/explorer/public/locales/zh/common.json @@ -0,0 +1,37 @@ +{ + "About Neuron": "关于 Neuron", + "Advanced Features": "高级功能", + "All services are online": "所有服务均可用", + "Announcement": "公告", + "Assets": "资产", + "Axon Explorer": "Axon 区块浏览器", + "Beginner's Guide": "新手指南", + "Change log": "更新日志", + "Changelog": "更新日志", + "CKB Explorer": "CKB 区块浏览器", + "Copyright © 2023 Magickbase All Rights Reserved.": "Copyright © 2023 Magickbase 版权所有", + "Develop guide": "开发指南", + "Download Neuron": "下载 Neuron", + "Foundation": "基金会", + "Frequently Asked Questions": "常见问题", + "Godwoke Explorer": "Godwoke 区块浏览器", + "Help Center": "帮助中心", + "Home": "首页", + "Kuai": "Kuai", + "Language": "语言", + "Nervos": "Nervos", + "Neuron": "Neuron", + "Neuron Wallet": "Neuron 钱包", + "Public Node": "公共节点", + "Report": "报告", + "Safety": "安全", + "Service Monitor": "服务监控", + "Services": "服务", + "Services status loading...": "服务状态加载中...", + "Some services are offline": "部分服务不可用", + "Status": "状态", + "Sync": "同步", + "Transaction": "交易", + "Usage Tutorial": "使用教程", + "Go to CKB Explorer": "访问 CKB 浏览器" +} diff --git a/packages/explorer/public/locales/zh/home.json b/packages/explorer/public/locales/zh/home.json new file mode 100644 index 0000000..c738b89 --- /dev/null +++ b/packages/explorer/public/locales/zh/home.json @@ -0,0 +1,12 @@ +{ + "Unlocking the Potential of the Nervos Blockchain": "释放 Nervos 区块链 的潜力", + "CKB Explorer is a blockchain explorer that provides users with a real-time view of the Nervos CKB blockchain. It allows users to search for specific transactions, blocks, and addresses, and provides detailed information on each transaction and block, including the status, timestamp, and fees.": "CKB Explorer 是一个区块链浏览器,为用户提供 Nervos CKB 区块链的实时视图。它允许用户搜索特定的交易、区块和地址,并提供每笔交易和每个区块的详细信息,包括状态、时间戳和费用。", + "Real-time parsing of data on the chain": "实时解析链上数据", + "Main chain information, block information, transaction information, contract information and address information all in one place to help you keep track of what's happening on the chain.": "主链信息、区块信息、交易信息、合约信息和地址信息都在一个地方,帮助您跟踪链上发生的事情。", + "Chain assets summary display": "链上资产汇总显示", + "Nervos DAO, Tokens, and NFT collection assets at a glance to help you understand the status of your on-chain projects right away.": "Nervos DAO、代币和 NFT 集合资产一目了然,帮助您立即了解链上项目的状态。", + "Multidimensional analysis of data charts": "数据图表的多维分析", + "Provide multi-dimensional, multi-type data chart display, is a good helper for you to analyze data and make decisions.": "提供多维度、多类型的数据图表显示,是您分析数据、做出决策的好帮手。", + "Experience CKB Explorer Now": "立即体验 CKB 浏览器", + "Easy to grasp information on the chain to help you start your journey to the world of CKB": "简单易懂的链上信息,帮助您开启 CKB 世界之旅" +} diff --git a/packages/explorer/src/components/Button/index.module.scss b/packages/explorer/src/components/Button/index.module.scss new file mode 100644 index 0000000..7d3e63b --- /dev/null +++ b/packages/explorer/src/components/Button/index.module.scss @@ -0,0 +1,76 @@ +@use 'sass:color'; + +.button { + all: unset; + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + padding: 16px 24px; + border-radius: 7px; + cursor: pointer; +} + +.contained { + color: var(--btnForeground); + background: var(--btnBackground); + + &:disabled { + color: var(--btnForegroundDisabled); + background: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + background: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + background: var(--btnBackgroundActive); + } +} + +.outlined { + color: var(--btnBackground); + border: 2px solid var(--btnBackground); + + &:disabled { + color: var(--btnBackgroundDisabled); + border-color: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + color: var(--btnBackgroundHover); + border-color: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + color: var(--btnBackgroundActive); + border-color: var(--btnBackgroundActive); + } +} + +.text { + padding: 6px 24px; + color: var(--btnBackground); + + &:disabled { + color: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + color: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + color: var(--btnBackgroundActive); + } +} + +.themeBlackWhite { + --btnForeground: #000; + --btnForegroundDisabled: #{rgba(#000, 0.5)}; + --btnBackground: #f5f5f5; + --btnBackgroundDisabled: #{color.mix(#fff, #f5f5f5, $weight: 50%)}; + --btnBackgroundHover: #{color.mix(#000, #f5f5f5, $weight: 20%)}; + --btnBackgroundActive: #{color.mix(#000, #f5f5f5, $weight: 40%)}; +} diff --git a/packages/explorer/src/components/Button/index.tsx b/packages/explorer/src/components/Button/index.tsx new file mode 100644 index 0000000..99876b3 --- /dev/null +++ b/packages/explorer/src/components/Button/index.tsx @@ -0,0 +1,38 @@ +/** + * Currently, there are no complex button scenarios, so a simple implementation is sufficient. + * If the requirements cannot be met in the future, consider using a third-party component library. + */ +import { ComponentProps, FC } from 'react' +import clsx from 'clsx' +import styles from './index.module.scss' +import presets from '../../styles/presets.module.scss' + +export type ButtonVariant = 'contained' | 'outlined' | 'text' + +export interface ButtonProps extends ComponentProps<'button'> { + variant?: ButtonVariant + theme?: 'light' | 'dark' | 'blackwhite' +} + +export const Button: FC = ({ children, variant = 'contained', theme, ...elProps }) => { + return ( + + ) +} diff --git a/packages/explorer/src/components/Page/index.module.scss b/packages/explorer/src/components/Page/index.module.scss new file mode 100644 index 0000000..6f770a5 --- /dev/null +++ b/packages/explorer/src/components/Page/index.module.scss @@ -0,0 +1,19 @@ +@import '../../styles/variables.module'; + +.page { + width: min-content; + min-width: 100%; + color: var(--colorPrimary); + background-color: var(--colorPrimaryBg); +} + +.contentWrapper { + box-sizing: content-box; + max-width: calc(var(--contentAreaWidth) + var(--contentWrapperPadding)); + margin: 0 auto; + padding: 0 var(--contentWrapperPadding); + + @media (max-width: $mobileBreakPoint) { + max-width: 100%; + } +} diff --git a/packages/explorer/src/components/Page/index.tsx b/packages/explorer/src/components/Page/index.tsx new file mode 100644 index 0000000..80246b2 --- /dev/null +++ b/packages/explorer/src/components/Page/index.tsx @@ -0,0 +1,71 @@ +import { ComponentProps, forwardRef, ReactNode, useMemo } from 'react' +import { clsx } from 'clsx' +import { Footer, FooterProps, Header, HeaderProps, useBodyClass } from '@magickbase-website/shared' +import { useTranslation } from 'react-i18next' +import styles from './index.module.scss' +import presets from '../../styles/presets.module.scss' +import { api } from '../../utils' + +type PageProps = Omit, 'children'> & { + children?: + | ReactNode + | ((opts: { + renderHeader: (props?: HeaderProps) => ReactNode + renderFooter: (props?: FooterProps) => ReactNode + }) => JSX.Element | undefined) + contentWrapper?: boolean | ComponentProps<'div'> +} + +export const Page = forwardRef(function Page(props, ref) { + const { children, contentWrapper = true, className, ...divProps } = props + const contentWrapperProps = typeof contentWrapper === 'object' ? contentWrapper : {} + const { t } = useTranslation('common') + + // _document.page.tsx adds a theme class name to the HTML element during initialization, but this class name remains fixed and does not change. + // In order to switch themes correctly when navigating between routes, it is necessary to add a theme class name to the body element. + // Currently, except for the post detail page, all other pages have a fixed dark mode and cannot switch color modes. + // Since the post detail page does not use the Page component, a fixed dark mode class name is applied here. + // TODO: This may not be a good code implementation, but we should consider refactoring it after having multiple pages with switchable color modes. + useBodyClass([presets.themeDark ?? '']) + + const aggregateStateQuery = api.uptime.aggregateState.useQuery() + + const navMenus = useMemo(() => [{ name: t('Changelog'), link: '/changelog' }], [t]) + + const finalChildren = + typeof children === 'function' ? ( + children({ + renderHeader: props => ( +
+ ), + renderFooter: props =>