Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2dd48be65 | ||
|
|
bd8268d848 | ||
|
|
4ca691608a | ||
|
|
5939bb4e13 | ||
|
|
68e665804c | ||
|
|
2efc3acd94 | ||
|
|
428153a9d9 | ||
|
|
78f48b240b | ||
|
|
8bf83aaa5b | ||
|
|
e0753a0779 | ||
|
|
9c331789b3 | ||
|
|
3e8a0e2233 | ||
|
|
a8bb75b2f5 | ||
|
|
8e4b6c832e | ||
|
|
0927c0dca5 | ||
|
|
a555d18417 | ||
|
|
ea4d8a4049 | ||
|
|
1448d3e7da | ||
|
|
e07353cd87 | ||
|
|
2210b4ab91 | ||
|
|
8e5c0a3edc | ||
|
|
4f73075cf9 | ||
|
|
b68ef748ca | ||
|
|
200f4f58a7 | ||
|
|
86a6bf013d | ||
|
|
6afa08e5f5 | ||
|
|
3173ec9342 | ||
|
|
c8352e6537 | ||
|
|
9fbc6423d1 | ||
|
|
e4f691ab78 | ||
|
|
446276fadf | ||
|
|
1b574ac347 | ||
|
|
0c7db9cd30 | ||
|
|
fc977cc43a | ||
|
|
0a0151d1dd | ||
|
|
369f0e375c | ||
|
|
79ca03823c | ||
|
|
49f3ee17a0 | ||
|
|
841e82e233 | ||
|
|
2212ea7433 | ||
|
|
f257066d5a | ||
|
|
027bf0d405 |
64
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Build and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- '*'
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- '*'
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
type: choice
|
||||
options:
|
||||
- info
|
||||
- warning
|
||||
- debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Get branch data
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: kandrusyak/blockchain-ui:master
|
||||
deploy:
|
||||
needs: build
|
||||
name: Deploy to host
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Get branch data
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Deploying changes
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: ${{ secrets.SSH_USERNAME }}
|
||||
password: ${{ secrets.SSH_PASSWORD }}
|
||||
script: |
|
||||
docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
./update.sh
|
||||
19
.prettierrc
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "lf",
|
||||
"jsxBracketSameLine": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"singleQuote": false,
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
FROM node:16-alpine AS builder
|
||||
|
||||
RUN apk --update add --no-cache curl git python3 alpine-sdk bash autoconf libtool automake
|
||||
|
||||
WORKDIR /front
|
||||
|
||||
COPY . .
|
||||
|
||||
BIN
dist/assets/00aab12feba6fab9c8e5cb94b7bab85c.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/assets/0d7b13bc31f623e46ce6d318d72b8fa0.jpg
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/assets/337193482a6fc165c3e101c4cd45864b.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
dist/assets/5bdc905a0d21817d9e9cf280f7464f6d.png
vendored
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
dist/assets/6e225e2cab53b1738e4fe13130e1ad4b.jpg
vendored
Normal file
|
After Width: | Height: | Size: 916 B |
BIN
dist/assets/9668c24210a4bf2fcbd7bd5570e95795.jpg
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
dist/assets/9c6b3ebd646e88131f55cd0e4de66a67.png
vendored
Normal file
|
After Width: | Height: | Size: 912 B |
BIN
dist/assets/a7c6e3bab1dca3610a3f01816c09b150.png
vendored
Normal file
|
After Width: | Height: | Size: 623 B |
4
dist/assets/adcfdc4f4455c26d9153b599b1475b09.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle fill="#0EA5E9" cx="18" cy="18" r="18" />
|
||||
<path d="M18 26a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8Zm0-14a6 6 0 1 0 0 12 6 6 0 0 0 0-12Z" fill="#F0F9FF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 258 B |
BIN
dist/assets/d33927970705d24be4be69220dbc3ad0.jpg
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
4
dist/assets/d53e2b56cd0cdf928c678711f37177ab.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle fill="#6366F1" cx="18" cy="18" r="18" />
|
||||
<path d="M24.446 19.335a2.5 2.5 0 00-3.522 3.194c-.845.63-1.87.97-2.924.971a4.979 4.979 0 01-1.113-.135 4.436 4.436 0 01-1.343 1.682 6.91 6.91 0 006.9-1.165 2.5 2.5 0 002-4.547h.002zM20.431 11.938a2.5 2.5 0 10-.4 2.014 5.027 5.027 0 012.723 3.078c.148-.018.297-.028.446-.03a4.5 4.5 0 011.7.334 7.023 7.023 0 00-4.469-5.396zM14.969 20.25a2.49 2.49 0 00-1.932-1.234A4.624 4.624 0 0113 18.5a4.97 4.97 0 011.348-3.391 4.456 4.456 0 01-.788-2.016A6.989 6.989 0 0011 18.5c.003.391.04.781.11 1.166a2.5 2.5 0 103.859.584z" fill="#EEF2FF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 683 B |
10
dist/assets/e9b097e399651f55a5ea32dac11e8b48.svg
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<circle cx="18" cy="18" r="18" fill="#E1E1F7"/>
|
||||
<rect x="6" y="6" width="24" height="24" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_21_1525" transform="scale(0.0125)"/>
|
||||
</pattern>
|
||||
<image id="image0_21_1525" width="80" height="80" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAUKADAAQAAAABAAAAUAAAAAAx4ExPAAAHHUlEQVR4Ae1bWYwVRRRlwBGXaBRxRQaMGvDDiAG/lCigjHEDE/VHPox8uGCi4o8mij/G6Iea6JcJ0TCSEDVKYhC3EOPglhgMGoMxKswQBRWXuG8snjNTd7xTr+u9qq7q9/pp3eRQ1bfvPffW7br9+r0eJkzIkiuQK5ArkCuQK5ArkCuQK5ArkCuQK5ArkCvw/6pAT5uWOw1xLgQWAWcCUw3+xrgL2ApsAJ4B/gJccixOLAb6gdnACQB1oTxw6Q45G2k+DewFDnhgGDaXA7acAcU6YB8Qw2Pz1vZ4MjJ7TC32d8yfB24B5gLckbQ5AmBxbgC2ACzOfuA2gNILPAroC8CdeitwDjAdeAt4FyDPjYDmoV3XyYnIWBbBwj0AsNVaCW8nKwEWkFgGvAGwqH8C24BBwJbNUGg9eW4HyMEd2w90jXBHvQ9w0Z8Ac4BQuQcOuk3/wPF5oSSwF54fMX8N4MU5Eqi1vIDsuHjuliklM50EvyFAF/ErHJ8LiLBtufNs0XofHtu/o8eXIDoXvQfoS5DJ4eBYAGwCyPsLMBug2G07qi3Wc9dtBHYDNo/4dXzkfecjk+CKxNlwJw0Y7rcjuMmzNgFPRApu13kmsSGMB7nNSp/hbmQbcwfpViahblseixTpuRv5wVbEI35B48Qga7fxEnNqPUY+cqSWX0G42pAutcj5acuC2FKk/wlG7xlDm8f2b+uxPG7wW0JVsgjELFRMGzO3VDzkSiafgomLOy0ZYyPRSSbGFxiL2pMePnrNQ58oSdXC8qDM+1RV8oMh5uNRUXvytI/+e8Vjpp0f5MZ8aIWpHAZu7nLeD2PkYDin4BnJIdUObMdVlQdz2YlSRJ+2FVuOfCak2Dyj2sB/UxVQkjkmMH6IuXDLxRJfn7YVW47ylGDzaJu2zzcjItviggojy6fn65Exzoc/c43lGUkj1Q78zixKdok5TDoIN3dOaNtq+2kmqyQ7MFUBd5qkTk5asvFk080hY4W2rbaXHCXn8VECj1J97Ro2cWcExg8xF+4hOPH3viKZX6SETuvlQgw5bIPUqXbgDhN1ZlD0MOM+Yy4XS7x1e4qOo0svxbR5tK/3PFUBvzQRZ3pHDjeUHbjLctXtqU+59EcZI5tH+7Z9/iwiHgA+rzDydhODb+5iJBVPTA4Nvj9DwwLyfUhVQm7GYCxXe/roNU90rqlaWD7RTo/OyE0g3Izlak8fveZxR2vzmUcQj7tjVYVxyc0YD0fGSMUTmcZ4d3kfwgUKxluUPxI+GS+2qHzali7iL6PNY9H6HaZqYX5Bvx+Q78SMzl89YkVzkPs+4GWL1Kdte5WPi0eZdHb6McLzKp+SII1TDRdfk8bIDDgzp1iehhxS7UBNLEnO0sqSc+EQTqHxbVuxf9xMbB45X3qsooDvmGyuKZ3Vv47CIZxyxqdtxZZjnzmwebRNbeb8eZ9/osY3YMdFZHU8fPnMRy7Oywpz4Ev5WJ6y8Uv5DcCL95znSnmPOq03HGsMR2jbiv06i8fQ1XvgHz3uMYnfXSLVe43vNxinGn/+aDto5npopudFJDSP9q31vB/Z8S+ruICHAjLlgzJ96HtRgJ9t+iAUKXhs3rYeX2YWwYX4Cm2JSwFpQ9vXR695bP9kx1V8CuvkNuiDwPmLsA/9tC2yJ09Xi+wE30WE2rt4U/G4+Ef0Ve/ApsEDT/q0bSBlvHmVBeTD66qIFOlLDpH9mHBX2eLSi53NI/pajj3Iir/MvALsA6SNQt4/0Fb8yPESUObT2OZhTsyNOdZO+HbvWuADQBbPv2FZAywAQnY6bekzAJBD+LZivgzoBXzamTwLAfLILiUXc2Suqd5Igqq8TIbrCmAHIAv9DPOVgLy8wbS0kOMOgO9ahH8Y873Aq4AtzR6qt8B4JyA82zFn7lxDR+RKRNUL4w65CuCVTy3kvBpgDF0AxgsR/r54HbANEB6ugWtpm8xCpE2AJPAh5lcAPUDVwhiMxZgSfzfm3Hm2NGtznlsKaB6uiWurVHgPkjdw32J+EzCp0ojF5Ix5M8AcWEje45YDWljUQa0wc623ebg23h+TC+8TqwG56k9hfnTyKOGEU+CyFpC8nsD8kHCakf8UpHm4Vq45iTAhPkowSX4qXg/UTZYjIfnEfhNz/S6FuTZrZ+5IEc2zEcoyF0O4xkZeVRbva+CsMW39JsyNOTJX7iAtLFKrdhb7Zjxi4z32w5IJ/QbUuXiyoDkmV+a8UJQlRs2zuIT/mAtfITKZO8c09Z/cZXLmT/i6PSVz33YWHt6+nDLReWb0xFxz/skWdnU6Lbn2IilefFv4ie2jF555NkHIMQMVBQvh6IRtqrxb8tTi+2AbKsy25c6bb8VqpbfMGw9bfXvoxt3XuMp4jbNOre6B8aEzQ65ArkCuQK5ArkCuQK5ArkCuQK5ArkCuQK5ArsB/qgL/AIxZGoBhTSTCAAAAAElFTkSuQmCC"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
BIN
dist/assets/f2a7edc970be4aa58ba65c495ab16ed0.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
dist/favicon.ico
vendored
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
1
dist/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><title>React App</title><link rel="icon" href="favicon.ico"><script defer="defer" src="main.3ab1a28d.js"></script></head><body><div id="root"></div><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-M6E6PDB6Y4")</script><script async src="https://www.googletagmanager.com/gtag/js?id=G-M6E6PDB6Y4"></script></body></html>
|
||||
8
dist/main.3ab1a28d.js
vendored
Normal file
96
dist/main.3ab1a28d.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! https://mths.be/cssesc v3.0.0 by @mathias */
|
||||
|
||||
/*! https://mths.be/punycode v1.3.2 by @mathias */
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/**
|
||||
* @license qrcode.react
|
||||
* Copyright (c) Paul O'Shannessy
|
||||
* SPDX-License-Identifier: ISC
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.3.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.3.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license URI.js v4.4.1 (c) 2011 Gary Court. License: http://github.com/garycourt/uri-js */
|
||||
|
||||
//! moment.js
|
||||
|
||||
//! moment.js locale configuration
|
||||
3
dist/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"API_URL": "https://tn-alpha.idntty.org:8080/api/"
|
||||
}
|
||||
29911
package-lock.json
generated
130
package.json
@@ -1,62 +1,68 @@
|
||||
{
|
||||
"name": "mosaic-react",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server",
|
||||
"build:dev": "webpack --mode development --progress",
|
||||
"build:prod": "webpack --mode production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@liskhq/lisk-client": "^5.2.2",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chart.js": "^3.5.0",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"mobx": "^6.6.0",
|
||||
"mobx-react-lite": "^3.4.0",
|
||||
"moment": "^2.29.1",
|
||||
"net": "^1.0.2",
|
||||
"os-browserify": "^0.3.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"process": "^0.11.10",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-flatpickr": "^3.10.7",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"url": "^0.11.0",
|
||||
"util": "^0.12.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.5",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"babel-loader": "^8.2.5",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"html-loader": "^3.1.2",
|
||||
"postcss": "^8.4.5",
|
||||
"sass": "^1.52.3",
|
||||
"sass-loader": "^13.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"postcss-loader": "^7.0.0",
|
||||
"tailwindcss": "^3.0.18",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.2"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/env",
|
||||
"@babel/react"
|
||||
]
|
||||
},
|
||||
"browser": {
|
||||
"fs": false
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "mosaic-react",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --https",
|
||||
"build:dev": "webpack --mode development --progress",
|
||||
"build:prod": "webpack --mode production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@liskhq/lisk-client": "^5.2.2",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"chart.js": "^3.5.0",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"classnames": "^2.3.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"mobx": "^6.6.0",
|
||||
"mobx-react-lite": "^3.4.0",
|
||||
"moment": "^2.29.1",
|
||||
"net": "^1.0.2",
|
||||
"os-browserify": "^0.3.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.8.0",
|
||||
"process": "^0.11.10",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-country-flag": "^3.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-flatpickr": "^3.10.7",
|
||||
"react-loader-spinner": "^5.2.0",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"sodium-universal": "^3.1.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"url": "^0.11.0",
|
||||
"util": "^0.12.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.5",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"autoprefixer": "^10.4.5",
|
||||
"babel-loader": "^8.2.5",
|
||||
"css-loader": "^6.7.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.1.2",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss-loader": "^7.0.0",
|
||||
"sass": "^1.52.3",
|
||||
"sass-loader": "^13.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.0.18",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.2"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/env",
|
||||
"@babel/react"
|
||||
]
|
||||
},
|
||||
"browser": {
|
||||
"fs": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-M6E6PDB6Y4');
|
||||
</script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-M6E6PDB6Y4"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
3
public/settings.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"API_URL": "https://tn-alpha.idntty.org:8080/api/"
|
||||
}
|
||||
58
src/App.jsx
@@ -1,55 +1,63 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
useLocation,
|
||||
} from 'react-router-dom';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
|
||||
import './css/style.scss';
|
||||
|
||||
// Import pages
|
||||
import ProfileId from './pages/digitalId/ProfileId';
|
||||
import Validate from './pages/digitalId/Validate';
|
||||
import ValidationLog from './pages/digitalId/ValidationLog';
|
||||
import Verify from './pages/digitalId/Verify';
|
||||
import Onboarding1 from "./pages/Onboarding1";
|
||||
import Onboarding2 from "./pages/Onboarding2";
|
||||
import Onboarding3 from "./pages/Onboarding3";
|
||||
import Onboarding4 from "./pages/Onboarding4";
|
||||
import SharedData from "./pages/SharedData";
|
||||
import ResetPassword from "./pages/ResetPassword";
|
||||
import SignIn from "./pages/SignIn";
|
||||
|
||||
function App() {
|
||||
import Onboarding1 from './pages/Onboarding1';
|
||||
import Onboarding2 from './pages/Onboarding2';
|
||||
import Onboarding3 from './pages/Onboarding3';
|
||||
import Onboarding4 from './pages/Onboarding4';
|
||||
import SharedData from './pages/SharedData';
|
||||
import SignIn from './pages/SignIn';
|
||||
import { LoadingOverlay } from './components/LoadingOverlay';
|
||||
import { store } from './store/store';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import Delegates from './pages/services/Delegates';
|
||||
import VPNServers from './pages/services/VPNServers';
|
||||
import VPNPrepare from './pages/services/VPNPrepare';
|
||||
import { NotificationContainer } from './components/NotificationContainer';
|
||||
|
||||
const App = observer(() => {
|
||||
const location = useLocation();
|
||||
useEffect(()=> {
|
||||
localStorage.setItem('svgAvatar', '')
|
||||
},[])
|
||||
useEffect(() => {
|
||||
localStorage.setItem('svgAvatar', '');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelector('html').style.scrollBehavior = 'auto'
|
||||
window.scroll({ top: 0 })
|
||||
document.querySelector('html').style.scrollBehavior = ''
|
||||
document.querySelector('html').style.scrollBehavior = 'auto';
|
||||
window.scroll({ top: 0 });
|
||||
document.querySelector('html').style.scrollBehavior = '';
|
||||
}, [location.pathname]); // triggered on route change
|
||||
|
||||
if (!store.nodeInfo.networkIdentifier) {
|
||||
return <LoadingOverlay />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{store.loading && <LoadingOverlay />}
|
||||
<NotificationContainer />
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Onboarding1 />} />
|
||||
<Route path="/digitalId/profile-id" element={<ProfileId />} />
|
||||
<Route path="/digitalId/validate" element={<Validate />} />
|
||||
{/*<Route path="/digitalId/validate" element={<Validate />} />*/}
|
||||
<Route path="/digitalId/validation-log" element={<ValidationLog />} />
|
||||
<Route path="/digitalId/verify" element={<Verify />} />
|
||||
<Route path="/onboarding-2" element={<Onboarding2 />} />
|
||||
<Route path="/onboarding-3" element={<Onboarding3 />} />
|
||||
<Route path="/onboarding-4" element={<Onboarding4 />} />
|
||||
<Route path="/shared-data" element={<SharedData />} />
|
||||
<Route path="/reset-password" element={<ResetPassword />} />
|
||||
<Route path="/services/delegates" element={<Delegates />} />
|
||||
<Route path="/services/vpn" element={<VPNPrepare />} />
|
||||
<Route path="/services/vpn/servers" element={<VPNServers />} />
|
||||
<Route path="/shared-data/:id" element={<SharedData />} />
|
||||
<Route path="/signIn" element={<SignIn />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import {fetchWrapper} from '../shared/fetchWrapper';
|
||||
import { fetchWrapper } from '../shared/fetchWrapper';
|
||||
|
||||
export const getNodeInfo = (headers) => fetchWrapper.get('node/info/', headers)
|
||||
export const getNodeInfo = (headers) => fetchWrapper.get('node/info/', headers);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {observer} from "mobx-react-lite";
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Transition from '../utils/Transition';
|
||||
import {store} from "../store/store";
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import { store } from '../store/store';
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import UserAvatar from '../images/user-avatar-32.png';
|
||||
|
||||
const DropdownProfile = observer(({
|
||||
align
|
||||
})=>{
|
||||
|
||||
const DropdownProfile = observer(({ align }) => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
@@ -19,7 +16,12 @@ import UserAvatar from '../images/user-avatar-32.png';
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
if (
|
||||
!dropdownOpen ||
|
||||
dropdown.current.contains(target) ||
|
||||
trigger.current.contains(target)
|
||||
)
|
||||
return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
@@ -37,9 +39,9 @@ import UserAvatar from '../images/user-avatar-32.png';
|
||||
});
|
||||
|
||||
const logOut = () => {
|
||||
sessionStorage.removeItem('passPhrase')
|
||||
store.fetchPassPhrase()
|
||||
}
|
||||
sessionStorage.removeItem('passPhrase');
|
||||
store.fetchPassPhrase();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
@@ -50,17 +52,30 @@ import UserAvatar from '../images/user-avatar-32.png';
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<img className="w-8 h-8 rounded-full" src={generateSvgAvatar(store.pubKey) || UserAvatar} width="32" height="32" alt="User" />
|
||||
<img
|
||||
className="w-8 h-8 rounded-full"
|
||||
src={generateSvgAvatar(store.pubKey) || UserAvatar}
|
||||
width="32"
|
||||
height="32"
|
||||
alt="User"
|
||||
/>
|
||||
<div className="flex items-center truncate">
|
||||
<span className="truncate ml-2 text-sm font-medium group-hover:text-slate-800">{store.accountName}</span>
|
||||
<svg className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<span className="truncate ml-2 text-sm font-medium group-hover:text-slate-800">
|
||||
{store.accountName}
|
||||
</span>
|
||||
<svg
|
||||
className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${
|
||||
align === 'right' ? 'right-0' : 'left-0'
|
||||
}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
@@ -75,7 +90,9 @@ import UserAvatar from '../images/user-avatar-32.png';
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="pt-0.5 pb-2 px-3 mb-1 border-b border-slate-200">
|
||||
<div className="font-medium text-slate-800">{store.accountName}</div>
|
||||
<div className="font-medium text-slate-800">
|
||||
{store.accountName}
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -99,7 +116,7 @@ import UserAvatar from '../images/user-avatar-32.png';
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default DropdownProfile;
|
||||
|
||||
104
src/components/DropdownTransaction.jsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownTransaction({ align, value, onChange }) {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
const changeValue = (val) => {
|
||||
setDropdownOpen(false);
|
||||
onChange(val);
|
||||
};
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (
|
||||
!dropdownOpen ||
|
||||
dropdown.current.contains(target) ||
|
||||
trigger.current.contains(target)
|
||||
)
|
||||
return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={dropdownOpen}
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<div className="flex items-center truncate">
|
||||
<span className="truncate font-medium text-indigo-500 group-hover:text-indigo-600">
|
||||
{value}
|
||||
</span>
|
||||
<svg
|
||||
className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${
|
||||
align === 'right' ? 'right-0' : 'left-0'
|
||||
}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<span
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3 cursor-pointer"
|
||||
onClick={() => changeValue('Voiting')}
|
||||
>
|
||||
Voiting
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3 cursor-pointer"
|
||||
onClick={() => changeValue('Unlocking')}
|
||||
>
|
||||
Unlocking
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownTransaction;
|
||||
31
src/components/Filters.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Filters = ({ value, onChange, values = [] }) => {
|
||||
return (
|
||||
<div className="mb-5">
|
||||
<ul className="flex flex-wrap -m-1">
|
||||
{values.map((e) =>
|
||||
e === value ? (
|
||||
<li className="m-1" key={e}>
|
||||
<button
|
||||
className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-transparent shadow-sm bg-indigo-500 text-white duration-150 ease-in-out"
|
||||
onClick={() => onChange(e)}
|
||||
>
|
||||
{e}
|
||||
</button>
|
||||
</li>
|
||||
) : (
|
||||
<li className="m-1" key={e}>
|
||||
<button
|
||||
className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-slate-200 hover:border-slate-300 shadow-sm bg-white text-slate-500 duration-150 ease-in-out"
|
||||
onClick={() => onChange(e)}
|
||||
>
|
||||
{e}
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
17
src/components/LoadingOverlay.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { ThreeCircles } from 'react-loader-spinner';
|
||||
|
||||
export const LoadingOverlay = () =>
|
||||
ReactDOM.createPortal(
|
||||
<div className="absolute left-0 top-0 h-screen w-screen z-50 loading-overlay">
|
||||
<ThreeCircles
|
||||
height="100"
|
||||
width="100"
|
||||
color="#6366f1"
|
||||
visible={true}
|
||||
ariaLabel="three-circles-rotating"
|
||||
/>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
14
src/components/NotificationContainer.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Toast } from './Toast2';
|
||||
import { store } from '../store/store';
|
||||
|
||||
export const NotificationContainer = observer(() => (
|
||||
<div className="absolute z-50 top-2 right-2 gap-2">
|
||||
{store.notifications.map((e) => (
|
||||
<Toast type={e.type} key={e.id} id={e.id}>
|
||||
{e.message}
|
||||
</Toast>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
52
src/components/PaginationClassic.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
function PaginationClassic({ count, limit, offset, onNextPage, onPrevPage }) {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<nav
|
||||
className="mb-4 sm:mb-0 sm:order-1"
|
||||
role="navigation"
|
||||
aria-label="Navigation"
|
||||
>
|
||||
<ul className="flex justify-center">
|
||||
<li className="ml-3 first:ml-0">
|
||||
<button
|
||||
className={cn(
|
||||
'btn bg-white border-slate-200 text-indigo-500',
|
||||
{ 'cursor-not-allowed text-slate-300': offset === 0 },
|
||||
{ 'hover:border-slate-300': offset !== 0 }
|
||||
)}
|
||||
disabled={offset === 0}
|
||||
onClick={onPrevPage}
|
||||
>
|
||||
<- Previous
|
||||
</button>
|
||||
</li>
|
||||
<li className="ml-3 first:ml-0">
|
||||
<button
|
||||
className={cn(
|
||||
'btn bg-white border-slate-200 text-indigo-500',
|
||||
{
|
||||
'cursor-not-allowed text-slate-300': offset + limit >= count,
|
||||
},
|
||||
{ 'hover:border-slate-300': offset + limit < count }
|
||||
)}
|
||||
disabled={offset + limit >= count}
|
||||
onClick={onNextPage}
|
||||
>
|
||||
Next ->
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="text-sm text-slate-500 text-center sm:text-left">
|
||||
Showing <span className="font-medium text-slate-600">{offset + 1}</span>{' '}
|
||||
to <span className="font-medium text-slate-600">{offset + limit}</span>{' '}
|
||||
of <span className="font-medium text-slate-600">{count}</span> results
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaginationClassic;
|
||||
90
src/components/Toast2.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { store } from '../store/store';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
export const Toast = observer(({ children, className, type, id }) => {
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 border-amber-200 text-amber-600';
|
||||
case 'error':
|
||||
return 'bg-rose-100 border-rose-200 text-rose-600';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 border-emerald-200 text-emerald-600';
|
||||
default:
|
||||
return 'bg-indigo-100 border-indigo-200 text-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timerId = setTimeout(() => store.removeNotification(id), 3000);
|
||||
return () => clearTimeout(timerId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={`inline-flex min-w-80 px-4 py-2 rounded-sm text-sm border ${typeColor(
|
||||
type
|
||||
)}`}
|
||||
>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
<button
|
||||
className="opacity-70 hover:opacity-80 ml-3 mt-[3px]"
|
||||
onClick={() => store.removeNotification(id)}
|
||||
>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
// Customise flatpickr
|
||||
$calendarPadding: 24px;
|
||||
$daySize: 36px;
|
||||
$daysWidth: $daySize*7;
|
||||
$daysWidth: $daySize * 7;
|
||||
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
@@ -18,63 +18,63 @@ $daysWidth: $daySize*7;
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
border: inherit;
|
||||
@apply rounded shadow-lg border border-slate-200 left-1/2;
|
||||
margin-left: - ($daysWidth + $calendarPadding*2)*0.5;
|
||||
padding: $calendarPadding;
|
||||
width: $daysWidth + $calendarPadding*2;
|
||||
border: inherit;
|
||||
@apply rounded shadow-lg border border-slate-200 left-1/2;
|
||||
margin-left: -($daysWidth + $calendarPadding * 2) * 0.5;
|
||||
padding: $calendarPadding;
|
||||
width: $daysWidth + $calendarPadding * 2;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.flatpickr-calendar {
|
||||
@apply left-0 right-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
.flatpickr-calendar {
|
||||
@apply left-0 right-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-right.flatpickr-calendar {
|
||||
@apply right-0 left-auto;
|
||||
margin-left: 0;
|
||||
@apply right-0 left-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.animate.open {
|
||||
animation: fpFadeInDown 200ms ease-out;
|
||||
animation: fpFadeInDown 200ms ease-out;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 20;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.flatpickr-days {
|
||||
width: $daysWidth;
|
||||
width: $daysWidth;
|
||||
}
|
||||
|
||||
.dayContainer {
|
||||
width: $daysWidth;
|
||||
min-width: $daysWidth;
|
||||
max-width: $daysWidth;
|
||||
width: $daysWidth;
|
||||
min-width: $daysWidth;
|
||||
max-width: $daysWidth;
|
||||
}
|
||||
|
||||
.flatpickr-day {
|
||||
@apply bg-slate-50 text-sm font-medium text-slate-600;
|
||||
max-width: $daySize;
|
||||
height: $daySize;
|
||||
line-height: $daySize;
|
||||
@apply bg-slate-50 text-sm font-medium text-slate-600;
|
||||
max-width: $daySize;
|
||||
height: $daySize;
|
||||
line-height: $daySize;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay {
|
||||
border: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.selected.startRange,
|
||||
.flatpickr-day.startRange.startRange,
|
||||
@@ -85,7 +85,7 @@ $daysWidth: $daySize*7;
|
||||
.flatpickr-day.selected.startRange.endRange,
|
||||
.flatpickr-day.startRange.startRange.endRange,
|
||||
.flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
@@ -95,11 +95,11 @@ $daysWidth: $daySize*7;
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply text-slate-400;
|
||||
@apply text-slate-400;
|
||||
}
|
||||
|
||||
.rangeMode .flatpickr-day {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected,
|
||||
@@ -120,7 +120,7 @@ $daysWidth: $daySize*7;
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
@apply bg-indigo-500 text-indigo-50;
|
||||
@apply bg-indigo-500 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
@@ -137,92 +137,92 @@ $daysWidth: $daySize*7;
|
||||
.flatpickr-day.nextMonthDay:focus,
|
||||
.flatpickr-day.today:hover,
|
||||
.flatpickr-day.today:focus {
|
||||
@apply bg-indigo-400 text-indigo-50;
|
||||
@apply bg-indigo-400 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
|
||||
box-shadow: none;
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.flatpickr-months {
|
||||
align-items: center;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 6px;
|
||||
align-items: center;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
position: static;
|
||||
height: auto;
|
||||
@apply text-slate-600;
|
||||
position: static;
|
||||
height: auto;
|
||||
@apply text-slate-600;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg,
|
||||
.flatpickr-months .flatpickr-next-month svg {
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover,
|
||||
.flatpickr-months .flatpickr-next-month:hover,
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||
fill: inherit;
|
||||
@apply text-slate-400;
|
||||
fill: inherit;
|
||||
@apply text-slate-400;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month {
|
||||
margin-left: -10px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
margin-right: -10px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
@apply text-slate-800;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
@apply text-slate-800;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.flatpickr-current-month {
|
||||
@apply text-sm font-medium;
|
||||
position: static;
|
||||
height: auto;
|
||||
width: auto;
|
||||
left: auto;
|
||||
padding: 0;
|
||||
@apply text-sm font-medium;
|
||||
position: static;
|
||||
height: auto;
|
||||
width: auto;
|
||||
left: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month {
|
||||
@apply font-medium m-0;
|
||||
@apply font-medium m-0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year {
|
||||
font-weight: inherit;
|
||||
box-shadow: none !important;
|
||||
font-weight: inherit;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.numInputWrapper:hover {
|
||||
background: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.numInputWrapper span {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.flatpickr-weekday {
|
||||
@apply text-slate-400 font-medium text-xs;
|
||||
@apply text-slate-400 font-medium text-xs;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop::before,
|
||||
.flatpickr-calendar.arrowTop::after {
|
||||
display: none;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
// Range slider
|
||||
$range-thumb-size: 36px;
|
||||
|
||||
input[type=range] {
|
||||
input[type='range'] {
|
||||
appearance: none;
|
||||
background: #ccc;
|
||||
border-radius: 3px;
|
||||
height: 6px;
|
||||
margin-top: ($range-thumb-size - 6px) * 0.5;
|
||||
margin-bottom: ($range-thumb-size - 6px) * 0.5;
|
||||
--thumb-size: #{$range-thumb-size};
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
background: #ccc;
|
||||
border-radius: 3px;
|
||||
height: 6px;
|
||||
margin-top: ($range-thumb-size - 6px) * 0.5;
|
||||
margin-bottom: ($range-thumb-size - 6px) * 0.5;
|
||||
--thumb-size: #{$range-thumb-size};
|
||||
-webkit-appearance: none;
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
&::-ms-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
|
||||
&:focus {
|
||||
@apply ring-0;
|
||||
}
|
||||
&:focus {
|
||||
@apply ring-0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -1,44 +1,41 @@
|
||||
// Switch element
|
||||
.form-switch {
|
||||
@apply relative select-none;
|
||||
width: 44px;
|
||||
@apply relative select-none;
|
||||
width: 44px;
|
||||
|
||||
label {
|
||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
||||
label {
|
||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
||||
|
||||
> span:first-child {
|
||||
@apply absolute block rounded-full;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 50%;
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
&:checked {
|
||||
+ label {
|
||||
@apply bg-indigo-500;
|
||||
|
||||
> span:first-child {
|
||||
@apply absolute block rounded-full;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 50%;
|
||||
transition: all .15s ease-out;
|
||||
left: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
|
||||
&:checked {
|
||||
&:disabled {
|
||||
+ label {
|
||||
@apply cursor-not-allowed bg-slate-100 border border-slate-200;
|
||||
|
||||
+ label {
|
||||
@apply bg-indigo-500;
|
||||
|
||||
> span:first-child {
|
||||
left: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
||||
+ label {
|
||||
@apply cursor-not-allowed bg-slate-100 border border-slate-200;
|
||||
|
||||
> span:first-child {
|
||||
@apply bg-slate-400;
|
||||
}
|
||||
}
|
||||
> span:first-child {
|
||||
@apply bg-slate-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
// Typography
|
||||
.h1 {
|
||||
@apply text-4xl font-extrabold tracking-tighter;
|
||||
@apply text-4xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-3xl font-extrabold tracking-tighter;
|
||||
@apply text-3xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply text-3xl font-extrabold;
|
||||
@apply text-3xl font-extrabold;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply text-2xl font-extrabold tracking-tight;
|
||||
@apply text-2xl font-extrabold tracking-tight;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
.h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
.h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
@@ -30,30 +30,30 @@
|
||||
.btn-lg,
|
||||
.btn-sm,
|
||||
.btn-xs {
|
||||
@apply font-medium text-sm inline-flex items-center justify-center border border-transparent rounded leading-5 shadow-sm transition duration-150 ease-in-out;
|
||||
@apply font-medium text-sm inline-flex items-center justify-center border border-transparent rounded leading-5 shadow-sm transition duration-150 ease-in-out;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-3 py-2;
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
@apply px-4 py-3;
|
||||
@apply px-4 py-3;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply px-2 py-1;
|
||||
@apply px-2 py-1;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
@apply px-2 py-0.5;
|
||||
@apply px-2 py-0.5;
|
||||
}
|
||||
|
||||
// Forms
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration {
|
||||
input[type='search']::-webkit-search-decoration,
|
||||
input[type='search']::-webkit-search-cancel-button,
|
||||
input[type='search']::-webkit-search-results-button,
|
||||
input[type='search']::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-sm text-slate-800 bg-white border;
|
||||
@apply text-sm text-slate-800 bg-white border;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
@@ -71,28 +71,28 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox {
|
||||
@apply rounded;
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select {
|
||||
@apply leading-5 py-2 px-3 border-slate-200 hover:border-slate-300 focus:border-indigo-300 shadow-sm;
|
||||
@apply leading-5 py-2 px-3 border-slate-200 hover:border-slate-300 focus:border-indigo-300 shadow-sm;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
@apply placeholder-slate-400;
|
||||
@apply placeholder-slate-400;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply pr-10;
|
||||
@apply pr-10;
|
||||
}
|
||||
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-indigo-500 border border-slate-300;
|
||||
@apply text-indigo-500 border border-slate-300;
|
||||
}
|
||||
|
||||
/* Chrome, Safari and Opera */
|
||||
@@ -101,6 +101,6 @@ input[type="search"]::-webkit-search-results-decoration {
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
@import 'additional-styles/flatpickr.scss';
|
||||
@import 'additional-styles/theme.scss';
|
||||
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -1,92 +1,112 @@
|
||||
function createBlob(options) {
|
||||
let points = [];
|
||||
let slice = (Math.PI * 2) / options.numPoints;
|
||||
let startAngle = 0;
|
||||
|
||||
let points = [];
|
||||
let slice = (Math.PI * 2) / options.numPoints;
|
||||
let startAngle = 0;
|
||||
for (let i = 0; i < options.subkey.length; i++) {
|
||||
let angle = startAngle + i * slice;
|
||||
let rnd = Number('0x' + options.subkey.substring(i + 0, i + 1));
|
||||
let point = {
|
||||
x: options.centerX + Math.cos(angle) * (options.radius + rnd),
|
||||
y: options.centerY + Math.sin(angle) * (options.radius + rnd),
|
||||
};
|
||||
points.push(point);
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.subkey.length; i++) {
|
||||
let angle = startAngle + i * slice;
|
||||
let rnd = Number("0x"+options.subkey.substring(i+0,i+1));
|
||||
let point = {
|
||||
x: options.centerX + Math.cos(angle) * (options.radius + rnd),
|
||||
y: options.centerY + Math.sin(angle) * (options.radius + rnd)
|
||||
};
|
||||
points.push(point);
|
||||
}
|
||||
let size = points.length;
|
||||
let path = 'M' + points[0].x + ' ' + points[0].y + ' C';
|
||||
|
||||
let size = points.length;
|
||||
let path = "M" + points[0].x + " " + points[0].y + " C";
|
||||
for (let i = 0; i < size; i++) {
|
||||
let p0 = points[(i - 1 + size) % size];
|
||||
let p1 = points[i];
|
||||
let p2 = points[(i + 1) % size];
|
||||
let p3 = points[(i + 2) % size];
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
let p0 = points[(i - 1 + size) % size];
|
||||
let p1 = points[i];
|
||||
let p2 = points[(i + 1) % size];
|
||||
let p3 = points[(i + 2) % size];
|
||||
let x1 = p1.x + (p2.x - p0.x) / 6;
|
||||
let y1 = p1.y + (p2.y - p0.y) / 6;
|
||||
|
||||
let x1 = p1.x + (p2.x - p0.x) / 6;
|
||||
let y1 = p1.y + (p2.y - p0.y) / 6;
|
||||
let x2 = p2.x - (p3.x - p1.x) / 6;
|
||||
let y2 = p2.y - (p3.y - p1.y) / 6;
|
||||
|
||||
let x2 = p2.x - (p3.x - p1.x) / 6;
|
||||
let y2 = p2.y - (p3.y - p1.y) / 6;
|
||||
path += ' ' + x1 + ' ' + y1 + ' ' + x2 + ' ' + y2 + ' ' + p2.x + ' ' + p2.y;
|
||||
}
|
||||
|
||||
path += " " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + p2.x + " " + p2.y;
|
||||
}
|
||||
|
||||
return path + "z";
|
||||
return path + 'z';
|
||||
}
|
||||
|
||||
export var generateSvgAvatar = ( function () {
|
||||
return function (publicKey, seed, raw) {
|
||||
let pubKey = publicKey || Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16) + Math.floor(Math.random()*65535*65535).toString(16);
|
||||
//pubKey = "73f47d4929d7b2b6598ca4999d52567f9bb5aef3e353e0a66b468096db6e4e88"
|
||||
let particles = [];
|
||||
let gradients = ['#000000'];
|
||||
let defs = [];
|
||||
export var generateSvgAvatar = (function () {
|
||||
return function (publicKey, seed, raw) {
|
||||
let pubKey =
|
||||
publicKey ||
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16) +
|
||||
Math.floor(Math.random() * 65535 * 65535).toString(16);
|
||||
//pubKey = "73f47d4929d7b2b6598ca4999d52567f9bb5aef3e353e0a66b468096db6e4e88"
|
||||
let particles = [];
|
||||
let gradients = ['#000000'];
|
||||
let defs = [];
|
||||
|
||||
for (let a = 0; a<64; a = a + 8) {
|
||||
let blob = createBlob({
|
||||
numPoints: 8,
|
||||
centerX: 256,
|
||||
centerY: 256-a*4,
|
||||
radius: 16,
|
||||
subkey: pubKey.substring(a,a + 8)
|
||||
});
|
||||
for (let a = 0; a < 64; a = a + 8) {
|
||||
let blob = createBlob({
|
||||
numPoints: 8,
|
||||
centerX: 256,
|
||||
centerY: 256 - a * 4,
|
||||
radius: 16,
|
||||
subkey: pubKey.substring(a, a + 8),
|
||||
});
|
||||
|
||||
let color = "#" + pubKey.substring(a+2,a+8);
|
||||
let width = Number("0x"+pubKey.substring(a,a+1));
|
||||
let opacity = Number("0x"+pubKey.substring(a+1,a+2))/8;
|
||||
let color = '#' + pubKey.substring(a + 2, a + 8);
|
||||
let width = Number('0x' + pubKey.substring(a, a + 1));
|
||||
let opacity = Number('0x' + pubKey.substring(a + 1, a + 2)) / 8;
|
||||
|
||||
gradients.push(color);
|
||||
defs.push(`<radialGradient id="a-${a}" cx="0" cy="0" r="0.75" fx="0.35" fy="0.5" spreadMethod="reflect"><stop offset="0%" stop-color="${gradients[gradients.length-1]}"/><stop offset="100%" stop-color="${gradients[gradients.length]}"/></radialGradient>`);
|
||||
particles.push(`<path d="${blob}" stroke="url(#a-${a})" stroke-width="${width}" stroke-opacity = "${opacity}" fill="transparent"/>`);
|
||||
gradients.push(color);
|
||||
defs.push(
|
||||
`<radialGradient id="a-${a}" cx="0" cy="0" r="0.75" fx="0.35" fy="0.5" spreadMethod="reflect"><stop offset="0%" stop-color="${
|
||||
gradients[gradients.length - 1]
|
||||
}"/><stop offset="100%" stop-color="${
|
||||
gradients[gradients.length]
|
||||
}"/></radialGradient>`
|
||||
);
|
||||
particles.push(
|
||||
`<path d="${blob}" stroke="url(#a-${a})" stroke-width="${width}" stroke-opacity = "${opacity}" fill="transparent"/>`
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let ph = "";
|
||||
let off = 4 + Number("0x"+pubKey.substring(0,1));
|
||||
let rotate = 0;
|
||||
for (var i = 0; i < off; i++){
|
||||
ph = ph + `<use xlink:href="#pattern-${pubKey}" mask="url(#mask)" class="segment" style="transform-origin:center center;transform: scaleY(1) rotate(${rotate}deg)"></use>,` + "\n";
|
||||
rotate = rotate + 360/off;
|
||||
}
|
||||
|
||||
var svg = [
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" preserveAspectRatio="xMidYMid slice" viewBox="0 0 512 512">',
|
||||
'<defs>',
|
||||
'<radialGradient id="e" cx="0" cy="0" r="0.75" fx="0.35" fy="0.5" spreadMethod="reflect">',
|
||||
'<stop offset="0%" stop-color="red"/>',
|
||||
'<stop offset="100%" stop-color="blue"/>',
|
||||
'</radialGradient>',defs.join('\n'),
|
||||
'</defs>',
|
||||
'<symbol id="pattern-' + pubKey + '">',
|
||||
particles.join(''),
|
||||
'</symbol>',
|
||||
'<g style="transform-origin:center center; transform: rotate(' + pubKey + 'deg)">',
|
||||
ph,
|
||||
'</g>',
|
||||
'</svg>',
|
||||
].join('');
|
||||
return raw ? svg : 'data:image/svg+xml;base64,' + btoa(svg);
|
||||
};
|
||||
let ph = '';
|
||||
let off = 4 + Number('0x' + pubKey.substring(0, 1));
|
||||
let rotate = 0;
|
||||
for (var i = 0; i < off; i++) {
|
||||
ph =
|
||||
ph +
|
||||
`<use xlink:href="#pattern-${pubKey}" mask="url(#mask)" class="segment" style="transform-origin:center center;transform: scaleY(1) rotate(${rotate}deg)"></use>,` +
|
||||
'\n';
|
||||
rotate = rotate + 360 / off;
|
||||
}
|
||||
|
||||
var svg = [
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" preserveAspectRatio="xMidYMid slice" viewBox="0 0 512 512">',
|
||||
'<defs>',
|
||||
'<radialGradient id="e" cx="0" cy="0" r="0.75" fx="0.35" fy="0.5" spreadMethod="reflect">',
|
||||
'<stop offset="0%" stop-color="red"/>',
|
||||
'<stop offset="100%" stop-color="blue"/>',
|
||||
'</radialGradient>',
|
||||
defs.join('\n'),
|
||||
'</defs>',
|
||||
'<symbol id="pattern-' + pubKey + '">',
|
||||
particles.join(''),
|
||||
'</symbol>',
|
||||
'<g style="transform-origin:center center; transform: rotate(' +
|
||||
pubKey +
|
||||
'deg)">',
|
||||
ph,
|
||||
'</g>',
|
||||
'</svg>',
|
||||
].join('');
|
||||
return raw ? svg : 'data:image/svg+xml;base64,' + btoa(svg);
|
||||
};
|
||||
})();
|
||||
|
||||
BIN
src/images/service_icon.png
Normal file
|
After Width: | Height: | Size: 912 B |
BIN
src/images/vpn_server_icon.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
src/images/vpn_service.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
41
src/main.jsx
@@ -1,16 +1,29 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {Buffer} from "buffer";
|
||||
import { HashRouter as Router } from 'react-router-dom'
|
||||
import App from './App'
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Buffer } from 'buffer';
|
||||
import { HashRouter as Router } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import { store } from './store/store';
|
||||
import { fetchWrapper } from './shared/fetchWrapper';
|
||||
|
||||
globalThis.Buffer = Buffer
|
||||
globalThis.Buffer = Buffer;
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
window.store = store;
|
||||
|
||||
BigInt.prototype.toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
fetch('settings.json')
|
||||
.then((e) => e.json())
|
||||
.then((data) => (fetchWrapper.baseUrl = data.API_URL))
|
||||
.then(() =>
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import OnboardingImage from '../images/onboarding-image.jpg';
|
||||
import OnboardingDecoration from '../images/auth-decoration.png';
|
||||
import Logo from "../images/logo.png";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { store } from "../store/store";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import Logo from '../images/logo.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { store } from '../store/store';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const Onboarding1 = observer(() => {
|
||||
|
||||
useEffect(() => {
|
||||
store.clearDataRegistration()
|
||||
}, [])
|
||||
store.clearDataRegistration();
|
||||
}, []);
|
||||
|
||||
if(store.passPhrase)
|
||||
return <Navigate to="/digitalId/profile-id" replace={true} />
|
||||
if (store.passPhrase)
|
||||
return <Navigate to="/digitalId/profile-id" replace={true} />;
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/dashboard">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
<div className="text-sm">
|
||||
Have an account? <Link className="font-medium text-indigo-500 hover:text-indigo-600" to="/signIn">Sign In</Link>
|
||||
Have an account?{' '}
|
||||
<Link
|
||||
className="font-medium text-indigo-500 hover:text-indigo-600"
|
||||
to="/signIn"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,19 +43,42 @@ const Onboarding1 = observer(() => {
|
||||
<div className="px-4 pt-12 pb-[142px]">
|
||||
<div className="max-w-md mx-auto w-full">
|
||||
<div className="relative">
|
||||
<div className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<ul className="relative flex justify-between w-full">
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/">1</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-2">2</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-2"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-3">3</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-3"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-4">4</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-4"
|
||||
>
|
||||
4
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -65,49 +88,120 @@ const Onboarding1 = observer(() => {
|
||||
|
||||
<div className="px-4 py-8">
|
||||
<div className="max-w-md mx-auto">
|
||||
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">Tell us about your company ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">
|
||||
Tell us about your company ✨
|
||||
</h1>
|
||||
{/* Form */}
|
||||
<div>
|
||||
<div className="sm:flex space-y-3 sm:space-y-0 sm:space-x-4 mb-8">
|
||||
<label className="flex-1 relative block cursor-pointer">
|
||||
<input type="radio" name="radio-buttons" className="peer sr-only" defaultChecked />
|
||||
<input
|
||||
type="radio"
|
||||
name="radio-buttons"
|
||||
className="peer sr-only"
|
||||
defaultChecked
|
||||
/>
|
||||
<div className="h-full text-center bg-white px-4 py-6 rounded border border-slate-200 hover:border-slate-300 shadow-sm duration-150 ease-in-out">
|
||||
<svg className="inline-flex w-10 h-10 shrink-0 fill-current mb-2" viewBox="0 0 40 40">
|
||||
<circle className="text-indigo-100" cx="20" cy="20" r="20" />
|
||||
<path className="text-indigo-500" d="m26.371 23.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 24 17v-1.828A4.087 4.087 0 0 0 20 11a4.087 4.087 0 0 0-4 4.172V17a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V27a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z" />
|
||||
<svg
|
||||
className="inline-flex w-10 h-10 shrink-0 fill-current mb-2"
|
||||
viewBox="0 0 40 40"
|
||||
>
|
||||
<circle
|
||||
className="text-indigo-100"
|
||||
cx="20"
|
||||
cy="20"
|
||||
r="20"
|
||||
/>
|
||||
<path
|
||||
className="text-indigo-500"
|
||||
d="m26.371 23.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 24 17v-1.828A4.087 4.087 0 0 0 20 11a4.087 4.087 0 0 0-4 4.172V17a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V27a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z"
|
||||
/>
|
||||
</svg>
|
||||
<div className="font-semibold text-slate-800 mb-1">Individual</div>
|
||||
<div className="text-sm">Lorem ipsum is place text commonly used.</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">
|
||||
Individual
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
Lorem ipsum is place text commonly used.
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 border-2 border-transparent peer-checked:border-indigo-400 rounded pointer-events-none" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute inset-0 border-2 border-transparent peer-checked:border-indigo-400 rounded pointer-events-none"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</label>
|
||||
<label className="flex-1 relative block cursor-pointer">
|
||||
<input type="radio" name="radio-buttons" className="peer sr-only" />
|
||||
<input
|
||||
type="radio"
|
||||
name="radio-buttons"
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<div className="h-full text-center bg-white px-4 py-6 rounded border border-slate-200 hover:border-slate-300 shadow-sm duration-150 ease-in-out">
|
||||
<svg className="inline-flex w-10 h-10 shrink-0 fill-current mb-2" viewBox="0 0 40 40">
|
||||
<circle className="text-indigo-100" cx="20" cy="20" r="20" />
|
||||
<path className="text-indigo-500" d="m26.371 23.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 24 17v-1.828A4.087 4.087 0 0 0 20 11a4.087 4.087 0 0 0-4 4.172V17a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V27a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z" />
|
||||
<circle className="text-indigo-100" cx="20" cy="20" r="20" />
|
||||
<path className="text-indigo-300" d="m30.377 22.749-3.709-1.5a1 1 0 0 1-.623-.926v-.878A3.989 3.989 0 0 0 28.027 16v-1.828c.047-2.257-1.728-4.124-3.964-4.172-2.236.048-4.011 1.915-3.964 4.172V16a3.989 3.989 0 0 0 1.982 3.445v.878a1 1 0 0 1-.623.928c-.906.266-1.626.557-2.159.872-.533.315-1.3 1.272-2.299 2.872 1.131.453 6.075-.546 6.072.682V28a2.99 2.99 0 0 1-.182 1h7.119A.996.996 0 0 0 31 28v-4.323a1 1 0 0 0-.623-.928Z" />
|
||||
<path className="text-indigo-500" d="m22.371 24.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 20 18v-1.828A4.087 4.087 0 0 0 16 12a4.087 4.087 0 0 0-4 4.172V18a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V28a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z" />
|
||||
<svg
|
||||
className="inline-flex w-10 h-10 shrink-0 fill-current mb-2"
|
||||
viewBox="0 0 40 40"
|
||||
>
|
||||
<circle
|
||||
className="text-indigo-100"
|
||||
cx="20"
|
||||
cy="20"
|
||||
r="20"
|
||||
/>
|
||||
<path
|
||||
className="text-indigo-500"
|
||||
d="m26.371 23.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 24 17v-1.828A4.087 4.087 0 0 0 20 11a4.087 4.087 0 0 0-4 4.172V17a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V27a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z"
|
||||
/>
|
||||
<circle
|
||||
className="text-indigo-100"
|
||||
cx="20"
|
||||
cy="20"
|
||||
r="20"
|
||||
/>
|
||||
<path
|
||||
className="text-indigo-300"
|
||||
d="m30.377 22.749-3.709-1.5a1 1 0 0 1-.623-.926v-.878A3.989 3.989 0 0 0 28.027 16v-1.828c.047-2.257-1.728-4.124-3.964-4.172-2.236.048-4.011 1.915-3.964 4.172V16a3.989 3.989 0 0 0 1.982 3.445v.878a1 1 0 0 1-.623.928c-.906.266-1.626.557-2.159.872-.533.315-1.3 1.272-2.299 2.872 1.131.453 6.075-.546 6.072.682V28a2.99 2.99 0 0 1-.182 1h7.119A.996.996 0 0 0 31 28v-4.323a1 1 0 0 0-.623-.928Z"
|
||||
/>
|
||||
<path
|
||||
className="text-indigo-500"
|
||||
d="m22.371 24.749-3.742-1.5a1 1 0 0 1-.629-.926v-.878A3.982 3.982 0 0 0 20 18v-1.828A4.087 4.087 0 0 0 16 12a4.087 4.087 0 0 0-4 4.172V18a3.982 3.982 0 0 0 2 3.445v.878a1 1 0 0 1-.629.928l-3.742 1.5a1 1 0 0 0-.629.926V28a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.323a1 1 0 0 0-.629-.928Z"
|
||||
/>
|
||||
</svg>
|
||||
<div className="font-semibold text-slate-800 mb-1">Organization</div>
|
||||
<div className="text-sm">Lorem ipsum is place text commonly used.</div>
|
||||
<div className="font-semibold text-slate-800 mb-1">
|
||||
Organization
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
Lorem ipsum is place text commonly used.
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 border-2 border-transparent peer-checked:border-indigo-400 rounded pointer-events-none" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute inset-0 border-2 border-transparent peer-checked:border-indigo-400 rounded pointer-events-none"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-6 mb-8">
|
||||
<div>
|
||||
<div className="font-medium text-slate-800 text-sm mb-1">💸 Lorem ipsum is place text commonly?</div>
|
||||
<div className="text-xs font-normal leading-6">Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts.</div>
|
||||
<div className="font-medium text-slate-800 text-sm mb-1">
|
||||
💸 Lorem ipsum is place text commonly?
|
||||
</div>
|
||||
<div className="text-xs font-normal leading-6">
|
||||
Lorem ipsum is placeholder text commonly used in the
|
||||
graphic, print, and publishing industries for previewing
|
||||
layouts.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="form-switch">
|
||||
<input type="checkbox" id="switch" className="sr-only" defaultChecked />
|
||||
<input
|
||||
type="checkbox"
|
||||
id="switch"
|
||||
className="sr-only"
|
||||
defaultChecked
|
||||
/>
|
||||
<label className="bg-slate-400" htmlFor="switch">
|
||||
<span className="bg-white shadow-sm" aria-hidden="true"></span>
|
||||
<span
|
||||
className="bg-white shadow-sm"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span className="sr-only">Switch label</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -119,24 +213,34 @@ const Onboarding1 = observer(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<img className="object-cover object-center w-full h-full" src={OnboardingImage} width="760" height="1024" alt="Onboarding" />
|
||||
<img className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block" src={OnboardingDecoration} width="218" height="224" alt="Authentication decoration" />
|
||||
<div
|
||||
className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img
|
||||
className="object-cover object-center w-full h-full"
|
||||
src={OnboardingImage}
|
||||
width="760"
|
||||
height="1024"
|
||||
alt="Onboarding"
|
||||
/>
|
||||
<img
|
||||
className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block"
|
||||
src={OnboardingDecoration}
|
||||
width="218"
|
||||
height="224"
|
||||
alt="Authentication decoration"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
export default Onboarding1;
|
||||
|
||||
@@ -1,74 +1,81 @@
|
||||
import React, {useEffect, useMemo, useState} from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { store } from "../store/store";
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { store } from '../store/store';
|
||||
import OnboardingImage from '../images/onboarding-image.jpg';
|
||||
import OnboardingDecoration from '../images/auth-decoration.png';
|
||||
import Logo from "../images/logo.png";
|
||||
import Logo from '../images/logo.png';
|
||||
|
||||
const Onboarding2 = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [dataRegistration, setDataRegistration] = useState(
|
||||
{
|
||||
firstname: '',
|
||||
secondname: '',
|
||||
gender: '',
|
||||
birthdate: '',
|
||||
}
|
||||
const [dataRegistration, setDataRegistration] = useState({
|
||||
firstname: '',
|
||||
secondname: '',
|
||||
gender: '',
|
||||
birthdate: '',
|
||||
});
|
||||
|
||||
const fillingForm = useMemo(
|
||||
() =>
|
||||
!Object.keys(dataRegistration).find((item) => !dataRegistration[item]),
|
||||
[dataRegistration]
|
||||
);
|
||||
|
||||
const fillingForm = useMemo(() => !Object.keys(dataRegistration).find(item => !dataRegistration[item]), [dataRegistration])
|
||||
|
||||
const saveValueChange = (event) => {
|
||||
const newValue=event.target.value;
|
||||
const newValue = event.target.value;
|
||||
setDataRegistration({
|
||||
...dataRegistration,
|
||||
[event.target.id]: newValue,
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const saveStoreRegistration = () => {
|
||||
if (fillingForm) {
|
||||
store.saveDataRegistration(Object.keys(dataRegistration).map(item => ({
|
||||
store.saveDataRegistration(
|
||||
Object.keys(dataRegistration).map((item) => ({
|
||||
key: `${item}`,
|
||||
value: dataRegistration[item],
|
||||
})
|
||||
))
|
||||
navigate("/onboarding-3", { replace: true });
|
||||
}))
|
||||
);
|
||||
navigate('/onboarding-3', { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(store.accountData.length === 0)
|
||||
return;
|
||||
setDataRegistration(store.accountData.reduce((acc, item) => ({
|
||||
...acc,
|
||||
[item.key]: item.value
|
||||
}), {}))
|
||||
|
||||
}, [])
|
||||
if (store.accountData.length === 0) return;
|
||||
setDataRegistration(
|
||||
store.accountData.reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
[item.key]: item.value,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/dashboard">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
<div className="text-sm">
|
||||
Have an account? <Link className="font-medium text-indigo-500 hover:text-indigo-600" to="/signIn">Sign In</Link>
|
||||
Have an account?{' '}
|
||||
<Link
|
||||
className="font-medium text-indigo-500 hover:text-indigo-600"
|
||||
to="/signIn"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,19 +83,42 @@ const Onboarding2 = observer(() => {
|
||||
<div className="px-4 pt-12 pb-[59px]">
|
||||
<div className="max-w-md mx-auto w-full">
|
||||
<div className="relative">
|
||||
<div className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<ul className="relative flex justify-between w-full">
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/">1</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-2">2</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-2"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-3">3</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-3"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-4">4</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-4"
|
||||
>
|
||||
4
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -98,57 +128,120 @@ const Onboarding2 = observer(() => {
|
||||
|
||||
<div className="px-4 py-8">
|
||||
<div className="max-w-[344px] mx-auto">
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-6">Create your Account ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-6">
|
||||
Create your Account ✨
|
||||
</h1>
|
||||
{/* Form */}
|
||||
<div>
|
||||
<div className="space-y-4 mb-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="first_name">First name <span className="text-rose-500">*</span></label>
|
||||
<input id="firstname" onChange={(event)=>saveValueChange(event)} className="form-input w-full" type="text" value={dataRegistration.firstname} />
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="first_name"
|
||||
>
|
||||
First name <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="firstname"
|
||||
onChange={(event) => saveValueChange(event)}
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
value={dataRegistration.firstname}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="second_name">Last name <span className="text-rose-500">*</span></label>
|
||||
<input id="secondname" onChange={(event)=>saveValueChange(event)} className="form-input w-full" type="text" value={dataRegistration.secondname} />
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="second_name"
|
||||
>
|
||||
Last name <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="secondname"
|
||||
onChange={(event) => saveValueChange(event)}
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
value={dataRegistration.secondname}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="gender">Gender <span className="text-rose-500">*</span></label>
|
||||
<select id="gender" onChange={(event)=>saveValueChange(event)} className="form-select w-full" value={dataRegistration.gender}>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="gender"
|
||||
>
|
||||
Gender <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="gender"
|
||||
onChange={(event) => saveValueChange(event)}
|
||||
className="form-select w-full"
|
||||
value={dataRegistration.gender}
|
||||
>
|
||||
<option className="hidden"></option>
|
||||
<option>male</option>
|
||||
<option>female</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="birthdate">Date of birth <span className="text-rose-500">*</span></label>
|
||||
<input id="birthdate" onChange={(event)=>saveValueChange(event)} className="form-input w-full" type="date" autoComplete="on" value={dataRegistration.birthdate} />
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="birthdate"
|
||||
>
|
||||
Date of birth <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="birthdate"
|
||||
onChange={(event) => saveValueChange(event)}
|
||||
className="form-input w-full"
|
||||
type="date"
|
||||
autoComplete="on"
|
||||
value={dataRegistration.birthdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-6">
|
||||
<div className="mr-1">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm ml-2">Email me about product news.</span>
|
||||
<span className="text-sm ml-2">
|
||||
Email me about product news.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<button onClick={saveStoreRegistration} className="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-3 whitespace-nowrap">Sign Up
|
||||
<button
|
||||
onClick={saveStoreRegistration}
|
||||
className="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-3 whitespace-nowrap"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<img className="object-cover object-center w-full h-full" src={OnboardingImage} width="760" height="1024" alt="Onboarding" />
|
||||
<img className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block" src={OnboardingDecoration} width="218" height="224" alt="Authentication decoration" />
|
||||
<div
|
||||
className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img
|
||||
className="object-cover object-center w-full h-full"
|
||||
src={OnboardingImage}
|
||||
width="760"
|
||||
height="1024"
|
||||
alt="Onboarding"
|
||||
/>
|
||||
<img
|
||||
className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block"
|
||||
src={OnboardingDecoration}
|
||||
width="218"
|
||||
height="224"
|
||||
alt="Authentication decoration"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,53 +1,55 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import {observer} from "mobx-react-lite";
|
||||
import React, { useEffect } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import Logo from "../images/logo.png";
|
||||
import {store} from "../store/store";
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import Logo from '../images/logo.png';
|
||||
import { store } from '../store/store';
|
||||
|
||||
const Onboarding3 = observer(()=>{
|
||||
|
||||
useEffect(()=>{
|
||||
const Onboarding3 = observer(() => {
|
||||
useEffect(() => {
|
||||
store.generatePassPhrase();
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const generatePassPhrase = () => {
|
||||
store.generatePassPhrase();
|
||||
};
|
||||
|
||||
const savePassPhraseInStore = copiedPhrase => {
|
||||
const savePassPhraseInStore = (copiedPhrase) => {
|
||||
if (copiedPhrase.split(' ').length === 12) {
|
||||
store.savePastPassPhrase(copiedPhrase.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const pastePassPhrase = () => {
|
||||
navigator.clipboard.readText().then(res => savePassPhraseInStore(res));
|
||||
navigator.clipboard.readText().then((res) => savePassPhraseInStore(res));
|
||||
};
|
||||
|
||||
const convertPassPhraseToArray = () => store.passPhrase.split(' ').
|
||||
map((item, index) => ({str: item, id: index}));
|
||||
const convertPassPhraseToArray = () =>
|
||||
store.passPhrase
|
||||
.split(' ')
|
||||
.map((item, index) => ({ str: item, id: index }));
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/dashboard">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
<div className="text-sm">
|
||||
Have an account? <Link className="font-medium text-indigo-500 hover:text-indigo-600" to="/signIn">Sign In</Link>
|
||||
Have an account?{' '}
|
||||
<Link
|
||||
className="font-medium text-indigo-500 hover:text-indigo-600"
|
||||
to="/signIn"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,19 +57,42 @@ const Onboarding3 = observer(()=>{
|
||||
<div className="px-4 pt-12 pb-8">
|
||||
<div className="max-w-md mx-auto w-full">
|
||||
<div className="relative">
|
||||
<div className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<ul className="relative flex justify-between w-full">
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/">1</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-2">2</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-2"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-3">3</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-3"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500" to="/onboarding-4">4</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-slate-100 text-slate-500"
|
||||
to="/onboarding-4"
|
||||
>
|
||||
4
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -77,58 +102,94 @@ const Onboarding3 = observer(()=>{
|
||||
|
||||
<div className="px-4 mt-20 py-8">
|
||||
<div className="max-w-fit mx-auto flex flex-col gap-y-36">
|
||||
|
||||
<div className="gap-0.5">
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">Company information ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">
|
||||
Company information ✨
|
||||
</h1>
|
||||
{/* htmlForm */}
|
||||
<div className="grid grid-cols-4 gap-y-2.5 gap-[18px] mb-14">
|
||||
{convertPassPhraseToArray().map(tag => (
|
||||
<button key={tag.id} className="min-w-93px w-24 min-h-26px h-26px bg-slate-100 text-slate-500 cursor-pointer hover:bg-blue-100 hover:text-blue-600 rounded-full text-center px-2.5">
|
||||
{convertPassPhraseToArray().map((tag) => (
|
||||
<button
|
||||
key={tag.id}
|
||||
className="min-w-93px w-24 min-h-26px h-26px bg-slate-100 text-slate-500 cursor-pointer hover:bg-blue-100 hover:text-blue-600 rounded-full text-center px-2.5"
|
||||
>
|
||||
<span className="text-sm font-medium">{tag.str}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex pl-7 gap-x-5">
|
||||
<button onClick={pastePassPhrase} className="btn pl-9 pr-16 border-slate-200 hover:border-slate-300 text-slate-600">
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 shrink-0 mb-0.5" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M11.7.3c-.4-.4-1-.4-1.4 0l-10 10c-.2.2-.3.4-.3.7v4c0 .6.4 1 1 1h4c.3 0 .5-.1.7-.3l10-10c.4-.4.4-1 0-1.4l-4-4zM4.6 14H2v-2.6l6-6L10.6 8l-6 6zM12 6.6L9.4 4 11 2.4 13.6 5 12 6.6z"></path>
|
||||
<button
|
||||
onClick={pastePassPhrase}
|
||||
className="btn pl-9 pr-16 border-slate-200 hover:border-slate-300 text-slate-600"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current text-slate-400 shrink-0 mb-0.5"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M11.7.3c-.4-.4-1-.4-1.4 0l-10 10c-.2.2-.3.4-.3.7v4c0 .6.4 1 1 1h4c.3 0 .5-.1.7-.3l10-10c.4-.4.4-1 0-1.4l-4-4zM4.6 14H2v-2.6l6-6L10.6 8l-6 6zM12 6.6L9.4 4 11 2.4 13.6 5 12 6.6z"></path>
|
||||
</svg>
|
||||
<span className="ml-2">Paste</span>
|
||||
</button>
|
||||
<button onClick={generatePassPhrase} className="btn pr-11 pl-7 border-slate-200 hover:border-slate-300 text-rose-500">
|
||||
<svg className="mb-0.5" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5744 5.66943L13.1504 7.09343C13.4284 7.44043 13.6564 7.75743 13.8194 8.00043C13.0594 9.13043 10.9694 11.8204 8.25836 11.9854L6.44336 13.8004C6.93936 13.9244 7.45736 14.0004 8.00036 14.0004C12.7074 14.0004 15.7444 8.71643 15.8714 8.49243C16.0424 8.18843 16.0434 7.81643 15.8724 7.51243C15.8254 7.42743 15.3724 6.63143 14.5744 5.66943Z" fill="#F43F5E"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M1.00038 16.0002C0.744375 16.0002 0.488375 15.9022 0.293375 15.7072C-0.0976249 15.3162 -0.0976249 14.6842 0.292375 14.2942L2.82138 11.7653C1.17238 10.2913 0.198375 8.61425 0.128375 8.48925C-0.0416249 8.18625 -0.0426249 7.81725 0.126375 7.51425C0.251375 7.28925 3.24537 2.00025 8.00037 2.00025C9.33138 2.00025 10.5154 2.43125 11.5484 3.03825L14.2934 0.29325C14.6844 -0.09775 15.3164 -0.09775 15.7074 0.29325C16.0984 0.68425 16.0984 1.31625 15.7074 1.70725L1.70738 15.7072C1.51238 15.9022 1.25638 16.0002 1.00038 16.0002ZM8.00037 4.00025C5.14637 4.00025 2.95837 6.83525 2.18138 7.99925C2.55938 8.56225 3.28538 9.51025 4.24038 10.3463L6.07438 8.51225C6.02938 8.34825 6.00037 8.17825 6.00037 8.00025C6.00037 6.89525 6.89537 6.00025 8.00037 6.00025C8.17838 6.00025 8.34838 6.02925 8.51237 6.07425L10.0784 4.50825C9.43738 4.20125 8.74237 4.00025 8.00037 4.00025Z" fill="#F43F5E"/>
|
||||
<button
|
||||
onClick={generatePassPhrase}
|
||||
className="btn pr-11 pl-7 border-slate-200 hover:border-slate-300 text-rose-500"
|
||||
>
|
||||
<svg
|
||||
className="mb-0.5"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.5744 5.66943L13.1504 7.09343C13.4284 7.44043 13.6564 7.75743 13.8194 8.00043C13.0594 9.13043 10.9694 11.8204 8.25836 11.9854L6.44336 13.8004C6.93936 13.9244 7.45736 14.0004 8.00036 14.0004C12.7074 14.0004 15.7444 8.71643 15.8714 8.49243C16.0424 8.18843 16.0434 7.81643 15.8724 7.51243C15.8254 7.42743 15.3724 6.63143 14.5744 5.66943Z"
|
||||
fill="#F43F5E"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.00038 16.0002C0.744375 16.0002 0.488375 15.9022 0.293375 15.7072C-0.0976249 15.3162 -0.0976249 14.6842 0.292375 14.2942L2.82138 11.7653C1.17238 10.2913 0.198375 8.61425 0.128375 8.48925C-0.0416249 8.18625 -0.0426249 7.81725 0.126375 7.51425C0.251375 7.28925 3.24537 2.00025 8.00037 2.00025C9.33138 2.00025 10.5154 2.43125 11.5484 3.03825L14.2934 0.29325C14.6844 -0.09775 15.3164 -0.09775 15.7074 0.29325C16.0984 0.68425 16.0984 1.31625 15.7074 1.70725L1.70738 15.7072C1.51238 15.9022 1.25638 16.0002 1.00038 16.0002ZM8.00037 4.00025C5.14637 4.00025 2.95837 6.83525 2.18138 7.99925C2.55938 8.56225 3.28538 9.51025 4.24038 10.3463L6.07438 8.51225C6.02938 8.34825 6.00037 8.17825 6.00037 8.00025C6.00037 6.89525 6.89537 6.00025 8.00037 6.00025C8.17838 6.00025 8.34838 6.02925 8.51237 6.07425L10.0784 4.50825C9.43738 4.20125 8.74237 4.00025 8.00037 4.00025Z"
|
||||
fill="#F43F5E"
|
||||
/>
|
||||
</svg>
|
||||
<span className="ml-2">Generate</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Link className="text-sm underline hover:no-underline" to="/onboarding-2"><- Back</Link>
|
||||
<Link
|
||||
className="text-sm underline hover:no-underline"
|
||||
to="/onboarding-2"
|
||||
>
|
||||
<- Back
|
||||
</Link>
|
||||
<button className="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-auto">
|
||||
<Link to="/onboarding-4">Next Step -></Link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<div
|
||||
className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="flex mt-40 flex-col items-center gap-2.5">
|
||||
<img className="object-cover object-center" src={generateSvgAvatar(store.pubKey)} width="493px" height="493px" alt="Onboarding" />
|
||||
<img
|
||||
className="object-cover object-center"
|
||||
src={generateSvgAvatar(store.pubKey)}
|
||||
width="493px"
|
||||
height="493px"
|
||||
alt="Onboarding"
|
||||
/>
|
||||
<span className="text-sm">Your generated Digital ID</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import Logo from "../images/logo.png";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import { store } from "../store/store";
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import Logo from '../images/logo.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { store } from '../store/store';
|
||||
|
||||
const Onboarding4 = observer(() => {
|
||||
|
||||
const [checkBoxesSelected, setCheckBoxesSelected] = useState([]);
|
||||
|
||||
function createAccount() {
|
||||
if(checkBoxesSelected.length === 2) {
|
||||
if (checkBoxesSelected.length === 2) {
|
||||
store.pushAccountData();
|
||||
sessionStorage.setItem('passPhrase', store.passPhrase);
|
||||
store.clearDataRegistration();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function saveCheckBoxesValues(e) {
|
||||
const { id, checked } = e.target;
|
||||
setCheckBoxesSelected([...checkBoxesSelected, id]);
|
||||
if (!checked) {
|
||||
setCheckBoxesSelected(checkBoxesSelected.filter(item => item !== id));
|
||||
setCheckBoxesSelected(checkBoxesSelected.filter((item) => item !== id));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const firstNameAccount = store.accountData.length && store.accountData.find(item => item.key === "firstname").value;
|
||||
const firstNameAccount =
|
||||
store.accountData.length &&
|
||||
store.accountData.find((item) => item.key === 'firstname').value;
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/dashboard">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
<div className="text-sm">
|
||||
Have an account? <Link className="font-medium text-indigo-500 hover:text-indigo-600" to="/signIn">Sign In</Link>
|
||||
Have an account?{' '}
|
||||
<Link
|
||||
className="font-medium text-indigo-500 hover:text-indigo-600"
|
||||
to="/signIn"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,19 +56,42 @@ const Onboarding4 = observer(() => {
|
||||
<div className="px-4 pt-12 pb-[85px]">
|
||||
<div className="max-w-md mx-auto w-full">
|
||||
<div className="relative">
|
||||
<div className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200" aria-hidden="true"></div>
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -mt-px w-full h-0.5 bg-slate-200"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<ul className="relative flex justify-between w-full">
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/">1</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-2">2</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-2"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-3">3</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-3"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white" to="/onboarding-4">4</span>
|
||||
<span
|
||||
className="flex items-center justify-center w-6 h-6 rounded-full text-xs font-semibold bg-indigo-500 text-white"
|
||||
to="/onboarding-4"
|
||||
>
|
||||
4
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -76,50 +101,98 @@ const Onboarding4 = observer(() => {
|
||||
|
||||
<div className="px-4 mt-40 py-8">
|
||||
<div className=" flex flex-col items-center gap-20 max-w-md mx-auto">
|
||||
|
||||
<div className="text-center">
|
||||
<svg className="inline-flex w-16 h-16 fill-current mb-6" viewBox="0 0 64 64">
|
||||
<circle className="text-emerald-100" cx="32" cy="32" r="32" />
|
||||
<path className="text-emerald-500" d="m28.5 41-8-8 3-3 5 5 12-12 3 3z" />
|
||||
<svg
|
||||
className="inline-flex w-16 h-16 fill-current mb-6"
|
||||
viewBox="0 0 64 64"
|
||||
>
|
||||
<circle
|
||||
className="text-emerald-100"
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="32"
|
||||
/>
|
||||
<path
|
||||
className="text-emerald-500"
|
||||
d="m28.5 41-8-8 3-3 5 5 12-12 3 3z"
|
||||
/>
|
||||
</svg>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-8">{firstNameAccount ? `Nice to meet you, ${firstNameAccount} 🙌` : 'Please, go back step 2'}</h1>
|
||||
<button onClick={createAccount} className="btn px-6 bg-indigo-500 hover:bg-indigo-600 text-white">
|
||||
<Link id="link-dashboard" to={checkBoxesSelected.length===2 && "/digitalId/profile-id"}>Go To Profile -></Link>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-8">
|
||||
{firstNameAccount
|
||||
? `Nice to meet you, ${firstNameAccount} 🙌`
|
||||
: 'Please, go back step 2'}
|
||||
</h1>
|
||||
<button
|
||||
onClick={createAccount}
|
||||
className="btn px-6 bg-indigo-500 hover:bg-indigo-600 text-white"
|
||||
>
|
||||
<Link
|
||||
id="link-dashboard"
|
||||
to={
|
||||
checkBoxesSelected.length === 2 &&
|
||||
'/digitalId/profile-id'
|
||||
}
|
||||
>
|
||||
Go To Profile ->
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-1">
|
||||
<label className="flex items-center">
|
||||
<input id="checkbox-1" onChange={(e)=>saveCheckBoxesValues(e)} type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm ml-2">I understand that Idntty cannot recover pass phrase</span>
|
||||
<input
|
||||
id="checkbox-1"
|
||||
onChange={(e) => saveCheckBoxesValues(e)}
|
||||
type="checkbox"
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span className="text-sm ml-2">
|
||||
I understand that Idntty cannot recover pass phrase
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center">
|
||||
<input id="checkbox-2" onChange={(e)=>saveCheckBoxesValues(e)} type="checkbox" className="form-checkbox" />
|
||||
<input
|
||||
id="checkbox-2"
|
||||
onChange={(e) => saveCheckBoxesValues(e)}
|
||||
type="checkbox"
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<div className="text-sm ml-2">
|
||||
I <Link className="text-indigo-500 hover:text-indigo-600 underline" to="/">store my pharse</Link> very carefuly
|
||||
I{' '}
|
||||
<Link
|
||||
className="text-indigo-500 hover:text-indigo-600 underline"
|
||||
to="/"
|
||||
>
|
||||
store my pharse
|
||||
</Link>{' '}
|
||||
very carefuly
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<div
|
||||
className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="flex mt-40 flex-col items-center gap-2.5">
|
||||
<img className="object-cover object-center" src={generateSvgAvatar(store.pubKey)} width="493px" height="493px" alt="Onboarding" />
|
||||
<img
|
||||
className="object-cover object-center"
|
||||
src={generateSvgAvatar(store.pubKey)}
|
||||
width="493px"
|
||||
height="493px"
|
||||
alt="Onboarding"
|
||||
/>
|
||||
<span className="text-sm">Your generated Digital ID</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,55 +2,77 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import AuthImage from '../images/auth-image.jpg';
|
||||
import AuthDecoration from '../images/auth-decoration.png';
|
||||
import Logo from "../images/logo.png";
|
||||
import Logo from '../images/logo.png';
|
||||
|
||||
function ResetPassword() {
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative md:flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="md:w-1/2">
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/dashboard">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-sm mx-auto px-4 py-8">
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-6">Reset your Password ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-6">
|
||||
Reset your Password ✨
|
||||
</h1>
|
||||
{/* Form */}
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="email">Email Address <span className="text-rose-500">*</span></label>
|
||||
<input id="email" className="form-input w-full" type="email" />
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
className="form-input w-full"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end mt-6">
|
||||
<button className="btn bg-indigo-500 hover:bg-indigo-600 text-white whitespace-nowrap">Send Reset Link</button>
|
||||
<button className="btn bg-indigo-500 hover:bg-indigo-600 text-white whitespace-nowrap">
|
||||
Send Reset Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<img className="object-cover object-center w-full h-full" src={AuthImage} width="760" height="1024" alt="Authentication" />
|
||||
<img className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block" src={AuthDecoration} width="218" height="224" alt="Authentication decoration" />
|
||||
<div
|
||||
className="hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img
|
||||
className="object-cover object-center w-full h-full"
|
||||
src={AuthImage}
|
||||
width="760"
|
||||
height="1024"
|
||||
alt="Authentication"
|
||||
/>
|
||||
<img
|
||||
className="absolute top-1/4 left-0 transform -translate-x-1/2 ml-8 hidden lg:block"
|
||||
src={AuthDecoration}
|
||||
width="218"
|
||||
height="224"
|
||||
alt="Authentication decoration"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,100 +1,153 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import User06 from "../images/user-28-06.jpg";
|
||||
import User08 from "../images/user-28-08.jpg";
|
||||
import User09 from "../images/user-28-09.jpg";
|
||||
import SharedDataRoadMap from "../partials/sharedData/SharedDataRoadmap";
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, Navigate, useParams } from 'react-router-dom';
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import User06 from '../images/user-28-06.jpg';
|
||||
import User08 from '../images/user-28-08.jpg';
|
||||
import User09 from '../images/user-28-09.jpg';
|
||||
import SharedDataRoadMap from '../partials/sharedData/SharedDataRoadmap';
|
||||
import { store } from '../store/store';
|
||||
import { fetchWrapper } from '../shared/fetchWrapper';
|
||||
import { cryptography } from '@liskhq/lisk-client';
|
||||
import { generateTransaction } from '../utils/Utils';
|
||||
|
||||
function SharedData() {
|
||||
const [encryptedData, setEncryptedData] = useState([]);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
||||
const sharedDataItems = {
|
||||
data: localStorage.getItem('digitalId'),
|
||||
items: [
|
||||
{
|
||||
dataShared: localStorage.getItem('digitalId'),
|
||||
id: '923',
|
||||
text: 'United Kindom',
|
||||
typeItem: 'Residence',
|
||||
usersImges: [
|
||||
{
|
||||
size: 32,
|
||||
img: User06,
|
||||
imgId: "90"
|
||||
},
|
||||
{
|
||||
size: 32,
|
||||
img: User08,
|
||||
imgId: "93"
|
||||
},
|
||||
{
|
||||
size: 32,
|
||||
img: User09,
|
||||
imgId: "89"
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
dataShared: localStorage.getItem('digitalId'),
|
||||
id: '401',
|
||||
text: 'A123B3143',
|
||||
typeItem: 'Document ID',
|
||||
usersImges: [
|
||||
{
|
||||
size: 32,
|
||||
img: User06,
|
||||
imgId: "67"
|
||||
},
|
||||
{
|
||||
size: 32,
|
||||
img: User08,
|
||||
imgId: "71"
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
const { id } = useParams();
|
||||
|
||||
const [address, pubKey] = id.split(':');
|
||||
|
||||
const passPhrase = sessionStorage.getItem('passPhrase');
|
||||
|
||||
useEffect(() => {
|
||||
fetchWrapper
|
||||
.get(`data/shared/${id}`)
|
||||
.then((res) => setEncryptedData(res.data ?? []))
|
||||
.catch((err) => console.log(err));
|
||||
}, [id]);
|
||||
|
||||
const decryptedData = useMemo(() => {
|
||||
return encryptedData.map((item) => {
|
||||
let seed, value, hash;
|
||||
try {
|
||||
[seed, value] = cryptography
|
||||
.decryptMessageWithPassphrase(
|
||||
item.value,
|
||||
item.value_nonce,
|
||||
passPhrase,
|
||||
Buffer.from(pubKey, 'hex')
|
||||
)
|
||||
.split(':');
|
||||
hash = cryptography
|
||||
.hash(
|
||||
Buffer.concat([
|
||||
Buffer.from(seed, 'utf8'),
|
||||
cryptography.hash(value, 'utf8'),
|
||||
])
|
||||
)
|
||||
.toString('hex');
|
||||
} catch (err) {
|
||||
setHasError(true);
|
||||
console.log(err);
|
||||
} finally {
|
||||
return {
|
||||
seed,
|
||||
value,
|
||||
hash,
|
||||
label: item.label,
|
||||
};
|
||||
}
|
||||
});
|
||||
}, [encryptedData]);
|
||||
|
||||
const validateAccountData = () => {
|
||||
const builder = generateTransaction(
|
||||
BigInt(store.accountInfo?.sequence?.nonce || 0),
|
||||
store.pubKey,
|
||||
store.nodeInfo.networkIdentifier,
|
||||
store.passPhrase,
|
||||
store.nodeInfo?.genesisConfig?.minFeePerByte,
|
||||
store.nodeInfo?.genesisConfig?.baseFees
|
||||
);
|
||||
|
||||
const signedTx = builder.validate(
|
||||
decryptedData.map((e) => ({
|
||||
label: e.label,
|
||||
value: Buffer.from(e.hash, 'hex'),
|
||||
})),
|
||||
address
|
||||
);
|
||||
|
||||
if (signedTx) {
|
||||
fetchWrapper
|
||||
.post('transactions', {}, signedTx)
|
||||
.catch((err) => console.log(err));
|
||||
}
|
||||
};
|
||||
|
||||
if (!passPhrase) return <Navigate to="/" replace={true} />;
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="px-4 mt-16 py-9">
|
||||
<div className=" flex flex-col max-w-md mx-auto">
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-8">Shared data ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-8">
|
||||
Shared data ✨
|
||||
</h1>
|
||||
<ul>
|
||||
{sharedDataItems.items.map(item => {
|
||||
return(
|
||||
<li key={item.id}>
|
||||
<SharedDataRoadMap data={item}/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{!hasError &&
|
||||
decryptedData.map((item) => (
|
||||
<li key={item.label}>
|
||||
<SharedDataRoadMap data={item} />
|
||||
</li>
|
||||
))}
|
||||
{hasError &&
|
||||
encryptedData.map((item) => (
|
||||
<li key={item.label}>
|
||||
<SharedDataRoadMap data={item} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{!hasError && (
|
||||
<button
|
||||
className="mt-12 btn bg-indigo-500 hover:bg-indigo-600 text-white"
|
||||
onClick={validateAccountData}
|
||||
>
|
||||
Validate data
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<div
|
||||
className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="flex mt-40 flex-col items-center gap-2.5">
|
||||
<img className="object-cover object-center" src={generateSvgAvatar()} width="493px" height="493px" alt="Onboarding" />
|
||||
<span className="text-sm">{sharedDataItems.data}</span>
|
||||
<Link className="mt-12 btn bg-indigo-500 hover:bg-indigo-600 text-white" to="/dashboard">Get my digital ID</Link>
|
||||
<img
|
||||
className="object-cover object-center"
|
||||
src={generateSvgAvatar(store.pubKey)}
|
||||
width="493px"
|
||||
height="493px"
|
||||
alt="Onboarding"
|
||||
/>
|
||||
<Link
|
||||
className="mt-12 btn bg-indigo-500 hover:bg-indigo-600 text-white"
|
||||
to="/"
|
||||
>
|
||||
Get my digital ID
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import React, {useState} from 'react';
|
||||
import {observer} from "mobx-react-lite";
|
||||
import React, { useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import Logo from "../images/logo.png";
|
||||
import {store} from "../store/store";
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import Logo from '../images/logo.png';
|
||||
import { store } from '../store/store';
|
||||
|
||||
const SignIn = observer(()=>{
|
||||
const SignIn = observer(() => {
|
||||
const [passPhrase, setPassPhrase] = useState('- - - - - - - - - - - -');
|
||||
const [addedPassPhrase, setAddedPassPhrase] = useState(false);
|
||||
|
||||
const[passPhrase, setPassPhrase] = useState('- - - - - - - - - - - -');
|
||||
const[addedPassPhrase, setAddedPassPhrase] = useState(false);
|
||||
|
||||
function savePassPhrase (copiedPhrase) {
|
||||
function savePassPhrase(copiedPhrase) {
|
||||
if (copiedPhrase.split(' ').length === 12) {
|
||||
store.savePastPassPhrase(copiedPhrase.trim());
|
||||
setPassPhrase(store.passPhrase);
|
||||
@@ -19,55 +18,67 @@ const SignIn = observer(()=>{
|
||||
}
|
||||
|
||||
function convertPassPhraseToArray() {
|
||||
return passPhrase.split(' ').map((item, index) => ({str:item, id:index}));
|
||||
return passPhrase
|
||||
.split(' ')
|
||||
.map((item, index) => ({ str: item, id: index }));
|
||||
}
|
||||
|
||||
function pastePassPhrase() {
|
||||
navigator.clipboard.readText().then(res => savePassPhrase(res));
|
||||
navigator.clipboard.readText().then((res) => savePassPhrase(res));
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="bg-white">
|
||||
|
||||
<div className="relative flex">
|
||||
|
||||
{/* Content */}
|
||||
<div className="w-full md:w-1/2">
|
||||
|
||||
<div className="min-h-screen h-full flex flex-col after:flex-1">
|
||||
|
||||
<div className="flex-1">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo */}
|
||||
<Link className="block" to="/">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</Link>
|
||||
<div className="text-sm">
|
||||
Have not an account? <Link className="font-medium text-indigo-500 hover:text-indigo-600" to="/">Sign Up</Link>
|
||||
Have not an account?{' '}
|
||||
<Link
|
||||
className="font-medium text-indigo-500 hover:text-indigo-600"
|
||||
to="/"
|
||||
>
|
||||
Sign Up
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-4 mt-20 py-8">
|
||||
<div className="max-w-fit mx-auto flex flex-col gap-y-36">
|
||||
|
||||
<div>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">Company information ✨</h1>
|
||||
<h1 className="text-3xl text-slate-800 font-bold mb-12">
|
||||
Company information ✨
|
||||
</h1>
|
||||
{/* htmlForm */}
|
||||
<div className="grid grid-cols-4 gap-y-2.5 gap-[18px] mb-14">
|
||||
{convertPassPhraseToArray().map(tag => (
|
||||
<button key={tag.id} className="min-w-[93px] min-h-[26px] bg-slate-100 text-slate-500 cursor-pointer hover:bg-blue-100 hover:text-blue-600 rounded-full text-center px-2.5">
|
||||
{convertPassPhraseToArray().map((tag) => (
|
||||
<button
|
||||
key={tag.id}
|
||||
className="min-w-[93px] min-h-[26px] bg-slate-100 text-slate-500 cursor-pointer hover:bg-blue-100 hover:text-blue-600 rounded-full text-center px-2.5"
|
||||
>
|
||||
<span className="text-sm font-medium">{tag.str}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex pl-7 gap-x-5">
|
||||
<button onClick={pastePassPhrase} className="btn pl-9 pr-16 border-slate-200 hover:border-slate-300 text-slate-600">
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 shrink-0 mb-0.5" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M11.7.3c-.4-.4-1-.4-1.4 0l-10 10c-.2.2-.3.4-.3.7v4c0 .6.4 1 1 1h4c.3 0 .5-.1.7-.3l10-10c.4-.4.4-1 0-1.4l-4-4zM4.6 14H2v-2.6l6-6L10.6 8l-6 6zM12 6.6L9.4 4 11 2.4 13.6 5 12 6.6z"></path>
|
||||
<button
|
||||
onClick={pastePassPhrase}
|
||||
className="btn pl-9 pr-16 border-slate-200 hover:border-slate-300 text-slate-600"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current text-slate-400 shrink-0 mb-0.5"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M11.7.3c-.4-.4-1-.4-1.4 0l-10 10c-.2.2-.3.4-.3.7v4c0 .6.4 1 1 1h4c.3 0 .5-.1.7-.3l10-10c.4-.4.4-1 0-1.4l-4-4zM4.6 14H2v-2.6l6-6L10.6 8l-6 6zM12 6.6L9.4 4 11 2.4 13.6 5 12 6.6z"></path>
|
||||
</svg>
|
||||
<span className="ml-2">Paste</span>
|
||||
</button>
|
||||
@@ -75,29 +86,37 @@ const SignIn = observer(()=>{
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<button className="btn bg-indigo-500 hover:bg-indigo-600 text-white ml-auto">
|
||||
<Link to={addedPassPhrase && "/digitalId/profile-id"}>Go To Profile -></Link>
|
||||
<Link to={addedPassPhrase && '/digitalId/profile-id'}>
|
||||
Go To Profile ->
|
||||
</Link>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2" aria-hidden="true">
|
||||
<div
|
||||
className="flex flex-col items-center h-full w-full hidden md:block absolute top-0 bottom-0 right-0 md:w-1/2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="flex mt-40 flex-col items-center gap-2.5">
|
||||
<div className={`${addedPassPhrase || 'opacity-0'} w-[493px] h-[493px]`}>
|
||||
<img className="object-cover object-center" alt="" src={generateSvgAvatar(store.pubKey)}/>
|
||||
<div
|
||||
className={`${
|
||||
addedPassPhrase || 'opacity-0'
|
||||
} w-[493px] h-[493px]`}
|
||||
>
|
||||
<img
|
||||
className="object-cover object-center"
|
||||
alt=""
|
||||
src={generateSvgAvatar(store.pubKey)}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm">Your Digital ID</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { store } from "../../store/store";
|
||||
import { store } from '../../store/store';
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import Header from '../../partials/Header';
|
||||
import ProfileTable from '../../partials/profile/ProfileTable';
|
||||
import Image from '../../images/transactions-image-04.svg';
|
||||
import {labelMap} from '../../shared/labelMap';
|
||||
import { labelMap } from '../../shared/labelMap';
|
||||
import { statusMap } from '../../shared/statusMap';
|
||||
|
||||
const initialPropertyValues = ['First name', 'Second name', 'Birthdate', 'Gender', 'National doctype', 'National doc ID', 'National doc issue date', 'National doc expiry date'];
|
||||
const initialPropertyValues = [
|
||||
'First name',
|
||||
'Second name',
|
||||
'Birthdate',
|
||||
'Gender',
|
||||
'National doctype',
|
||||
'National doc ID',
|
||||
'National doc issue date',
|
||||
'National doc expiry date',
|
||||
];
|
||||
|
||||
const Profile = observer (() => {
|
||||
const Profile = observer(() => {
|
||||
const defaultValues = {
|
||||
label: '',
|
||||
value: '',
|
||||
seed: String(Math.floor(Math.random() * 90000000000000000000), 10),
|
||||
};
|
||||
|
||||
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [buttonPanelOpen, setButtonPanelOpen] = useState(true);
|
||||
const [removePanelOpen, setRemovePanelOpen] = useState(false);
|
||||
const [changePanelOpen, setChangePanelOpen] = useState(false);
|
||||
const [storeOnBlockchain, setStoreOnBlockchain] = useState(false);
|
||||
const [addPanelOpen, setAddPanelOpen] = useState(false);
|
||||
const [sharePanelOpen, setSharePanelOpen] = useState(false);
|
||||
const [updatePanelOpen, setUpdatePanelOpen] = useState(false);
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const [propertyValues, setPropertyValues] = useState([]);
|
||||
@@ -29,17 +40,19 @@ const Profile = observer (() => {
|
||||
const [addedValues, setAddedValues] = useState(defaultValues);
|
||||
const [isCheck, setIsCheck] = useState([]);
|
||||
const [alreadyExists, setAlreadyExists] = useState(false);
|
||||
const [blockChainValue, setBlockChainValue] = useState('');
|
||||
const [publicKey, setPublicKey] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
handleSelectedItems(isCheck);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCheck]);
|
||||
|
||||
const handleClick = e => {
|
||||
const handleClick = (e) => {
|
||||
const { id, checked } = e.target;
|
||||
setIsCheck([...isCheck, id]);
|
||||
if (!checked || !id) {
|
||||
setIsCheck(isCheck.filter(item => item !== id));
|
||||
setIsCheck(isCheck.filter((item) => item !== id));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,24 +66,29 @@ const Profile = observer (() => {
|
||||
setAddedValues((prevState) => ({
|
||||
...prevState,
|
||||
[field]: value,
|
||||
}))
|
||||
}));
|
||||
};
|
||||
|
||||
const changeUpdatedValues = (value, field, key) => {
|
||||
if (field === 'value') {
|
||||
setBlockChainValue(value);
|
||||
}
|
||||
setUpdatedValues((prevState) =>
|
||||
prevState.map((elem) => (
|
||||
(elem.key === key) ? {
|
||||
...elem,
|
||||
[field]: value,
|
||||
} : elem
|
||||
))
|
||||
)
|
||||
prevState.map((elem) =>
|
||||
elem.key === key
|
||||
? {
|
||||
...elem,
|
||||
[field]: value,
|
||||
}
|
||||
: elem
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const selectHint = (element) => {
|
||||
changeAddedValues(element, 'label');
|
||||
setPropertyValues([]);
|
||||
}
|
||||
};
|
||||
|
||||
const closeHint = (eventTarget) => {
|
||||
if (!eventTarget || eventTarget.tabIndex !== -1) {
|
||||
@@ -80,12 +98,21 @@ const Profile = observer (() => {
|
||||
|
||||
const handleSelectedItems = (selectedItems) => {
|
||||
setSelectedItems([...selectedItems]);
|
||||
setUpdatedValues(store.decryptedAccountData.filter(({ key }) => selectedItems.includes(key))
|
||||
.map(elem => (
|
||||
(!elem.seed) ? {
|
||||
...elem,
|
||||
seed: String(Math.floor(Math.random() * 90000000000000000000), 10),
|
||||
}: elem)));
|
||||
setUpdatedValues(
|
||||
store.decryptedAccountData
|
||||
.filter(({ key }) => selectedItems.includes(key))
|
||||
.map((elem) =>
|
||||
!elem.seed
|
||||
? {
|
||||
...elem,
|
||||
seed: String(
|
||||
Math.floor(Math.random() * 90000000000000000000),
|
||||
10
|
||||
),
|
||||
}
|
||||
: elem
|
||||
)
|
||||
);
|
||||
if (selectedItems.length > 0) {
|
||||
setButtonPanelOpen(false);
|
||||
setAddPanelOpen(false);
|
||||
@@ -108,8 +135,9 @@ const Profile = observer (() => {
|
||||
};
|
||||
|
||||
const sendAddedData = () => {
|
||||
const checkingAddedData = !store.decryptedAccountData
|
||||
.some((element) => element.label === addedValues.label)
|
||||
const checkingAddedData = !store.decryptedAccountData.some(
|
||||
(element) => element.label === addedValues.label
|
||||
);
|
||||
setAlreadyExists(!checkingAddedData);
|
||||
if (checkingAddedData) {
|
||||
setAddPanelOpen(false);
|
||||
@@ -127,41 +155,65 @@ const Profile = observer (() => {
|
||||
};
|
||||
|
||||
const deleteDataParameters = () => {
|
||||
const changeData = store.decryptedAccountData.filter(item=>!selectedItems.includes(item.key))
|
||||
const changeData = store.decryptedAccountData.filter(
|
||||
(item) => !selectedItems.includes(item.key)
|
||||
);
|
||||
store.pushAccountData(changeData);
|
||||
if (storeOnBlockchain)
|
||||
store.pushAccountDataToBlockchain(
|
||||
changeData.filter((elem) => elem.status !== 'Stored')
|
||||
);
|
||||
};
|
||||
|
||||
const changeInitialArray = () => {
|
||||
let newArr = [];
|
||||
store.decryptedAccountData.map(elem => {
|
||||
updatedValues.forEach(item => {
|
||||
store.decryptedAccountData.map((elem) => {
|
||||
updatedValues.forEach((item) => {
|
||||
if (elem.label === item.label) {
|
||||
newArr.push({
|
||||
...elem,
|
||||
'value': item.value,
|
||||
'seed': item.seed,
|
||||
})
|
||||
status: 'new',
|
||||
value: item.value,
|
||||
seed: item.seed,
|
||||
});
|
||||
}
|
||||
})
|
||||
if (!newArr.find(element => element.label === elem.label)) {
|
||||
});
|
||||
if (!newArr.find((element) => element.label === elem.label)) {
|
||||
newArr.push(elem);
|
||||
}
|
||||
})
|
||||
});
|
||||
return newArr;
|
||||
};
|
||||
|
||||
const changeDataParameters = () => {
|
||||
const updatedData = changeInitialArray();
|
||||
store.pushAccountData(updatedData);
|
||||
if (storeOnBlockchain)
|
||||
store.pushAccountDataToBlockchain(
|
||||
updatedData.filter((elem) => elem.status !== 'Stored')
|
||||
);
|
||||
};
|
||||
|
||||
const shareAccountData = () => {
|
||||
const sharedData = store.decryptedAccountData.filter((item) =>
|
||||
selectedItems.includes(item.key)
|
||||
);
|
||||
store.pushSharedData(sharedData, publicKey);
|
||||
cancelSharePanel();
|
||||
};
|
||||
|
||||
const addDataParameters = () => {
|
||||
const label = addedValues.label.toLowerCase().split(' ').join('');
|
||||
if(labelMap[label])
|
||||
if (labelMap[label])
|
||||
addedValues.key = addedValues.label.toLowerCase().split(' ').join('');
|
||||
else
|
||||
addedValues.key = addedValues.label
|
||||
else addedValues.key = addedValues.label;
|
||||
store.pushAccountData(store.decryptedAccountData.concat(addedValues));
|
||||
if (storeOnBlockchain)
|
||||
store.pushAccountDataToBlockchain(
|
||||
store.decryptedAccountData
|
||||
.concat(addedValues)
|
||||
.filter((elem) => elem.status !== 'Stored')
|
||||
);
|
||||
};
|
||||
|
||||
const cancelAddPanel = () => {
|
||||
@@ -171,12 +223,21 @@ const Profile = observer (() => {
|
||||
};
|
||||
|
||||
const cancelUpdatePanel = () => {
|
||||
setUpdatedValues(store.decryptedAccountData.filter(({ key }) => selectedItems.includes(key))
|
||||
.map(elem => (
|
||||
(!elem.seed) ? {
|
||||
...elem,
|
||||
seed: String(Math.floor(Math.random() * 90000000000000000000), 10),
|
||||
}: elem)));
|
||||
setUpdatedValues(
|
||||
store.decryptedAccountData
|
||||
.filter(({ key }) => selectedItems.includes(key))
|
||||
.map((elem) =>
|
||||
!elem.seed
|
||||
? {
|
||||
...elem,
|
||||
seed: String(
|
||||
Math.floor(Math.random() * 90000000000000000000),
|
||||
10
|
||||
),
|
||||
}
|
||||
: elem
|
||||
)
|
||||
);
|
||||
setUpdatePanelOpen(false);
|
||||
setChangePanelOpen(true);
|
||||
setIsCheck([]);
|
||||
@@ -187,6 +248,16 @@ const Profile = observer (() => {
|
||||
setChangePanelOpen(false);
|
||||
};
|
||||
|
||||
const openSharePanel = () => {
|
||||
setSharePanelOpen(true);
|
||||
setChangePanelOpen(false);
|
||||
};
|
||||
|
||||
const cancelSharePanel = () => {
|
||||
setSharePanelOpen(false);
|
||||
setChangePanelOpen(true);
|
||||
};
|
||||
|
||||
const openUpdatePanel = () => {
|
||||
setUpdatePanelOpen(true);
|
||||
setChangePanelOpen(false);
|
||||
@@ -204,7 +275,9 @@ const Profile = observer (() => {
|
||||
setPropertyValues(initialPropertyValues);
|
||||
if (text) {
|
||||
let reg = new RegExp(`^${text}`, 'img');
|
||||
setPropertyValues((prevState) => prevState.filter((element) => element.match(reg)));
|
||||
setPropertyValues((prevState) =>
|
||||
prevState.filter((element) => element.match(reg))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -242,19 +315,26 @@ const Profile = observer (() => {
|
||||
</div>
|
||||
{/* Table */}
|
||||
<div>
|
||||
<ProfileTable isCheck={isCheck} handleClick={handleClick} userData={store.decryptedAccountData}/>
|
||||
<ProfileTable
|
||||
isCheck={isCheck}
|
||||
handleClick={handleClick}
|
||||
userData={store.decryptedAccountData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Left sidebar */}
|
||||
<div>
|
||||
{/* Button panel*/}
|
||||
<div className={`${!buttonPanelOpen && "hidden"} ml-2.5`}>
|
||||
<div className={`${!buttonPanelOpen && 'hidden'} ml-2.5`}>
|
||||
<div className="bg-white px-5 py-[43px] shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80">
|
||||
<button
|
||||
className="btn w-full bg-indigo-500 hover:bg-indigo-600 text-white"
|
||||
onClick={openAddPanel}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current shrink-0" viewBox="0 0 16 16">
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="m2.457 8.516.969-.99 2.516 2.481 5.324-5.304.985.989-6.309 6.284z" />
|
||||
</svg>
|
||||
<span className="ml-1">Add new field</span>
|
||||
@@ -263,13 +343,24 @@ const Profile = observer (() => {
|
||||
</div>
|
||||
<div className="ml-2.5">
|
||||
{/* Add panel */}
|
||||
<div className={`${!addPanelOpen && 'hidden'} relative flex flex-col bg-white p-5 shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-11`}>
|
||||
<div className='relative z-20'>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Property <span className="text-rose-500">*</span></label>
|
||||
<div
|
||||
className={`${
|
||||
!addPanelOpen && 'hidden'
|
||||
} relative flex flex-col bg-white p-5 shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-11`}
|
||||
>
|
||||
<div className="relative z-20">
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Property <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
id="label"
|
||||
className={`form-input w-full ${alreadyExists && 'border-rose-300'}`}
|
||||
className={`form-input w-full ${
|
||||
alreadyExists && 'border-rose-300'
|
||||
}`}
|
||||
type="text"
|
||||
required
|
||||
onInput={(e) => handleInput(e.target.value, 'label')}
|
||||
@@ -277,48 +368,90 @@ const Profile = observer (() => {
|
||||
onClick={openHint}
|
||||
onBlur={(e) => closeHint(e.relatedTarget)}
|
||||
/>
|
||||
<div tabIndex='-1' className={`absolute w-full box-border bg-white flex flex-col gap-y-2 py-2 pl-2 border border-t-0 border-slate-200 rounded ${(propertyValues.length > 0) || 'hidden'}`}>
|
||||
<div
|
||||
tabIndex="-1"
|
||||
className={`absolute w-full box-border bg-white flex flex-col gap-y-2 py-2 pl-2 border border-t-0 border-slate-200 rounded ${
|
||||
propertyValues.length > 0 || 'hidden'
|
||||
}`}
|
||||
>
|
||||
<ul>
|
||||
{propertyValues.map((element, index) => (
|
||||
<li className='hover:text-indigo-600 cursor-pointer' key={index} onClick={() => selectHint(element)}>{element}</li>
|
||||
<li
|
||||
className="hover:text-indigo-600 cursor-pointer"
|
||||
key={index}
|
||||
onClick={() => selectHint(element)}
|
||||
>
|
||||
{element}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{ alreadyExists && <div className="text-xs mt-1 text-rose-500">This property already added!</div> }
|
||||
</div>
|
||||
{alreadyExists && (
|
||||
<div className="text-xs mt-1 text-rose-500">
|
||||
This property already added!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='my-3 z-10'>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Value <span className="text-rose-500">*</span></label>
|
||||
<div className="my-3 z-10">
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Value <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
id="value"
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onClick={() => setPropertyValues([])}
|
||||
onChange={(e) => changeAddedValues(e.target.value, 'value')}
|
||||
onChange={(e) =>
|
||||
changeAddedValues(e.target.value, 'value')
|
||||
}
|
||||
value={addedValues.value}
|
||||
/>
|
||||
</div>
|
||||
<div className='z-10'>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Seed <span className="text-rose-500">*</span></label>
|
||||
<div className="z-10">
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Seed <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
id="seed"
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onClick={() => setPropertyValues([])}
|
||||
onChange={(e) => changeAddedValues(e.target.value, 'seed')}
|
||||
onChange={(e) =>
|
||||
changeAddedValues(e.target.value, 'seed')
|
||||
}
|
||||
value={addedValues.seed}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row z-10 justify-between pt-[18px] pb-[23px] items-center border-b border-slate-200">
|
||||
<span className="block text-sm font-medium">Store data on blockchain</span>
|
||||
<span className="block text-sm font-medium">
|
||||
Store data on blockchain
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<div className="form-switch">
|
||||
<input type="checkbox" id="blockcheined" className="sr-only" checked={storeOnBlockchain} onChange={() => setStoreOnBlockchain(!storeOnBlockchain)} />
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span className="bg-white shadow-sm" aria-hidden="true"></span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="blockcheined"
|
||||
className="sr-only"
|
||||
checked={storeOnBlockchain}
|
||||
onChange={() =>
|
||||
setStoreOnBlockchain(!storeOnBlockchain)
|
||||
}
|
||||
/>
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span
|
||||
className="bg-white shadow-sm"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,57 +473,111 @@ const Profile = observer (() => {
|
||||
</div>
|
||||
</div>
|
||||
{/* Update panel */}
|
||||
<div className={`${!updatePanelOpen && 'hidden'} bg-white p-5 shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-11`}>
|
||||
<div
|
||||
className={`${
|
||||
!updatePanelOpen && 'hidden'
|
||||
} bg-white p-5 shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-11`}
|
||||
>
|
||||
{updatedValues.map((item, index) => (
|
||||
<div key={index} className="flex flex-col gap-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Property <span className="text-rose-500">*</span></label>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Property <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id={`${item.key}:label`}
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onChange={(e) => changeUpdatedValues(e.target.value, 'label', item.key)}
|
||||
onChange={(e) =>
|
||||
changeUpdatedValues(
|
||||
e.target.value,
|
||||
'label',
|
||||
item.key
|
||||
)
|
||||
}
|
||||
value={item.label}
|
||||
disabled='disabled'
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Value <span className="text-rose-500">*</span></label>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Value <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
id={`${item.key}:value`}
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onChange={(e) => changeUpdatedValues(e.target.value, 'value', item.key)}
|
||||
value={item.value}
|
||||
onChange={(e) =>
|
||||
changeUpdatedValues(
|
||||
e.target.value,
|
||||
'value',
|
||||
item.key
|
||||
)
|
||||
}
|
||||
value={
|
||||
item.status === statusMap.blockchained
|
||||
? blockChainValue
|
||||
: item.value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">Seed <span className="text-rose-500">*</span></label>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Seed <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
id={`${item.key}:seed`}
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onChange={(e) => changeUpdatedValues(e.target.value, 'seed', item.key)}
|
||||
onChange={(e) =>
|
||||
changeUpdatedValues(
|
||||
e.target.value,
|
||||
'seed',
|
||||
item.key
|
||||
)
|
||||
}
|
||||
value={item.seed}
|
||||
/>
|
||||
</div>
|
||||
{(updatedValues[index+1]) ? (
|
||||
<div className="mt-2 mb-3.5 items-center border-b border-slate-200"></div>
|
||||
{updatedValues[index + 1] ? (
|
||||
<div className="mt-2 mb-3.5 items-center border-b border-slate-200"></div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-row justify-between pt-[16px] pb-[23px] items-center border-b border-slate-200">
|
||||
<span className="block text-sm font-medium">Store data on blockchain</span>
|
||||
<span className="block text-sm font-medium">
|
||||
Store data on blockchain
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<div className="form-switch">
|
||||
<input type="checkbox" id="blockcheined" className="sr-only" checked={storeOnBlockchain} onChange={() => setStoreOnBlockchain(!storeOnBlockchain)} />
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span className="bg-white shadow-sm" aria-hidden="true"></span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="blockcheined"
|
||||
className="sr-only"
|
||||
checked={storeOnBlockchain}
|
||||
onChange={() =>
|
||||
setStoreOnBlockchain(!storeOnBlockchain)
|
||||
}
|
||||
/>
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span
|
||||
className="bg-white shadow-sm"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -412,20 +599,46 @@ const Profile = observer (() => {
|
||||
</div>
|
||||
</div>
|
||||
{/* Remove panel */}
|
||||
<div className={`${!removePanelOpen && 'hidden'} bg-white px-5 pt-4 pb-[190px] shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-12`}>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2">Summary</h2>
|
||||
<div
|
||||
className={`${
|
||||
!removePanelOpen && 'hidden'
|
||||
} bg-white px-5 pt-4 pb-[190px] shadow-lg rounded-sm border border-slate-200 lg:w-72 xl:w-80 mb-12`}
|
||||
>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2">
|
||||
Summary
|
||||
</h2>
|
||||
<div className="flex flex-col">
|
||||
{store.decryptedAccountData.filter(({ key }) => selectedItems.includes(key)).map(item => (
|
||||
<span key={item.label} className="text-sm font-normal text-slate-600 py-3 border-b border-slate-200">{item.label}</span>
|
||||
))}
|
||||
{store.decryptedAccountData
|
||||
.filter(({ key }) => selectedItems.includes(key))
|
||||
.map((item) => (
|
||||
<span
|
||||
key={item.label}
|
||||
className="text-sm font-normal text-slate-600 py-3 border-b border-slate-200"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-row justify-between pt-[16px] pb-7 items-center">
|
||||
<span className="block text-sm font-medium">On blockchain too</span>
|
||||
<span className="block text-sm font-medium">
|
||||
On blockchain too
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<div className="form-switch">
|
||||
<input type="checkbox" id="switch-1" className="sr-only" checked={storeOnBlockchain} onChange={() => setStoreOnBlockchain(!storeOnBlockchain)} />
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span className="bg-white shadow-sm" aria-hidden="true"></span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="switch-1"
|
||||
className="sr-only"
|
||||
checked={storeOnBlockchain}
|
||||
onChange={() =>
|
||||
setStoreOnBlockchain(!storeOnBlockchain)
|
||||
}
|
||||
/>
|
||||
<label className="bg-slate-400" htmlFor="switch-1">
|
||||
<span
|
||||
className="bg-white shadow-sm"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -434,36 +647,108 @@ const Profile = observer (() => {
|
||||
<button
|
||||
className="btn w-full bg-indigo-500 hover:bg-indigo-600 text-white px-[100px] justify-start"
|
||||
onClick={applyDataDeletion}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current shrink-0" viewBox="0 0 16 16">
|
||||
<path d="m2.457 8.516.969-.99 2.516 2.481 5.324-5.304.985.989-6.309 6.284z" />
|
||||
</svg>
|
||||
<span className="ml-2">Apply</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span className="text-descriptionSize text-slate-500 font-normal italic text-center">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do Terms.</span>
|
||||
<span className="text-descriptionSize text-slate-500 font-normal italic text-center">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
||||
sed do Terms.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Share panel */}
|
||||
<div
|
||||
className={`${
|
||||
!sharePanelOpen && 'hidden'
|
||||
} bg-white px-5 pt-4 shadow-lg rounded-sm border border-slate-200 mb-12 lg:w-72 xl:w-80`}
|
||||
>
|
||||
<div className="relative z-20">
|
||||
<div className="z-10">
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="mandatory"
|
||||
>
|
||||
Public Key <span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
autoComplete="off"
|
||||
id="seed"
|
||||
className="form-input w-full"
|
||||
type="text"
|
||||
required
|
||||
onChange={(e) => setPublicKey(e.target.value)}
|
||||
value={publicKey}
|
||||
/>
|
||||
</div>
|
||||
{/* Buttons */}
|
||||
<div className="flex flex-row z-10 justify-end gap-x-2 pt-[18px] pb-[14px] items-end">
|
||||
<button
|
||||
className="btn-sm bg-white border-slate-200 hover:bg-slate-50 text-slate-600"
|
||||
onClick={cancelSharePanel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn-sm bg-indigo-500 hover:bg-indigo-600 text-white"
|
||||
onClick={shareAccountData}
|
||||
>
|
||||
Share
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Details */}
|
||||
<div className={`${(!(removePanelOpen || addPanelOpen) || !storeOnBlockchain) && 'hidden'} drop-shadow-lg`}>
|
||||
<div
|
||||
className={`${
|
||||
(!(removePanelOpen || addPanelOpen) ||
|
||||
!storeOnBlockchain) &&
|
||||
'hidden'
|
||||
} drop-shadow-lg`}
|
||||
>
|
||||
{/* Top */}
|
||||
<div className="bg-white rounded-t-xl px-5 pb-4 text-center">
|
||||
<div className="mb-3 text-center">
|
||||
<img className="inline-flex w-12 h-12 rounded-full -mt-6" src={Image} width="48" height="48" alt="Transaction 04" />
|
||||
<img
|
||||
className="inline-flex w-12 h-12 rounded-full -mt-6"
|
||||
src={Image}
|
||||
width="48"
|
||||
height="48"
|
||||
alt="Transaction 04"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[31px] text-2xl font-semibold text-emerald-500 mb-1">
|
||||
0.012 IDN
|
||||
</div>
|
||||
<div className="text-sm font-medium text-slate-800 mb-3">
|
||||
Total amount fee
|
||||
</div>
|
||||
<div className="mt-[31px] text-2xl font-semibold text-emerald-500 mb-1">0.012 IDN</div>
|
||||
<div className="text-sm font-medium text-slate-800 mb-3">Total amount fee</div>
|
||||
</div>
|
||||
{/* Divider */}
|
||||
<div className="flex justify-between items-center" aria-hidden="true">
|
||||
<svg className="w-5 h-5 fill-white" xmlns="http://www.w3.org/2000/svg">
|
||||
<div
|
||||
className="flex justify-between items-center"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
<div className="grow w-full h-5 bg-white flex flex-col justify-center">
|
||||
<div className="h-px w-full border-t border-dashed border-slate-200" />
|
||||
</div>
|
||||
<svg className="w-5 h-5 fill-white rotate-180" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="w-5 h-5 fill-white rotate-180"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
@@ -471,15 +756,21 @@ const Profile = observer (() => {
|
||||
<div className="bg-white rounded-b-xl p-5 pt-[22px] pb-10 text-sm space-y-3">
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Validator:</span>
|
||||
<span className="font-medium text-slate-700 text-right">IT17 2207 1010 0504 0006 88</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
IT17 2207 1010 0504 0006 88
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Recipient:</span>
|
||||
<span className="font-medium text-slate-700 text-right">IT17 2207 1010 0504 0006 88</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
IT17 2207 1010 0504 0006 88
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Transaction:</span>
|
||||
<span className="font-medium text-slate-700 text-right">145 bytes</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
145 bytes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -490,8 +781,11 @@ const Profile = observer (() => {
|
||||
<button
|
||||
className="btn w-full bg-indigo-500 hover:bg-indigo-600 text-white px-[100px] justify-start"
|
||||
onClick={openRemovePanel}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current shrink-0" viewBox="0 0 16 16">
|
||||
<path d="m2.457 8.516.969-.99 2.516 2.481 5.324-5.304.985.989-6.309 6.284z" />
|
||||
</svg>
|
||||
<span className="ml-2">Remove</span>
|
||||
@@ -499,14 +793,23 @@ const Profile = observer (() => {
|
||||
<button
|
||||
className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600 px-[100px] justify-start"
|
||||
onClick={openUpdatePanel}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-rose-500 shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<svg className="w-4 h-4 fill-rose-500 shrink-0" viewBox="0 0 16 16">
|
||||
<path d="M14.682 2.318A4.485 4.485 0 0 0 11.5 1 4.377 4.377 0 0 0 8 2.707 4.383 4.383 0 0 0 4.5 1a4.5 4.5 0 0 0-3.182 7.682L8 15l6.682-6.318a4.5 4.5 0 0 0 0-6.364Zm-1.4 4.933L8 12.247l-5.285-5A2.5 2.5 0 0 1 4.5 3c1.437 0 2.312.681 3.5 2.625C9.187 3.681 10.062 3 11.5 3a2.5 2.5 0 0 1 1.785 4.251h-.003Z" />
|
||||
</svg>
|
||||
<span className="ml-2">Update</span>
|
||||
</button>
|
||||
<button className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600 px-[100px] justify-start">
|
||||
<svg className="w-4 h-4 fill-rose-500 shrink-0" viewBox="0 0 16 16">
|
||||
<button
|
||||
className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600 px-[100px] justify-start"
|
||||
onClick={openSharePanel}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-rose-500 shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M14.682 2.318A4.485 4.485 0 0 0 11.5 1 4.377 4.377 0 0 0 8 2.707 4.383 4.383 0 0 0 4.5 1a4.5 4.5 0 0 0-3.182 7.682L8 15l6.682-6.318a4.5 4.5 0 0 0 0-6.364Zm-1.4 4.933L8 12.247l-5.285-5A2.5 2.5 0 0 1 4.5 3c1.437 0 2.312.681 3.5 2.625C9.187 3.681 10.062 3 11.5 3a2.5 2.5 0 0 1 1.785 4.251h-.003Z" />
|
||||
</svg>
|
||||
<span className="ml-2">Share</span>
|
||||
@@ -519,7 +822,7 @@ const Profile = observer (() => {
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default Profile
|
||||
export default Profile;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import {observer} from "mobx-react-lite";
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import ValidateTable from "../../partials/validate/ValidateTable";
|
||||
import ValidatePanel from "../../partials/validate/ValidatePanel";
|
||||
import ValidateRoadMap from "../../partials/validationLog/ValidateRoadMap";
|
||||
import {Link} from "react-router-dom";
|
||||
import {store} from "../../store/store";
|
||||
import ValidateTable from '../../partials/validate/ValidateTable';
|
||||
import ValidatePanel from '../../partials/validate/ValidatePanel';
|
||||
import ValidateRoadMap from '../../partials/validationLog/ValidateRoadMap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { store } from '../../store/store';
|
||||
|
||||
const Validate = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
@@ -23,12 +23,19 @@ const Validate = observer(() => {
|
||||
<main className="relative">
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
<div className="w-fit">
|
||||
{/* Page header */}
|
||||
{/* Page header */}
|
||||
<div className="pb-2.5">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">0x28394710234192304719234</h1>
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
0x28394710234192304719234
|
||||
</h1>
|
||||
</div>
|
||||
<Link className="font-normal text-sm text-indigo-500 hover:text-indigo-600" to="/digitalId/validate">Explore -></Link>
|
||||
<Link
|
||||
className="font-normal text-sm text-indigo-500 hover:text-indigo-600"
|
||||
to="/digitalId/validate"
|
||||
>
|
||||
Explore ->
|
||||
</Link>
|
||||
<div className="mb-5 mt-5">
|
||||
<ul className="flex flex-wrap -m-1">
|
||||
<li className="m-1">
|
||||
@@ -53,20 +60,25 @@ const Validate = observer(() => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ValidateTable setValidatePanelOpen={setValidatePanelOpen}/>
|
||||
<ValidateTable setValidatePanelOpen={setValidatePanelOpen} />
|
||||
<div className="pb-8 border-b border-zinc-200 mt-[50px]">
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">Digital ID validation log ✨</h1>
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
Digital ID validation log ✨
|
||||
</h1>
|
||||
</div>
|
||||
{store.transactionsInfo.map(item => {
|
||||
return <ValidateRoadMap season={item} key={item.id}/>
|
||||
{store.transactionsInfo.map((item) => {
|
||||
return <ValidateRoadMap season={item} key={item.id} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<ValidatePanel validatePanelOpen={validatePanelOpen} setValidatePanelOpen={setValidatePanelOpen}/>
|
||||
<ValidatePanel
|
||||
validatePanelOpen={validatePanelOpen}
|
||||
setValidatePanelOpen={setValidatePanelOpen}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default Validate;
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
import React, {useState} from 'react';
|
||||
import ValidateRoadMap from "../../partials/validationLog/ValidateRoadMap";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import React, { useState } from 'react';
|
||||
import ValidateRoadMap from '../../partials/validationLog/ValidateRoadMap';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import {store} from "../../store/store";
|
||||
import { store } from '../../store/store';
|
||||
|
||||
const ValidationLog = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
{/* Content area */}
|
||||
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
{/* Site header */}
|
||||
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
<main>
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
{/* Page header */}
|
||||
<div className="pb-8 border-b border-zinc-200">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">Digital ID transaction log ✨</h1>
|
||||
</div>
|
||||
{/*Psosts*/}
|
||||
<div className="max-w-3xl m-auto mt-6">
|
||||
<div className="xl:-translate-x-16 max-w-fit">
|
||||
{/* PostsID */}
|
||||
{store.transactionsInfo.map(item => {
|
||||
return <ValidateRoadMap season={item} key={item.id}/>
|
||||
})}
|
||||
</div>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
{/* Content area */}
|
||||
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
|
||||
{/* Site header */}
|
||||
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
<main>
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
{/* Page header */}
|
||||
<div className="pb-8 border-b border-zinc-200">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
Digital ID transaction log ✨
|
||||
</h1>
|
||||
</div>
|
||||
{/*Psosts*/}
|
||||
<div className="max-w-3xl m-auto mt-6">
|
||||
<div className="xl:-translate-x-16 max-w-fit">
|
||||
{/* PostsID */}
|
||||
{store.transactionsInfo.map((item) => {
|
||||
return <ValidateRoadMap season={item} key={item.id} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ValidationLog;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import React, { useState } from 'react';
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import {store} from "../../store/store";
|
||||
import { store } from '../../store/store';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const Verify = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [descriptionOpen, setDescriptionOpen] = useState(false);
|
||||
|
||||
const openShareLink = (id) => {
|
||||
window.open(`${window.location.origin}/#/shared-data/${id}`, '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
@@ -20,48 +24,62 @@ const Verify = observer(() => {
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-11 w-full max-w-9xl mx-auto">
|
||||
{/* Page header */}
|
||||
<div className="mb-11">
|
||||
<div>
|
||||
<ul className="flex flex-wrap -m-1">
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-transparent shadow-sm bg-indigo-500 text-white duration-150 ease-in-out">
|
||||
View All
|
||||
</button>
|
||||
</li>
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-slate-200 hover:border-slate-300 shadow-sm bg-white text-slate-500 duration-150 ease-in-out">
|
||||
Validated
|
||||
</button>
|
||||
</li>
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-slate-200 hover:border-slate-300 shadow-sm bg-white text-slate-500 duration-150 ease-in-out">
|
||||
Blockchained
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="flex flex-wrap -m-1">
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-transparent shadow-sm bg-indigo-500 text-white duration-150 ease-in-out">
|
||||
View All
|
||||
</button>
|
||||
</li>
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-slate-200 hover:border-slate-300 shadow-sm bg-white text-slate-500 duration-150 ease-in-out">
|
||||
Validated
|
||||
</button>
|
||||
</li>
|
||||
<li className="m-1">
|
||||
<button className="inline-flex items-center justify-center text-sm font-medium leading-5 rounded-full px-3 py-1 border border-slate-200 hover:border-slate-300 shadow-sm bg-white text-slate-500 duration-150 ease-in-out">
|
||||
Blockchained
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/* Block */}
|
||||
<div className="flex flex-col gap-y-4">
|
||||
{store.sharedData.map((item, index) => (
|
||||
<div key={index} className="w-[1095px] pl-5 pr-[13px] pb-8 bg-white border border-slate-200 rounded shadow-[0_4px_6px_-1px_rgba(5,23,42,0.08)]">
|
||||
<div
|
||||
key={index}
|
||||
className="w-[1095px] pl-5 pr-[13px] pb-8 bg-white border border-slate-200 rounded shadow-[0_4px_6px_-1px_rgba(5,23,42,0.08)]"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex justify-end items-center h-[42px]">
|
||||
{/* Buttons */}
|
||||
<div className="flex gap-x-[6px]">
|
||||
<div className="flex items-center">
|
||||
<button className="text-slate-400 hover:text-slate-500 rounded-full">
|
||||
<button
|
||||
className="text-slate-400 hover:text-slate-500 rounded-full"
|
||||
onClick={() => openShareLink(item.id)}
|
||||
>
|
||||
<span className="sr-only">Link</span>
|
||||
<svg className="w-4 h-4 fill-current shrink-0 text-slate-400" viewBox="0 0 16 16">
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0 text-slate-400"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M11 0c1.3 0 2.6.5 3.5 1.5 1 .9 1.5 2.2 1.5 3.5 0 1.3-.5 2.6-1.4 3.5l-1.2 1.2c-.2.2-.5.3-.7.3-.2 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l1.1-1.2c.6-.5.9-1.3.9-2.1s-.3-1.6-.9-2.2C12 1.7 10 1.7 8.9 2.8L7.7 4c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l1.2-1.1C8.4.5 9.7 0 11 0ZM8.3 12c.4-.4 1-.5 1.4-.1.4.4.4 1 0 1.4l-1.2 1.2C7.6 15.5 6.3 16 5 16c-1.3 0-2.6-.5-3.5-1.5C.5 13.6 0 12.3 0 11c0-1.3.5-2.6 1.5-3.5l1.1-1.2c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4L2.9 8.9c-.6.5-.9 1.3-.9 2.1s.3 1.6.9 2.2c1.1 1.1 3.1 1.1 4.2 0L8.3 12Zm1.1-6.8c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4l-4.2 4.2c-.2.2-.5.3-.7.3-.2 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l4.2-4.2Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${descriptionOpen && 'rotate-180'}`}
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${
|
||||
descriptionOpen && 'rotate-180'
|
||||
}`}
|
||||
aria-expanded={descriptionOpen}
|
||||
>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<svg
|
||||
className="w-8 h-8 fill-current"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -69,13 +87,17 @@ const Verify = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className='flex flex-col gap-y-[9px]'>
|
||||
<div className="flex flex-col gap-y-[9px]">
|
||||
{item.data.map((elem, index) => (
|
||||
<div key={index} className='flex gap-y-2'>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-row items-center'>
|
||||
<span className='font-inter font-semibold text-slate-800 text-base w-[204px]'>{elem.label}</span>
|
||||
<span className='font-inter truncate font-semibold text-descriptionSize text-slate-600 underline w-[770px]'>{elem.value}</span>
|
||||
<div key={index} className="flex gap-y-2">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="font-inter font-semibold text-slate-800 text-base w-[204px]">
|
||||
{elem.label}
|
||||
</span>
|
||||
<span className="font-inter truncate font-semibold text-descriptionSize text-slate-600 underline w-[770px]">
|
||||
{elem.value}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,7 +110,7 @@ const Verify = observer(() => {
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default Verify;
|
||||
|
||||
119
src/pages/services/Delegates.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import DropdownTransaction from '../../components/DropdownTransaction';
|
||||
import TransactionsTable from '../../partials/finance/TransactionsTable';
|
||||
import PaginationClassic from '../../components/PaginationClassic';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import TransactionPanel from '../../partials/finance/TransactionPanel';
|
||||
import { Filters } from '../../components/Filters';
|
||||
|
||||
const filters = {
|
||||
'View All': 'all',
|
||||
Voted: 'Unvote',
|
||||
Pending: 'Pending',
|
||||
};
|
||||
|
||||
const Delegates = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [currentDelegate, setCurrentDelegate] = useState(null);
|
||||
const [currentFilter, setCurrentFilter] = useState('View All');
|
||||
|
||||
const [lockedFor, setLockedFor] = useState('Voiting');
|
||||
|
||||
const delegates = useMemo(() => {
|
||||
if (filters[currentFilter] === 'all') return store.delegates;
|
||||
return store.delegates.filter((e) => e.status === filters[currentFilter]);
|
||||
}, [store.delegates, currentFilter]);
|
||||
|
||||
const lockedLabel = (
|
||||
lockedFor === 'Voiting'
|
||||
? Object.values(store.accountSentVotes)
|
||||
: Object.values(store.accountLockedVotesCanReturn)
|
||||
.flat()
|
||||
.map((e) => e.amount)
|
||||
)
|
||||
.reduce((sum, e) => sum + e, 0n)
|
||||
.toString();
|
||||
|
||||
useEffect(() => {
|
||||
store.fetchDelegates(0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
{/* Content area */}
|
||||
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden bg-white">
|
||||
{/* Site header */}
|
||||
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
<main className="overflow-auto">
|
||||
{/* Content */}
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
{/* Page header */}
|
||||
<div className="sm:flex sm:justify-between sm:items-center mb-4 md:mb-2">
|
||||
{/* Left: Title */}
|
||||
<div className="mb-4 sm:mb-0">
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
{lockedLabel}
|
||||
/idn
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<span>Locked for </span>
|
||||
<DropdownTransaction value={lockedFor} onChange={setLockedFor} />
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Filters
|
||||
value={currentFilter}
|
||||
values={Object.keys(filters)}
|
||||
onChange={setCurrentFilter}
|
||||
/>
|
||||
|
||||
{/* Table */}
|
||||
<TransactionsTable
|
||||
data={delegates}
|
||||
lockedFor={lockedFor}
|
||||
rowClick={(delegate) =>
|
||||
(lockedFor === 'Voiting' ||
|
||||
store.accountLockedVotesCanReturnSum[delegate.address]) &&
|
||||
setCurrentDelegate(delegate)
|
||||
}
|
||||
/>
|
||||
|
||||
{/*/!* Pagination *!/*/}
|
||||
{/*<div className="mt-8">*/}
|
||||
{/* <PaginationClassic*/}
|
||||
{/* count={store.delegatesMeta.count}*/}
|
||||
{/* limit={store.delegatesMeta.limit}*/}
|
||||
{/* offset={store.delegatesMeta.offset}*/}
|
||||
{/* onNextPage={() => setFetchOffset((prev) => prev + 10)}*/}
|
||||
{/* onPrevPage={() => setFetchOffset((prev) => prev - 10)}*/}
|
||||
{/* />*/}
|
||||
{/*</div>*/}
|
||||
|
||||
<TransactionPanel
|
||||
transactionPanelOpen={!!currentDelegate}
|
||||
onClose={() => setCurrentDelegate(null)}
|
||||
delegate={currentDelegate || {}}
|
||||
unlocking={lockedFor !== 'Voiting'}
|
||||
postTransaction={(address, amount) => {
|
||||
if (lockedFor === 'Voiting') store.pushVoteTransaction(address, amount);
|
||||
else store.pushUnlockTransaction(address);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Delegates;
|
||||
105
src/pages/services/VPNPrepare.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import VPNServiceImg from '../../images/vpn_service.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const VPNPrepare = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
store.fetchVPNServers();
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const canVote = store.accountBalance > 20;
|
||||
|
||||
const clickButton = () => {
|
||||
if (canVote) navigate('/services/delegates');
|
||||
else window.open('https://testnet.idntty.org/faucet', '_blank');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (store.canVPN) navigate('/services/vpn/servers');
|
||||
}, [store.canVPN]);
|
||||
|
||||
if (store.loading && !store.canVPN) return null;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
{/* Content area */}
|
||||
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden bg-white">
|
||||
{/* Site header */}
|
||||
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
<main>
|
||||
<div className="lg:relative lg:flex border-b border-slate-200">
|
||||
{/* Content */}
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto">
|
||||
{/* Page header */}
|
||||
<div className="sm:flex sm:justify-between sm:items-center">
|
||||
{/* Left: Title */}
|
||||
<div className="mb-4 sm:mb-0">
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
VPN Service✨
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center py-16">
|
||||
<div className="flex w-2/4 flex-col gap-2 justify-center items-center">
|
||||
<img
|
||||
width="64"
|
||||
height="64"
|
||||
src={VPNServiceImg}
|
||||
alt="VPN service"
|
||||
/>
|
||||
<h2 className="text-xl md:text-2xl text-slate-800 font-bold text-center">
|
||||
{canVote
|
||||
? 'Vote for VPN delegate to get service'
|
||||
: 'You cannot get the VPN service, sorry 😢'}
|
||||
</h2>
|
||||
<p className="text-gray-600 text-center">
|
||||
{canVote ? (
|
||||
'The tokens used for voting are locked, which means that they still belong to you but they cannot be used for transactions. The funds are locked for as long as you are voting using these tokens and you are able to unlock them any time you want, by removing them from the votes.'
|
||||
) : (
|
||||
<>
|
||||
Only users who can vote for a VPN delegate using{' '}
|
||||
<b>at least 19.9 tokens</b> can use this service.
|
||||
Unfortunately, your account currently has{' '}
|
||||
<b>only {store.accountBalance} tokens</b>, which is not
|
||||
enough to vote.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<button
|
||||
className="btn bg-indigo-500 hover:bg-indigo-600 text-white max-w-fit"
|
||||
onClick={clickButton}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current opacity-50 shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
|
||||
</svg>
|
||||
<span className="hidden xs:block mx-6">
|
||||
{canVote ? 'Vote' : 'Get It'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default VPNPrepare;
|
||||
268
src/pages/services/VPNServers.jsx
Normal file
@@ -0,0 +1,268 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
|
||||
import VPNServeIcon from '../../images/vpn_server_icon.png';
|
||||
import { Filters } from '../../components/Filters';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import ReactCountryFlag from 'react-country-flag';
|
||||
import Transition from '../../utils/Transition';
|
||||
|
||||
const filters = {
|
||||
'View All': 'all',
|
||||
Active: true,
|
||||
Offline: false,
|
||||
};
|
||||
|
||||
const VPNServers = observer(() => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [selectedServer, setSelectedServer] = useState(0);
|
||||
const [currentFilter, setCurrentFilter] = useState('View All');
|
||||
|
||||
const servers = useMemo(() => {
|
||||
if (filters[currentFilter] === 'all') return store.vpnServers;
|
||||
return store.vpnServers.filter((e) => e.state === filters[currentFilter]);
|
||||
}, [store.vpnServers, currentFilter]);
|
||||
|
||||
const qrContent = useMemo(
|
||||
() => `
|
||||
[Interface]
|
||||
PrivateKey = ${store.vpnPrivateKey}
|
||||
Address = ${servers?.[selectedServer]?.address}
|
||||
DNS = ${servers?.[selectedServer]?.dns || '1.1.1.1'}
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${servers?.[selectedServer]?.serverPublickKey}
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = ${servers?.[selectedServer]?.endpoint}
|
||||
PersistentKeepalive = 25
|
||||
`,
|
||||
[servers, store.vpnPrivateKey, selectedServer]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedServer(0);
|
||||
}, [currentFilter]);
|
||||
|
||||
const downloadTxtFile = () => {
|
||||
const element = document.createElement('a');
|
||||
const file = new Blob([qrContent], { type: 'text/plain' });
|
||||
element.href = URL.createObjectURL(file);
|
||||
element.download = 'IDNTTY-' + servers?.[selectedServer]?.country + '.conf';
|
||||
document.body.appendChild(element); // Required for this to work in FireFox
|
||||
element.click();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
store.fetchVPNServers();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
{/* Content area */}
|
||||
<div className="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden bg-white">
|
||||
{/* Site header */}
|
||||
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
|
||||
|
||||
<main>
|
||||
<div className="lg:relative lg:flex">
|
||||
{/* Content */}
|
||||
<div className="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl">
|
||||
{/* Page header */}
|
||||
<div className="sm:flex sm:justify-between sm:items-center mb-5">
|
||||
{/* Left: Title */}
|
||||
<div className="mb-4 sm:mb-0">
|
||||
<h1 className="text-2xl md:text-3xl text-slate-800 font-bold">
|
||||
Servers ✨
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Filters
|
||||
value={currentFilter}
|
||||
values={Object.keys(filters)}
|
||||
onChange={setCurrentFilter}
|
||||
/>
|
||||
|
||||
{/* Credit cards */}
|
||||
<div className="space-y-2">
|
||||
{servers?.map((e, i) => (
|
||||
<label
|
||||
className="relative block cursor-pointer text-left w-full"
|
||||
onClick={() => setSelectedServer(i)}
|
||||
key={e.serverPublickKey}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="radio-buttons"
|
||||
className="peer sr-only"
|
||||
checked={i === selectedServer}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<div className="p-4 rounded border border-slate-200 hover:border-slate-300 shadow-sm duration-150 ease-in-out">
|
||||
<div className="grid grid-cols-12 items-center gap-x-2">
|
||||
{/* Card */}
|
||||
<div className="col-span-6 order-1 sm:order-none flex items-center space-x-4 ">
|
||||
<ReactCountryFlag
|
||||
countryCode={e.shortCountry}
|
||||
svg
|
||||
style={{ width: '36px', height: 'auto' }}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-800">
|
||||
{e.endpoint}
|
||||
</div>
|
||||
<div className="text-xs">{e.serverPublickKey}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Card limits */}
|
||||
<div className="col-span-3 order-1 sm:order-none text-right sm:text-center">
|
||||
<div className="text-sm">
|
||||
{e.transferSum} / 50.00 GB
|
||||
</div>
|
||||
</div>
|
||||
{/* Card status */}
|
||||
<div className="col-span-3 order-2 sm:order-none text-right">
|
||||
{e?.state ? (
|
||||
<div className="text-xs inline-flex font-medium bg-emerald-100 text-emerald-600 rounded-full text-center px-2.5 py-1">
|
||||
Active
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs inline-flex font-medium bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1">
|
||||
Offline
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="absolute inset-0 border-2 border-transparent peer-checked:border-indigo-400 rounded pointer-events-none"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
|
||||
<Transition
|
||||
show={servers?.length > 0 && servers?.[selectedServer]?.state}
|
||||
enter="transition-transform duration-200 ease-in"
|
||||
enterStart="translate-x-full"
|
||||
enterEnd="translate-x-"
|
||||
leave="transition-transform duration-200 ease-out"
|
||||
leaveStart="translate-x-"
|
||||
leaveEnd="translate-x-full"
|
||||
>
|
||||
<div className="lg:sticky lg:top-16 bg-slate-50 lg:overflow-x-hidden lg:overflow-y-auto no-scrollbar lg:shrink-0 border-t lg:border-t-0 lg:border-l border-slate-200 lg:w-[390px] lg:h-[calc(100vh-64px)]">
|
||||
<div className="py-8 px-4 lg:px-8 h-full">
|
||||
<div className="max-w-sm mx-auto lg:max-w-none flex flex-col justify-between h-full">
|
||||
<div>
|
||||
<div className="text-slate-800 font-semibold text-center mb-6">
|
||||
{servers?.[selectedServer]?.country} -{' '}
|
||||
{servers?.[selectedServer]?.region}
|
||||
</div>
|
||||
|
||||
{/* Credit Card */}
|
||||
<div className="flex justify-center">
|
||||
<QRCodeSVG
|
||||
value={qrContent}
|
||||
size={200}
|
||||
includeMargin
|
||||
level="L"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Details */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-semibold text-slate-800 mb-1">
|
||||
Details
|
||||
</div>
|
||||
<ul>
|
||||
<li className="flex items-center justify-between py-3 border-b border-slate-200">
|
||||
<div className="text-sm">Server address</div>
|
||||
<div className="text-sm font-medium text-slate-800 ml-2">
|
||||
{servers?.[selectedServer]?.endpoint}
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-center justify-between py-3 border-b border-slate-200">
|
||||
<div className="text-sm">Status</div>
|
||||
<div className="flex items-center whitespace-nowrap">
|
||||
{servers?.[selectedServer]?.state ? (
|
||||
<>
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500 mr-2" />
|
||||
<div className="text-sm font-medium text-slate-800">
|
||||
Active
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="w-2 h-2 rounded-full mr-2 bg-amber-500" />
|
||||
<div className="text-sm font-medium text-slate-800">
|
||||
Offline
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Payment Limits */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-semibold text-slate-800 mb-4">
|
||||
Transfer
|
||||
</div>
|
||||
<div className="pb-4 border-b border-slate-200">
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<div>Spent This Month</div>
|
||||
<div className="italic">
|
||||
{servers?.[selectedServer]?.transferSum}{' '}
|
||||
<span className="text-slate-400">/</span> 50.00 GB
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full h-2 bg-slate-300">
|
||||
<div
|
||||
className="absolute inset-0 bg-indigo-500"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: `${servers?.[selectedServer]?.trafficUsed}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600"
|
||||
onClick={downloadTxtFile}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-current text-slate-400 shrink-0 rotate-180"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 4c-.3 0-.5.1-.7.3L1.6 10 3 11.4l4-4V16h2V7.4l4 4 1.4-1.4-5.7-5.7C8.5 4.1 8.3 4 8 4ZM1 2h14V0H1v2Z" />
|
||||
</svg>
|
||||
<span className="ml-2">Download</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default VPNServers;
|
||||
@@ -2,21 +2,15 @@ import React, { useState } from 'react';
|
||||
|
||||
import UserMenu from '../components/DropdownProfile';
|
||||
|
||||
function Header({
|
||||
sidebarOpen,
|
||||
setSidebarOpen
|
||||
}) {
|
||||
|
||||
const [searchModalOpen, setSearchModalOpen] = useState(false)
|
||||
function Header({ sidebarOpen, setSidebarOpen }) {
|
||||
const [searchModalOpen, setSearchModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 bg-white border-b border-slate-200 z-30">
|
||||
<div className="px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16 -mb-px">
|
||||
|
||||
{/* Header: Left side */}
|
||||
<div className="flex">
|
||||
|
||||
{/* Hamburger button */}
|
||||
<button
|
||||
className="text-slate-500 hover:text-slate-600 lg:hidden"
|
||||
@@ -25,21 +19,22 @@ function Header({
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
<svg className="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="w-6 h-6 fill-current"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="5" width="16" height="2" />
|
||||
<rect x="4" y="11" width="16" height="2" />
|
||||
<rect x="4" y="17" width="16" height="2" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Header: Right side */}
|
||||
<div className="flex items-center space-x-3">
|
||||
<UserMenu align="right" />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -4,24 +4,27 @@ import Logo from '../images/logo.png';
|
||||
|
||||
import SidebarLinkGroup from './SidebarLinkGroup';
|
||||
|
||||
function Sidebar({
|
||||
sidebarOpen,
|
||||
setSidebarOpen
|
||||
}) {
|
||||
|
||||
function Sidebar({ sidebarOpen, setSidebarOpen }) {
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
const trigger = useRef(null);
|
||||
const sidebar = useRef(null);
|
||||
const storedSidebarExpanded = localStorage.getItem('sidebar-expanded');
|
||||
const [sidebarExpanded, setSidebarExpanded] = useState(storedSidebarExpanded === null ? false : storedSidebarExpanded === 'true');
|
||||
const [sidebarExpanded, setSidebarExpanded] = useState(
|
||||
storedSidebarExpanded === null ? false : storedSidebarExpanded === 'true'
|
||||
);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!sidebar.current || !trigger.current) return;
|
||||
if (!sidebarOpen || sidebar.current.contains(target) || trigger.current.contains(target)) return;
|
||||
if (
|
||||
!sidebarOpen ||
|
||||
sidebar.current.contains(target) ||
|
||||
trigger.current.contains(target)
|
||||
)
|
||||
return;
|
||||
setSidebarOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
@@ -76,13 +79,17 @@ function Sidebar({
|
||||
aria-expanded={sidebarOpen}
|
||||
>
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<svg className="w-6 h-6 fill-current" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="w-6 h-6 fill-current"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M10.7 18.7l1.4-1.4L7.8 13H20v-2H7.8l4.3-4.3-1.4-1.4L4 12z" />
|
||||
</svg>
|
||||
</button>
|
||||
{/* Logo */}
|
||||
<NavLink end to="/digitalId/profile-id" className="block">
|
||||
<img alt='logo' src={Logo} width="89" height="32"/>
|
||||
<img alt="logo" src={Logo} width="89" height="32" />
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
@@ -91,44 +98,68 @@ function Sidebar({
|
||||
{/* Pages group */}
|
||||
<div>
|
||||
<h3 className="text-xs uppercase text-slate-500 font-semibold pl-3">
|
||||
<span className="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6" aria-hidden="true">
|
||||
<span
|
||||
className="hidden lg:block lg:sidebar-expanded:hidden 2xl:hidden text-center w-6"
|
||||
aria-hidden="true"
|
||||
>
|
||||
•••
|
||||
</span>
|
||||
<span className="lg:hidden lg:sidebar-expanded:block 2xl:block">Pages</span>
|
||||
<span className="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
Pages
|
||||
</span>
|
||||
</h3>
|
||||
<ul className="mt-3">
|
||||
{/* digitalId */}
|
||||
<SidebarLinkGroup activecondition={pathname.includes('digitalId')}>
|
||||
<SidebarLinkGroup
|
||||
activecondition={pathname.includes('digitalId')}
|
||||
>
|
||||
{(handleClick, open) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<a
|
||||
href="#0"
|
||||
className={`block text-slate-200 hover:text-white truncate transition duration-150 ${
|
||||
pathname.includes('digitalId') && 'hover:text-slate-200'
|
||||
pathname.includes('digitalId') &&
|
||||
'hover:text-slate-200'
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
sidebarExpanded ? handleClick() : setSidebarExpanded(true);
|
||||
sidebarExpanded
|
||||
? handleClick()
|
||||
: setSidebarExpanded(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<svg className="shrink-0 h-6 w-6" viewBox="0 0 24 24">
|
||||
<svg
|
||||
className="shrink-0 h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className={`fill-current text-slate-600 ${pathname.includes('digitalId') && 'text-indigo-500'}`}
|
||||
className={`fill-current text-slate-600 ${
|
||||
pathname.includes('digitalId') &&
|
||||
'text-indigo-500'
|
||||
}`}
|
||||
d="M19.714 14.7l-7.007 7.007-1.414-1.414 7.007-7.007c-.195-.4-.298-.84-.3-1.286a3 3 0 113 3 2.969 2.969 0 01-1.286-.3z"
|
||||
/>
|
||||
<path
|
||||
className={`fill-current text-slate-400 ${pathname.includes('digitalId') && 'text-indigo-300'}`}
|
||||
className={`fill-current text-slate-400 ${
|
||||
pathname.includes('digitalId') &&
|
||||
'text-indigo-300'
|
||||
}`}
|
||||
d="M10.714 18.3c.4-.195.84-.298 1.286-.3a3 3 0 11-3 3c.002-.446.105-.885.3-1.286l-6.007-6.007 1.414-1.414 6.007 6.007z"
|
||||
/>
|
||||
<path
|
||||
className={`fill-current text-slate-600 ${pathname.includes('digitalId') && 'text-indigo-500'}`}
|
||||
className={`fill-current text-slate-600 ${
|
||||
pathname.includes('digitalId') &&
|
||||
'text-indigo-500'
|
||||
}`}
|
||||
d="M5.7 10.714c.195.4.298.84.3 1.286a3 3 0 11-3-3c.446.002.885.105 1.286.3l7.007-7.007 1.414 1.414L5.7 10.714z"
|
||||
/>
|
||||
<path
|
||||
className={`fill-current text-slate-400 ${pathname.includes('digitalId') && 'text-indigo-300'}`}
|
||||
className={`fill-current text-slate-400 ${
|
||||
pathname.includes('digitalId') &&
|
||||
'text-indigo-300'
|
||||
}`}
|
||||
d="M19.707 9.292a3.012 3.012 0 00-1.415 1.415L13.286 5.7c-.4.195-.84.298-1.286.3a3 3 0 113-3 2.969 2.969 0 01-.3 1.286l5.007 5.006z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -139,7 +170,9 @@ function Sidebar({
|
||||
{/* Icon */}
|
||||
<div className="flex shrink-0 ml-2">
|
||||
<svg
|
||||
className={`w-3 h-3 shrink-0 ml-1 fill-current text-slate-400 ${open && 'transform rotate-180'}`}
|
||||
className={`w-3 h-3 shrink-0 ml-1 fill-current text-slate-400 ${
|
||||
open && 'transform rotate-180'
|
||||
}`}
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
@@ -154,7 +187,8 @@ function Sidebar({
|
||||
end
|
||||
to="/digitalId/profile-id"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' + (isActive ? '!text-indigo-500' : '')
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' +
|
||||
(isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
@@ -162,25 +196,26 @@ function Sidebar({
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="mb-1 last:mb-0">
|
||||
<NavLink
|
||||
end
|
||||
to="/digitalId/validate"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' + (isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
Validate
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
{/*<li className="mb-1 last:mb-0">*/}
|
||||
{/* <NavLink*/}
|
||||
{/* end*/}
|
||||
{/* to="/digitalId/validate"*/}
|
||||
{/* className={({ isActive }) =>*/}
|
||||
{/* 'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' + (isActive ? '!text-indigo-500' : '')*/}
|
||||
{/* }*/}
|
||||
{/* >*/}
|
||||
{/* <span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">*/}
|
||||
{/* Validate*/}
|
||||
{/* </span>*/}
|
||||
{/* </NavLink>*/}
|
||||
{/*</li>*/}
|
||||
<li className="mb-1 last:mb-0">
|
||||
<NavLink
|
||||
end
|
||||
to="/digitalId/validation-log"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' + (isActive ? '!text-indigo-500' : '')
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' +
|
||||
(isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
@@ -193,7 +228,8 @@ function Sidebar({
|
||||
end
|
||||
to="/digitalId/verify"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' + (isActive ? '!text-indigo-500' : '')
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' +
|
||||
(isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
@@ -207,6 +243,118 @@ function Sidebar({
|
||||
);
|
||||
}}
|
||||
</SidebarLinkGroup>
|
||||
<SidebarLinkGroup activecondition={pathname.includes('services')}>
|
||||
{(handleClick, open) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<a
|
||||
className={`block text-slate-200 hover:text-white truncate transition duration-150 ${
|
||||
pathname.includes('services') &&
|
||||
'hover:text-slate-200'
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
sidebarExpanded
|
||||
? handleClick()
|
||||
: setSidebarExpanded(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="shrink-0 h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className={`fill-current text-slate-400 ${
|
||||
pathname.includes('services') &&
|
||||
'text-indigo-300'
|
||||
}`}
|
||||
cx="18.5"
|
||||
cy="5.5"
|
||||
r="4.5"
|
||||
/>
|
||||
<circle
|
||||
className={`fill-current text-slate-600 ${
|
||||
pathname.includes('services') &&
|
||||
'text-indigo-500'
|
||||
}`}
|
||||
cx="5.5"
|
||||
cy="5.5"
|
||||
r="4.5"
|
||||
/>
|
||||
<circle
|
||||
className={`fill-current text-slate-600 ${
|
||||
pathname.includes('services') &&
|
||||
'text-indigo-500'
|
||||
}`}
|
||||
cx="18.5"
|
||||
cy="18.5"
|
||||
r="4.5"
|
||||
/>
|
||||
<circle
|
||||
className={`fill-current text-slate-400 ${
|
||||
pathname.includes('services') &&
|
||||
'text-indigo-300'
|
||||
}`}
|
||||
cx="5.5"
|
||||
cy="18.5"
|
||||
r="4.5"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-sm font-medium ml-3 lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
Services
|
||||
</span>
|
||||
</div>
|
||||
{/* Icon */}
|
||||
<div className="flex shrink-0 ml-2">
|
||||
<svg
|
||||
className={`w-3 h-3 shrink-0 ml-1 fill-current text-slate-400 ${
|
||||
open && 'transform rotate-180'
|
||||
}`}
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className="lg:hidden lg:sidebar-expanded:block 2xl:block">
|
||||
<ul className={`pl-9 mt-1 ${!open && 'hidden'}`}>
|
||||
<li className="mb-1 last:mb-0">
|
||||
<NavLink
|
||||
end
|
||||
to="/services/delegates"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' +
|
||||
(isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
Delegates
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="mb-1 last:mb-0">
|
||||
<NavLink
|
||||
end
|
||||
to="/services/vpn"
|
||||
className={({ isActive }) =>
|
||||
'block text-slate-400 hover:text-slate-200 transition duration-150 truncate ' +
|
||||
(isActive ? '!text-indigo-500' : '')
|
||||
}
|
||||
>
|
||||
<span className="text-sm font-medium lg:opacity-0 lg:sidebar-expanded:opacity-100 2xl:opacity-100 duration-200">
|
||||
VPN
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}}
|
||||
</SidebarLinkGroup>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,8 +364,14 @@ function Sidebar({
|
||||
<div className="px-3 py-2">
|
||||
<button onClick={() => setSidebarExpanded(!sidebarExpanded)}>
|
||||
<span className="sr-only">Expand / collapse sidebar</span>
|
||||
<svg className="w-6 h-6 fill-current sidebar-expanded:rotate-180" viewBox="0 0 24 24">
|
||||
<path className="text-slate-400" d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z" />
|
||||
<svg
|
||||
className="w-6 h-6 fill-current sidebar-expanded:rotate-180"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
className="text-slate-400"
|
||||
d="M19.586 11l-5-5L16 4.586 23.414 12 16 19.414 14.586 18l5-5H7v-2z"
|
||||
/>
|
||||
<path className="text-slate-600" d="M3 23H1V1h2z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function SidebarLinkGroup({
|
||||
children,
|
||||
activecondition,
|
||||
}) {
|
||||
|
||||
function SidebarLinkGroup({ children, activecondition }) {
|
||||
const [open, setOpen] = useState(activecondition);
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<li className={`px-3 py-2 rounded-sm mb-0.5 last:mb-0 ${activecondition && 'bg-slate-900'}`}>
|
||||
<li
|
||||
className={`px-3 py-2 rounded-sm mb-0.5 cursor-pointer last:mb-0 ${
|
||||
activecondition && 'bg-slate-900'
|
||||
}`}
|
||||
>
|
||||
{children(handleClick, open)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarLinkGroup;
|
||||
export default SidebarLinkGroup;
|
||||
|
||||
201
src/partials/finance/TransactionPanel.jsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import Image from '../../images/transactions-image-04.svg';
|
||||
import moment from 'moment';
|
||||
import { formatAddressBig } from '../../utils/Utils';
|
||||
import { generateSvgAvatar } from '../../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import { store } from '../../store/store';
|
||||
import UserAvatar from '../../images/user-avatar-32.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const TransactionPanel = observer(
|
||||
({ transactionPanelOpen, onClose, delegate, postTransaction, unlocking }) => {
|
||||
const closeBtn = useRef(null);
|
||||
const panelContent = useRef(null);
|
||||
|
||||
const [amount, setAmount] = useState(0);
|
||||
|
||||
const status = useMemo(() => (unlocking ? 'Unlock' : delegate.status), [unlocking, delegate]);
|
||||
|
||||
const statusColor = () => {
|
||||
switch (status) {
|
||||
case 'Vote':
|
||||
return 'bg-emerald-100 text-emerald-600';
|
||||
case 'Unlock':
|
||||
case 'Unvote':
|
||||
return 'bg-rose-100 text-rose-500';
|
||||
default:
|
||||
return 'bg-slate-100 text-slate-500';
|
||||
}
|
||||
};
|
||||
|
||||
const amountLabel = delegate.status === 'Vote' ? 'Voiting amount' : 'Unvoiting amount';
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!transactionPanelOpen || keyCode !== 27) return;
|
||||
onClose();
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'Unvote') {
|
||||
setAmount(store.accountSentVotes[delegate.address].toString());
|
||||
}
|
||||
if (status === 'Unlock') {
|
||||
setAmount(store.accountLockedVotesCanReturnSum[delegate.address]?.toString());
|
||||
}
|
||||
}, [delegate, status]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={panelContent}
|
||||
className={`absolute inset-0 sm:left-auto z-20 transform shadow-xl transition-transform duration-200 ease-in-out ${
|
||||
transactionPanelOpen ? 'translate-x-' : 'translate-x-full'
|
||||
}`}
|
||||
>
|
||||
<div className="sticky top-16 bg-slate-50 overflow-x-hidden overflow-y-auto no-scrollbar shrink-0 border-l border-slate-200 w-full sm:w-[390px] h-[calc(100vh-64px)]">
|
||||
<button
|
||||
ref={closeBtn}
|
||||
onClick={() => onClose()}
|
||||
className="absolute top-0 right-0 mt-6 mr-6 group p-2"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-slate-400 group-hover:fill-slate-600 pointer-events-none"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m7.95 6.536 4.242-4.243a1 1 0 1 1 1.415 1.414L9.364 7.95l4.243 4.242a1 1 0 1 1-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 0 1-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 0 1 1.414-1.414L7.95 6.536Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="py-8 px-4 lg:px-8">
|
||||
<div className="max-w-sm mx-auto lg:max-w-none">
|
||||
<div className="text-slate-800 font-semibold text-center mb-1">Vote Transaction</div>
|
||||
<div className="text-sm text-center italic">
|
||||
{moment().format('DD/MM/YYYY hh:mm A')}
|
||||
</div>
|
||||
{/* Details */}
|
||||
<div className="drop-shadow-lg mt-12">
|
||||
{/* Top */}
|
||||
<div className="bg-white rounded-t-xl px-5 pb-2.5 text-center">
|
||||
<div className="mb-3 text-center">
|
||||
<img
|
||||
className="inline-flex w-12 h-12 rounded-full -mt-6"
|
||||
src={Image}
|
||||
width="48"
|
||||
height="48"
|
||||
alt="Transaction 04"
|
||||
/>
|
||||
</div>
|
||||
<div className={`text-2xl font-semibold mb-1 ${statusColor()} bg-transparent`}>
|
||||
{amount}/idn
|
||||
</div>
|
||||
<div className="text-sm font-medium text-slate-800 mb-3">
|
||||
{delegate?.dpos?.delegate?.username || formatAddressBig(delegate.address || '')}
|
||||
</div>
|
||||
<div
|
||||
className={`text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 ${statusColor()} justify-center`}
|
||||
style={{ minWidth: '84px' }}
|
||||
>
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
{/* Divider */}
|
||||
<div className="flex justify-between items-center" aria-hidden="true">
|
||||
<svg className="w-5 h-5 fill-white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
<div className="grow w-full h-5 bg-white flex flex-col justify-center">
|
||||
<div className="h-px w-full border-t border-dashed border-slate-200" />
|
||||
</div>
|
||||
<svg className="w-5 h-5 fill-white rotate-180" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
{/* Bottom */}
|
||||
<div className="bg-white rounded-b-xl p-5 pt-2.5 text-sm space-y-3">
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">ADDRESS:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
{formatAddressBig(delegate.address || '')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">FEE:</span>
|
||||
<span className="font-medium text-slate-700 text-right">145/idn</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Nonce:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
{delegate?.sequence?.nonce}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Sender:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
{formatAddressBig(store.address)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Receipts */}
|
||||
<div className="mt-6">
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="mandatory">
|
||||
{unlocking ? 'Unlock amount' : amountLabel}{' '}
|
||||
<span className="text-rose-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="amount"
|
||||
className="form-input w-full"
|
||||
type="number"
|
||||
required
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
value={amount}
|
||||
disabled={unlocking}
|
||||
/>
|
||||
</div>
|
||||
{/* Notes */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-semibold text-slate-800 mb-2">Transaction details</div>
|
||||
<div className="flex p-2 justify-between items-center border border-slate-300 rounded-md">
|
||||
<div className="flex gap-2">
|
||||
<img
|
||||
className="w-8 h-8 rounded-full"
|
||||
src={generateSvgAvatar(store.pubKey) || UserAvatar}
|
||||
width="32"
|
||||
height="32"
|
||||
alt="User"
|
||||
/>
|
||||
<div className="flex items-center text-sm text-slate-700">Balance</div>
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-slate-700">
|
||||
{store.accountBalance}/idn
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 mt-6">
|
||||
<button
|
||||
className="btn bg-indigo-500 hover:bg-indigo-600 text-white w-full"
|
||||
onClick={() => {
|
||||
postTransaction(
|
||||
delegate.address,
|
||||
BigInt(delegate.status === 'Vote' ? amount : amount * -1)
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<span className="hidden xs:block">Post Transaction</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TransactionPanel;
|
||||
71
src/partials/finance/TransactionsTable.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import TransactionItem from './TransactionsTableItem';
|
||||
|
||||
import Image01 from '../../images/transactions-image-01.svg';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const TransactionsTable = observer(({ data, rowClick, lockedFor }) => {
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div>
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-full">
|
||||
{/* Table header */}
|
||||
<thead className="text-xs font-semibold uppercase text-slate-500 border-t border-b border-slate-200">
|
||||
<tr>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-left">Delegate</div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center">Service</div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center">Balance</div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center">Voted Total</div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center"></div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center">
|
||||
{lockedFor === 'Voiting' ? 'Sent Votes' : 'Blocked votes'}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{/* Table body */}
|
||||
<tbody className="text-sm divide-y divide-slate-200 border-b border-slate-200">
|
||||
{data.map((delegate) => {
|
||||
return (
|
||||
<TransactionItem
|
||||
key={delegate.address}
|
||||
id={delegate.address}
|
||||
image={Image01}
|
||||
balance={delegate?.token?.balance}
|
||||
name={delegate.dpos?.delegate?.username || delegate.address}
|
||||
total={delegate.dpos?.delegate?.totalVotesReceived || ''}
|
||||
hasSevice={delegate.dpos?.delegate?.username === 'delegate_0'}
|
||||
date={delegate.date}
|
||||
displayReturn={lockedFor === 'Unlocking'}
|
||||
status={delegate.status}
|
||||
amount={
|
||||
(lockedFor === 'Voiting'
|
||||
? store.accountSentVotes[delegate.address]
|
||||
: store.accountLockedVotesCanReturnSum[delegate.address]) || ''
|
||||
}
|
||||
handleClick={() => rowClick(delegate)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TransactionsTable;
|
||||
98
src/partials/finance/TransactionsTableItem.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
|
||||
import ServiceImage from '../../images/service_icon.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const TransactionsTableItem = observer((props) => {
|
||||
const statusColor = () => {
|
||||
switch (props.status) {
|
||||
case 'Vote':
|
||||
return 'bg-emerald-100 text-emerald-600';
|
||||
case 'Unvote':
|
||||
return 'bg-rose-100 text-rose-500';
|
||||
default:
|
||||
return 'bg-slate-100 text-slate-500';
|
||||
}
|
||||
};
|
||||
|
||||
const amountColor = () => {
|
||||
switch (props.status === 'Unvote') {
|
||||
case '+':
|
||||
return 'text-emerald-500';
|
||||
default:
|
||||
return 'text-slate-700';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<tr className="cursor-pointer" onClick={() => props.handleClick()}>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap md:w-1/2">
|
||||
<div className="flex items-center">
|
||||
<div className="w-9 h-9 shrink-0 mr-2 sm:mr-3">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={props.image}
|
||||
width="36"
|
||||
height="36"
|
||||
alt={props.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="font-medium text-slate-800">{props.name}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="w-9 h-9 shrink-0">
|
||||
{props.hasSevice && (
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={ServiceImage}
|
||||
width="36"
|
||||
height="36"
|
||||
alt="service"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">{props.balance}/idn</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">
|
||||
{props.total}
|
||||
{props.total && '/idn'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">
|
||||
{props.displayReturn && props.status !== 'Pending' ? (
|
||||
store.accountLockedVotesCanReturn[props.id] && (
|
||||
<div
|
||||
className={`text-xs inline-flex font-medium rounded-full justify-center px-2.5 py-1 bg-rose-100 text-rose-500`}
|
||||
style={{ minWidth: '84px' }}
|
||||
>
|
||||
Unlock
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
className={`text-xs inline-flex font-medium rounded-full justify-center px-2.5 py-1 ${statusColor()}`}
|
||||
style={{ minWidth: '84px' }}
|
||||
>
|
||||
{props.status}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className={`text-center font-medium ${amountColor()}`}>
|
||||
{props.amount.toString()}
|
||||
{props.amount && '/idn'}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
export default TransactionsTableItem;
|
||||
@@ -1,37 +1,72 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ProfileTableItem from './ProfileTableItem';
|
||||
import ProfileIcon from '../../images/profile-icon.svg';
|
||||
|
||||
function ProfileTable({
|
||||
userData,
|
||||
handleClick,
|
||||
isCheck,
|
||||
}) {
|
||||
|
||||
function ProfileTable({ userData, handleClick, isCheck }) {
|
||||
const defaultData = {
|
||||
image: ProfileIcon,
|
||||
status: 'Stored',
|
||||
transactions: ['0x7234ABC342342352345', '0x5745DEF342342352345'],
|
||||
avatars: null
|
||||
avatars: null,
|
||||
};
|
||||
|
||||
const data = {
|
||||
general:["First name","Second name", "Gender", "Birthdate", "Place of birth"],
|
||||
nationality:["Nationality", "National ID", "National doctype", "National doc ID", "National doc issue date", "National doc expiry date"],
|
||||
social:["Telephone", "Twitter", "Facebook", "Instagram", "Youtube", "Wechat", "Tiktok", "Linkedin", "Vk", "Github", "Telegram", "Qq", "Snapchat", "Reddit"]
|
||||
}
|
||||
general: [
|
||||
'First name',
|
||||
'Second name',
|
||||
'Gender',
|
||||
'Birthdate',
|
||||
'Place of birth',
|
||||
],
|
||||
nationality: [
|
||||
'Nationality',
|
||||
'National ID',
|
||||
'National doctype',
|
||||
'National doc ID',
|
||||
'National doc issue date',
|
||||
'National doc expiry date',
|
||||
],
|
||||
social: [
|
||||
'Telephone',
|
||||
'Twitter',
|
||||
'Facebook',
|
||||
'Instagram',
|
||||
'Youtube',
|
||||
'Wechat',
|
||||
'Tiktok',
|
||||
'Linkedin',
|
||||
'Vk',
|
||||
'Github',
|
||||
'Telegram',
|
||||
'Qq',
|
||||
'Snapchat',
|
||||
'Reddit',
|
||||
],
|
||||
};
|
||||
|
||||
const generalData = userData.filter(elem => data.general.includes(elem.label));
|
||||
const nationalityData = userData.filter(elem => data.nationality.includes(elem.label));
|
||||
const socialData = userData.filter(elem => data.social.includes(elem.label));
|
||||
const otherData = userData.filter((elem) =>
|
||||
!data.general.includes(elem.label) && !data.nationality.includes(elem.label) && !data.social.includes(elem.label));
|
||||
const generalData = userData.filter((elem) =>
|
||||
data.general.includes(elem.label)
|
||||
);
|
||||
const nationalityData = userData.filter((elem) =>
|
||||
data.nationality.includes(elem.label)
|
||||
);
|
||||
const socialData = userData.filter((elem) =>
|
||||
data.social.includes(elem.label)
|
||||
);
|
||||
const otherData = userData.filter(
|
||||
(elem) =>
|
||||
!data.general.includes(elem.label) &&
|
||||
!data.nationality.includes(elem.label) &&
|
||||
!data.social.includes(elem.label)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* General */}
|
||||
{/* General */}
|
||||
<div className={`${generalData.length > 0 || 'hidden'}`}>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">General 🖋️</h2>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">
|
||||
General 🖋️
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-full">
|
||||
<tbody className="divide-slate-200 divide-y">
|
||||
@@ -41,15 +76,17 @@ function ProfileTable({
|
||||
key={data.label}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
|
||||
value={
|
||||
data.value.charAt(0).toUpperCase() + data.value.slice(1)
|
||||
}
|
||||
property={data.label}
|
||||
status={defaultData.status}
|
||||
status={data.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -57,25 +94,29 @@ function ProfileTable({
|
||||
</div>
|
||||
{/* Nationality */}
|
||||
<div className={`${nationalityData.length > 0 || 'hidden'}`}>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">Nationality 🖋️</h2>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">
|
||||
Nationality 🖋️
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-full">
|
||||
<tbody className="text-sm divide-slate-200 divide-y ">
|
||||
{nationalityData.map(data => {
|
||||
{nationalityData.map((data) => {
|
||||
return (
|
||||
<ProfileTableItem
|
||||
key={data.label}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
|
||||
value={
|
||||
data.value.charAt(0).toUpperCase() + data.value.slice(1)
|
||||
}
|
||||
property={data.label}
|
||||
status={defaultData.status}
|
||||
status={data.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -83,58 +124,64 @@ function ProfileTable({
|
||||
</div>
|
||||
{/* Social */}
|
||||
<div className={`${socialData.length > 0 || 'hidden'}`}>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">Social 🖋️</h2>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">
|
||||
Social 🖋️
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-full">
|
||||
<tbody className="text-sm divide-slate-200 divide-y ">
|
||||
{socialData.map(data => {
|
||||
return (
|
||||
<ProfileTableItem
|
||||
key={data.label}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={data.value.charAt(0).toUpperCase()+data.value.slice(1)}
|
||||
property={data.label}
|
||||
status={defaultData.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{socialData.map((data) => {
|
||||
return (
|
||||
<ProfileTableItem
|
||||
key={data.label}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={
|
||||
data.value.charAt(0).toUpperCase() + data.value.slice(1)
|
||||
}
|
||||
property={data.label}
|
||||
status={data.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/* Other */}
|
||||
<div className={`${otherData.length > 0 || 'hidden'}`}>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">Other 🖋️</h2>
|
||||
<h2 className="grow text-base font-semibold text-slate-800 truncate mb-2.5 mt-8">
|
||||
Other 🖋️
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-full">
|
||||
<tbody className="text-sm divide-slate-200 divide-y ">
|
||||
{otherData.map(data => {
|
||||
return (
|
||||
<ProfileTableItem
|
||||
key={data.key}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={data.value}
|
||||
property={data.label}
|
||||
status={defaultData.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{otherData.map((data) => {
|
||||
return (
|
||||
<ProfileTableItem
|
||||
key={data.key}
|
||||
id={data.key}
|
||||
image={defaultData.image}
|
||||
value={data.value}
|
||||
property={data.label}
|
||||
status={data.status}
|
||||
transactions={defaultData.transactions}
|
||||
avatars={defaultData.avatars}
|
||||
handleClick={handleClick}
|
||||
isChecked={isCheck.includes(data.key)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileTable;
|
||||
|
||||
@@ -1,63 +1,95 @@
|
||||
import React, { useState } from 'react';
|
||||
import { statusMap } from '../../shared/statusMap';
|
||||
|
||||
function ProfileTableItem(props) {
|
||||
const [descriptionOpen, setDescriptionOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={`${props.status !== 'Progress' || 'bg-[#eaf0f6]'}`}>
|
||||
<tr>
|
||||
<td className="pl-4 pr-4 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<label className="inline-flex">
|
||||
<span className="sr-only">Select</span>
|
||||
<input id={props.id} className="form-checkbox" type="checkbox" onChange={props.handleClick} checked={props.isChecked} />
|
||||
<input
|
||||
id={props.id}
|
||||
className="form-checkbox"
|
||||
type="checkbox"
|
||||
onChange={props.handleClick}
|
||||
checked={props.isChecked}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 whitespace-nowrap">
|
||||
<div className="flex">
|
||||
<div className="w-9 h-9 shrink-0 mr-2 sm:mr-4">
|
||||
<img className="rounded-full" src={props.image} width="40" height="40" alt={props.property} />
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={props.image}
|
||||
width="40"
|
||||
height="40"
|
||||
alt={props.property}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="font-semibold text-slate-800 text-base">{props.value}</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="font-semibold text-slate-800 text-base">
|
||||
{props.status === statusMap.blockchained ||
|
||||
props.status === statusMap.processed
|
||||
? `${props.value.slice(0, 4)}****${props.value.slice(
|
||||
props.value.length - 4
|
||||
)}`
|
||||
: props.value}
|
||||
</div>
|
||||
<div className="font-normal text-xxs">{props.property}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 whitespace-nowrap w-px">
|
||||
<div className="w-fit text-xs inline-flex font-medium px-2.5 py-1">
|
||||
{(props.status === 'Progress') ? (
|
||||
<td className="py-3 whitespace-nowrap w-px px-2.5 text-left min-w-[117px]">
|
||||
<div className="w-fit text-xs inline-flex font-medium py-1">
|
||||
{props.status === statusMap.blockchained ? (
|
||||
<div className="bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1">
|
||||
{props.status}
|
||||
</div>) :
|
||||
(props.status === 'Stored') ? (
|
||||
</div>
|
||||
) : props.status === statusMap.processed ? (
|
||||
<div className="bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-1">
|
||||
{props.status}
|
||||
</div>
|
||||
) : props.status === statusMap.stored ? (
|
||||
<div className="bg-slate-700 text-slate-100 rounded-full text-center px-2.5 py-1">
|
||||
{props.status}
|
||||
</div>) :
|
||||
(props.status === 'Completed') ? (
|
||||
</div>
|
||||
) : props.status === statusMap.completed ? (
|
||||
<div className="bg-emerald-100 text-emerald-600 rounded-full text-center px-2.5 py-1">
|
||||
{props.status}
|
||||
</div>) : (
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-rose-100 text-rose-600 rounded-full text-center px-2.5 py-1">
|
||||
{props.status}
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 py-3 whitespace-nowrap w-[340px] box-border">
|
||||
<div className="flex flex-wrap items-center -m-1.5 justify-center">
|
||||
<div className="flex -space-x-3 -ml-0.5">
|
||||
{(props.avatars) ? (
|
||||
props.avatars.map((avatar, index) => (
|
||||
<img className="rounded-full border-2 border-slate-100 box-content" key={index} src={avatar} width="32" height="32" alt="Avatar" />
|
||||
))
|
||||
): null}
|
||||
{props.avatars
|
||||
? props.avatars.map((avatar, index) => (
|
||||
<img
|
||||
className="rounded-full border-2 border-slate-100 box-content"
|
||||
key={index}
|
||||
src={avatar}
|
||||
width="32"
|
||||
height="32"
|
||||
alt="Avatar"
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
<button className="flex justify-center items-center w-9 h-9 rounded-full bg-white border-2 border-slate-200 hover:border-slate-300 text-indigo-500 shadow-sm transition duration-150">
|
||||
<span className="sr-only">Add avatar</span>
|
||||
<svg className="w-4 h-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
|
||||
</svg>
|
||||
<span className="sr-only">Add avatar</span>
|
||||
<svg className="w-4 h-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M15 7H9V1c0-.6-.4-1-1-1S7 .4 7 1v6H1c-.6 0-1 .4-1 1s.4 1 1 1h6v6c0 .6.4 1 1 1s1-.4 1-1V9h6c.6 0 1-.4 1-1s-.4-1-1-1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,45 +99,69 @@ function ProfileTableItem(props) {
|
||||
<div className="flex items-center">
|
||||
<button className="text-slate-400 hover:text-slate-500 rounded-full">
|
||||
<span className="sr-only">Link</span>
|
||||
<svg className="w-4 h-4 fill-current shrink-0 text-slate-400" viewBox="0 0 16 16">
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0 text-slate-400"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M11 0c1.3 0 2.6.5 3.5 1.5 1 .9 1.5 2.2 1.5 3.5 0 1.3-.5 2.6-1.4 3.5l-1.2 1.2c-.2.2-.5.3-.7.3-.2 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l1.1-1.2c.6-.5.9-1.3.9-2.1s-.3-1.6-.9-2.2C12 1.7 10 1.7 8.9 2.8L7.7 4c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l1.2-1.1C8.4.5 9.7 0 11 0ZM8.3 12c.4-.4 1-.5 1.4-.1.4.4.4 1 0 1.4l-1.2 1.2C7.6 15.5 6.3 16 5 16c-1.3 0-2.6-.5-3.5-1.5C.5 13.6 0 12.3 0 11c0-1.3.5-2.6 1.5-3.5l1.1-1.2c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4L2.9 8.9c-.6.5-.9 1.3-.9 2.1s.3 1.6.9 2.2c1.1 1.1 3.1 1.1 4.2 0L8.3 12Zm1.1-6.8c.4-.4 1-.4 1.4 0 .4.4.4 1 0 1.4l-4.2 4.2c-.2.2-.5.3-.7.3-.2 0-.5-.1-.7-.3-.4-.4-.4-1 0-1.4l4.2-4.2Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 whitespace-nowrap w-px pr-1.5">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${descriptionOpen && 'rotate-180'}`}
|
||||
aria-expanded={descriptionOpen}
|
||||
onClick={() => setDescriptionOpen(!descriptionOpen)}
|
||||
aria-controls={`description-${props.id}`}
|
||||
>
|
||||
<span className="sr-only">Show more</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${
|
||||
descriptionOpen && 'rotate-180'
|
||||
}`}
|
||||
aria-expanded={descriptionOpen}
|
||||
onClick={() => setDescriptionOpen(!descriptionOpen)}
|
||||
aria-controls={`description-${props.id}`}
|
||||
>
|
||||
<span className="sr-only">Show more</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className={`${!descriptionOpen && 'hidden'} ${props.status !== 'Progress' || 'bg-[#eaf0f6]'}` }>
|
||||
<tr className={`${!descriptionOpen && 'hidden'}`}>
|
||||
<td colSpan="10" className="px-12 pt-3.5 pb-[14px]">
|
||||
<div className="flex items-center gap-x-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" htmlFor="placeholder">Seed</label>
|
||||
<input id="placeholder" className="form-input w-[396px] bg-white" type="text" placeholder="2342423423423234223" />
|
||||
<label
|
||||
className="block text-sm font-medium mb-1"
|
||||
htmlFor="placeholder"
|
||||
>
|
||||
Seed
|
||||
</label>
|
||||
<input
|
||||
id="placeholder"
|
||||
className="form-input w-[396px] bg-white"
|
||||
type="text"
|
||||
placeholder="2342423423423234223"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm font-medium mb-1">Transaction</span>
|
||||
<a href="" className="w-[396px] text-slate-400 font-normal text-sm underline">0x12831823791203192418234841238468</a>
|
||||
<span className="block text-sm font-medium mb-1">
|
||||
Transaction
|
||||
</span>
|
||||
<a
|
||||
href=""
|
||||
className="w-[396px] text-slate-400 font-normal text-sm underline"
|
||||
>
|
||||
0x12831823791203192418234841238468
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* Progress validation bar */}
|
||||
<div className="pt-[18px]">
|
||||
<div className="h-full max-w-md w-full flex flex-row">
|
||||
<div className="w-32 shrink-0">
|
||||
<h2 className="text-xl leading-snug font-bold text-slate-800 xl:leading-7 mb-4 xl:mb-0">Today</h2>
|
||||
<h2 className="text-xl leading-snug font-bold text-slate-800 xl:leading-7 mb-4 xl:mb-0">
|
||||
Today
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="-my-2">
|
||||
@@ -113,41 +169,74 @@ function ProfileTableItem(props) {
|
||||
{props.transactions.map((data, index) => (
|
||||
<li className="relative py-2" key={index}>
|
||||
<div className="flex items-center mb-2.5">
|
||||
{(props.transactions[index+1]) ? (
|
||||
<div className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3" aria-hidden="true"></div>
|
||||
{props.transactions[index + 1] ? (
|
||||
<div
|
||||
className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
) : null}
|
||||
<div className="absolute left-0 rounded-full bg-indigo-500" aria-hidden="true">
|
||||
<svg className="w-5 h-5 fill-current text-white" viewBox="0 0 20 20">
|
||||
<div
|
||||
className="absolute left-0 rounded-full bg-indigo-500"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 fill-current text-white"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M14.4 8.4L13 7l-4 4-2-2-1.4 1.4L9 13.8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="pl-9 whitespace-nowrap">
|
||||
<span className="text-validateSize font-bold text-slate-800">Validate by </span>
|
||||
<a href="" className="font-bold text-validateSize underline text-indigo-500">{data}</a>
|
||||
<span className="text-validateSize font-bold text-slate-800">
|
||||
Validate by{' '}
|
||||
</span>
|
||||
<a
|
||||
href=""
|
||||
className="font-bold text-validateSize underline text-indigo-500"
|
||||
>
|
||||
{data}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="pl-9">
|
||||
<a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">{data}</a>
|
||||
<span className="block text-xxs font-normal mb-1">Transactions ID</span>
|
||||
<a
|
||||
href=""
|
||||
className="w-[396px] text-slate-800 font-semibold text-base underline"
|
||||
>
|
||||
{data}
|
||||
</a>
|
||||
<span className="block text-xxs font-normal mb-1">
|
||||
Transactions ID
|
||||
</span>
|
||||
</div>
|
||||
<div className="pl-9">
|
||||
<a href="" className="w-[396px] text-slate-800 font-semibold text-base underline">{data.slice(2, data.length)}</a>
|
||||
<span className="block text-xxs font-normal mb-1">Validated data</span>
|
||||
<a
|
||||
href=""
|
||||
className="w-[396px] text-slate-800 font-semibold text-base underline"
|
||||
>
|
||||
{data.slice(2, data.length)}
|
||||
</a>
|
||||
<span className="block text-xxs font-normal mb-1">
|
||||
Validated data
|
||||
</span>
|
||||
</div>
|
||||
<div className="pl-9 pt-[14px]">
|
||||
<a className="text-sm font-medium text-indigo-500 hover:text-indigo-600" href="#0">
|
||||
<a
|
||||
className="text-sm font-medium text-indigo-500 hover:text-indigo-600"
|
||||
href="#0"
|
||||
>
|
||||
Explore ->
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,63 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import ValidationUsersImg from "../validationLog/ValidationUsersImg";
|
||||
import ValidationUsersImg from '../validationLog/ValidationUsersImg';
|
||||
|
||||
function SharedDataRoadMap({ data }) {
|
||||
|
||||
return (
|
||||
<article className="mb-2">
|
||||
<div className="xl:flex">
|
||||
<div className="grow">
|
||||
<div className="flex items-center mb-2 relative">
|
||||
<div className="absolute left-0" aria-hidden="true">
|
||||
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 7H0V11.9C0 12.9 0.7 13.8 1.7 14C2.9 14.2 4 13.2 4 12V7Z" fill="#A5B4FC"/>
|
||||
<path d="M15.0004 0H7.00039C6.40039 0 6.00039 0.4 6.00039 1V12C6.00039 12.7 5.80039 13.4 5.40039 14H13.0004C14.7004 14 16.0004 12.7 16.0004 11V1C16.0004 0.4 15.6004 0 15.0004 0Z" fill="#6366F1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-base font-semibold text-slate-800 pl-7">
|
||||
{data.text}
|
||||
</h3>
|
||||
<span className="font-normal text-[10px] text-slate-600 pl-7">{data.typeItem}</span>
|
||||
</div>
|
||||
<article className="mb-2">
|
||||
<div className="xl:flex">
|
||||
<div className="grow">
|
||||
<div className="flex items-center mb-2 relative">
|
||||
<div className="absolute left-0" aria-hidden="true">
|
||||
<svg
|
||||
width="16"
|
||||
height="15"
|
||||
viewBox="0 0 16 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4 7H0V11.9C0 12.9 0.7 13.8 1.7 14C2.9 14.2 4 13.2 4 12V7Z"
|
||||
fill="#A5B4FC"
|
||||
/>
|
||||
<path
|
||||
d="M15.0004 0H7.00039C6.40039 0 6.00039 0.4 6.00039 1V12C6.00039 12.7 5.80039 13.4 5.40039 14H13.0004C14.7004 14 16.0004 12.7 16.0004 11V1C16.0004 0.4 15.6004 0 15.0004 0Z"
|
||||
fill="#6366F1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex flex-col max-w-3xl">
|
||||
<h3 className="text-base font-semibold text-slate-800 pl-7 overflow-ellipsis overflow-hidden">
|
||||
{data.value}
|
||||
</h3>
|
||||
<span className="font-normal text-[10px] text-slate-600 pl-7">
|
||||
{data.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{data.seed && (
|
||||
<div className="block text-slate-600 w-fit pl-7">
|
||||
<div className="text-xs font-semibold underline mb-[-10px] cursor-pointer">{data.dataShared}</div>
|
||||
<div className="text-xs font-semibold underline mb-[-10px] cursor-pointer">
|
||||
{data.seed.toUpperCase()}
|
||||
</div>
|
||||
<span className="font-normal text-[10px]">seed</span>
|
||||
</div>
|
||||
)}
|
||||
{data.hash && (
|
||||
<div className="mb-3.5 block text-slate-600 w-fit pl-7">
|
||||
<div className="text-xs font-semibold underline mb-[-10px] cursor-pointer">{data.dataShared}</div>
|
||||
<div className="text-xs font-semibold underline mb-[-10px] cursor-pointer">
|
||||
{data.hash.toUpperCase()}
|
||||
</div>
|
||||
<span className="font-normal text-[10px]">hash</span>
|
||||
</div>
|
||||
<div className="flex flex-nowrap items-center space-x-2 mb-3.5">
|
||||
{/* Avatars */}
|
||||
<div className="flex shrink-0 -space-x-3 -ml-px pl-7">
|
||||
{data.usersImges.map(pic => {
|
||||
return <ValidationUsersImg key={pic.imgId} image={pic}/>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<a className="cursor-pointer pl-7 font-normal text-sm text-indigo-500 hover:text-indigo-600">Explore -></a>
|
||||
</div>
|
||||
)}
|
||||
<a className="cursor-pointer pl-7 font-normal text-sm text-indigo-500 hover:text-indigo-600">
|
||||
Explore ->
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default SharedDataRoadMap;
|
||||
export default SharedDataRoadMap;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
function ValidateTableItem(props) {
|
||||
|
||||
const statusColor = (status) => {
|
||||
switch (status) {
|
||||
case 'Correct':
|
||||
@@ -13,49 +12,67 @@ function ValidateTableItem(props) {
|
||||
}
|
||||
};
|
||||
|
||||
function openValidatePanel (event) {
|
||||
event.stopPropagation()
|
||||
props.setValidatePanelOpen(true)
|
||||
function openValidatePanel(event) {
|
||||
event.stopPropagation();
|
||||
props.setValidatePanelOpen(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr className="hover:bg-[#eaf0f6] cursor-pointer" onClick={(e) => openValidatePanel(e)}>
|
||||
<td className="px-2 first:pl-4 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<label className="inline-flex">
|
||||
<span className="sr-only">Select</span>
|
||||
{props.check && <input id={props.id} className="form-checkbox" type="checkbox" onChange={props.handleClick} checked={props.isChecked} onClick={(e) => e.stopPropagation()} />}
|
||||
</label>
|
||||
<tr
|
||||
className="hover:bg-[#eaf0f6] cursor-pointer"
|
||||
onClick={(e) => openValidatePanel(e)}
|
||||
>
|
||||
<td className="px-2 first:pl-4 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<label className="inline-flex">
|
||||
<span className="sr-only">Select</span>
|
||||
{props.check && (
|
||||
<input
|
||||
id={props.id}
|
||||
className="form-checkbox"
|
||||
type="checkbox"
|
||||
onChange={props.handleClick}
|
||||
checked={props.isChecked}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap md:w-1/2">
|
||||
<div className="flex items-center">
|
||||
<div>{props.filed}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left font-medium text-slate-800">{props.data}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className={`text-left`}>{props.seed}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">
|
||||
<div
|
||||
className={`text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 ${statusColor(
|
||||
props.status
|
||||
)}`}
|
||||
>
|
||||
{props.status}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap md:w-1/2">
|
||||
<div className="flex items-center">
|
||||
<div>{props.filed}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left font-medium text-slate-800">{props.data}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className={`text-left`}>{props.seed}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">
|
||||
<div className={`text-xs inline-flex font-medium rounded-full text-center px-2.5 py-1 ${statusColor(props.status)}`}>{props.status}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 pt-3 pb-2 whitespace-nowrap w-px">
|
||||
{/* Menu button */}
|
||||
<button className="text-slate-400 hover:text-slate-500 rounded-full">
|
||||
<span className="sr-only">Menu</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="2" />
|
||||
<circle cx="10" cy="16" r="2" />
|
||||
<circle cx="22" cy="16" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 pt-3 pb-2 whitespace-nowrap w-px">
|
||||
{/* Menu button */}
|
||||
<button className="text-slate-400 hover:text-slate-500 rounded-full">
|
||||
<span className="sr-only">Menu</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="2" />
|
||||
<circle cx="10" cy="16" r="2" />
|
||||
<circle cx="22" cy="16" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,109 +2,167 @@ import React from 'react';
|
||||
|
||||
import Image from '../../images/transactions-image-04.svg';
|
||||
|
||||
function TransactionPanel({
|
||||
validatePanelOpen,
|
||||
setValidatePanelOpen
|
||||
}) {
|
||||
|
||||
function TransactionPanel({ validatePanelOpen, setValidatePanelOpen }) {
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 sm:left-auto z-20 transition-transform duration-200 ease-in-out ${
|
||||
validatePanelOpen ? 'translate-x-' : 'translate-x-full'
|
||||
}`}>
|
||||
<div className="top-16 bg-gradient-to-b from-[#e4eaf0] to-white shrink-0 border-l border-slate-200 w-full sm:w-[390px] h-fit">
|
||||
<button
|
||||
onClick={() => setValidatePanelOpen(false)}
|
||||
className="absolute top-0 right-0 mt-6 mr-6 group p-2"
|
||||
<div
|
||||
className={`absolute inset-0 sm:left-auto z-20 transition-transform duration-200 ease-in-out ${
|
||||
validatePanelOpen ? 'translate-x-' : 'translate-x-full'
|
||||
}`}
|
||||
>
|
||||
<div className="top-16 bg-gradient-to-b from-[#e4eaf0] to-white shrink-0 border-l border-slate-200 w-full sm:w-[390px] h-fit">
|
||||
<button
|
||||
onClick={() => setValidatePanelOpen(false)}
|
||||
className="absolute top-0 right-0 mt-6 mr-6 group p-2"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 fill-slate-400 group-hover:fill-slate-600 pointer-events-none"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg className="w-4 h-4 fill-slate-400 group-hover:fill-slate-600 pointer-events-none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m7.95 6.536 4.242-4.243a1 1 0 1 1 1.415 1.414L9.364 7.95l4.243 4.242a1 1 0 1 1-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 0 1-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 0 1 1.414-1.414L7.95 6.536Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="py-8 px-4 lg:px-8">
|
||||
<div className="max-w-sm mx-auto lg:max-w-none">
|
||||
<div className="text-slate-800 font-semibold text-center mb-1">Bank Transfer</div>
|
||||
<div className="text-sm text-center italic">22/01/2022, 8:56 PM</div>
|
||||
{/* Details */}
|
||||
<div className="drop-shadow-lg mt-12">
|
||||
{/* Top */}
|
||||
<div className="bg-white rounded-t-xl px-5 pb-2.5 text-center">
|
||||
<div className="mb-3 text-center">
|
||||
<img className="inline-flex w-12 h-12 rounded-full -mt-6" src={Image} width="48" height="48" alt="Transaction 04" />
|
||||
</div>
|
||||
<div className="mt-5 text-2xl font-semibold text-emerald-500 mb-1">0.012 IDN</div>
|
||||
<div className="text-sm font-medium text-slate-800 mb-3">Total amount fee</div>
|
||||
<path d="m7.95 6.536 4.242-4.243a1 1 0 1 1 1.415 1.414L9.364 7.95l4.243 4.242a1 1 0 1 1-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 0 1-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 0 1 1.414-1.414L7.95 6.536Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="py-8 px-4 lg:px-8">
|
||||
<div className="max-w-sm mx-auto lg:max-w-none">
|
||||
<div className="text-slate-800 font-semibold text-center mb-1">
|
||||
Bank Transfer
|
||||
</div>
|
||||
<div className="text-sm text-center italic">
|
||||
22/01/2022, 8:56 PM
|
||||
</div>
|
||||
{/* Details */}
|
||||
<div className="drop-shadow-lg mt-12">
|
||||
{/* Top */}
|
||||
<div className="bg-white rounded-t-xl px-5 pb-2.5 text-center">
|
||||
<div className="mb-3 text-center">
|
||||
<img
|
||||
className="inline-flex w-12 h-12 rounded-full -mt-6"
|
||||
src={Image}
|
||||
width="48"
|
||||
height="48"
|
||||
alt="Transaction 04"
|
||||
/>
|
||||
</div>
|
||||
{/* Divider */}
|
||||
<div className="flex justify-between items-center" aria-hidden="true">
|
||||
<svg className="w-5 h-5 fill-white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
<div className="grow w-full h-5 bg-white flex flex-col justify-center">
|
||||
<div className="h-px w-full border-t border-dashed border-slate-200" />
|
||||
</div>
|
||||
<svg className="w-5 h-5 fill-white rotate-180" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
<div className="mt-5 text-2xl font-semibold text-emerald-500 mb-1">
|
||||
0.012 IDN
|
||||
</div>
|
||||
{/* Bottom */}
|
||||
<div className="bg-white rounded-b-xl p-5 pt-8 pb-10 text-sm space-y-3">
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Validator:</span>
|
||||
<span className="font-medium text-slate-700 text-right">IT17 2207 1010 0504 0006 88</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Recipient:</span>
|
||||
<span className="font-medium text-slate-700 text-right">IT17 2207 1010 0504 0006 88</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Transaction:</span>
|
||||
<span className="font-medium text-slate-700 text-right">145 bytes</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-slate-800 mb-3">
|
||||
Total amount fee
|
||||
</div>
|
||||
</div>
|
||||
{/* Receipts */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-medium text-slate-800 mb-2">Private Data</div>
|
||||
<form className="rounded bg-slate-50 border border-dashed border-slate-300 text-center px-5 py-8">
|
||||
<svg className="inline-flex w-4 h-4 fill-slate-400 mb-3" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Divider */}
|
||||
<div
|
||||
className="flex justify-between items-center"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 fill-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
<div className="grow w-full h-5 bg-white flex flex-col justify-center">
|
||||
<div className="h-px w-full border-t border-dashed border-slate-200" />
|
||||
</div>
|
||||
<svg
|
||||
className="w-5 h-5 fill-white rotate-180"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0 20c5.523 0 10-4.477 10-10S5.523 0 0 0h20v20H0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
{/* Bottom */}
|
||||
<div className="bg-white rounded-b-xl p-5 pt-8 pb-10 text-sm space-y-3">
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Validator:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
IT17 2207 1010 0504 0006 88
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Recipient:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
IT17 2207 1010 0504 0006 88
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between space-x-1">
|
||||
<span className="italic">Transaction:</span>
|
||||
<span className="font-medium text-slate-700 text-right">
|
||||
145 bytes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Receipts */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-medium text-slate-800 mb-2">
|
||||
Private Data
|
||||
</div>
|
||||
<form className="rounded bg-slate-50 border border-dashed border-slate-300 text-center px-5 py-8">
|
||||
<svg
|
||||
className="inline-flex w-4 h-4 fill-slate-400 mb-3"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 4c-.3 0-.5.1-.7.3L1.6 10 3 11.4l4-4V16h2V7.4l4 4 1.4-1.4-5.7-5.7C8.5 4.1 8.3 4 8 4ZM1 2h14V0H1v2Z" />
|
||||
</svg>
|
||||
<label
|
||||
htmlFor="upload"
|
||||
className="block text-sm text-slate-400 italic"
|
||||
>
|
||||
Upload exported JSON data
|
||||
</label>
|
||||
<input className="sr-only" id="upload" type="file" />
|
||||
</form>
|
||||
</div>
|
||||
{/* Notes */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-medium text-slate-800 mb-2">
|
||||
Notes
|
||||
</div>
|
||||
<form>
|
||||
<label className="sr-only" htmlFor="notes">
|
||||
Write a note
|
||||
</label>
|
||||
<textarea
|
||||
id="notes"
|
||||
className="resize-none form-textarea w-full focus:border-slate-300"
|
||||
rows="4"
|
||||
placeholder="Write a note…"
|
||||
defaultValue={''}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
{/* Download / Report */}
|
||||
<div className="flex items-center space-x-3 mt-[84px]">
|
||||
<div className="w-1/2">
|
||||
<button className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600">
|
||||
<svg
|
||||
className="w-4 h-4 fill-current text-slate-400 shrink-0 rotate-180"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8 4c-.3 0-.5.1-.7.3L1.6 10 3 11.4l4-4V16h2V7.4l4 4 1.4-1.4-5.7-5.7C8.5 4.1 8.3 4 8 4ZM1 2h14V0H1v2Z" />
|
||||
</svg>
|
||||
<label htmlFor="upload" className="block text-sm text-slate-400 italic">Upload exported JSON data</label>
|
||||
<input className="sr-only" id="upload" type="file" />
|
||||
</form>
|
||||
<span className="ml-2 text-sm font-medium">Validate</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Notes */}
|
||||
<div className="mt-6">
|
||||
<div className="text-sm font-medium text-slate-800 mb-2">Notes</div>
|
||||
<form>
|
||||
<label className="sr-only" htmlFor="notes">Write a note</label>
|
||||
<textarea id="notes" className="resize-none form-textarea w-full focus:border-slate-300" rows="4" placeholder="Write a note…" defaultValue={""} />
|
||||
</form>
|
||||
</div>
|
||||
{/* Download / Report */}
|
||||
<div className="flex items-center space-x-3 mt-[84px]">
|
||||
<div className="w-1/2">
|
||||
<button className="btn w-full border-slate-200 hover:border-slate-300 text-slate-600">
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 shrink-0 rotate-180" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4c-.3 0-.5.1-.7.3L1.6 10 3 11.4l4-4V16h2V7.4l4 4 1.4-1.4-5.7-5.7C8.5 4.1 8.3 4 8 4ZM1 2h14V0H1v2Z" />
|
||||
</svg>
|
||||
<span className="ml-2 text-sm font-medium">Validate</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<button className="btn w-full border-slate-200 hover:border-slate-300 text-rose-500">
|
||||
<svg className="w-4 h-4 fill-current shrink-0" viewBox="0 0 16 16">
|
||||
<path d="M7.001 3h2v4h-2V3Zm1 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2ZM15 16a1 1 0 0 1-.6-.2L10.667 13H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1ZM2 11h9a1 1 0 0 1 .6.2L14 13V2H2v9Z" />
|
||||
</svg>
|
||||
<span className="ml-2 text-sm font-medium">Invalidate</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<button className="btn w-full border-slate-200 hover:border-slate-300 text-rose-500">
|
||||
<svg
|
||||
className="w-4 h-4 fill-current shrink-0"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M7.001 3h2v4h-2V3Zm1 7a1 1 0 1 1 0-2 1 1 0 0 1 0 2ZM15 16a1 1 0 0 1-.6-.2L10.667 13H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1ZM2 11h9a1 1 0 0 1 .6.2L14 13V2H2v9Z" />
|
||||
</svg>
|
||||
<span className="ml-2 text-sm font-medium">Invalidate</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import ValidateTableItem from "./ValidateItem";
|
||||
|
||||
function ValidateTable({
|
||||
setValidatePanelOpen
|
||||
}) {
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ValidateTableItem from './ValidateItem';
|
||||
|
||||
function ValidateTable({ setValidatePanelOpen }) {
|
||||
const validateData = [
|
||||
{
|
||||
id: '10',
|
||||
@@ -59,35 +56,40 @@ function ValidateTable({
|
||||
|
||||
const handleSelectAll = () => {
|
||||
setSelectAll(!selectAll);
|
||||
setMarked(selectedList.map(li => li.id));
|
||||
setMarked(selectedList.map((li) => li.id));
|
||||
if (selectAll) {
|
||||
setMarked([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = e => {
|
||||
const handleClick = (e) => {
|
||||
const { id, checked } = e.target;
|
||||
setSelectAll(false);
|
||||
setMarked([...marked, id]);
|
||||
if (!checked) {
|
||||
setMarked(marked.filter(item => item !== id));
|
||||
setMarked(marked.filter((item) => item !== id));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-fit">
|
||||
<div>
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-fit">
|
||||
{/* Table header */}
|
||||
<thead className="bg-white text-xs font-semibold uppercase text-slate-500 border-t border-b-2 border-slate-200">
|
||||
<div className="w-fit">
|
||||
<div>
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-auto w-fit">
|
||||
{/* Table header */}
|
||||
<thead className="bg-white text-xs font-semibold uppercase text-slate-500 border-t border-b-2 border-slate-200">
|
||||
<tr>
|
||||
<th className="first:pl-4 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<label className="inline-flex">
|
||||
<span className="sr-only">Select all</span>
|
||||
<input className="form-checkbox" type="checkbox" checked={selectAll} onChange={handleSelectAll} />
|
||||
<input
|
||||
className="form-checkbox"
|
||||
type="checkbox"
|
||||
checked={selectAll}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</th>
|
||||
@@ -105,30 +107,30 @@ function ValidateTable({
|
||||
</th>
|
||||
<th className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap" />
|
||||
</tr>
|
||||
</thead>
|
||||
{/* Table body */}
|
||||
<tbody className="text-sm divide-y divide-slate-200 border-b border-slate-200">
|
||||
</thead>
|
||||
{/* Table body */}
|
||||
<tbody className="text-sm divide-y divide-slate-200 border-b border-slate-200">
|
||||
{selectedList.map((data) => {
|
||||
return (
|
||||
<ValidateTableItem
|
||||
key={data.id}
|
||||
id={data.id}
|
||||
filed={data.filed}
|
||||
data={data.data}
|
||||
status={data.status}
|
||||
seed={data.seed}
|
||||
check={data.check}
|
||||
handleClick={handleClick}
|
||||
isChecked={marked.includes(data.id)}
|
||||
setValidatePanelOpen={setValidatePanelOpen}
|
||||
/>
|
||||
<ValidateTableItem
|
||||
key={data.id}
|
||||
id={data.id}
|
||||
filed={data.filed}
|
||||
data={data.data}
|
||||
status={data.status}
|
||||
seed={data.seed}
|
||||
check={data.check}
|
||||
handleClick={handleClick}
|
||||
isChecked={marked.includes(data.id)}
|
||||
setValidatePanelOpen={setValidatePanelOpen}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,50 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import ValidationSeasonItem from "./ValidationSeasonItem";
|
||||
import ValidationUsersImg from "./ValidationUsersImg";
|
||||
import ValidationSeasonItem from './ValidationSeasonItem';
|
||||
import ValidationUsersImg from './ValidationUsersImg';
|
||||
|
||||
function ValidateRoadMap({ season }) {
|
||||
|
||||
return (
|
||||
<article className="pt-3">
|
||||
<div className="xl:flex">
|
||||
<div className="w-32 shrink-0">
|
||||
<h2 className="text-xl leading-snug font-bold text-slate-800 xl:leading-7 mb-4 xl:mb-0">{''}</h2>
|
||||
</div>
|
||||
<div className="grow pb-6 border-b border-slate-200">
|
||||
{season.sender_avatar &&
|
||||
<article className="pt-3">
|
||||
<div className="xl:flex">
|
||||
<div className="w-32 shrink-0">
|
||||
<h2 className="text-xl leading-snug font-bold text-slate-800 xl:leading-7 mb-4 xl:mb-0">
|
||||
{''}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grow pb-6 border-b border-slate-200">
|
||||
{season.sender_avatar && (
|
||||
<header>
|
||||
<div className="flex flex-nowrap items-center space-x-2 mb-6">
|
||||
{/* Avatars */}
|
||||
<div className="flex shrink-0 -space-x-3 -ml-px">
|
||||
<ValidationUsersImg image={season.sender_avatar} size={season.avatar_size}/>
|
||||
<ValidationUsersImg
|
||||
image={season.sender_avatar}
|
||||
size={season.avatar_size}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-slate-400">·</div>
|
||||
</div>
|
||||
</header>}
|
||||
{/* List */}
|
||||
<ul className="-my-2">
|
||||
{/* List item(s) */}
|
||||
{season.transaction.map((item, index) => {
|
||||
return <ValidationSeasonItem key={item.label} length={season.transaction.length} item={item} index={index}/>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
{/* List */}
|
||||
<ul className="-my-2">
|
||||
{/* List item(s) */}
|
||||
{season.transaction.map((item, index) => {
|
||||
return (
|
||||
<ValidationSeasonItem
|
||||
key={item.label}
|
||||
length={season.transaction.length}
|
||||
item={item}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default ValidateRoadMap;
|
||||
export default ValidateRoadMap;
|
||||
|
||||
@@ -1,30 +1,59 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
function ValidationSeasonItem({ length, item, index }) {
|
||||
return (
|
||||
<li className="relative pt-2 pb-2.5">
|
||||
<div className="flex items-center mb-2.5">
|
||||
{length-1!==index && <div className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3" aria-hidden="true"></div>}
|
||||
<div className={`${item.address ? "bg-indigo-500" : "bg-[#FF0000]"} absolute left-0 rounded-full`} aria-hidden="true">
|
||||
<svg className="w-5 h-5 fill-current text-white" viewBox="0 0 20 20">
|
||||
{item.address && <path d="M14.4 8.4L13 7l-4 4-2-2-1.4 1.4L9 13.8z"/>}
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-slate-800 pl-9">
|
||||
{`${item.label} ${item.address ? "by" : "Updated"}`} <a className="cursor-pointer font-bold text-indigo-500 hover:text-indigo-600 underline hover:no-underline" >{item.address}</a>
|
||||
</h3>
|
||||
<li className="relative pt-2 pb-2.5">
|
||||
<div className="flex items-center mb-2.5">
|
||||
{length - 1 !== index && (
|
||||
<div
|
||||
className="absolute left-0 h-full w-0.5 bg-slate-200 self-start ml-2.5 -translate-x-1/2 translate-y-3"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
item.address ? 'bg-indigo-500' : 'bg-[#FF0000]'
|
||||
} absolute left-0 rounded-full`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg className="w-5 h-5 fill-current text-white" viewBox="0 0 20 20">
|
||||
{item.address && (
|
||||
<path d="M14.4 8.4L13 7l-4 4-2-2-1.4 1.4L9 13.8z" />
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
<div className="block text-slate-800 w-fit pl-9">
|
||||
<div className="text-base font-semibold underline mb-[-10px]">{item.transaction_id}</div>
|
||||
<span className="font-normal text-slate-600 text-[10px]">Transactions ID</span>
|
||||
<h3 className="text-lg font-bold text-slate-800 pl-9">
|
||||
{`${item.label} ${item.address ? 'by' : 'Updated'}`}{' '}
|
||||
<a className="cursor-pointer font-bold text-indigo-500 hover:text-indigo-600 underline hover:no-underline">
|
||||
{item.address}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="block text-slate-800 w-fit pl-9">
|
||||
<div className="text-base font-semibold underline mb-[-10px]">
|
||||
{item.transaction_id}
|
||||
</div>
|
||||
<div className="mb-3.5 block text-slate-800 w-fit pl-9">
|
||||
<div className="text-base font-semibold underline mb-[-10px]">{item.value}</div>
|
||||
<span className={`${!item.value && "hidden"} font-normal text-slate-600 text-[10px]`}>{item.address ? "Validated data" : "Updated data"}</span>
|
||||
<span className="font-normal text-slate-600 text-[10px]">
|
||||
Transactions ID
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-3.5 block text-slate-800 w-fit pl-9">
|
||||
<div className="text-base font-semibold underline mb-[-10px]">
|
||||
{item.value}
|
||||
</div>
|
||||
<a className="cursor-pointer pl-9 font-normal text-sm text-indigo-500 hover:text-indigo-600">Explore -></a>
|
||||
</li>
|
||||
<span
|
||||
className={`${
|
||||
!item.value && 'hidden'
|
||||
} font-normal text-slate-600 text-[10px]`}
|
||||
>
|
||||
{item.address ? 'Validated data' : 'Updated data'}
|
||||
</span>
|
||||
</div>
|
||||
<a className="cursor-pointer pl-9 font-normal text-sm text-indigo-500 hover:text-indigo-600">
|
||||
Explore ->
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default ValidationSeasonItem
|
||||
export default ValidationSeasonItem;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
function ValidationUsersImg({ image,size }) {
|
||||
import React from 'react';
|
||||
|
||||
function ValidationUsersImg({ image, size }) {
|
||||
return (
|
||||
<a className="block">
|
||||
<img className="rounded-full border-2 border-slate-100 box-content" src={image} alt={image} width={size} height={size}/>
|
||||
</a>
|
||||
<a className="block">
|
||||
<img
|
||||
className="rounded-full border-2 border-slate-100 box-content"
|
||||
src={image}
|
||||
alt={image}
|
||||
width={size}
|
||||
height={size}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default ValidationUsersImg
|
||||
export default ValidationUsersImg;
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
import {store} from '../store/store';
|
||||
import { store } from '../store/store';
|
||||
import { jsonReplacer } from '../utils/Utils';
|
||||
|
||||
function prepareUrl(url) {
|
||||
if (url.startsWith('http')) return url;
|
||||
return `${window.location.origin}/api/${url}`;
|
||||
return `${fetchWrapper.baseUrl}${url}`;
|
||||
}
|
||||
|
||||
function handleRequest(method, url, headers, attempts, token, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
(function internalRequest() {
|
||||
if (store) store.loading = true;
|
||||
return request(method, url, headers, token, body)
|
||||
.then(resolve)
|
||||
.catch(err => --attempts > 0 ? internalRequest() : reject(err))
|
||||
})()
|
||||
.catch((err) => (--attempts > 0 ? internalRequest() : reject(err)));
|
||||
})();
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch(() => [])
|
||||
};
|
||||
.then((res) => {
|
||||
store.loading = false;
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
store.loading = false;
|
||||
console.log(err);
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request
|
||||
@@ -26,67 +35,72 @@ function handleRequest(method, url, headers, attempts, token, body) {
|
||||
* @param {object} headers - request headers
|
||||
*/
|
||||
function request(method, url, headers = {}, token, body) {
|
||||
let controller = new AbortController;
|
||||
let controller = new AbortController();
|
||||
let requestOptions = {};
|
||||
if (method === 'GET' || method === 'DELETE'){
|
||||
if (method === 'GET' || method === 'DELETE') {
|
||||
requestOptions = {
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: `IDNTTY ${token}`,
|
||||
...headers
|
||||
...headers,
|
||||
},
|
||||
signal: controller.signal,
|
||||
}
|
||||
};
|
||||
if (method === 'POST' || method === 'PUT'){
|
||||
};
|
||||
}
|
||||
if (method === 'POST' || method === 'PUT') {
|
||||
requestOptions = {
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: `IDNTTY ${token}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify(body, jsonReplacer),
|
||||
signal: controller.signal,
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
if (!token) {
|
||||
requestOptions.headers.Authorization = null;
|
||||
}
|
||||
setTimeout(() => controller.abort(), 3000);
|
||||
return fetch(prepareUrl(url), requestOptions);
|
||||
};
|
||||
setTimeout(() => controller.abort(), 15000);
|
||||
return fetch(prepareUrl(url), requestOptions).then((res) => {
|
||||
if (res.status > 400) {
|
||||
store.addNotification(new Date().getTime(), 'error', res.statusText);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
function get(url, headers, attempts = 1) {
|
||||
function get(url, headers, attempts = 3) {
|
||||
return handleRequest('GET', url, headers, attempts, null);
|
||||
}
|
||||
|
||||
function getAuth(url, headers, attempts = 1) {
|
||||
function getAuth(url, headers, attempts = 3) {
|
||||
return handleRequest('GET', url, headers, attempts, store.tokenKey);
|
||||
}
|
||||
|
||||
function del(url, headers, attempts = 1) {
|
||||
function del(url, headers, attempts = 3) {
|
||||
return handleRequest('DELETE', url, headers, attempts, null);
|
||||
}
|
||||
|
||||
function delAuth(url, headers, attempts = 1) {
|
||||
function delAuth(url, headers, attempts = 3) {
|
||||
return handleRequest('DELETE', url, headers, attempts, store.tokenKey);
|
||||
}
|
||||
|
||||
function post(url, headers, body, attempts = 1) {
|
||||
function post(url, headers, body, attempts = 3) {
|
||||
return handleRequest('POST', url, headers, attempts, null, body);
|
||||
}
|
||||
|
||||
function postAuth(url, headers, body, attempts = 1) {
|
||||
function postAuth(url, headers, body, attempts = 3) {
|
||||
return handleRequest('POST', url, headers, attempts, store.tokenKey, body);
|
||||
}
|
||||
|
||||
function put(url, headers, body, attempts = 1) {
|
||||
function put(url, headers, body, attempts = 3) {
|
||||
return handleRequest('PUT', url, headers, attempts, null, body);
|
||||
}
|
||||
|
||||
function putAuth(url, headers, body, attempts = 1) {
|
||||
function putAuth(url, headers, body, attempts = 3) {
|
||||
return handleRequest('PUT', url, headers, attempts, store.tokenKey, body);
|
||||
}
|
||||
|
||||
@@ -99,5 +113,7 @@ export const fetchWrapper = {
|
||||
delAuth,
|
||||
postAuth,
|
||||
putAuth,
|
||||
baseUrl: ''
|
||||
};
|
||||
baseUrl: '',
|
||||
};
|
||||
|
||||
fetchWrapper.baseUrl = `${window.location.origin}/api/`;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
export const labelMap = {
|
||||
firstname: 'First name',
|
||||
secondname: 'Second name',
|
||||
gender: "Gender",
|
||||
birthdate: "Birthdate",
|
||||
placeofbirth: "Place of birth",
|
||||
nationality: "Nationality",
|
||||
nationalid: "National ID",
|
||||
nationaldoctype: "National doctype",
|
||||
nationaldocid: "National doc ID",
|
||||
nationaldocissuedate: "National doc issue date",
|
||||
nationaldocexpirydate: "National doc expiry date",
|
||||
telephone: "Telephone",
|
||||
twitter: "Twitter",
|
||||
facebook: "Facebook",
|
||||
instagram: "Instagram",
|
||||
youtube: "Youtube",
|
||||
wechat: "Wechat",
|
||||
tiktok: "Tiktok",
|
||||
linkedin: "Linkedin",
|
||||
vk: "Vk",
|
||||
github: "Github",
|
||||
telegram: "Telegram",
|
||||
qq: "Qq",
|
||||
snapchat: "Snapchat",
|
||||
reddit: "Reddit",
|
||||
gender: 'Gender',
|
||||
birthdate: 'Birthdate',
|
||||
placeofbirth: 'Place of birth',
|
||||
nationality: 'Nationality',
|
||||
nationalid: 'National ID',
|
||||
nationaldoctype: 'National doctype',
|
||||
nationaldocid: 'National doc ID',
|
||||
nationaldocissuedate: 'National doc issue date',
|
||||
nationaldocexpirydate: 'National doc expiry date',
|
||||
telephone: 'Telephone',
|
||||
twitter: 'Twitter',
|
||||
facebook: 'Facebook',
|
||||
instagram: 'Instagram',
|
||||
youtube: 'Youtube',
|
||||
wechat: 'Wechat',
|
||||
tiktok: 'Tiktok',
|
||||
linkedin: 'Linkedin',
|
||||
vk: 'Vk',
|
||||
github: 'Github',
|
||||
telegram: 'Telegram',
|
||||
qq: 'Qq',
|
||||
snapchat: 'Snapchat',
|
||||
reddit: 'Reddit',
|
||||
};
|
||||
|
||||
7
src/shared/statusMap.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const statusMap = {
|
||||
completed: 'Completed',
|
||||
incorrect: 'Incorrect',
|
||||
stored: 'Stored',
|
||||
blockchained: 'Blockchained',
|
||||
processed: 'Processed',
|
||||
};
|
||||
@@ -1,202 +1,401 @@
|
||||
import {reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved} from "mobx";
|
||||
import {cryptography} from "@liskhq/lisk-client";
|
||||
import { passphrase } from "@liskhq/lisk-client";
|
||||
import {getNodeInfo} from '../api/node';
|
||||
import {fetchWrapper} from '../shared/fetchWrapper';
|
||||
import {labelMap} from "../shared/labelMap";
|
||||
import {generateSvgAvatar} from "../images/GenerateOnboardingSvg/GenerateSvg";
|
||||
import { reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved } from 'mobx';
|
||||
import { cryptography, transactions } from '@liskhq/lisk-client';
|
||||
import { passphrase } from '@liskhq/lisk-client';
|
||||
import { getNodeInfo } from '../api/node';
|
||||
import { fetchWrapper } from '../shared/fetchWrapper';
|
||||
import { labelMap } from '../shared/labelMap';
|
||||
import { statusMap } from '../shared/statusMap';
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import { decryptedData } from '../utils/decryptedData';
|
||||
import {
|
||||
encryptAccountData,
|
||||
encryptSharedData,
|
||||
formatBytes,
|
||||
generateTransaction,
|
||||
hashAccountData,
|
||||
} from '../utils/Utils';
|
||||
import sodium from 'sodium-universal';
|
||||
import { countryCodes } from '../utils/countryCodes';
|
||||
|
||||
class Store {
|
||||
_accountData = []
|
||||
_accountData = [];
|
||||
|
||||
_passPhrase = ''
|
||||
_passPhrase = '';
|
||||
|
||||
_nodeInfo = {}
|
||||
_nodeInfo = {};
|
||||
|
||||
_keysArray = []
|
||||
_keysArray = [];
|
||||
|
||||
_sharedData = []
|
||||
_sharedData = [];
|
||||
|
||||
_transactionsInfo = []
|
||||
_transactionsInfo = [];
|
||||
|
||||
_accountInfo = {};
|
||||
|
||||
_loading = false;
|
||||
|
||||
_tempTransactions = [];
|
||||
|
||||
_vpnServers = [];
|
||||
|
||||
_canVPN = false;
|
||||
|
||||
_delegates = {
|
||||
data: [],
|
||||
meta: {
|
||||
count: 0,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
},
|
||||
};
|
||||
|
||||
_notifications = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {});
|
||||
|
||||
this.fetchNodeInfo()
|
||||
reaction(
|
||||
() => this.keysArray,
|
||||
() => this.fetchSharedData()
|
||||
);
|
||||
reaction(
|
||||
() => this.address,
|
||||
() => this.fetchAccountInfo()
|
||||
);
|
||||
onBecomeObserved(this, 'decryptedAccountData', () => this.fetchNewAccountData());
|
||||
onBecomeUnobserved(this, 'decryptedAccountData', () => this.unobservedAccountData());
|
||||
onBecomeObserved(this, 'sharedData', () => this.fetchKeysArray());
|
||||
onBecomeUnobserved(this, 'sharedData', () => this.unobservedSharedData());
|
||||
onBecomeObserved(this, 'transactionsInfo', () => this.fetchTransactionsInfo());
|
||||
onBecomeUnobserved(this, 'transactionsInfo', () => this.unobservedTransactionsInfo());
|
||||
|
||||
reaction(() => this.keysArray, () => this.fetchSharedData())
|
||||
onBecomeObserved(this, "decryptedAccountData", () => this.fetchNewAccountData())
|
||||
onBecomeObserved(this, "sharedData", () => this.fetchKeysArray())
|
||||
onBecomeUnobserved(this, "sharedData", () => this.unobservedSharedData())
|
||||
onBecomeObserved(this, "transactionsInfo", () => this.fetchTransactionsInfo())
|
||||
onBecomeUnobserved(this, "transactionsInfo", () => this.unobservedTransactionsInfo())
|
||||
};
|
||||
|
||||
fetchNewAccountData() {
|
||||
getNodeInfo()
|
||||
.then((info)=>{
|
||||
this.fetchNodeInfoSuccess(info)
|
||||
fetchWrapper.getAuth('data/private', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID
|
||||
}).then((res) => this.accountDataFetchChange(res))
|
||||
})
|
||||
.catch((err) => this.fetchNodeInfoFailed(err))
|
||||
this.fetchNodeInfo();
|
||||
}
|
||||
|
||||
pushAccountData(data = this.accountData) {
|
||||
fetchWrapper.postAuth('data/private', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID
|
||||
}, [...this.encryptAccountData(data)])
|
||||
.then(() => this.fetchNewAccountData())
|
||||
fetchVPNServers() {
|
||||
fetchWrapper
|
||||
.getAuth('vpn/servers', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
})
|
||||
.then((r) => this.fetchVPNServersSuccess(r));
|
||||
}
|
||||
|
||||
fetchDelegates(offset = 0, limit = 100) {
|
||||
fetchWrapper
|
||||
.getAuth(`delegates?limit=${limit}&offset=${offset}`)
|
||||
.then((r) => this.fetchDelegatesSuccess(r));
|
||||
}
|
||||
|
||||
fetchNewAccountData() {
|
||||
fetchWrapper
|
||||
.getAuth('data/private', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
})
|
||||
.then((res) => this.accountDataFetchChange(res))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
fetchAccountInfo() {
|
||||
fetchWrapper
|
||||
.get(`accounts/${this.address}`)
|
||||
.then((res) => this.fetchAccountInfoSuccess(res))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
fetchKeysArray() {
|
||||
getNodeInfo()
|
||||
.then((info)=>{
|
||||
this.fetchNodeInfoSuccess(info)
|
||||
fetchWrapper.getAuth('data/shared/', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID
|
||||
}).then((res) => this.keysArrayFetchChange(res))
|
||||
.then((info) => {
|
||||
this.fetchNodeInfoSuccess(info);
|
||||
fetchWrapper
|
||||
.getAuth('data/shared/', {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
})
|
||||
.then((res) => this.keysArrayFetchChange(res));
|
||||
})
|
||||
.catch((err) => this.fetchNodeInfoFailed(err))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
fetchTransactionsInfo() {
|
||||
fetchWrapper.get(`account/transactions/${this.address}`, {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID
|
||||
}).then((res) => this.saveInfoTransactions(res))
|
||||
};
|
||||
fetchWrapper
|
||||
.get(`account/transactions/${this.address}`, {
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
})
|
||||
.then((res) => this.saveInfoTransactions(res))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
fetchTempTransactions() {
|
||||
fetchWrapper
|
||||
.get('node/transactions')
|
||||
.then((res) => this.fetchTempTransactionsSuccess(res))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
fetchPassPhrase() {
|
||||
this._passPhrase = sessionStorage.getItem('passPhrase') || '';
|
||||
}
|
||||
|
||||
fetchSharedData() {
|
||||
this._keysArray.forEach((elem) => {
|
||||
fetchWrapper
|
||||
.get(`data/shared/${elem}`)
|
||||
.then((res) => this.changeSharedData(res, elem))
|
||||
.catch((err) => this.throwError(err));
|
||||
});
|
||||
}
|
||||
|
||||
fetchNodeInfo() {
|
||||
getNodeInfo()
|
||||
.then((info) => this.fetchNodeInfoSuccess(info))
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
pushSharedData(data, pubKey = this.pubKey) {
|
||||
data = data.filter((item) => item.status !== statusMap.blockchained);
|
||||
if (data.length > 0) {
|
||||
fetchWrapper
|
||||
.postAuth(
|
||||
'data/shared',
|
||||
{
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
},
|
||||
{
|
||||
publickey: this.pubKey,
|
||||
shared: encryptSharedData(data, this.passPhrase, pubKey),
|
||||
}
|
||||
)
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
}
|
||||
|
||||
pushAccountData(data = this.accountData) {
|
||||
fetchWrapper
|
||||
.postAuth(
|
||||
'data/private',
|
||||
{
|
||||
networkIdentifier: this.nodeInfo.networkIdentifier,
|
||||
lastBlockID: this.nodeInfo.lastBlockID,
|
||||
},
|
||||
[...encryptAccountData(data, this.passPhrase, this.pubKey)]
|
||||
)
|
||||
.then(() => this.fetchNewAccountData())
|
||||
.catch((err) => this.throwError(err));
|
||||
}
|
||||
|
||||
pushAccountDataToBlockchain(data = this.decryptedAccountData) {
|
||||
const [removed, changed] = hashAccountData(
|
||||
data,
|
||||
this.decryptedAccountData,
|
||||
this.accountFeaturesMap
|
||||
);
|
||||
const builder = generateTransaction(
|
||||
BigInt(this.accountInfo?.sequence?.nonce || 0),
|
||||
this.addressAndPubKey.publicKey,
|
||||
this.nodeInfo.networkIdentifier,
|
||||
this.passPhrase,
|
||||
this.nodeInfo?.genesisConfig?.minFeePerByte,
|
||||
this.nodeInfo?.genesisConfig?.baseFees
|
||||
);
|
||||
|
||||
let signedTx = null;
|
||||
|
||||
if (removed.length !== 0) signedTx = builder.remove(removed);
|
||||
|
||||
if (changed.length !== 0) signedTx = builder.update(changed);
|
||||
if (signedTx) {
|
||||
fetchWrapper
|
||||
.post('transactions', {}, signedTx)
|
||||
.then(() => this.fetchTempTransactions())
|
||||
.catch((err) => this.throwError(err));
|
||||
this.accountInfo.sequence.nonce++;
|
||||
}
|
||||
}
|
||||
|
||||
pushVoteTransaction(delegateAddress, amount) {
|
||||
const builder = generateTransaction(
|
||||
BigInt(this.accountInfo?.sequence?.nonce || 0),
|
||||
this.addressAndPubKey.publicKey,
|
||||
this.nodeInfo.networkIdentifier,
|
||||
this.passPhrase,
|
||||
this.nodeInfo?.genesisConfig?.minFeePerByte,
|
||||
this.nodeInfo?.genesisConfig?.baseFees
|
||||
);
|
||||
|
||||
const signedTx = builder.vote(delegateAddress, amount);
|
||||
|
||||
if (signedTx) {
|
||||
fetchWrapper
|
||||
.post('transactions', {}, signedTx)
|
||||
.then(() => this.fetchTempTransactions())
|
||||
.catch((err) => this.throwError(err));
|
||||
this.accountInfo.sequence.nonce++;
|
||||
}
|
||||
}
|
||||
|
||||
pushUnlockTransaction(delegateAddress) {
|
||||
const builder = generateTransaction(
|
||||
BigInt(this.accountInfo?.sequence?.nonce || 0),
|
||||
this.addressAndPubKey.publicKey,
|
||||
this.nodeInfo.networkIdentifier,
|
||||
this.passPhrase,
|
||||
this.nodeInfo?.genesisConfig?.minFeePerByte,
|
||||
this.nodeInfo?.genesisConfig?.baseFees
|
||||
);
|
||||
|
||||
const unlockObjects = this.accountLockedVotesCanReturn[delegateAddress];
|
||||
|
||||
const signedTx = builder.unlock(delegateAddress, unlockObjects);
|
||||
|
||||
if (signedTx) {
|
||||
fetchWrapper
|
||||
.post('transactions', {}, signedTx)
|
||||
.then(() => this.fetchTempTransactions())
|
||||
.catch((err) => this.throwError(err));
|
||||
this.accountInfo.sequence.nonce++;
|
||||
}
|
||||
}
|
||||
|
||||
generatePassPhrase() {
|
||||
this._passPhrase = passphrase.Mnemonic.generateMnemonic();
|
||||
sessionStorage.setItem('passPhrase', this._passPhrase);
|
||||
};
|
||||
|
||||
savePastPassPhrase(phrase) {
|
||||
this._passPhrase=phrase
|
||||
sessionStorage.setItem('passPhrase', this._passPhrase);
|
||||
};
|
||||
|
||||
fetchPassPhrase() {
|
||||
this._passPhrase = sessionStorage.getItem('passPhrase') || ''
|
||||
}
|
||||
|
||||
fetchSharedData() {
|
||||
this._keysArray.forEach((elem) => fetchWrapper.get(`data/shared/${elem}`).then((res) => this.changeSharedData(res)))
|
||||
};
|
||||
savePastPassPhrase(phrase) {
|
||||
this._passPhrase = phrase;
|
||||
sessionStorage.setItem('passPhrase', this._passPhrase);
|
||||
}
|
||||
|
||||
unobservedTransactionsInfo() {
|
||||
this._transactionsInfo = []
|
||||
};
|
||||
this._transactionsInfo = [];
|
||||
}
|
||||
|
||||
unobservedSharedData() {
|
||||
this._sharedData = []
|
||||
};
|
||||
this._sharedData = [];
|
||||
}
|
||||
|
||||
changeSharedData(incomingData) {
|
||||
unobservedAccountData() {
|
||||
this._accountData = [];
|
||||
}
|
||||
|
||||
changeSharedData(incomingData, id) {
|
||||
this._sharedData.push({
|
||||
data: incomingData.data,
|
||||
})
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
saveDataRegistration(data) {
|
||||
this._accountData = data;
|
||||
};
|
||||
}
|
||||
|
||||
clearDataRegistration() {
|
||||
this._accountData = [];
|
||||
};
|
||||
|
||||
fetchNodeInfo() {
|
||||
getNodeInfo()
|
||||
.then((info)=>this.fetchNodeInfoSuccess(info))
|
||||
.catch((err) => this.fetchNodeInfoFailed(err))
|
||||
}
|
||||
|
||||
saveInfoTransactions(transaction) {
|
||||
this._transactionsInfo = transaction.data
|
||||
this._transactionsInfo = transaction.data;
|
||||
}
|
||||
|
||||
accountDataFetchChange(res) {
|
||||
if (res.data) {
|
||||
this._accountData = res.data;
|
||||
}
|
||||
};
|
||||
this.fetchTempTransactions();
|
||||
}
|
||||
|
||||
keysArrayFetchChange(res) {
|
||||
this._keysArray = res.data;
|
||||
}
|
||||
|
||||
fetchNodeInfoSuccess(info) {
|
||||
this._nodeInfo = info.data;
|
||||
fetchVPNServersSuccess(res) {
|
||||
this._canVPN = res.meta;
|
||||
this._vpnServers = res.servers;
|
||||
}
|
||||
|
||||
fetchNodeInfoFailed(err) {
|
||||
fetchDelegatesSuccess(res) {
|
||||
this._delegates = res;
|
||||
}
|
||||
|
||||
fetchNodeInfoSuccess(info) {
|
||||
this._nodeInfo = info.data;
|
||||
this.fetchPassPhrase();
|
||||
}
|
||||
|
||||
fetchAccountInfoSuccess(res) {
|
||||
this._accountInfo = res.data;
|
||||
if (this.accountInfo?.sequence?.nonce)
|
||||
this.accountInfo.sequence.nonce = parseInt(this.accountInfo.sequence.nonce) || 0;
|
||||
}
|
||||
|
||||
fetchTempTransactionsSuccess(res) {
|
||||
this._tempTransactions = res.data;
|
||||
}
|
||||
|
||||
throwError(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
get sharedData() {
|
||||
return this._sharedData.map(elem => ({
|
||||
data: elem.data.map(item => ({
|
||||
label: labelMap[item.label],
|
||||
return this._sharedData.map((elem) => ({
|
||||
data: elem.data.map((item) => ({
|
||||
label: labelMap[item.label] || item.label,
|
||||
value: item.value,
|
||||
}))
|
||||
})),
|
||||
id: elem.id,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
get transactionsInfo() {
|
||||
return this._transactionsInfo.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
sender_avatar: item.asset.recipientAddress && generateSvgAvatar(item.senderPublicKey),
|
||||
avatar_size: 24,
|
||||
transaction: item.asset.features.map(asset => {
|
||||
return {
|
||||
transaction_id: item.id,
|
||||
address: item.asset.recipientAddress && cryptography.bufferToHex(cryptography.getAddressFromPublicKey(cryptography.hexToBuffer(item.senderPublicKey))),
|
||||
value: asset.value,
|
||||
label: labelMap[asset.label] || asset.label
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return this._transactionsInfo
|
||||
.filter((e) => e.moduleID === 1001)
|
||||
.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
sender_avatar: item?.asset?.recipientAddress && generateSvgAvatar(item.senderPublicKey),
|
||||
avatar_size: 24,
|
||||
transaction:
|
||||
item.asset?.features?.map((asset) => {
|
||||
return {
|
||||
transaction_id: item.id,
|
||||
address:
|
||||
item.asset.recipientAddress &&
|
||||
cryptography.bufferToHex(
|
||||
cryptography.getAddressFromPublicKey(
|
||||
cryptography.hexToBuffer(item.senderPublicKey)
|
||||
)
|
||||
),
|
||||
value: asset.value,
|
||||
label: labelMap[asset.label] || asset.label,
|
||||
};
|
||||
}) || [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get decryptedAccountData() {
|
||||
return this._accountData.map((elem) => ({
|
||||
...elem,
|
||||
key: elem.label,
|
||||
label: labelMap[elem.label] || elem.label,
|
||||
value: cryptography.decryptMessageWithPassphrase(elem.value, elem.value_nonce, this.passPhrase, this.pubKey).split(':')[1]
|
||||
}))
|
||||
}
|
||||
|
||||
encryptAccountData(data) {
|
||||
return data.map((item) => {
|
||||
let value = cryptography.encryptMessageWithPassphrase(cryptography.getRandomBytes(32).toString('hex') + ":" + item.value, this.passPhrase, this.pubKey);
|
||||
return {
|
||||
label: item.key,
|
||||
value: value.encryptedMessage,
|
||||
value_nonce: value.nonce,
|
||||
seed: item.seed,
|
||||
}
|
||||
})
|
||||
return decryptedData(
|
||||
this._accountData,
|
||||
this.accountFeatures,
|
||||
this.passPhrase,
|
||||
this.pubKey,
|
||||
this.processedFeatures
|
||||
);
|
||||
}
|
||||
|
||||
get firstName() {
|
||||
return this.decryptedAccountData.find(item => item.key === 'firstname')?.value
|
||||
return this.decryptedAccountData.find((item) => item.key === 'firstname')?.value;
|
||||
}
|
||||
|
||||
get lastName() {
|
||||
return this.decryptedAccountData.find(item => item.key === 'secondname')?.value
|
||||
return this.decryptedAccountData.find((item) => item.key === 'secondname')?.value;
|
||||
}
|
||||
|
||||
get accountName() {
|
||||
return this.firstName || this.lastName || this.pubKey
|
||||
return this.firstName || this.lastName || this.pubKey;
|
||||
}
|
||||
|
||||
get keysArray() {
|
||||
@@ -204,13 +403,16 @@ class Store {
|
||||
}
|
||||
|
||||
get passPhrase() {
|
||||
this.fetchPassPhrase()
|
||||
return this._passPhrase
|
||||
return this._passPhrase;
|
||||
}
|
||||
|
||||
get accountData() {
|
||||
return this._accountData
|
||||
};
|
||||
return this._accountData;
|
||||
}
|
||||
|
||||
get accountInfo() {
|
||||
return this._accountInfo;
|
||||
}
|
||||
|
||||
get nodeInfo() {
|
||||
return this._nodeInfo;
|
||||
@@ -220,20 +422,235 @@ class Store {
|
||||
return cryptography.getAddressAndPublicKeyFromPassphrase(this.passPhrase);
|
||||
}
|
||||
|
||||
get privateKey() {
|
||||
return cryptography.getPrivateAndPublicKeyFromPassphrase(this.passPhrase)?.privateKey;
|
||||
}
|
||||
|
||||
get vpnPrivateKey() {
|
||||
let x25519_sk = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES);
|
||||
sodium.crypto_sign_ed25519_sk_to_curve25519(x25519_sk, Buffer.from(this.privateKey, 'hex'));
|
||||
return x25519_sk.toString('Base64');
|
||||
}
|
||||
|
||||
get pubKey() {
|
||||
return this.addressAndPubKey.publicKey.toString('hex');
|
||||
}
|
||||
|
||||
get address() {
|
||||
return this.addressAndPubKey.address.toString('hex')
|
||||
return this.addressAndPubKey.address.toString('hex');
|
||||
}
|
||||
|
||||
get tokenKey() {
|
||||
const stringToSign = this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID;
|
||||
if(!stringToSign)
|
||||
return '';
|
||||
const sign = cryptography.signDataWithPassphrase(Buffer.from(stringToSign, 'hex'), this.passPhrase).toString('hex')
|
||||
return this.pubKey + ':' +sign
|
||||
if (!stringToSign) return '';
|
||||
const sign = cryptography
|
||||
.signDataWithPassphrase(Buffer.from(stringToSign, 'hex'), this.passPhrase)
|
||||
.toString('hex');
|
||||
return this.pubKey + ':' + sign;
|
||||
}
|
||||
|
||||
get accountIdentity() {
|
||||
return this.accountInfo?.identity || {};
|
||||
}
|
||||
|
||||
get accountFeatures() {
|
||||
return this.accountIdentity?.features || [];
|
||||
}
|
||||
|
||||
get accountBalance() {
|
||||
return (BigInt(this.accountInfo?.token?.balance || 0) / 100000000n).toString();
|
||||
}
|
||||
|
||||
get accountSentVotes() {
|
||||
return (this.accountInfo?.dpos?.sentVotes || []).reduce(
|
||||
(acc, e) => ({
|
||||
...acc,
|
||||
[e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get accountLockedVotes() {
|
||||
return (this.accountInfo?.dpos?.unlocking || []).reduce(
|
||||
(acc, e) => ({
|
||||
...acc,
|
||||
[e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n),
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get accountLockedVotesCanReturn() {
|
||||
return (this.accountInfo?.dpos?.unlocking || [])
|
||||
.filter((e) => this.nodeInfo.height - e.unvoteHeight > 2000)
|
||||
.reduce(
|
||||
(acc, e) => ({
|
||||
...acc,
|
||||
[e.delegateAddress]: [
|
||||
{
|
||||
...acc[e.delegateAddress],
|
||||
amount: BigInt(e.amount || 0) / 100000000n,
|
||||
unvoteHeight: e.unvoteHeight,
|
||||
},
|
||||
],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get accountLockedVotesCanReturnSum() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.accountLockedVotesCanReturn).map(([key, val]) => [
|
||||
key,
|
||||
val.reduce((sum, e) => sum + e.amount, 0n),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
get accountFeaturesMap() {
|
||||
return this.accountFeatures.reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
[item.label]: item.value,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
set loading(value) {
|
||||
this._loading = value;
|
||||
}
|
||||
|
||||
get tempTransactions() {
|
||||
return this._tempTransactions;
|
||||
}
|
||||
|
||||
get processedFeatures() {
|
||||
return this.tempTransactions
|
||||
.filter((item) => item.senderPublicKey === this.pubKey)
|
||||
.reduce((acc, item) => {
|
||||
const { features } = item.asset;
|
||||
return {
|
||||
...acc,
|
||||
...(features?.reduce((obj, feature) => ({ ...obj, [feature.label]: true }), {}) || {}),
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
get processedVotes() {
|
||||
return this.tempTransactions
|
||||
.filter((item) => item.senderPublicKey === this.pubKey)
|
||||
.reduce((acc, item) => {
|
||||
const { votes } = item.asset;
|
||||
return {
|
||||
...acc,
|
||||
...(votes?.reduce(
|
||||
(obj, vote) => ({
|
||||
...obj,
|
||||
[vote.delegateAddress]:
|
||||
BigInt(Number(vote.amount) > 0 ? vote.amount : vote.amount.slice(1)) / 100000000n +
|
||||
(obj[vote.delegateAddress] || 0n),
|
||||
}),
|
||||
{}
|
||||
) || {}),
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
get processedUnlocking() {
|
||||
return this.tempTransactions
|
||||
.filter((item) => item.senderPublicKey === this.pubKey)
|
||||
.reduce((acc, item) => {
|
||||
const { unlockObjects } = item.asset;
|
||||
return {
|
||||
...acc,
|
||||
...(unlockObjects?.reduce(
|
||||
(obj, vote) => ({
|
||||
...obj,
|
||||
[vote.delegateAddress]:
|
||||
BigInt(vote.amount) / 100000000n + (obj[vote.delegateAddress] || 0n),
|
||||
}),
|
||||
{}
|
||||
) || {}),
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
get vpnServers() {
|
||||
return this._vpnServers?.map((server) => ({
|
||||
...server,
|
||||
transferSum: formatBytes(Number(server.transferTx || 0) + Number(server.transferRx || 0)),
|
||||
trafficUsed: Math.floor(
|
||||
((Number(server.transferTx || 0) + Number(server.transferRx || 0)) /
|
||||
(50 * 1024 * 1024 * 1024)) *
|
||||
100
|
||||
),
|
||||
state: server.state === '1',
|
||||
shortCountry: countryCodes[server.country] || 'US',
|
||||
}));
|
||||
}
|
||||
|
||||
get canVPN() {
|
||||
return this._canVPN;
|
||||
}
|
||||
|
||||
get delegates() {
|
||||
const getStatus = (delegate) => {
|
||||
const { address } = delegate;
|
||||
|
||||
let amount = this.processedVotes[address] || this.processedUnlocking[address];
|
||||
|
||||
if (amount) return 'Pending';
|
||||
|
||||
amount = this.accountSentVotes[address];
|
||||
|
||||
if (amount) return 'Unvote';
|
||||
|
||||
return 'Vote';
|
||||
};
|
||||
|
||||
return this._delegates.data.map((delegate) => ({
|
||||
...delegate,
|
||||
status: getStatus(delegate),
|
||||
token: {
|
||||
...delegate.token,
|
||||
balance: Number(BigInt(delegate.token.balance) / 100000000n).toString(),
|
||||
},
|
||||
dpos: {
|
||||
...delegate.dpos,
|
||||
delegate: {
|
||||
...delegate.dpos.delegate,
|
||||
totalVotesReceived: Number(
|
||||
BigInt(delegate.dpos.delegate.totalVotesReceived) / 100000000n
|
||||
),
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
get delegatesMeta() {
|
||||
return this._delegates.meta;
|
||||
}
|
||||
|
||||
get notifications() {
|
||||
return this._notifications;
|
||||
}
|
||||
|
||||
addNotification(id, type, message) {
|
||||
this._notifications.push({
|
||||
id,
|
||||
type,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
removeNotification(id) {
|
||||
this._notifications = this._notifications.filter((e) => e.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
boxShadow: {
|
||||
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.02)',
|
||||
DEFAULT:
|
||||
'0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.02)',
|
||||
md: '0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.02)',
|
||||
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.01)',
|
||||
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.01)',
|
||||
@@ -26,7 +24,10 @@ module.exports = {
|
||||
sm: ['0.875rem', { lineHeight: '1.5715' }],
|
||||
base: ['1rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
validateSize: ['1.125rem', { lineHeight: '1.875', letterSpacing: '-0.014em' }],
|
||||
validateSize: [
|
||||
'1.125rem',
|
||||
{ lineHeight: '1.875', letterSpacing: '-0.014em' },
|
||||
],
|
||||
xl: ['1.25rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
'3xl': ['1.88rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
@@ -63,7 +64,12 @@ module.exports = {
|
||||
// add custom variant for expanding sidebar
|
||||
plugin(({ addVariant, e }) => {
|
||||
addVariant('sidebar-expanded', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ className }) => `.sidebar-expanded .${e(`sidebar-expanded${separator}${className}`)}`);
|
||||
modifySelectors(
|
||||
({ className }) =>
|
||||
`.sidebar-expanded .${e(
|
||||
`sidebar-expanded${separator}${className}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
}),
|
||||
],
|
||||
|
||||
200
src/utils/Schemas.js
Normal file
@@ -0,0 +1,200 @@
|
||||
export const IdentityModuleSchema = {
|
||||
$id: 'idntty/identity/module',
|
||||
title: 'Identity module account schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
features: {
|
||||
fieldNumber: 1,
|
||||
type: 'array',
|
||||
maxItems: 256,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label', 'value'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string' },
|
||||
value: { fieldNumber: 2, dataType: 'bytes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
verifications: {
|
||||
fieldNumber: 2,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label', 'account', 'tx'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string' },
|
||||
account: { fieldNumber: 2, dataType: 'bytes' },
|
||||
tx: { fieldNumber: 3, dataType: 'bytes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
default: { features: [], verifications: [] },
|
||||
};
|
||||
|
||||
export const setFeatureAssetSchema = {
|
||||
$id: 'idntty/identity/setfeature',
|
||||
title: 'Asset schema to set or update account features for identity module',
|
||||
type: 'object',
|
||||
required: ['features'],
|
||||
properties: {
|
||||
features: {
|
||||
fieldNumber: 1,
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label', 'value'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string', maxLength: 16 },
|
||||
value: { fieldNumber: 2, dataType: 'bytes', maxLength: 32 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const removeFeatureAssetSchema = {
|
||||
$id: 'idntty/identity/removefeature',
|
||||
title: 'Asset schema to remove account features for identity module',
|
||||
type: 'object',
|
||||
required: ['features'],
|
||||
properties: {
|
||||
features: {
|
||||
fieldNumber: 1,
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string', maxLength: 16 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const validateFeatureAssetSchema = {
|
||||
$id: 'idntty/identity/validatefeature',
|
||||
title: 'Asset schema to validate account features for identity module',
|
||||
type: 'object',
|
||||
required: ['recipientAddress', 'features'],
|
||||
properties: {
|
||||
recipientAddress: {
|
||||
fieldNumber: 1,
|
||||
dataType: 'bytes',
|
||||
minLength: 20,
|
||||
maxLength: 20,
|
||||
},
|
||||
features: {
|
||||
fieldNumber: 2,
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label', 'value'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string', maxLength: 16 },
|
||||
value: { fieldNumber: 2, dataType: 'bytes', maxLength: 32 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const invalidateFeatureAssetSchema = {
|
||||
$id: 'idntty/identity/invalidatefeature',
|
||||
title: 'Asset schema to invalidate account features for identity module',
|
||||
type: 'object',
|
||||
required: ['recipientAddress', 'features'],
|
||||
properties: {
|
||||
recipientAddress: {
|
||||
fieldNumber: 1,
|
||||
dataType: 'bytes',
|
||||
minLength: 20,
|
||||
maxLength: 20,
|
||||
},
|
||||
features: {
|
||||
fieldNumber: 2,
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['label'],
|
||||
properties: {
|
||||
label: { fieldNumber: 1, dataType: 'string', maxLength: 16 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const voteDelegateAssetSchema = {
|
||||
$id: 'lisk/dpos/vote',
|
||||
type: 'object',
|
||||
required: ['votes'],
|
||||
properties: {
|
||||
votes: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 20,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['delegateAddress', 'amount'],
|
||||
properties: {
|
||||
delegateAddress: {
|
||||
dataType: 'bytes',
|
||||
fieldNumber: 1,
|
||||
minLength: 20,
|
||||
maxLength: 20,
|
||||
},
|
||||
amount: {
|
||||
dataType: 'sint64',
|
||||
fieldNumber: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
fieldNumber: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const unlockDelegateAssetSchema = {
|
||||
$id: 'lisk/dpos/unlock',
|
||||
type: 'object',
|
||||
required: ['unlockObjects'],
|
||||
properties: {
|
||||
unlockObjects: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
maxItems: 20,
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['delegateAddress', 'amount', 'unvoteHeight'],
|
||||
properties: {
|
||||
delegateAddress: {
|
||||
dataType: 'bytes',
|
||||
fieldNumber: 1,
|
||||
minLength: 20,
|
||||
maxLength: 20,
|
||||
},
|
||||
amount: {
|
||||
dataType: 'uint64',
|
||||
fieldNumber: 2,
|
||||
},
|
||||
unvoteHeight: {
|
||||
dataType: 'uint32',
|
||||
fieldNumber: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
fieldNumber: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -3,13 +3,13 @@ import { CSSTransition as ReactCSSTransition } from 'react-transition-group';
|
||||
|
||||
const TransitionContext = React.createContext({
|
||||
parent: {},
|
||||
})
|
||||
});
|
||||
|
||||
function useIsInitialRender() {
|
||||
const isInitialRender = useRef(true);
|
||||
useEffect(() => {
|
||||
isInitialRender.current = false;
|
||||
}, [])
|
||||
}, []);
|
||||
return isInitialRender.current;
|
||||
}
|
||||
|
||||
@@ -53,34 +53,40 @@ function CSSTransition({
|
||||
unmountOnExit={removeFromDom}
|
||||
in={show}
|
||||
addEndListener={(done) => {
|
||||
nodeRef.current.addEventListener('transitionend', done, false)
|
||||
nodeRef.current.addEventListener('transitionend', done, false);
|
||||
}}
|
||||
onEnter={() => {
|
||||
if (!removeFromDom) nodeRef.current.style.display = null;
|
||||
addClasses(nodeRef.current, [...enterClasses, ...enterStartClasses])
|
||||
addClasses(nodeRef.current, [...enterClasses, ...enterStartClasses]);
|
||||
}}
|
||||
onEntering={() => {
|
||||
removeClasses(nodeRef.current, enterStartClasses)
|
||||
addClasses(nodeRef.current, enterEndClasses)
|
||||
removeClasses(nodeRef.current, enterStartClasses);
|
||||
addClasses(nodeRef.current, enterEndClasses);
|
||||
}}
|
||||
onEntered={() => {
|
||||
removeClasses(nodeRef.current, [...enterEndClasses, ...enterClasses])
|
||||
removeClasses(nodeRef.current, [...enterEndClasses, ...enterClasses]);
|
||||
}}
|
||||
onExit={() => {
|
||||
addClasses(nodeRef.current, [...leaveClasses, ...leaveStartClasses])
|
||||
addClasses(nodeRef.current, [...leaveClasses, ...leaveStartClasses]);
|
||||
}}
|
||||
onExiting={() => {
|
||||
removeClasses(nodeRef.current, leaveStartClasses)
|
||||
addClasses(nodeRef.current, leaveEndClasses)
|
||||
removeClasses(nodeRef.current, leaveStartClasses);
|
||||
addClasses(nodeRef.current, leaveEndClasses);
|
||||
}}
|
||||
onExited={() => {
|
||||
removeClasses(nodeRef.current, [...leaveEndClasses, ...leaveClasses])
|
||||
removeClasses(nodeRef.current, [...leaveEndClasses, ...leaveClasses]);
|
||||
if (!removeFromDom) nodeRef.current.style.display = 'none';
|
||||
}}
|
||||
>
|
||||
<Component ref={nodeRef} {...rest} style={{ display: !removeFromDom ? 'none': null }}>{children}</Component>
|
||||
<Component
|
||||
ref={nodeRef}
|
||||
{...rest}
|
||||
style={{ display: !removeFromDom ? 'none' : null }}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
</ReactCSSTransition>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function Transition({ show, appear, ...rest }) {
|
||||
@@ -95,7 +101,7 @@ function Transition({ show, appear, ...rest }) {
|
||||
show={parent.show}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -110,7 +116,7 @@ function Transition({ show, appear, ...rest }) {
|
||||
>
|
||||
<CSSTransition appear={appear} show={show} {...rest} />
|
||||
</TransitionContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Transition;
|
||||
export default Transition;
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import resolveConfig from 'tailwindcss/resolveConfig';
|
||||
import { cryptography, transactions } from '@liskhq/lisk-client';
|
||||
import {
|
||||
removeFeatureAssetSchema,
|
||||
setFeatureAssetSchema,
|
||||
unlockDelegateAssetSchema,
|
||||
validateFeatureAssetSchema,
|
||||
voteDelegateAssetSchema,
|
||||
} from './Schemas';
|
||||
import { statusMap } from '../shared/statusMap';
|
||||
|
||||
export const tailwindConfig = () => {
|
||||
// Tailwind config
|
||||
return resolveConfig('./src/tailwind.config.js')
|
||||
}
|
||||
return resolveConfig('./src/tailwind.config.js');
|
||||
};
|
||||
|
||||
export const hexToRGB = (h) => {
|
||||
let r = 0;
|
||||
@@ -21,14 +30,422 @@ export const hexToRGB = (h) => {
|
||||
return `${+r},${+g},${+b}`;
|
||||
};
|
||||
|
||||
export const formatValue = (value) => Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumSignificantDigits: 3,
|
||||
notation: 'compact',
|
||||
}).format(value);
|
||||
export const formatValue = (value) =>
|
||||
Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
maximumSignificantDigits: 3,
|
||||
notation: 'compact',
|
||||
}).format(value);
|
||||
|
||||
export const formatThousands = (value) => Intl.NumberFormat('en-US', {
|
||||
maximumSignificantDigits: 3,
|
||||
notation: 'compact',
|
||||
}).format(value);
|
||||
export const formatThousands = (value) =>
|
||||
Intl.NumberFormat('en-US', {
|
||||
maximumSignificantDigits: 3,
|
||||
notation: 'compact',
|
||||
}).format(value);
|
||||
|
||||
export const formatAddressBig = (address) =>
|
||||
`${address.slice(0, 4)} ${address.slice(4, 8)} ****** ${address.slice(
|
||||
address.length - 8,
|
||||
address.length - 4
|
||||
)} ${address.slice(address.length - 4)}`;
|
||||
|
||||
export const encryptAccountData = (data = [], passPhrase = '', pubKey = '') => {
|
||||
return data
|
||||
.filter((item) => item.status !== statusMap.blockchained)
|
||||
.map((item) => {
|
||||
let value = cryptography.encryptMessageWithPassphrase(
|
||||
item.seed + ':' + item.value,
|
||||
passPhrase,
|
||||
pubKey
|
||||
);
|
||||
return {
|
||||
label: item.key,
|
||||
value: value.encryptedMessage,
|
||||
value_nonce: value.nonce,
|
||||
seed: item.seed,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const encryptSharedData = (data = [], passPhrase = '', pubKey = '') => {
|
||||
return data.map((item) => {
|
||||
let value = cryptography.encryptMessageWithPassphrase(
|
||||
item.seed + ':' + item.value,
|
||||
passPhrase,
|
||||
Buffer.from(pubKey, 'hex')
|
||||
);
|
||||
return {
|
||||
label: item.key,
|
||||
value: value.encryptedMessage,
|
||||
value_nonce: value.nonce,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const hashAccountData = (data = [], oldData = [], hashMap = {}) => {
|
||||
const accountMap = data.reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
[item.label]: item.value,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const oldAccountMap = oldData.reduce(
|
||||
(acc, item) => ({
|
||||
...acc,
|
||||
[item.label]: item.value,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
const removed = oldData.reduce((acc, item) => {
|
||||
if (
|
||||
!accountMap[item.label] &&
|
||||
!data.find((elem) => elem.label === item.label)
|
||||
)
|
||||
return [...acc, { label: item.label }];
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const changed = data.reduce((acc, item) => {
|
||||
if (!oldAccountMap[item.label]) return [...acc, item];
|
||||
const oldValue = hashMap[item.key];
|
||||
const newValue = hashValue(item.value, item.seed).toString('hex');
|
||||
if (oldValue !== newValue && item.status === 'new') return [...acc, item];
|
||||
return acc;
|
||||
}, []);
|
||||
return [
|
||||
removed,
|
||||
changed.map((item) => {
|
||||
const value = hashValue(item.value, item.seed);
|
||||
return {
|
||||
label: item.key,
|
||||
value,
|
||||
};
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
export const jsonReplacer = (key, value) => {
|
||||
if (key === 'big') {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const hashValue = (value = '', seed = '') =>
|
||||
cryptography.hash(
|
||||
Buffer.concat([Buffer.from(seed, 'utf8'), cryptography.hash(value, 'utf8')])
|
||||
);
|
||||
|
||||
export const formatBytes = (bytes, decimals = 2) => {
|
||||
if (!+bytes) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const generateTransaction = (
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = []
|
||||
) => {
|
||||
return {
|
||||
update: (features) =>
|
||||
generateSetTransaction(
|
||||
features,
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
networkIdentifier,
|
||||
passPhrase,
|
||||
minFeePerByte,
|
||||
baseFees
|
||||
),
|
||||
remove: (features) =>
|
||||
generateRemoveTransaction(
|
||||
features,
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
networkIdentifier,
|
||||
passPhrase,
|
||||
minFeePerByte,
|
||||
baseFees
|
||||
),
|
||||
validate: (features, recipientAddress) =>
|
||||
generateValidateTransaction(
|
||||
features,
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
networkIdentifier,
|
||||
passPhrase,
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
recipientAddress
|
||||
),
|
||||
vote: (delegateAddress, amount) =>
|
||||
generateVoteTransaction(
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
networkIdentifier,
|
||||
passPhrase,
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
delegateAddress,
|
||||
amount
|
||||
),
|
||||
unlock: (delegateAddress, unlockObjects) =>
|
||||
generateUnlockTransaction(
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
networkIdentifier,
|
||||
passPhrase,
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
delegateAddress,
|
||||
unlockObjects
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const generateSetTransaction = (
|
||||
features,
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = []
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 1001,
|
||||
assetID: 1,
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
asset: {
|
||||
features,
|
||||
},
|
||||
};
|
||||
|
||||
tx.fee = transactions.computeMinFee(setFeatureAssetSchema, tx, {
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
numberOfSignatures: 1,
|
||||
});
|
||||
|
||||
if (features.length === 0) return;
|
||||
|
||||
const signedTx = transactions.signTransaction(
|
||||
setFeatureAssetSchema,
|
||||
tx,
|
||||
Buffer.from(networkIdentifier, 'hex'),
|
||||
passPhrase
|
||||
);
|
||||
|
||||
signedTx.senderPublicKey = signedTx.senderPublicKey.toString('hex');
|
||||
signedTx.signatures[0] = signedTx.signatures[0].toString('hex');
|
||||
signedTx.asset.features = signedTx.asset.features.map((feature) => ({
|
||||
...feature,
|
||||
value: feature.value.toString('hex'),
|
||||
}));
|
||||
|
||||
delete signedTx.id;
|
||||
|
||||
return signedTx;
|
||||
};
|
||||
|
||||
export const generateRemoveTransaction = (
|
||||
features,
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = []
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 1001,
|
||||
assetID: 2,
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
asset: {
|
||||
features,
|
||||
},
|
||||
};
|
||||
|
||||
tx.fee = transactions.computeMinFee(removeFeatureAssetSchema, tx, {
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
numberOfSignatures: 1,
|
||||
});
|
||||
const signedTx = transactions.signTransaction(
|
||||
removeFeatureAssetSchema,
|
||||
tx,
|
||||
Buffer.from(networkIdentifier, 'hex'),
|
||||
passPhrase
|
||||
);
|
||||
|
||||
signedTx.senderPublicKey = signedTx.senderPublicKey.toString('hex');
|
||||
signedTx.signatures[0] = signedTx.signatures[0].toString('hex');
|
||||
|
||||
delete signedTx.id;
|
||||
|
||||
return signedTx;
|
||||
};
|
||||
|
||||
const generateValidateTransaction = (
|
||||
features,
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = [],
|
||||
recipientAddress = ''
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 1001,
|
||||
assetID: 11,
|
||||
nonce,
|
||||
senderPublicKey: Buffer.from(senderPublicKey, 'hex'),
|
||||
asset: {
|
||||
features,
|
||||
recipientAddress: Buffer.from(recipientAddress, 'hex'),
|
||||
},
|
||||
};
|
||||
|
||||
tx.fee = transactions.computeMinFee(validateFeatureAssetSchema, tx, {
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
numberOfSignatures: 1,
|
||||
});
|
||||
|
||||
if (features.length === 0) return;
|
||||
|
||||
const signedTx = transactions.signTransaction(
|
||||
validateFeatureAssetSchema,
|
||||
tx,
|
||||
Buffer.from(networkIdentifier, 'hex'),
|
||||
passPhrase
|
||||
);
|
||||
|
||||
signedTx.senderPublicKey = signedTx.senderPublicKey.toString('hex');
|
||||
signedTx.asset.recipientAddress =
|
||||
signedTx.asset.recipientAddress.toString('hex');
|
||||
signedTx.signatures[0] = signedTx.signatures[0].toString('hex');
|
||||
signedTx.asset.features = signedTx.asset.features.map((feature) => ({
|
||||
...feature,
|
||||
value: feature.value.toString('hex'),
|
||||
}));
|
||||
|
||||
delete signedTx.id;
|
||||
|
||||
return signedTx;
|
||||
};
|
||||
|
||||
const generateVoteTransaction = (
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = [],
|
||||
delegateAddress = '',
|
||||
amount = 0n
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 5,
|
||||
assetID: 1,
|
||||
nonce,
|
||||
senderPublicKey: Buffer.from(senderPublicKey, 'hex'),
|
||||
asset: {
|
||||
votes: [
|
||||
{
|
||||
delegateAddress: Buffer.from(delegateAddress, 'hex'),
|
||||
amount: amount * 100000000n,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
tx.fee = transactions.computeMinFee(voteDelegateAssetSchema, tx, {
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
numberOfSignatures: 1,
|
||||
});
|
||||
|
||||
const signedTx = transactions.signTransaction(
|
||||
voteDelegateAssetSchema,
|
||||
tx,
|
||||
Buffer.from(networkIdentifier, 'hex'),
|
||||
passPhrase
|
||||
);
|
||||
|
||||
signedTx.senderPublicKey = signedTx.senderPublicKey.toString('hex');
|
||||
signedTx.signatures[0] = signedTx.signatures[0].toString('hex');
|
||||
signedTx.asset.votes = signedTx.asset.votes.map((vote) => ({
|
||||
...vote,
|
||||
delegateAddress: vote.delegateAddress.toString('hex'),
|
||||
}));
|
||||
|
||||
delete signedTx.id;
|
||||
|
||||
return signedTx;
|
||||
};
|
||||
|
||||
const generateUnlockTransaction = (
|
||||
nonce = BigInt(0),
|
||||
senderPublicKey = '',
|
||||
networkIdentifier = '',
|
||||
passPhrase = '',
|
||||
minFeePerByte = 0,
|
||||
baseFees = [],
|
||||
delegateAddress = '',
|
||||
unlockObjects = []
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 5,
|
||||
assetID: 2,
|
||||
nonce,
|
||||
senderPublicKey: Buffer.from(senderPublicKey, 'hex'),
|
||||
asset: {
|
||||
unlockObjects: unlockObjects.map((e) => ({
|
||||
delegateAddress: Buffer.from(delegateAddress, 'hex'),
|
||||
amount: e.amount * 100000000n,
|
||||
unvoteHeight: e.unvoteHeight,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
tx.fee = transactions.computeMinFee(unlockDelegateAssetSchema, tx, {
|
||||
minFeePerByte,
|
||||
baseFees,
|
||||
numberOfSignatures: 1,
|
||||
});
|
||||
|
||||
const signedTx = transactions.signTransaction(
|
||||
unlockDelegateAssetSchema,
|
||||
tx,
|
||||
Buffer.from(networkIdentifier, 'hex'),
|
||||
passPhrase
|
||||
);
|
||||
|
||||
signedTx.senderPublicKey = signedTx.senderPublicKey.toString('hex');
|
||||
signedTx.signatures[0] = signedTx.signatures[0].toString('hex');
|
||||
signedTx.asset.unlockObjects = signedTx.asset.unlockObjects.map((vote) => ({
|
||||
...vote,
|
||||
delegateAddress: vote.delegateAddress.toString('hex'),
|
||||
}));
|
||||
|
||||
delete signedTx.id;
|
||||
|
||||
return signedTx;
|
||||
};
|
||||
|
||||
252
src/utils/countryCodes.js
Normal file
@@ -0,0 +1,252 @@
|
||||
export const countryCodes = {
|
||||
BGD: 'BD',
|
||||
BEL: 'BE',
|
||||
BFA: 'BF',
|
||||
BGR: 'BG',
|
||||
BIH: 'BA',
|
||||
BRB: 'BB',
|
||||
WLF: 'WF',
|
||||
BLM: 'BL',
|
||||
BMU: 'BM',
|
||||
BRN: 'BN',
|
||||
BOL: 'BO',
|
||||
BHR: 'BH',
|
||||
BDI: 'BI',
|
||||
BEN: 'BJ',
|
||||
BTN: 'BT',
|
||||
JAM: 'JM',
|
||||
BVT: 'BV',
|
||||
BWA: 'BW',
|
||||
WSM: 'WS',
|
||||
BES: 'BQ',
|
||||
BRA: 'BR',
|
||||
BHS: 'BS',
|
||||
JEY: 'JE',
|
||||
BLR: 'BY',
|
||||
BLZ: 'BZ',
|
||||
RUS: 'RU',
|
||||
RWA: 'RW',
|
||||
SRB: 'RS',
|
||||
TLS: 'TL',
|
||||
REU: 'RE',
|
||||
TKM: 'TM',
|
||||
TJK: 'TJ',
|
||||
ROU: 'RO',
|
||||
TKL: 'TK',
|
||||
GNB: 'GW',
|
||||
GUM: 'GU',
|
||||
GTM: 'GT',
|
||||
SGS: 'GS',
|
||||
GRC: 'GR',
|
||||
GNQ: 'GQ',
|
||||
GLP: 'GP',
|
||||
JPN: 'JP',
|
||||
GUY: 'GY',
|
||||
GGY: 'GG',
|
||||
GUF: 'GF',
|
||||
GEO: 'GE',
|
||||
GRD: 'GD',
|
||||
GBR: 'GB',
|
||||
GAB: 'GA',
|
||||
SLV: 'SV',
|
||||
GIN: 'GN',
|
||||
GMB: 'GM',
|
||||
GRL: 'GL',
|
||||
GIB: 'GI',
|
||||
GHA: 'GH',
|
||||
OMN: 'OM',
|
||||
TUN: 'TN',
|
||||
JOR: 'JO',
|
||||
HRV: 'HR',
|
||||
HTI: 'HT',
|
||||
HUN: 'HU',
|
||||
HKG: 'HK',
|
||||
HND: 'HN',
|
||||
HMD: 'HM',
|
||||
VEN: 'VE',
|
||||
PRI: 'PR',
|
||||
PSE: 'PS',
|
||||
PLW: 'PW',
|
||||
PRT: 'PT',
|
||||
SJM: 'SJ',
|
||||
PRY: 'PY',
|
||||
IRQ: 'IQ',
|
||||
PAN: 'PA',
|
||||
PYF: 'PF',
|
||||
PNG: 'PG',
|
||||
PER: 'PE',
|
||||
PAK: 'PK',
|
||||
PHL: 'PH',
|
||||
PCN: 'PN',
|
||||
POL: 'PL',
|
||||
SPM: 'PM',
|
||||
ZMB: 'ZM',
|
||||
ESH: 'EH',
|
||||
EST: 'EE',
|
||||
EGY: 'EG',
|
||||
ZAF: 'ZA',
|
||||
ECU: 'EC',
|
||||
ITA: 'IT',
|
||||
VNM: 'VN',
|
||||
SLB: 'SB',
|
||||
ETH: 'ET',
|
||||
SOM: 'SO',
|
||||
ZWE: 'ZW',
|
||||
SAU: 'SA',
|
||||
ESP: 'ES',
|
||||
ERI: 'ER',
|
||||
MNE: 'ME',
|
||||
MDA: 'MD',
|
||||
MDG: 'MG',
|
||||
MAF: 'MF',
|
||||
MAR: 'MA',
|
||||
MCO: 'MC',
|
||||
UZB: 'UZ',
|
||||
MMR: 'MM',
|
||||
MLI: 'ML',
|
||||
MAC: 'MO',
|
||||
MNG: 'MN',
|
||||
MHL: 'MH',
|
||||
MKD: 'MK',
|
||||
MUS: 'MU',
|
||||
MLT: 'MT',
|
||||
MWI: 'MW',
|
||||
MDV: 'MV',
|
||||
MTQ: 'MQ',
|
||||
MNP: 'MP',
|
||||
MSR: 'MS',
|
||||
MRT: 'MR',
|
||||
IMN: 'IM',
|
||||
UGA: 'UG',
|
||||
TZA: 'TZ',
|
||||
MYS: 'MY',
|
||||
MEX: 'MX',
|
||||
ISR: 'IL',
|
||||
FRA: 'FR',
|
||||
IOT: 'IO',
|
||||
SHN: 'SH',
|
||||
FIN: 'FI',
|
||||
FJI: 'FJ',
|
||||
FLK: 'FK',
|
||||
FSM: 'FM',
|
||||
FRO: 'FO',
|
||||
NIC: 'NI',
|
||||
NLD: 'NL',
|
||||
NOR: 'NO',
|
||||
NAM: 'NA',
|
||||
VUT: 'VU',
|
||||
NCL: 'NC',
|
||||
NER: 'NE',
|
||||
NFK: 'NF',
|
||||
NGA: 'NG',
|
||||
NZL: 'NZ',
|
||||
NPL: 'NP',
|
||||
NRU: 'NR',
|
||||
NIU: 'NU',
|
||||
COK: 'CK',
|
||||
XKX: 'XK',
|
||||
CIV: 'CI',
|
||||
CHE: 'CH',
|
||||
COL: 'CO',
|
||||
CHN: 'CN',
|
||||
CMR: 'CM',
|
||||
CHL: 'CL',
|
||||
CCK: 'CC',
|
||||
CAN: 'CA',
|
||||
COG: 'CG',
|
||||
CAF: 'CF',
|
||||
COD: 'CD',
|
||||
CZE: 'CZ',
|
||||
CYP: 'CY',
|
||||
CXR: 'CX',
|
||||
CRI: 'CR',
|
||||
CUW: 'CW',
|
||||
CPV: 'CV',
|
||||
CUB: 'CU',
|
||||
SWZ: 'SZ',
|
||||
SYR: 'SY',
|
||||
SXM: 'SX',
|
||||
KGZ: 'KG',
|
||||
KEN: 'KE',
|
||||
SSD: 'SS',
|
||||
SUR: 'SR',
|
||||
KIR: 'KI',
|
||||
KHM: 'KH',
|
||||
KNA: 'KN',
|
||||
COM: 'KM',
|
||||
STP: 'ST',
|
||||
SVK: 'SK',
|
||||
KOR: 'KR',
|
||||
SVN: 'SI',
|
||||
PRK: 'KP',
|
||||
KWT: 'KW',
|
||||
SEN: 'SN',
|
||||
SMR: 'SM',
|
||||
SLE: 'SL',
|
||||
SYC: 'SC',
|
||||
KAZ: 'KZ',
|
||||
CYM: 'KY',
|
||||
SGP: 'SG',
|
||||
SWE: 'SE',
|
||||
SDN: 'SD',
|
||||
DOM: 'DO',
|
||||
DMA: 'DM',
|
||||
DJI: 'DJ',
|
||||
DNK: 'DK',
|
||||
VGB: 'VG',
|
||||
DEU: 'DE',
|
||||
YEM: 'YE',
|
||||
DZA: 'DZ',
|
||||
USA: 'US',
|
||||
URY: 'UY',
|
||||
MYT: 'YT',
|
||||
UMI: 'UM',
|
||||
LBN: 'LB',
|
||||
LCA: 'LC',
|
||||
LAO: 'LA',
|
||||
TUV: 'TV',
|
||||
TWN: 'TW',
|
||||
TTO: 'TT',
|
||||
TUR: 'TR',
|
||||
LKA: 'LK',
|
||||
LIE: 'LI',
|
||||
LVA: 'LV',
|
||||
TON: 'TO',
|
||||
LTU: 'LT',
|
||||
LUX: 'LU',
|
||||
LBR: 'LR',
|
||||
LSO: 'LS',
|
||||
THA: 'TH',
|
||||
ATF: 'TF',
|
||||
TGO: 'TG',
|
||||
TCD: 'TD',
|
||||
TCA: 'TC',
|
||||
LBY: 'LY',
|
||||
VAT: 'VA',
|
||||
VCT: 'VC',
|
||||
ARE: 'AE',
|
||||
AND: 'AD',
|
||||
ATG: 'AG',
|
||||
AFG: 'AF',
|
||||
AIA: 'AI',
|
||||
VIR: 'VI',
|
||||
ISL: 'IS',
|
||||
IRN: 'IR',
|
||||
ARM: 'AM',
|
||||
ALB: 'AL',
|
||||
AGO: 'AO',
|
||||
ATA: 'AQ',
|
||||
ASM: 'AS',
|
||||
ARG: 'AR',
|
||||
AUS: 'AU',
|
||||
AUT: 'AT',
|
||||
ABW: 'AW',
|
||||
IND: 'IN',
|
||||
ALA: 'AX',
|
||||
AZE: 'AZ',
|
||||
IRL: 'IE',
|
||||
IDN: 'ID',
|
||||
UKR: 'UA',
|
||||
QAT: 'QA',
|
||||
MOZ: 'MZ',
|
||||
};
|
||||
81
src/utils/decryptedData.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { labelMap } from '../shared/labelMap';
|
||||
import { statusMap } from '../shared/statusMap';
|
||||
import { cryptography } from '@liskhq/lisk-client';
|
||||
|
||||
export const decryptedData = (
|
||||
serverData,
|
||||
blockchainData,
|
||||
passPhrase,
|
||||
pubKey,
|
||||
processedFeatures
|
||||
) => {
|
||||
const allAccountData = serverData.concat(
|
||||
blockchainData.filter(
|
||||
(item) => !serverData.find((elem) => elem.label === item.label)
|
||||
)
|
||||
);
|
||||
return allAccountData.map((elem) => {
|
||||
const initialData = {
|
||||
...elem,
|
||||
key: elem.label,
|
||||
label: labelMap[elem.label] || elem.label,
|
||||
status: '',
|
||||
value: '',
|
||||
seed: '',
|
||||
};
|
||||
if (processedFeatures[elem.label])
|
||||
return {
|
||||
...initialData,
|
||||
status: statusMap.processed,
|
||||
value: elem.value,
|
||||
};
|
||||
if (!serverData.find((item) => item.label === elem.label)) {
|
||||
return {
|
||||
...initialData,
|
||||
status: statusMap.blockchained,
|
||||
value: elem.value,
|
||||
};
|
||||
}
|
||||
const [seed, value] = cryptography
|
||||
.decryptMessageWithPassphrase(
|
||||
elem.value,
|
||||
elem.value_nonce,
|
||||
passPhrase,
|
||||
pubKey
|
||||
)
|
||||
.split(':');
|
||||
const hashValue = cryptography
|
||||
.hash(
|
||||
Buffer.concat([
|
||||
Buffer.from(seed, 'utf8'),
|
||||
cryptography.hash(value, 'utf8'),
|
||||
])
|
||||
)
|
||||
.toString('hex');
|
||||
if (!blockchainData.find((item) => item.label === elem.label)) {
|
||||
return {
|
||||
...initialData,
|
||||
status: statusMap.stored,
|
||||
value,
|
||||
seed,
|
||||
};
|
||||
}
|
||||
if (
|
||||
blockchainData.find((item) => item.label === elem.label).value !==
|
||||
hashValue
|
||||
) {
|
||||
return {
|
||||
...initialData,
|
||||
status: statusMap.incorrect,
|
||||
value,
|
||||
seed,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...initialData,
|
||||
status: statusMap.completed,
|
||||
value,
|
||||
seed,
|
||||
};
|
||||
});
|
||||
};
|
||||
50
src/utils/test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { cryptography, passphrase } from '@liskhq/lisk-client';
|
||||
import sodium from 'sodium-universal';
|
||||
|
||||
let passphrase1 = passphrase.Mnemonic.generateMnemonic();
|
||||
const keys = cryptography.getPrivateAndPublicKeyFromPassphrase(passphrase1);
|
||||
console.log('IDNTTY Public & Private keys(hex):');
|
||||
console.log(
|
||||
'publicKey:',
|
||||
keys.publicKey.length,
|
||||
keys.publicKey.toString('hex')
|
||||
);
|
||||
console.log(
|
||||
'privateKey:',
|
||||
keys.privateKey.length,
|
||||
keys.privateKey.toString('hex')
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log('SODIUM Public & Private keys(hex):');
|
||||
|
||||
let pk = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES);
|
||||
let sk = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES);
|
||||
|
||||
let x25519_sk = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES);
|
||||
let x25519_pk = Buffer.alloc(sodium.crypto_box_PUBLICKEYBYTES);
|
||||
|
||||
sodium.crypto_sign_seed_keypair(
|
||||
pk,
|
||||
sk,
|
||||
cryptography.hash(Buffer.from(passphrase1, 'UTF-8'))
|
||||
);
|
||||
|
||||
sodium.crypto_sign_ed25519_sk_to_curve25519(x25519_sk, sk);
|
||||
sodium.crypto_sign_ed25519_pk_to_curve25519(x25519_pk, pk);
|
||||
|
||||
console.log('Public & Private keys(hex):');
|
||||
console.log('publicKey:', pk.length, pk.toString('hex'));
|
||||
console.log('privateKey:', sk.length, sk.toString('hex'));
|
||||
console.log('');
|
||||
|
||||
console.log(
|
||||
'x25519_sk privateKey:',
|
||||
x25519_sk.length,
|
||||
x25519_sk.toString('Base64')
|
||||
);
|
||||
console.log(
|
||||
'x25519_pk publicKey:',
|
||||
x25519_pk.length,
|
||||
x25519_pk.toString('Base64')
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
const path = require("path");
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
@@ -13,10 +13,14 @@ const plugins = [
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
require('tailwindcss')
|
||||
require('tailwindcss'),
|
||||
new webpack.ContextReplacementPlugin(/sodium/, (data) => {
|
||||
delete data.dependencies[0].critical;
|
||||
return data;
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = (env, argv) => ( {
|
||||
module.exports = (env, argv) => ({
|
||||
entry: './src/main.jsx',
|
||||
mode: argv.mode || 'development',
|
||||
devtool: argv.mode === 'production' ? false : 'eval-source-map',
|
||||
@@ -40,13 +44,12 @@ module.exports = (env, argv) => ( {
|
||||
},
|
||||
proxy: {
|
||||
'/api/**': {
|
||||
target: 'http://3.125.47.101',
|
||||
target: 'https://tn-alpha.idntty.org:8080/',
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.(html)$/, use: ['html-loader'] },
|
||||
@@ -61,19 +64,19 @@ module.exports = (env, argv) => ( {
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader'
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader'
|
||||
loader: 'css-loader',
|
||||
},
|
||||
|
||||
{
|
||||
loader: 'postcss-loader'
|
||||
loader: 'postcss-loader',
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader'
|
||||
}
|
||||
]
|
||||
loader: 'sass-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|svg|jpg|gif)$/,
|
||||
@@ -81,21 +84,21 @@ module.exports = (env, argv) => ( {
|
||||
options: {
|
||||
outputPath: 'assets',
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
fallback: {
|
||||
"path": require.resolve("path-browserify"),
|
||||
"os": require.resolve("os-browserify/browser"),
|
||||
"querystring": require.resolve("querystring-es3"),
|
||||
"crypto": require.resolve("crypto-browserify"),
|
||||
"util": require.resolve("util/"),
|
||||
"stream": require.resolve("stream-browserify"),
|
||||
"url": require.resolve("url/"),
|
||||
"buffer": require.resolve("buffer/"),
|
||||
"fs": false
|
||||
}
|
||||
path: require.resolve('path-browserify'),
|
||||
os: require.resolve('os-browserify/browser'),
|
||||
querystring: require.resolve('querystring-es3'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
util: require.resolve('util/'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
url: require.resolve('url/'),
|
||||
buffer: require.resolve('buffer/'),
|
||||
fs: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||