initial commit
23
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
46
README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
41
api/server.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// import express, { Request, Response } from "express";
|
||||
// import path, { dirname } from "path";
|
||||
// import { fileURLToPath } from "url";
|
||||
|
||||
const express = require("express");
|
||||
// const { Request, Response } = require("express");
|
||||
const path = require("path");
|
||||
const { dirname } = require("path");
|
||||
const { fileURLToPath } = require("url");
|
||||
|
||||
const PORT = 3005;
|
||||
|
||||
// const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function main() {
|
||||
const app = express();
|
||||
app.use(express.static("./build"));
|
||||
|
||||
app.all(["/proxy/*"], async (req: any, res: any) => {
|
||||
try {
|
||||
const result = await fetch('https://explorer.zano.org' + req.url.replace("/proxy", ""));
|
||||
try {
|
||||
const json = await result.json();
|
||||
res.status(200).send(json);
|
||||
} catch {
|
||||
res.status(404).send({ success: false, error: "Not found" })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ success: false, error: 'Proxy server error' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/*", (req: any, res: any) => {
|
||||
res.sendFile(path.resolve(__dirname, "../build/index.html"))
|
||||
});
|
||||
|
||||
app.listen(PORT, () => console.log("Server started at port " + PORT));
|
||||
}
|
||||
|
||||
main();
|
||||
17827
package-lock.json
generated
Normal file
55
package.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "zano-explorer",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.57",
|
||||
"@types/react": "^18.2.25",
|
||||
"@types/react-dom": "^18.2.10",
|
||||
"express": "^4.18.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"nanoid": "^5.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-json-view-lite": "^1.1.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.68.0",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"server-dev": "npx nodemon ./api/server.ts",
|
||||
"server": "npx ts-node ./api/server.ts"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
43
public/index.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Zano Block Explorer</title>
|
||||
</head>
|
||||
<body class="custom-scroll">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/logo192.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
25
public/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
29
src/App.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React, { Suspense } from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Block from "./pages/Block/Block";
|
||||
|
||||
const Blockchain = React.lazy(() => import("./pages/Blockchain/Blockchain"));
|
||||
const AltBlocks = React.lazy(() => import("./pages/AltBlocks/AltBlocks"));
|
||||
const Aliases = React.lazy(() => import("./pages/Aliases/Aliases"));
|
||||
const API = React.lazy(() => import("./pages/API/API"));
|
||||
const Transaction = React.lazy(() => import("./pages/Transaction/Transaction"));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Suspense fallback={<></>}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Blockchain />} />
|
||||
<Route path="/alt-blocks" element={<AltBlocks />} />
|
||||
<Route path="/aliases" element={<Aliases />} />
|
||||
<Route path="/api" element={<API />} />
|
||||
<Route path="/block/:hash" element={<Block />} />
|
||||
<Route path="/transaction/:hash" element={<Transaction />} />
|
||||
<Route path="/alt-blocks/:hash" element={<Block alt />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
BIN
src/assets/fonts/Inconsolata/Inconsolata-Regular.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-Bold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-BoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-ExtraBold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-Italic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-Light.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-LightItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-Medium.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-MediumItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-Regular.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-SemiBold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans-SemiBoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-Bold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-BoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-ExtraBoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-Italic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-Light.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-LightItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-Medium.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-MediumItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-Regular.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-SemiBold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_Condensed-SemiBoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-Bold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-BoldItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-ExtraBold.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-Italic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-Light.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-LightItalic.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-Medium.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-Regular.ttf
Normal file
BIN
src/assets/fonts/OpenSans/OpenSans_SemiCondensed-SemiBold.ttf
Normal file
3
src/assets/images/UI/arrow.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" _ngcontent-ani-c19="" x="0px" y="0px" viewBox="0 0 477.175 477.175" class="ng-tns-c19-0" width="18" height="18" ><g _ngcontent-ani-c19="" class="ng-tns-c19-0" fill="#FFFFFF"><path _ngcontent-ani-c19="" d="M360.731,229.075l-225.1-225.1c-5.3-5.3-13.8-5.3-19.1,0s-5.3,13.8,0,19.1l215.5,215.5l-215.5,215.5
|
||||
c-5.3,5.3-5.3,13.8,0,19.1c2.6,2.6,6.1,4,9.5,4c3.4,0,6.9-1.3,9.5-4l225.1-225.1C365.931,242.875,365.931,234.275,360.731,229.075z
|
||||
" class="ng-tns-c19-0" fill="#FFFFFF"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 596 B |
12
src/assets/images/UI/back.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 7 13" style="enable-background:new 0 0 7 13;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#ffffff;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M6.4,12.9c-0.1,0-0.3-0.1-0.4-0.2L0.2,6.9c-0.2-0.2-0.2-0.6,0-0.8L6,0.3C6.2,0,6.6,0,6.8,0.3
|
||||
c0.2,0.2,0.2,0.6,0,0.8L1.4,6.5l5.4,5.4c0.2,0.2,0.2,0.6,0,0.8C6.7,12.9,6.6,12.9,6.4,12.9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
3
src/assets/images/UI/burger.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H22V2H0V0ZM0 14H22V16H0V14ZM0 7H22V9H0V7Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 214 B |
36
src/assets/images/UI/logo.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<svg width="71" height="70" viewBox="0 0 71 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6912 0.0692164H49.7357C52.4911 0.0691532 54.8094 0.069099 56.7071 0.225796C58.6921 0.389705 60.583 0.745948 62.379 1.67082C65.1149 3.07963 67.3392 5.3276 68.7332 8.09255C69.6483 9.90771 70.0008 11.8186 70.163 13.8248C70.3181 15.7427 70.318 18.0857 70.3179 20.8704V28.5817C70.318 31.3665 70.3181 33.7094 70.163 35.6273C70.0008 37.6335 69.6483 39.5444 68.7332 41.3596C67.3392 44.1245 65.1149 46.3725 62.379 47.7813C60.583 48.7062 58.6921 49.0624 56.7071 49.2263C54.8094 49.383 52.491 49.383 49.7356 49.3829H25.7125C24.0251 49.3829 22.5024 48.3598 21.8494 46.7872C21.1965 45.2147 21.5408 43.3997 22.7229 42.1828L42.5612 21.7599L48.5405 27.6922L35.6962 40.9149H49.5678C52.5353 40.9149 54.509 40.9116 56.0248 40.7864C57.4917 40.6653 58.1571 40.4515 58.5751 40.2363C59.7343 39.6393 60.6768 38.6868 61.2675 37.5152C61.4805 37.0927 61.6921 36.4202 61.8119 34.9378C61.9358 33.4059 61.939 31.4112 61.939 28.4121V21.04C61.939 18.0409 61.9358 16.0462 61.8119 14.5143C61.6921 13.0319 61.4805 12.3594 61.2675 11.9369C60.6768 10.7654 59.7343 9.81282 58.5751 9.21587C58.1571 9.00062 57.4917 8.7868 56.0248 8.66568C54.509 8.54052 52.5353 8.53723 49.5678 8.53723H41.8603C38.8603 8.53723 36.8636 8.54055 35.3311 8.66772C33.8471 8.79087 33.1762 9.00831 32.7568 9.22625C31.5905 9.83223 30.6461 10.7985 30.0607 11.9844C29.8502 12.4109 29.6432 13.0915 29.5395 14.5927C29.4324 16.143 29.4534 18.1608 29.49 21.1925L29.532 24.6744L21.1537 24.7777L21.1097 21.1248C21.0756 18.3089 21.047 15.9412 21.1809 14.0027C21.321 11.9762 21.6552 10.0434 22.5629 8.20455C23.9443 5.40576 26.1732 3.12543 28.9255 1.69532C30.7339 0.755698 32.6421 0.394356 34.6455 0.228115C36.5617 0.0690985 38.9047 0.0691527 41.6912 0.0692164Z" fill="url(#paint0_linear_901_3770)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6912 0.0692164H49.7357C52.4911 0.0691532 54.8094 0.069099 56.7071 0.225796C58.6921 0.389705 60.583 0.745948 62.379 1.67082C65.1149 3.07963 67.3392 5.3276 68.7332 8.09255C69.6483 9.90771 70.0008 11.8186 70.163 13.8248C70.3181 15.7427 70.318 18.0857 70.3179 20.8704V28.5817C70.318 31.3665 70.3181 33.7094 70.163 35.6273C70.0008 37.6335 69.6483 39.5444 68.7332 41.3596C67.3392 44.1245 65.1149 46.3725 62.379 47.7813C60.583 48.7062 58.6921 49.0624 56.7071 49.2263C54.8094 49.383 52.491 49.383 49.7356 49.3829H25.7125C24.0251 49.3829 22.5024 48.3598 21.8494 46.7872C21.1965 45.2147 21.5408 43.3997 22.7229 42.1828L42.5612 21.7599L48.5405 27.6922L35.6962 40.9149H49.5678C52.5353 40.9149 54.509 40.9116 56.0248 40.7864C57.4917 40.6653 58.1571 40.4515 58.5751 40.2363C59.7343 39.6393 60.6768 38.6868 61.2675 37.5152C61.4805 37.0927 61.6921 36.4202 61.8119 34.9378C61.9358 33.4059 61.939 31.4112 61.939 28.4121V21.04C61.939 18.0409 61.9358 16.0462 61.8119 14.5143C61.6921 13.0319 61.4805 12.3594 61.2675 11.9369C60.6768 10.7654 59.7343 9.81282 58.5751 9.21587C58.1571 9.00062 57.4917 8.7868 56.0248 8.66568C54.509 8.54052 52.5353 8.53723 49.5678 8.53723H41.8603C38.8603 8.53723 36.8636 8.54055 35.3311 8.66772C33.8471 8.79087 33.1762 9.00831 32.7568 9.22625C31.5905 9.83223 30.6461 10.7985 30.0607 11.9844C29.8502 12.4109 29.6432 13.0915 29.5395 14.5927C29.4324 16.143 29.4534 18.1608 29.49 21.1925L29.532 24.6744L21.1537 24.7777L21.1097 21.1248C21.0756 18.3089 21.047 15.9412 21.1809 14.0027C21.321 11.9762 21.6552 10.0434 22.5629 8.20455C23.9443 5.40576 26.1732 3.12543 28.9255 1.69532C30.7339 0.755698 32.6421 0.394356 34.6455 0.228115C36.5617 0.0690985 38.9047 0.0691527 41.6912 0.0692164Z" fill="url(#paint1_radial_901_3770)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6912 0.0692164H49.7357C52.4911 0.0691532 54.8094 0.069099 56.7071 0.225796C58.6921 0.389705 60.583 0.745948 62.379 1.67082C65.1149 3.07963 67.3392 5.3276 68.7332 8.09255C69.6483 9.90771 70.0008 11.8186 70.163 13.8248C70.3181 15.7427 70.318 18.0857 70.3179 20.8704V28.5817C70.318 31.3665 70.3181 33.7094 70.163 35.6273C70.0008 37.6335 69.6483 39.5444 68.7332 41.3596C67.3392 44.1245 65.1149 46.3725 62.379 47.7813C60.583 48.7062 58.6921 49.0624 56.7071 49.2263C54.8094 49.383 52.491 49.383 49.7356 49.3829H25.7125C24.0251 49.3829 22.5024 48.3598 21.8494 46.7872C21.1965 45.2147 21.5408 43.3997 22.7229 42.1828L42.5612 21.7599L48.5405 27.6922L35.6962 40.9149H49.5678C52.5353 40.9149 54.509 40.9116 56.0248 40.7864C57.4917 40.6653 58.1571 40.4515 58.5751 40.2363C59.7343 39.6393 60.6768 38.6868 61.2675 37.5152C61.4805 37.0927 61.6921 36.4202 61.8119 34.9378C61.9358 33.4059 61.939 31.4112 61.939 28.4121V21.04C61.939 18.0409 61.9358 16.0462 61.8119 14.5143C61.6921 13.0319 61.4805 12.3594 61.2675 11.9369C60.6768 10.7654 59.7343 9.81282 58.5751 9.21587C58.1571 9.00062 57.4917 8.7868 56.0248 8.66568C54.509 8.54052 52.5353 8.53723 49.5678 8.53723H41.8603C38.8603 8.53723 36.8636 8.54055 35.3311 8.66772C33.8471 8.79087 33.1762 9.00831 32.7568 9.22625C31.5905 9.83223 30.6461 10.7985 30.0607 11.9844C29.8502 12.4109 29.6432 13.0915 29.5395 14.5927C29.4324 16.143 29.4534 18.1608 29.49 21.1925L29.532 24.6744L21.1537 24.7777L21.1097 21.1248C21.0756 18.3089 21.047 15.9412 21.1809 14.0027C21.321 11.9762 21.6552 10.0434 22.5629 8.20455C23.9443 5.40576 26.1732 3.12543 28.9255 1.69532C30.7339 0.755698 32.6421 0.394356 34.6455 0.228115C36.5617 0.0690985 38.9047 0.0691527 41.6912 0.0692164Z" fill="url(#paint2_radial_901_3770)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6912 0.0692164H49.7357C52.4911 0.0691532 54.8094 0.069099 56.7071 0.225796C58.6921 0.389705 60.583 0.745948 62.379 1.67082C65.1149 3.07963 67.3392 5.3276 68.7332 8.09255C69.6483 9.90771 70.0008 11.8186 70.163 13.8248C70.3181 15.7427 70.318 18.0857 70.3179 20.8704V28.5817C70.318 31.3665 70.3181 33.7094 70.163 35.6273C70.0008 37.6335 69.6483 39.5444 68.7332 41.3596C67.3392 44.1245 65.1149 46.3725 62.379 47.7813C60.583 48.7062 58.6921 49.0624 56.7071 49.2263C54.8094 49.383 52.491 49.383 49.7356 49.3829H25.7125C24.0251 49.3829 22.5024 48.3598 21.8494 46.7872C21.1965 45.2147 21.5408 43.3997 22.7229 42.1828L42.5612 21.7599L48.5405 27.6922L35.6962 40.9149H49.5678C52.5353 40.9149 54.509 40.9116 56.0248 40.7864C57.4917 40.6653 58.1571 40.4515 58.5751 40.2363C59.7343 39.6393 60.6768 38.6868 61.2675 37.5152C61.4805 37.0927 61.6921 36.4202 61.8119 34.9378C61.9358 33.4059 61.939 31.4112 61.939 28.4121V21.04C61.939 18.0409 61.9358 16.0462 61.8119 14.5143C61.6921 13.0319 61.4805 12.3594 61.2675 11.9369C60.6768 10.7654 59.7343 9.81282 58.5751 9.21587C58.1571 9.00062 57.4917 8.7868 56.0248 8.66568C54.509 8.54052 52.5353 8.53723 49.5678 8.53723H41.8603C38.8603 8.53723 36.8636 8.54055 35.3311 8.66772C33.8471 8.79087 33.1762 9.00831 32.7568 9.22625C31.5905 9.83223 30.6461 10.7985 30.0607 11.9844C29.8502 12.4109 29.6432 13.0915 29.5395 14.5927C29.4324 16.143 29.4534 18.1608 29.49 21.1925L29.532 24.6744L21.1537 24.7777L21.1097 21.1248C21.0756 18.3089 21.047 15.9412 21.1809 14.0027C21.321 11.9762 21.6552 10.0434 22.5629 8.20455C23.9443 5.40576 26.1732 3.12543 28.9255 1.69532C30.7339 0.755698 32.6421 0.394356 34.6455 0.228115C36.5617 0.0690985 38.9047 0.0691527 41.6912 0.0692164Z" fill="url(#paint3_radial_901_3770)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2387 61.2094C16.7545 61.3345 18.7282 61.3378 21.6957 61.3378H29.5325C32.2383 61.3378 34.0379 61.3351 35.4241 61.2305C36.7675 61.1291 37.3836 60.9496 37.7694 60.7708C39.11 60.1493 40.1854 59.0624 40.8004 57.7076C40.9774 57.3176 41.155 56.695 41.2553 55.3373C41.3588 53.9363 41.3615 52.1177 41.3615 49.383H49.7404L49.7404 49.5375C49.7405 52.077 49.7405 54.2139 49.611 55.9677C49.4756 57.8002 49.1819 59.5515 48.4162 61.2384C46.9649 64.4357 44.4268 67.0008 41.2631 68.4675C39.594 69.2414 37.8611 69.5382 36.0479 69.675C34.3125 69.8059 32.1981 69.8059 29.6853 69.8058L21.5278 69.8058C18.7724 69.8059 16.4541 69.806 14.5564 69.6493C12.5714 69.4854 10.6805 69.1291 8.88448 68.2042C6.14862 66.7954 3.92431 64.5475 2.53032 61.7825C1.61517 59.9673 1.26268 58.0564 1.1005 56.0503C0.945447 54.1324 0.945503 51.7894 0.945566 49.0046V41.2933C0.945503 38.5086 0.945447 36.1656 1.1005 34.2477C1.26268 32.2416 1.61518 30.3306 2.53032 28.5155C3.92431 25.7505 6.14862 23.5026 8.88448 22.0938C10.6805 21.1689 12.5714 20.8126 14.5564 20.6487C16.4541 20.492 18.7725 20.4921 21.5279 20.4921H45.551C47.8647 20.4921 49.7404 22.3878 49.7404 24.7262C49.7404 27.0645 47.8647 28.9602 45.551 28.9602H21.6957C18.7282 28.9602 16.7545 28.9634 15.2387 29.0886C13.7718 29.2097 13.1064 29.4236 12.6884 29.6388C11.5292 30.2358 10.5867 31.1883 9.99598 32.3599C9.783 32.7823 9.57143 33.4548 9.45158 34.9373C9.32774 36.4692 9.32448 38.4638 9.32448 41.4629V48.8351C9.32448 51.8341 9.32774 53.8288 9.45158 55.3607C9.57143 56.8432 9.783 57.5157 9.99598 57.9381C10.5867 59.1097 11.5292 60.0622 12.6884 60.6592C13.1064 60.8744 13.7718 61.0883 15.2387 61.2094Z" fill="url(#paint4_linear_901_3770)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2387 61.2094C16.7545 61.3345 18.7282 61.3378 21.6957 61.3378H29.5325C32.2383 61.3378 34.0379 61.3351 35.4241 61.2305C36.7675 61.1291 37.3836 60.9496 37.7694 60.7708C39.11 60.1493 40.1854 59.0624 40.8004 57.7076C40.9774 57.3176 41.155 56.695 41.2553 55.3373C41.3588 53.9363 41.3615 52.1177 41.3615 49.383H49.7404L49.7404 49.5375C49.7405 52.077 49.7405 54.2139 49.611 55.9677C49.4756 57.8002 49.1819 59.5515 48.4162 61.2384C46.9649 64.4357 44.4268 67.0008 41.2631 68.4675C39.594 69.2414 37.8611 69.5382 36.0479 69.675C34.3125 69.8059 32.1981 69.8059 29.6853 69.8058L21.5278 69.8058C18.7724 69.8059 16.4541 69.806 14.5564 69.6493C12.5714 69.4854 10.6805 69.1291 8.88448 68.2042C6.14862 66.7954 3.92431 64.5475 2.53032 61.7825C1.61517 59.9673 1.26268 58.0564 1.1005 56.0503C0.945447 54.1324 0.945503 51.7894 0.945566 49.0046V41.2933C0.945503 38.5086 0.945447 36.1656 1.1005 34.2477C1.26268 32.2416 1.61518 30.3306 2.53032 28.5155C3.92431 25.7505 6.14862 23.5026 8.88448 22.0938C10.6805 21.1689 12.5714 20.8126 14.5564 20.6487C16.4541 20.492 18.7725 20.4921 21.5279 20.4921H45.551C47.8647 20.4921 49.7404 22.3878 49.7404 24.7262C49.7404 27.0645 47.8647 28.9602 45.551 28.9602H21.6957C18.7282 28.9602 16.7545 28.9634 15.2387 29.0886C13.7718 29.2097 13.1064 29.4236 12.6884 29.6388C11.5292 30.2358 10.5867 31.1883 9.99598 32.3599C9.783 32.7823 9.57143 33.4548 9.45158 34.9373C9.32774 36.4692 9.32448 38.4638 9.32448 41.4629V48.8351C9.32448 51.8341 9.32774 53.8288 9.45158 55.3607C9.57143 56.8432 9.783 57.5157 9.99598 57.9381C10.5867 59.1097 11.5292 60.0622 12.6884 60.6592C13.1064 60.8744 13.7718 61.0883 15.2387 61.2094Z" fill="url(#paint5_radial_901_3770)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_901_3770" x1="34" y1="49" x2="34.5606" y2="0.00641514" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.43118" stop-color="#498FFD"/>
|
||||
<stop offset="1" stop-color="#16D1D6"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_901_3770" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.5 15) rotate(-15.5725) scale(31.6623 31.6129)">
|
||||
<stop stop-color="#18CFD7"/>
|
||||
<stop offset="1" stop-color="#18CFD7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_901_3770" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(39 29) rotate(152.354) scale(11.8533 11.8348)">
|
||||
<stop stop-color="#4990FE"/>
|
||||
<stop offset="0.353962" stop-color="#4990FE"/>
|
||||
<stop offset="1" stop-color="#4990FE" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint3_radial_901_3770" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.5 45) rotate(-45.9392) scale(21.5697 21.536)">
|
||||
<stop stop-color="#4990FE"/>
|
||||
<stop offset="0.405688" stop-color="#4990FE"/>
|
||||
<stop offset="1" stop-color="#4990FE" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear_901_3770" x1="50" y1="70.0001" x2="50.1803" y2="20.0007" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2950FF"/>
|
||||
<stop offset="0.821717" stop-color="#498FFD"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint5_radial_901_3770" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(50 49.0001) rotate(129.289) scale(21.319 21.0947)">
|
||||
<stop offset="0.337169" stop-color="#2950FF"/>
|
||||
<stop offset="0.792226" stop-color="#2950FF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
12
src/assets/images/UI/search.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 25 20" style="enable-background:new 0 0 25 20;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#9eaacc;}
|
||||
</style>
|
||||
<path class="st0" d="M24.7,18.9l-9.9-6.6c1-1.7,1.3-3.6,1-5.5C15,2.5,10.9-0.4,6.6,0.4C2.2,1.2-0.7,5.3,0.1,9.6
|
||||
c0.7,3.8,4.1,6.5,7.8,6.5c0.5,0,0.9,0,1.4-0.1c1.9-0.3,3.6-1.3,4.7-2.8l9.9,6.6c0.1,0.1,0.2,0.1,0.3,0.1c0.2,0,0.4-0.1,0.5-0.3
|
||||
C25.1,19.4,25,19.1,24.7,18.9z M9.2,14.9C5.5,15.6,2,13.1,1.3,9.4C0.6,5.7,3.1,2.2,6.8,1.5C7.2,1.5,7.6,1.4,8,1.4
|
||||
c3.2,0,6.1,2.3,6.7,5.6c0.3,1.8-0.1,3.6-1.1,5.1C12.5,13.6,11,14.6,9.2,14.9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
6
src/components/UI/Button/Button.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.button {
|
||||
padding: 10px 30px;
|
||||
background: linear-gradient(to bottom,#212f6c,#212f6c);
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
17
src/components/UI/Button/Button.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import "./Button.scss";
|
||||
|
||||
function Button(props: React.HTMLProps<HTMLButtonElement>) {
|
||||
const { children, className, onClick, style } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={"button " + (className || "")}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button;
|
||||
17
src/components/UI/Input/Input.scss
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.input {
|
||||
padding: 8px 15px;
|
||||
background-color: #7694fe1a;
|
||||
border-radius: 5px;
|
||||
color: #9eaacc;
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder {
|
||||
color: #9eaacc;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
27
src/components/UI/Input/Input.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import "./Input.scss";
|
||||
|
||||
interface InputProps extends React.HTMLProps<HTMLInputElement> {
|
||||
onEnterPress?: () => void;
|
||||
}
|
||||
|
||||
function Input(props: InputProps) {
|
||||
const { onEnterPress, ...restProps } = props;
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||
if (props.onKeyDown) props.onKeyDown(event);
|
||||
if (!onEnterPress) return;
|
||||
if (event.key === 'Enter' || event.keyCode === 13) {
|
||||
onEnterPress();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
{...restProps}
|
||||
onKeyDown={onKeyDown}
|
||||
className={"input " + (props.className || "")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Input;
|
||||
17
src/components/default/AliasText/AliasText.scss
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.alias__text {
|
||||
height: 16px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
color: #68a1ff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
16
src/components/default/AliasText/AliasText.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import "./AliasText.scss";
|
||||
|
||||
interface AliasTextProps {
|
||||
children: React.ReactNode,
|
||||
href: string
|
||||
}
|
||||
|
||||
function AliasText(props: AliasTextProps) {
|
||||
return (
|
||||
<span className="alias__text">
|
||||
<a href={props.href}>{props.children}</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default AliasText;
|
||||
10
src/components/default/Header/Header.props.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import PageState from "../../../interfaces/common/PageState";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface HeaderProps {
|
||||
page: PageState;
|
||||
burgerOpened: boolean;
|
||||
setBurgerOpened: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export default HeaderProps;
|
||||
88
src/components/default/Header/Header.scss
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
.header {
|
||||
|
||||
.header__top {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 60px;
|
||||
border-bottom: 1px solid rgba(255,255,255,.1);
|
||||
|
||||
.header__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
svg {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
gap: 45px;
|
||||
|
||||
> a {
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
|
||||
&.selected {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header__burger__button {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header__nav__mobile {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 30px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
> a {
|
||||
height: 35px;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
|
||||
&.selected {
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 761px) {
|
||||
.header__top {
|
||||
justify-content: space-between;
|
||||
|
||||
.header__burger__button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header__nav__mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
src/components/default/Header/Header.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import "./Header.scss";
|
||||
import { ReactComponent as LogoImg } from "../../../assets/images/UI/logo.svg";
|
||||
import { ReactComponent as BurgerImg } from "../../../assets/images/UI/burger.svg";
|
||||
import HeaderProps from "./Header.props";
|
||||
import Button from "../../UI/Button/Button";
|
||||
|
||||
function Header(props: HeaderProps) {
|
||||
const { page, burgerOpened, setBurgerOpened } = props;
|
||||
|
||||
function Nav({ className }: { className?: string }) {
|
||||
return (
|
||||
<nav className={className}>
|
||||
<a
|
||||
className={page === "Blockchain" ? "selected" : undefined}
|
||||
href="/"
|
||||
>
|
||||
Blockchain
|
||||
</a>
|
||||
<a
|
||||
className={page === "Alt-blocks" ? "selected" : undefined}
|
||||
href="/alt-blocks"
|
||||
>
|
||||
Alt-blocks
|
||||
</a>
|
||||
<a
|
||||
className={page === "Aliases" ? "selected" : undefined}
|
||||
href="/aliases"
|
||||
>
|
||||
Aliases
|
||||
</a>
|
||||
<a
|
||||
className={page === "Charts" ? "selected" : undefined}
|
||||
href="/"
|
||||
>
|
||||
Charts
|
||||
</a>
|
||||
<a
|
||||
className={page === "API" ? "selected" : undefined}
|
||||
href="/api"
|
||||
>
|
||||
API
|
||||
</a>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
<div className="header__top">
|
||||
<a href="/">
|
||||
<div className="header__logo">
|
||||
<LogoImg />
|
||||
<p>ZANO</p>
|
||||
</div>
|
||||
</a>
|
||||
<Nav />
|
||||
<Button
|
||||
onClick={() => setBurgerOpened(!burgerOpened)}
|
||||
className="header__burger__button"
|
||||
>
|
||||
<BurgerImg />
|
||||
</Button>
|
||||
</div>
|
||||
{ burgerOpened && <Nav className="header__nav__mobile" /> }
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
111
src/components/default/InfoTopPanel/InfoTopPanel.scss
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
.blockchain__info__top {
|
||||
height: 77px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h4 {
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.info__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
> svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.info__top__input {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
position: relative;
|
||||
|
||||
> p {
|
||||
color: #ff5252;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
}
|
||||
|
||||
> input {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
background-color: #234ee21a;
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
> button {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 761px) {
|
||||
.info__top__content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h4 {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.info__top__hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
justify-content: flex-end;
|
||||
|
||||
h4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.info__back {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
&.blockchain__input__closed {
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
.info__top__input {
|
||||
max-width: 50px;
|
||||
|
||||
> input {
|
||||
padding-left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.info__back {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
h4 {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
src/components/default/InfoTopPanel/InfoTopPanel.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import Button from "../../UI/Button/Button";
|
||||
import Input from "../../UI/Input/Input";
|
||||
import "./InfoTopPanel.scss";
|
||||
import { ReactComponent as SearchImg } from "../../../assets/images/UI/search.svg";
|
||||
import { ReactComponent as BackImg } from "../../../assets/images/UI/back.svg";
|
||||
|
||||
import { useState } from "react";
|
||||
import Fetch from "../../../utils/methods";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface InfoTopPanelProps {
|
||||
burgerOpened: boolean;
|
||||
title: string;
|
||||
content?: React.ReactNode;
|
||||
back?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function InfoTopPanel(props: InfoTopPanelProps) {
|
||||
const { burgerOpened, title, content, back, className } = props;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [inputClosed, setInputClosed] = useState(false);
|
||||
const [inputState, setInputState] = useState("");
|
||||
|
||||
const [noMatch, setNoMatch] = useState(false);
|
||||
|
||||
async function onButtonClick() {
|
||||
setInputClosed(!inputClosed);
|
||||
if (!inputState) return;
|
||||
const input = inputState.replace(/\s/g, '');
|
||||
|
||||
const searchInfo = await Fetch.searchById(input);
|
||||
|
||||
if (searchInfo && typeof searchInfo === "object") {
|
||||
const result = searchInfo.result;
|
||||
if (result === "tx") {
|
||||
return navigate("/transaction/" + input);
|
||||
}
|
||||
if (result === "block") {
|
||||
return navigate("/block/" + input);
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
try {
|
||||
const parsedHash = await Fetch.getHashByHeight(parseInt(input, 10));
|
||||
if (parsedHash) {
|
||||
return navigate("/block/" + parsedHash);
|
||||
}
|
||||
} catch {
|
||||
return setNoMatch(true);
|
||||
}
|
||||
} else {
|
||||
return setNoMatch(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onBackClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||
event.preventDefault();
|
||||
navigate(-1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"blockchain__info__top" + " " + className + " "
|
||||
+ (inputClosed ? "blockchain__input__closed" : "") + " "
|
||||
+ (burgerOpened ? "info__top__hidden" : "")
|
||||
}
|
||||
>
|
||||
{!back ?
|
||||
<div className="info__top__content">
|
||||
{
|
||||
content || <></>
|
||||
}
|
||||
</div> :
|
||||
<a href="/" onClick={onBackClick}>
|
||||
<div className="info__back">
|
||||
<BackImg />
|
||||
<p>Back</p>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
<h4>{title}</h4>
|
||||
<div className="info__top__input">
|
||||
{noMatch && <p>No matching records found!</p> }
|
||||
<Input
|
||||
placeholder="block height / block hash / transaction hash"
|
||||
value={inputState}
|
||||
onInput={(event) => {
|
||||
setInputState(event.currentTarget.value);
|
||||
setNoMatch(false);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={onButtonClick}
|
||||
>
|
||||
<SearchImg />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoTopPanel;
|
||||
270
src/components/default/StatsPanel/StatsPanel.scss
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
.item__difficulty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 5px;
|
||||
border: 11px solid transparent;
|
||||
border-bottom: 0;
|
||||
border-left: 11px solid transparent;
|
||||
}
|
||||
|
||||
&:first-child::before {
|
||||
border-left-color: #0c69fe;
|
||||
}
|
||||
|
||||
&:last-child::before {
|
||||
border-left-color: #47effb;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
.item__difficulty p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.blockchain__info__main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
margin-bottom: 25px;
|
||||
|
||||
.info__main__top {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
> .main__top__item {
|
||||
flex: 1;
|
||||
background-color: #2b3768;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
||||
> div:first-child {
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
flex-basis: 35%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
> .item__value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
> p {
|
||||
font-size: 25px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
padding: 2px 8px;
|
||||
background-color: #0c69fe;
|
||||
border-radius: 20px;
|
||||
|
||||
> p {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info__main__bottom {
|
||||
height: 127px;
|
||||
display: flex;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
|
||||
.main__bottom__item {
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:nth-child(1), &:nth-child(4) {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
|
||||
> div:first-child {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
color: #9eaacc;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom__item__value {
|
||||
flex: 1;
|
||||
background-color: #2b3768;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item__text__large {
|
||||
font-size: 25px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.item__text__small {
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
.item__text__large {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.item__text__small {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.info__main__mobile {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
|
||||
.info__main__top, .info__main__bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #2b3768;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
p {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
> :nth-child(2n) {
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
}
|
||||
}
|
||||
|
||||
.info__main__top {
|
||||
.main__top__item {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
height: 55px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
padding: 12px 20px;
|
||||
justify-content: flex-start;
|
||||
flex-basis: 35%;
|
||||
}
|
||||
|
||||
&.item__value {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
> div {
|
||||
padding: 2px 8px;
|
||||
background-color: #0c69fe;
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info__main__bottom {
|
||||
.main__bottom__item {
|
||||
padding: 12px 24px;
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.item__difficulty {
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 991px) {
|
||||
.blockchain__info__main {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.info__main__mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
168
src/components/default/StatsPanel/StatsPanel.tsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import Info from "../../../interfaces/state/Info";
|
||||
import VisibilityInfo from "../../../interfaces/state/VisibilityInfo";
|
||||
import Fetch from "../../../utils/methods";
|
||||
import Utils from "../../../utils/utils";
|
||||
import "./StatsPanel.scss";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
function StatsPanel(props: { onlyBottom?: boolean, visibilityInfo?: VisibilityInfo | null }) {
|
||||
const { onlyBottom, visibilityInfo } = props;
|
||||
|
||||
const [info, setInfo] = useState<Info | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchInfo() {
|
||||
const result = await Fetch.getInfo();
|
||||
if (result.success === false) return;
|
||||
if (!result.height) return;
|
||||
setInfo(result);
|
||||
}
|
||||
|
||||
fetchInfo();
|
||||
|
||||
const id = setInterval(fetchInfo, 20 * 1e3);
|
||||
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
|
||||
const infoHeight = Utils.formatNumber(info?.height || 0, 0);
|
||||
const posDiff = Utils.toShiftedNumber(info?.pos_difficulty || "0", 0);
|
||||
const powDiff = Utils.formatNumber(info?.pow_difficulty || 0, 0);
|
||||
const coinsEmitted = Utils.toShiftedNumber(info?.total_coins || "0", 12);
|
||||
|
||||
const stackedCoins = Utils.toShiftedNumber(visibilityInfo?.amount.toString() || "0", 12);
|
||||
const percentage = visibilityInfo?.percentage || "0";
|
||||
const devFund = Utils.toShiftedNumber(visibilityInfo?.unlocked_balance.toString() || "0", 12);
|
||||
|
||||
function TopItem(props: { title: string, amount: string, percent?: string }) {
|
||||
const { title, amount, percent } = props;
|
||||
|
||||
return (
|
||||
<div className="main__top__item">
|
||||
<div>
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
<div className="item__value">
|
||||
<p>{amount + " ZANO"}</p>
|
||||
{percent &&
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
{percent + "%"}
|
||||
</p>
|
||||
</div>
|
||||
<p>from total supply</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function BottomItem(props: { title: string, children: React.ReactNode }) {
|
||||
const { title, children } = props;
|
||||
|
||||
return (
|
||||
<div className="main__bottom__item">
|
||||
<div>
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
<div className="bottom__item__value">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="blockchain__info__main">
|
||||
{!onlyBottom &&
|
||||
<div className="info__main__top">
|
||||
<TopItem
|
||||
title="Staked Coins (est)"
|
||||
amount={stackedCoins}
|
||||
percent={percentage}
|
||||
/>
|
||||
<TopItem
|
||||
title="Dev Fund"
|
||||
amount={devFund}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className="info__main__bottom">
|
||||
<BottomItem title="Height">
|
||||
<p className="item__text__large">{infoHeight}</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Difficulty">
|
||||
<div className="item__difficulty">
|
||||
<div>
|
||||
<p>PoS: {posDiff}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>PoW: {powDiff}</p>
|
||||
</div>
|
||||
</div>
|
||||
</BottomItem>
|
||||
<BottomItem title="Coins Emitted">
|
||||
<p className="item__text__small">{coinsEmitted}</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Transactions">
|
||||
<p className="item__text__large">2 936 934</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Hash Rate (aprox):">
|
||||
<p className="item__text__large">79.992 GH/sec</p>
|
||||
</BottomItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="info__main__mobile">
|
||||
{!onlyBottom &&
|
||||
<div className="info__main__top">
|
||||
<TopItem
|
||||
title="Staked Coins (est)"
|
||||
amount={stackedCoins}
|
||||
percent={percentage}
|
||||
/>
|
||||
<TopItem
|
||||
title="Dev Fund"
|
||||
amount={devFund}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div className="info__main__bottom">
|
||||
<BottomItem title="Height">
|
||||
<p className="item__text__large">{infoHeight}</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Difficulty">
|
||||
<div className="item__difficulty">
|
||||
<div>
|
||||
<p>PoS: {posDiff}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>PoW: {powDiff}</p>
|
||||
</div>
|
||||
</div>
|
||||
</BottomItem>
|
||||
<BottomItem title="Coins Emitted">
|
||||
<p className="item__text__small">{coinsEmitted}</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Transactions">
|
||||
<p className="item__text__large">2 936 934</p>
|
||||
</BottomItem>
|
||||
<BottomItem title="Hash Rate (aprox):">
|
||||
<p className="item__text__large">79.992 GH/sec</p>
|
||||
</BottomItem>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatsPanel;
|
||||
18
src/components/default/Table/Table.props.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface TableProps {
|
||||
headers: string[];
|
||||
elements: React.ReactNode[][];
|
||||
pagination?: boolean;
|
||||
hidePaginationBlock?: boolean;
|
||||
className?: string;
|
||||
itemsOnPage?: string;
|
||||
setItemsOnPage?: Dispatch<SetStateAction<string>>;
|
||||
page?: string;
|
||||
setPage?: Dispatch<SetStateAction<string>>;
|
||||
goToBlock?: string;
|
||||
setGoToBlock?: Dispatch<SetStateAction<string>>;
|
||||
goToBlockEnter?: () => void;
|
||||
}
|
||||
|
||||
export default TableProps;
|
||||
113
src/components/default/Table/Table.scss
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
overflow: hidden;
|
||||
|
||||
p, a {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
tr {
|
||||
> * {
|
||||
padding: 12px 20px;
|
||||
white-space: nowrap;
|
||||
|
||||
@media screen and (max-width: 1170px) {
|
||||
padding: 12px 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
|
||||
&:last-child {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
|
||||
th {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #9eaacc;
|
||||
text-align: start;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:nth-child(2n) {
|
||||
background-color: #263163;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table__pagination {
|
||||
height: 60px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: #9eaacc;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 100px;
|
||||
text-align: center;
|
||||
|
||||
&::placeholder {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.table__pagination__pages {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
height: 29px;
|
||||
width: 29px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:first-child > svg {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table__pagination__blocks {
|
||||
display: flex;
|
||||
gap: 29px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&:last-child input {
|
||||
max-width: 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/components/default/Table/Table.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import "./Table.scss";
|
||||
import { ReactComponent as ArrowImg } from "../../../assets/images/UI/arrow.svg";
|
||||
import { nanoid } from "nanoid";
|
||||
import Input from "../../UI/Input/Input";
|
||||
import TableProps from "./Table.props";
|
||||
|
||||
function Table(props: TableProps) {
|
||||
function onNumberInput(event: React.FormEvent<HTMLInputElement>, setState?: React.Dispatch<React.SetStateAction<string>>, max?: number) {
|
||||
if (!setState) return;
|
||||
const newValue = event.currentTarget.value;
|
||||
if (newValue !== "" && !newValue.match(/^\d+$/)) return event.preventDefault();
|
||||
if (max) {
|
||||
const number = parseInt(newValue, 10) || 0;
|
||||
if (number > max) return;
|
||||
}
|
||||
setState(newValue);
|
||||
}
|
||||
|
||||
const {
|
||||
headers,
|
||||
elements,
|
||||
pagination,
|
||||
className,
|
||||
hidePaginationBlock,
|
||||
itemsOnPage,
|
||||
setItemsOnPage,
|
||||
page,
|
||||
setPage,
|
||||
goToBlock,
|
||||
setGoToBlock,
|
||||
goToBlockEnter
|
||||
} = props;
|
||||
|
||||
function changePage(increase: number) {
|
||||
if (!page || !setPage) return;
|
||||
const pageNumber = parseInt(page, 10);
|
||||
setPage(Math.max(1, pageNumber + increase).toString());
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
{
|
||||
headers.map(e => <th key={nanoid(16)}>{e}</th>)
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{elements.map(row =>
|
||||
<tr key={nanoid(16)}>
|
||||
{row.map(e => <td key={nanoid(16)}>{e}</td>)}
|
||||
</tr>
|
||||
)}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{pagination &&
|
||||
<div className="table__pagination">
|
||||
<div className="table__pagination__pages">
|
||||
<p>Pages: </p>
|
||||
<div>
|
||||
<button
|
||||
className={page === "1" ? "disabled" : undefined}
|
||||
onClick={() => changePage(-1)}
|
||||
>
|
||||
<ArrowImg />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => changePage(1)}
|
||||
>
|
||||
<ArrowImg />
|
||||
</button>
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={page}
|
||||
onInput={(e) => onNumberInput(e, setPage)}
|
||||
/>
|
||||
</div>
|
||||
<div className="table__pagination__blocks">
|
||||
<div>
|
||||
<p>Items on page: </p>
|
||||
<Input
|
||||
type="text"
|
||||
value={itemsOnPage}
|
||||
onInput={(e) => onNumberInput(e, setItemsOnPage, 50)}
|
||||
/>
|
||||
</div>
|
||||
{!hidePaginationBlock &&
|
||||
<div>
|
||||
<p>Go to block: </p>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="number"
|
||||
value={goToBlock}
|
||||
onInput={(e) => onNumberInput(e, setGoToBlock)}
|
||||
onEnterPress={goToBlockEnter}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Table;
|
||||
113
src/index.scss
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: all 0.3s;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
line-height: 100%;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#root {
|
||||
max-width: 1200px;
|
||||
padding: 0 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
transition: none;
|
||||
background: radial-gradient(circle at 50% 2%,#365099 0%,#213069 15%,#091233 39%);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
p, a {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #68a1ff;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.custom-scroll::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: #32439f;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar-track {
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-Light.ttf) format("opentype");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-Regular.ttf) format("opentype");
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-Medium.ttf) format("opentype");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-SemiBold.ttf) format("opentype");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-Bold.ttf) format("opentype");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url(./assets/fonts/OpenSans/OpenSans-ExtraBold.ttf) format("opentype");
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inconsolata";
|
||||
src: url(./assets/fonts/Inconsolata/Inconsolata-Regular.ttf) format("opentype");
|
||||
font-weight: 400;
|
||||
}
|
||||
19
src/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.scss';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
9
src/interfaces/common/APIItemValue.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
interface APIItemValue {
|
||||
key: string;
|
||||
value: {
|
||||
text: string;
|
||||
link?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export default APIItemValue;
|
||||
24
src/interfaces/common/BlockInfo.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
interface BlockInfo {
|
||||
type: "PoS" | "PoW";
|
||||
timestamp?: number;
|
||||
actualTimestamp?: number;
|
||||
difficulty: string;
|
||||
minerTextInfo?: string;
|
||||
cummulativeDiffAdjusted?: string;
|
||||
cummulativeDiffPresize?: string;
|
||||
orphan?: boolean;
|
||||
baseReward: string;
|
||||
transactionsFee: string;
|
||||
rewardPenalty: string;
|
||||
reward: string;
|
||||
totalBlockSize?: string;
|
||||
effectiveTxsMedian?: number;
|
||||
blockFeeMedian?: string;
|
||||
effectiveFeeMedian?: string;
|
||||
currentTxsMedian?: number;
|
||||
transactions: string;
|
||||
transactionsSize?: string;
|
||||
seed?: string;
|
||||
}
|
||||
|
||||
export default BlockInfo;
|
||||
3
src/interfaces/common/PageState.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
type PageState = "Blockchain" | "Alt-blocks" | "Aliases" | "Charts" | "API";
|
||||
|
||||
export default PageState;
|
||||
13
src/interfaces/common/TransactionInfo.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
interface TransactionInfo {
|
||||
hash: string;
|
||||
amount: string;
|
||||
fee: string;
|
||||
size: string;
|
||||
confirmations: string;
|
||||
publicKey: string;
|
||||
mixin?: string;
|
||||
extraItems: string[];
|
||||
attachments?: string;
|
||||
}
|
||||
|
||||
export default TransactionInfo;
|
||||
6
src/interfaces/state/Alias.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
interface Alias {
|
||||
alias: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export default Alias;
|
||||
10
src/interfaces/state/Block.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
interface Block {
|
||||
height: number;
|
||||
type: "PoS" | "PoW",
|
||||
timestamp: number;
|
||||
size: number;
|
||||
transactions: number;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export default Block;
|
||||
8
src/interfaces/state/Info.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
interface Info {
|
||||
height: number;
|
||||
pos_difficulty: string;
|
||||
pow_difficulty: number;
|
||||
total_coins: string;
|
||||
}
|
||||
|
||||
export default Info;
|
||||
7
src/interfaces/state/VisibilityInfo.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface VisibilityInfo {
|
||||
amount: number;
|
||||
unlocked_balance: number;
|
||||
percentage: string;
|
||||
}
|
||||
|
||||
export default VisibilityInfo;
|
||||
177
src/pages/API/API.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import "../../styles/API.scss";
|
||||
import APIItemValue from "../../interfaces/common/APIItemValue";
|
||||
import { JsonView, darkStyles } from 'react-json-view-lite';
|
||||
import 'react-json-view-lite/dist/index.css';
|
||||
import examples from "./examples";
|
||||
|
||||
interface APIEndpointItemProps {
|
||||
title: string,
|
||||
method: {
|
||||
text: string,
|
||||
link?: string
|
||||
},
|
||||
example: string,
|
||||
json?: string
|
||||
}
|
||||
|
||||
function API() {
|
||||
const howToUseValues = [
|
||||
{ key: "API ENDPOINT", value: { text: "https://explorer.zano.org/api" } },
|
||||
{ key: "URL Request Format", value: { text: "https://explorer.zano.org/api/{method}/{param1}/{param2}" } },
|
||||
];
|
||||
|
||||
const endpoints: APIEndpointItemProps[] = [
|
||||
{
|
||||
title: "Request current coin stats",
|
||||
method: { text: "get_info", link: "https://docs.zano.org/reference/#getinfo" },
|
||||
example: "https://explorer.zano.org/api/get_info/4294967295",
|
||||
json: JSON.stringify(examples.get_info)
|
||||
},
|
||||
{
|
||||
title: "Request current total coins",
|
||||
method: { text: "get_total_coins"},
|
||||
example: "https://explorer.zano.org/api/get_total_coins"
|
||||
},
|
||||
{
|
||||
title: "Request blocks (offset and count)",
|
||||
method: { text: "get_blocks_details", link: "https://docs.zano.org/reference/#get_blocks_details" },
|
||||
example: "https://explorer.zano.org/api/get_blocks_details/{:offset}/{:count}"
|
||||
},
|
||||
{
|
||||
title: "Request a given block by hash",
|
||||
method: { text: "get_main_block_details", link: "https://docs.zano.org/reference/#get_main_block_details" },
|
||||
example: "https://explorer.zano.org/api/get_main_block_details/{:hash}"
|
||||
},
|
||||
{
|
||||
title: "Request Alt-blocks (offset and count)",
|
||||
method: { text: "get_alt_blocks_details", link: "https://docs.zano.org/reference/#get_alt_blocks_details" },
|
||||
example: "https://explorer.zano.org/api/get_alt_blocks_details/{:offset}/{:count}"
|
||||
},
|
||||
{
|
||||
title: "Request a given Alt-block by hash",
|
||||
method: { text: "get_alt_block_details", link: "https://docs.zano.org/reference/#get_alt_blocks_details" },
|
||||
example: "https://explorer.zano.org/api/get_alt_block_details/{:hash}"
|
||||
},
|
||||
{
|
||||
title: "Request transaction from the pool",
|
||||
method: { text: "get_pool_txs_details", link: "https://docs.zano.org/reference/#get_pool_txs_details" },
|
||||
example: "https://explorer.zano.org/api/get_pool_txs_details"
|
||||
},
|
||||
{
|
||||
title: "Request brief information transactions from the pool",
|
||||
method: { text: "get_pool_txs_brief_details", link: "https://docs.zano.org/reference/#get_pool_txs_brief_details" },
|
||||
example: "https://explorer.zano.org/api/get_pool_txs_brief_details"
|
||||
},
|
||||
{
|
||||
title: "Request IDs for all txs from the pool",
|
||||
method: { text: "get_all_pool_tx_list", link: "https://docs.zano.org/reference/#get_all_pool_tx_list" },
|
||||
example: "https://explorer.zano.org/api/get_all_pool_tx_list"
|
||||
},
|
||||
{
|
||||
title: "Request a given transaction by hash",
|
||||
method: { text: "get_tx_details", link: "https://docs.zano.org/reference/#get_tx_details" },
|
||||
example: "https://explorer.zano.org/api/get_tx_details/{:tx_hash}"
|
||||
},
|
||||
]
|
||||
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
function APIItem(props: { title: string, values?: APIItemValue[], json?: string }) {
|
||||
const { title, values, json } = props;
|
||||
|
||||
const [parsedJson, setParsedJson] = useState<Object | any[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function parseJson() {
|
||||
if (!json) return;
|
||||
try {
|
||||
const result = JSON.parse(json);
|
||||
setParsedJson(result);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
parseJson();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="api__item">
|
||||
<div className="api__item__title">
|
||||
<h3>{title}</h3>
|
||||
</div>
|
||||
<div className="api__item__units">
|
||||
{
|
||||
values?.map(e => (
|
||||
<div key={e.key} className="api__item__unit">
|
||||
<div>
|
||||
<p>{e.key}</p>
|
||||
</div>
|
||||
<div>
|
||||
{!e.value.link ?
|
||||
<p>{e.value.text}</p> :
|
||||
<a href={e.value.link} target="_blank">{e.value.text}</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{json &&
|
||||
<div className="api__item__json">
|
||||
<p>JSON Response</p>
|
||||
<JsonView
|
||||
data={JSON.parse(json)}
|
||||
style={
|
||||
{
|
||||
...darkStyles,
|
||||
container: "item__json__container",
|
||||
basicChildStyle: "item__json__element",
|
||||
numberValue: "item__json__number"
|
||||
}
|
||||
}
|
||||
shouldExpandNode={() => false}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function APIEndpointItem(props: APIEndpointItemProps) {
|
||||
const { title, method, example, json } = props;
|
||||
|
||||
const ItemValues: APIItemValue[] = [
|
||||
{ key: "Method:", value: method },
|
||||
{ key: "Example:", value: { text: example } }
|
||||
]
|
||||
|
||||
return (
|
||||
<APIItem title={title} values={ItemValues} json={json} />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="api">
|
||||
<Header
|
||||
page="API"
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
/>
|
||||
<div className="api__title">
|
||||
<p>API Documentation</p>
|
||||
</div>
|
||||
<div className="api__items">
|
||||
<APIItem title="How to use" values={howToUseValues} />
|
||||
{/* <APIEndpointItem title="Request current coin stats" method={{ text: "get_info" }} /> */}
|
||||
{
|
||||
endpoints.map(e => (
|
||||
<APIEndpointItem {...e} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default API;
|
||||
117
src/pages/API/examples.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
const examples = {
|
||||
get_info: {
|
||||
alias_count: 84,
|
||||
alt_blocks_count: 244,
|
||||
block_reward: 1000000000000,
|
||||
current_blocks_median: 125000,
|
||||
current_max_allowed_block_size: 250000,
|
||||
current_network_hashrate_350: 11097233049,
|
||||
current_network_hashrate_50: 0,
|
||||
daemon_network_state: 2,
|
||||
default_fee: 10000000000,
|
||||
expiration_median_timestamp: 0,
|
||||
grey_peerlist_size: 45,
|
||||
height: 18435,
|
||||
incoming_connections_count: 9,
|
||||
last_block_hash: "f36c1dab5a7eb04bb6ff504e38239b827ac572883277aa6908fc320a6e285d85",
|
||||
last_block_size: 0,
|
||||
last_block_timestamp: 1558424224,
|
||||
last_block_total_reward: 1000000000000,
|
||||
last_pos_timestamp: 0,
|
||||
last_pow_timestamp: 0,
|
||||
max_net_seen_height: 13374,
|
||||
mi: {
|
||||
build_no: 26,
|
||||
mode: 0,
|
||||
ver_major: 1,
|
||||
ver_minor: 0,
|
||||
ver_revision: 0,
|
||||
},
|
||||
minimum_fee: 100000,
|
||||
net_time_delta_median: 0,
|
||||
offers_count: 0,
|
||||
outgoing_connections_count: 8,
|
||||
outs_stat: {
|
||||
amount_0_001: 0,
|
||||
amount_0_01: 0,
|
||||
amount_0_1: 0,
|
||||
amount_1: 0,
|
||||
amount_10: 0,
|
||||
amount_100: 0,
|
||||
amount_1000: 0,
|
||||
amount_10000: 0,
|
||||
amount_100000: 0,
|
||||
amount_1000000: 0
|
||||
},
|
||||
performance_data: {
|
||||
all_txs_insert_time_5: 0,
|
||||
block_processing_time_0: 0,
|
||||
block_processing_time_1: 0,
|
||||
etc_stuff_6: 0,
|
||||
insert_time_4: 0,
|
||||
longhash_calculating_time_3: 0,
|
||||
map_size: 0,
|
||||
raise_block_core_event: 0,
|
||||
target_calculating_calc: 0,
|
||||
target_calculating_enum_blocks: 0,
|
||||
target_calculating_time_2: 0,
|
||||
tx_add_one_tx_time: 0,
|
||||
tx_append_is_expired: 0,
|
||||
tx_append_rl_wait: 0,
|
||||
tx_append_time: 0,
|
||||
tx_check_exist: 0,
|
||||
tx_check_inputs_attachment_check: 0,
|
||||
tx_check_inputs_loop: 0,
|
||||
tx_check_inputs_loop_ch_in_val_sig: 0,
|
||||
tx_check_inputs_loop_kimage_check: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_get_item_size: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_loop: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_loop_find_tx: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_loop_get_subitem: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_loop_handle_output: 0,
|
||||
tx_check_inputs_loop_scan_outputkeys_relative_to_absolute: 0,
|
||||
tx_check_inputs_prefix_hash: 0,
|
||||
tx_check_inputs_time: 0,
|
||||
tx_count: 0,
|
||||
tx_prapare_append: 0,
|
||||
tx_print_log: 0,
|
||||
tx_process_attachment: 0,
|
||||
tx_process_extra: 0,
|
||||
tx_process_inputs: 0,
|
||||
tx_push_global_index: 0,
|
||||
tx_store_db: 0,
|
||||
writer_tx_count: 0
|
||||
},
|
||||
pos_allowed: true,
|
||||
pos_block_ts_shift_vs_actual: 0,
|
||||
pos_diff_total_coins_rate: 72,
|
||||
pos_difficulty: "1338766821857070584",
|
||||
pos_sequence_factor: 0,
|
||||
pow_difficulty: 1282748568346,
|
||||
pow_sequence_factor: 0,
|
||||
seconds_for_10_blocks: 0,
|
||||
seconds_for_30_blocks: 0,
|
||||
status: "OK",
|
||||
synchronization_start_height: 9197,
|
||||
synchronized_connections_count: 17,
|
||||
total_coins: "17535637000000000000",
|
||||
transactions_cnt_per_day: 0,
|
||||
transactions_volume_per_day: 0,
|
||||
tx_count: 7391,
|
||||
tx_count_in_last_block: 0,
|
||||
tx_pool_performance_data: {
|
||||
begin_tx_time: 0,
|
||||
check_inputs_time: 0,
|
||||
check_inputs_types_supported_time: 0,
|
||||
check_keyimages_ws_ms_time: 0,
|
||||
db_commit_time: 0,
|
||||
expiration_validate_time: 0,
|
||||
tx_processing_time: 0,
|
||||
update_db_time: 0,
|
||||
validate_alias_time: 0,
|
||||
validate_amount_time: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default examples;
|
||||
74
src/pages/Aliases/Aliases.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import "../../styles/Aliases.scss";
|
||||
import { useState, useEffect } from "react";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import InfoTopPanel from "../../components/default/InfoTopPanel/InfoTopPanel";
|
||||
import Table from "../../components/default/Table/Table";
|
||||
import Alias from "../../interfaces/state/Alias";
|
||||
import Fetch from "../../utils/methods";
|
||||
|
||||
function Aliases() {
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
const [aliases, setAliases] = useState<Alias[]>([]);
|
||||
|
||||
const [itemsOnPage, setItemsOnPage] = useState("10");
|
||||
const [page, setPage] = useState("1");
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchAliases() {
|
||||
const currentPage = parseInt(page, 10) || 0;
|
||||
const itemsAmount = parseInt(itemsOnPage, 10) || 0;
|
||||
const result = await Fetch.getAliases((currentPage - 1) * itemsAmount, itemsAmount);
|
||||
|
||||
if (result.sucess === false) return;
|
||||
if (!(result instanceof Array)) return;
|
||||
setAliases(
|
||||
result.map((e: any) => ({
|
||||
alias: e.alias || "",
|
||||
address: e.address || ""
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
fetchAliases();
|
||||
|
||||
const id = setInterval(fetchAliases, 20 * 1e3);
|
||||
|
||||
return () => clearInterval(id);
|
||||
}, [itemsOnPage, page]);
|
||||
|
||||
const tableHeaders = [ "NAME", "ADDRESS" ];
|
||||
|
||||
const tableElements = aliases.map(e => [
|
||||
e.alias,
|
||||
e.address
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="aliases">
|
||||
<Header
|
||||
page="Aliases"
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
/>
|
||||
<InfoTopPanel
|
||||
burgerOpened={burgerOpened}
|
||||
title="Aliases"
|
||||
/>
|
||||
<div className="aliases__table custom-scroll">
|
||||
<Table
|
||||
headers={tableHeaders}
|
||||
elements={tableElements}
|
||||
pagination
|
||||
hidePaginationBlock
|
||||
itemsOnPage={itemsOnPage}
|
||||
setItemsOnPage={setItemsOnPage}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Aliases;
|
||||
89
src/pages/AltBlocks/AltBlocks.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import "../../styles/AltBlocks.scss";
|
||||
import { useState, useEffect } from "react";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import InfoTopPanel from "../../components/default/InfoTopPanel/InfoTopPanel";
|
||||
import Table from "../../components/default/Table/Table";
|
||||
import AliasText from "../../components/default/AliasText/AliasText";
|
||||
import Block from "../../interfaces/state/Block";
|
||||
import Fetch from "../../utils/methods";
|
||||
import Utils from "../../utils/utils";
|
||||
|
||||
|
||||
function AltBlocks() {
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
const [altBlocks, setAltBlocks] = useState<Block[]>([]);
|
||||
|
||||
console.log(altBlocks);
|
||||
|
||||
const [itemsOnPage, setItemsOnPage] = useState("10");
|
||||
const [page, setPage] = useState("1");
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchAltBlocks() {
|
||||
const currentPage = parseInt(page, 10) || 0;
|
||||
const itemsAmount = parseInt(itemsOnPage, 10) || 0;
|
||||
const result = await Fetch.getAltBlocksInfo((currentPage - 1) * itemsAmount, itemsAmount);
|
||||
|
||||
if (result.sucess === false) return;
|
||||
if (!(result instanceof Array)) return;
|
||||
setAltBlocks(
|
||||
Utils.transformToBlocks(result, false, true)
|
||||
);
|
||||
}
|
||||
|
||||
fetchAltBlocks();
|
||||
|
||||
const id = setInterval(fetchAltBlocks, 20 * 1e3);
|
||||
|
||||
return () => clearInterval(id);
|
||||
}, [itemsOnPage, page]);
|
||||
|
||||
const tableHeaders = [ "HEIGHT", "TIMESTAMP (UTC)", "ACTIAL TIMESTAMP (UTC)", "SIZE", "TRANSACTIONS", "HASH" ];
|
||||
|
||||
const tableElements = altBlocks.map(e => {
|
||||
const hash = e.hash;
|
||||
const hashLink = hash ? "/alt-blocks/" + hash : "/";
|
||||
|
||||
return [
|
||||
<p>
|
||||
<a href={hashLink}>{e.height}</a>
|
||||
{` (${e.type})`}
|
||||
</p>,
|
||||
Utils.formatTimestampUTC(e.timestamp),
|
||||
Utils.formatTimestampUTC(e.timestamp),
|
||||
`${e.size} bytes`,
|
||||
e.transactions?.toString() || "0",
|
||||
<AliasText href={hashLink}>{e.hash}</AliasText>
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div className="alt_blocks">
|
||||
<Header
|
||||
page="Alt-blocks"
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
/>
|
||||
<InfoTopPanel
|
||||
burgerOpened={burgerOpened}
|
||||
title="Alt-blocks"
|
||||
/>
|
||||
<div className="alt_blocks__table custom-scroll">
|
||||
<Table
|
||||
headers={tableHeaders}
|
||||
elements={tableElements}
|
||||
pagination
|
||||
hidePaginationBlock
|
||||
itemsOnPage={itemsOnPage}
|
||||
setItemsOnPage={setItemsOnPage}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AltBlocks;
|
||||
272
src/pages/Block/Block.tsx
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
import "../../styles/Block.scss";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import InfoTopPanel from "../../components/default/InfoTopPanel/InfoTopPanel";
|
||||
import StatsPanel from "../../components/default/StatsPanel/StatsPanel";
|
||||
import { useEffect, useState } from "react";
|
||||
import Table from "../../components/default/Table/Table";
|
||||
import { ReactComponent as ArrowImg } from "../../assets/images/UI/arrow.svg";
|
||||
import BlockInfo from "../../interfaces/common/BlockInfo";
|
||||
import Utils from "../../utils/utils";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Fetch from "../../utils/methods";
|
||||
|
||||
interface Transaction {
|
||||
hash: string;
|
||||
amount: string;
|
||||
fee: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
function Block(props: { alt?: boolean }) {
|
||||
const { alt } = props;
|
||||
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
const { hash } = useParams();
|
||||
|
||||
const tableHeaders = [ "HASH", "FEE", "TOTAL AMOUNT", "SIZE" ];
|
||||
|
||||
|
||||
const [blockInfo, setBlockInfo] = useState<BlockInfo | null>(null);
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
|
||||
const tableElements = transactions.map(e => [
|
||||
!alt
|
||||
?
|
||||
(
|
||||
<a href={"/transaction/" + (e.hash || "")} className="block__table__hash">
|
||||
{e.hash}
|
||||
</a>
|
||||
)
|
||||
:
|
||||
(
|
||||
<p className="block__table__hash">{e.hash}</p>
|
||||
),
|
||||
e.fee,
|
||||
e.amount,
|
||||
e.size + " bytes"
|
||||
]);
|
||||
|
||||
const [height, setHeight] = useState<number | null>(null);
|
||||
|
||||
const [prevHash, setPrevHash] = useState<string | null>(null);
|
||||
const [nextHash, setNextHash] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchHash() {
|
||||
if (!height) return;
|
||||
const prevHashFetched = await Fetch.getHashByHeight(height - 1);
|
||||
const nextHashFetched = await Fetch.getHashByHeight(height + 1);
|
||||
setPrevHash(prevHashFetched);
|
||||
setNextHash(nextHashFetched);
|
||||
}
|
||||
|
||||
fetchHash();
|
||||
}, [height]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchBlock() {
|
||||
if (!hash) return;
|
||||
setBlockInfo(null);
|
||||
const result = await Fetch.getBlockInfo(hash, alt);
|
||||
|
||||
if (result.success === false) return;
|
||||
|
||||
setHeight(result.height || null);
|
||||
|
||||
setBlockInfo({
|
||||
type: result.type === 1 ? "PoW" : "PoS",
|
||||
timestamp: result.timestamp || undefined,
|
||||
actualTimestamp: result.actual_timestamp || undefined,
|
||||
difficulty: Utils.formatNumber(result.difficulty || "", 0),
|
||||
minerTextInfo: result.miner_text_info || undefined,
|
||||
cummulativeDiffAdjusted: Utils.formatNumber(result.cumulative_diff_adjusted || "", 0),
|
||||
cummulativeDiffPresize: Utils.formatNumber(result.cumulative_diff_precise || "", 0),
|
||||
orphan: result.is_orphan || false,
|
||||
baseReward: Utils.toShiftedNumber(result.base_reward || "0", 12),
|
||||
transactionsFee: Utils.formatNumber(result.total_fee || "0"),
|
||||
rewardPenalty: "",
|
||||
reward: Utils.toShiftedNumber(result.summary_reward || "0", 12),
|
||||
totalBlockSize: result.block_tself_size || undefined,
|
||||
effectiveTxsMedian: undefined,
|
||||
blockFeeMedian: result.this_block_fee_median || undefined,
|
||||
effectiveFeeMedian: Utils.toShiftedNumber(result.effective_fee_median || "0", 12),
|
||||
currentTxsMedian: undefined,
|
||||
transactions: result.tr_count || "0",
|
||||
transactionsSize: result.total_txs_size || "0"
|
||||
});
|
||||
|
||||
const rawTransactionsDetails = result.transactions_details;
|
||||
|
||||
const transactionsDetails =
|
||||
typeof rawTransactionsDetails === "string"
|
||||
? (() => {
|
||||
try {
|
||||
return JSON.parse(rawTransactionsDetails);
|
||||
} catch {}
|
||||
})()
|
||||
: rawTransactionsDetails;
|
||||
|
||||
if (!(transactionsDetails instanceof Array)) return;
|
||||
|
||||
setTransactions(
|
||||
transactionsDetails.map(e => ({
|
||||
hash: e?.id || "",
|
||||
fee: Utils.formatNumber(e?.fee || "0"),
|
||||
amount: Utils.toShiftedNumber(e?.amount?.toString() || "0", 12),
|
||||
size: e?.blob_size || "0"
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
fetchBlock();
|
||||
}, [hash]);
|
||||
|
||||
function BlockInfo() {
|
||||
return (
|
||||
<div className="block__info">
|
||||
<div className="block__info__title">
|
||||
<h2>Zano Block</h2>
|
||||
<div>
|
||||
{!alt && prevHash !== "" &&
|
||||
<a href={prevHash ? "/block/" + prevHash : undefined}>
|
||||
<ArrowImg />
|
||||
</a>
|
||||
}
|
||||
<h2>{height}</h2>
|
||||
{!alt && nextHash !== "" &&
|
||||
<a href={nextHash ? "/block/" + nextHash : undefined}>
|
||||
<ArrowImg />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<p>{hash?.toUpperCase() || ""}</p>
|
||||
</div>
|
||||
<div className="block__info__table">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Type:</td>
|
||||
<td>
|
||||
<span
|
||||
className={`block__info__type ${blockInfo?.type === "PoS" ? "type__pos" : "type__pow"}`}
|
||||
>
|
||||
{blockInfo?.type ?? "-"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Timestamp (UTC):</td>
|
||||
<td>{blockInfo?.timestamp ? Utils.formatTimestampUTC(blockInfo?.timestamp) : "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Actual Timestamp (UTC):</td>
|
||||
<td>{blockInfo?.actualTimestamp ? Utils.formatTimestampUTC(blockInfo?.actualTimestamp) : "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difficulty:</td>
|
||||
<td>{blockInfo?.difficulty ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Miner text info:</td>
|
||||
<td>{blockInfo?.minerTextInfo ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cumulative diff adjusted:</td>
|
||||
<td>{blockInfo?.cummulativeDiffAdjusted ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cumulative diff presize:</td>
|
||||
<td>{blockInfo?.cummulativeDiffPresize ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Orphan:</td>
|
||||
<td>{blockInfo?.orphan ? "yes" : "no"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Base reward:</td>
|
||||
<td>{blockInfo?.baseReward ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transactions fee:</td>
|
||||
<td>{blockInfo?.transactionsFee ?? "-"}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Reward penalty:</td>
|
||||
<td>{blockInfo?.rewardPenalty ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reward:</td>
|
||||
<td>{blockInfo?.reward || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total block size, bytes:</td>
|
||||
<td>{blockInfo?.totalBlockSize ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Effective txs median, bytes:</td>
|
||||
<td>{blockInfo?.effectiveTxsMedian ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>This block fee median</td>
|
||||
<td>{blockInfo?.blockFeeMedian ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Effective fee median</td>
|
||||
<td>{blockInfo?.effectiveFeeMedian ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current txs median, bytes:</td>
|
||||
<td>{blockInfo?.currentTxsMedian ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transactions:</td>
|
||||
<td>{blockInfo?.transactions ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total transactions size, bytes:</td>
|
||||
<td>{blockInfo?.transactionsSize ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Seed</td>
|
||||
<td>{blockInfo?.seed ?? ""}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="block">
|
||||
<Header
|
||||
page="Blockchain"
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
/>
|
||||
<InfoTopPanel
|
||||
burgerOpened={burgerOpened}
|
||||
title=""
|
||||
back
|
||||
className="block__info__top"
|
||||
/>
|
||||
<StatsPanel onlyBottom />
|
||||
<BlockInfo />
|
||||
<div className="block__transactions">
|
||||
<h2>Transactions</h2>
|
||||
<Table
|
||||
headers={tableHeaders}
|
||||
elements={tableElements}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Block;
|
||||
51
src/pages/Blockchain/Blockchain.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import "../../styles/Blockchain.scss";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import StatsPanel from "../../components/default/StatsPanel/StatsPanel";
|
||||
import InfoTopPanel from "../../components/default/InfoTopPanel/InfoTopPanel";
|
||||
import LatestBlocks from "./components/LatestBlocks/LatestBlocks";
|
||||
import TransactionPool from "./components/TransactionPool/TransactionPool";
|
||||
import { useEffect, useState } from "react";
|
||||
import Fetch from "../../utils/methods";
|
||||
import VisibilityInfo from "../../interfaces/state/VisibilityInfo";
|
||||
|
||||
function Blockchain() {
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
const [visibliltyInfo, setVisibilityInfo] = useState<VisibilityInfo | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchVisibilityInfo() {
|
||||
const result = await Fetch.getVisibilityInfo();
|
||||
if (result.success === false) return;
|
||||
setVisibilityInfo(result);
|
||||
}
|
||||
|
||||
fetchVisibilityInfo();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="blockchain">
|
||||
<Header
|
||||
page="Blockchain"
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
/>
|
||||
<InfoTopPanel
|
||||
burgerOpened={burgerOpened}
|
||||
title="Blockchain"
|
||||
content={
|
||||
<div className="info__top__daemon">
|
||||
<p>Daemon state: Online</p>
|
||||
<p>Default network fee: 0,01</p>
|
||||
<p>Minimum network fee: 0,01</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<StatsPanel visibilityInfo={visibliltyInfo} />
|
||||
<LatestBlocks />
|
||||
<TransactionPool />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Blockchain;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.blockchain__latest_blocks {
|
||||
background-color: #234ee21a;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
h3 {
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 761px) {
|
||||
overflow-x: auto;
|
||||
|
||||
> :last-child {
|
||||
width: 850px;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/pages/Blockchain/components/LatestBlocks/LatestBlocks.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import AliasText from "../../../../components/default/AliasText/AliasText";
|
||||
import Table from "../../../../components/default/Table/Table";
|
||||
import Block from "../../../../interfaces/state/Block";
|
||||
import Info from "../../../../interfaces/state/Info";
|
||||
import Fetch from "../../../../utils/methods";
|
||||
import Utils from "../../../../utils/utils";
|
||||
import "./LatestBlocks.scss";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
function LatestBlocks() {
|
||||
|
||||
const [info, setInfo] = useState<Info | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchInfo() {
|
||||
const result = await Fetch.getInfo();
|
||||
if (result.success === false) return;
|
||||
if (!result.height) return;
|
||||
setInfo(result);
|
||||
}
|
||||
|
||||
fetchInfo();
|
||||
}, []);
|
||||
|
||||
|
||||
const [blocks, setBlocks] = useState<Block[]>([]);
|
||||
|
||||
const [itemsOnPage, setItemsOnPage] = useState("10");
|
||||
const [page, setPage] = useState("1");
|
||||
const [goToBlock, setGoToBlock] = useState("");
|
||||
|
||||
console.log(itemsOnPage, page, goToBlock);
|
||||
|
||||
function onGoToBlockEnter() {
|
||||
if (!goToBlock || !itemsOnPage) return;
|
||||
const goToHeight = parseInt(goToBlock || "0", 10);
|
||||
const itemsParsed = parseInt(itemsOnPage || "0", 10);
|
||||
if (!info) return;
|
||||
const { height } = info;
|
||||
if (goToHeight > height - 1) return;
|
||||
const offset = height - goToHeight;
|
||||
const newPage = Math.ceil(offset / itemsParsed);
|
||||
setPage(newPage.toString());
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchBlocks() {
|
||||
const items = parseInt(itemsOnPage, 10) || 0;
|
||||
const pageNumber = parseInt(page, 10) || 0;
|
||||
if (pageNumber === 0) return;
|
||||
if (!info) return;
|
||||
const { height } = info;
|
||||
const result = await Fetch.getBlockDetails(height - items * pageNumber, items);
|
||||
if (result.sucess === false) return;
|
||||
if (!(result instanceof Array)) return;
|
||||
setBlocks(
|
||||
Utils.transformToBlocks(result, true)
|
||||
);
|
||||
}
|
||||
|
||||
fetchBlocks();
|
||||
const id = setInterval(fetchBlocks, 20 * 1e3);
|
||||
return () => clearInterval(id);
|
||||
}, [info, itemsOnPage, page]);
|
||||
|
||||
const tableHeaders = [ "HEIGHT", "TIMESTAMP (UTC)", "AGE", "SIZE", "TRANSACTIONS", "HASH" ];
|
||||
|
||||
const tableElements = blocks.map(e => {
|
||||
const hash = e.hash;
|
||||
const hashLink = hash ? "/block/" + hash : "/";
|
||||
return [
|
||||
<p>
|
||||
<a href={hashLink}>{e.height}</a>
|
||||
{` (${e.type})`}
|
||||
</p>,
|
||||
Utils.formatTimestampUTC(e.timestamp),
|
||||
Utils.timeElapsedString(e.timestamp),
|
||||
`${e.size} bytes`,
|
||||
e.transactions?.toString() || "0",
|
||||
<AliasText href={hashLink}>{hash}</AliasText>
|
||||
]
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="blockchain__latest_blocks custom-scroll">
|
||||
<h3>Latest Blocks</h3>
|
||||
<Table
|
||||
pagination
|
||||
headers={tableHeaders}
|
||||
elements={tableElements}
|
||||
itemsOnPage={itemsOnPage}
|
||||
setItemsOnPage={setItemsOnPage}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
goToBlock={goToBlock}
|
||||
setGoToBlock={setGoToBlock}
|
||||
goToBlockEnter={onGoToBlockEnter}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LatestBlocks;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
.transaction_pool {
|
||||
background-color: #234ee21a;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.transation_pool__title {
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.transation_pool__empty {
|
||||
height: 125px;
|
||||
padding-bottom: 25px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
color: #9eaacc;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 761px) {
|
||||
overflow-x: auto;
|
||||
|
||||
> .transaction__table {
|
||||
width: 850px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import "./TransactionPool.scss";
|
||||
import { useState } from "react";
|
||||
import Button from "../../../../components/UI/Button/Button";
|
||||
import Table from "../../../../components/default/Table/Table";
|
||||
import AliasText from "../../../../components/default/AliasText/AliasText";
|
||||
|
||||
function TransactionPool() {
|
||||
|
||||
const [turnedOn, setTurnedOn] = useState(false);
|
||||
|
||||
const tableHeaders = [ "TIMESTAMP (UTC)", "AGE", "SIZE", "FEE", "HASH" ];
|
||||
|
||||
const tableElements = Array(10).fill(
|
||||
[
|
||||
"2023-10-07 10:01:51",
|
||||
"3 minutes ago",
|
||||
"0 bytes",
|
||||
"0.01",
|
||||
<AliasText href="/">b861efc1ad2007b4af32e21c4e3edf777c08ccb9632cad7343ea6c94d1245f17</AliasText>
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="transaction_pool custom-scroll">
|
||||
<div className="transation_pool__title">
|
||||
<h3>Transaction Pool</h3>
|
||||
<Button
|
||||
style={ turnedOn ? { color: "#ff5252" } : { color: "#00c853" }}
|
||||
onClick={() => setTurnedOn(!turnedOn)}
|
||||
>
|
||||
{turnedOn ? "TURN OFF" : "TURN ON"}
|
||||
</Button>
|
||||
</div>
|
||||
{!turnedOn ?
|
||||
<div className="transation_pool__empty">
|
||||
<p>Pool is empty</p>
|
||||
</div> :
|
||||
<Table
|
||||
className="transaction__table"
|
||||
headers={tableHeaders}
|
||||
elements={tableElements}
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransactionPool;
|
||||
178
src/pages/Transaction/Transaction.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import "../../styles/Transaction.scss";
|
||||
import { useEffect, useState } from "react";
|
||||
import Header from "../../components/default/Header/Header";
|
||||
import InfoTopPanel from "../../components/default/InfoTopPanel/InfoTopPanel";
|
||||
import StatsPanel from "../../components/default/StatsPanel/StatsPanel";
|
||||
import Table from "../../components/default/Table/Table";
|
||||
import TransactionInfo from "../../interfaces/common/TransactionInfo";
|
||||
import Fetch from "../../utils/methods";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Utils from "../../utils/utils";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
interface Block {
|
||||
hash: string;
|
||||
height: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Transaction() {
|
||||
const [burgerOpened, setBurgerOpened] = useState(false);
|
||||
|
||||
const [transactionInfo, setTransactionInfo] = useState<TransactionInfo | null>(null);
|
||||
const [blockOrigin, setBlockOrigin] = useState<Block | null>(null);
|
||||
|
||||
const { hash } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTransaction() {
|
||||
if (!hash) return;
|
||||
const result = await Fetch.getTransaction(hash);
|
||||
if (result.success === false) return;
|
||||
if (!(typeof result === "object")) return;
|
||||
const newTransactionInfo: TransactionInfo = {
|
||||
hash: result.id || "",
|
||||
amount: Utils.toShiftedNumber(result.amount || "0", 12),
|
||||
fee: Utils.toShiftedNumber(result.fee || "0", 0),
|
||||
size: result.block_size || "0",
|
||||
confirmations: "-",
|
||||
publicKey: result.pub_key || "-",
|
||||
mixin: "-",
|
||||
extraItems: [],
|
||||
attachments: undefined
|
||||
}
|
||||
setBlockOrigin({
|
||||
hash: result.block_hash || "",
|
||||
height: Utils.formatNumber(result.keeper_block || "0", 0),
|
||||
timestamp: result.timestamp || ""
|
||||
});
|
||||
|
||||
try {
|
||||
const parsedExtraItems = JSON.parse(result.extra);
|
||||
if (parsedExtraItems instanceof Array) {
|
||||
newTransactionInfo.extraItems = parsedExtraItems.map(e => {
|
||||
return `(${e.type || ""}) ${e.short_view || ""}`;
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
|
||||
setTransactionInfo(newTransactionInfo);
|
||||
}
|
||||
|
||||
fetchTransaction();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="transaction">
|
||||
<Header
|
||||
burgerOpened={burgerOpened}
|
||||
setBurgerOpened={setBurgerOpened}
|
||||
page="Blockchain"
|
||||
/>
|
||||
<InfoTopPanel
|
||||
burgerOpened={burgerOpened}
|
||||
title=""
|
||||
back
|
||||
className="block__info__top"
|
||||
/>
|
||||
<StatsPanel onlyBottom />
|
||||
<div className="transaction__info">
|
||||
<h2>Transaction</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hash</td>
|
||||
<td>{transactionInfo?.hash || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>{transactionInfo?.amount || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fee</td>
|
||||
<td>{transactionInfo?.fee || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Size</td>
|
||||
<td>{(transactionInfo?.size || "0") + " bytes"}</td>
|
||||
</tr>
|
||||
<tr className="transaction__confirmation">
|
||||
<td>Confirmations</td>
|
||||
<td>{transactionInfo?.confirmations || "0"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>One-time public key</td>
|
||||
<td>{transactionInfo?.publicKey || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mixin</td>
|
||||
<td>{transactionInfo?.mixin || "-"}</td>
|
||||
</tr>
|
||||
<tr className="transaction__extra_items">
|
||||
<td>Extra items</td>
|
||||
<td>
|
||||
{
|
||||
transactionInfo?.extraItems.map((e, i) =>
|
||||
<p key={nanoid(16)}>
|
||||
{`[${i + 1}] ` + e}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Attachments</td>
|
||||
<td>{transactionInfo?.attachments || "-"}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="transaction__table__wrapper">
|
||||
<h3>From Block</h3>
|
||||
<Table
|
||||
className="custom-scroll"
|
||||
headers={[ "HASH", "HEIGHT", "TIMESTAMP (UTC)" ]}
|
||||
elements={[[
|
||||
<a
|
||||
className="table__hash"
|
||||
href={blockOrigin?.hash ? "/block/" + blockOrigin.hash : undefined}
|
||||
>
|
||||
{blockOrigin?.hash}
|
||||
</a>,
|
||||
blockOrigin?.height || "",
|
||||
blockOrigin?.timestamp ? Utils.formatTimestampUTC(parseInt(blockOrigin.timestamp, 10)) : ""
|
||||
]]}
|
||||
/>
|
||||
</div>
|
||||
<div className="transaction__table__wrapper">
|
||||
<h3>Inputs ( 2 )</h3>
|
||||
<Table
|
||||
className="custom-scroll"
|
||||
headers={[ "AMOUNT", "IMAGE / MULTISIG ID", "MIXIN COUNT", "GLOBAL INDEX" ]}
|
||||
elements={[[
|
||||
"1.00",
|
||||
"a32ef806cc193cbe07e1cf7cff27e3a9609bae44e57e5999f46b69ffd9366a57",
|
||||
"1",
|
||||
<a className="table__hash" href="/block/1234">13496</a>,
|
||||
]]}
|
||||
/>
|
||||
</div>
|
||||
<div className="transaction__table__wrapper">
|
||||
<h3>Outputs ( 2 )</h3>
|
||||
<Table
|
||||
className="custom-scroll"
|
||||
headers={[ "AMOUNT", "KEY", "GLOBAL INDEX / MULTISIG ID" ]}
|
||||
elements={[[
|
||||
"1.00",
|
||||
"048a327cb79739b33f6859b3a3082b6beafd380f1570b62e4598959442cd8641 [SPENT]",
|
||||
"14124"
|
||||
]]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Transaction;
|
||||
1
src/react-app-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
||||
15
src/reportWebVitals.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
10
src/setupProxy.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||
module.exports = function (app) {
|
||||
app.use(
|
||||
["/proxy"],
|
||||
createProxyMiddleware({
|
||||
target: "http://127.0.0.1:3005",
|
||||
changeOrigin: true
|
||||
})
|
||||
);
|
||||
};
|
||||
95
src/styles/API.scss
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
.api {
|
||||
.api__title {
|
||||
height: 77px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
color: #9eaacc;
|
||||
}
|
||||
}
|
||||
|
||||
.api__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
|
||||
.api__item {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background-color: #234ee21a;
|
||||
|
||||
|
||||
.api__item__title {
|
||||
padding: 18px 20px;
|
||||
background: linear-gradient(to right,#1b3a8a,#32439f);
|
||||
|
||||
> h3 {
|
||||
font-size: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.api__item__units {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.api__item__unit {
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: #263163;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
p, a {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #68a1ff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api__item__json {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
|
||||
.item__json__element {
|
||||
margin-left: 25px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: "Inconsolata";
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.item__json__number {
|
||||
color: #009688;
|
||||
}
|
||||
|
||||
.item__json__container {
|
||||
> :first-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/styles/Aliases.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.aliases {
|
||||
.aliases__table {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
overflow-x: auto;
|
||||
|
||||
> :first-child {
|
||||
width: 850px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/styles/AltBlocks.scss
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.alt_blocks {
|
||||
.alt_blocks__table {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
overflow-x: auto;
|
||||
|
||||
> :first-child {
|
||||
width: 850px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/styles/Block.scss
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
.block {
|
||||
.block__transactions {
|
||||
h2 {
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> div {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
td {
|
||||
width: initial !important;
|
||||
}
|
||||
|
||||
th {
|
||||
white-space: unset;
|
||||
}
|
||||
}
|
||||
|
||||
> :not(:first-child, :nth-child(2)) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.block__table__hash {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 660px) {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
max-width: 65px;
|
||||
}
|
||||
}
|
||||
|
||||
.block__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
|
||||
.block__info__title {
|
||||
display: flex;
|
||||
column-gap: 25px;
|
||||
row-gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
> a:first-child svg {
|
||||
transform: rotateY(180deg)
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: #9eaacc;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.block__info__table {
|
||||
display: flex;
|
||||
|
||||
.block__info__type {
|
||||
padding: 2px 12px;
|
||||
border-radius: 100px;
|
||||
font-size: 14px;
|
||||
|
||||
&.type__pos {
|
||||
background-color: #0c69fe;
|
||||
}
|
||||
|
||||
&.type__pow {
|
||||
background-color: #47effbb3;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
background-color: #234ee21a;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
|
||||
&:first-child {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
table tbody {
|
||||
tr {
|
||||
height: 38px;
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
padding: 12px 20px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
background-color: #263163;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 760px) {
|
||||
flex-direction: column;
|
||||
|
||||
table tbody tr td {
|
||||
white-space: initial;
|
||||
|
||||
&:last-child {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @media screen and (max-width: 500px) {
|
||||
// table tbody tr td:last-child {
|
||||
// width: 40%;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/styles/Blockchain.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.blockchain {
|
||||
> :not(:first-child, :nth-child(2)) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.info__top__daemon {
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
|
||||
> p {
|
||||
font-size: 14px;
|
||||
color: #9eaacc;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 991px) {
|
||||
> p:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/styles/Transaction.scss
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
.transaction {
|
||||
> :not(:first-child, :nth-child(2)) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.transaction__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
td {
|
||||
padding: 12px 20px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
&:nth-child(2n) {
|
||||
background-color: #234ee21a;
|
||||
}
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
background-color: #263163;
|
||||
}
|
||||
|
||||
&.transaction__confirmation {
|
||||
background-color: #0e5311;
|
||||
}
|
||||
|
||||
&.transaction__extra_items td:last-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transaction__table__wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 13px;
|
||||
|
||||
> :last-child {
|
||||
background-color: #234ee21a;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table tbody tr td {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.table__hash {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
@media screen and (max-width: 800px) {
|
||||
overflow-x: auto;
|
||||
|
||||
table {
|
||||
width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||