init: eslint
This commit is contained in:
parent
1f193343a5
commit
d49839b3e4
95 changed files with 39039 additions and 37706 deletions
32
.babelrc
32
.babelrc
|
|
@ -1,18 +1,18 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["last 2 versions", "safari >= 7"]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/preset-react",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
]
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["last 2 versions", "safari >= 7"]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/preset-react",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
|
|||
5
.eslintignore
Normal file
5
.eslintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
submodules/
|
||||
**/*.js
|
||||
68
.eslintrc
68
.eslintrc
|
|
@ -1,9 +1,63 @@
|
|||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "import"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"jsx-quotes": [1, "prefer-double"],
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"jsonc/json-files": "off"
|
||||
},
|
||||
"plugins": ["prettier"]
|
||||
}
|
||||
"no-console": "off",
|
||||
"no-void": "off",
|
||||
"import/extensions": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true,
|
||||
"vars": "all"
|
||||
}
|
||||
],
|
||||
"import/no-unresolved": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
"no-use-before-define": ["error", { "functions": false, "classes": true, "variables": true }],
|
||||
"func-names": "off",
|
||||
"consistent-return": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"lines-between-class-members": "off",
|
||||
"camelcase": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-shadow": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"radix": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-promise-executor-return": "off",
|
||||
"import/no-duplicates": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-cycle": "off",
|
||||
"no-continue": "off",
|
||||
"no-useless-return": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-useless-escape": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"array-callback-return": "off",
|
||||
"no-loop-func": "warn",
|
||||
"max-classes-per-file": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
}
|
||||
5
.prettierignore
Normal file
5
.prettierignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
build
|
||||
dist
|
||||
public
|
||||
**/*.js
|
||||
18
.prettierrc
18
.prettierrc
|
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"useTabs": false,
|
||||
"printWidth": 160,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"jsxBracketSameLine": false,
|
||||
"semi": true
|
||||
}
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
|
|||
30
README.md
30
README.md
|
|
@ -1,32 +1,40 @@
|
|||
# Zano Companion
|
||||
|
||||
Zano Companion is a browser extension that enhances your experience with Zano-based dApps by providing seamless wallet integration, transaction signing, and more.
|
||||
|
||||
## Features
|
||||
* Connect your Zano wallet to web applications securely.
|
||||
* Sign transactions and messages directly from the extension.
|
||||
* Easily access your balance and transaction history.
|
||||
* Compatible with dApps in the Zano ecosystem.
|
||||
|
||||
|
||||
- Connect your Zano wallet to web applications securely.
|
||||
- Sign transactions and messages directly from the extension.
|
||||
- Easily access your balance and transaction history.
|
||||
- Compatible with dApps in the Zano ecosystem.
|
||||
|
||||
## Installation
|
||||
|
||||
### Chrome Web Store
|
||||
|
||||
Install Zano Companion from the [Chrome Web Store](https://chromewebstore.google.com/detail/zano-companion/akcgnllhhhkcpmlenfpicmcpgfpindlb)
|
||||
|
||||
### Manual Installation
|
||||
|
||||
If you prefer to install manually:
|
||||
|
||||
* Download the latest release from [GitHub Releases](https://github.com/hyle-team/zano-extension/releases).
|
||||
* Extract the archive.
|
||||
* Open Chrome and navigate to chrome://extensions/.
|
||||
* Enable Developer mode (top-right corner).
|
||||
* Click Load unpacked and select the extracted folder.
|
||||
|
||||
- Download the latest release from [GitHub Releases](https://github.com/hyle-team/zano-extension/releases).
|
||||
- Extract the archive.
|
||||
- Open Chrome and navigate to chrome://extensions/.
|
||||
- Enable Developer mode (top-right corner).
|
||||
- Click Load unpacked and select the extracted folder.
|
||||
|
||||
## Usage
|
||||
|
||||
Open the extension and connect your Zano wallet.
|
||||
Approve connection requests from supported dApps.
|
||||
Sign transactions securely.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Feel free to submit issues or pull requests.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
|
|
|||
65122
package-lock.json
generated
65122
package-lock.json
generated
File diff suppressed because it is too large
Load diff
181
package.json
181
package.json
|
|
@ -1,89 +1,96 @@
|
|||
{
|
||||
"name": "zano-extension",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"big.js": "^6.2.1",
|
||||
"buffer": "^6.0.3",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"decimal.js": "^10.4.3",
|
||||
"json-bigint": "^1.0.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chrome-extension-router": "^1.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-id-generator": "^3.0.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"sha256": "^0.2.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development --watch",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"precommit": "lint-staged",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,css,scss}": ["eslint --fix", "prettier --write"]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.21.0",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@types/big.js": "^6.2.2",
|
||||
"@types/chrome": "^0.0.251",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/sha256": "^0.2.2",
|
||||
"babel-loader": "^9.2.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^5.5.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.62.0",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^4.9.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
"name": "zano-extension",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"big.js": "^6.2.1",
|
||||
"buffer": "^6.0.3",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"decimal.js": "^10.4.3",
|
||||
"json-bigint": "^1.0.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-chrome-extension-router": "^1.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-id-generator": "^3.0.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"sha256": "^0.2.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development --watch",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"precommit": "lint-staged",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"format": "prettier --write .",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,css,scss}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.21.0",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@types/big.js": "^6.2.2",
|
||||
"@types/chrome": "^0.0.251",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/node": "^22.10.0",
|
||||
"@types/node-forge": "^1.3.11",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/sha256": "^0.2.2",
|
||||
"babel-loader": "^9.2.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.5.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.62.0",
|
||||
"sass-loader": "^13.3.3",
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^4.9.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Zano browser wallet extension"
|
||||
/>
|
||||
<meta name="description" content="Zano browser walslet extension" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Zano extension</title>
|
||||
|
|
|
|||
283
src/app/App.tsx
283
src/app/App.tsx
|
|
@ -1,13 +1,14 @@
|
|||
/*global chrome*/
|
||||
import React from "react";
|
||||
import { useContext, useEffect, useState, useCallback } from "react";
|
||||
import { Router, goTo } from "react-chrome-extension-router";
|
||||
import AppPlug from "./components/AppPlug/AppPlug";
|
||||
import Header from "./components/Header/Header";
|
||||
import TokensTabs from "./components/TokensTabs/TokensTabs";
|
||||
import AppLoader from "./components/UI/AppLoader/AppLoader";
|
||||
import Wallet from "./components/Wallet/Wallet";
|
||||
import ModalConfirmation from "./components/ModalConfirmation/ModalConfirmation";
|
||||
/* global chrome */
|
||||
import React from 'react';
|
||||
import { useContext, useEffect, useState, useCallback } from 'react';
|
||||
import { Router, goTo } from 'react-chrome-extension-router';
|
||||
import Big from 'big.js';
|
||||
import AppPlug from './components/AppPlug/AppPlug';
|
||||
import Header from './components/Header/Header';
|
||||
import TokensTabs from './components/TokensTabs/TokensTabs';
|
||||
import AppLoader from './components/UI/AppLoader/AppLoader';
|
||||
import Wallet from './components/Wallet/Wallet';
|
||||
import ModalConfirmation from './components/ModalConfirmation/ModalConfirmation';
|
||||
import {
|
||||
comparePasswords,
|
||||
fetchBackground,
|
||||
|
|
@ -15,7 +16,7 @@ import {
|
|||
passwordExists,
|
||||
setPassword,
|
||||
setSessionPassword,
|
||||
} from "./utils/utils";
|
||||
} from './utils/utils';
|
||||
import {
|
||||
updateWalletConnected,
|
||||
updateActiveWalletId,
|
||||
|
|
@ -27,21 +28,27 @@ import {
|
|||
updateTransactionStatus,
|
||||
setConnectData,
|
||||
setWhiteList,
|
||||
} from "./store/actions";
|
||||
import { Store } from "./store/store-reducer";
|
||||
import { getZanoPrice } from "./api/coingecko";
|
||||
import "./styles/App.scss";
|
||||
import PasswordPage from "./components/PasswordPage/PasswordPage";
|
||||
import MessageSignPage from "./components/MessageSignPage/MessageSignPage";
|
||||
import AliasCreatePage from "./components/AliasCreatePage/AliasCreatePaget";
|
||||
import ConnectPage from "./components/ConnectPage/ConnectPage";
|
||||
import ConnectKeyUtils from "./utils/ConnectKeyUtils";
|
||||
import { defaultPort } from "./config/config";
|
||||
import OuterConfirmation from "./components/OuterConfirmation/OuterConfirmation";
|
||||
import Formatters from "./utils/formatters";
|
||||
import Big from "big.js";
|
||||
import swapModalStyles from "./styles/SwapModal.module.scss";
|
||||
import { AcceptSwapReq, AssetWhitelistReq, dispatchType, RequestType, SwapRequest, transferType } from "../types";
|
||||
} from './store/actions';
|
||||
import { Store } from './store/store-reducer';
|
||||
import { getZanoPrice } from './api/coingecko';
|
||||
import './styles/App.scss';
|
||||
import PasswordPage from './components/PasswordPage/PasswordPage';
|
||||
import MessageSignPage from './components/MessageSignPage/MessageSignPage';
|
||||
import AliasCreatePage from './components/AliasCreatePage/AliasCreatePaget';
|
||||
import ConnectPage from './components/ConnectPage/ConnectPage';
|
||||
import ConnectKeyUtils from './utils/ConnectKeyUtils';
|
||||
import { defaultPort } from './config/config';
|
||||
import OuterConfirmation from './components/OuterConfirmation/OuterConfirmation';
|
||||
import Formatters from './utils/formatters';
|
||||
import swapModalStyles from './styles/SwapModal.module.scss';
|
||||
import {
|
||||
AcceptSwapReq,
|
||||
AssetWhitelistReq,
|
||||
dispatchType,
|
||||
RequestType,
|
||||
SwapRequest,
|
||||
transferType,
|
||||
} from '../types';
|
||||
|
||||
function App() {
|
||||
const { state, dispatch } = useContext(Store);
|
||||
|
|
@ -73,13 +80,12 @@ function App() {
|
|||
const executeTransfer = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetchBackground({
|
||||
method: "EXECUTE_BRIDGING_TRANSFER",
|
||||
method: 'EXECUTE_BRIDGING_TRANSFER',
|
||||
});
|
||||
if (response.data.error) {
|
||||
return { sent: false, status: response.data.error };
|
||||
} else {
|
||||
return { sent: true, status: response.data.result };
|
||||
}
|
||||
return { sent: true, status: response.data.result };
|
||||
} catch (error) {
|
||||
return { sent: false, status: error };
|
||||
}
|
||||
|
|
@ -88,8 +94,8 @@ function App() {
|
|||
const closeModal = () => {
|
||||
setConfirmationModalOpen(false);
|
||||
updateConfirmationModal(dispatch as dispatchType, null);
|
||||
chrome.storage?.local?.remove?.(["pendingTx"]);
|
||||
chrome.action.setBadgeText({ text: "" });
|
||||
chrome.storage?.local?.remove?.(['pendingTx']);
|
||||
chrome.action.setBadgeText({ text: '' });
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
|
|
@ -102,13 +108,13 @@ function App() {
|
|||
console.log(response.status);
|
||||
updateTransactionStatus(dispatch as dispatchType, {
|
||||
visible: true,
|
||||
type: "error",
|
||||
type: 'error',
|
||||
code: response.status.code || 0,
|
||||
message: response.status.message || "Insufficient balance",
|
||||
message: response.status.message || 'Insufficient balance',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error during transfer confirmation:", error);
|
||||
console.log('Error during transfer confirmation:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -121,7 +127,7 @@ function App() {
|
|||
if (!chrome?.runtime?.sendMessage) return;
|
||||
|
||||
const walletActive = await fetchBackground({
|
||||
method: "GET_WALLET_DATA",
|
||||
method: 'GET_WALLET_DATA',
|
||||
});
|
||||
updateWalletConnected(dispatch as dispatchType, !walletActive.error);
|
||||
updateLoading(dispatch as dispatchType, false);
|
||||
|
|
@ -130,12 +136,12 @@ function App() {
|
|||
const getWalletData = async () => {
|
||||
if (!chrome?.runtime?.sendMessage) return;
|
||||
|
||||
const walletsList = await fetchBackground({ method: "GET_WALLETS" });
|
||||
const walletsList = await fetchBackground({ method: 'GET_WALLETS' });
|
||||
if (!walletsList.data) return;
|
||||
updateWalletsList(dispatch as dispatchType, walletsList.data);
|
||||
|
||||
const walletData = await fetchBackground({
|
||||
method: "GET_WALLET_DATA",
|
||||
method: 'GET_WALLET_DATA',
|
||||
});
|
||||
if (!walletData.data) return;
|
||||
const { address, alias, balance, transactions, assets } = walletData.data;
|
||||
|
|
@ -151,14 +157,14 @@ function App() {
|
|||
transactions,
|
||||
});
|
||||
|
||||
console.log("wallet data updated");
|
||||
console.log('wallet data updated');
|
||||
updateLoading(dispatch as dispatchType, false);
|
||||
setFirstWalletLoaded(true);
|
||||
};
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
await checkConnection();
|
||||
console.log("connected", state.isConnected);
|
||||
console.log('connected', state.isConnected);
|
||||
if (state.isConnected && loggedIn) {
|
||||
await getWalletData();
|
||||
}
|
||||
|
|
@ -169,7 +175,7 @@ function App() {
|
|||
|
||||
useEffect(() => {
|
||||
async function updateWhiteList() {
|
||||
const whiteList = await fetchBackground({ method: "GET_WHITELIST" });
|
||||
const whiteList = await fetchBackground({ method: 'GET_WHITELIST' });
|
||||
if (whiteList.data) {
|
||||
setWhiteList(dispatch as dispatchType, whiteList.data);
|
||||
}
|
||||
|
|
@ -179,28 +185,32 @@ function App() {
|
|||
|
||||
useEffect(() => {
|
||||
getZanoPrice().then((priceData) => {
|
||||
console.log("price data", priceData);
|
||||
console.log('price data', priceData);
|
||||
updatePriceData(dispatch as dispatchType, priceData);
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (request: RequestType, sender: chrome.runtime.MessageSender, sendResponse: (response: { status: string }) => void) => {
|
||||
const listener = (
|
||||
request: RequestType,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
sendResponse: (response: { status: string }) => void,
|
||||
) => {
|
||||
if (
|
||||
!(
|
||||
"assetId" in request &&
|
||||
"amount" in request &&
|
||||
"destinationAddress" in request &&
|
||||
"destinationChainId" in request
|
||||
'assetId' in request &&
|
||||
'amount' in request &&
|
||||
'destinationAddress' in request &&
|
||||
'destinationChainId' in request
|
||||
)
|
||||
) {
|
||||
console.error("Invalid BRIDGING_TRANSFER request", request);
|
||||
console.error('Invalid BRIDGING_TRANSFER request', request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method === "BRIDGING_TRANSFER") {
|
||||
if (request.method === 'BRIDGING_TRANSFER') {
|
||||
updateConfirmationModal(dispatch as dispatchType, {
|
||||
method: "SEND_TRANSFER",
|
||||
method: 'SEND_TRANSFER',
|
||||
params: [
|
||||
request.assetId,
|
||||
request.amount,
|
||||
|
|
@ -210,27 +220,27 @@ function App() {
|
|||
});
|
||||
chrome.storage?.local?.set?.({ pendingTx: request });
|
||||
setConfirmationModalOpen(true);
|
||||
sendResponse({ status: "confirmation_pending" });
|
||||
sendResponse({ status: 'confirmation_pending' });
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (typeof chrome !== "undefined" && chrome.runtime?.onMessage) {
|
||||
if (typeof chrome !== 'undefined' && chrome.runtime?.onMessage) {
|
||||
chrome.runtime.onMessage.addListener(listener);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (typeof chrome !== "undefined" && chrome.runtime?.onMessage) {
|
||||
if (typeof chrome !== 'undefined' && chrome.runtime?.onMessage) {
|
||||
chrome.runtime.onMessage.removeListener(listener);
|
||||
}
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage?.local?.get?.(["pendingTx"], function (result) {
|
||||
chrome.storage?.local?.get?.(['pendingTx'], (result) => {
|
||||
if (result.pendingTx) {
|
||||
updateConfirmationModal(dispatch as dispatchType, {
|
||||
method: "SEND_TRANSFER",
|
||||
method: 'SEND_TRANSFER',
|
||||
params: [
|
||||
result.pendingTx.assetId,
|
||||
result.pendingTx.amount,
|
||||
|
|
@ -244,17 +254,17 @@ function App() {
|
|||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage?.local?.get?.(["key"], function (result) {
|
||||
chrome.storage?.local?.get?.(['key'], (result) => {
|
||||
let walletId = 0;
|
||||
if (!result.key) {
|
||||
chrome.storage?.local?.set?.({ key: walletId }, function () {
|
||||
console.log("Active wallet set to", walletId);
|
||||
chrome.storage?.local?.set?.({ key: walletId }, () => {
|
||||
console.log('Active wallet set to', walletId);
|
||||
});
|
||||
} else {
|
||||
walletId = result.key;
|
||||
}
|
||||
fetchBackground({
|
||||
method: "SET_ACTIVE_WALLET",
|
||||
method: 'SET_ACTIVE_WALLET',
|
||||
id: walletId,
|
||||
});
|
||||
updateActiveWalletId(dispatch as dispatchType, walletId);
|
||||
|
|
@ -269,7 +279,7 @@ function App() {
|
|||
async function modalLoad() {
|
||||
async function getTransferRequests() {
|
||||
const transferRes = await fetchBackground({
|
||||
method: "GET_TRANSFER_REQUEST",
|
||||
method: 'GET_TRANSFER_REQUEST',
|
||||
});
|
||||
const transferRequests = transferRes.data;
|
||||
|
||||
|
|
@ -277,44 +287,47 @@ function App() {
|
|||
const { transfer } = e;
|
||||
const transferParams = [
|
||||
{
|
||||
key: "From",
|
||||
key: 'From',
|
||||
value: transfer.sender
|
||||
? Formatters.walletAddress(transfer.sender)
|
||||
: "???",
|
||||
: '???',
|
||||
},
|
||||
{
|
||||
key: "Amount",
|
||||
value: transfer.amount || "???",
|
||||
key: 'Amount',
|
||||
value: transfer.amount || '???',
|
||||
},
|
||||
{
|
||||
key: "Asset",
|
||||
value: transfer?.asset?.ticker || "???",
|
||||
key: 'Asset',
|
||||
value: transfer?.asset?.ticker || '???',
|
||||
},
|
||||
];
|
||||
|
||||
if (!transfer.destinations || transfer.destinations.length === 0) {
|
||||
transferParams.push({
|
||||
key: "To",
|
||||
key: 'To',
|
||||
value: transfer.destination
|
||||
? Formatters.walletAddress(transfer.destination)
|
||||
: "???",
|
||||
: '???',
|
||||
});
|
||||
}
|
||||
|
||||
if (transfer.comment) {
|
||||
transferParams.push({ key: "Comment", value: transfer.comment });
|
||||
transferParams.push({ key: 'Comment', value: transfer.comment });
|
||||
}
|
||||
|
||||
const destinations = transfer.destinations?.map((d: { address: string, amount: number }, index: number) => ({
|
||||
address: Formatters.walletAddress(d.address),
|
||||
amount: d.amount,
|
||||
})) || [];
|
||||
const destinations =
|
||||
transfer.destinations?.map(
|
||||
(d: { address: string; amount: number }, _index: number) => ({
|
||||
address: Formatters.walletAddress(d.address),
|
||||
amount: d.amount,
|
||||
}),
|
||||
) || [];
|
||||
|
||||
return {
|
||||
id: e.id,
|
||||
fee: "fee" in transfer ? transfer.fee : "???",
|
||||
method: "FINALIZE_TRANSFER_REQUEST",
|
||||
name: "Transfer",
|
||||
fee: 'fee' in transfer ? transfer.fee : '???',
|
||||
method: 'FINALIZE_TRANSFER_REQUEST',
|
||||
name: 'Transfer',
|
||||
params: transferParams,
|
||||
destinations,
|
||||
};
|
||||
|
|
@ -325,9 +338,8 @@ function App() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
async function getSignRequests() {
|
||||
const response = await fetchBackground({ method: "GET_SIGN_REQUESTS" });
|
||||
const response = await fetchBackground({ method: 'GET_SIGN_REQUESTS' });
|
||||
const signRequests = response.data;
|
||||
|
||||
if (signRequests && signRequests.length > 0) {
|
||||
|
|
@ -337,13 +349,13 @@ function App() {
|
|||
|
||||
async function getAliasCreationRequests() {
|
||||
const response = await fetchBackground({
|
||||
method: "GET_ALIAS_CREATE_REQUESTS",
|
||||
method: 'GET_ALIAS_CREATE_REQUESTS',
|
||||
});
|
||||
console.log("alias creation requests", response);
|
||||
console.log('alias creation requests', response);
|
||||
const createRequests = response.data;
|
||||
|
||||
if (createRequests && createRequests.length > 0) {
|
||||
console.log("open alias create page");
|
||||
console.log('open alias create page');
|
||||
goTo(AliasCreatePage, { createRequests });
|
||||
}
|
||||
}
|
||||
|
|
@ -352,9 +364,7 @@ function App() {
|
|||
function getSwapAmountText(amount: number | Big, asset: { ticker: string }) {
|
||||
const result = (
|
||||
<>
|
||||
<span className={swapModalStyles.swapAmount}>
|
||||
{amount.toFixed()}
|
||||
</span>{" "}
|
||||
<span className={swapModalStyles.swapAmount}>{amount.toFixed()}</span>{' '}
|
||||
{asset.ticker}
|
||||
</>
|
||||
);
|
||||
|
|
@ -363,14 +373,14 @@ function App() {
|
|||
}
|
||||
|
||||
const ionicSwapRes = await fetchBackground({
|
||||
method: "GET_IONIC_SWAP_REQUESTS",
|
||||
method: 'GET_IONIC_SWAP_REQUESTS',
|
||||
});
|
||||
const swapRequests = ionicSwapRes.data;
|
||||
|
||||
const swapPageReqs = swapRequests.map((e: SwapRequest) => {
|
||||
const { swap } = e;
|
||||
|
||||
const swapParams: ({ address: string } | any) = {};
|
||||
const swapParams: { address: string } | any = {};
|
||||
|
||||
swapParams.address = swap.destinationAddress;
|
||||
|
||||
|
|
@ -379,7 +389,7 @@ function App() {
|
|||
|
||||
swapParams.receiving = getSwapAmountText(
|
||||
receivingAmount,
|
||||
receivingAsset as any
|
||||
receivingAsset as any,
|
||||
);
|
||||
|
||||
const sendingAsset = swap.currentAsset;
|
||||
|
|
@ -389,33 +399,33 @@ function App() {
|
|||
|
||||
return {
|
||||
id: e.id,
|
||||
method: "FINALIZE_IONIC_SWAP_REQUEST",
|
||||
name: "Ionic Swap",
|
||||
method: 'FINALIZE_IONIC_SWAP_REQUEST',
|
||||
name: 'Ionic Swap',
|
||||
params: [
|
||||
{
|
||||
key: "Address",
|
||||
key: 'Address',
|
||||
value: swapParams.address
|
||||
? Formatters.walletAddress(swapParams.address)
|
||||
: "???",
|
||||
: '???',
|
||||
},
|
||||
{
|
||||
key: "Sending",
|
||||
value: swapParams.sending || "???",
|
||||
key: 'Sending',
|
||||
value: swapParams.sending || '???',
|
||||
},
|
||||
{
|
||||
key: "Receiving",
|
||||
value: swapParams.receiving || "???",
|
||||
key: 'Receiving',
|
||||
value: swapParams.receiving || '???',
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const ionicSwapAcceptRes = await fetchBackground({
|
||||
method: "GET_ACCEPT_IONIC_SWAP_REQUESTS",
|
||||
method: 'GET_ACCEPT_IONIC_SWAP_REQUESTS',
|
||||
});
|
||||
const acceptSwapReqs = ionicSwapAcceptRes.data;
|
||||
|
||||
console.log("ACCEPT SWAP", acceptSwapReqs);
|
||||
console.log('ACCEPT SWAP', acceptSwapReqs);
|
||||
|
||||
const acceptPageReqs = await Promise.all(
|
||||
acceptSwapReqs.map(async (e: AcceptSwapReq) => {
|
||||
|
|
@ -423,7 +433,7 @@ function App() {
|
|||
|
||||
const swap = e?.swapProposal;
|
||||
|
||||
const swapParams: ({ receiving: string } | any) = {};
|
||||
const swapParams: { receiving: string } | any = {};
|
||||
|
||||
function toBigWithDecimal(amount: Big, decimalPoint: number) {
|
||||
if (amount) {
|
||||
|
|
@ -435,50 +445,50 @@ function App() {
|
|||
const receivingAsset = e?.receivingAsset;
|
||||
const receivingAmount = toBigWithDecimal(
|
||||
swap.to_finalizer[0]?.amount,
|
||||
receivingAsset.decimal_point
|
||||
receivingAsset.decimal_point,
|
||||
);
|
||||
|
||||
if (receivingAmount !== undefined) {
|
||||
swapParams.receiving = getSwapAmountText(
|
||||
receivingAmount,
|
||||
receivingAsset as any
|
||||
receivingAsset as any,
|
||||
);
|
||||
}
|
||||
|
||||
const sendingAsset = e?.sendingAsset;
|
||||
const sendingAmount = toBigWithDecimal(
|
||||
swap.to_initiator[0]?.amount,
|
||||
sendingAsset.decimal_point
|
||||
sendingAsset.decimal_point,
|
||||
);
|
||||
|
||||
if (sendingAmount !== undefined) {
|
||||
swapParams.sending = getSwapAmountText(
|
||||
sendingAmount,
|
||||
sendingAsset as any
|
||||
sendingAsset as any,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: e.id,
|
||||
method: "FINALIZE_ACCEPT_IONIC_SWAP_REQUEST",
|
||||
name: "Accept Ionic Swap",
|
||||
method: 'FINALIZE_ACCEPT_IONIC_SWAP_REQUEST',
|
||||
name: 'Accept Ionic Swap',
|
||||
params: [
|
||||
{
|
||||
key: "Hex Proposal",
|
||||
key: 'Hex Proposal',
|
||||
value: Formatters.walletAddress(hex_raw_proposal),
|
||||
},
|
||||
{
|
||||
key: "Sending",
|
||||
value: swapParams.sending || "???",
|
||||
key: 'Sending',
|
||||
value: swapParams.sending || '???',
|
||||
},
|
||||
{
|
||||
key: "Receiving",
|
||||
value: swapParams.receiving || "???",
|
||||
key: 'Receiving',
|
||||
value: swapParams.receiving || '???',
|
||||
},
|
||||
],
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const pageReqs = [...swapPageReqs, ...acceptPageReqs];
|
||||
|
|
@ -490,25 +500,26 @@ function App() {
|
|||
|
||||
async function getAssetWhitelistAddRequests() {
|
||||
try {
|
||||
const response = await fetchBackground({ method: "GET_ASSETS_WHITELIST_ADD_REQUESTS" });
|
||||
const assetWhitelistAddRequests = response.data?.map((request: AssetWhitelistReq) => {
|
||||
return {
|
||||
const response = await fetchBackground({
|
||||
method: 'GET_ASSETS_WHITELIST_ADD_REQUESTS',
|
||||
});
|
||||
const assetWhitelistAddRequests = response.data?.map(
|
||||
(request: AssetWhitelistReq) => ({
|
||||
id: request.id,
|
||||
method: "FINALIZE_ASSETS_WHITELIST_ADD_REQUESTS",
|
||||
name: "Confirm adding asset to whitelist",
|
||||
method: 'FINALIZE_ASSETS_WHITELIST_ADD_REQUESTS',
|
||||
name: 'Confirm adding asset to whitelist',
|
||||
params: [
|
||||
{
|
||||
key: "Asset id",
|
||||
key: 'Asset id',
|
||||
value: Formatters.walletAddress(request.asset_id),
|
||||
},
|
||||
{
|
||||
key: "Asset name",
|
||||
key: 'Asset name',
|
||||
value: request.asset_name,
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
if (assetWhitelistAddRequests && assetWhitelistAddRequests.length > 0) {
|
||||
goTo(OuterConfirmation, { reqs: assetWhitelistAddRequests });
|
||||
}
|
||||
|
|
@ -518,7 +529,7 @@ function App() {
|
|||
}
|
||||
|
||||
async function getBurnAssetRequests() {
|
||||
const response = await fetchBackground({ method: "GET_BURN_ASSET_REQUESTS" });
|
||||
const response = await fetchBackground({ method: 'GET_BURN_ASSET_REQUESTS' });
|
||||
const burnRequests = response.data;
|
||||
|
||||
const pageReqs = burnRequests.map((e: any) => {
|
||||
|
|
@ -526,11 +537,9 @@ function App() {
|
|||
|
||||
return {
|
||||
id: e.id,
|
||||
method: "FINALIZE_BURN_ASSET_REQUEST",
|
||||
name: "BURN_ASSET",
|
||||
params: [
|
||||
data
|
||||
],
|
||||
method: 'FINALIZE_BURN_ASSET_REQUEST',
|
||||
name: 'BURN_ASSET',
|
||||
params: [data],
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -544,25 +553,19 @@ function App() {
|
|||
await getIonicSwapRequests();
|
||||
await getSignRequests();
|
||||
await getTransferRequests();
|
||||
await getAssetWhitelistAddRequests()
|
||||
await getAssetWhitelistAddRequests();
|
||||
}
|
||||
|
||||
if (appConnected && !connectOpened && loggedIn && state.isConnected) {
|
||||
modalLoad();
|
||||
}
|
||||
}, [
|
||||
appConnected,
|
||||
connectOpened,
|
||||
loggedIn,
|
||||
state.isConnected,
|
||||
state.wallet?.assets,
|
||||
]);
|
||||
}, [appConnected, connectOpened, loggedIn, state.isConnected, state.wallet?.assets]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("connectCredentials", state.connectCredentials);
|
||||
console.log('connectCredentials', state.connectCredentials);
|
||||
if (state.connectCredentials.token) {
|
||||
fetchBackground({
|
||||
method: "SET_API_CREDENTIALS",
|
||||
method: 'SET_API_CREDENTIALS',
|
||||
credentials: {
|
||||
token: state.connectCredentials.token,
|
||||
port: state?.connectCredentials?.port || defaultPort,
|
||||
|
|
@ -588,7 +591,7 @@ function App() {
|
|||
setLoggedIn(true);
|
||||
setSessionPassword(password);
|
||||
const connectData = ConnectKeyUtils.getConnectData(password);
|
||||
console.log("connectData", connectData);
|
||||
console.log('connectData', connectData);
|
||||
if (connectData?.token) {
|
||||
setConnectData(dispatch as dispatchType, {
|
||||
token: connectData.token,
|
||||
|
|
@ -645,7 +648,7 @@ function App() {
|
|||
ConnectKeyUtils.setConnectData(
|
||||
connectKey,
|
||||
String(walletPort),
|
||||
password
|
||||
password,
|
||||
);
|
||||
setLoggedIn(true);
|
||||
await setSessionPassword(password);
|
||||
|
|
@ -657,4 +660,4 @@ function App() {
|
|||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
export const getZanoPrice = async () => {
|
||||
const coinId = "zano";
|
||||
const vsCurrency = "usd";
|
||||
const coinId = 'zano';
|
||||
const vsCurrency = 'usd';
|
||||
|
||||
// Fetch current price
|
||||
const priceResponse = await fetch(
|
||||
`https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=${vsCurrency}`
|
||||
);
|
||||
const priceData = await priceResponse.json();
|
||||
const currentPrice = priceData[coinId][vsCurrency];
|
||||
// Fetch current price
|
||||
const priceResponse = await fetch(
|
||||
`https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=${vsCurrency}`,
|
||||
);
|
||||
const priceData = await priceResponse.json();
|
||||
const currentPrice = priceData[coinId][vsCurrency];
|
||||
|
||||
// Fetch 24-hour price change
|
||||
const coinResponse = await fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${coinId}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`
|
||||
);
|
||||
const coinData = await coinResponse.json();
|
||||
const priceChange24h = coinData.market_data.price_change_percentage_24h;
|
||||
// Fetch 24-hour price change
|
||||
const coinResponse = await fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${coinId}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`,
|
||||
);
|
||||
const coinData = await coinResponse.json();
|
||||
const priceChange24h = coinData.market_data.price_change_percentage_24h;
|
||||
|
||||
console.log(`Current price of Zano: $${currentPrice}`);
|
||||
console.log(`24-hour price change: ${priceChange24h.toFixed(2)}%`);
|
||||
console.log(`Current price of Zano: $${currentPrice}`);
|
||||
console.log(`24-hour price change: ${priceChange24h.toFixed(2)}%`);
|
||||
|
||||
return { price: currentPrice, change: priceChange24h.toFixed(2) };
|
||||
return { price: currentPrice, change: priceChange24h.toFixed(2) };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
.signContainer {
|
||||
padding-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
margin-top: 20px;
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
.subtext {
|
||||
margin-top: 20px;
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.messageBlock {
|
||||
width: 100%;
|
||||
border: 1px solid #ffffff62;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
.messageBlock {
|
||||
width: 100%;
|
||||
border: 1px solid #ffffff62;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.messageTitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
.messageTitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsContainer {
|
||||
margin-top: 80px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
.buttonsContainer {
|
||||
margin-top: 80px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,74 @@
|
|||
import React from 'react';
|
||||
import { getCurrent, goBack } from "react-chrome-extension-router";
|
||||
import Button, { ButtonThemes } from "../UI/Button/Button";
|
||||
import styles from "./AliasCreatePage.module.scss";
|
||||
import { useEffect, useState } from "react";
|
||||
import { fetchBackground } from "../../utils/utils";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getCurrent, goBack } from 'react-chrome-extension-router';
|
||||
import Button, { ButtonThemes } from '../UI/Button/Button';
|
||||
import styles from './AliasCreatePage.module.scss';
|
||||
import { fetchBackground } from '../../utils/utils';
|
||||
|
||||
export default function AliasCreatePage() {
|
||||
const { props } = getCurrent();
|
||||
|
||||
const { props } = getCurrent();
|
||||
const { createRequests } = props;
|
||||
|
||||
const createRequests = props.createRequests;
|
||||
const [reqIndex, setReqIndex] = useState(0);
|
||||
const [accepting, setAccepting] = useState(false);
|
||||
const [denying, setDenying] = useState(false);
|
||||
|
||||
const [reqIndex, setReqIndex] = useState(0);
|
||||
const [accepting, setAccepting] = useState(false);
|
||||
const [denying, setDenying] = useState(false);
|
||||
const signRequest = createRequests[reqIndex];
|
||||
|
||||
const signRequest = createRequests[reqIndex];
|
||||
useEffect(() => {
|
||||
setReqIndex(0);
|
||||
}, [createRequests]);
|
||||
|
||||
useEffect(() => {
|
||||
setReqIndex(0);
|
||||
}, [createRequests]);
|
||||
function nextRequest() {
|
||||
if (reqIndex < createRequests.length - 1) {
|
||||
setReqIndex(reqIndex + 1);
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
function nextRequest() {
|
||||
if (reqIndex < createRequests.length - 1) {
|
||||
setReqIndex(reqIndex + 1);
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
async function acceptClick() {
|
||||
setAccepting(true);
|
||||
await fetchBackground({
|
||||
method: 'FINALIZE_ALIAS_CREATE',
|
||||
id: signRequest.id,
|
||||
success: true,
|
||||
});
|
||||
setAccepting(false);
|
||||
nextRequest();
|
||||
}
|
||||
|
||||
async function acceptClick() {
|
||||
setAccepting(true);
|
||||
await fetchBackground({ method: "FINALIZE_ALIAS_CREATE", id: signRequest.id, success: true });
|
||||
setAccepting(false);
|
||||
nextRequest();
|
||||
}
|
||||
async function denyClick() {
|
||||
setDenying(true);
|
||||
await fetchBackground({
|
||||
method: 'FINALIZE_ALIAS_CREATE',
|
||||
id: signRequest.id,
|
||||
success: false,
|
||||
});
|
||||
setDenying(false);
|
||||
nextRequest();
|
||||
}
|
||||
|
||||
async function denyClick() {
|
||||
setDenying(true);
|
||||
await fetchBackground({ method: "FINALIZE_ALIAS_CREATE", id: signRequest.id, success: false });
|
||||
setDenying(false);
|
||||
nextRequest();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.signContainer}>
|
||||
<h3 className={styles.title}>Create alias request</h3>
|
||||
<p className={styles.text}>New alias will be created for your wallet</p>
|
||||
<div className={styles.messageBlock}>
|
||||
<p className={styles.messageTitle}>New alias:</p>
|
||||
<p>@{signRequest.alias}</p>
|
||||
</div>
|
||||
<br /><br /><br /><br />
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button disabled={denying} theme={ButtonThemes.Outline} onClick={denyClick}>Deny</Button>
|
||||
<Button disabled={accepting} onClick={acceptClick}>Accept</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={styles.signContainer}>
|
||||
<h3 className={styles.title}>Create alias request</h3>
|
||||
<p className={styles.text}>New alias will be created for your wallet</p>
|
||||
<div className={styles.messageBlock}>
|
||||
<p className={styles.messageTitle}>New alias:</p>
|
||||
<p>@{signRequest.alias}</p>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button disabled={denying} theme={ButtonThemes.Outline} onClick={denyClick}>
|
||||
Deny
|
||||
</Button>
|
||||
<Button disabled={accepting} onClick={acceptClick}>
|
||||
Accept
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,66 @@
|
|||
@use "sass:math";
|
||||
@import "src/app/styles/variables";
|
||||
@use 'sass:math';
|
||||
@import 'src/app/styles/variables';
|
||||
|
||||
.plug {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.plugBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugLogo {
|
||||
max-width: 175px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
margin: 0 auto;
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
max-width: 175px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
margin: 0 auto;
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.plugContent {
|
||||
max-width: 247px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
transform: translate(0, -30px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 247px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
transform: translate(0, -30px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
strong {
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
.plugText {
|
||||
font-size: 18px;
|
||||
line-height: math.div(24, 18);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
strong {
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
.plugText {
|
||||
font-size: 18px;
|
||||
line-height: math.div(24, 18);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.plugImage {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plugButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 6px;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
color: #1F8FEB;
|
||||
padding: 15px;
|
||||
margin-bottom: 17px;
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 6px;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
color: #1f8feb;
|
||||
padding: 15px;
|
||||
margin-bottom: 17px;
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +1,60 @@
|
|||
import React, { useContext } from "react";
|
||||
import displayImage from "../../assets/images/display.svg";
|
||||
import logo from "../../assets/svg/logo.svg";
|
||||
import questionIcon from "../../assets/svg/question.svg";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import s from "./AppPlug.module.scss";
|
||||
import React, { useContext } from 'react';
|
||||
import displayImage from '../../assets/images/display.svg';
|
||||
import logo from '../../assets/svg/logo.svg';
|
||||
import questionIcon from '../../assets/svg/question.svg';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import s from './AppPlug.module.scss';
|
||||
|
||||
// Define the type for props
|
||||
interface AppPlugProps {
|
||||
setConnectOpened: (isOpened: boolean) => void;
|
||||
setConnectOpened: (isOpened: boolean) => void;
|
||||
}
|
||||
|
||||
const AppPlug: React.FC<AppPlugProps> = (props) => {
|
||||
const { state } = useContext(Store);
|
||||
const { state } = useContext(Store);
|
||||
|
||||
const { setConnectOpened } = props;
|
||||
const { setConnectOpened } = props;
|
||||
|
||||
// Type of btnClasses is inferred as string
|
||||
const btnClasses = state.isLoading
|
||||
? [s.plugButton, s.hidden].join(" ")
|
||||
: s.plugButton;
|
||||
// Type of btnClasses is inferred as string
|
||||
const btnClasses = state.isLoading ? [s.plugButton, s.hidden].join(' ') : s.plugButton;
|
||||
|
||||
const openDocs = () => {
|
||||
window.open("https://docs.zano.org/docs/use/companion", "_blank");
|
||||
};
|
||||
const openDocs = () => {
|
||||
window.open('https://docs.zano.org/docs/use/companion', '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!state.isConnected && (
|
||||
<div className={s.plug}>
|
||||
<div className={`${s.plugBody} container`}>
|
||||
<div className={s.plugLogo}>
|
||||
<img src={logo} alt="zano logo" />
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{!state.isConnected && (
|
||||
<div className={s.plug}>
|
||||
<div className={`${s.plugBody} container`}>
|
||||
<div className={s.plugLogo}>
|
||||
<img src={logo} alt="zano logo" />
|
||||
</div>
|
||||
|
||||
<div className={s.plugContent}>
|
||||
<div className={s.plugImage}>
|
||||
<img src={displayImage} alt="display" />
|
||||
</div>
|
||||
<div className={s.plugContent}>
|
||||
<div className={s.plugImage}>
|
||||
<img src={displayImage} alt="display" />
|
||||
</div>
|
||||
|
||||
<strong>Wallet offline</strong>
|
||||
<strong>Wallet offline</strong>
|
||||
|
||||
<div className={s.plugText}>
|
||||
Make sure you're running <br />a wallet with RPC enabled
|
||||
</div>
|
||||
<div className={s.plugText}>
|
||||
Make sure you're running <br />a wallet with RPC enabled
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={btnClasses}
|
||||
onClick={() => setConnectOpened(true)}
|
||||
>
|
||||
Connection Settings
|
||||
</button>
|
||||
</div>
|
||||
<button className={btnClasses} onClick={openDocs}>
|
||||
<img src={questionIcon} alt="question icon" />
|
||||
How to connect to the wallet?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
<button className={btnClasses} onClick={() => setConnectOpened(true)}>
|
||||
Connection Settings
|
||||
</button>
|
||||
</div>
|
||||
<button className={btnClasses} onClick={openDocs}>
|
||||
<img src={questionIcon} alt="question icon" />
|
||||
How to connect to the wallet?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppPlug;
|
||||
|
|
|
|||
|
|
@ -1,43 +1,43 @@
|
|||
.connect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.logoImage {
|
||||
width: 175px;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
margin-top: 20px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.logoImage {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
> button {
|
||||
margin-top: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
> p {
|
||||
margin-top: 20px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.connectCodeContent {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
> button {
|
||||
margin-top: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
> button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.connectCodeContent {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
> button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
color: #ff6767;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
> p {
|
||||
font-size: 16px;
|
||||
color: #ff6767;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,166 +1,158 @@
|
|||
import React from "react";
|
||||
import Button from "../UI/Button/Button";
|
||||
import s from "./ConnectPage.module.scss";
|
||||
import logo from "../../assets/svg/logo.svg";
|
||||
import { Dispatch, SetStateAction, useContext, useEffect, useState } from "react";
|
||||
import MyInput from "../UI/MyInput/MyInput";
|
||||
import { fetchBackground, getSessionPassword } from "../../utils/utils";
|
||||
import { setConnectData } from "../../store/actions";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import ConnectKeyUtils from "../../utils/ConnectKeyUtils";
|
||||
import { defaultPort } from "../../config/config";
|
||||
import React, { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import Button from '../UI/Button/Button';
|
||||
import s from './ConnectPage.module.scss';
|
||||
import logo from '../../assets/svg/logo.svg';
|
||||
import MyInput from '../UI/MyInput/MyInput';
|
||||
import { fetchBackground, getSessionPassword } from '../../utils/utils';
|
||||
import { setConnectData } from '../../store/actions';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import ConnectKeyUtils from '../../utils/ConnectKeyUtils';
|
||||
import { defaultPort } from '../../config/config';
|
||||
|
||||
interface ConnectPageProps {
|
||||
incorrectPassword: boolean;
|
||||
setIncorrectPassword: Dispatch<SetStateAction<boolean>>;
|
||||
onConfirm?: (password?: string, keyValue?: string, walletPort?: string) => void;
|
||||
passwordExists: boolean;
|
||||
setConnectOpened: Dispatch<SetStateAction<boolean>>;
|
||||
|
||||
incorrectPassword: boolean;
|
||||
setIncorrectPassword: Dispatch<SetStateAction<boolean>>;
|
||||
onConfirm?: (password?: string, keyValue?: string, walletPort?: string) => void;
|
||||
passwordExists: boolean;
|
||||
setConnectOpened: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export default function ConnectPage({
|
||||
incorrectPassword,
|
||||
setIncorrectPassword,
|
||||
onConfirm,
|
||||
passwordExists,
|
||||
setConnectOpened,
|
||||
incorrectPassword,
|
||||
setIncorrectPassword,
|
||||
onConfirm,
|
||||
passwordExists,
|
||||
setConnectOpened,
|
||||
}: ConnectPageProps) {
|
||||
const updateSettings = !!passwordExists;
|
||||
const updateSettings = !!passwordExists;
|
||||
|
||||
const { dispatch } = useContext(Store);
|
||||
const { dispatch } = useContext(Store);
|
||||
|
||||
const [keyValue, setKeyValue] = useState("");
|
||||
const [keyValue, setKeyValue] = useState('');
|
||||
|
||||
const [keyIncorrect, setKeyIncorrect] = useState(false);
|
||||
const [portIncorrect, setPortIncorrect] = useState(false);
|
||||
const [keyIncorrect, setKeyIncorrect] = useState(false);
|
||||
const [portIncorrect, setPortIncorrect] = useState(false);
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordRepeat, setPasswordRepeat] = useState("");
|
||||
const [walletPort, setWalletPort] = useState(`${defaultPort}`);
|
||||
const [password, setPassword] = useState('');
|
||||
const [passwordRepeat, setPasswordRepeat] = useState('');
|
||||
const [walletPort, setWalletPort] = useState(`${defaultPort}`);
|
||||
|
||||
const [invalidPassword, setInvalidPassword] = useState(false);
|
||||
const [invalidPassword, setInvalidPassword] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function getExistingPort() {
|
||||
if (!passwordExists) return;
|
||||
const password = await getSessionPassword();
|
||||
if (!password) return;
|
||||
const connectData = ConnectKeyUtils.getConnectData(password);
|
||||
if (connectData?.port) setWalletPort(connectData.port);
|
||||
}
|
||||
getExistingPort();
|
||||
}, [passwordExists]);
|
||||
useEffect(() => {
|
||||
async function getExistingPort() {
|
||||
if (!passwordExists) return;
|
||||
const password = await getSessionPassword();
|
||||
if (!password) return;
|
||||
const connectData = ConnectKeyUtils.getConnectData(password);
|
||||
if (connectData?.port) setWalletPort(connectData.port);
|
||||
}
|
||||
getExistingPort();
|
||||
}, [passwordExists]);
|
||||
|
||||
function onPasswordInput(event: React.ChangeEvent<HTMLInputElement>, repeat: boolean) {
|
||||
const { value } = event.currentTarget;
|
||||
setIncorrectPassword(false);
|
||||
setInvalidPassword(false);
|
||||
if (repeat) {
|
||||
setPasswordRepeat(value);
|
||||
} else {
|
||||
setPassword(value);
|
||||
}
|
||||
}
|
||||
function onPasswordInput(event: React.ChangeEvent<HTMLInputElement>, repeat: boolean) {
|
||||
const { value } = event.currentTarget;
|
||||
setIncorrectPassword(false);
|
||||
setInvalidPassword(false);
|
||||
if (repeat) {
|
||||
setPasswordRepeat(value);
|
||||
} else {
|
||||
setPassword(value);
|
||||
}
|
||||
}
|
||||
|
||||
async function continueClick() {
|
||||
if (!updateSettings) {
|
||||
const correctPassword = password === passwordRepeat && password;
|
||||
async function continueClick() {
|
||||
if (!updateSettings) {
|
||||
const correctPassword = password === passwordRepeat && password;
|
||||
|
||||
if (!correctPassword) return setInvalidPassword(true);
|
||||
}
|
||||
if (!correctPassword) return setInvalidPassword(true);
|
||||
}
|
||||
|
||||
if (!parseInt(walletPort, 10)) {
|
||||
console.log("PORT IS NOT A NUMBER");
|
||||
return setPortIncorrect(true);
|
||||
}
|
||||
if (!parseInt(walletPort, 10)) {
|
||||
console.log('PORT IS NOT A NUMBER');
|
||||
return setPortIncorrect(true);
|
||||
}
|
||||
|
||||
await fetchBackground({
|
||||
method: "SET_API_CREDENTIALS",
|
||||
credentials: { port: walletPort },
|
||||
});
|
||||
await fetchBackground({
|
||||
method: 'SET_API_CREDENTIALS',
|
||||
credentials: { port: walletPort },
|
||||
});
|
||||
|
||||
setConnectData(dispatch as () => void, {
|
||||
token: keyValue,
|
||||
port: walletPort,
|
||||
});
|
||||
setConnectData(dispatch as () => void, {
|
||||
token: keyValue,
|
||||
port: walletPort,
|
||||
});
|
||||
|
||||
if (onConfirm) {
|
||||
await onConfirm(
|
||||
!updateSettings ? password : undefined,
|
||||
keyValue,
|
||||
walletPort
|
||||
);
|
||||
if (updateSettings) {
|
||||
setConnectOpened(false);
|
||||
}
|
||||
} else {
|
||||
throw new Error("No onConfirm function provided");
|
||||
}
|
||||
}
|
||||
if (onConfirm) {
|
||||
await onConfirm(!updateSettings ? password : undefined, keyValue, walletPort);
|
||||
if (updateSettings) {
|
||||
setConnectOpened(false);
|
||||
}
|
||||
} else {
|
||||
throw new Error('No onConfirm function provided');
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setKeyValue(event.currentTarget.value);
|
||||
setKeyIncorrect(false);
|
||||
}
|
||||
function onKeyInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setKeyValue(event.currentTarget.value);
|
||||
setKeyIncorrect(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.connect}>
|
||||
<img className={s.logoImage} src={logo} alt="Zano" />
|
||||
<div className={s.connectCodeContent}>
|
||||
<div className={s.input}>
|
||||
<MyInput
|
||||
label="Wallet port"
|
||||
placeholder="Enter port here"
|
||||
inputData={{ value: walletPort }}
|
||||
noValidation={true}
|
||||
type={"number"}
|
||||
onChange={(event) => {
|
||||
setWalletPort(event.currentTarget.value);
|
||||
setPortIncorrect(false);
|
||||
}}
|
||||
/>
|
||||
{portIncorrect && <p>Wallet is not responding</p>}
|
||||
</div>
|
||||
return (
|
||||
<div className={s.connect}>
|
||||
<img className={s.logoImage} src={logo} alt="Zano" />
|
||||
<div className={s.connectCodeContent}>
|
||||
<div className={s.input}>
|
||||
<MyInput
|
||||
label="Wallet port"
|
||||
placeholder="Enter port here"
|
||||
inputData={{ value: walletPort }}
|
||||
noValidation={true}
|
||||
type={'number'}
|
||||
onChange={(event) => {
|
||||
setWalletPort(event.currentTarget.value);
|
||||
setPortIncorrect(false);
|
||||
}}
|
||||
/>
|
||||
{portIncorrect && <p>Wallet is not responding</p>}
|
||||
</div>
|
||||
|
||||
<MyInput
|
||||
label="Wallet secret"
|
||||
placeholder="Enter secret here"
|
||||
inputData={{ value: keyValue, isDirty: keyIncorrect }}
|
||||
onChange={onKeyInput}
|
||||
/>
|
||||
{!updateSettings && (
|
||||
<>
|
||||
<MyInput
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
inputData={{
|
||||
value: password,
|
||||
isDirty: !!(incorrectPassword || invalidPassword),
|
||||
}}
|
||||
onChange={(event) => onPasswordInput(event, false)}
|
||||
/>
|
||||
<MyInput
|
||||
type="password"
|
||||
placeholder="Repeat password"
|
||||
inputData={{
|
||||
value: passwordRepeat,
|
||||
isDirty: !!(incorrectPassword || invalidPassword),
|
||||
}}
|
||||
onChange={(event) => onPasswordInput(event, true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={continueClick}>
|
||||
{updateSettings ? "Save" : "Continue"}
|
||||
</Button>
|
||||
{updateSettings && (
|
||||
<Button theme="outline" onClick={() => setConnectOpened(false)}>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<MyInput
|
||||
label="Wallet secret"
|
||||
placeholder="Enter secret here"
|
||||
inputData={{ value: keyValue, isDirty: keyIncorrect }}
|
||||
onChange={onKeyInput}
|
||||
/>
|
||||
{!updateSettings && (
|
||||
<>
|
||||
<MyInput
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
inputData={{
|
||||
value: password,
|
||||
isDirty: !!(incorrectPassword || invalidPassword),
|
||||
}}
|
||||
onChange={(event) => onPasswordInput(event, false)}
|
||||
/>
|
||||
<MyInput
|
||||
type="password"
|
||||
placeholder="Repeat password"
|
||||
inputData={{
|
||||
value: passwordRepeat,
|
||||
isDirty: !!(incorrectPassword || invalidPassword),
|
||||
}}
|
||||
onChange={(event) => onPasswordInput(event, true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button onClick={continueClick}>{updateSettings ? 'Save' : 'Continue'}</Button>
|
||||
{updateSettings && (
|
||||
<Button theme="outline" onClick={() => setConnectOpened(false)}>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,105 @@
|
|||
@use "sass:math";
|
||||
@import "src/app/styles/variables";
|
||||
@use 'sass:math';
|
||||
@import 'src/app/styles/variables';
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 360px;
|
||||
height: 56px;
|
||||
background: #0f2055;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
column-gap: 30px;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 360px;
|
||||
height: 56px;
|
||||
background: #0f2055;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
column-gap: 30px;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
.headerStatus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
span {
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
span {
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: calc(100% + 1px);
|
||||
height: calc($appHeight - 56px);
|
||||
background-color: var(--overlay-color);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: calc(100% + 1px);
|
||||
height: calc($appHeight - 56px);
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
.dropdownButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(21, 18);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(21, 18);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.dropdownList {
|
||||
background-color: #0F2055;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 5px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 5px;
|
||||
}
|
||||
background-color: #0f2055;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 5px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownTitle {
|
||||
padding: 20px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 30px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 20px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 30px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(21, 18);
|
||||
span {
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(21, 18);
|
||||
span {
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownBalance {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: right;
|
||||
color: #1F8FEB;
|
||||
}
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: right;
|
||||
color: #1f8feb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,91 +1,88 @@
|
|||
import React from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import arrowIcon from "../../assets/svg/arrow-shevron.svg";
|
||||
import { useCensorDigits } from "../../hooks/useCensorDigits";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import { updateActiveWalletId, updateLoading } from "../../store/actions";
|
||||
import Formatters from "../../utils/formatters";
|
||||
import s from "./Header.module.scss";
|
||||
import { fetchBackground } from "../../utils/utils";
|
||||
import React, { useContext, useState } from 'react';
|
||||
import arrowIcon from '../../assets/svg/arrow-shevron.svg';
|
||||
import { useCensorDigits } from '../../hooks/useCensorDigits';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import { updateActiveWalletId, updateLoading } from '../../store/actions';
|
||||
import Formatters from '../../utils/formatters';
|
||||
import s from './Header.module.scss';
|
||||
import { fetchBackground } from '../../utils/utils';
|
||||
|
||||
const Header = () => {
|
||||
const { dispatch, state } = useContext(Store);
|
||||
const { censorValue } = useCensorDigits();
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const { dispatch, state } = useContext(Store);
|
||||
const { censorValue } = useCensorDigits();
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
if (dropdownOpen) {
|
||||
setDropdownOpen(false);
|
||||
document.body.style.overflow = "auto";
|
||||
} else {
|
||||
setDropdownOpen(true);
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
};
|
||||
const toggleDropdown = () => {
|
||||
if (dropdownOpen) {
|
||||
setDropdownOpen(false);
|
||||
document.body.style.overflow = 'auto';
|
||||
} else {
|
||||
setDropdownOpen(true);
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
};
|
||||
|
||||
const switchWallet = (id: number | undefined) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.storage.local.set({ key: id }, function () {
|
||||
updateLoading(dispatch as () => void, true);
|
||||
updateActiveWalletId(dispatch as () => void, String(id));
|
||||
const switchWallet = (id: number | undefined) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.storage.local.set({ key: id }, () => {
|
||||
updateLoading(dispatch as () => void, true);
|
||||
updateActiveWalletId(dispatch as () => void, String(id));
|
||||
|
||||
fetchBackground({
|
||||
method: "SET_ACTIVE_WALLET",
|
||||
id: id,
|
||||
});
|
||||
fetchBackground({
|
||||
method: 'SET_ACTIVE_WALLET',
|
||||
id,
|
||||
});
|
||||
|
||||
console.log("Active wallet set to", id);
|
||||
setTimeout(() => updateLoading(dispatch as () => void, false), 1000);
|
||||
});
|
||||
console.log('Active wallet set to', id);
|
||||
setTimeout(() => updateLoading(dispatch as () => void, false), 1000);
|
||||
});
|
||||
|
||||
toggleDropdown();
|
||||
};
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<header className={s.header}>
|
||||
<button onClick={toggleDropdown} className={s.dropdownButton}>
|
||||
<span>
|
||||
{state.wallet.alias
|
||||
? state.wallet.alias
|
||||
: Formatters.walletAddress(state.wallet.address)}
|
||||
</span>
|
||||
<img src={arrowIcon} alt="arrow icon" />
|
||||
</button>
|
||||
return (
|
||||
<header className={s.header}>
|
||||
<button onClick={toggleDropdown} className={s.dropdownButton}>
|
||||
<span>
|
||||
{state.wallet.alias
|
||||
? state.wallet.alias
|
||||
: Formatters.walletAddress(state.wallet.address)}
|
||||
</span>
|
||||
<img src={arrowIcon} alt="arrow icon" />
|
||||
</button>
|
||||
|
||||
<div className={s.headerStatus}>
|
||||
{state.isConnected ? "online" : "offline"}
|
||||
<span
|
||||
style={{ backgroundColor: state.isConnected ? "#16D1D6" : "#FF6767" }}
|
||||
></span>
|
||||
</div>
|
||||
<div className={s.headerStatus}>
|
||||
{state.isConnected ? 'online' : 'offline'}
|
||||
<span style={{ backgroundColor: state.isConnected ? '#16D1D6' : '#FF6767' }}></span>
|
||||
</div>
|
||||
|
||||
{dropdownOpen && (
|
||||
<div onClick={toggleDropdown} className={s.dropdown}>
|
||||
<div onClick={(e) => e.stopPropagation()} className={s.dropdownList}>
|
||||
{state.walletsList.map((wallet) => (
|
||||
<button
|
||||
key={wallet.address}
|
||||
className={s.dropdownTitle}
|
||||
onClick={() => switchWallet(wallet.wallet_id)}
|
||||
>
|
||||
<div>
|
||||
{wallet.alias
|
||||
? wallet.alias
|
||||
: Formatters.walletAddress(wallet.address)}
|
||||
{wallet.alias && (
|
||||
<span>{Formatters.walletAddress(wallet.address)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={s.dropdownBalance}>
|
||||
{censorValue(Number(wallet.balance).toFixed(2))} ZANO
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
{dropdownOpen && (
|
||||
<div onClick={toggleDropdown} className={s.dropdown}>
|
||||
<div onClick={(e) => e.stopPropagation()} className={s.dropdownList}>
|
||||
{state.walletsList.map((wallet) => (
|
||||
<button
|
||||
key={wallet.address}
|
||||
className={s.dropdownTitle}
|
||||
onClick={() => switchWallet(wallet.wallet_id)}
|
||||
>
|
||||
<div>
|
||||
{wallet.alias
|
||||
? wallet.alias
|
||||
: Formatters.walletAddress(wallet.address)}
|
||||
{wallet.alias && (
|
||||
<span>{Formatters.walletAddress(wallet.address)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={s.dropdownBalance}>
|
||||
{censorValue(Number(wallet.balance).toFixed(2))} ZANO
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
.signContainer {
|
||||
padding-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
margin-top: 20px;
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
.subtext {
|
||||
margin-top: 20px;
|
||||
opacity: 0.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.messageBlock {
|
||||
width: 100%;
|
||||
border: 1px solid #ffffff62;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
.messageBlock {
|
||||
width: 100%;
|
||||
border: 1px solid #ffffff62;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.messageTitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
.messageTitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsContainer {
|
||||
margin-top: 80px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
.buttonsContainer {
|
||||
margin-top: 80px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,73 @@
|
|||
import React from "react";
|
||||
import { getCurrent, goBack } from "react-chrome-extension-router";
|
||||
import Button, { ButtonThemes } from "../UI/Button/Button";
|
||||
import styles from "./MessageSignPage.module.scss";
|
||||
import { useEffect, useState } from "react";
|
||||
import { fetchBackground } from "../../utils/utils";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getCurrent, goBack } from 'react-chrome-extension-router';
|
||||
import Button, { ButtonThemes } from '../UI/Button/Button';
|
||||
import styles from './MessageSignPage.module.scss';
|
||||
import { fetchBackground } from '../../utils/utils';
|
||||
|
||||
export default function MessageSignPage() {
|
||||
const { props } = getCurrent();
|
||||
|
||||
const { props } = getCurrent();
|
||||
const { signRequests } = props;
|
||||
|
||||
const signRequests = props.signRequests;
|
||||
const [reqIndex, setReqIndex] = useState(0);
|
||||
const [accepting, setAccepting] = useState(false);
|
||||
const [denying, setDenying] = useState(false);
|
||||
|
||||
const [reqIndex, setReqIndex] = useState(0);
|
||||
const [accepting, setAccepting] = useState(false);
|
||||
const [denying, setDenying] = useState(false);
|
||||
const signRequest = signRequests[reqIndex];
|
||||
|
||||
const signRequest = signRequests[reqIndex];
|
||||
useEffect(() => {
|
||||
setReqIndex(0);
|
||||
}, [signRequests]);
|
||||
|
||||
useEffect(() => {
|
||||
setReqIndex(0);
|
||||
}, [signRequests]);
|
||||
async function nextRequest() {
|
||||
if (reqIndex < signRequests.length - 1) {
|
||||
setReqIndex(reqIndex + 1);
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
async function nextRequest() {
|
||||
if (reqIndex < signRequests.length - 1) {
|
||||
setReqIndex(reqIndex + 1);
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
async function acceptClick() {
|
||||
setAccepting(true);
|
||||
await fetchBackground({
|
||||
method: 'FINALIZE_MESSAGE_SIGN',
|
||||
id: signRequest?.id,
|
||||
success: true,
|
||||
});
|
||||
setAccepting(false);
|
||||
await nextRequest();
|
||||
}
|
||||
|
||||
async function acceptClick() {
|
||||
setAccepting(true);
|
||||
await fetchBackground({ method: "FINALIZE_MESSAGE_SIGN", id: signRequest?.id, success: true });
|
||||
setAccepting(false);
|
||||
await nextRequest();
|
||||
}
|
||||
|
||||
async function denyClick() {
|
||||
setDenying(true);
|
||||
await fetchBackground({ method: "FINALIZE_MESSAGE_SIGN", id: signRequest?.id, success: false });
|
||||
setDenying(false);
|
||||
await nextRequest();
|
||||
}
|
||||
return (
|
||||
<div className={styles.signContainer}>
|
||||
<h3 className={styles.title}>Sign Request</h3>
|
||||
<p className={styles.text}>Sign this message only if you fully understand its contents and trust the requesting site.</p>
|
||||
<p className={styles.subtext}>You sign:</p>
|
||||
<div className={styles.messageBlock}>
|
||||
<p className={styles.messageTitle}>Message:</p>
|
||||
<p>{signRequest?.message}</p>
|
||||
</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button disabled={denying} theme={ButtonThemes.Outline} onClick={denyClick}>Deny</Button>
|
||||
<Button disabled={accepting} onClick={acceptClick}>Accept</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
async function denyClick() {
|
||||
setDenying(true);
|
||||
await fetchBackground({
|
||||
method: 'FINALIZE_MESSAGE_SIGN',
|
||||
id: signRequest?.id,
|
||||
success: false,
|
||||
});
|
||||
setDenying(false);
|
||||
await nextRequest();
|
||||
}
|
||||
return (
|
||||
<div className={styles.signContainer}>
|
||||
<h3 className={styles.title}>Sign Request</h3>
|
||||
<p className={styles.text}>
|
||||
Sign this message only if you fully understand its contents and trust the requesting
|
||||
site.
|
||||
</p>
|
||||
<p className={styles.subtext}>You sign:</p>
|
||||
<div className={styles.messageBlock}>
|
||||
<p className={styles.messageTitle}>Message:</p>
|
||||
<p>{signRequest?.message}</p>
|
||||
</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button disabled={denying} theme={ButtonThemes.Outline} onClick={denyClick}>
|
||||
Deny
|
||||
</Button>
|
||||
<Button disabled={accepting} onClick={acceptClick}>
|
||||
Accept
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,69 +1,68 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.ConfirmationModal {
|
||||
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
color: rgba(255, 255, 255, 0.90);
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin: 16px 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
column-gap: 20px;
|
||||
max-height: 150px;
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
margin: 16px 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
column-gap: 20px;
|
||||
max-height: 150px;
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
column-gap: 20px;
|
||||
row-gap: 5px;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
column-gap: 20px;
|
||||
row-gap: 5px;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #1F8FEB;
|
||||
margin-bottom: 5px;
|
||||
color: #1f8feb;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
span {
|
||||
//text-align: right;
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
span {
|
||||
//text-align: right;
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 16px;
|
||||
}
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,54 @@
|
|||
import React, { useCallback, useContext } from "react";
|
||||
import cls from "../../components/ModalConfirmation/ModalConfirmation.module.scss";
|
||||
import Modal from "../UI/Modal/Modal";
|
||||
import Button, { ButtonThemes } from "../UI/Button/Button";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import cls from './ModalConfirmation.module.scss';
|
||||
import Modal from '../UI/Modal/Modal';
|
||||
import Button, { ButtonThemes } from '../UI/Button/Button';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
|
||||
interface ModalConfirmationProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const ModalConfirmation = ({ isOpen, onClose, onConfirm }: ModalConfirmationProps) => {
|
||||
const { state } = useContext(Store);
|
||||
const { method, params } = state.confirmationModal || {};
|
||||
const { state } = useContext(Store);
|
||||
const { method, params } = state.confirmationModal || {};
|
||||
|
||||
const closeHandler = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
const closeHandler = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
const confirmHandler = useCallback(() => {
|
||||
onConfirm();
|
||||
}, [onConfirm]);
|
||||
const confirmHandler = useCallback(() => {
|
||||
onConfirm();
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={closeHandler}>
|
||||
<div className={cls.ConfirmationModal}>
|
||||
<div className={cls.title}>Confirmation</div>
|
||||
<div className={cls.subTitle}>Confirm and send tx</div>
|
||||
<div className={cls.table}>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>method:</div>
|
||||
<div className={cls.value}>{method}</div>
|
||||
</div>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>params:</div>
|
||||
<div className={cls.value}>
|
||||
{Array.isArray(params) &&
|
||||
params.map((param: string) => <span key={param}>{param}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cls.actions}>
|
||||
<Button onClick={closeHandler} theme={ButtonThemes.Outline}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={confirmHandler}>Sign</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={closeHandler}>
|
||||
<div className={cls.ConfirmationModal}>
|
||||
<div className={cls.title}>Confirmation</div>
|
||||
<div className={cls.subTitle}>Confirm and send tx</div>
|
||||
<div className={cls.table}>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>method:</div>
|
||||
<div className={cls.value}>{method}</div>
|
||||
</div>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>params:</div>
|
||||
<div className={cls.value}>
|
||||
{Array.isArray(params) &&
|
||||
params.map((param: string) => <span key={param}>{param}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cls.actions}>
|
||||
<Button onClick={closeHandler} theme={ButtonThemes.Outline}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={confirmHandler}>Sign</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalConfirmation;
|
||||
|
|
|
|||
|
|
@ -1,104 +1,103 @@
|
|||
.ModalTransactionStatus {
|
||||
min-height: 400px;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statusMessage {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 12px;
|
||||
color: #1f8feb;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin-top: 12px;
|
||||
color: #1f8feb;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
// Error|Success
|
||||
.icon {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
margin: 0 auto;
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
margin: 0 auto;
|
||||
|
||||
img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
max-width: 100%;
|
||||
}
|
||||
img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.redColor {
|
||||
transform: rotate(45deg);
|
||||
background-color: #ff6767;
|
||||
}
|
||||
&.redColor {
|
||||
transform: rotate(45deg);
|
||||
background-color: #ff6767;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin: 16px 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
column-gap: 20px;
|
||||
max-height: 150px;
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
margin: 16px 0;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
column-gap: 20px;
|
||||
max-height: 150px;
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
column-gap: 20px;
|
||||
row-gap: 5px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
column-gap: 20px;
|
||||
row-gap: 5px;
|
||||
|
||||
.label {
|
||||
color: #1F8FEB;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.label {
|
||||
color: #1f8feb;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
.value {
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
span {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,82 +1,77 @@
|
|||
import React, { useCallback, useContext } from "react";
|
||||
import cls from "./ModalTransactionStatus.module.scss";
|
||||
import Modal from "../UI/Modal/Modal";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import errorImage from "../../assets/svg/plus.svg";
|
||||
import { updateTransactionStatus } from "../../store/actions";
|
||||
import Loader from "../UI/Loader/Loader";
|
||||
import { classNames } from "../../utils/classNames";
|
||||
import Button, { ButtonThemes } from "../UI/Button/Button";
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import cls from './ModalTransactionStatus.module.scss';
|
||||
import Modal from '../UI/Modal/Modal';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import errorImage from '../../assets/svg/plus.svg';
|
||||
import { updateTransactionStatus } from '../../store/actions';
|
||||
import Loader from '../UI/Loader/Loader';
|
||||
import { classNames } from '../../utils/classNames';
|
||||
import Button, { ButtonThemes } from '../UI/Button/Button';
|
||||
|
||||
const ModalTransactionStatus = () => {
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { visible, type, code, message } = state?.transactionStatus;
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const transactionStatus = state?.transactionStatus;
|
||||
|
||||
const closeHandler = useCallback(() => {
|
||||
updateTransactionStatus(dispatch as () => void, (prevState: object) => ({
|
||||
...prevState,
|
||||
isVisible: false,
|
||||
}));
|
||||
}, [dispatch]);
|
||||
const { visible, type, code, message } = transactionStatus || {};
|
||||
|
||||
const Loading = () => {
|
||||
if (type !== "loading") return <></>;
|
||||
|
||||
return (
|
||||
<div className={cls.loading}>
|
||||
<Loader />
|
||||
<div className={cls.title}>Sending...</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const closeHandler = useCallback(() => {
|
||||
updateTransactionStatus(dispatch as () => void, (prevState: object) => ({
|
||||
...prevState,
|
||||
isVisible: false,
|
||||
}));
|
||||
}, [dispatch]);
|
||||
|
||||
const Error = () => {
|
||||
if (type !== "error") return <></>;
|
||||
|
||||
return (
|
||||
<div className={cls.error}>
|
||||
<div className={classNames(cls.icon, {}, [cls.redColor])}>
|
||||
<img src={errorImage} alt="error" />
|
||||
</div>
|
||||
const Loading = () => {
|
||||
if (type !== 'loading') return <></>;
|
||||
|
||||
<div className={cls.title}>Error!</div>
|
||||
<div className={cls.statusMessage}>{message && message}</div>
|
||||
<div className={cls.table}>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>code:</div>
|
||||
<div className={cls.value}>{code}</div>
|
||||
</div>
|
||||
{/* <div className={cls.tableRow}>
|
||||
return (
|
||||
<div className={cls.loading}>
|
||||
<Loader />
|
||||
<div className={cls.title}>Sending...</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Error = () => {
|
||||
if (type !== 'error') return <></>;
|
||||
|
||||
return (
|
||||
<div className={cls.error}>
|
||||
<div className={classNames(cls.icon, {}, [cls.redColor])}>
|
||||
<img src={errorImage} alt="error" />
|
||||
</div>
|
||||
|
||||
<div className={cls.title}>Error!</div>
|
||||
<div className={cls.statusMessage}>{message && message}</div>
|
||||
<div className={cls.table}>
|
||||
<div className={cls.tableRow}>
|
||||
<div className={cls.label}>code:</div>
|
||||
<div className={cls.value}>{code}</div>
|
||||
</div>
|
||||
{/* <div className={cls.tableRow}>
|
||||
<div className={cls.label}>params:</div>
|
||||
<div className={cls.value}>
|
||||
<span>Value</span>
|
||||
<span>Value2</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
<Button
|
||||
className={cls.button}
|
||||
theme={ButtonThemes.Outline}
|
||||
onClick={closeHandler}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
<Button className={cls.button} theme={ButtonThemes.Outline} onClick={closeHandler}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
width={296}
|
||||
isOpen={visible}
|
||||
onClose={type !== "loading" ? closeHandler : () => { }}
|
||||
>
|
||||
<div className={cls.ModalTransactionStatus}>
|
||||
<Loading />
|
||||
<Error />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
const noop = () => null;
|
||||
return (
|
||||
<Modal width={296} isOpen={visible} onClose={type !== 'loading' ? closeHandler : noop}>
|
||||
<div className={cls.ModalTransactionStatus}>
|
||||
<Loading />
|
||||
<Error />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalTransactionStatus;
|
||||
|
|
|
|||
|
|
@ -1,168 +1,167 @@
|
|||
@import "src/app/styles/variables";
|
||||
@import 'src/app/styles/variables';
|
||||
|
||||
.confirmation {
|
||||
height: calc($appHeight - 72px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 180px;
|
||||
height: calc($appHeight - 72px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 180px;
|
||||
|
||||
&__title {
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
&__title {
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
text-align: center;
|
||||
color: #B6B6C4;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
&__subtitle {
|
||||
text-align: center;
|
||||
color: #b6b6c4;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
&__content {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #0F2055;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
&__block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #0f2055;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.col {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #B6B6C4;
|
||||
}
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #b6b6c4;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.commentBtn {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1F8FEB;
|
||||
margin-left: 4px;
|
||||
.commentBtn {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1f8feb;
|
||||
margin-left: 4px;
|
||||
|
||||
&.less {
|
||||
margin-left: 0;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.less {
|
||||
margin-left: 0;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__destinationWrapper {
|
||||
margin-top: 4px;
|
||||
&__destinationWrapper {
|
||||
margin-top: 4px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #B6B6C4;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #b6b6c4;
|
||||
}
|
||||
}
|
||||
|
||||
&__showAddressesBtn {
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1F8FEB;
|
||||
}
|
||||
&__showAddressesBtn {
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1f8feb;
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
background-color: #0F2055;
|
||||
border-top: 1px solid #273666;
|
||||
width: 100%;
|
||||
padding-inline: 16px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 24px;
|
||||
&__bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
background-color: #0f2055;
|
||||
border-top: 1px solid #273666;
|
||||
width: 100%;
|
||||
padding-inline: 16px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 24px;
|
||||
|
||||
&_fee {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
&_fee {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
h5 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #B6B6C4;
|
||||
}
|
||||
h5 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #b6b6c4;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
&_total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
&_total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
h5,
|
||||
p {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
h5,
|
||||
p {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #273666;
|
||||
margin: 12px 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #273666;
|
||||
margin: 12px 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&_buttons {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
&_buttons {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import React from "react";
|
||||
import Button, { ButtonThemes } from "../UI/Button/Button";
|
||||
import styles from "./OuterConfirmation.module.scss";
|
||||
import { useState, useEffect } from "react";
|
||||
import { fetchBackground, shortenAddress } from "../../utils/utils";
|
||||
import customTokenIcon from "../../assets/tokens-svg/custom-token.svg";
|
||||
import banditIcon from "../../assets/tokens-svg/bandit-icon.svg";
|
||||
import zanoIcon from "../../assets/tokens-svg/zano.svg";
|
||||
import bitcoinIcon from "../../assets/tokens-svg/bitcoin.svg";
|
||||
import ethIcon from "../../assets/tokens-svg/eth.svg";
|
||||
import arrowIcon from "../../assets/svg/arrow-blue.svg";
|
||||
import InfoTooltip from "../UI/InfoTooltip";
|
||||
import { getCurrent, goBack } from "react-chrome-extension-router";
|
||||
import { BurnAssetDataType } from "../../../types";
|
||||
import { BANDIT_ASSET_ID, ZANO_ASSET_ID } from "../../../constants";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { getCurrent, goBack } from 'react-chrome-extension-router';
|
||||
import Button, { ButtonThemes } from '../UI/Button/Button';
|
||||
import styles from './OuterConfirmation.module.scss';
|
||||
import { fetchBackground, shortenAddress } from '../../utils/utils';
|
||||
import customTokenIcon from '../../assets/tokens-svg/custom-token.svg';
|
||||
import banditIcon from '../../assets/tokens-svg/bandit-icon.svg';
|
||||
import zanoIcon from '../../assets/tokens-svg/zano.svg';
|
||||
import bitcoinIcon from '../../assets/tokens-svg/bitcoin.svg';
|
||||
import ethIcon from '../../assets/tokens-svg/eth.svg';
|
||||
import arrowIcon from '../../assets/svg/arrow-blue.svg';
|
||||
import InfoTooltip from '../UI/InfoTooltip';
|
||||
import { BurnAssetDataType } from '../../../types';
|
||||
import { BANDIT_ASSET_ID, ZANO_ASSET_ID } from '../../../constants';
|
||||
|
||||
interface ParamsType {
|
||||
key: number;
|
||||
|
|
@ -20,8 +19,8 @@ interface ParamsType {
|
|||
}
|
||||
|
||||
interface DestionationType {
|
||||
address: string
|
||||
amount: string
|
||||
address: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
const OuterConfirmation = () => {
|
||||
|
|
@ -37,15 +36,22 @@ const OuterConfirmation = () => {
|
|||
const req = reqs[reqIndex] || {};
|
||||
const { id, name, params, method, destinations } = req;
|
||||
|
||||
const isTransferMethod = name?.toLowerCase() === "transfer";
|
||||
const isBurnMethod = name?.toLowerCase() === "burn_asset";
|
||||
const isTransferMethod = name?.toLowerCase() === 'transfer';
|
||||
const isBurnMethod = name?.toLowerCase() === 'burn_asset';
|
||||
|
||||
const isMultipleDestinations = destinations && destinations.length > 0;
|
||||
|
||||
const transactionParams = params
|
||||
? Object.fromEntries((params as ParamsType[]).map(item => [item.key, item.value]))
|
||||
? Object.fromEntries((params as ParamsType[]).map((item) => [item.key, item.value]))
|
||||
: {};
|
||||
const totalAmount = Number(isMultipleDestinations ? destinations.reduce((sum: number, dest: { amount: number }) => sum + Number(dest.amount), 0) : transactionParams.Amount).toLocaleString();
|
||||
const totalAmount = Number(
|
||||
isMultipleDestinations
|
||||
? destinations.reduce(
|
||||
(sum: number, dest: { amount: number }) => sum + Number(dest.amount),
|
||||
0,
|
||||
)
|
||||
: transactionParams.Amount,
|
||||
).toLocaleString();
|
||||
|
||||
useEffect(() => {
|
||||
setReqIndex(0);
|
||||
|
|
@ -75,30 +81,30 @@ const OuterConfirmation = () => {
|
|||
|
||||
const getAssetIcon = (assetId: string) => {
|
||||
switch (assetId) {
|
||||
case "ZANO":
|
||||
case 'ZANO':
|
||||
return <img width={16} src={zanoIcon} alt="ZANO asset" />;
|
||||
case "BANDIT":
|
||||
case 'BANDIT':
|
||||
return <img width={16} src={banditIcon} alt="ZANO asset" />;
|
||||
case "Wrapped Bitcoin":
|
||||
case 'Wrapped Bitcoin':
|
||||
return <img width={16} src={bitcoinIcon} alt="bitcoin icon" />;
|
||||
case "Wrapped Ethereum":
|
||||
case 'Wrapped Ethereum':
|
||||
return <img width={16} src={ethIcon} alt="EthIcon" />;
|
||||
default:
|
||||
return <img width={16} src={customTokenIcon} alt="CustomTokenIcon" />;;
|
||||
return <img width={16} src={customTokenIcon} alt="CustomTokenIcon" />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = accepting || denying;
|
||||
|
||||
const getConfirmationName = () => {
|
||||
if (isTransferMethod) {
|
||||
return "Please confirm the transfer details";
|
||||
} else if (isBurnMethod) {
|
||||
return "BURN ASSET"
|
||||
} else {
|
||||
return name
|
||||
return 'Please confirm the transfer details';
|
||||
}
|
||||
}
|
||||
if (isBurnMethod) {
|
||||
return 'BURN ASSET';
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
const getConfirmationContent = () => {
|
||||
if (isTransferMethod) {
|
||||
|
|
@ -111,7 +117,9 @@ const OuterConfirmation = () => {
|
|||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Asset</h5>
|
||||
<p>{getAssetIcon(transactionParams?.Asset)} {transactionParams?.Asset}</p>
|
||||
<p>
|
||||
{getAssetIcon(transactionParams?.Asset)} {transactionParams?.Asset}
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Amount</h5>
|
||||
|
|
@ -120,158 +128,209 @@ const OuterConfirmation = () => {
|
|||
|
||||
<div className={styles.col}>
|
||||
<h5>Comment</h5>
|
||||
<p>{(transactionParams?.Comment?.length > 60 && !showFullComment) ?
|
||||
<>
|
||||
{transactionParams?.Comment?.slice(0, 60)}...
|
||||
<button className={styles.commentBtn} onClick={() => setShowFullComment(true)}>Show more</button>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
{transactionParams?.Comment}
|
||||
{showFullComment && <button className={`${styles.commentBtn} ${styles.less}`} onClick={() => setShowFullComment(false)}>Show less</button>}
|
||||
</>
|
||||
}</p>
|
||||
<p>
|
||||
{transactionParams?.Comment?.length > 60 && !showFullComment ? (
|
||||
<>
|
||||
{transactionParams?.Comment?.slice(0, 60)}...
|
||||
<button
|
||||
className={styles.commentBtn}
|
||||
onClick={() => setShowFullComment(true)}
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{transactionParams?.Comment}
|
||||
{showFullComment && (
|
||||
<button
|
||||
className={`${styles.commentBtn} ${styles.less}`}
|
||||
onClick={() => setShowFullComment(false)}
|
||||
>
|
||||
Show less
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>To</h5>
|
||||
<p>{isMultipleDestinations ? <>{destinations?.length} addresses</> : transactionParams?.To}</p>
|
||||
<p>
|
||||
{isMultipleDestinations ? (
|
||||
<>{destinations?.length} addresses</>
|
||||
) : (
|
||||
transactionParams?.To
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isMultipleDestinations && <div className={styles.row}>
|
||||
<h5>Amount</h5>
|
||||
<p>{totalAmount}</p>
|
||||
</div>}
|
||||
{!isMultipleDestinations && (
|
||||
<div className={styles.row}>
|
||||
<h5>Amount</h5>
|
||||
<p>{totalAmount}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isMultipleDestinations && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setShowFullItems(prev => !prev)}
|
||||
onClick={() => setShowFullItems((prev) => !prev)}
|
||||
className={styles.confirmation__showAddressesBtn}
|
||||
>
|
||||
Show addresses <img style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }} width={18} src={arrowIcon} alt="arrow" />
|
||||
Show addresses{' '}
|
||||
<img
|
||||
style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }}
|
||||
width={18}
|
||||
src={arrowIcon}
|
||||
alt="arrow"
|
||||
/>
|
||||
</button>
|
||||
|
||||
{showFullItems && destinations?.map((item: DestionationType, idx: number) => (
|
||||
<div className={styles.confirmation__destinationWrapper} key={idx}>
|
||||
<p className={styles.title}>RECIPIENT {idx + 1}</p>
|
||||
{showFullItems &&
|
||||
destinations?.map((item: DestionationType, idx: number) => (
|
||||
<div
|
||||
className={styles.confirmation__destinationWrapper}
|
||||
key={idx}
|
||||
>
|
||||
<p className={styles.title}>RECIPIENT {idx + 1}</p>
|
||||
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>To</h5>
|
||||
<p>{item.address}</p>
|
||||
</div>
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>To</h5>
|
||||
<p>{item.address}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<h5>Amount</h5>
|
||||
<p>{item.amount}</p>
|
||||
<div className={styles.row}>
|
||||
<h5>Amount</h5>
|
||||
<p>{item.amount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
} else if (isBurnMethod) {
|
||||
);
|
||||
}
|
||||
if (isBurnMethod) {
|
||||
const {
|
||||
assetId,
|
||||
burnAmount,
|
||||
nativeAmount,
|
||||
pointTxToAddress,
|
||||
serviceEntries
|
||||
serviceEntries,
|
||||
}: BurnAssetDataType = params[0];
|
||||
const getIconByAsseetId = () => {
|
||||
if (assetId === ZANO_ASSET_ID) {
|
||||
return "ZANO"
|
||||
} else if (assetId === BANDIT_ASSET_ID) {
|
||||
return "BANDIT"
|
||||
} else {
|
||||
return assetId
|
||||
return 'ZANO';
|
||||
}
|
||||
}
|
||||
if (assetId === BANDIT_ASSET_ID) {
|
||||
return 'BANDIT';
|
||||
}
|
||||
return assetId;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>Asset</h5>
|
||||
<p>{getAssetIcon(getIconByAsseetId())} {shortenAddress(assetId, 6, 6)}</p>
|
||||
<p>
|
||||
{getAssetIcon(getIconByAsseetId())} {shortenAddress(assetId, 6, 6)}
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Burn Amount</h5>
|
||||
<p>{burnAmount}</p>
|
||||
</div>
|
||||
{nativeAmount && <div className={styles.row}>
|
||||
<h5>Native Amount</h5>
|
||||
<p>{nativeAmount}</p>
|
||||
</div>}
|
||||
{pointTxToAddress && <div className={styles.row}>
|
||||
<h5>Send Tx To</h5>
|
||||
<p>{shortenAddress(pointTxToAddress, 6, 6)}</p>
|
||||
</div>}
|
||||
{nativeAmount && (
|
||||
<div className={styles.row}>
|
||||
<h5>Native Amount</h5>
|
||||
<p>{nativeAmount}</p>
|
||||
</div>
|
||||
)}
|
||||
{pointTxToAddress && (
|
||||
<div className={styles.row}>
|
||||
<h5>Send Tx To</h5>
|
||||
<p>{shortenAddress(pointTxToAddress, 6, 6)}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{serviceEntries && <button
|
||||
onClick={() => setShowFullItems(prev => !prev)}
|
||||
className={styles.confirmation__showAddressesBtn}
|
||||
>
|
||||
Show service entries <img style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }} width={18} src={arrowIcon} alt="arrow" />
|
||||
</button>}
|
||||
{serviceEntries && (
|
||||
<button
|
||||
onClick={() => setShowFullItems((prev) => !prev)}
|
||||
className={styles.confirmation__showAddressesBtn}
|
||||
>
|
||||
Show service entries{' '}
|
||||
<img
|
||||
style={{ transform: `rotate(${showFullItems ? '180deg' : 0})` }}
|
||||
width={18}
|
||||
src={arrowIcon}
|
||||
alt="arrow"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{showFullItems && serviceEntries?.map((item, idx) => {
|
||||
const dataLength = serviceEntries?.length || 1;
|
||||
{showFullItems &&
|
||||
serviceEntries?.map((item, idx) => {
|
||||
const dataLength = serviceEntries?.length || 1;
|
||||
|
||||
return <div className={styles.confirmation__destinationWrapper} key={idx}>
|
||||
{dataLength > 1 && <p className={styles.title}>
|
||||
Service Entries {idx + 1}
|
||||
</p>}
|
||||
return (
|
||||
<div className={styles.confirmation__destinationWrapper} key={idx}>
|
||||
{dataLength > 1 && (
|
||||
<p className={styles.title}>Service Entries {idx + 1}</p>
|
||||
)}
|
||||
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>Body</h5>
|
||||
<p>{shortenAddress(item.body, 6, 6)}</p>
|
||||
<div className={styles.confirmation__block}>
|
||||
<div className={styles.row}>
|
||||
<h5>Body</h5>
|
||||
<p>{shortenAddress(item.body, 6, 6)}</p>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Flags</h5>
|
||||
<p>{item.flags}</p>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Instruction</h5>
|
||||
<p>{item.instruction}</p>
|
||||
</div>
|
||||
{item.security && (
|
||||
<div className={styles.row}>
|
||||
<h5>Security</h5>
|
||||
<p>{shortenAddress(item.security, 6, 6)}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.row}>
|
||||
<h5>Service Id</h5>
|
||||
<p>{item.service_id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Flags</h5>
|
||||
<p>{item.flags}</p>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<h5>Instruction</h5>
|
||||
<p>{item.instruction}</p>
|
||||
</div>
|
||||
{item.security && <div className={styles.row}>
|
||||
<h5>Security</h5>
|
||||
<p>{shortenAddress(item.security, 6, 6)}</p>
|
||||
</div>}
|
||||
<div className={styles.row}>
|
||||
<h5>Service Id</h5>
|
||||
<p>{item.service_id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.confirmation__block}>
|
||||
{Array.isArray(params) && params?.map((item: ParamsType, idx: number) => (
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.confirmation__block}>
|
||||
{Array.isArray(params) &&
|
||||
params?.map((item: ParamsType, idx: number) => (
|
||||
<div key={idx} className={styles.row}>
|
||||
<h5>{item.key}</h5>
|
||||
<p>{item.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (!req) {
|
||||
return <div>No request found.</div>;
|
||||
|
|
@ -280,32 +339,33 @@ const OuterConfirmation = () => {
|
|||
return (
|
||||
<div className={styles.confirmation}>
|
||||
<h3 className={styles.confirmation__title}>Request Confirmation</h3>
|
||||
<h5 className={styles.confirmation__subtitle}>
|
||||
{getConfirmationName()}
|
||||
</h5>
|
||||
<h5 className={styles.confirmation__subtitle}>{getConfirmationName()}</h5>
|
||||
|
||||
<div className={styles.confirmation__content}>
|
||||
{getConfirmationContent()}
|
||||
</div>
|
||||
<div className={styles.confirmation__content}>{getConfirmationContent()}</div>
|
||||
|
||||
<div className={styles.confirmation__bottom}>
|
||||
{isTransferMethod || isBurnMethod && <>
|
||||
<div className={styles.confirmation__bottom_fee}>
|
||||
<h5>
|
||||
Transaction fee <InfoTooltip title="Total network fee" />
|
||||
</h5>
|
||||
<p>0.01 ZANO</p>
|
||||
</div>
|
||||
{isTransferMethod ||
|
||||
(isBurnMethod && (
|
||||
<>
|
||||
<div className={styles.confirmation__bottom_fee}>
|
||||
<h5>
|
||||
Transaction fee <InfoTooltip title="Total network fee" />
|
||||
</h5>
|
||||
<p>0.01 ZANO</p>
|
||||
</div>
|
||||
|
||||
{isTransferMethod && <>
|
||||
<div className={styles.divider} />
|
||||
{isTransferMethod && (
|
||||
<>
|
||||
<div className={styles.divider} />
|
||||
|
||||
<div className={styles.confirmation__bottom_total}>
|
||||
<h5>Total</h5>
|
||||
<p>{totalAmount}</p>
|
||||
</div>
|
||||
</>}
|
||||
</>}
|
||||
<div className={styles.confirmation__bottom_total}>
|
||||
<h5>Total</h5>
|
||||
<p>{totalAmount}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
|
||||
<div className={styles.confirmation__bottom_buttons}>
|
||||
<Button
|
||||
|
|
@ -317,17 +377,13 @@ const OuterConfirmation = () => {
|
|||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className={styles.btn}
|
||||
disabled={disabled}
|
||||
onClick={acceptClick}
|
||||
>
|
||||
<Button className={styles.btn} disabled={disabled} onClick={acceptClick}>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default OuterConfirmation;
|
||||
export default OuterConfirmation;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
.passwordPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
// line-height: 150%;
|
||||
> p {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
// line-height: 150%;
|
||||
|
||||
// > span {
|
||||
// font-size: 32px;
|
||||
// font-weight: 700;
|
||||
// }
|
||||
}
|
||||
// > span {
|
||||
// font-size: 32px;
|
||||
// font-weight: 700;
|
||||
// }
|
||||
}
|
||||
|
||||
.logoImage {
|
||||
width: 175px;
|
||||
}
|
||||
.logoImage {
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
.inputPanel {
|
||||
margin-top: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
.inputPanel {
|
||||
margin-top: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import React, { ChangeEvent } from "react";
|
||||
import s from "./PasswordPage.module.scss";
|
||||
import logo from "../../assets/svg/logo.svg";
|
||||
import MyInput from "../UI/MyInput/MyInput";
|
||||
import Button from "../UI/Button/Button";
|
||||
import { useState } from "react";
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import s from './PasswordPage.module.scss';
|
||||
import logo from '../../assets/svg/logo.svg';
|
||||
import MyInput from '../UI/MyInput/MyInput';
|
||||
import Button from '../UI/Button/Button';
|
||||
|
||||
interface PasswordPageProps {
|
||||
onConfirm: (password: string) => void;
|
||||
|
|
@ -12,13 +11,9 @@ interface PasswordPageProps {
|
|||
}
|
||||
|
||||
function PasswordPage(props: PasswordPageProps) {
|
||||
const {
|
||||
onConfirm,
|
||||
incorrectPassword,
|
||||
setIncorrectPassword
|
||||
} = props;
|
||||
const { onConfirm, incorrectPassword, setIncorrectPassword } = props;
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
function onInputChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
setIncorrectPassword(false);
|
||||
|
|
@ -26,17 +21,12 @@ function PasswordPage(props: PasswordPageProps) {
|
|||
}
|
||||
|
||||
function onButtonClick() {
|
||||
onConfirm && onConfirm(password);
|
||||
if (onConfirm) onConfirm(password);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.passwordPage}>
|
||||
|
||||
<img
|
||||
className={s.logoImage}
|
||||
src={logo}
|
||||
alt="Zano"
|
||||
/>
|
||||
<img className={s.logoImage} src={logo} alt="Zano" />
|
||||
<p>Enter your password</p>
|
||||
<div className={s.inputPanel}>
|
||||
<MyInput
|
||||
|
|
@ -44,15 +34,12 @@ function PasswordPage(props: PasswordPageProps) {
|
|||
type="password"
|
||||
inputData={{ value: password, isDirty: !!incorrectPassword }}
|
||||
onChange={onInputChange}
|
||||
onKeyDown={event => event.key === "Enter" ? onButtonClick() : undefined}
|
||||
onKeyDown={(event) => (event.key === 'Enter' ? onButtonClick() : undefined)}
|
||||
/>
|
||||
<Button onClick={onButtonClick}>
|
||||
Enter
|
||||
</Button>
|
||||
<Button onClick={onButtonClick}>Enter</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default PasswordPage;
|
||||
export default PasswordPage;
|
||||
|
|
|
|||
|
|
@ -1,78 +1,78 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.asset {
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(31, 143, 235, 0.15);
|
||||
transition: background-color 0.2s ease 0s;
|
||||
&:hover {
|
||||
background-color: rgba(31, 143, 235, 0.3);
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(31, 143, 235, 0.15);
|
||||
transition: background-color 0.2s ease 0s;
|
||||
&:hover {
|
||||
background-color: rgba(31, 143, 235, 0.3);
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.assetBody {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.assetRemoveBtn {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 5px;
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s ease 0s;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
padding: 5px;
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s ease 0s;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.assetTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(28, 24);
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: math.div(28, 24);
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.assetInfo {
|
||||
display: grid;
|
||||
grid-template-columns: 56% 44%;
|
||||
column-gap: 20px;
|
||||
line-height: math.div(21, 18);
|
||||
display: grid;
|
||||
grid-template-columns: 56% 44%;
|
||||
column-gap: 20px;
|
||||
line-height: math.div(21, 18);
|
||||
}
|
||||
|
||||
.assetInfoLabel {
|
||||
color: #1F8FEB;
|
||||
margin-bottom: 4px;
|
||||
color: #1f8feb;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.assetInfoValue {
|
||||
font-weight: 600;
|
||||
font-weight: 600;
|
||||
|
||||
> span {
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,85 @@
|
|||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
import crossIcon from "../../../assets/svg/cross.svg";
|
||||
import bitcoinIcon from "../../../assets/tokens-svg/bitcoin.svg";
|
||||
import banditIcon from "../../../assets/tokens-svg/bandit-icon.svg";
|
||||
import customTokenIcon from "../../../assets/tokens-svg/custom-token.svg";
|
||||
import ethIcon from "../../../assets/tokens-svg/eth.svg";
|
||||
import zanoIcon from "../../../assets/tokens-svg/zano.svg";
|
||||
import { useCensorDigits } from "../../../hooks/useCensorDigits";
|
||||
import { Store } from "../../../store/store-reducer";
|
||||
import s from "./Assets.module.scss";
|
||||
import Decimal from "decimal.js";
|
||||
import React, { useContext } from 'react';
|
||||
import Decimal from 'decimal.js';
|
||||
import bitcoinIcon from '../../../assets/tokens-svg/bitcoin.svg';
|
||||
import banditIcon from '../../../assets/tokens-svg/bandit-icon.svg';
|
||||
import customTokenIcon from '../../../assets/tokens-svg/custom-token.svg';
|
||||
import ethIcon from '../../../assets/tokens-svg/eth.svg';
|
||||
import zanoIcon from '../../../assets/tokens-svg/zano.svg';
|
||||
import { useCensorDigits } from '../../../hooks/useCensorDigits';
|
||||
import { Store } from '../../../store/store-reducer';
|
||||
import s from './Assets.module.scss';
|
||||
|
||||
interface Asset {
|
||||
name: string;
|
||||
ticker: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
value: number;
|
||||
name: string;
|
||||
ticker: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const getIconImage = (asset: Asset) => {
|
||||
switch (asset.name) {
|
||||
case "Zano":
|
||||
return <img src={zanoIcon} alt="ZanoIcon" />;
|
||||
case "Wrapped Bitcoin":
|
||||
return <img src={bitcoinIcon} alt="bitcoin icon" />;
|
||||
case "Wrapped Ethereum":
|
||||
return <img src={ethIcon} alt="EthIcon" />;
|
||||
case "BANDIT":
|
||||
return <img src={banditIcon} alt="bandit icon" />
|
||||
default:
|
||||
return <img src={customTokenIcon} alt="CustomTokenIcon" />;
|
||||
}
|
||||
switch (asset.name) {
|
||||
case 'Zano':
|
||||
return <img src={zanoIcon} alt="ZanoIcon" />;
|
||||
case 'Wrapped Bitcoin':
|
||||
return <img src={bitcoinIcon} alt="bitcoin icon" />;
|
||||
case 'Wrapped Ethereum':
|
||||
return <img src={ethIcon} alt="EthIcon" />;
|
||||
case 'BANDIT':
|
||||
return <img src={banditIcon} alt="bandit icon" />;
|
||||
default:
|
||||
return <img src={customTokenIcon} alt="CustomTokenIcon" />;
|
||||
}
|
||||
};
|
||||
|
||||
const Assets = () => {
|
||||
const { state } = useContext(Store);
|
||||
const { censorValue } = useCensorDigits();
|
||||
//TODO: only remove non whitelisted assets
|
||||
const remove = () => console.log("remove icon click");
|
||||
const { state } = useContext(Store);
|
||||
const { censorValue } = useCensorDigits();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(state.wallet.assets).map((asset) => {
|
||||
const fiatBalance = (
|
||||
Number(asset.balance) * state.priceData.price
|
||||
).toFixed(2);
|
||||
return (
|
||||
<div className={s.asset} key={asset.name}>
|
||||
{/* <button className={s.assetRemoveBtn} onClick={remove}>
|
||||
return (
|
||||
<div>
|
||||
{state.wallet.assets.map((asset) => {
|
||||
const fiatBalance = (Number(asset.balance) * state.priceData.price).toFixed(2);
|
||||
return (
|
||||
<div className={s.asset} key={asset.name}>
|
||||
{/* <button className={s.assetRemoveBtn} onClick={remove}>
|
||||
<img src={crossIcon} alt="CrossIcon" />
|
||||
</button> */}
|
||||
<button className={s.assetBody}>
|
||||
<span className={s.assetTitle}>
|
||||
{getIconImage(asset)}
|
||||
{asset.name}
|
||||
</span>
|
||||
<span className={s.assetInfo}>
|
||||
<div>
|
||||
<div className={s.assetInfoLabel}>Balance</div>
|
||||
<div className={s.assetInfoValue}>
|
||||
<span>{censorValue(new Decimal(asset.balance).toSignificantDigits(20).toString())}</span>
|
||||
{" "}
|
||||
{asset.ticker}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={s.assetInfoLabel}>Value</div>
|
||||
<div className={s.assetInfoValue}>
|
||||
${censorValue(asset.name === "Zano" ? fiatBalance : 0)}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* <MyButton style={{ transform: "translateY(30%)" }}>
|
||||
<button className={s.assetBody}>
|
||||
<span className={s.assetTitle}>
|
||||
{getIconImage(asset)}
|
||||
{asset.name}
|
||||
</span>
|
||||
<span className={s.assetInfo}>
|
||||
<div>
|
||||
<div className={s.assetInfoLabel}>Balance</div>
|
||||
<div className={s.assetInfoValue}>
|
||||
<span>
|
||||
{censorValue(
|
||||
new Decimal(asset.balance)
|
||||
.toSignificantDigits(20)
|
||||
.toString(),
|
||||
)}
|
||||
</span>{' '}
|
||||
{asset.ticker}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={s.assetInfoLabel}>Value</div>
|
||||
<div className={s.assetInfoValue}>
|
||||
${censorValue(asset.name === 'Zano' ? fiatBalance : 0)}
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* <MyButton style={{ transform: "translateY(30%)" }}>
|
||||
<img src={plusIcon} alt="PlusIcon" /> Add Custom Token
|
||||
</MyButton> */}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Assets;
|
||||
|
|
|
|||
|
|
@ -1,75 +1,75 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.historyItem {
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: rgba(31, 143, 235, 0.15);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
&:hover {
|
||||
background-color: rgba(31, 143, 235, 0.3);
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: rgba(31, 143, 235, 0.15);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
&:hover {
|
||||
background-color: rgba(31, 143, 235, 0.3);
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.historyLoading {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation: rotate 0.8s infinite linear;
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation: rotate 0.8s infinite linear;
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.historyTop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
margin-bottom: 11px;
|
||||
padding-right: 45px;
|
||||
p {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: math.div(29, 24);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
margin-bottom: 11px;
|
||||
padding-right: 45px;
|
||||
p {
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: math.div(29, 24);
|
||||
|
||||
> span {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.historyIcon {
|
||||
font-size: 28px;
|
||||
flex: 0 0 28px;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
color: #1f8feb;
|
||||
&.receiveVariant {
|
||||
color: #16d1d6;
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
font-size: 28px;
|
||||
flex: 0 0 28px;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
color: #1f8feb;
|
||||
&.receiveVariant {
|
||||
color: #16d1d6;
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.historyAddress {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
color: #1F8FEB;
|
||||
opacity: 0.5;
|
||||
}
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
color: #1f8feb;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,87 @@
|
|||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
import Big from "big.js";
|
||||
import LoadingIcon from "../../../assets/svg/loading.svg";
|
||||
import receiveIcon from "../../../assets/svg/receive-colored.svg";
|
||||
import sendIcon from "../../../assets/svg/send-colored.svg";
|
||||
import { Store } from "../../../store/store-reducer";
|
||||
import TransactionDetails from "../../TransactionDetails/TransactionDetails";
|
||||
import s from "./History.module.scss";
|
||||
import React, { useContext } from 'react';
|
||||
import Big from 'big.js';
|
||||
import LoadingIcon from '../../../assets/svg/loading.svg';
|
||||
import receiveIcon from '../../../assets/svg/receive-colored.svg';
|
||||
import sendIcon from '../../../assets/svg/send-colored.svg';
|
||||
import { Store } from '../../../store/store-reducer';
|
||||
import TransactionDetails from '../../TransactionDetails/TransactionDetails';
|
||||
import s from './History.module.scss';
|
||||
import NavLink from '../../UI/NavLink/NavLink';
|
||||
import useGetAsset from "../../../hooks/useGetAsset";
|
||||
import { ZANO_ASSET_ID } from "../../../../constants";
|
||||
import useGetAsset from '../../../hooks/useGetAsset';
|
||||
import { ZANO_ASSET_ID } from '../../../../constants';
|
||||
|
||||
interface HistoryItemProps {
|
||||
transfer: {
|
||||
assetId: string;
|
||||
amount: string;
|
||||
incoming: boolean;
|
||||
};
|
||||
fee: string;
|
||||
isInitiator: boolean;
|
||||
transfer: {
|
||||
assetId?: string;
|
||||
amount?: number;
|
||||
incoming?: boolean;
|
||||
};
|
||||
fee: string;
|
||||
isInitiator: boolean;
|
||||
}
|
||||
|
||||
const HistoryItem = ({ transfer, fee, isInitiator }: HistoryItemProps) => {
|
||||
const { getAssetById } = useGetAsset();
|
||||
const { getAssetById } = useGetAsset();
|
||||
|
||||
if (transfer.amount === fee) return null;
|
||||
const amount = new Big(transfer.amount);
|
||||
const fixedFee = new Big(fee);
|
||||
if (String(transfer.amount) === fee) return null;
|
||||
const amount = new Big(String(transfer.amount));
|
||||
const fixedFee = new Big(fee);
|
||||
|
||||
return (
|
||||
<div className={s.historyTop}>
|
||||
<div className={s.historyIcon}>
|
||||
<img src={transfer.incoming ? receiveIcon : sendIcon} alt="ArrowIcon" />
|
||||
</div>
|
||||
<p>
|
||||
<span>
|
||||
{transfer.assetId ===
|
||||
ZANO_ASSET_ID
|
||||
? !isInitiator
|
||||
? amount.toFixed()
|
||||
: amount.minus(fixedFee).toFixed()
|
||||
: amount.toFixed()
|
||||
}
|
||||
</span>
|
||||
{" "}
|
||||
{
|
||||
getAssetById(transfer.assetId)
|
||||
?.ticker || '***'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
let displayAmount: string;
|
||||
|
||||
if (transfer.assetId === ZANO_ASSET_ID) {
|
||||
if (!isInitiator) {
|
||||
displayAmount = amount.toFixed();
|
||||
} else {
|
||||
displayAmount = amount.minus(fixedFee).toFixed();
|
||||
}
|
||||
} else {
|
||||
displayAmount = amount.toFixed();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.historyTop}>
|
||||
<div className={s.historyIcon}>
|
||||
<img src={transfer.incoming ? receiveIcon : sendIcon} alt="ArrowIcon" />
|
||||
</div>
|
||||
<p>
|
||||
<span>{displayAmount}</span>{' '}
|
||||
{getAssetById(String(transfer.assetId))?.ticker || '***'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const History = () => {
|
||||
const { state } = useContext(Store);
|
||||
const { state } = useContext(Store);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{state.wallet.transactions.map((tx) => {
|
||||
return (
|
||||
<NavLink
|
||||
key={tx.txHash}
|
||||
className={s.historyItem}
|
||||
component={TransactionDetails}
|
||||
props={tx}
|
||||
>
|
||||
{!tx.isConfirmed && (
|
||||
<div className={s.historyLoading}>
|
||||
<img src={LoadingIcon} alt="LoadingIcon" />
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div>
|
||||
{state.wallet.transactions.map((tx) => (
|
||||
<NavLink
|
||||
key={tx.txHash}
|
||||
className={s.historyItem}
|
||||
component={TransactionDetails}
|
||||
props={tx}
|
||||
>
|
||||
{!tx.isConfirmed && (
|
||||
<div className={s.historyLoading}>
|
||||
<img src={LoadingIcon} alt="LoadingIcon" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tx.transfers?.map((transfer) => (
|
||||
<HistoryItem transfer={transfer as any} fee={String(tx.fee)} isInitiator={Boolean(tx.isInitiator)} />
|
||||
))}
|
||||
<span className={s.historyAddress}>{tx.txHash}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
{tx.transfers?.map((transfer) => (
|
||||
<HistoryItem
|
||||
transfer={transfer}
|
||||
fee={String(tx.fee)}
|
||||
isInitiator={Boolean(tx.isInitiator)}
|
||||
/>
|
||||
))}
|
||||
<span className={s.historyAddress}>{tx.txHash}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default History;
|
||||
export default History;
|
||||
|
|
|
|||
|
|
@ -1,48 +1,50 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.tabs {
|
||||
position: relative;
|
||||
background: #0F2055;
|
||||
border-radius: 24px 24px 0 0;
|
||||
margin: 0 -16px;
|
||||
min-height: 373px;
|
||||
padding: 16px 16px 30px 16px;
|
||||
position: relative;
|
||||
background: #0f2055;
|
||||
border-radius: 24px 24px 0 0;
|
||||
margin: 0 -16px;
|
||||
min-height: 373px;
|
||||
padding: 16px 16px 30px 16px;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
.tabsNav {
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 20px;
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 20px;
|
||||
}
|
||||
|
||||
.tabsNavBtn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
height: 45px;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
}
|
||||
&[disabled] {
|
||||
&::after {
|
||||
background-color: #1F8FEB;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
height: 45px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
&::after {
|
||||
background-color: #1f8feb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
|
|
@ -118,4 +120,3 @@
|
|||
.assetInfoValue {
|
||||
font-weight: 600;
|
||||
}*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,41 @@
|
|||
import React, { MouseEvent } from "react";
|
||||
import { useState } from "react";
|
||||
import Assets from "./Assets/Assets";
|
||||
import History from "./History/History";
|
||||
import s from "./TokensTabs.module.scss";
|
||||
import React, { MouseEvent, useState } from 'react';
|
||||
import Assets from './Assets/Assets';
|
||||
import History from './History/History';
|
||||
import s from './TokensTabs.module.scss';
|
||||
|
||||
const TokensTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const tabs = [
|
||||
{ label: "assets", content: <Assets /> },
|
||||
{ label: "history", content: <History /> },
|
||||
];
|
||||
const tabs = [
|
||||
{ label: 'assets', content: <Assets /> },
|
||||
{ label: 'history', content: <History /> },
|
||||
];
|
||||
|
||||
const toggleTabs = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (activeTab !== Number(e.currentTarget.value)) {
|
||||
setActiveTab(Number(e.currentTarget.value));
|
||||
}
|
||||
};
|
||||
const toggleTabs = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (activeTab !== Number(e.currentTarget.value)) {
|
||||
setActiveTab(Number(e.currentTarget.value));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s.tabs}>
|
||||
<div className={s.tabsNav}>
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={tab.label}
|
||||
onClick={(e) => toggleTabs(e)}
|
||||
value={index}
|
||||
disabled={activeTab === index}
|
||||
className={s.tabsNavBtn}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<div className={s.tabs}>
|
||||
<div className={s.tabsNav}>
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={tab.label}
|
||||
onClick={(e) => toggleTabs(e)}
|
||||
value={index}
|
||||
disabled={activeTab === index}
|
||||
className={s.tabsNavBtn}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{tabs[activeTab].content}
|
||||
</div>
|
||||
);
|
||||
{tabs[activeTab].content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokensTabs;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
@use "sass:math";
|
||||
|
||||
@use 'sass:math';
|
||||
|
||||
.transaction__transfers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
|
||||
.transaction__transfer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.transaction__transfer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +1,146 @@
|
|||
import React, { useEffect, useContext } from "react";
|
||||
import Big from "big.js";
|
||||
import copyIcon from "../../assets/svg/copy-blue.svg";
|
||||
import incomingIcon from "../../assets/svg/incoming_ico.svg";
|
||||
import outgoingIcon from "../../assets/svg/outgoing_ico.svg";
|
||||
import { useCopy } from "../../hooks/useCopy";
|
||||
import RoutersNav from "../UI/RoutersNav/RoutersNav";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import styles from "./TransactionDetails.module.scss";
|
||||
import { ZANO_ASSET_ID } from "../../../constants";
|
||||
import React, { useEffect, useContext } from 'react';
|
||||
import Big from 'big.js';
|
||||
import copyIcon from '../../assets/svg/copy-blue.svg';
|
||||
import incomingIcon from '../../assets/svg/incoming_ico.svg';
|
||||
import outgoingIcon from '../../assets/svg/outgoing_ico.svg';
|
||||
import { useCopy } from '../../hooks/useCopy';
|
||||
import RoutersNav from '../UI/RoutersNav/RoutersNav';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import styles from './TransactionDetails.module.scss';
|
||||
import { ZANO_ASSET_ID } from '../../../constants';
|
||||
|
||||
type Transfer = {
|
||||
amount: string;
|
||||
assetId: string;
|
||||
incoming: boolean;
|
||||
amount: string;
|
||||
assetId: string;
|
||||
incoming: boolean;
|
||||
};
|
||||
|
||||
type TransactionDetailsProps = {
|
||||
transfers?: Transfer[];
|
||||
fee: string;
|
||||
addresses?: string[];
|
||||
txHash: string;
|
||||
blobSize: number;
|
||||
timestamp: string;
|
||||
height: number;
|
||||
paymentId?: string | null;
|
||||
comment: string;
|
||||
isInitiator?: boolean;
|
||||
transfers?: Transfer[];
|
||||
fee: string;
|
||||
addresses?: string[];
|
||||
txHash: string;
|
||||
blobSize: number;
|
||||
timestamp: string;
|
||||
height: number;
|
||||
paymentId?: string | null;
|
||||
comment: string;
|
||||
isInitiator?: boolean;
|
||||
};
|
||||
|
||||
type TableRowProps = {
|
||||
label: string;
|
||||
value?: string | number;
|
||||
copyButton?: boolean;
|
||||
children?: React.ReactNode;
|
||||
label: string;
|
||||
value?: string | number;
|
||||
copyButton?: boolean;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type WhitelistedAssetType = {
|
||||
asset_id: string;
|
||||
ticker: string;
|
||||
asset_id: string;
|
||||
ticker: string;
|
||||
};
|
||||
|
||||
const TransactionDetails: React.FC<TransactionDetailsProps> = (props) => {
|
||||
const { state } = useContext(Store);
|
||||
const { copyToClipboard } = useCopy(); // removed: SuccessCopyModal
|
||||
const { state } = useContext(Store);
|
||||
const { copyToClipboard } = useCopy();
|
||||
|
||||
useEffect(() => {
|
||||
document.body.scrollTo(0, 0);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
document.body.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
const TableRow: React.FC<TableRowProps> = ({ label, value, copyButton, children }) => {
|
||||
return (
|
||||
<div className={`${copyButton && "table__row_button"} table__row`}>
|
||||
<div className="table__label">
|
||||
{label}:
|
||||
{copyButton && value && (
|
||||
<button
|
||||
className="round-button"
|
||||
onClick={() => copyToClipboard(value.toString())}
|
||||
>
|
||||
<img src={copyIcon} alt="copy icon" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="table__value">{value}</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const TableRow: React.FC<TableRowProps> = ({ label, value, copyButton, children }) => (
|
||||
<div className={`${copyButton && 'table__row_button'} table__row`}>
|
||||
<div className="table__label">
|
||||
{label}:
|
||||
{copyButton && value && (
|
||||
<button
|
||||
className="round-button"
|
||||
onClick={() => copyToClipboard(value.toString())}
|
||||
>
|
||||
<img src={copyIcon} alt="copy icon" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="table__value">{value}</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* {SuccessCopyModal} */}
|
||||
return (
|
||||
<div>
|
||||
{/* {SuccessCopyModal} */}
|
||||
|
||||
<RoutersNav title="Transaction details" />
|
||||
<RoutersNav title="Transaction details" />
|
||||
|
||||
<div className="table">
|
||||
<TableRow label="Transfers">
|
||||
<div className={styles.transaction__transfers}>
|
||||
{props?.transfers?.map((transfer, index) => {
|
||||
if (transfer.amount === props.fee) return null;
|
||||
const amount = new Big(transfer.amount);
|
||||
const fixedFee = new Big(props.fee);
|
||||
return (
|
||||
<div key={index} className={styles.transaction__transfer}>
|
||||
<p className="table__value">
|
||||
{transfer.assetId ===
|
||||
ZANO_ASSET_ID
|
||||
? !props.isInitiator
|
||||
? amount.toFixed()
|
||||
: amount.minus(fixedFee).toFixed()
|
||||
: amount.toFixed()}{" "}
|
||||
{
|
||||
(state.whitelistedAssets as any).find(
|
||||
(asset: WhitelistedAssetType) => asset.asset_id === transfer.assetId
|
||||
)?.ticker ?? "***"
|
||||
}
|
||||
</p>
|
||||
<div className="table__icon">
|
||||
<img
|
||||
src={transfer.incoming ? incomingIcon : outgoingIcon}
|
||||
alt="transfer icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TableRow>
|
||||
<TableRow label="Fee" value={props.fee + " ZANO"} />
|
||||
{props.addresses && (
|
||||
<TableRow
|
||||
label="Remote address"
|
||||
value={props.addresses[0]}
|
||||
copyButton
|
||||
/>
|
||||
)}
|
||||
<TableRow label="Transaction hash" value={props.txHash} copyButton />
|
||||
<TableRow label="Blob size" value={props.blobSize + " bytes"} />
|
||||
<TableRow label="Timestamp" value={props.timestamp} />
|
||||
<TableRow label="Height" value={props.height} />
|
||||
{props.paymentId ? (
|
||||
<TableRow label="Payment Id" value={props.paymentId} copyButton />
|
||||
) : (
|
||||
<TableRow label="Payment Id" value={props.paymentId ?? "N/A"} />
|
||||
)}
|
||||
<TableRow label="Comment" value={props.comment} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="table">
|
||||
<TableRow label="Transfers">
|
||||
<div className={styles.transaction__transfers}>
|
||||
{props?.transfers?.map((transfer, index) => {
|
||||
if (transfer.amount === props.fee) return null;
|
||||
const amount = new Big(transfer.amount);
|
||||
const fixedFee = new Big(props.fee);
|
||||
|
||||
return (
|
||||
<div key={index} className={styles.transaction__transfer}>
|
||||
<p className="table__value">
|
||||
{(() => {
|
||||
const isZano = transfer.assetId === ZANO_ASSET_ID;
|
||||
|
||||
let amountText: string;
|
||||
|
||||
if (isZano) {
|
||||
if (!props.isInitiator) {
|
||||
amountText = amount.toFixed();
|
||||
} else {
|
||||
amountText = amount.minus(fixedFee).toFixed();
|
||||
}
|
||||
} else {
|
||||
amountText = amount.toFixed();
|
||||
}
|
||||
|
||||
const ticker =
|
||||
(
|
||||
state.whitelistedAssets as unknown as WhitelistedAssetType[]
|
||||
).find(
|
||||
(asset: WhitelistedAssetType) =>
|
||||
asset.asset_id === transfer.assetId,
|
||||
)?.ticker ?? '***';
|
||||
|
||||
return (
|
||||
<p className="table__value">
|
||||
{amountText} {ticker}
|
||||
</p>
|
||||
);
|
||||
})()}
|
||||
</p>
|
||||
<div className="table__icon">
|
||||
<img
|
||||
src={transfer.incoming ? incomingIcon : outgoingIcon}
|
||||
alt="transfer icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TableRow>
|
||||
<TableRow label="Fee" value={`${props.fee} ZANO`} />
|
||||
{props.addresses && (
|
||||
<TableRow label="Remote address" value={props.addresses[0]} copyButton />
|
||||
)}
|
||||
<TableRow label="Transaction hash" value={props.txHash} copyButton />
|
||||
<TableRow label="Blob size" value={`${props.blobSize} bytes`} />
|
||||
<TableRow label="Timestamp" value={props.timestamp} />
|
||||
<TableRow label="Height" value={props.height} />
|
||||
{props.paymentId ? (
|
||||
<TableRow label="Payment Id" value={props.paymentId} copyButton />
|
||||
) : (
|
||||
<TableRow label="Payment Id" value={props.paymentId ?? 'N/A'} />
|
||||
)}
|
||||
<TableRow label="Comment" value={props.comment} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionDetails;
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
@use "sass:math";
|
||||
@import "src/app/styles/variables";
|
||||
@use 'sass:math';
|
||||
@import 'src/app/styles/variables';
|
||||
|
||||
.loaderWrapper {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: $appWidth;
|
||||
height: $appHeight;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #0C0C3A;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: $appWidth;
|
||||
height: $appHeight;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #0c0c3a;
|
||||
}
|
||||
|
||||
.loaderContent {
|
||||
margin-top: 72px;
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 175px;
|
||||
height: 60px;
|
||||
margin: 0 auto 120px auto;
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
width: 175px;
|
||||
height: 60px;
|
||||
margin: 0 auto 120px auto;
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
animation: rotate 1s infinite linear;
|
||||
width: 124px;
|
||||
margin: 0 auto;
|
||||
height: 124px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
&.small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
animation: rotate 1s infinite linear;
|
||||
width: 124px;
|
||||
margin: 0 auto;
|
||||
height: 124px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
&.small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,37 @@
|
|||
import React, { useContext } from "react";
|
||||
import loader from "../../../assets/svg/loader.svg";
|
||||
import logo from "../../../assets/svg/logo.svg";
|
||||
import { Store } from "../../../store/store-reducer";
|
||||
import s from "./AppLoader.module.scss";
|
||||
import React, { useContext } from 'react';
|
||||
import loader from '../../../assets/svg/loader.svg';
|
||||
import logo from '../../../assets/svg/logo.svg';
|
||||
import { Store } from '../../../store/store-reducer';
|
||||
import s from './AppLoader.module.scss';
|
||||
|
||||
interface AppLoaderProps {
|
||||
isSmall?: boolean;
|
||||
firstWalletLoaded: boolean;
|
||||
loggedIn: boolean;
|
||||
isSmall?: boolean;
|
||||
firstWalletLoaded: boolean;
|
||||
loggedIn: boolean;
|
||||
}
|
||||
|
||||
const AppLoader: React.FC<AppLoaderProps> = ({ isSmall, firstWalletLoaded, loggedIn }) => {
|
||||
const { state } = useContext(Store);
|
||||
const { state } = useContext(Store);
|
||||
|
||||
const loaderClasses = isSmall ? [s.loader, s.small].join(" ") : s.loader;
|
||||
const loaderClasses = isSmall ? [s.loader, s.small].join(' ') : s.loader;
|
||||
|
||||
return (
|
||||
<>
|
||||
{(state.isLoading || (state.isConnected !== false && loggedIn && !firstWalletLoaded)) && (
|
||||
<div className={s.loaderWrapper}>
|
||||
<div className={s.loaderContent}>
|
||||
<div className={s.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<div className={loaderClasses}>
|
||||
<img src={loader} alt="loader" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{(state.isLoading ||
|
||||
(state.isConnected !== false && loggedIn && !firstWalletLoaded)) && (
|
||||
<div className={s.loaderWrapper}>
|
||||
<div className={s.loaderContent}>
|
||||
<div className={s.logo}>
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
<div className={loaderClasses}>
|
||||
<img src={loader} alt="loader" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppLoader;
|
||||
|
|
|
|||
|
|
@ -1,58 +1,59 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.Button {
|
||||
width: 100%;
|
||||
height: 47px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
column-gap: 6px;
|
||||
font-size: 18px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
transition: all 0.2s ease 0s;
|
||||
img {
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
height: 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
width: 100%;
|
||||
height: 47px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
column-gap: 6px;
|
||||
font-size: 18px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
transition: all 0.2s ease 0s;
|
||||
img {
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
height: 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: #fff;
|
||||
background-color: #1F8FEB;
|
||||
@media (any-hover: hover) {
|
||||
&:hover, &:focus {
|
||||
background-color: #1a7dcc;
|
||||
}
|
||||
}
|
||||
color: #fff;
|
||||
background-color: #1f8feb;
|
||||
@media (any-hover: hover) {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: #1a7dcc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.outline {
|
||||
background: transparent;
|
||||
border: 2px solid #1F8FEB;
|
||||
@media (any-hover: hover) {
|
||||
&:hover, &:focus {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
background: transparent;
|
||||
border: 2px solid #1f8feb;
|
||||
@media (any-hover: hover) {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clear {
|
||||
min-width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
svg {
|
||||
font-size: 16px;
|
||||
flex: 0 0 16px;
|
||||
color: #fff;
|
||||
}
|
||||
min-width: auto;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
svg {
|
||||
font-size: 16px;
|
||||
flex: 0 0 16px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +1,63 @@
|
|||
import React, { memo, ButtonHTMLAttributes, AnchorHTMLAttributes } from "react";
|
||||
import cls from "./Button.module.scss";
|
||||
import { classNames } from "../../../utils/classNames";
|
||||
|
||||
interface ButtonBaseProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
theme?: keyof ThemeProps | string;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
import React, { memo, ButtonHTMLAttributes, AnchorHTMLAttributes } from 'react';
|
||||
import cls from './Button.module.scss';
|
||||
import { classNames } from '../../../utils/classNames';
|
||||
|
||||
interface ThemeProps {
|
||||
Primary: "primary";
|
||||
Outline: "outline";
|
||||
Clear: "clear";
|
||||
Primary: 'primary';
|
||||
Outline: 'outline';
|
||||
Clear: 'clear';
|
||||
}
|
||||
|
||||
interface ButtonBaseProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
theme?: keyof ThemeProps | string;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
export const ButtonThemes: ThemeProps = {
|
||||
Primary: "primary",
|
||||
Outline: "outline",
|
||||
Clear: "clear",
|
||||
Primary: 'primary',
|
||||
Outline: 'outline',
|
||||
Clear: 'clear',
|
||||
};
|
||||
|
||||
type ButtonProps = ButtonBaseProps &
|
||||
(
|
||||
| (ButtonHTMLAttributes<HTMLButtonElement> & { href?: never })
|
||||
| (AnchorHTMLAttributes<HTMLAnchorElement> & { href: string })
|
||||
);
|
||||
(
|
||||
| (ButtonHTMLAttributes<HTMLButtonElement> & { href?: never })
|
||||
| (AnchorHTMLAttributes<HTMLAnchorElement> & { href: string })
|
||||
);
|
||||
|
||||
export const Button = memo((props: ButtonProps) => {
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
theme = ButtonThemes.Primary,
|
||||
href,
|
||||
fullWidth,
|
||||
...otherProps
|
||||
} = props;
|
||||
const {
|
||||
className,
|
||||
children,
|
||||
theme = ButtonThemes.Primary,
|
||||
href,
|
||||
fullWidth,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classNames(cls.Button, {}, [className, cls[theme]])}
|
||||
{...(otherProps as AnchorHTMLAttributes<HTMLAnchorElement>)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classNames(cls.Button, {}, [className, cls[theme]])}
|
||||
{...(otherProps as AnchorHTMLAttributes<HTMLAnchorElement>)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames(cls.Button, {}, [className, cls[theme]])}
|
||||
type="button"
|
||||
{...(otherProps as ButtonHTMLAttributes<HTMLButtonElement>)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
className={classNames(cls.Button, {}, [className, cls[theme]])}
|
||||
type="button"
|
||||
{...(otherProps as ButtonHTMLAttributes<HTMLButtonElement>)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
export default Button;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import React from 'react';
|
||||
import infoIcon from "../../../assets/svg/info-blue.svg";
|
||||
import styles from "./styles.module.scss";
|
||||
import infoIcon from '../../../assets/svg/info-blue.svg';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const InfoTooltip = ({ title }: { title: string }) => {
|
||||
return (
|
||||
<button className={styles.tooltip}>
|
||||
<img src={infoIcon} width={16} alt="info" />
|
||||
const InfoTooltip = ({ title }: { title: string }) => (
|
||||
<button className={styles.tooltip}>
|
||||
<img src={infoIcon} width={16} alt="info" />
|
||||
|
||||
<div className={styles.tooltip__content}>
|
||||
{title}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<div className={styles.tooltip__content}>{title}</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
export default InfoTooltip
|
||||
export default InfoTooltip;
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
.tooltip {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.tooltip__content {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.tooltip__content {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
color: #fff;
|
||||
transition: .3s opacity ease;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 130%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #144182;
|
||||
border-radius: 5px;
|
||||
padding: 5px 12px;
|
||||
width: max-content;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
&__content {
|
||||
color: #fff;
|
||||
transition: 0.3s opacity ease;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 130%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #144182;
|
||||
border-radius: 5px;
|
||||
padding: 5px 12px;
|
||||
width: max-content;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(-45deg);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #144182;
|
||||
}
|
||||
}
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(-45deg);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #144182;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,28 @@
|
|||
.Loader {
|
||||
animation: rotate 1s infinite linear;
|
||||
width: 124px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
height: 124px;
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
&.small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
animation: rotate 1s infinite linear;
|
||||
width: 124px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
height: 124px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import React from "react";
|
||||
import loader from "../../../assets/svg/loader.svg";
|
||||
import cls from "./Loader.module.scss";
|
||||
import React from 'react';
|
||||
import loader from '../../../assets/svg/loader.svg';
|
||||
import cls from './Loader.module.scss';
|
||||
|
||||
const Loader = () => {
|
||||
return (
|
||||
<div className={cls.Loader}>
|
||||
<img src={loader} alt="loader" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const Loader = () => (
|
||||
<div className={cls.Loader}>
|
||||
<img src={loader} alt="loader" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Loader;
|
||||
export default Loader;
|
||||
|
|
|
|||
|
|
@ -1,45 +1,47 @@
|
|||
.Modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--app-width);
|
||||
height: var(--app-height);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease 0s;
|
||||
z-index: -1;
|
||||
&.opened {
|
||||
z-index: var(--modal-z-index);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--app-width);
|
||||
height: var(--app-height);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease 0s;
|
||||
z-index: -1;
|
||||
|
||||
&.opened {
|
||||
z-index: var(--modal-z-index);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--overlay-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--overlay-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
z-index: 11;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
width: 328px;
|
||||
min-height: 200px;
|
||||
background: #11316B;
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease 0s;
|
||||
.opened & {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
z-index: 11;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
width: 328px;
|
||||
min-height: 200px;
|
||||
background: #11316b;
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease 0s;
|
||||
|
||||
.opened & {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,74 @@
|
|||
import React, { MouseEvent, ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import cls from "./Modal.module.scss";
|
||||
import { createPortal } from "react-dom";
|
||||
import React, { MouseEvent, ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import cls from './Modal.module.scss';
|
||||
import { classNames } from '../../../utils/classNames';
|
||||
|
||||
interface ModalProps {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
width?: string | number;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
width?: string | number;
|
||||
}
|
||||
|
||||
const Modal = (props: ModalProps) => {
|
||||
const { className, children, isOpen, onClose, width } = props;
|
||||
const { className, children, isOpen, onClose, width } = props;
|
||||
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsMounted(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsMounted(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const closeHandler = useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
const closeHandler = useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
closeHandler();
|
||||
}
|
||||
},
|
||||
[closeHandler]
|
||||
);
|
||||
const onKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeHandler();
|
||||
}
|
||||
},
|
||||
[closeHandler],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [isOpen, onKeyDown]);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [isOpen, onKeyDown]);
|
||||
|
||||
const onContentClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
const onContentClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const mods = {
|
||||
[cls.opened]: isOpen,
|
||||
};
|
||||
const mods = {
|
||||
[cls.opened]: isOpen,
|
||||
};
|
||||
|
||||
if (!isMounted) {
|
||||
return null;
|
||||
}
|
||||
if (!isMounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div className={classNames(cls.Modal, mods, [className])}>
|
||||
<div onClick={closeHandler} className={cls.wrapper}>
|
||||
<div onClick={onContentClick} style={{ width }} className={cls.content}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
return createPortal(
|
||||
<div className={classNames(cls.Modal, mods, [className])}>
|
||||
<div onClick={closeHandler} className={cls.wrapper}>
|
||||
<div onClick={onContentClick} style={{ width }} className={cls.content}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
|
|
|||
|
|
@ -1,51 +1,51 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.myCheckbox {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.myCheckboxInput {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
&:focus + .myCheckboxLabel:before {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
&:checked + .myCheckboxLabel:after {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
&:focus + .myCheckboxLabel:before {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
&:checked + .myCheckboxLabel:after {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.myCheckboxLabel {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
color: #FFFFFF;
|
||||
&:before {
|
||||
content: "";
|
||||
align-self: flex-start;
|
||||
flex: 0 0 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
flex: 0 0 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: all 0.1s ease 0s;
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
color: #ffffff;
|
||||
&:before {
|
||||
content: '';
|
||||
align-self: flex-start;
|
||||
flex: 0 0 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
background: #ffffff;
|
||||
border-radius: 2px;
|
||||
flex: 0 0 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: all 0.1s ease 0s;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import React, { InputHTMLAttributes } from "react";
|
||||
import nextId from "react-id-generator";
|
||||
import s from "./MyCheckbox.module.scss";
|
||||
import React, { InputHTMLAttributes } from 'react';
|
||||
import nextId from 'react-id-generator';
|
||||
import s from './MyCheckbox.module.scss';
|
||||
|
||||
interface MyCheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const MyCheckbox: React.FC<MyCheckboxProps> = ({ label, ...props }) => {
|
||||
const id = nextId();
|
||||
const id = nextId();
|
||||
|
||||
return (
|
||||
<div className={s.myCheckbox}>
|
||||
<input className={s.myCheckboxInput} id={id} {...props} type="checkbox" />
|
||||
<label className={s.myCheckboxLabel} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={s.myCheckbox}>
|
||||
<input className={s.myCheckboxInput} id={id} {...props} type="checkbox" />
|
||||
<label className={s.myCheckboxLabel} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyCheckbox;
|
||||
|
|
|
|||
|
|
@ -1,52 +1,53 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1F8FEB;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1f8feb;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.myInput {
|
||||
transition: border-color 0.1s ease 0s;
|
||||
transition: border-color 0.1s ease 0s;
|
||||
|
||||
input {
|
||||
height: 41px;
|
||||
padding: 10px 12px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
font-weight: 300;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
input {
|
||||
height: 41px;
|
||||
padding: 10px 12px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
font-weight: 300;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
border-color: #16D1D6;
|
||||
}
|
||||
&.filled {
|
||||
border-color: #16d1d6;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&.error, &.customError {
|
||||
border-color: #FF6767;
|
||||
}
|
||||
}
|
||||
&.error,
|
||||
&.customError {
|
||||
border-color: #ff6767;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,76 @@
|
|||
import React, { memo, ChangeEvent, InputHTMLAttributes } from "react";
|
||||
import nextId from "react-id-generator";
|
||||
import cls from "./MyInput.module.scss";
|
||||
import { classNames } from "../../../utils/classNames";
|
||||
import React, { memo, ChangeEvent, InputHTMLAttributes } from 'react';
|
||||
import nextId from 'react-id-generator';
|
||||
import cls from './MyInput.module.scss';
|
||||
import { classNames } from '../../../utils/classNames';
|
||||
|
||||
interface inputDataProps {
|
||||
value?: string;
|
||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput?: (value: string) => void;
|
||||
inputValid?: boolean;
|
||||
onBlur?: () => void;
|
||||
isDirty?: boolean;
|
||||
isFilled?: boolean;
|
||||
export interface inputDataProps {
|
||||
value?: string;
|
||||
onChange?: (_e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput?: (_value: string) => void;
|
||||
inputValid?: boolean;
|
||||
onBlur?: () => void;
|
||||
isDirty?: boolean;
|
||||
isFilled?: boolean;
|
||||
}
|
||||
|
||||
interface MyInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
inputData?: inputDataProps;
|
||||
isValid?: boolean;
|
||||
noActiveBorder?: boolean;
|
||||
isError?: boolean;
|
||||
noValidation?: boolean;
|
||||
type?: string;
|
||||
label?: string;
|
||||
inputData?: inputDataProps;
|
||||
isValid?: boolean;
|
||||
noActiveBorder?: boolean;
|
||||
isError?: boolean;
|
||||
noValidation?: boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const MyInput: React.FC<MyInputProps> = memo((props) => {
|
||||
const id = nextId();
|
||||
const {
|
||||
label,
|
||||
inputData,
|
||||
isValid,
|
||||
noActiveBorder,
|
||||
type,
|
||||
isError,
|
||||
noValidation,
|
||||
...otherProps
|
||||
} = props;
|
||||
const id = nextId();
|
||||
const {
|
||||
label,
|
||||
inputData,
|
||||
isValid,
|
||||
noActiveBorder,
|
||||
type,
|
||||
isError,
|
||||
noValidation,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const { value, onChange, onInput, inputValid, onBlur, isDirty, isFilled } = inputData || {};
|
||||
const { value, onChange, onInput, inputValid, onBlur, isDirty, isFilled } = inputData || {};
|
||||
|
||||
const onInputHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (type === "number" && !noValidation) {
|
||||
const newValue = e.target.value
|
||||
.replace(/[^0-9.]/g, "")
|
||||
.replace(/(\..*?)\..*/g, "$1")
|
||||
.replace(/^0[^.]/, "0");
|
||||
if (onInput) onInput(newValue);
|
||||
} else {
|
||||
if (onChange) onChange(e);
|
||||
}
|
||||
};
|
||||
const onInputHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (type === 'number' && !noValidation) {
|
||||
const newValue = e.target.value
|
||||
.replace(/[^0-9.]/g, '')
|
||||
.replace(/(\..*?)\..*/g, '$1')
|
||||
.replace(/^0[^.]/, '0');
|
||||
if (onInput) onInput(newValue);
|
||||
} else if (onChange) onChange(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(cls.myInput, {})}>
|
||||
{label && (
|
||||
<label className={cls.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className={cls.myInput}>
|
||||
<input
|
||||
onBlur={onBlur}
|
||||
onChange={onInputHandler}
|
||||
type={type}
|
||||
id={id}
|
||||
value={value}
|
||||
className={classNames("", {
|
||||
[cls.filled]: isFilled && !noActiveBorder,
|
||||
[cls.error]: (isDirty && !inputValid) || isError,
|
||||
[cls.customError]: isDirty && isValid === false && inputValid
|
||||
})}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={classNames(cls.myInput, {})}>
|
||||
{label && (
|
||||
<label className={cls.label} htmlFor={id}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className={cls.myInput}>
|
||||
<input
|
||||
onBlur={onBlur}
|
||||
onChange={onInputHandler}
|
||||
type={type}
|
||||
id={id}
|
||||
value={value}
|
||||
className={classNames('', {
|
||||
[cls.filled]: isFilled && !noActiveBorder,
|
||||
[cls.error]: (isDirty && !inputValid) || isError,
|
||||
[cls.customError]: isDirty && isValid === false && inputValid,
|
||||
})}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default MyInput;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-chrome-extension-router";
|
||||
import { classNames } from "../../../utils/classNames";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react';
|
||||
import { Link } from 'react-chrome-extension-router';
|
||||
import { classNames } from '../../../utils/classNames';
|
||||
|
||||
interface NavLinkProps {
|
||||
component: React.ComponentType<any>;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
props?: any;
|
||||
component: React.ComponentType<any>;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
props?: any;
|
||||
}
|
||||
|
||||
const NavLink = ({ component, children, className, ...props }: NavLinkProps) => {
|
||||
const scrollHandler = () => {
|
||||
document.body.scrollTop = 0;
|
||||
};
|
||||
const scrollHandler = () => {
|
||||
document.body.scrollTop = 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
onClick={scrollHandler}
|
||||
component={component}
|
||||
className={classNames("", {}, [className])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
return (
|
||||
<Link
|
||||
onClick={scrollHandler}
|
||||
component={component}
|
||||
className={classNames('', {}, [className])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavLink;
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.navHeader {
|
||||
position: relative;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
position: relative;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.backBtn {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -3px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.1s ease 0s;
|
||||
&:focus {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
img {
|
||||
transform: translate(-1px, 0);
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -3px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.1s ease 0s;
|
||||
&:focus {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
img {
|
||||
transform: translate(-1px, 0);
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 30px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: math.div(26, 22);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 30px;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: math.div(26, 22);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
import React from "react";
|
||||
import { goBack } from "react-chrome-extension-router";
|
||||
import backIcon from "../../../assets/svg/arrow-back.svg";
|
||||
import s from "./RoutersNav.module.scss";
|
||||
import React from 'react';
|
||||
import { goBack } from 'react-chrome-extension-router';
|
||||
import backIcon from '../../../assets/svg/arrow-back.svg';
|
||||
import s from './RoutersNav.module.scss';
|
||||
|
||||
interface RoutersNavProps {
|
||||
title: string;
|
||||
onClick?: "none" | (() => void | undefined);
|
||||
title: string;
|
||||
onClick?: 'none' | (() => void | undefined);
|
||||
}
|
||||
|
||||
const RoutersNav = ({ title, onClick }: RoutersNavProps) => {
|
||||
const clickHandler = () => {
|
||||
if (typeof onClick === "function") {
|
||||
onClick();
|
||||
} else {
|
||||
goBack();
|
||||
document.body.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
const clickHandler = () => {
|
||||
if (typeof onClick === 'function') {
|
||||
onClick();
|
||||
} else {
|
||||
goBack();
|
||||
document.body.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s.navHeader}>
|
||||
{onClick !== "none" && (
|
||||
<button onClick={clickHandler} className={s.backBtn}>
|
||||
<img src={backIcon} alt="back button icon" />
|
||||
</button>
|
||||
)}
|
||||
<div className={s.title}>{title}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={s.navHeader}>
|
||||
{onClick !== 'none' && (
|
||||
<button onClick={clickHandler} className={s.backBtn}>
|
||||
<img src={backIcon} alt="back button icon" />
|
||||
</button>
|
||||
)}
|
||||
<div className={s.title}>{title}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoutersNav;
|
||||
|
|
|
|||
|
|
@ -1,184 +1,196 @@
|
|||
@use "sass:math";
|
||||
@import "src/app/styles/variables";
|
||||
@use 'sass:math';
|
||||
@import 'src/app/styles/variables';
|
||||
|
||||
.wallet {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 25px;
|
||||
align-items: start;
|
||||
background: radial-gradient(100% 188.88% at 0% 0%, #16d1d6 0%, #274cff 100%);
|
||||
border-radius: 16px;
|
||||
padding: 16px 16px 10px 16px;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 25px;
|
||||
align-items: start;
|
||||
background: radial-gradient(100% 188.88% at 0% 0%, #16d1d6 0%, #274cff 100%);
|
||||
border-radius: 16px;
|
||||
padding: 16px 16px 10px 16px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.actionsWallet {
|
||||
display: grid;
|
||||
row-gap: 5px;
|
||||
display: grid;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.actionsSettings {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 105%;
|
||||
right: -5px;
|
||||
background: #11316b;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 8px;
|
||||
width: 213px;
|
||||
padding: 10px 0;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 105%;
|
||||
right: -5px;
|
||||
background: #11316b;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 8px;
|
||||
width: 213px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.settingsBtn {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
height: 41px;
|
||||
padding: 0 20px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
img {
|
||||
flex: 0 0 18px;
|
||||
}
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
height: 41px;
|
||||
padding: 0 20px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
flex: 0 0 18px;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
.infoWallet {
|
||||
display: grid;
|
||||
row-gap: 11px;
|
||||
display: grid;
|
||||
row-gap: 11px;
|
||||
}
|
||||
|
||||
.balance {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
line-height: math.div(38, 32);
|
||||
.percentChange {
|
||||
color: #ffcbcb;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: math.div(21, 18);
|
||||
padding-left: 6px;
|
||||
}
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
line-height: math.div(38, 32);
|
||||
|
||||
.percentChange {
|
||||
color: #ffcbcb;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: math.div(21, 18);
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.infoAddress {
|
||||
display: flex;
|
||||
column-gap: 20px;
|
||||
align-items: center;
|
||||
max-width: 256px;
|
||||
span {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
opacity: 0.5;
|
||||
}
|
||||
display: flex;
|
||||
column-gap: 20px;
|
||||
align-items: center;
|
||||
max-width: 256px;
|
||||
|
||||
span {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
line-height: math.div(17, 14);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
.aliasContent {
|
||||
display: inline-block;
|
||||
&.active {
|
||||
position: relative;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 5px 16px 6px 16px;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: url("../../assets/svg/crown.svg") center no-repeat;
|
||||
}
|
||||
}
|
||||
display: inline-block;
|
||||
|
||||
&.active {
|
||||
position: relative;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 5px 16px 6px 16px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: url('../../assets/svg/crown.svg') center no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aliasCreateBtn {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 0 16px;
|
||||
line-height: 32px;
|
||||
transition: background-color 0.1s ease 0s;
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 0 16px;
|
||||
line-height: 32px;
|
||||
transition: background-color 0.1s ease 0s;
|
||||
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
|
||||
.balanceWrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&:hover .tooltipText {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover .tooltipText {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipText {
|
||||
visibility: hidden;
|
||||
background-color: #144182;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 125%;
|
||||
left: 50%;
|
||||
margin-left: -120px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #144182 transparent;
|
||||
}
|
||||
visibility: hidden;
|
||||
background-color: #144182;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 125%;
|
||||
left: 50%;
|
||||
margin-left: -120px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #144182 transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +1,175 @@
|
|||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import copyIcon from "../../assets/svg/copy.svg";
|
||||
import dotsIcon from "../../assets/svg/dots.svg";
|
||||
import sendIcon from "../../assets/svg/send.svg";
|
||||
import settingsIcon from "../../assets/svg/settings.svg";
|
||||
import showIcon from "../../assets/svg/show.svg";
|
||||
import hideIcon from "../../assets/svg/hide.svg";
|
||||
import lockedIcon from "../../assets/svg/lockedIcon.svg";
|
||||
import checkIcon from "../../assets/svg/check-icon.svg";
|
||||
import useAwayClick from "../../hooks/useAwayClick";
|
||||
import { useCensorDigits } from "../../hooks/useCensorDigits";
|
||||
import { useCopy } from "../../hooks/useCopy";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import { updateBalancesHidden, updateDisplay } from "../../store/actions";
|
||||
import ModalTransactionStatus from "../ModalTransactionStatus/ModalTransactionStatus";
|
||||
import WalletSend from "../WalletSend/WalletSend";
|
||||
import WalletSettings from "../WalletSettings/WalletSettings";
|
||||
import s from "./Wallet.module.scss";
|
||||
import NavLink from "../UI/NavLink/NavLink";
|
||||
import { classNames } from "../../utils/classNames";
|
||||
import { ZANO_ASSET_ID } from "../../../constants";
|
||||
import React, { Dispatch, SetStateAction, useContext, useRef, useState } from 'react';
|
||||
import copyIcon from '../../assets/svg/copy.svg';
|
||||
import dotsIcon from '../../assets/svg/dots.svg';
|
||||
import sendIcon from '../../assets/svg/send.svg';
|
||||
import settingsIcon from '../../assets/svg/settings.svg';
|
||||
import showIcon from '../../assets/svg/show.svg';
|
||||
import hideIcon from '../../assets/svg/hide.svg';
|
||||
import lockedIcon from '../../assets/svg/lockedIcon.svg';
|
||||
import checkIcon from '../../assets/svg/check-icon.svg';
|
||||
import useAwayClick from '../../hooks/useAwayClick';
|
||||
import { useCensorDigits } from '../../hooks/useCensorDigits';
|
||||
import { useCopy } from '../../hooks/useCopy';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import { DispatchFunction, updateBalancesHidden, updateDisplay } from '../../store/actions';
|
||||
import ModalTransactionStatus from '../ModalTransactionStatus/ModalTransactionStatus';
|
||||
import WalletSend from '../WalletSend/WalletSend';
|
||||
import s from './Wallet.module.scss';
|
||||
import NavLink from '../UI/NavLink/NavLink';
|
||||
import { classNames } from '../../utils/classNames';
|
||||
import { ZANO_ASSET_ID } from '../../../constants';
|
||||
|
||||
const Wallet = ({ setConnectOpened }: { setConnectOpened: Dispatch<SetStateAction<boolean>> }) => {
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { copied, copyToClipboard } = useCopy();
|
||||
const { censorValue } = useCensorDigits();
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { copied, copyToClipboard } = useCopy();
|
||||
const { censorValue } = useCensorDigits();
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
|
||||
const renderBalance = () => {
|
||||
const fiatBalance = (
|
||||
Number(state.wallet.balance) * state.priceData.price
|
||||
).toFixed(2);
|
||||
const renderBalance = () => {
|
||||
const fiatBalance = (Number(state.wallet.balance) * state.priceData.price).toFixed(2);
|
||||
|
||||
if (state.displayUsd) {
|
||||
return (
|
||||
<>
|
||||
<span>${censorValue(fiatBalance)}</span>
|
||||
<span
|
||||
style={{
|
||||
color: state.priceData.change > 0 ? "#16D1D6" : "#FFCBCB",
|
||||
}}
|
||||
className={s.percentChange}
|
||||
>
|
||||
{state.priceData.change}%
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span>{censorValue(Number(state.wallet.balance).toFixed(2))} ZANO</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
if (state.displayUsd) {
|
||||
return (
|
||||
<>
|
||||
<span>${censorValue(fiatBalance)}</span>
|
||||
<span
|
||||
style={{
|
||||
color: state.priceData.change > 0 ? '#16D1D6' : '#FFCBCB',
|
||||
}}
|
||||
className={s.percentChange}
|
||||
>
|
||||
{state.priceData.change}%
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <span>{censorValue(Number(state.wallet.balance).toFixed(2))} ZANO</span>;
|
||||
};
|
||||
|
||||
const getUnlockedBalance = () =>
|
||||
state.wallet.assets.find(
|
||||
(asset) =>
|
||||
asset.assetId ===
|
||||
ZANO_ASSET_ID
|
||||
)?.unlockedBalance;
|
||||
const getUnlockedBalance = () =>
|
||||
state.wallet.assets.find((asset) => asset.assetId === ZANO_ASSET_ID)?.unlockedBalance;
|
||||
|
||||
const flipDisplay = () => {
|
||||
updateDisplay(dispatch as any, !state.displayUsd as any);
|
||||
};
|
||||
const flipDisplay = () => {
|
||||
updateDisplay(dispatch as DispatchFunction, !state.displayUsd as never);
|
||||
};
|
||||
|
||||
const flipMenu = () => {
|
||||
setMenuVisible((prevState) => !prevState);
|
||||
};
|
||||
const flipMenu = () => {
|
||||
setMenuVisible((prevState) => !prevState);
|
||||
};
|
||||
|
||||
const createAliasHandler = () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.tabs.create({
|
||||
url: "https://docs.zano.org/docs/aliases",
|
||||
});
|
||||
};
|
||||
const createAliasHandler = () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.tabs.create({
|
||||
url: 'https://docs.zano.org/docs/aliases',
|
||||
});
|
||||
};
|
||||
|
||||
const flipBalancesVisibility = () => {
|
||||
if (state.isBalancesHidden) {
|
||||
updateBalancesHidden(dispatch, false);
|
||||
} else {
|
||||
updateBalancesHidden(dispatch, true);
|
||||
}
|
||||
flipMenu();
|
||||
};
|
||||
const flipBalancesVisibility = () => {
|
||||
if (state.isBalancesHidden) {
|
||||
updateBalancesHidden(dispatch, false);
|
||||
} else {
|
||||
updateBalancesHidden(dispatch, true);
|
||||
}
|
||||
flipMenu();
|
||||
};
|
||||
|
||||
// Function and hook to close menu if click away
|
||||
const menuRef = useRef(null);
|
||||
const handleAwayClick = () => {
|
||||
setMenuVisible(false);
|
||||
};
|
||||
useAwayClick(menuRef, handleAwayClick);
|
||||
// Function and hook to close menu if click away
|
||||
const menuRef = useRef(null);
|
||||
const handleAwayClick = () => {
|
||||
setMenuVisible(false);
|
||||
};
|
||||
useAwayClick(menuRef, handleAwayClick);
|
||||
|
||||
return (
|
||||
<div className={s.wallet}>
|
||||
<ModalTransactionStatus />
|
||||
<div className={s.infoWallet}>
|
||||
<div>
|
||||
<div
|
||||
className={classNames(s.aliasContent, {
|
||||
[s.active]: state.wallet.alias,
|
||||
})}
|
||||
>
|
||||
{state.wallet.alias ? (
|
||||
`@${state.wallet.alias}`
|
||||
) : (
|
||||
<button className={s.aliasCreateBtn} onClick={createAliasHandler}>
|
||||
Create alias
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.balanceWrapper}>
|
||||
<button onClick={flipDisplay} className={s.balance}>
|
||||
{state.displayUsd ||
|
||||
getUnlockedBalance() === state.wallet.balance || (
|
||||
<>
|
||||
<img src={lockedIcon} alt="locked icon" />
|
||||
</>
|
||||
)}
|
||||
{renderBalance()}
|
||||
</button>
|
||||
{getUnlockedBalance() !== state.wallet.balance && (
|
||||
<span className={s.tooltipText}>
|
||||
Locked balance:{" "}
|
||||
{(Number(state.wallet.balance) - Number(getUnlockedBalance())).toFixed(2)}{" "}
|
||||
ZANO
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className={s.wallet}>
|
||||
<ModalTransactionStatus />
|
||||
<div className={s.infoWallet}>
|
||||
<div>
|
||||
<div
|
||||
className={classNames(s.aliasContent, {
|
||||
[s.active]: state.wallet.alias,
|
||||
})}
|
||||
>
|
||||
{state.wallet.alias ? (
|
||||
`@${state.wallet.alias}`
|
||||
) : (
|
||||
<button className={s.aliasCreateBtn} onClick={createAliasHandler}>
|
||||
Create alias
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.balanceWrapper}>
|
||||
<button onClick={flipDisplay} className={s.balance}>
|
||||
{state.displayUsd || getUnlockedBalance() === state.wallet.balance || (
|
||||
<>
|
||||
<img src={lockedIcon} alt="locked icon" />
|
||||
</>
|
||||
)}
|
||||
{renderBalance()}
|
||||
</button>
|
||||
{getUnlockedBalance() !== state.wallet.balance && (
|
||||
<span className={s.tooltipText}>
|
||||
Locked balance:{' '}
|
||||
{(Number(state.wallet.balance) - Number(getUnlockedBalance())).toFixed(
|
||||
2,
|
||||
)}{' '}
|
||||
ZANO
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={s.infoAddress}>
|
||||
<span>{state.wallet.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.infoAddress}>
|
||||
<span>{state.wallet.address}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.actionsWallet}>
|
||||
<div ref={menuRef} className={s.actionsSettings}>
|
||||
<button onClick={flipMenu} className="round-button">
|
||||
<img src={dotsIcon} alt="dots icon" />
|
||||
{/* Tooltip */}
|
||||
<span>options</span>
|
||||
</button>
|
||||
<div className={s.actionsWallet}>
|
||||
<div ref={menuRef} className={s.actionsSettings}>
|
||||
<button onClick={flipMenu} className="round-button">
|
||||
<img src={dotsIcon} alt="dots icon" />
|
||||
{/* Tooltip */}
|
||||
<span>options</span>
|
||||
</button>
|
||||
|
||||
{menuVisible && (
|
||||
<div className={s.settings}>
|
||||
<div className={s.settingsBtn} onClick={() => setConnectOpened(true)}>
|
||||
<img src={settingsIcon} alt="settings icon" />
|
||||
Settings
|
||||
</div>
|
||||
<button
|
||||
onClick={flipBalancesVisibility}
|
||||
className={s.settingsBtn}
|
||||
>
|
||||
<img
|
||||
src={state.isBalancesHidden ? showIcon : hideIcon}
|
||||
alt="show or hide icon"
|
||||
/>
|
||||
{state.isBalancesHidden ? "Show values" : "Hide values"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{menuVisible && (
|
||||
<div className={s.settings}>
|
||||
<div className={s.settingsBtn} onClick={() => setConnectOpened(true)}>
|
||||
<img src={settingsIcon} alt="settings icon" />
|
||||
Settings
|
||||
</div>
|
||||
<button onClick={flipBalancesVisibility} className={s.settingsBtn}>
|
||||
<img
|
||||
src={state.isBalancesHidden ? showIcon : hideIcon}
|
||||
alt="show or hide icon"
|
||||
/>
|
||||
{state.isBalancesHidden ? 'Show values' : 'Hide values'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<NavLink component={WalletSend} className="round-button">
|
||||
<img src={sendIcon} alt="send icon" />
|
||||
{/* Tooltip */}
|
||||
<span>send</span>
|
||||
</NavLink>
|
||||
<NavLink component={WalletSend} className="round-button">
|
||||
<img src={sendIcon} alt="send icon" />
|
||||
{/* Tooltip */}
|
||||
<span>send</span>
|
||||
</NavLink>
|
||||
|
||||
<button
|
||||
onClick={() => copyToClipboard(state.wallet.address)}
|
||||
className="round-button"
|
||||
>
|
||||
{copied
|
||||
? <img src={checkIcon} alt="copy icon" />
|
||||
: <img src={copyIcon} alt="copy icon" />
|
||||
}
|
||||
{/* Tooltip */}
|
||||
{copied
|
||||
? <span>copied!</span>
|
||||
: <span>copy address</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<button
|
||||
onClick={() => copyToClipboard(state.wallet.address)}
|
||||
className="round-button"
|
||||
>
|
||||
{copied ? (
|
||||
<img src={checkIcon} alt="copy icon" />
|
||||
) : (
|
||||
<img src={copyIcon} alt="copy icon" />
|
||||
)}
|
||||
{/* Tooltip */}
|
||||
{copied ? <span>copied!</span> : <span>copy address</span>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wallet;
|
||||
|
|
|
|||
|
|
@ -1,57 +1,61 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.link {
|
||||
color: #1F8FEB;
|
||||
color: #1f8feb;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1F8FEB;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1f8feb;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.sendForm {
|
||||
display: grid;
|
||||
row-gap: 15px;
|
||||
padding-bottom: 20px;
|
||||
display: grid;
|
||||
row-gap: 15px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
.transactionContent {
|
||||
min-height: 410px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(0, -30px);
|
||||
min-height: 410px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(0, -30px);
|
||||
}
|
||||
|
||||
.transactionIcon {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
margin: 0 auto 8px auto;
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
margin: 0 auto 8px auto;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.transactionText {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
font-size: 18px;
|
||||
margin: 0 15px;
|
||||
line-height: 1.4;
|
||||
span {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
font-size: 18px;
|
||||
margin: 0 15px;
|
||||
line-height: 1.4;
|
||||
|
||||
span {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,285 +1,271 @@
|
|||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { popToTop } from "react-chrome-extension-router";
|
||||
import failedImage from "../../assets/images/failed-round.png";
|
||||
import successImage from "../../assets/images/success-round.png";
|
||||
import { useCheckbox } from "../../hooks/useCheckbox";
|
||||
import { useInput } from "../../hooks/useInput";
|
||||
import { Store } from "../../store/store-reducer";
|
||||
import Button from "../UI/Button/Button";
|
||||
import MyInput from "../UI/MyInput/MyInput";
|
||||
import RoutersNav from "../UI/RoutersNav/RoutersNav";
|
||||
import s from "./WalletSend.module.scss";
|
||||
import { fetchBackground, validateTokensInput } from "../../utils/utils";
|
||||
// import { getAliasDetails } from "../../../background/wallet";
|
||||
import AssetsSelect from "./ui/AssetsSelect/AssetsSelect";
|
||||
import AdditionalDetails from "./ui/AdditionalDetails/AdditionalDetails";
|
||||
|
||||
interface ResponseData {
|
||||
data?: any;
|
||||
error?: string;
|
||||
}
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { popToTop } from 'react-chrome-extension-router';
|
||||
import failedImage from '../../assets/images/failed-round.png';
|
||||
import successImage from '../../assets/images/success-round.png';
|
||||
import { useCheckbox } from '../../hooks/useCheckbox';
|
||||
import { useInput } from '../../hooks/useInput';
|
||||
import { Store } from '../../store/store-reducer';
|
||||
import Button from '../UI/Button/Button';
|
||||
import MyInput, { inputDataProps } from '../UI/MyInput/MyInput';
|
||||
import RoutersNav from '../UI/RoutersNav/RoutersNav';
|
||||
import s from './WalletSend.module.scss';
|
||||
import { fetchBackground, validateTokensInput } from '../../utils/utils';
|
||||
import AssetsSelect from './ui/AssetsSelect/AssetsSelect';
|
||||
import AdditionalDetails from './ui/AdditionalDetails/AdditionalDetails';
|
||||
|
||||
interface FetchBackgroundParams {
|
||||
method: string;
|
||||
assetId: string;
|
||||
destination: string;
|
||||
amount: string | number;
|
||||
comment: string;
|
||||
decimalPoint: number;
|
||||
password?: string;
|
||||
id?: number;
|
||||
success?: boolean;
|
||||
credentials?: {
|
||||
port: string;
|
||||
};
|
||||
method: string;
|
||||
assetId: string;
|
||||
destination: string;
|
||||
amount: string | number;
|
||||
comment: string;
|
||||
decimalPoint: number;
|
||||
password?: string;
|
||||
id?: number;
|
||||
success?: boolean;
|
||||
credentials?: {
|
||||
port: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AssetProps {
|
||||
unlockedBalance: number;
|
||||
balance: number;
|
||||
unlockedBalance: number;
|
||||
balance: number;
|
||||
}
|
||||
|
||||
const WalletSend = () => {
|
||||
const { state } = useContext(Store);
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [transactionSuccess, setTransactionSuccess] = useState(false);
|
||||
const [txId, setTxId] = useState("");
|
||||
const { state } = useContext(Store);
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [transactionSuccess, setTransactionSuccess] = useState(false);
|
||||
const [txId, setTxId] = useState('');
|
||||
|
||||
// Form data
|
||||
const [asset, setAsset] = useState(state.wallet.assets[0]);
|
||||
const [submitAddress, setSubmitAddress] = useState("");
|
||||
const [amountValid, setAmountValid] = useState(false);
|
||||
// Form data
|
||||
const [asset, setAsset] = useState(state.wallet.assets[0]);
|
||||
const [submitAddress, setSubmitAddress] = useState('');
|
||||
const [amountValid, setAmountValid] = useState(false);
|
||||
|
||||
const address = useInput("", { customValidation: true });
|
||||
const amount = useInput("", {
|
||||
isEmpty: true,
|
||||
isAmountCorrect: true,
|
||||
});
|
||||
const comment = useInput("", { isEmpty: true });
|
||||
const mixin = useInput(10, { isEmpty: true });
|
||||
const fee = useInput(0.01, { isEmpty: true });
|
||||
const isSenderInfo = useCheckbox(false);
|
||||
const isReceiverInfo = useCheckbox(false);
|
||||
const address = useInput('', { customValidation: true });
|
||||
const amount = useInput('', {
|
||||
isEmpty: true,
|
||||
isAmountCorrect: true,
|
||||
});
|
||||
const comment = useInput('', { isEmpty: true });
|
||||
const mixin = useInput(10, { isEmpty: true });
|
||||
const fee = useInput(0.01, { isEmpty: true });
|
||||
const isSenderInfo = useCheckbox(false);
|
||||
const isReceiverInfo = useCheckbox(false);
|
||||
|
||||
const sendTransfer = (
|
||||
destination: string,
|
||||
amount: string | number,
|
||||
comment: string,
|
||||
assetId: string,
|
||||
decimalPoint: number
|
||||
): Promise<any> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (chrome.runtime.sendMessage as any) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const response: ResponseData = await fetchBackground({
|
||||
method: "SEND_TRANSFER",
|
||||
assetId,
|
||||
destination,
|
||||
amount,
|
||||
comment,
|
||||
decimalPoint,
|
||||
} as FetchBackgroundParams);
|
||||
const sendTransfer = (
|
||||
destination: string,
|
||||
amount: string | number,
|
||||
comment: string,
|
||||
assetId: string,
|
||||
decimalPoint: number,
|
||||
) =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (chrome.runtime.sendMessage as any) {
|
||||
const response = await fetchBackground({
|
||||
method: 'SEND_TRANSFER',
|
||||
assetId,
|
||||
destination,
|
||||
amount,
|
||||
comment,
|
||||
decimalPoint,
|
||||
} as FetchBackgroundParams);
|
||||
|
||||
if (response.data) {
|
||||
resolve(response.data);
|
||||
} else if (response.error) {
|
||||
reject(response.error);
|
||||
} else {
|
||||
reject("No data or error received in response.");
|
||||
}
|
||||
} else {
|
||||
reject("chrome.runtime.sendMessage is not available.");
|
||||
}
|
||||
});
|
||||
};
|
||||
if (response.data) {
|
||||
resolve(response.data);
|
||||
} else if (response.error) {
|
||||
reject(response.error);
|
||||
} else {
|
||||
reject(new Error('No data or error received in response.'));
|
||||
}
|
||||
} else {
|
||||
reject(new Error('chrome.runtime.sendMessage is not available.'));
|
||||
}
|
||||
});
|
||||
|
||||
const openExplorer = (txId: string) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.tabs.create({
|
||||
url: `https://testnet-explorer.zano.org/block/${txId}`,
|
||||
});
|
||||
};
|
||||
const openExplorer = (txId: string) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
chrome.tabs.create({
|
||||
url: `https://testnet-explorer.zano.org/block/${txId}`,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (String(address.value).startsWith("@")) {
|
||||
const alias = String(address.value).slice(1);
|
||||
const resolvedAddress = await fetchAddress(alias);
|
||||
if (resolvedAddress) {
|
||||
setSubmitAddress(resolvedAddress);
|
||||
} else {
|
||||
setSubmitAddress("");
|
||||
}
|
||||
} else {
|
||||
if (String(address.value).length === 97) {
|
||||
setSubmitAddress(String(address.value));
|
||||
} else {
|
||||
setSubmitAddress("");
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [address.value]);
|
||||
const fetchAddress = async (alias: string) =>
|
||||
fetchBackground({ method: 'GET_ALIAS_DETAILS', alias });
|
||||
|
||||
useEffect(() => {
|
||||
const isValid = !!validateTokensInput(amount.value, Number(asset.decimalPoint));
|
||||
setAmountValid(isValid);
|
||||
}, [amount.value, asset]);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (String(address.value).startsWith('@')) {
|
||||
const alias = String(address.value).slice(1);
|
||||
const resolvedAddress = await fetchAddress(alias);
|
||||
if (resolvedAddress) {
|
||||
setSubmitAddress(resolvedAddress);
|
||||
} else {
|
||||
setSubmitAddress('');
|
||||
}
|
||||
} else if (String(address.value).length === 97) {
|
||||
setSubmitAddress(String(address.value));
|
||||
} else {
|
||||
setSubmitAddress('');
|
||||
}
|
||||
})();
|
||||
}, [address.value]);
|
||||
|
||||
const fetchAddress = async (alias: string) => await fetchBackground({ method: "GET_ALIAS_DETAILS", alias });
|
||||
useEffect(() => {
|
||||
const isValid = !!validateTokensInput(amount.value, Number(asset.decimalPoint));
|
||||
setAmountValid(isValid);
|
||||
}, [amount.value, asset]);
|
||||
|
||||
const checkAvailableBalance = (amount: string | number, asset: AssetProps) =>
|
||||
asset.unlockedBalance !== asset.balance
|
||||
? +amount <= asset.unlockedBalance - Number(fee.value)
|
||||
: true;
|
||||
const checkAvailableBalance = (amount: string | number, asset: AssetProps) =>
|
||||
asset.unlockedBalance !== asset.balance
|
||||
? +amount <= asset.unlockedBalance - Number(fee.value)
|
||||
: true;
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
// Subcomponents
|
||||
const TableRow = ({ label, value }: { label: string; value: string }) => {
|
||||
return (
|
||||
<div className="table__row">
|
||||
<div className="table__label">{label}:</div>
|
||||
<div className="table__value">{value}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
// Subcomponents
|
||||
const TableRow = ({ label, value }: { label: string; value: string }) => (
|
||||
<div className="table__row">
|
||||
<div className="table__label">{label}:</div>
|
||||
<div className="table__value">{value}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(() => {
|
||||
console.log('activeStep', activeStep);
|
||||
console.log('address', address);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(() => {
|
||||
switch (activeStep) {
|
||||
// Send form
|
||||
case 0:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav title="Send" />
|
||||
<div className={s.sendForm}>
|
||||
<MyInput
|
||||
placeholder="Address or alias"
|
||||
label="Address"
|
||||
inputData={address as inputDataProps}
|
||||
isValid={!!submitAddress}
|
||||
/>
|
||||
<AssetsSelect value={asset} setValue={setAsset} />
|
||||
<MyInput
|
||||
type="number"
|
||||
placeholder="Amount to transfer"
|
||||
label="Amount:"
|
||||
inputData={amount as inputDataProps}
|
||||
isError={amount.value ? !amountValid : false}
|
||||
/>
|
||||
<MyInput
|
||||
placeholder="Enter the comment"
|
||||
label="Comment:"
|
||||
inputData={comment as inputDataProps}
|
||||
/>
|
||||
<AdditionalDetails
|
||||
mixin={mixin}
|
||||
fee={fee}
|
||||
isSenderInfo={isSenderInfo}
|
||||
isReceiverInfo={isReceiverInfo}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setActiveStep(1)}
|
||||
disabled={
|
||||
!submitAddress ||
|
||||
!amount.value ||
|
||||
!amountValid ||
|
||||
!checkAvailableBalance(amount.value, asset)
|
||||
}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Confirm
|
||||
case 1:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav onClick={() => setActiveStep(0)} title="Confirm" />
|
||||
|
||||
console.log("activeStep", activeStep);
|
||||
console.log("address", address);
|
||||
<div style={{ minHeight: '410px' }} className="table">
|
||||
<TableRow
|
||||
label="Amount"
|
||||
value={`${amount?.value} ${asset?.ticker}`}
|
||||
/>
|
||||
<TableRow label="From" value={state?.wallet?.address} />
|
||||
<TableRow label="To" value={String(address.value)} />
|
||||
<TableRow label="Comment" value={String(comment?.value)} />
|
||||
<TableRow label="Fee" value={String(fee?.value)} />
|
||||
</div>
|
||||
|
||||
switch (activeStep) {
|
||||
// Send form
|
||||
case 0:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav title="Send" />
|
||||
<div className={s.sendForm}>
|
||||
<MyInput
|
||||
placeholder="Address or alias"
|
||||
label="Address"
|
||||
inputData={address as any}
|
||||
isValid={!!submitAddress}
|
||||
/>
|
||||
<AssetsSelect value={asset} setValue={setAsset as React.Dispatch<React.SetStateAction<any>>} />
|
||||
<MyInput
|
||||
type="number"
|
||||
placeholder="Amount to transfer"
|
||||
label="Amount:"
|
||||
inputData={amount as any}
|
||||
isError={amount.value ? !amountValid : false}
|
||||
/>
|
||||
<MyInput
|
||||
placeholder="Enter the comment"
|
||||
label="Comment:"
|
||||
inputData={comment as any}
|
||||
/>
|
||||
<AdditionalDetails
|
||||
mixin={mixin}
|
||||
fee={fee}
|
||||
isSenderInfo={isSenderInfo}
|
||||
isReceiverInfo={isReceiverInfo}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setActiveStep(1)}
|
||||
disabled={
|
||||
!submitAddress ||
|
||||
!amount.value ||
|
||||
!amountValid ||
|
||||
!checkAvailableBalance(amount.value, asset as any)
|
||||
}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Confirm
|
||||
case 1:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav onClick={() => setActiveStep(0)} title="Confirm" />
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const transferStatus = await sendTransfer(
|
||||
submitAddress,
|
||||
amount.value,
|
||||
String(comment.value),
|
||||
String(asset.assetId),
|
||||
Number(asset.decimalPoint),
|
||||
);
|
||||
console.log('transfer status', transferStatus);
|
||||
if (transferStatus.result) {
|
||||
setTxId(transferStatus.result.tx_hash);
|
||||
setTransactionSuccess(true);
|
||||
}
|
||||
setActiveStep(2);
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
// Transaction status
|
||||
case 2:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav
|
||||
onClick={transactionSuccess ? 'none' : () => setActiveStep(1)}
|
||||
title="Transaction"
|
||||
/>
|
||||
|
||||
<div style={{ minHeight: "410px" }} className="table">
|
||||
<TableRow
|
||||
label="Amount"
|
||||
value={amount?.value + " " + asset?.ticker}
|
||||
/>
|
||||
<TableRow label="From" value={state?.wallet?.address} />
|
||||
<TableRow label="To" value={String(address.value)} />
|
||||
<TableRow label="Comment" value={String(comment?.value)} />
|
||||
<TableRow label="Fee" value={String(fee?.value)} />
|
||||
</div>
|
||||
<div className={s.transactionContent}>
|
||||
<div className={s.transactionIcon}>
|
||||
<img
|
||||
src={transactionSuccess ? successImage : failedImage}
|
||||
alt="transaction status icon"
|
||||
/>
|
||||
</div>
|
||||
<div className={s.transactionText}>
|
||||
{transactionSuccess ? (
|
||||
<div>
|
||||
Sent in <span>{txId}</span>
|
||||
</div>
|
||||
) : (
|
||||
'Sending failed'
|
||||
)}
|
||||
</div>
|
||||
{transactionSuccess && (
|
||||
<button
|
||||
className={s.link}
|
||||
onClick={() => openExplorer(txId)}
|
||||
>
|
||||
See details
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const transferStatus = await sendTransfer(
|
||||
submitAddress,
|
||||
amount.value,
|
||||
String(comment.value),
|
||||
String(asset.assetId),
|
||||
Number(asset.decimalPoint)
|
||||
);
|
||||
console.log("transfer status", transferStatus);
|
||||
if (transferStatus.result) {
|
||||
setTxId(transferStatus.result.tx_hash);
|
||||
setTransactionSuccess(true);
|
||||
}
|
||||
setActiveStep(2);
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
// Transaction status
|
||||
case 2:
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav
|
||||
onClick={transactionSuccess ? "none" : () => setActiveStep(1)}
|
||||
title="Transaction"
|
||||
/>
|
||||
|
||||
<div className={s.transactionContent}>
|
||||
<div className={s.transactionIcon}>
|
||||
<img
|
||||
src={transactionSuccess ? successImage : failedImage}
|
||||
alt="transaction status icon"
|
||||
/>
|
||||
</div>
|
||||
<div className={s.transactionText}>
|
||||
{transactionSuccess ? (
|
||||
<div>
|
||||
Sent in <span>{txId}</span>
|
||||
</div>
|
||||
) : (
|
||||
"Sending failed"
|
||||
)}
|
||||
</div>
|
||||
{transactionSuccess && (
|
||||
<button
|
||||
className={s.link}
|
||||
onClick={() => openExplorer(txId)}
|
||||
>
|
||||
See details
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button onClick={() => popToTop()}>Close</Button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
})()}
|
||||
</>
|
||||
);
|
||||
<Button onClick={() => popToTop()}>Close</Button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
})()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletSend;
|
||||
|
|
|
|||
|
|
@ -1,57 +1,56 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.detailsSelect {
|
||||
margin: 5px 0;
|
||||
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.detailsSelectValue {
|
||||
background: #11316B;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
justify-content: space-between;
|
||||
height: 41px;
|
||||
width: 100%;
|
||||
border: 2px solid transparent;
|
||||
img{
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
background: #11316b;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
justify-content: space-between;
|
||||
height: 41px;
|
||||
width: 100%;
|
||||
border: 2px solid transparent;
|
||||
img {
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
transition: border-color 0.2s ease 0s;
|
||||
transition: border-color 0.2s ease 0s;
|
||||
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-radius: 8px 8px 0 0;
|
||||
&.active {
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
||||
img {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
img {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailsSelectContent {
|
||||
background: #11316B;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 5px 12px 18px 12px;
|
||||
background: #11316b;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 5px 12px 18px 12px;
|
||||
}
|
||||
|
||||
.detailsSelectInputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detailsSelectCheckboxes {
|
||||
display: grid;
|
||||
row-gap: 16px;
|
||||
}
|
||||
display: grid;
|
||||
row-gap: 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,90 @@
|
|||
import React, { useState } from "react";
|
||||
import MyCheckbox from "../../../UI/MyCheckbox/MyCheckbox";
|
||||
import MyInput from "../../../UI/MyInput/MyInput";
|
||||
import s from "./AdditionalDetails.module.scss";
|
||||
import { classNames } from "../../../../utils/classNames";
|
||||
import arrowIcon from "../../../../assets/svg/arrow-select.svg";
|
||||
import React, { useState } from 'react';
|
||||
import MyCheckbox from '../../../UI/MyCheckbox/MyCheckbox';
|
||||
import MyInput, { inputDataProps } from '../../../UI/MyInput/MyInput';
|
||||
import s from './AdditionalDetails.module.scss';
|
||||
import { classNames } from '../../../../utils/classNames';
|
||||
import arrowIcon from '../../../../assets/svg/arrow-select.svg';
|
||||
|
||||
interface mixinType {
|
||||
isEmpty: boolean;
|
||||
minLengthError: boolean;
|
||||
amountCorrectError: boolean;
|
||||
inputValid: boolean;
|
||||
value: string | number;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput: (newValue: string | number) => void;
|
||||
onBlur: () => void;
|
||||
isFilled: boolean;
|
||||
isDirty: boolean;
|
||||
isEmpty: boolean;
|
||||
minLengthError: boolean;
|
||||
amountCorrectError: boolean;
|
||||
inputValid: boolean;
|
||||
value: string | number;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput: (newValue: string | number) => void;
|
||||
onBlur: () => void;
|
||||
isFilled: boolean;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
interface feeType {
|
||||
isEmpty: boolean;
|
||||
minLengthError: boolean;
|
||||
amountCorrectError: boolean;
|
||||
inputValid: boolean;
|
||||
value: string | number;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput: (newValue: string | number) => void;
|
||||
onBlur: () => void;
|
||||
isFilled: boolean;
|
||||
isDirty: boolean;
|
||||
isEmpty: boolean;
|
||||
minLengthError: boolean;
|
||||
amountCorrectError: boolean;
|
||||
inputValid: boolean;
|
||||
value: string | number;
|
||||
onChange: (_e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onInput: (_newValue: string | number) => void;
|
||||
onBlur: () => void;
|
||||
isFilled: boolean;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
interface AdditionalDetailsProps {
|
||||
fee: string | number | feeType;
|
||||
mixin: string | number | mixinType;
|
||||
isSenderInfo: { isChecked: boolean; onChange: () => void };
|
||||
isReceiverInfo: { isChecked: boolean; onChange: () => void };
|
||||
fee: string | number | feeType;
|
||||
mixin: string | number | mixinType;
|
||||
isSenderInfo: { isChecked: boolean; onChange: () => void };
|
||||
isReceiverInfo: { isChecked: boolean; onChange: () => void };
|
||||
}
|
||||
const AdditionalDetails = ({ fee, mixin, isSenderInfo, isReceiverInfo }: AdditionalDetailsProps) => {
|
||||
const [detailsVisible, setDetailsVisible] = useState(false);
|
||||
const AdditionalDetails = ({
|
||||
fee,
|
||||
mixin,
|
||||
isSenderInfo,
|
||||
isReceiverInfo,
|
||||
}: AdditionalDetailsProps) => {
|
||||
const [detailsVisible, setDetailsVisible] = useState(false);
|
||||
|
||||
const toggleDetails = () => {
|
||||
setDetailsVisible((prevState) => !prevState);
|
||||
};
|
||||
const toggleDetails = () => {
|
||||
setDetailsVisible((prevState) => !prevState);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s.detailsSelect}>
|
||||
<button
|
||||
onClick={toggleDetails}
|
||||
className={classNames(s.detailsSelectValue, {
|
||||
[s.active]: detailsVisible,
|
||||
})}
|
||||
>
|
||||
Additional details
|
||||
<img src={arrowIcon} alt="arrow" />
|
||||
</button>
|
||||
return (
|
||||
<div className={s.detailsSelect}>
|
||||
<button
|
||||
onClick={toggleDetails}
|
||||
className={classNames(s.detailsSelectValue, {
|
||||
[s.active]: detailsVisible,
|
||||
})}
|
||||
>
|
||||
Additional details
|
||||
<img src={arrowIcon} alt="arrow" />
|
||||
</button>
|
||||
|
||||
{detailsVisible && (
|
||||
<div className={s.detailsSelectContent}>
|
||||
<div className={s.detailsSelectInputs}>
|
||||
<MyInput label="Mixin:" inputData={mixin as any} disabled />
|
||||
<MyInput label="Fee:" inputData={fee as any} disabled />
|
||||
</div>
|
||||
<div className={s.detailsSelectCheckboxes}>
|
||||
<MyCheckbox
|
||||
label="Include sender info"
|
||||
checked={isSenderInfo.isChecked}
|
||||
onChange={isSenderInfo.onChange}
|
||||
/>
|
||||
<MyCheckbox
|
||||
label="Include receiver info"
|
||||
checked={isReceiverInfo.isChecked}
|
||||
onChange={isReceiverInfo.onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{detailsVisible && (
|
||||
<div className={s.detailsSelectContent}>
|
||||
<div className={s.detailsSelectInputs}>
|
||||
<MyInput label="Mixin:" inputData={mixin as inputDataProps} disabled />
|
||||
<MyInput label="Fee:" inputData={fee as inputDataProps} disabled />
|
||||
</div>
|
||||
|
||||
<div className={s.detailsSelectCheckboxes}>
|
||||
<MyCheckbox
|
||||
label="Include sender info"
|
||||
checked={isSenderInfo.isChecked}
|
||||
onChange={isSenderInfo.onChange}
|
||||
/>
|
||||
|
||||
<MyCheckbox
|
||||
label="Include receiver info"
|
||||
checked={isReceiverInfo.isChecked}
|
||||
onChange={isReceiverInfo.onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdditionalDetails;
|
||||
|
|
|
|||
|
|
@ -1,152 +1,152 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.select {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selectValue {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
height: 41px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
background: #11316B;
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
justify-content: space-between;
|
||||
text-transform: capitalize;
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.valueArrow {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
}
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
span {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
&.active {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 8px 8px 0 0;
|
||||
.valueArrow {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
height: 41px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
background: #11316b;
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
justify-content: space-between;
|
||||
text-transform: capitalize;
|
||||
&:focus {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.valueArrow {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
}
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
span {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
&.active {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 8px 8px 0 0;
|
||||
.valueArrow {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
border-radius: 0 0 8px 8px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-top: none;
|
||||
background-color: #11316B;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
max-height: 300px;
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
border-radius: 0 0 8px 8px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-top: none;
|
||||
background-color: #11316b;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
max-height: 300px;
|
||||
|
||||
&.active{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
&.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.option {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
text-transform: capitalize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
height: 41px;
|
||||
.selectPoint {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
top: 11px;
|
||||
right: 12px;
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
border-radius: 50%;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
text-align: left;
|
||||
text-transform: capitalize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
height: 41px;
|
||||
.selectPoint {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
top: 11px;
|
||||
right: 12px;
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
border-radius: 50%;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:focus,
|
||||
&:active {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&[data-active*=true] {
|
||||
.selectPoint {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
&[data-active*='true'] {
|
||||
.selectPoint {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
width: 100%;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
&:not([disabled]) {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
width: 100%;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
@media (any-hover: hover) {
|
||||
&:hover {
|
||||
&:not([disabled]) {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,118 +1,112 @@
|
|||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import bitcoinIcon from "../../../../assets/tokens-svg/bitcoin.svg";
|
||||
import customTokenIcon from "../../../../assets/tokens-svg/custom-token.svg";
|
||||
import ethIcon from "../../../../assets/tokens-svg/eth.svg";
|
||||
import zanoIcon from "../../../../assets/tokens-svg/zano.svg";
|
||||
import arrowIcon from "../../../../assets/svg/arrow-select.svg";
|
||||
import { Store } from "../../../../store/store-reducer";
|
||||
import mainStyles from "../../WalletSend.module.scss";
|
||||
import s from "./AssetsSelect.module.scss";
|
||||
import { classNames } from "../../../../utils/classNames";
|
||||
import React, { Dispatch, SetStateAction, useContext, useEffect, useRef, useState } from 'react';
|
||||
import bitcoinIcon from '../../../../assets/tokens-svg/bitcoin.svg';
|
||||
import customTokenIcon from '../../../../assets/tokens-svg/custom-token.svg';
|
||||
import ethIcon from '../../../../assets/tokens-svg/eth.svg';
|
||||
import zanoIcon from '../../../../assets/tokens-svg/zano.svg';
|
||||
import arrowIcon from '../../../../assets/svg/arrow-select.svg';
|
||||
import { Store } from '../../../../store/store-reducer';
|
||||
import mainStyles from '../../WalletSend.module.scss';
|
||||
import s from './AssetsSelect.module.scss';
|
||||
import { classNames } from '../../../../utils/classNames';
|
||||
|
||||
interface Asset {
|
||||
name: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AssetsSelectProps {
|
||||
value: Asset;
|
||||
setValue: (asset: Asset) => void;
|
||||
value: Asset;
|
||||
setValue: Dispatch<SetStateAction<Asset>>;
|
||||
}
|
||||
|
||||
const AssetsSelect = ({ value, setValue }: AssetsSelectProps) => {
|
||||
const { state } = useContext(Store);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = React.useState<number | null>(null);
|
||||
const selectRef = useRef<HTMLDivElement>(null);
|
||||
const { state } = useContext(Store);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [focusedIndex, setFocusedIndex] = React.useState<number | null>(null);
|
||||
const selectRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
if (
|
||||
focusedIndex === null ||
|
||||
focusedIndex === state.wallet.assets.length - 1
|
||||
) {
|
||||
setFocusedIndex(0);
|
||||
} else {
|
||||
setFocusedIndex((prevIndex) => Number(prevIndex) + 1);
|
||||
}
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
if (focusedIndex === null || focusedIndex === 0) {
|
||||
setFocusedIndex(state.wallet.assets.length - 1);
|
||||
} else {
|
||||
setFocusedIndex((prevIndex) => Number(prevIndex) - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (focusedIndex === null || focusedIndex === state.wallet.assets.length - 1) {
|
||||
setFocusedIndex(0);
|
||||
} else {
|
||||
setFocusedIndex((prevIndex) => Number(prevIndex) + 1);
|
||||
}
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (focusedIndex === null || focusedIndex === 0) {
|
||||
setFocusedIndex(state.wallet.assets.length - 1);
|
||||
} else {
|
||||
setFocusedIndex((prevIndex) => Number(prevIndex) - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedIndex !== null && selectRef.current) {
|
||||
const childNodes = selectRef.current.childNodes;
|
||||
if (childNodes && childNodes[focusedIndex as number]) {
|
||||
(childNodes[focusedIndex as number] as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}, [focusedIndex]);
|
||||
useEffect(() => {
|
||||
if (focusedIndex !== null && selectRef.current) {
|
||||
const { childNodes } = selectRef.current;
|
||||
if (childNodes && childNodes[focusedIndex as number]) {
|
||||
(childNodes[focusedIndex as number] as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}, [focusedIndex]);
|
||||
|
||||
function openHandler() {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
function openHandler() {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
|
||||
function setValueHandler(asset: Asset) {
|
||||
setValue(asset);
|
||||
setIsOpen(false);
|
||||
}
|
||||
function setValueHandler(asset: Asset) {
|
||||
setValue(asset);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
const getAssetImage = (name: string) => {
|
||||
switch (name) {
|
||||
case "Zano":
|
||||
return zanoIcon;
|
||||
case "Wrapped Bitcoin":
|
||||
return bitcoinIcon;
|
||||
case "Wrapped Ethereum":
|
||||
return ethIcon;
|
||||
default:
|
||||
return customTokenIcon;
|
||||
}
|
||||
};
|
||||
const getAssetImage = (name: string) => {
|
||||
switch (name) {
|
||||
case 'Zano':
|
||||
return zanoIcon;
|
||||
case 'Wrapped Bitcoin':
|
||||
return bitcoinIcon;
|
||||
case 'Wrapped Ethereum':
|
||||
return ethIcon;
|
||||
default:
|
||||
return customTokenIcon;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onClick={() => setIsOpen(false)} onKeyDown={handleKeyDown}>
|
||||
<div className={mainStyles.label}>Asset:</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className={s.select}>
|
||||
<button
|
||||
onClick={openHandler}
|
||||
className={isOpen ? s.selectValue + " " + s.active : s.selectValue}
|
||||
>
|
||||
<span>
|
||||
<img src={getAssetImage(value.name)} alt={value.name + " icon"} />
|
||||
{value.name}
|
||||
</span>
|
||||
<span className={s.valueArrow}>
|
||||
<img src={arrowIcon} alt="arrow" />
|
||||
</span>
|
||||
</button>
|
||||
return (
|
||||
<div onClick={() => setIsOpen(false)} onKeyDown={handleKeyDown}>
|
||||
<div className={mainStyles.label}>Asset:</div>
|
||||
<div onClick={(e) => e.stopPropagation()} className={s.select}>
|
||||
<button
|
||||
onClick={openHandler}
|
||||
className={isOpen ? `${s.selectValue} ${s.active}` : s.selectValue}
|
||||
>
|
||||
<span>
|
||||
<img src={getAssetImage(value.name)} alt={`${value.name} icon`} />
|
||||
{value.name}
|
||||
</span>
|
||||
<span className={s.valueArrow}>
|
||||
<img src={arrowIcon} alt="arrow" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={classNames(s.options, { [s.active]: isOpen })}
|
||||
ref={selectRef}
|
||||
>
|
||||
{state.wallet.assets.map((asset) => (
|
||||
<button
|
||||
data-active={asset.name === value.name}
|
||||
className={s.option}
|
||||
key={asset.name}
|
||||
onClick={() => setValueHandler(asset)}
|
||||
>
|
||||
<img src={getAssetImage(asset.name)} alt={value.name + " icon"} />
|
||||
{asset.name}
|
||||
<span className={s.selectPoint} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className={classNames(s.options, { [s.active]: isOpen })} ref={selectRef}>
|
||||
{state.wallet.assets.map((asset) => (
|
||||
<button
|
||||
data-active={asset.name === value.name}
|
||||
className={s.option}
|
||||
key={asset.name}
|
||||
onClick={() => setValueHandler(asset)}
|
||||
>
|
||||
<img src={getAssetImage(asset.name)} alt={`${value.name} icon`} />
|
||||
{asset.name}
|
||||
<span className={s.selectPoint} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetsSelect;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
.settingsBody {
|
||||
|
||||
}
|
||||
|
||||
.settingsForm {
|
||||
min-height: 410px;
|
||||
}
|
||||
min-height: 410px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import React, { useState } from "react";
|
||||
import { useInput } from "../../hooks/useInput";
|
||||
import Button from "../UI/Button/Button";
|
||||
import MyInput from "../UI/MyInput/MyInput";
|
||||
import RoutersNav from "../UI/RoutersNav/RoutersNav";
|
||||
import s from "./WalletSettings.module.scss";
|
||||
import React, { useState } from 'react';
|
||||
import { useInput } from '../../hooks/useInput';
|
||||
import Button from '../UI/Button/Button';
|
||||
import MyInput from '../UI/MyInput/MyInput';
|
||||
import RoutersNav from '../UI/RoutersNav/RoutersNav';
|
||||
import s from './WalletSettings.module.scss';
|
||||
|
||||
const WalletSettings = () => {
|
||||
const [isBtnDisabled, setIsBtnDisabled] = useState(true);
|
||||
const localNodePort = useInput("11112", {});
|
||||
const [isBtnDisabled] = useState(true);
|
||||
const localNodePort = useInput('11112', {});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav title="Settings" />
|
||||
<div className={s.settingsForm}>
|
||||
<MyInput
|
||||
label="Local node port:"
|
||||
type="number"
|
||||
noActiveBorder
|
||||
value={localNodePort.value}
|
||||
onChange={localNodePort.onChange}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isBtnDisabled}>Confirm</Button>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<RoutersNav title="Settings" />
|
||||
<div className={s.settingsForm}>
|
||||
<MyInput
|
||||
label="Local node port:"
|
||||
type="number"
|
||||
noActiveBorder
|
||||
value={localNodePort.value}
|
||||
onChange={localNodePort.onChange}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isBtnDisabled}>Confirm</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletSettings;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
import { ZANO_ASSET_ID } from "../../constants";
|
||||
import { ZANO_ASSET_ID } from '../../constants';
|
||||
|
||||
export const whitelistedAssets = [
|
||||
{
|
||||
asset_id:
|
||||
ZANO_ASSET_ID,
|
||||
ticker: "ZANO",
|
||||
full_name: "Zano",
|
||||
},
|
||||
{
|
||||
asset_id:
|
||||
"e03a140b8447d2895290022b25c06bdabea514e2475ae56ce5bcbc554ab9865c",
|
||||
ticker: "CT",
|
||||
full_name: "Confidential token",
|
||||
},
|
||||
{
|
||||
asset_id: ZANO_ASSET_ID,
|
||||
ticker: 'ZANO',
|
||||
full_name: 'Zano',
|
||||
},
|
||||
{
|
||||
asset_id: 'e03a140b8447d2895290022b25c06bdabea514e2475ae56ce5bcbc554ab9865c',
|
||||
ticker: 'CT',
|
||||
full_name: 'Confidential token',
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultPort = 11211;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
type Callback = () => void;
|
||||
|
||||
const useAwayClick = (ref: React.RefObject<HTMLElement>, callback: Callback) => {
|
||||
const savedCallback = useRef<Callback | undefined>();
|
||||
const savedCallback = useRef<Callback | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
savedCallback.current?.();
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
savedCallback.current?.();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
document.addEventListener('mousedown', handleClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClick);
|
||||
};
|
||||
}, [ref]);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClick);
|
||||
};
|
||||
}, [ref]);
|
||||
};
|
||||
|
||||
export default useAwayClick;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
import { useContext } from "react";
|
||||
import { updateBalancesHidden } from "../store/actions";
|
||||
import { Store } from "../store/store-reducer";
|
||||
import { useContext } from 'react';
|
||||
import { updateBalancesHidden } from '../store/actions';
|
||||
import { Store } from '../store/store-reducer';
|
||||
|
||||
export function useCensorDigits() {
|
||||
const { state, dispatch } = useContext(Store);
|
||||
const { state, dispatch } = useContext(Store);
|
||||
|
||||
const changeCensor = () => {
|
||||
updateBalancesHidden(dispatch, (prevState: boolean) => !prevState);
|
||||
};
|
||||
const changeCensor = () => {
|
||||
updateBalancesHidden(dispatch, (prevState: boolean) => !prevState);
|
||||
};
|
||||
|
||||
const censorValue = (number: number | string): string | number => {
|
||||
if (state.isBalancesHidden) {
|
||||
return number.toString().replace(/\d/g, "*");
|
||||
} else {
|
||||
return number;
|
||||
}
|
||||
};
|
||||
const censorValue = (number: number | string): string | number => {
|
||||
if (state.isBalancesHidden) {
|
||||
return number.toString().replace(/\d/g, '*');
|
||||
}
|
||||
return number;
|
||||
};
|
||||
|
||||
return { changeCensor, censorValue };
|
||||
return { changeCensor, censorValue };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useState } from "react";
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useCheckbox = (initialState: boolean) => {
|
||||
const [isChecked, setIsChecked] = useState(initialState);
|
||||
const [isChecked, setIsChecked] = useState(initialState);
|
||||
|
||||
const onChange = () => {
|
||||
setIsChecked(!isChecked);
|
||||
};
|
||||
const onChange = () => {
|
||||
setIsChecked(!isChecked);
|
||||
};
|
||||
|
||||
return { isChecked, onChange };
|
||||
};
|
||||
return { isChecked, onChange };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import copy from "copy-to-clipboard";
|
||||
import { useState } from "react";
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useCopy = () => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
copy(text);
|
||||
if (!copied) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 3000);
|
||||
}
|
||||
};
|
||||
const copyToClipboard = (text: string) => {
|
||||
copy(text);
|
||||
if (!copied) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
copyToClipboard,
|
||||
copied,
|
||||
};
|
||||
return {
|
||||
copyToClipboard,
|
||||
copied,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { useContext } from "react";
|
||||
import { Store } from "../store/store-reducer";
|
||||
import { useContext } from 'react';
|
||||
import { Store } from '../store/store-reducer';
|
||||
|
||||
interface Asset {
|
||||
assetId: string;
|
||||
assetId: string;
|
||||
}
|
||||
|
||||
export default function useGetAsset() {
|
||||
const { state } = useContext(Store);
|
||||
const { state } = useContext(Store);
|
||||
|
||||
function getAssetById(id: string) {
|
||||
return state.wallet.assets.find((asset: Asset | any) => asset.assetId === id);
|
||||
}
|
||||
function getAssetById(id: string) {
|
||||
return state.wallet.assets.find((asset: Asset | any) => asset.assetId === id);
|
||||
}
|
||||
|
||||
return { getAssetById };
|
||||
return { getAssetById };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,39 @@
|
|||
import { useState } from "react";
|
||||
import { useValidation } from "./useValidation";
|
||||
import { useState } from 'react';
|
||||
import { useValidation } from './useValidation';
|
||||
|
||||
type Validations = {
|
||||
minLength?: number;
|
||||
isEmpty?: boolean;
|
||||
isAmountCorrect?: boolean;
|
||||
customValidation?: boolean;
|
||||
minLength?: number;
|
||||
isEmpty?: boolean;
|
||||
isAmountCorrect?: boolean;
|
||||
customValidation?: boolean;
|
||||
};
|
||||
|
||||
export const useInput = (
|
||||
initialState: string | number,
|
||||
validations: Validations
|
||||
) => {
|
||||
const [value, setValue] = useState<string | number>(initialState);
|
||||
const [isDirty, setIsDirty] = useState<boolean>(false);
|
||||
const valid = useValidation(value, validations);
|
||||
export const useInput = (initialState: string | number, validations: Validations) => {
|
||||
const [value, setValue] = useState<string | number>(initialState);
|
||||
const [isDirty, setIsDirty] = useState<boolean>(false);
|
||||
const valid = useValidation(value, validations);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
|
||||
const onInput = (newValue: string | number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
const onInput = (newValue: string | number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
setIsDirty(true);
|
||||
};
|
||||
const onBlur = () => {
|
||||
setIsDirty(true);
|
||||
};
|
||||
|
||||
const isFilled = typeof value === "string" && value.length > 0;
|
||||
const isFilled = typeof value === 'string' && value.length > 0;
|
||||
|
||||
return { value, onChange, onInput, onBlur, isFilled, isDirty, ...valid };
|
||||
return {
|
||||
value,
|
||||
onChange,
|
||||
onInput,
|
||||
onBlur,
|
||||
isFilled,
|
||||
isDirty,
|
||||
...valid,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,60 +1,60 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type Validations = {
|
||||
minLength?: number;
|
||||
isEmpty?: boolean;
|
||||
isAmountCorrect?: boolean;
|
||||
customValidation?: boolean;
|
||||
minLength?: number;
|
||||
isEmpty?: boolean;
|
||||
isAmountCorrect?: boolean;
|
||||
customValidation?: boolean;
|
||||
};
|
||||
|
||||
export const useValidation = (value: string | number, validations: Validations) => {
|
||||
const [isEmpty, setIsEmpty] = useState<boolean>(true);
|
||||
const [minLengthError, setMinLengthError] = useState<boolean>(false);
|
||||
const [amountCorrectError, setAmountCorrectError] = useState<boolean>(false);
|
||||
const [inputValid, setInputValid] = useState<boolean>(false);
|
||||
const [isEmpty, setIsEmpty] = useState<boolean>(true);
|
||||
const [minLengthError, setMinLengthError] = useState<boolean>(false);
|
||||
const [amountCorrectError, setAmountCorrectError] = useState<boolean>(false);
|
||||
const [inputValid, setInputValid] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
for (const validation in validations) {
|
||||
switch (validation) {
|
||||
case "minLength":
|
||||
if (typeof value === "string" && value.length < validations[validation]!) {
|
||||
setMinLengthError(true);
|
||||
} else {
|
||||
setMinLengthError(false);
|
||||
}
|
||||
break;
|
||||
case "isEmpty":
|
||||
setIsEmpty(!value);
|
||||
break;
|
||||
case "isAmountCorrect":
|
||||
const amountCheckResult =
|
||||
typeof value === "number" &&
|
||||
!isNaN(value) &&
|
||||
value >= 0.000000000001 &&
|
||||
value <= 1000000000;
|
||||
setAmountCorrectError(!amountCheckResult);
|
||||
break;
|
||||
case "customValidation":
|
||||
setInputValid(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [validations, value]);
|
||||
useEffect(() => {
|
||||
for (const validation in validations) {
|
||||
switch (validation) {
|
||||
case 'minLength':
|
||||
if (typeof value === 'string' && value.length < validations[validation]!) {
|
||||
setMinLengthError(true);
|
||||
} else {
|
||||
setMinLengthError(false);
|
||||
}
|
||||
break;
|
||||
case 'isEmpty':
|
||||
setIsEmpty(!value);
|
||||
break;
|
||||
case 'isAmountCorrect':
|
||||
const amountCheckResult =
|
||||
typeof value === 'number' &&
|
||||
!isNaN(value) &&
|
||||
value >= 0.000000000001 &&
|
||||
value <= 1000000000;
|
||||
setAmountCorrectError(!amountCheckResult);
|
||||
break;
|
||||
case 'customValidation':
|
||||
setInputValid(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [validations, value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmpty || minLengthError || amountCorrectError) {
|
||||
setInputValid(false);
|
||||
} else {
|
||||
setInputValid(true);
|
||||
}
|
||||
}, [isEmpty, minLengthError, amountCorrectError]);
|
||||
useEffect(() => {
|
||||
if (isEmpty || minLengthError || amountCorrectError) {
|
||||
setInputValid(false);
|
||||
} else {
|
||||
setInputValid(true);
|
||||
}
|
||||
}, [isEmpty, minLengthError, amountCorrectError]);
|
||||
|
||||
return {
|
||||
isEmpty,
|
||||
minLengthError,
|
||||
amountCorrectError,
|
||||
inputValid,
|
||||
};
|
||||
return {
|
||||
isEmpty,
|
||||
minLengthError,
|
||||
amountCorrectError,
|
||||
inputValid,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,107 +1,121 @@
|
|||
// Define types for the structure of your state
|
||||
interface WalletState {
|
||||
wallets: string[];
|
||||
walletData: object;
|
||||
isConnected: boolean;
|
||||
activeWalletId: string | number;
|
||||
priceData: object;
|
||||
displayCurrency: string;
|
||||
isLoading: boolean;
|
||||
balancesHidden: boolean;
|
||||
confirmationModalOpen: boolean | null | {method: string; params: string[]};
|
||||
transactionStatus: string;
|
||||
connectData: object;
|
||||
whiteList: string[];
|
||||
wallets: string[];
|
||||
walletData: object;
|
||||
isConnected: boolean;
|
||||
activeWalletId: string | number;
|
||||
priceData: object;
|
||||
displayCurrency: string;
|
||||
isLoading: boolean;
|
||||
balancesHidden: boolean;
|
||||
confirmationModalOpen: boolean | null | { method: string; params: string[] };
|
||||
transactionStatus: string;
|
||||
connectData: object;
|
||||
whiteList: string[];
|
||||
}
|
||||
|
||||
type DispatchFunction = (action: { type: string; payload: WalletState[keyof WalletState] }) => void;
|
||||
export type DispatchFunction = (_saction: {
|
||||
type: string;
|
||||
payload: WalletState[keyof WalletState];
|
||||
}) => void;
|
||||
|
||||
export const updateWalletsList = (dispatch: DispatchFunction, state: WalletState['wallets']): void => {
|
||||
return dispatch({
|
||||
type: "WALLETS_LIST_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateWalletsList = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['wallets'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'WALLETS_LIST_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateWalletData = (dispatch: DispatchFunction, state: WalletState['walletData']): void => {
|
||||
return dispatch({
|
||||
type: "WALLET_DATA_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateWalletData = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['walletData'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'WALLET_DATA_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateWalletConnected = (dispatch: DispatchFunction, state: WalletState['isConnected']): void => {
|
||||
return dispatch({
|
||||
type: "WALLET_CONNECTED_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateWalletConnected = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['isConnected'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'WALLET_CONNECTED_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateActiveWalletId = (dispatch: DispatchFunction, state: WalletState['activeWalletId']): void => {
|
||||
return dispatch({
|
||||
type: "ACTIVE_WALLET_ID_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateActiveWalletId = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['activeWalletId'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'ACTIVE_WALLET_ID_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updatePriceData = (dispatch: DispatchFunction, state: WalletState['priceData']): void => {
|
||||
return dispatch({
|
||||
type: "PRICE_DATA_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updatePriceData = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['priceData'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'PRICE_DATA_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateDisplay = (dispatch: DispatchFunction, state: WalletState['displayCurrency']): void => {
|
||||
return dispatch({
|
||||
type: "DISPLAY_CURRENCY_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateDisplay = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['displayCurrency'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'DISPLAY_CURRENCY_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateLoading = (dispatch: DispatchFunction, state: WalletState['isLoading']): void => {
|
||||
return dispatch({
|
||||
type: "LOADING_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateLoading = (dispatch: DispatchFunction, state: WalletState['isLoading']): void =>
|
||||
dispatch({
|
||||
type: 'LOADING_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateBalancesHidden = (dispatch: any, state: any): void => {
|
||||
return dispatch({
|
||||
type: "BALANCES_HIDDEN_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateBalancesHidden = (dispatch: any, state: any): void =>
|
||||
dispatch({
|
||||
type: 'BALANCES_HIDDEN_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateConfirmationModal = (dispatch: DispatchFunction, state: WalletState['confirmationModalOpen']): void => {
|
||||
return dispatch({
|
||||
type: "CONFIRMATION_MODAL_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateConfirmationModal = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['confirmationModalOpen'],
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'CONFIRMATION_MODAL_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const updateTransactionStatus = (dispatch: DispatchFunction, state: WalletState['transactionStatus'] | any): void => {
|
||||
return dispatch({
|
||||
type: "TRANSACTION_STATUS_UPDATED",
|
||||
payload: state,
|
||||
});
|
||||
};
|
||||
export const updateTransactionStatus = (
|
||||
dispatch: DispatchFunction,
|
||||
state: WalletState['transactionStatus'] | any,
|
||||
): void =>
|
||||
dispatch({
|
||||
type: 'TRANSACTION_STATUS_UPDATED',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
interface ConnectCredentials {
|
||||
token: string;
|
||||
port: string;
|
||||
token: string;
|
||||
port: string;
|
||||
}
|
||||
|
||||
export const setConnectData = (dispatch: DispatchFunction, state: ConnectCredentials): void =>
|
||||
dispatch({
|
||||
type: 'SET_CONNECT_DATA',
|
||||
payload: state,
|
||||
});
|
||||
|
||||
export const setConnectData = (dispatch: DispatchFunction, state: ConnectCredentials): void => {
|
||||
return dispatch({
|
||||
type: "SET_CONNECT_DATA",
|
||||
payload: state
|
||||
});
|
||||
};
|
||||
|
||||
export const setWhiteList = (dispatch: DispatchFunction, state: WalletState['whiteList']): void => {
|
||||
return dispatch({
|
||||
type: "SET_WHITE_LIST",
|
||||
payload: state
|
||||
});
|
||||
};
|
||||
export const setWhiteList = (dispatch: DispatchFunction, state: WalletState['whiteList']): void =>
|
||||
dispatch({
|
||||
type: 'SET_WHITE_LIST',
|
||||
payload: state,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,253 +1,234 @@
|
|||
import React, { createContext, useReducer, ReactNode, useContext } from "react";
|
||||
import React, { createContext, useReducer, ReactNode, useContext } from 'react';
|
||||
|
||||
// Define the types for the state
|
||||
interface Asset {
|
||||
name: string;
|
||||
ticker: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
unlockedBalance?: number;
|
||||
value: number;
|
||||
decimalPoint?: number;
|
||||
assetId?: string;
|
||||
name: string;
|
||||
ticker: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
unlockedBalance?: number;
|
||||
value: number;
|
||||
decimalPoint?: number;
|
||||
assetId?: string;
|
||||
}
|
||||
|
||||
interface Transfer {
|
||||
assetId?: string;
|
||||
amount?: number;
|
||||
incoming?: boolean;
|
||||
assetId?: string;
|
||||
amount?: number;
|
||||
incoming?: boolean;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
txHash?: string;
|
||||
isConfirmed: boolean;
|
||||
incoming: boolean;
|
||||
amount?: number;
|
||||
value?: number;
|
||||
ticker: string;
|
||||
address: string;
|
||||
transfers?: Transfer[];
|
||||
isInitiator?: boolean;
|
||||
fee?: number | string;
|
||||
txHash?: string;
|
||||
isConfirmed: boolean;
|
||||
incoming: boolean;
|
||||
amount?: number;
|
||||
value?: number;
|
||||
ticker: string;
|
||||
address: string;
|
||||
transfers?: Transfer[];
|
||||
isInitiator?: boolean;
|
||||
fee?: number | string;
|
||||
}
|
||||
|
||||
interface Wallet {
|
||||
address: string;
|
||||
alias: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
assets: Asset[];
|
||||
transactions: Transaction[];
|
||||
address: string;
|
||||
alias: string;
|
||||
balance: number;
|
||||
lockedBalance?: number;
|
||||
assets: Asset[];
|
||||
transactions: Transaction[];
|
||||
}
|
||||
|
||||
interface TransactionStatus {
|
||||
visible: boolean;
|
||||
type: string;
|
||||
code: number;
|
||||
message: string;
|
||||
visible: boolean;
|
||||
type: string;
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ConnectCredentials {
|
||||
token: string | null;
|
||||
port: string | null;
|
||||
token: string | null;
|
||||
port: string | null;
|
||||
}
|
||||
|
||||
interface PriceData {
|
||||
price: number;
|
||||
change: number;
|
||||
price: number;
|
||||
change: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
walletsList: { address: string; alias: string; balance: number, wallet_id?: number; }[];
|
||||
activeWalletId: number;
|
||||
wallet: Wallet;
|
||||
displayUsd: boolean;
|
||||
isLoading: boolean;
|
||||
isConnected: boolean | undefined;
|
||||
isBalancesHidden: boolean;
|
||||
priceData: PriceData;
|
||||
confirmationModal: string | null | any;
|
||||
transactionStatus: TransactionStatus;
|
||||
connectCredentials: ConnectCredentials;
|
||||
whitelistedAssets: string[];
|
||||
walletAddress?: string;
|
||||
walletBalance?: number;
|
||||
walletsList: { address: string; alias: string; balance: number; wallet_id?: number }[];
|
||||
activeWalletId: number;
|
||||
wallet: Wallet;
|
||||
displayUsd: boolean;
|
||||
isLoading: boolean;
|
||||
isConnected: boolean | undefined;
|
||||
isBalancesHidden: boolean;
|
||||
priceData: PriceData;
|
||||
confirmationModal: string | null | any;
|
||||
transactionStatus: TransactionStatus;
|
||||
connectCredentials: ConnectCredentials;
|
||||
whitelistedAssets: string[];
|
||||
walletAddress?: string;
|
||||
walletBalance?: number;
|
||||
}
|
||||
|
||||
// Initial state
|
||||
const initialState: State = {
|
||||
walletsList: [
|
||||
{
|
||||
address:
|
||||
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
|
||||
alias: "ravaga",
|
||||
balance: 1337,
|
||||
},
|
||||
{
|
||||
address:
|
||||
"ZxDCEeVaHsYXEJotN8Q5y4PW7Y4inrNaibqpmU7P9KGCZ76LBPYkn9Gf8BzCSLSJfpVDJ7GzBPApGEK4BVbogZwN2opPAQDfU",
|
||||
alias: "test",
|
||||
balance: 27,
|
||||
},
|
||||
],
|
||||
activeWalletId: 0,
|
||||
wallet: {
|
||||
address:
|
||||
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
|
||||
alias: "ravaga",
|
||||
balance: 1337,
|
||||
lockedBalance: 0,
|
||||
assets: [
|
||||
{
|
||||
name: "Zano",
|
||||
ticker: "ZANO",
|
||||
balance: 1337,
|
||||
lockedBalance: 0,
|
||||
value: 1000,
|
||||
},
|
||||
{
|
||||
name: "Wrapped Bitcoin",
|
||||
ticker: "WBTC",
|
||||
balance: 0.212,
|
||||
value: 4096.96,
|
||||
},
|
||||
{
|
||||
name: "Wrapped Ethereum",
|
||||
ticker: "WETH",
|
||||
balance: 2.1,
|
||||
value: 3020.12,
|
||||
},
|
||||
{
|
||||
name: "Confidential Token",
|
||||
ticker: "CT",
|
||||
balance: 15.52,
|
||||
value: 672.84,
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
isConfirmed: true,
|
||||
incoming: true,
|
||||
amount: 100,
|
||||
ticker: "ZANO",
|
||||
address:
|
||||
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
|
||||
},
|
||||
{
|
||||
isConfirmed: false,
|
||||
incoming: false,
|
||||
value: 17,
|
||||
ticker: "ZANO",
|
||||
address:
|
||||
"ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9",
|
||||
},
|
||||
],
|
||||
},
|
||||
displayUsd: false,
|
||||
isLoading: true,
|
||||
isConnected: undefined,
|
||||
isBalancesHidden: false,
|
||||
priceData: { price: 1, change: -4.6 },
|
||||
confirmationModal: null,
|
||||
transactionStatus: {
|
||||
visible: false,
|
||||
type: "",
|
||||
code: 0,
|
||||
message: "",
|
||||
},
|
||||
connectCredentials: {
|
||||
token: null,
|
||||
port: null,
|
||||
},
|
||||
whitelistedAssets: [],
|
||||
walletsList: [
|
||||
{
|
||||
address:
|
||||
'ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9',
|
||||
alias: 'ravaga',
|
||||
balance: 1337,
|
||||
},
|
||||
{
|
||||
address:
|
||||
'ZxDCEeVaHsYXEJotN8Q5y4PW7Y4inrNaibqpmU7P9KGCZ76LBPYkn9Gf8BzCSLSJfpVDJ7GzBPApGEK4BVbogZwN2opPAQDfU',
|
||||
alias: 'test',
|
||||
balance: 27,
|
||||
},
|
||||
],
|
||||
activeWalletId: 0,
|
||||
wallet: {
|
||||
address:
|
||||
'ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9',
|
||||
alias: 'ravaga',
|
||||
balance: 1337,
|
||||
lockedBalance: 0,
|
||||
assets: [
|
||||
{
|
||||
name: 'Zano',
|
||||
ticker: 'ZANO',
|
||||
balance: 1337,
|
||||
lockedBalance: 0,
|
||||
value: 1000,
|
||||
},
|
||||
{
|
||||
name: 'Wrapped Bitcoin',
|
||||
ticker: 'WBTC',
|
||||
balance: 0.212,
|
||||
value: 4096.96,
|
||||
},
|
||||
{
|
||||
name: 'Wrapped Ethereum',
|
||||
ticker: 'WETH',
|
||||
balance: 2.1,
|
||||
value: 3020.12,
|
||||
},
|
||||
{
|
||||
name: 'Confidential Token',
|
||||
ticker: 'CT',
|
||||
balance: 15.52,
|
||||
value: 672.84,
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
isConfirmed: true,
|
||||
incoming: true,
|
||||
amount: 100,
|
||||
ticker: 'ZANO',
|
||||
address:
|
||||
'ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9',
|
||||
},
|
||||
{
|
||||
isConfirmed: false,
|
||||
incoming: false,
|
||||
value: 17,
|
||||
ticker: 'ZANO',
|
||||
address:
|
||||
'ZxDTZ8LJ88ZK6Ja1P9iqDNgCiBM6FhiBKdDoTAoEp9nY9q8d846iePAGYGjNvrU9uFHDXD3by5CooSBrsXBDfE9M11WBwAxQ9',
|
||||
},
|
||||
],
|
||||
},
|
||||
displayUsd: false,
|
||||
isLoading: true,
|
||||
isConnected: undefined,
|
||||
isBalancesHidden: false,
|
||||
priceData: { price: 1, change: -4.6 },
|
||||
confirmationModal: null,
|
||||
transactionStatus: {
|
||||
visible: false,
|
||||
type: '',
|
||||
code: 0,
|
||||
message: '',
|
||||
},
|
||||
connectCredentials: {
|
||||
token: null,
|
||||
port: null,
|
||||
},
|
||||
whitelistedAssets: [],
|
||||
};
|
||||
|
||||
type Action =
|
||||
| { type: "WALLET_ADDRESS_UPDATED"; payload: string }
|
||||
| { type: "WALLET_BALANCE_UPDATED"; payload: number }
|
||||
| { type: "WALLET_CONNECTED_UPDATED"; payload: boolean | undefined }
|
||||
| { type: "WALLETS_LIST_UPDATED"; payload: { address: string; alias: string; balance: number }[] }
|
||||
| { type: "ACTIVE_WALLET_ID_UPDATED"; payload: number }
|
||||
| { type: "WALLET_DATA_UPDATED"; payload: Wallet }
|
||||
| { type: "PRICE_DATA_UPDATED"; payload: PriceData }
|
||||
| { type: "DISPLAY_CURRENCY_UPDATED"; payload: boolean }
|
||||
| { type: "LOADING_UPDATED"; payload: boolean }
|
||||
| { type: "BALANCES_HIDDEN_UPDATED"; payload: boolean }
|
||||
| { type: "CONFIRMATION_MODAL_UPDATED"; payload: string | null }
|
||||
| { type: "TRANSACTION_STATUS_UPDATED"; payload: TransactionStatus }
|
||||
| { type: "SET_CONNECT_DATA"; payload: ConnectCredentials }
|
||||
| { type: "SET_WHITE_LIST"; payload: string[] }
|
||||
| { type: "SET_BALANCES_HIDDEN"; payload: boolean };
|
||||
| { type: 'WALLET_ADDRESS_UPDATED'; payload: string }
|
||||
| { type: 'WALLET_BALANCE_UPDATED'; payload: number }
|
||||
| { type: 'WALLET_CONNECTED_UPDATED'; payload: boolean | undefined }
|
||||
| {
|
||||
type: 'WALLETS_LIST_UPDATED';
|
||||
payload: { address: string; alias: string; balance: number }[];
|
||||
}
|
||||
| { type: 'ACTIVE_WALLET_ID_UPDATED'; payload: number }
|
||||
| { type: 'WALLET_DATA_UPDATED'; payload: Wallet }
|
||||
| { type: 'PRICE_DATA_UPDATED'; payload: PriceData }
|
||||
| { type: 'DISPLAY_CURRENCY_UPDATED'; payload: boolean }
|
||||
| { type: 'LOADING_UPDATED'; payload: boolean }
|
||||
| { type: 'BALANCES_HIDDEN_UPDATED'; payload: boolean }
|
||||
| { type: 'CONFIRMATION_MODAL_UPDATED'; payload: string | null }
|
||||
| { type: 'TRANSACTION_STATUS_UPDATED'; payload: TransactionStatus }
|
||||
| { type: 'SET_CONNECT_DATA'; payload: ConnectCredentials }
|
||||
| { type: 'SET_WHITE_LIST'; payload: string[] }
|
||||
| { type: 'SET_BALANCES_HIDDEN'; payload: boolean };
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "WALLET_ADDRESS_UPDATED":
|
||||
return { ...state, walletAddress: action.payload };
|
||||
case "WALLET_BALANCE_UPDATED":
|
||||
return { ...state, walletBalance: action.payload };
|
||||
case "WALLET_CONNECTED_UPDATED":
|
||||
return { ...state, isConnected: action.payload };
|
||||
case "WALLETS_LIST_UPDATED":
|
||||
return { ...state, walletsList: action.payload };
|
||||
case "ACTIVE_WALLET_ID_UPDATED":
|
||||
return { ...state, activeWalletId: action.payload };
|
||||
case "WALLET_DATA_UPDATED":
|
||||
return { ...state, wallet: action.payload };
|
||||
case "PRICE_DATA_UPDATED":
|
||||
return { ...state, priceData: action.payload };
|
||||
case "DISPLAY_CURRENCY_UPDATED":
|
||||
return { ...state, displayUsd: action.payload };
|
||||
case "LOADING_UPDATED":
|
||||
return { ...state, isLoading: action.payload };
|
||||
case "BALANCES_HIDDEN_UPDATED":
|
||||
return { ...state, isBalancesHidden: action.payload };
|
||||
case "CONFIRMATION_MODAL_UPDATED":
|
||||
return { ...state, confirmationModal: action.payload };
|
||||
case "TRANSACTION_STATUS_UPDATED":
|
||||
return { ...state, transactionStatus: action.payload };
|
||||
case "SET_CONNECT_DATA":
|
||||
return { ...state, connectCredentials: action.payload };
|
||||
case "SET_WHITE_LIST":
|
||||
return { ...state, whitelistedAssets: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'WALLET_ADDRESS_UPDATED':
|
||||
return { ...state, walletAddress: action.payload };
|
||||
case 'WALLET_BALANCE_UPDATED':
|
||||
return { ...state, walletBalance: action.payload };
|
||||
case 'WALLET_CONNECTED_UPDATED':
|
||||
return { ...state, isConnected: action.payload };
|
||||
case 'WALLETS_LIST_UPDATED':
|
||||
return { ...state, walletsList: action.payload };
|
||||
case 'ACTIVE_WALLET_ID_UPDATED':
|
||||
return { ...state, activeWalletId: action.payload };
|
||||
case 'WALLET_DATA_UPDATED':
|
||||
return { ...state, wallet: action.payload };
|
||||
case 'PRICE_DATA_UPDATED':
|
||||
return { ...state, priceData: action.payload };
|
||||
case 'DISPLAY_CURRENCY_UPDATED':
|
||||
return { ...state, displayUsd: action.payload };
|
||||
case 'LOADING_UPDATED':
|
||||
return { ...state, isLoading: action.payload };
|
||||
case 'BALANCES_HIDDEN_UPDATED':
|
||||
return { ...state, isBalancesHidden: action.payload };
|
||||
case 'CONFIRMATION_MODAL_UPDATED':
|
||||
return { ...state, confirmationModal: action.payload };
|
||||
case 'TRANSACTION_STATUS_UPDATED':
|
||||
return { ...state, transactionStatus: action.payload };
|
||||
case 'SET_CONNECT_DATA':
|
||||
return { ...state, connectCredentials: action.payload };
|
||||
case 'SET_WHITE_LIST':
|
||||
return { ...state, whitelistedAssets: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const Store = createContext<{ state: State; dispatch: React.Dispatch<Action> }>({
|
||||
state: initialState,
|
||||
dispatch: () => { },
|
||||
state: initialState,
|
||||
dispatch: () => Object,
|
||||
});
|
||||
|
||||
interface StoreProviderProps {
|
||||
children: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const StoreProvider = ({ children }: StoreProviderProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider>;
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider>;
|
||||
};
|
||||
|
||||
export const useStore = () => useContext(Store);
|
||||
|
||||
const updateWhiteList = (dispatch: React.Dispatch<Action>, whiteList: string[]) => {
|
||||
dispatch({
|
||||
type: "SET_WHITE_LIST",
|
||||
payload: whiteList,
|
||||
});
|
||||
};
|
||||
|
||||
// Usage in a component:
|
||||
const ExampleComponent = () => {
|
||||
const { state, dispatch } = useStore();
|
||||
|
||||
const handleUpdateWhiteList = () => {
|
||||
updateWhiteList(dispatch, ["asset1", "asset2"]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleUpdateWhiteList}>Update WhiteList</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
@use "reset";
|
||||
@use "global";
|
||||
@use "fonts";
|
||||
@use "variables";
|
||||
@use 'reset';
|
||||
@use 'global';
|
||||
@use 'fonts';
|
||||
@use 'variables';
|
||||
|
||||
.App {
|
||||
position: relative;
|
||||
background-color: #0C0C3A;
|
||||
padding-top: 72px;
|
||||
min-height: variables.$appHeight;
|
||||
resize: none;
|
||||
position: relative;
|
||||
background-color: #0c0c3a;
|
||||
padding-top: 72px;
|
||||
min-height: variables.$appHeight;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 16px;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 528px;
|
||||
}
|
||||
margin: 0 16px;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 528px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
.swapAmount {
|
||||
word-break: break-all;
|
||||
}
|
||||
word-break: break-all;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +1,101 @@
|
|||
@use "sass:math";
|
||||
@use 'sass:math';
|
||||
|
||||
.round-button {
|
||||
position: relative;
|
||||
flex: 0 0 35px;
|
||||
border-radius: 50%;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
span {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
left: -5px;
|
||||
transform: translate(-100%, 0);
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
height: 29px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
line-height: math.div(19, 16);
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
background: rgba(20, 65, 130, 1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 11.5px;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: rgba(20, 65, 130, 1);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
span {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.1s ease 1s;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
flex: 0 0 35px;
|
||||
border-radius: 50%;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease 0s;
|
||||
span {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
left: -5px;
|
||||
transform: translate(-100%, 0);
|
||||
padding: 0 8px;
|
||||
border-radius: 5px;
|
||||
height: 29px;
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
line-height: math.div(19, 16);
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
background: rgba(20, 65, 130, 1);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 11.5px;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: rgba(20, 65, 130, 1);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
max-width: 100%;
|
||||
}
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
span {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.1s ease 1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
._input-filled {
|
||||
border-color: #16D1D6 !important;
|
||||
border-color: #16d1d6 !important;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Table
|
||||
.table {
|
||||
padding-bottom: 50px;
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
row-gap: 2px;
|
||||
column-gap: 20px;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding-bottom: 50px;
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
row-gap: 2px;
|
||||
column-gap: 20px;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
&__label {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1F8FEB;
|
||||
}
|
||||
&__value {
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
word-break: break-all;
|
||||
.table__row_button & {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
&__label {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
color: #1f8feb;
|
||||
}
|
||||
&__value {
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: math.div(21, 18);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
word-break: break-all;
|
||||
.table__row_button & {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
$fontFamily: "SFUIDisplay";
|
||||
$fontFamily: 'SFUIDisplay';
|
||||
$fontSize: 18px;
|
||||
$mainColor: #ffffff;
|
||||
$blueColor: #1f8feb;
|
||||
$appWidth: 360px;
|
||||
$appHeight: 600px;
|
||||
|
||||
:root{
|
||||
// Sizes
|
||||
--app-width: 360px;
|
||||
--app-height: 600px;
|
||||
:root {
|
||||
// Sizes
|
||||
--app-width: 360px;
|
||||
--app-height: 600px;
|
||||
|
||||
// Z-index's
|
||||
--modal-z-index: 150;
|
||||
// Z-index's
|
||||
--modal-z-index: 150;
|
||||
|
||||
// Colors
|
||||
--overlay-color: rgba(0, 0, 0, 0.7);
|
||||
// Colors
|
||||
--overlay-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,51 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src: url("../assets/fonts/SFUIDisplay-light.woff2") format("woff2"), url("../assets/fonts/SFUIDisplay-light.woff") format("woff");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('../assets/fonts/SFUIDisplay-light.woff2') format('woff2'),
|
||||
url('../assets/fonts/SFUIDisplay-light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src: url("../assets/fonts/SFUIDisplay-regular.woff2") format("woff2"), url("../assets/fonts/SFUIDisplay-regular.woff") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('../assets/fonts/SFUIDisplay-regular.woff2') format('woff2'),
|
||||
url('../assets/fonts/SFUIDisplay-regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src: url("../assets/fonts/SFUIDisplay-medium.woff2") format("woff2"), url("../assets/fonts/SFUIDisplay-medium.woff") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('../assets/fonts/SFUIDisplay-medium.woff2') format('woff2'),
|
||||
url('../assets/fonts/SFUIDisplay-medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src: url("../assets/fonts/SFUIDisplay-semibold.woff2") format("woff2"), url("../assets/fonts/SFUIDisplay-semibold.woff") format("woff");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('../assets/fonts/SFUIDisplay-semibold.woff2') format('woff2'),
|
||||
url('../assets/fonts/SFUIDisplay-semibold.woff') format('woff');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src: url("../assets/fonts/SFUIDisplay-bold.woff2") format("woff2"), url("../assets/fonts/SFUIDisplay-bold.woff") format("woff");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-family: SFUIDisplay;
|
||||
font-display: swap;
|
||||
src:
|
||||
url('../assets/fonts/SFUIDisplay-bold.woff2') format('woff2'),
|
||||
url('../assets/fonts/SFUIDisplay-bold.woff') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +1,75 @@
|
|||
@import "_variables";
|
||||
@import '_variables';
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
scrollbar-width: none;
|
||||
display: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:focus,
|
||||
:active {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: $appHeight;
|
||||
overflow: auto;
|
||||
width: $appWidth;
|
||||
height: $appHeight;
|
||||
overflow: auto;
|
||||
width: $appWidth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: $fontSize;
|
||||
color: $mainColor;
|
||||
line-height: 1;
|
||||
font-family: $fontFamily;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
font-size: $fontSize;
|
||||
color: $mainColor;
|
||||
line-height: 1;
|
||||
font-family: $fontFamily;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea {
|
||||
font-family: $fontFamily;
|
||||
font-size: inherit;
|
||||
font-family: $fontFamily;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style: none;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: top;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
|
@ -79,6 +78,6 @@ h3,
|
|||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
import CryptoJS from "crypto-js";
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
interface ConnectData {
|
||||
token: string;
|
||||
port: string;
|
||||
token: string;
|
||||
port: string;
|
||||
}
|
||||
|
||||
export default class ConnectKeyUtils {
|
||||
static setConnectData(key: string, walletPort: string, extPass: string): void {
|
||||
const data: ConnectData = { token: key, port: walletPort };
|
||||
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), extPass).toString();
|
||||
localStorage.setItem("connectKey", encrypted);
|
||||
}
|
||||
static setConnectData(key: string, walletPort: string, extPass: string): void {
|
||||
const data: ConnectData = { token: key, port: walletPort };
|
||||
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), extPass).toString();
|
||||
localStorage.setItem('connectKey', encrypted);
|
||||
}
|
||||
|
||||
static getConnectKeyEncrypted(): string | null {
|
||||
return localStorage.getItem("connectKey");
|
||||
}
|
||||
static getConnectKeyEncrypted(): string | null {
|
||||
return localStorage.getItem('connectKey');
|
||||
}
|
||||
|
||||
static getConnectData(password: string): ConnectData | null {
|
||||
const encrypted = localStorage.getItem("connectKey");
|
||||
if (!encrypted) return null;
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (!decrypted) return null;
|
||||
|
||||
try {
|
||||
const data: ConnectData = JSON.parse(decrypted);
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static getConnectData(password: string): ConnectData | null {
|
||||
const encrypted = localStorage.getItem('connectKey');
|
||||
if (!encrypted) return null;
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(encrypted, password).toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (!decrypted) return null;
|
||||
|
||||
try {
|
||||
const data: ConnectData = JSON.parse(decrypted);
|
||||
return data;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
type Mods = Record<string, boolean | string | undefined>;
|
||||
|
||||
export const classNames = (
|
||||
cls: string,
|
||||
mods: Mods,
|
||||
additional: (string | undefined)[] = []
|
||||
): string => {
|
||||
return [
|
||||
cls,
|
||||
...additional.filter(Boolean),
|
||||
...Object.entries(mods)
|
||||
.filter(([className, value]) => Boolean(value))
|
||||
.map(([classNames]) => classNames),
|
||||
].join(" ");
|
||||
};
|
||||
cls: string,
|
||||
mods: Mods,
|
||||
additional: (string | undefined)[] = [],
|
||||
): string =>
|
||||
[
|
||||
cls,
|
||||
...additional.filter(Boolean),
|
||||
...Object.entries(mods)
|
||||
.filter(([className, value]) => Boolean(value))
|
||||
.map(([classNames]) => classNames),
|
||||
].join(' ');
|
||||
|
|
|
|||
|
|
@ -1,31 +1,19 @@
|
|||
export default class Formatters {
|
||||
static walletAddress(str: string): string {
|
||||
if (str.length > 20) {
|
||||
if (window.innerWidth > 768) {
|
||||
return (
|
||||
str.substring(0, 6) +
|
||||
"..." +
|
||||
str.substring(str.length - 6, str.length)
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
str.substring(0, 5) +
|
||||
"..." +
|
||||
str.substring(str.length - 5, str.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
static walletAddress(str: string): string {
|
||||
if (str.length > 20) {
|
||||
if (window.innerWidth > 768) {
|
||||
return `${str.substring(0, 6)}...${str.substring(str.length - 6, str.length)}`;
|
||||
}
|
||||
return `${str.substring(0, 5)}...${str.substring(str.length - 5, str.length)}`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static historyAmount(amount: number): string {
|
||||
let str = amount.toString();
|
||||
if (str.length > 10) {
|
||||
return (
|
||||
str.substring(0, 3) + "..." + str.substring(str.length - 3, str.length)
|
||||
);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
static historyAmount(amount: number): string {
|
||||
const str = amount.toString();
|
||||
if (str.length > 10) {
|
||||
return `${str.substring(0, 3)}...${str.substring(str.length - 3, str.length)}`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,158 +1,153 @@
|
|||
/*global chrome*/
|
||||
import Big from "big.js";
|
||||
import Decimal from "decimal.js";
|
||||
import sha256 from "sha256";
|
||||
/* global chrome */
|
||||
import Big from 'big.js';
|
||||
import Decimal from 'decimal.js';
|
||||
import sha256 from 'sha256';
|
||||
|
||||
interface BackgroundResponse {
|
||||
password: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface ValidationResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function fetchBackground(data: {
|
||||
method: string;
|
||||
password?: string;
|
||||
id?: number;
|
||||
success?: boolean;
|
||||
credentials?: { port: string };
|
||||
alias?: string;
|
||||
method: string;
|
||||
password?: string;
|
||||
id?: number;
|
||||
success?: boolean;
|
||||
credentials?: { port: string };
|
||||
alias?: string;
|
||||
}): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.runtime.sendMessage(data, function (response) {
|
||||
resolve(response);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error while fetching data (${data.method}):`, error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.runtime.sendMessage(data, (response) => {
|
||||
resolve(response);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error while fetching data (${data.method}):`, error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const removeZeros = (amount: string | number, decimal_point: number = 12): string => {
|
||||
const multiplier = new Big(10).pow(decimal_point);
|
||||
const bigAmount = new Big(amount);
|
||||
const fixedAmount = bigAmount.div(multiplier).toString();
|
||||
return fixedAmount;
|
||||
export const removeZeros = (amount: string | number, decimal_point = 12): string => {
|
||||
const multiplier = new Big(10).pow(decimal_point);
|
||||
const bigAmount = new Big(amount);
|
||||
const fixedAmount = bigAmount.div(multiplier).toString();
|
||||
return fixedAmount;
|
||||
};
|
||||
|
||||
export const addZeros = (amount: string | number, decimal_point: number = 12): Big => {
|
||||
const multiplier = new Big(10).pow(decimal_point);
|
||||
const bigAmount = new Big(amount);
|
||||
const fixedAmount = bigAmount.times(multiplier);
|
||||
return fixedAmount;
|
||||
export const addZeros = (amount: string | number, decimal_point = 12): Big => {
|
||||
const multiplier = new Big(10).pow(decimal_point);
|
||||
const bigAmount = new Big(amount);
|
||||
const fixedAmount = bigAmount.times(multiplier);
|
||||
return fixedAmount;
|
||||
};
|
||||
|
||||
export const setPassword = (password: string): void => {
|
||||
localStorage.setItem("hash", sha256(password));
|
||||
localStorage.setItem('hash', sha256(password));
|
||||
};
|
||||
|
||||
export const comparePasswords = (password: string): boolean => {
|
||||
const hash = localStorage.getItem("hash");
|
||||
return hash === sha256(password);
|
||||
const hash = localStorage.getItem('hash');
|
||||
return hash === sha256(password);
|
||||
};
|
||||
|
||||
export const passwordExists = (): boolean => {
|
||||
return !!localStorage.getItem("hash");
|
||||
};
|
||||
export const passwordExists = (): boolean => !!localStorage.getItem('hash');
|
||||
|
||||
export const getSessionPassword = async (): Promise<string> => {
|
||||
const sessionPass = (await fetchBackground({ method: "GET_PASSWORD" })) as BackgroundResponse;
|
||||
return sessionPass.password;
|
||||
const sessionPass = (await fetchBackground({ method: 'GET_PASSWORD' })) as BackgroundResponse;
|
||||
return sessionPass.password;
|
||||
};
|
||||
|
||||
export const setSessionPassword = async (password: string): Promise<void> => {
|
||||
await fetchBackground({ method: "SET_PASSWORD", password });
|
||||
await fetchBackground({ method: 'SET_PASSWORD', password });
|
||||
};
|
||||
|
||||
export function validateTokensInput(input: string | number, decimal_point: number): ValidationResult {
|
||||
if (typeof input === 'number') {
|
||||
input = input.toString();
|
||||
}
|
||||
export function validateTokensInput(
|
||||
input: string | number,
|
||||
decimal_point: number,
|
||||
): ValidationResult {
|
||||
if (typeof input === 'number') {
|
||||
input = input.toString();
|
||||
}
|
||||
|
||||
if (input === "") {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid input',
|
||||
};
|
||||
}
|
||||
if (input === '') {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid input',
|
||||
};
|
||||
}
|
||||
|
||||
input = input.replace(/[^0-9.,]/g, '');
|
||||
input = input.replace(/[^0-9.,]/g, '');
|
||||
|
||||
const MAX_NUMBER = new Decimal(2).pow(64).minus(1);
|
||||
const MAX_NUMBER = new Decimal(2).pow(64).minus(1);
|
||||
|
||||
if (decimal_point < 0 || decimal_point > 18) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid decimal point',
|
||||
};
|
||||
}
|
||||
if (decimal_point < 0 || decimal_point > 18) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid decimal point',
|
||||
};
|
||||
}
|
||||
|
||||
const dotInput = input.replace(/,/g, '');
|
||||
const dotInput = input.replace(/,/g, '');
|
||||
|
||||
const decimalDevider = new Decimal(10).pow(decimal_point);
|
||||
const decimalDevider = new Decimal(10).pow(decimal_point);
|
||||
|
||||
const maxAllowedNumber = MAX_NUMBER.div(decimalDevider);
|
||||
const maxAllowedNumber = MAX_NUMBER.div(decimalDevider);
|
||||
|
||||
const minAllowedNumber = new Decimal(1).div(decimalDevider);
|
||||
const minAllowedNumber = new Decimal(1).div(decimalDevider);
|
||||
|
||||
const rounded = (() => {
|
||||
if (dotInput.replace('.', '').length > 20) {
|
||||
const decimalParts = dotInput.split('.');
|
||||
const rounded = (() => {
|
||||
if (dotInput.replace('.', '').length > 20) {
|
||||
const decimalParts = dotInput.split('.');
|
||||
|
||||
if (decimalParts.length === 2 && decimalParts[1].length > 1) {
|
||||
const roundedInput = new Decimal(dotInput).toFixed(1);
|
||||
if (decimalParts.length === 2 && decimalParts[1].length > 1) {
|
||||
const roundedInput = new Decimal(dotInput).toFixed(1);
|
||||
|
||||
if (roundedInput.replace(/./g, '').length <= 20) {
|
||||
return roundedInput;
|
||||
}
|
||||
}
|
||||
if (roundedInput.replace(/./g, '').length <= 20) {
|
||||
return roundedInput;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return dotInput;
|
||||
}
|
||||
})();
|
||||
return false;
|
||||
}
|
||||
return dotInput;
|
||||
})();
|
||||
|
||||
if (rounded === false) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too big or has too many decimal points',
|
||||
};
|
||||
}
|
||||
if (rounded === false) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too big or has too many decimal points',
|
||||
};
|
||||
}
|
||||
|
||||
const dotInputDecimal = new Decimal(rounded);
|
||||
const dotInputDecimal = new Decimal(rounded);
|
||||
|
||||
if (dotInputDecimal.gt(maxAllowedNumber)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too big',
|
||||
};
|
||||
}
|
||||
if (dotInputDecimal.gt(maxAllowedNumber)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too big',
|
||||
};
|
||||
}
|
||||
|
||||
if (dotInputDecimal.lt(minAllowedNumber)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too small',
|
||||
};
|
||||
}
|
||||
if (dotInputDecimal.lt(minAllowedNumber)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid amount - number is too small',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true
|
||||
};
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const shortenAddress = (
|
||||
address: string | undefined,
|
||||
startAmount: number = 5,
|
||||
endAmount: number = 3
|
||||
) => {
|
||||
if (!address) {
|
||||
return "";
|
||||
}
|
||||
return address.slice(0, startAmount) + "..." + address.slice(-endAmount);
|
||||
};
|
||||
export const shortenAddress = (address: string | undefined, startAmount = 5, endAmount = 3) => {
|
||||
if (!address) {
|
||||
return '';
|
||||
}
|
||||
return `${address.slice(0, startAmount)}...${address.slice(-endAmount)}`;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,562 +1,530 @@
|
|||
import { addZeros, removeZeros } from "../app/utils/utils";
|
||||
import { apiCredentials } from "./background";
|
||||
import forge from "node-forge";
|
||||
import { Buffer } from "buffer";
|
||||
import JSONbig from "json-bigint";
|
||||
import { ZANO_ASSET_ID } from "../constants";
|
||||
import forge from 'node-forge';
|
||||
import { Buffer } from 'buffer';
|
||||
import JSONbig from 'json-bigint';
|
||||
import { apiCredentials } from './background';
|
||||
import { addZeros, removeZeros } from '../app/utils/utils';
|
||||
import { ZANO_ASSET_ID } from '../constants';
|
||||
// window.Buffer = Buffer;
|
||||
|
||||
interface JWTPayload {
|
||||
body_hash: string,
|
||||
user: string,
|
||||
salt: string,
|
||||
exp: number
|
||||
body_hash: string;
|
||||
user: string;
|
||||
salt: string;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
function createJWSToken(payload: JWTPayload, secretStr: string): string {
|
||||
const header = { alg: "HS256", typ: "JWT" };
|
||||
const encodedHeader = Buffer.from(JSON.stringify(header))
|
||||
.toString("base64")
|
||||
.replace(/=/g, "");
|
||||
const encodedPayload = Buffer.from(JSON.stringify(payload))
|
||||
.toString("base64")
|
||||
.replace(/=/g, "");
|
||||
const header = { alg: 'HS256', typ: 'JWT' };
|
||||
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64').replace(/=/g, '');
|
||||
const encodedPayload = Buffer.from(JSON.stringify(payload))
|
||||
.toString('base64')
|
||||
.replace(/=/g, '');
|
||||
|
||||
const signature = forge.hmac.create();
|
||||
signature.start("sha256", secretStr);
|
||||
signature.update(`${encodedHeader}.${encodedPayload}`);
|
||||
const encodedSignature = forge.util
|
||||
.encode64(signature.digest().getBytes())
|
||||
.replace(/=/g, "");
|
||||
const signature = forge.hmac.create();
|
||||
signature.start('sha256', secretStr);
|
||||
signature.update(`${encodedHeader}.${encodedPayload}`);
|
||||
const encodedSignature = forge.util.encode64(signature.digest().getBytes()).replace(/=/g, '');
|
||||
|
||||
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
||||
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
||||
}
|
||||
|
||||
function generateRandomString(length: number) {
|
||||
const bytes = forge.random.getBytesSync(Math.ceil(length / 2));
|
||||
const hexString = forge.util.bytesToHex(bytes);
|
||||
return hexString.substring(0, length);
|
||||
const bytes = forge.random.getBytesSync(Math.ceil(length / 2));
|
||||
const hexString = forge.util.bytesToHex(bytes);
|
||||
return hexString.substring(0, length);
|
||||
}
|
||||
|
||||
function generateAccessToken(httpBody: string) {
|
||||
if (!apiCredentials?.token) {
|
||||
throw new Error("No API credentials found, extension is not connected");
|
||||
}
|
||||
if (!apiCredentials?.token) {
|
||||
throw new Error('No API credentials found, extension is not connected');
|
||||
}
|
||||
|
||||
// Calculate the SHA-256 hash of the HTTP body
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(httpBody);
|
||||
const bodyHash = md.digest().toHex();
|
||||
// Calculate the SHA-256 hash of the HTTP body
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(httpBody);
|
||||
const bodyHash = md.digest().toHex();
|
||||
|
||||
// Example payload
|
||||
const payload = {
|
||||
body_hash: bodyHash,
|
||||
user: "zano_extension",
|
||||
salt: generateRandomString(64),
|
||||
exp: Math.floor(Date.now() / 1000) + 60, // Expires in 1 minute
|
||||
};
|
||||
// Example payload
|
||||
const payload = {
|
||||
body_hash: bodyHash,
|
||||
user: 'zano_extension',
|
||||
salt: generateRandomString(64),
|
||||
exp: Math.floor(Date.now() / 1000) + 60, // Expires in 1 minute
|
||||
};
|
||||
|
||||
return createJWSToken(payload, apiCredentials?.token);
|
||||
return createJWSToken(payload, apiCredentials?.token);
|
||||
}
|
||||
|
||||
interface fetchDataProps {
|
||||
offset: number,
|
||||
update_provision_info: boolean,
|
||||
exclude_mining_txs: boolean,
|
||||
count: number,
|
||||
order: string,
|
||||
exclude_unconfirmed: boolean,
|
||||
offset: number;
|
||||
update_provision_info: boolean;
|
||||
exclude_mining_txs: boolean;
|
||||
count: number;
|
||||
order: string;
|
||||
exclude_unconfirmed: boolean;
|
||||
}
|
||||
|
||||
export const fetchData = async (method: string, params: fetchDataProps | {} = {}): Promise<Response> => {
|
||||
const httpBody: string = JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: "0",
|
||||
method,
|
||||
params,
|
||||
});
|
||||
export const fetchData = async (
|
||||
method: string,
|
||||
params: fetchDataProps | object = {},
|
||||
): Promise<Response> => {
|
||||
const httpBody: string = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: '0',
|
||||
method,
|
||||
params,
|
||||
});
|
||||
|
||||
return fetch(`http://localhost:${apiCredentials.port}/json_rpc`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Zano-Access-Token": generateAccessToken(httpBody),
|
||||
},
|
||||
body: httpBody,
|
||||
});
|
||||
return fetch(`http://localhost:${apiCredentials.port}/json_rpc`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Zano-Access-Token': generateAccessToken(httpBody),
|
||||
},
|
||||
body: httpBody,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchTxData = async () => {
|
||||
try {
|
||||
const response = await fetchData("get_recent_txs_and_info2", {
|
||||
offset: 0,
|
||||
update_provision_info: true,
|
||||
exclude_mining_txs: true,
|
||||
count: 20,
|
||||
order: "FROM_END_TO_BEGIN",
|
||||
exclude_unconfirmed: false,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.text();
|
||||
const response = await fetchData('get_recent_txs_and_info2', {
|
||||
offset: 0,
|
||||
update_provision_info: true,
|
||||
exclude_mining_txs: true,
|
||||
count: 20,
|
||||
order: 'FROM_END_TO_BEGIN',
|
||||
exclude_unconfirmed: false,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.text();
|
||||
|
||||
return JSONbig.parse(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
throw error;
|
||||
}
|
||||
return JSONbig.parse(data);
|
||||
};
|
||||
|
||||
export const getAlias = async (address: string) => {
|
||||
const response = await fetchData("get_alias_by_address", address);
|
||||
const data = await response.json();
|
||||
if (data.result?.status === "OK") {
|
||||
return data.result.alias_info_list[0].alias;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
const response = await fetchData('get_alias_by_address', address);
|
||||
const data = await response.json();
|
||||
if (data.result?.status === 'OK') {
|
||||
return data.result.alias_info_list[0].alias;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getAliasDetails = async (alias: string) => {
|
||||
const response = await fetchData("get_alias_details", { alias });
|
||||
const data = await response.json();
|
||||
if (data.result.status === "OK") {
|
||||
return data.result.alias_details.address;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
const response = await fetchData('get_alias_details', { alias });
|
||||
const data = await response.json();
|
||||
if (data.result.status === 'OK') {
|
||||
return data.result.alias_details.address;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getWallets = async () => {
|
||||
try {
|
||||
const response = await fetchData("mw_get_wallets");
|
||||
const data = await response.json();
|
||||
const response = await fetchData('mw_get_wallets');
|
||||
const data = await response.json();
|
||||
|
||||
if (!data?.result?.wallets) {
|
||||
return [];
|
||||
}
|
||||
if (!data?.result?.wallets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// console.log("wallets:", data.result.wallets);
|
||||
// console.log("wallets:", data.result.wallets);
|
||||
|
||||
const wallets = await Promise.all(
|
||||
data.result.wallets.map(async (wallet: any) => {
|
||||
const alias = await getAlias(wallet.wi.address);
|
||||
const balance = wallet.wi.balances.find(
|
||||
(asset: any) =>
|
||||
asset.asset_info.asset_id ===
|
||||
ZANO_ASSET_ID
|
||||
).total;
|
||||
const wallets = await Promise.all(
|
||||
data.result.wallets.map(async (wallet: any) => {
|
||||
const alias = await getAlias(wallet.wi.address);
|
||||
const balance = wallet.wi.balances.find(
|
||||
(asset: any) => asset.asset_info.asset_id === ZANO_ASSET_ID,
|
||||
).total;
|
||||
|
||||
return {
|
||||
address: wallet.wi.address,
|
||||
alias: alias,
|
||||
balance: removeZeros(balance),
|
||||
is_watch_only: wallet?.wi?.is_watch_only,
|
||||
wallet_id: wallet.wallet_id,
|
||||
};
|
||||
})
|
||||
);
|
||||
return {
|
||||
address: wallet.wi.address,
|
||||
alias,
|
||||
balance: removeZeros(balance),
|
||||
is_watch_only: wallet?.wi?.is_watch_only,
|
||||
wallet_id: wallet.wallet_id,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return wallets
|
||||
.filter((e) => !e.is_watch_only)
|
||||
.map((e) => {
|
||||
delete e.is_watch_only;
|
||||
return e;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching wallet data:", error);
|
||||
throw error;
|
||||
}
|
||||
return wallets
|
||||
.filter((e) => !e.is_watch_only)
|
||||
.map((e) => {
|
||||
delete e.is_watch_only;
|
||||
return e;
|
||||
});
|
||||
};
|
||||
|
||||
export const getWalletData = async () => {
|
||||
const addressResponse = await fetchData("getaddress");
|
||||
const addressParsed = await addressResponse.json();
|
||||
const address = addressParsed.result.address;
|
||||
const balanceResponse = await fetchData("getbalance");
|
||||
const balanceParsed = JSONbig.parse(await balanceResponse.text());
|
||||
const addressResponse = await fetchData('getaddress');
|
||||
const addressParsed = await addressResponse.json();
|
||||
const { address } = addressParsed.result;
|
||||
const balanceResponse = await fetchData('getbalance');
|
||||
const balanceParsed = JSONbig.parse(await balanceResponse.text());
|
||||
|
||||
const assets = balanceParsed.result.balances
|
||||
.map((asset: any) => ({
|
||||
name: asset.asset_info.full_name,
|
||||
ticker: asset.asset_info.ticker,
|
||||
assetId: asset.asset_info.asset_id,
|
||||
decimalPoint: asset.asset_info.decimal_point,
|
||||
balance: removeZeros(asset.total, asset.asset_info.decimal_point),
|
||||
unlockedBalance: removeZeros(
|
||||
asset.unlocked,
|
||||
asset.asset_info.decimal_point
|
||||
),
|
||||
}))
|
||||
.sort((a: any, b: any) => {
|
||||
if (
|
||||
a.assetId ===
|
||||
ZANO_ASSET_ID
|
||||
) {
|
||||
return -1;
|
||||
} else if (
|
||||
b.assetId ===
|
||||
ZANO_ASSET_ID
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
const assets = balanceParsed.result.balances
|
||||
.map((asset: any) => ({
|
||||
name: asset.asset_info.full_name,
|
||||
ticker: asset.asset_info.ticker,
|
||||
assetId: asset.asset_info.asset_id,
|
||||
decimalPoint: asset.asset_info.decimal_point,
|
||||
balance: removeZeros(asset.total, asset.asset_info.decimal_point),
|
||||
unlockedBalance: removeZeros(asset.unlocked, asset.asset_info.decimal_point),
|
||||
}))
|
||||
.sort((a: any, b: any) => {
|
||||
if (a.assetId === ZANO_ASSET_ID) {
|
||||
return -1;
|
||||
}
|
||||
if (b.assetId === ZANO_ASSET_ID) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
function getAssetDecimalPoint(assetId: any) {
|
||||
return assets.find((asset: any) => asset.assetId === assetId)?.decimalPoint;
|
||||
}
|
||||
function getAssetDecimalPoint(assetId: any) {
|
||||
return assets.find((asset: any) => asset.assetId === assetId)?.decimalPoint;
|
||||
}
|
||||
|
||||
const balance = removeZeros(
|
||||
balanceParsed.result.balances.find(
|
||||
(asset: any) =>
|
||||
asset.asset_info.asset_id ===
|
||||
ZANO_ASSET_ID
|
||||
).total
|
||||
);
|
||||
const txDataResponse = await fetchTxData();
|
||||
const txData = txDataResponse.result.transfers;
|
||||
let transactions = [];
|
||||
const balance = removeZeros(
|
||||
balanceParsed.result.balances.find(
|
||||
(asset: any) => asset.asset_info.asset_id === ZANO_ASSET_ID,
|
||||
).total,
|
||||
);
|
||||
const txDataResponse = await fetchTxData();
|
||||
const txData = txDataResponse.result.transfers;
|
||||
let transactions = [];
|
||||
|
||||
if (txData) {
|
||||
transactions = txData
|
||||
.filter((tx: any) => !tx.is_service)
|
||||
.map((tx: any) => ({
|
||||
isConfirmed: tx.height === 0 ? false : true,
|
||||
txHash: tx.tx_hash,
|
||||
blobSize: tx.tx_blob_size,
|
||||
timestamp: tx.timestamp,
|
||||
height: tx.height,
|
||||
paymentId: tx.payment_id,
|
||||
comment: tx.comment,
|
||||
fee: removeZeros(tx.fee),
|
||||
addresses: tx.remote_addresses,
|
||||
isInitiator: !!tx.employed_entries?.spent?.some?.(
|
||||
(e: any) => e?.index === 0
|
||||
),
|
||||
transfers: tx.subtransfers.map((transfer: any) => ({
|
||||
amount: removeZeros(
|
||||
transfer.amount,
|
||||
getAssetDecimalPoint(transfer.asset_id) || 12
|
||||
),
|
||||
assetId: transfer.asset_id,
|
||||
incoming: transfer.is_income,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
if (txData) {
|
||||
transactions = txData
|
||||
.filter((tx: any) => !tx.is_service)
|
||||
.map((tx: any) => ({
|
||||
isConfirmed: tx.height !== 0,
|
||||
txHash: tx.tx_hash,
|
||||
blobSize: tx.tx_blob_size,
|
||||
timestamp: tx.timestamp,
|
||||
height: tx.height,
|
||||
paymentId: tx.payment_id,
|
||||
comment: tx.comment,
|
||||
fee: removeZeros(tx.fee),
|
||||
addresses: tx.remote_addresses,
|
||||
isInitiator: !!tx.employed_entries?.spent?.some?.((e: any) => e?.index === 0),
|
||||
transfers: tx.subtransfers.map((transfer: any) => ({
|
||||
amount: removeZeros(
|
||||
transfer.amount,
|
||||
getAssetDecimalPoint(transfer.asset_id) || 12,
|
||||
),
|
||||
assetId: transfer.asset_id,
|
||||
incoming: transfer.is_income,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
// console.log("get alias:", address);
|
||||
// console.log("get alias:", address);
|
||||
|
||||
const alias = await getAlias(address);
|
||||
return { address, alias, balance, transactions, assets };
|
||||
const alias = await getAlias(address);
|
||||
return {
|
||||
address,
|
||||
alias,
|
||||
balance,
|
||||
transactions,
|
||||
assets,
|
||||
};
|
||||
};
|
||||
|
||||
export const ionicSwap = async (swapParams: any) => {
|
||||
const response = await fetchData("ionic_swap_generate_proposal", {
|
||||
proposal: {
|
||||
to_initiator: [
|
||||
{
|
||||
asset_id: swapParams.destinationAssetID,
|
||||
amount: addZeros(
|
||||
swapParams.destinationAssetAmount,
|
||||
swapParams.destinationAsset.decimal_point
|
||||
),
|
||||
},
|
||||
],
|
||||
to_finalizer: [
|
||||
{
|
||||
asset_id: swapParams.currentAssetID,
|
||||
amount: addZeros(
|
||||
swapParams.currentAssetAmount,
|
||||
swapParams.currentAsset.decimal_point
|
||||
),
|
||||
},
|
||||
],
|
||||
mixins: 10,
|
||||
fee_paid_by_a: 10000000000,
|
||||
expiration_time: swapParams.expirationTimestamp,
|
||||
},
|
||||
destination_address: swapParams.destinationAddress,
|
||||
});
|
||||
const response = await fetchData('ionic_swap_generate_proposal', {
|
||||
proposal: {
|
||||
to_initiator: [
|
||||
{
|
||||
asset_id: swapParams.destinationAssetID,
|
||||
amount: addZeros(
|
||||
swapParams.destinationAssetAmount,
|
||||
swapParams.destinationAsset.decimal_point,
|
||||
),
|
||||
},
|
||||
],
|
||||
to_finalizer: [
|
||||
{
|
||||
asset_id: swapParams.currentAssetID,
|
||||
amount: addZeros(
|
||||
swapParams.currentAssetAmount,
|
||||
swapParams.currentAsset.decimal_point,
|
||||
),
|
||||
},
|
||||
],
|
||||
mixins: 10,
|
||||
fee_paid_by_a: 10000000000,
|
||||
expiration_time: swapParams.expirationTimestamp,
|
||||
},
|
||||
destination_address: swapParams.destinationAddress,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const ionicSwapAccept = async (swapParams: any) => {
|
||||
console.log(swapParams.hex_raw_proposal);
|
||||
console.log(swapParams.hex_raw_proposal);
|
||||
|
||||
const response = await fetchData("ionic_swap_accept_proposal", {
|
||||
hex_raw_proposal: swapParams.hex_raw_proposal,
|
||||
});
|
||||
const response = await fetchData('ionic_swap_accept_proposal', {
|
||||
hex_raw_proposal: swapParams.hex_raw_proposal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createAlias = async ({ alias, address }: any) => {
|
||||
const response = await fetchData("register_alias", {
|
||||
al: {
|
||||
address: address,
|
||||
alias: alias,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
return data;
|
||||
export const createAlias = async ({ alias, address }: { address: string; alias: string }) => {
|
||||
const response = await fetchData('register_alias', {
|
||||
al: {
|
||||
address,
|
||||
alias,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const transfer = async (
|
||||
assetId = ZANO_ASSET_ID,
|
||||
destination: string,
|
||||
amount: string,
|
||||
decimalPoint: any,
|
||||
comment?: string,
|
||||
destinations: { address: string; amount: number }[] = [],
|
||||
assetId = ZANO_ASSET_ID,
|
||||
destination: string,
|
||||
amount: string,
|
||||
decimalPoint: any,
|
||||
comment?: string,
|
||||
destinations: { address: string; amount: number }[] = [],
|
||||
) => {
|
||||
const allDestinations = destinations.length > 0
|
||||
? destinations.map(dest => ({
|
||||
address: dest.address,
|
||||
amount: addZeros(
|
||||
dest.amount,
|
||||
typeof decimalPoint === "number" ? decimalPoint : 12
|
||||
),
|
||||
asset_id: assetId
|
||||
}))
|
||||
: [{
|
||||
address: destination,
|
||||
amount: addZeros(
|
||||
amount,
|
||||
typeof decimalPoint === "number" ? decimalPoint : 12
|
||||
),
|
||||
asset_id: assetId
|
||||
}];
|
||||
const allDestinations =
|
||||
destinations.length > 0
|
||||
? destinations.map((dest) => ({
|
||||
address: dest.address,
|
||||
amount: addZeros(
|
||||
dest.amount,
|
||||
typeof decimalPoint === 'number' ? decimalPoint : 12,
|
||||
),
|
||||
asset_id: assetId,
|
||||
}))
|
||||
: [
|
||||
{
|
||||
address: destination,
|
||||
amount: addZeros(
|
||||
amount,
|
||||
typeof decimalPoint === 'number' ? decimalPoint : 12,
|
||||
),
|
||||
asset_id: assetId,
|
||||
},
|
||||
];
|
||||
|
||||
const options: { destinations: typeof allDestinations; fee: number; mixin: number; comment?: string } = {
|
||||
destinations: allDestinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
};
|
||||
const options: {
|
||||
destinations: typeof allDestinations;
|
||||
fee: number;
|
||||
mixin: number;
|
||||
comment?: string;
|
||||
} = {
|
||||
destinations: allDestinations,
|
||||
fee: 10000000000,
|
||||
mixin: 10,
|
||||
};
|
||||
|
||||
if (comment) options.comment = comment;
|
||||
if (comment) options.comment = comment;
|
||||
|
||||
const response = await fetchData("transfer", options);
|
||||
const response = await fetchData('transfer', options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// TODO: move bridge address to the config
|
||||
export const burnBridge = async (
|
||||
assetId = ZANO_ASSET_ID,
|
||||
amount: any,
|
||||
destinationAddress: any,
|
||||
destinationChainId: any
|
||||
assetId = ZANO_ASSET_ID,
|
||||
amount: any,
|
||||
destinationAddress: any,
|
||||
destinationChainId: any,
|
||||
) => {
|
||||
const bodyData = {
|
||||
service_id: "B",
|
||||
instruction: "BI",
|
||||
dst_add: destinationAddress,
|
||||
dst_net_id: destinationChainId,
|
||||
uniform_padding: " ",
|
||||
};
|
||||
const bodyData = {
|
||||
service_id: 'B',
|
||||
instruction: 'BI',
|
||||
dst_add: destinationAddress,
|
||||
dst_net_id: destinationChainId,
|
||||
uniform_padding: ' ',
|
||||
};
|
||||
|
||||
const jsonString = JSON.stringify(bodyData);
|
||||
const bytes = new TextEncoder().encode(jsonString);
|
||||
const bodyHex = Array.from(bytes, (byte) =>
|
||||
byte.toString(16).padStart(2, "0")
|
||||
).join("");
|
||||
const jsonString = JSON.stringify(bodyData);
|
||||
const bytes = new TextEncoder().encode(jsonString);
|
||||
const bodyHex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
const response = await fetchData("burn_asset", {
|
||||
fee: 10000000000,
|
||||
mixin: 15,
|
||||
service_entries_permanent: true,
|
||||
asset_id: assetId,
|
||||
burn_amount: addZeros(amount),
|
||||
service_entries: [
|
||||
{
|
||||
service_id: "X",
|
||||
instruction: "",
|
||||
security: "",
|
||||
body: bodyHex,
|
||||
flags: 5,
|
||||
},
|
||||
],
|
||||
point_tx_to_address:
|
||||
"ZxCzikmFWMZEX8z3nojPyzcFUeEYcihX2jFvhLLYvJqtdgne2RLFd6UDaPgmzMNgDZP71E7citLPei4pLCWDjUWS1qGzMuagu",
|
||||
native_amount: 10000000000,
|
||||
});
|
||||
const response = await fetchData('burn_asset', {
|
||||
fee: 10000000000,
|
||||
mixin: 15,
|
||||
service_entries_permanent: true,
|
||||
asset_id: assetId,
|
||||
burn_amount: addZeros(amount),
|
||||
service_entries: [
|
||||
{
|
||||
service_id: 'X',
|
||||
instruction: '',
|
||||
security: '',
|
||||
body: bodyHex,
|
||||
flags: 5,
|
||||
},
|
||||
],
|
||||
point_tx_to_address:
|
||||
'ZxCzikmFWMZEX8z3nojPyzcFUeEYcihX2jFvhLLYvJqtdgne2RLFd6UDaPgmzMNgDZP71E7citLPei4pLCWDjUWS1qGzMuagu',
|
||||
native_amount: 10000000000,
|
||||
});
|
||||
|
||||
// console.log(response);
|
||||
// console.log(response);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const signMessage = async (message: any) => {
|
||||
const base64 = Buffer.from(message).toString("base64");
|
||||
const base64 = Buffer.from(message).toString('base64');
|
||||
|
||||
const signRequest = {
|
||||
buff: base64,
|
||||
};
|
||||
const signRequest = {
|
||||
buff: base64,
|
||||
};
|
||||
|
||||
const response = await fetchData("sign_message", signRequest);
|
||||
const response = await fetchData('sign_message', signRequest);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
export const createConnectKey = async () => {
|
||||
return await fetch(
|
||||
`http://localhost:${apiCredentials.port}/connect-api-consumer`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then((r) => r.json());
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
export const createConnectKey = async () =>
|
||||
await fetch(`http://localhost:${apiCredentials.port}/connect-api-consumer`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((r) => r.json());
|
||||
|
||||
export const validateConnectKey = async (key: any) => {
|
||||
return await fetch(
|
||||
`http://localhost:${apiCredentials.port}/validate-connection-key`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ key }),
|
||||
}
|
||||
).then((r) => r.json());
|
||||
};
|
||||
export const validateConnectKey = async (key: any) =>
|
||||
await fetch(`http://localhost:${apiCredentials.port}/validate-connection-key`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ key }),
|
||||
}).then((r) => r.json());
|
||||
|
||||
export const getSwapProposalInfo = async (hex: any) => {
|
||||
const response = await fetchData("ionic_swap_get_proposal_info", {
|
||||
hex_raw_proposal: hex,
|
||||
});
|
||||
const response = await fetchData('ionic_swap_get_proposal_info', {
|
||||
hex_raw_proposal: hex,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
|
||||
return data;
|
||||
return data;
|
||||
};
|
||||
|
||||
export async function getWhiteList() {
|
||||
const fetchedWhiteList = await fetch(
|
||||
"https://api.zano.org/assets_whitelist.json"
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => data.assets);
|
||||
const fetchedWhiteList = await fetch('https://api.zano.org/assets_whitelist.json')
|
||||
.then((response) => response.json())
|
||||
.then((data) => data.assets);
|
||||
|
||||
if (
|
||||
fetchedWhiteList.every(
|
||||
(e: any) =>
|
||||
e.asset_id !==
|
||||
ZANO_ASSET_ID
|
||||
)
|
||||
) {
|
||||
fetchedWhiteList.push({
|
||||
asset_id:
|
||||
ZANO_ASSET_ID,
|
||||
ticker: "ZANO",
|
||||
full_name: "Zano",
|
||||
decimal_point: 12,
|
||||
});
|
||||
}
|
||||
if (fetchedWhiteList.every((e: any) => e.asset_id !== ZANO_ASSET_ID)) {
|
||||
fetchedWhiteList.push({
|
||||
asset_id: ZANO_ASSET_ID,
|
||||
ticker: 'ZANO',
|
||||
full_name: 'Zano',
|
||||
decimal_point: 12,
|
||||
});
|
||||
}
|
||||
|
||||
return fetchedWhiteList;
|
||||
return fetchedWhiteList;
|
||||
}
|
||||
|
||||
export async function getAssetInfo(assetId: any) {
|
||||
const response = await fetchData("get_asset_info", { asset_id: assetId });
|
||||
const response = await fetchData('get_asset_info', { asset_id: assetId });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function addAssetToWhitelist(assetId: string) {
|
||||
const response = await fetchData("assets_whitelist_add", {
|
||||
asset_id: assetId,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
|
||||
const response = await fetchData('assets_whitelist_add', {
|
||||
asset_id: assetId,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
|
||||
export const burnAsset = async ({
|
||||
assetId,
|
||||
burnAmount,
|
||||
decimalPoint = 12,
|
||||
nativeAmount = 0,
|
||||
pointTxToAddress,
|
||||
serviceEntries = [],
|
||||
assetId,
|
||||
burnAmount,
|
||||
decimalPoint = 12,
|
||||
nativeAmount = 0,
|
||||
pointTxToAddress,
|
||||
serviceEntries = [],
|
||||
}: {
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
decimalPoint?: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: {
|
||||
body: string;
|
||||
flags: number;
|
||||
instruction: string;
|
||||
security?: string;
|
||||
service_id: string;
|
||||
}[];
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
decimalPoint?: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: {
|
||||
body: string;
|
||||
flags: number;
|
||||
instruction: string;
|
||||
security?: string;
|
||||
service_id: string;
|
||||
}[];
|
||||
}) => {
|
||||
const params: any = {
|
||||
asset_id: assetId,
|
||||
burn_amount: addZeros(burnAmount, decimalPoint).toFixed(0),
|
||||
};
|
||||
const params: any = {
|
||||
asset_id: assetId,
|
||||
burn_amount: addZeros(burnAmount, decimalPoint).toFixed(0),
|
||||
};
|
||||
|
||||
if (nativeAmount) {
|
||||
params.native_amount = nativeAmount;
|
||||
}
|
||||
if (nativeAmount) {
|
||||
params.native_amount = nativeAmount;
|
||||
}
|
||||
|
||||
if (pointTxToAddress) {
|
||||
params.point_tx_to_address = pointTxToAddress;
|
||||
}
|
||||
if (pointTxToAddress) {
|
||||
params.point_tx_to_address = pointTxToAddress;
|
||||
}
|
||||
|
||||
if (serviceEntries.length > 0) {
|
||||
params.service_entries = serviceEntries;
|
||||
}
|
||||
if (serviceEntries.length > 0) {
|
||||
params.service_entries = serviceEntries;
|
||||
}
|
||||
|
||||
const response = await fetchData("burn_asset", params);
|
||||
const response = await fetchData('burn_asset', params);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export const ZANO_ASSET_ID = "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
|
||||
export const BANDIT_ASSET_ID = "55a8e0a730b133fb83915ba0e4335a680ae9d07a99642b17774460560f3b003d";
|
||||
export const ZANO_ASSET_ID = 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a';
|
||||
export const BANDIT_ASSET_ID = '55a8e0a730b133fb83915ba0e4335a680ae9d07a99642b17774460560f3b003d';
|
||||
|
|
|
|||
|
|
@ -1,55 +1,50 @@
|
|||
interface DocumentEventMap {
|
||||
"zano_request": CustomEvent<ZanoRequestData>;
|
||||
}
|
||||
|
||||
interface ZanoRequestData {
|
||||
method: string;
|
||||
listenerID: string;
|
||||
timeout?: number | null;
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
method: string;
|
||||
listenerID: string;
|
||||
timeout?: number | null;
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
}
|
||||
interface DocumentEventMap {
|
||||
zano_request: CustomEvent<ZanoRequestData>;
|
||||
}
|
||||
|
||||
interface ZanoResponse {
|
||||
error?: string;
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
error?: string;
|
||||
[key: string]: string | number | boolean | null | undefined;
|
||||
}
|
||||
|
||||
async function fetchData(data: ZanoRequestData): Promise<ZanoResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.runtime.sendMessage(data, (response: ZanoResponse) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error while fetching data (${data.method}):`, error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.runtime.sendMessage(data, (response: ZanoResponse) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("zano_request", async (e: CustomEvent<ZanoRequestData>) => {
|
||||
const data = e.detail;
|
||||
document.addEventListener('zano_request', async (e: CustomEvent<ZanoRequestData>) => {
|
||||
const data = e.detail;
|
||||
|
||||
try {
|
||||
const response = await fetchData(data);
|
||||
try {
|
||||
const response = await fetchData(data);
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`zano_response_${data.listenerID}`, {
|
||||
detail: response,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error while processing zano_request:`, error);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`zano_response_${data.listenerID}`, {
|
||||
detail: { error: error instanceof Error ? error.message : String(error) },
|
||||
})
|
||||
);
|
||||
}
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`zano_response_${data.listenerID}`, {
|
||||
detail: response,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(`zano_response_${data.listenerID}`, {
|
||||
detail: { error: error instanceof Error ? error.message : String(error) },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Zano wallet loaded");
|
||||
|
|
|
|||
|
|
@ -1,51 +1,64 @@
|
|||
class Zano {
|
||||
async request(method: string, params: Record<string, any>, timeoutParam?: number): Promise<any> {
|
||||
|
||||
function getRandonString(length: number): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
const charLength = chars.length;
|
||||
let result = '';
|
||||
async request(
|
||||
method: string,
|
||||
params: Record<string, unknown>,
|
||||
timeoutParam?: number,
|
||||
): Promise<unknown> {
|
||||
function getRandonString(length: number): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
const charLength = chars.length;
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * charLength));
|
||||
}
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * charLength));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const listenerID = getRandonString(16);
|
||||
const timeoutMs: number | null = typeof timeoutParam === "number" ? timeoutParam : null;
|
||||
const listenerID = getRandonString(16);
|
||||
const timeoutMs: number | null = typeof timeoutParam === 'number' ? timeoutParam : null;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout =
|
||||
timeoutMs !== null
|
||||
? setTimeout(() => {
|
||||
reject(new Error('Request timeout exceeded'));
|
||||
document.removeEventListener(
|
||||
`zano_response_${listenerID}`,
|
||||
handleResponse as EventListener,
|
||||
);
|
||||
}, timeoutMs)
|
||||
: undefined;
|
||||
|
||||
const timeout = timeoutMs !== null ? (
|
||||
setTimeout(() => {
|
||||
reject('Request timeout exceeded');
|
||||
document.removeEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
|
||||
}, timeoutMs)
|
||||
) : undefined;
|
||||
document.addEventListener(
|
||||
`zano_response_${listenerID}`,
|
||||
handleResponse as EventListener,
|
||||
);
|
||||
|
||||
function handleResponse(e: CustomEvent) {
|
||||
document.removeEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
resolve(e.detail);
|
||||
}
|
||||
function handleResponse(e: CustomEvent) {
|
||||
document.removeEventListener(
|
||||
`zano_response_${listenerID}`,
|
||||
handleResponse as EventListener,
|
||||
);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
resolve(e.detail);
|
||||
}
|
||||
|
||||
document.addEventListener(`zano_response_${listenerID}`, handleResponse as EventListener);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('zano_request', {
|
||||
detail: {
|
||||
method: method,
|
||||
listenerID: listenerID,
|
||||
timeout: timeoutMs,
|
||||
...params
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('zano_request', {
|
||||
detail: {
|
||||
method,
|
||||
listenerID,
|
||||
timeout: timeoutMs,
|
||||
...params,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.zano = new Zano();
|
||||
|
|
|
|||
39
src/global.d.ts
vendored
39
src/global.d.ts
vendored
|
|
@ -1,28 +1,27 @@
|
|||
interface Window {
|
||||
zano: Zano;
|
||||
zano: Zano;
|
||||
}
|
||||
declare module '*.scss' {
|
||||
const content: { [className: string]: string };
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.scss" {
|
||||
const content: { [className: string]: string };
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.svg" {
|
||||
const content: string;
|
||||
export default content;
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const value: string;
|
||||
export default value;
|
||||
declare module '*.png' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
declare module '*.jpg' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpeg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './app/App';
|
||||
import { StoreProvider } from './app/store/store-reducer';
|
||||
import App from './app/App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<StoreProvider>
|
||||
<App />
|
||||
</StoreProvider>
|
||||
</React.StrictMode>
|
||||
<React.StrictMode>
|
||||
<StoreProvider>
|
||||
<App />
|
||||
</StoreProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,96 +1,90 @@
|
|||
// Types
|
||||
export type dispatchType = () => void;
|
||||
export type destinationsType = { address: string, amount: number }[];
|
||||
export type destinationsType = { address: string; amount: number }[];
|
||||
|
||||
export type transferType = {
|
||||
transfer:
|
||||
{
|
||||
sender: string,
|
||||
destination: string,
|
||||
destinations: destinationsType,
|
||||
amount: string,
|
||||
asset: {
|
||||
ticker: string
|
||||
},
|
||||
comment?: string,
|
||||
}, id: number
|
||||
transfer: {
|
||||
sender: string;
|
||||
destination: string;
|
||||
destinations: destinationsType;
|
||||
amount: string;
|
||||
asset: {
|
||||
ticker: string;
|
||||
};
|
||||
comment?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type serviceEntriesType = {
|
||||
body: string;
|
||||
flags: number;
|
||||
instruction: string;
|
||||
security?: string;
|
||||
service_id: string;
|
||||
};
|
||||
|
||||
export type RequestType = {
|
||||
method: string;
|
||||
assetId: string,
|
||||
amount: string,
|
||||
destinationAddress: string,
|
||||
destinationChainId: string,
|
||||
burnAmount: string;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: any[];
|
||||
method: string;
|
||||
assetId: string;
|
||||
amount: string;
|
||||
destinationAddress: string;
|
||||
destinationChainId: string;
|
||||
burnAmount: string;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: serviceEntriesType[];
|
||||
};
|
||||
|
||||
export type SwapRequest = {
|
||||
id: string;
|
||||
swap: {
|
||||
destinationAddress: string;
|
||||
destinationAsset: string;
|
||||
destinationAssetAmount: string;
|
||||
currentAsset: string;
|
||||
currentAssetAmount: string;
|
||||
};
|
||||
id: string;
|
||||
swap: {
|
||||
destinationAddress: string;
|
||||
destinationAsset: string;
|
||||
destinationAssetAmount: string;
|
||||
currentAsset: string;
|
||||
currentAssetAmount: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type SwapProposal = {
|
||||
to_finalizer: { amount: Big }[];
|
||||
to_initiator: { amount: Big }[];
|
||||
to_finalizer: { amount: Big }[];
|
||||
to_initiator: { amount: Big }[];
|
||||
};
|
||||
|
||||
export type Asset = {
|
||||
decimal_point: number;
|
||||
[key: string]: any;
|
||||
decimal_point: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type AcceptSwapReq = {
|
||||
id: string;
|
||||
hex_raw_proposal: string;
|
||||
swapProposal: SwapProposal;
|
||||
receivingAsset: Asset;
|
||||
sendingAsset: Asset;
|
||||
id: string;
|
||||
hex_raw_proposal: string;
|
||||
swapProposal: SwapProposal;
|
||||
receivingAsset: Asset;
|
||||
sendingAsset: Asset;
|
||||
};
|
||||
|
||||
export type AssetWhitelistReq = {
|
||||
id: string;
|
||||
asset_id: string;
|
||||
asset_name: string;
|
||||
}
|
||||
id: string;
|
||||
asset_id: string;
|
||||
asset_name: string;
|
||||
};
|
||||
|
||||
export interface BurnAssetRequest {
|
||||
params: {
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: {
|
||||
body: string;
|
||||
flags: number;
|
||||
instruction: string;
|
||||
security?: string;
|
||||
service_id: string;
|
||||
}[];
|
||||
};
|
||||
[key: string]: any;
|
||||
params: {
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: serviceEntriesType[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface BurnAssetDataType {
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
decimalPoint?: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: {
|
||||
service_id: string;
|
||||
instruction: string;
|
||||
body: string;
|
||||
flags: number;
|
||||
security?: string;
|
||||
}[];
|
||||
}
|
||||
assetId: string;
|
||||
burnAmount: number;
|
||||
decimalPoint?: number;
|
||||
nativeAmount?: number;
|
||||
pointTxToAddress?: string;
|
||||
serviceEntries?: serviceEntriesType[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"allowJs": true,
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const path = require("path");
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
module: {
|
||||
|
|
@ -6,38 +6,38 @@ module.exports = {
|
|||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["babel-loader"],
|
||||
use: ['babel-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"],
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
"style-loader",
|
||||
'style-loader',
|
||||
{
|
||||
loader: "css-loader",
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
auto: /\.module\.\w+$/i,
|
||||
localIdentName: "[path][name]__[local]--[hash:base64:5]",
|
||||
localIdentName: '[path][name]__[local]--[hash:base64:5]',
|
||||
},
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
"sass-loader",
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: "url-loader",
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 8192,
|
||||
mimetype: "image/svg+xml",
|
||||
mimetype: 'image/svg+xml',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -46,10 +46,10 @@ module.exports = {
|
|||
test: /\.(png|jpe?g|gif)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: "file-loader",
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
outputPath: "static/images",
|
||||
name: "[name].[ext]",
|
||||
outputPath: 'static/images',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -57,11 +57,11 @@ module.exports = {
|
|||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: "ts-loader",
|
||||
use: 'ts-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ["*", ".js", ".jsx", ".ts", ".tsx"],
|
||||
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const common = require("./webpack.common.js");
|
||||
const path = require('path');
|
||||
const { merge } = require('webpack-merge');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
const appConfig = {
|
||||
entry: "./src/index.tsx",
|
||||
entry: './src/index.tsx',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build"),
|
||||
filename: "static/js/app.bundle.js",
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
filename: 'static/js/app.bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./public/index.html",
|
||||
filename: "index.html",
|
||||
inject: "body",
|
||||
template: './public/index.html',
|
||||
filename: 'index.html',
|
||||
inject: 'body',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
const backgroundConfig = {
|
||||
entry: "./src/background/background.ts",
|
||||
entry: './src/background/background.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build/static/js"),
|
||||
filename: "background.bundle.js",
|
||||
path: path.resolve(__dirname, 'build/static/js'),
|
||||
filename: 'background.bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
const contentConfig = {
|
||||
entry: "./src/content/content.ts",
|
||||
entry: './src/content/content.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build/static/js"),
|
||||
filename: "content.bundle.js",
|
||||
path: path.resolve(__dirname, 'build/static/js'),
|
||||
filename: 'content.bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
const injectConfig = {
|
||||
entry: "./src/content/inject.ts",
|
||||
entry: './src/content/inject.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build/static/js"),
|
||||
filename: "inject.bundle.js",
|
||||
path: path.resolve(__dirname, 'build/static/js'),
|
||||
filename: 'inject.bundle.js',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -47,17 +47,12 @@ const copyConfig = {
|
|||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: "public/images", to: "images" },
|
||||
{ from: "public/manifest.json", to: "manifest.json" },
|
||||
{ from: "public/robots.txt", to: "robots.txt" },
|
||||
{ from: 'public/images', to: 'images' },
|
||||
{ from: 'public/manifest.json', to: 'manifest.json' },
|
||||
{ from: 'public/robots.txt', to: 'robots.txt' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
merge(common, appConfig, copyConfig),
|
||||
merge(common, backgroundConfig),
|
||||
merge(common, contentConfig),
|
||||
merge(common, injectConfig),
|
||||
];
|
||||
module.exports = [merge(common, appConfig, copyConfig), merge(common, backgroundConfig), merge(common, contentConfig), merge(common, injectConfig)];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue