init: eslint

This commit is contained in:
AzizbekFayziyev 2025-06-22 14:20:38 +05:00
parent 1f193343a5
commit d49839b3e4
95 changed files with 39039 additions and 37706 deletions

View file

@ -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
View file

@ -0,0 +1,5 @@
node_modules/
build/
dist/
submodules/
**/*.js

View file

@ -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
View file

@ -0,0 +1,5 @@
node_modules
build
dist
public
**/*.js

View file

@ -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"
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
}
}

View file

@ -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>

View file

@ -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;

View file

@ -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) };
};

View file

@ -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;
}
}
}

View file

@ -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>
);
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}
}
}

View file

@ -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>
);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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>
);
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}*/

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}
}
}
}
}

View file

@ -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;

View file

@ -1,7 +1,3 @@
.settingsBody {
}
.settingsForm {
min-height: 410px;
}
min-height: 410px;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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 };
}

View file

@ -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 };
};

View file

@ -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,
};
};

View file

@ -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 };
}

View file

@ -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,
};
};

View file

@ -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,
};
};

View file

@ -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,
});

View file

@ -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>
);
};

View file

@ -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;
}

View file

@ -1,3 +1,3 @@
.swapAmount {
word-break: break-all;
}
word-break: break-all;
}

View file

@ -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%;
}
}
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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(' ');

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
};

View file

@ -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';

View file

@ -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");

View file

@ -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
View file

@ -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;
}

View file

@ -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>,
);

View file

@ -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[];
}

View file

@ -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"]
}

View file

@ -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'],
},
};

View file

@ -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)];