Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1266f39446 |
@@ -1,75 +0,0 @@
|
|||||||
name: ci-nym-wallet-storybook
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'nym-wallet/**'
|
|
||||||
- '.github/workflows/ci-nym-wallet-storybook.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: custom-linux
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install rsync
|
|
||||||
run: sudo apt-get install rsync
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- uses: rlespinasse/github-slug-action@v3.x
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Setup yarn
|
|
||||||
run: npm install -g yarn
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
|
|
||||||
- name: Install wasm-pack
|
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
||||||
|
|
||||||
- name: Build dependencies
|
|
||||||
run: yarn && yarn build
|
|
||||||
|
|
||||||
- name: Build storybook
|
|
||||||
run: yarn storybook:build
|
|
||||||
working-directory: ./nym-wallet
|
|
||||||
|
|
||||||
- name: Deploy branch to CI www (storybook)
|
|
||||||
continue-on-error: true
|
|
||||||
uses: easingthemes/ssh-deploy@main
|
|
||||||
env:
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
|
||||||
ARGS: "-rltgoDzvO --delete"
|
|
||||||
SOURCE: "nym-wallet/storybook-static/"
|
|
||||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
|
||||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
|
||||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
|
|
||||||
EXCLUDE: "/dist/, /node_modules/"
|
|
||||||
|
|
||||||
- name: Matrix - Node Install
|
|
||||||
run: npm install
|
|
||||||
working-directory: .github/workflows/support-files
|
|
||||||
|
|
||||||
- name: Matrix - Send Notification
|
|
||||||
env:
|
|
||||||
NYM_NOTIFICATION_KIND: nym-wallet
|
|
||||||
NYM_PROJECT_NAME: "nym-wallet"
|
|
||||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
|
||||||
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
|
|
||||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
|
||||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
|
||||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
|
||||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
|
||||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
|
|
||||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
|
||||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
|
||||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
|
||||||
uses: docker://keybaseio/client:stable-node
|
|
||||||
with:
|
|
||||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
>
|
>
|
||||||
> ➡️➡️➡️➡️➡️ **View output:**
|
> ➡️➡️➡️➡️➡️ **View output:**
|
||||||
>
|
>
|
||||||
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
|
|
||||||
>
|
|
||||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||||
>
|
>
|
||||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
|
||||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
|
||||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
|
||||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
|
||||||
framework: '@storybook/react',
|
|
||||||
core: {
|
|
||||||
builder: 'webpack5',
|
|
||||||
},
|
|
||||||
typescript: { reactDocgen: false },
|
|
||||||
// webpackFinal: async (config, { configType }) => {
|
|
||||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
|
||||||
// // You can change the configuration based on that.
|
|
||||||
// // 'PRODUCTION' is used when building the static version of storybook.
|
|
||||||
webpackFinal: async (config) => {
|
|
||||||
config.module.rules.forEach((rule) => {
|
|
||||||
// look for SVG import rule and replace
|
|
||||||
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
|
|
||||||
if (rule.test?.toString().includes('svg')) {
|
|
||||||
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle asset loading with this
|
|
||||||
config.module.rules.unshift({
|
|
||||||
test: /\.svg(\?.*)?$/i,
|
|
||||||
issuer: /\.[jt]sx?$/,
|
|
||||||
use: ['@svgr/webpack'],
|
|
||||||
});
|
|
||||||
|
|
||||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
|
||||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
|
||||||
|
|
||||||
config.plugins.push(
|
|
||||||
new ForkTsCheckerWebpackPlugin({
|
|
||||||
typescript: {
|
|
||||||
mode: 'write-references',
|
|
||||||
diagnosticOptions: {
|
|
||||||
semantic: true,
|
|
||||||
syntactic: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!config.resolve.alias) {
|
|
||||||
config.resolve.alias = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`;
|
|
||||||
|
|
||||||
// Return the altered config
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
features: {
|
|
||||||
emotionAlias: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use
|
|
||||||
* or import dependencies that use Tauri.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getVersion: () => undefined,
|
|
||||||
};
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use
|
|
||||||
* or import dependencies that use Tauri.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
invoke: () => undefined,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use
|
|
||||||
* or import dependencies that use Tauri.
|
|
||||||
*/
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
const delegations = [
|
|
||||||
{
|
|
||||||
mix_id: 1234,
|
|
||||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
amount: { amount: '10', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(100),
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.04',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '20',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
stake_saturation: '0.2',
|
|
||||||
avg_uptime_percent: 0.5,
|
|
||||||
accumulated_by_delegates: { amount: '0', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '0', denom: 'nym' },
|
|
||||||
uses_vesting_contract_tokens: false,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: false,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 5678,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
unclaimed_rewards: { amount: '0.1', denom: 'nym' },
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.04',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '60',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '0', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '0', denom: 'nym' },
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: false,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use
|
|
||||||
* or import dependencies that use Tauri.
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
invoke: (operation, args) => {
|
|
||||||
switch (operation) {
|
|
||||||
case 'get_balance': {
|
|
||||||
return {
|
|
||||||
amount: {
|
|
||||||
amount: '100',
|
|
||||||
denom: 'nymt',
|
|
||||||
},
|
|
||||||
printable_balance: '100 NYMT',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'delegate_to_mixnode': {
|
|
||||||
return {
|
|
||||||
logs_json: '[]',
|
|
||||||
data_json: '{}',
|
|
||||||
transaction_hash: '12345',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'simulate_send': {
|
|
||||||
return {
|
|
||||||
amount: {
|
|
||||||
amount: '0.01',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'get_delegation_summary': {
|
|
||||||
return {
|
|
||||||
delegations,
|
|
||||||
total_delegations: {
|
|
||||||
amount: '1000',
|
|
||||||
denom: 'nymt',
|
|
||||||
},
|
|
||||||
total_rewards: {
|
|
||||||
amount: '42',
|
|
||||||
denom: 'nymt',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'get_pending_delegation_events' : {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
case 'migrate_vested_delegations': {
|
|
||||||
delegations[1].uses_vesting_contract_tokens = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(
|
|
||||||
`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_wallet/.storybook/mocks/tauri.js" if you need. The default response is "void".`,
|
|
||||||
);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
reject(new Error(`Tauri operation ${operation} not available in storybook.`));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use
|
|
||||||
* or import dependencies that use Tauri.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
appWindow: {
|
|
||||||
maximize: () => undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { NymWalletThemeWithMode } from '../src/theme/NymWalletTheme';
|
|
||||||
import { Box } from '@mui/material';
|
|
||||||
|
|
||||||
export const parameters = {
|
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
|
||||||
controls: {
|
|
||||||
matchers: {
|
|
||||||
color: /(background|color)$/i,
|
|
||||||
date: /Date$/,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const withThemeProvider = (Story, context) => (
|
|
||||||
<div style={{ display: 'grid', height: '100%', gridTemplateColumns: '50% 50%' }}>
|
|
||||||
<div>
|
|
||||||
<NymWalletThemeWithMode mode="light">
|
|
||||||
<Box
|
|
||||||
p={4}
|
|
||||||
sx={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateRows: '80vh 2rem',
|
|
||||||
background: (theme) => theme.palette.background.default,
|
|
||||||
color: 'text.primary',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ overflowY: 'auto' }}>
|
|
||||||
<Story {...context} />
|
|
||||||
</Box>
|
|
||||||
<h4 style={{ textAlign: 'center' }}>Light mode</h4>
|
|
||||||
</Box>
|
|
||||||
</NymWalletThemeWithMode>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<NymWalletThemeWithMode mode="dark">
|
|
||||||
<Box
|
|
||||||
p={4}
|
|
||||||
sx={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateRows: '80vh 2rem',
|
|
||||||
background: (theme) => theme.palette.background.default,
|
|
||||||
color: 'text.primary',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ overflowY: 'auto' }}>
|
|
||||||
<Story {...context} />
|
|
||||||
</Box>
|
|
||||||
<h4 style={{ textAlign: 'center' }}>Dark mode</h4>
|
|
||||||
</Box>
|
|
||||||
</NymWalletThemeWithMode>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const decorators = [withThemeProvider];
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Theme } from '@mui/material/styles';
|
|
||||||
|
|
||||||
export const backDropStyles = (theme: Theme) => {
|
|
||||||
const { mode } = theme.palette;
|
|
||||||
return {
|
|
||||||
style: {
|
|
||||||
left: mode === 'light' ? '0' : '50%',
|
|
||||||
width: '50%',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const modalStyles = (theme: Theme) => {
|
|
||||||
const { mode } = theme.palette;
|
|
||||||
return { left: mode === 'light' ? '25%' : '75%' };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dialogStyles = (theme: Theme) => {
|
|
||||||
const { mode } = theme.palette;
|
|
||||||
return { left: mode === 'light' ? '-50%' : '50%' };
|
|
||||||
};
|
|
||||||
Vendored
@@ -10,10 +10,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"lint:fix": "eslint src --fix",
|
"lint:fix": "eslint src --fix",
|
||||||
"prebuild": "yarn --cwd .. build",
|
"prebuild": "yarn --cwd .. build",
|
||||||
"prestorybook": "yarn --cwd .. build",
|
|
||||||
"prewebpack:dev": "yarn --cwd .. build",
|
"prewebpack:dev": "yarn --cwd .. build",
|
||||||
"storybook": "start-storybook -p 6006",
|
|
||||||
"storybook:build": "build-storybook",
|
|
||||||
"tauri:build": "yarn tauri build",
|
"tauri:build": "yarn tauri build",
|
||||||
"tauri:dev": "yarn tauri dev",
|
"tauri:dev": "yarn tauri dev",
|
||||||
"tauri:buildx86": "yarn tauri build --target x86_64-apple-darwin",
|
"tauri:buildx86": "yarn tauri build --target x86_64-apple-darwin",
|
||||||
@@ -35,7 +32,6 @@
|
|||||||
"@nymproject/node-tester": "^1.2.3",
|
"@nymproject/node-tester": "^1.2.3",
|
||||||
"@nymproject/react": "^1.0.0",
|
"@nymproject/react": "^1.0.0",
|
||||||
"@nymproject/types": "^1.0.0",
|
"@nymproject/types": "^1.0.0",
|
||||||
"@storybook/react": "^6.5.15",
|
|
||||||
"@tauri-apps/api": "^2.4.0",
|
"@tauri-apps/api": "^2.4.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.6",
|
"@tauri-apps/plugin-opener": "^2.2.6",
|
||||||
@@ -72,7 +68,6 @@
|
|||||||
"@babel/preset-typescript": "^7.15.0",
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||||
"@storybook/react": "^6.5.15",
|
|
||||||
"@svgr/webpack": "^6.1.1",
|
"@svgr/webpack": "^6.1.1",
|
||||||
"@tauri-apps/cli": "^2.4.0",
|
"@tauri-apps/cli": "^2.4.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
@@ -105,8 +100,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-react": "^7.29.2",
|
"eslint-plugin-react": "^7.29.2",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"eslint-plugin-storybook": "^0.5.12",
|
|
||||||
"favicons": "^7.0.2",
|
"favicons": "^7.0.2",
|
||||||
"favicons-webpack-plugin": "^5.0.2",
|
"favicons-webpack-plugin": "^5.0.2",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box } from '@mui/material';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { MockAccountsProvider } from 'src/context/mocks/accounts';
|
|
||||||
import { Accounts } from '../Accounts';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Multi Account',
|
|
||||||
component: Accounts,
|
|
||||||
} as ComponentMeta<typeof Accounts>;
|
|
||||||
|
|
||||||
export const Default: ComponentStory<typeof Accounts> = () => (
|
|
||||||
<Box display="flex" alignContent="center">
|
|
||||||
<MockAccountsProvider>
|
|
||||||
<Accounts />
|
|
||||||
</MockAccountsProvider>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Tutorial } from './Tutorial';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Buy/Tutorial',
|
|
||||||
component: Tutorial,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TutorialPage = () => <Tutorial />;
|
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Typography, Grid, Link, Card, CardContent, Stack } from '@mui/material';
|
import { Box, Typography, Grid, Link, Card, CardContent, Stack } from '@mui/material';
|
||||||
import { NymCard } from '..';
|
|
||||||
import BitfinexIcon from 'src/svg-icons/bitfinex.svg';
|
import BitfinexIcon from 'src/svg-icons/bitfinex.svg';
|
||||||
import KrakenIcon from 'src/svg-icons/kraken.svg';
|
import KrakenIcon from 'src/svg-icons/kraken.svg';
|
||||||
import BybitIcon from 'src/svg-icons/bybit.svg';
|
import BybitIcon from 'src/svg-icons/bybit.svg';
|
||||||
import GateIcon from 'src/svg-icons/gate22.svg';
|
import GateIcon from 'src/svg-icons/gate22.svg';
|
||||||
import HTXIcon from 'src/svg-icons/htx.svg';
|
import HTXIcon from 'src/svg-icons/htx.svg';
|
||||||
|
import { NymCard } from '..';
|
||||||
|
|
||||||
const ExchangeCard = ({
|
const ExchangeCard = ({
|
||||||
name,
|
name,
|
||||||
tokenType,
|
tokenType,
|
||||||
url,
|
url,
|
||||||
IconComponent
|
IconComponent,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
tokenType: string;
|
tokenType: string;
|
||||||
url: string;
|
url: string;
|
||||||
IconComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
IconComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
||||||
}) => (
|
}) => (
|
||||||
<Card
|
<Card
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
transition: 'all 0.2s ease-in-out',
|
transition: 'all 0.2s ease-in-out',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'translateY(-2px)',
|
transform: 'translateY(-2px)',
|
||||||
boxShadow: 2,
|
boxShadow: 2,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent sx={{ p: 3 }}>
|
<CardContent sx={{ p: 3 }}>
|
||||||
@@ -51,17 +51,17 @@ const ExchangeCard = ({
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{tokenType}
|
{tokenType}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Link
|
<Link
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
variant="body2"
|
variant="body2"
|
||||||
data-testid="link-get-nym"
|
data-testid="link-get-nym"
|
||||||
sx={{
|
sx={{
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
GET NYM
|
GET NYM
|
||||||
@@ -78,44 +78,40 @@ export const Tutorial = () => {
|
|||||||
name: 'Bitfinex',
|
name: 'Bitfinex',
|
||||||
tokenType: 'Native NYM, ERC-20',
|
tokenType: 'Native NYM, ERC-20',
|
||||||
url: 'https://www.bitfinex.com/',
|
url: 'https://www.bitfinex.com/',
|
||||||
IconComponent: BitfinexIcon
|
IconComponent: BitfinexIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Kraken',
|
name: 'Kraken',
|
||||||
tokenType: 'Native NYM',
|
tokenType: 'Native NYM',
|
||||||
url: 'https://www.kraken.com/',
|
url: 'https://www.kraken.com/',
|
||||||
IconComponent: KrakenIcon
|
IconComponent: KrakenIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Bybit',
|
name: 'Bybit',
|
||||||
tokenType: 'ERC-20',
|
tokenType: 'ERC-20',
|
||||||
url: 'https://www.bybit.com/en/',
|
url: 'https://www.bybit.com/en/',
|
||||||
IconComponent: BybitIcon
|
IconComponent: BybitIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Gate.io',
|
name: 'Gate.io',
|
||||||
tokenType: 'ERC-20',
|
tokenType: 'ERC-20',
|
||||||
url: 'https://www.gate.io/',
|
url: 'https://www.gate.io/',
|
||||||
IconComponent: GateIcon
|
IconComponent: GateIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'HTX',
|
name: 'HTX',
|
||||||
tokenType: 'ERC-20',
|
tokenType: 'ERC-20',
|
||||||
url: 'https://www.htx.com/',
|
url: 'https://www.htx.com/',
|
||||||
IconComponent: HTXIcon
|
IconComponent: HTXIcon,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NymCard
|
<NymCard borderless title="Where you can get NYM tokens" sx={{ mt: 4 }}>
|
||||||
borderless
|
|
||||||
title="Where you can get NYM tokens"
|
|
||||||
sx={{ mt: 4 }}
|
|
||||||
>
|
|
||||||
<Typography mb={3} fontSize={14} sx={{ color: 'text.secondary' }}>
|
<Typography mb={3} fontSize={14} sx={{ color: 'text.secondary' }}>
|
||||||
You can get NYM tokens from these exchanges
|
You can get NYM tokens from these exchanges
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
{exchanges.map((exchange) => (
|
{exchanges.map((exchange) => (
|
||||||
<Grid item xs={12} md={6} lg={4} key={exchange.name}>
|
<Grid item xs={12} md={6} lg={4} key={exchange.name}>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { ConfirmTx } from './ConfirmTX';
|
|
||||||
import { ModalListItem } from './Modals/ModalListItem';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Confirm Transaction',
|
|
||||||
component: ConfirmTx,
|
|
||||||
} as ComponentMeta<typeof ConfirmTx>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
|
|
||||||
<ConfirmTx {...args}>
|
|
||||||
<ModalListItem label="Transaction type" value="Bond" divider />
|
|
||||||
<ModalListItem label="Current bond" value="100 NYM" divider />
|
|
||||||
<ModalListItem label="Additional bond" value="50 NYM" divider />
|
|
||||||
</ConfirmTx>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
Default.args = {
|
|
||||||
open: true,
|
|
||||||
header: 'Confirm transaction',
|
|
||||||
subheader: 'Confirm and proceed or cancel transaction',
|
|
||||||
fee: { amount: { amount: '0.001', denom: 'nym' }, fee: { Auto: null } },
|
|
||||||
onClose: () => {},
|
|
||||||
onConfirm: async () => {},
|
|
||||||
onPrev: () => {},
|
|
||||||
isStorybook: true,
|
|
||||||
};
|
|
||||||
@@ -5,15 +5,6 @@ import { useTheme, Theme } from '@mui/material/styles';
|
|||||||
import { SimpleModal } from './Modals/SimpleModal';
|
import { SimpleModal } from './Modals/SimpleModal';
|
||||||
import { ModalFee } from './Modals/ModalFee';
|
import { ModalFee } from './Modals/ModalFee';
|
||||||
import { ModalDivider } from './Modals/ModalDivider';
|
import { ModalDivider } from './Modals/ModalDivider';
|
||||||
import { backDropStyles, modalStyles } from '../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
|
||||||
isStorybook
|
|
||||||
? {
|
|
||||||
backdropProps: { ...backDropStyles(theme), ...backdropProps },
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
export const ConfirmTx: FCWithChildren<{
|
export const ConfirmTx: FCWithChildren<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -23,9 +14,9 @@ export const ConfirmTx: FCWithChildren<{
|
|||||||
onConfirm: () => Promise<void>;
|
onConfirm: () => Promise<void>;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onPrev: () => void;
|
onPrev: () => void;
|
||||||
isStorybook?: boolean;
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children, isStorybook }) => {
|
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<SimpleModal
|
<SimpleModal
|
||||||
@@ -36,7 +27,6 @@ export const ConfirmTx: FCWithChildren<{
|
|||||||
onOk={onConfirm}
|
onOk={onConfirm}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onBack={onPrev}
|
onBack={onPrev}
|
||||||
{...storybookStyles(theme, isStorybook)}
|
|
||||||
>
|
>
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
import { useTheme } from '@mui/material/styles';
|
|
||||||
import { Button, Paper, Typography } from '@mui/material';
|
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
import { OverSaturatedBlockerModal } from './DelegateBlocker';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Delegation Over Saturated Warning Modal',
|
|
||||||
component: OverSaturatedBlockerModal,
|
|
||||||
} as ComponentMeta<typeof OverSaturatedBlockerModal>;
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Lorem ipsum</h2>
|
|
||||||
<Button variant="contained" onClick={handleClick} sx={{ mb: 3 }}>
|
|
||||||
Show modal
|
|
||||||
</Button>
|
|
||||||
<Typography>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis
|
|
||||||
sunt velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur
|
|
||||||
culpa exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
<OverSaturatedBlockerModal
|
|
||||||
open={open}
|
|
||||||
header="Node saturation: 114%"
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
backdropProps={backDropStyles(theme)}
|
|
||||||
sx={modalStyles(theme)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { DelegationActions } from './DelegationActions';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Delegation List Item Actions',
|
|
||||||
component: DelegationActions,
|
|
||||||
} as ComponentMeta<typeof DelegationActions>;
|
|
||||||
|
|
||||||
export const Default = () => <DelegationActions />;
|
|
||||||
|
|
||||||
export const RedeemingDisabled = () => <DelegationActions disableRedeemingRewards />;
|
|
||||||
|
|
||||||
export const PendingDelegation = () => <DelegationActions isPending={{ actionType: 'delegate', blockHeight: 1000 }} />;
|
|
||||||
|
|
||||||
export const PendingUndelegation = () => (
|
|
||||||
<DelegationActions isPending={{ actionType: 'undelegate', blockHeight: 1000 }} />
|
|
||||||
);
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { DelegationWithEverything } from '@nymproject/types';
|
|
||||||
import { DelegationList } from './DelegationList';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Delegation List',
|
|
||||||
component: DelegationList,
|
|
||||||
} as ComponentMeta<typeof DelegationList>;
|
|
||||||
|
|
||||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net/network-components/mixnodes';
|
|
||||||
|
|
||||||
export const items: DelegationWithEverything[] = [
|
|
||||||
{
|
|
||||||
mix_id: 1,
|
|
||||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
amount: { amount: '10', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(100),
|
|
||||||
stake_saturation: '0.25',
|
|
||||||
avg_uptime_percent: 0.5,
|
|
||||||
uses_vesting_contract_tokens: false,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 2,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '1010', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '200', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.43',
|
|
||||||
avg_uptime_percent: 0.22,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 3,
|
|
||||||
node_identity: '',
|
|
||||||
amount: { amount: '300', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '50',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '300', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 4,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '201', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '60',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '202', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 5,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '80',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 6,
|
|
||||||
node_identity: '',
|
|
||||||
amount: { amount: '202', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.8',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 7,
|
|
||||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
amount: { amount: '202', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.59',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(100),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.5,
|
|
||||||
uses_vesting_contract_tokens: false,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 8,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.9',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 9,
|
|
||||||
node_identity: '',
|
|
||||||
amount: { amount: '1000', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.4',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.9',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 10,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 11,
|
|
||||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.56',
|
|
||||||
avg_uptime_percent: 0.9,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mix_id: 12,
|
|
||||||
node_identity: '',
|
|
||||||
amount: { amount: '100', denom: 'nym' },
|
|
||||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
|
||||||
unclaimed_rewards: { amount: '0.05', denom: 'nym' },
|
|
||||||
cost_params: {
|
|
||||||
profit_margin_percent: '0.1122323949234',
|
|
||||||
interval_operating_cost: {
|
|
||||||
amount: '40',
|
|
||||||
denom: 'nym',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accumulated_by_delegates: { amount: '50', denom: 'nym' },
|
|
||||||
accumulated_by_operator: { amount: '100', denom: 'nym' },
|
|
||||||
owner: '',
|
|
||||||
block_height: BigInt(4000),
|
|
||||||
stake_saturation: '0.5',
|
|
||||||
avg_uptime_percent: 0.1,
|
|
||||||
uses_vesting_contract_tokens: true,
|
|
||||||
pending_events: [],
|
|
||||||
mixnode_is_unbonding: true,
|
|
||||||
errors: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const WithData = () => <DelegationList items={items} explorerUrl={explorerUrl} />;
|
|
||||||
|
|
||||||
export const Empty = () => <DelegationList items={[]} explorerUrl={explorerUrl} />;
|
|
||||||
|
|
||||||
export const OneItem = () => <DelegationList items={[items[0]]} explorerUrl={explorerUrl} />;
|
|
||||||
|
|
||||||
export const Loading = () => <DelegationList items={[]} isLoading explorerUrl={explorerUrl} />;
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Paper, Button } from '@mui/material';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { Delegations } from './Delegations';
|
|
||||||
import { items } from './DelegationList.stories';
|
|
||||||
import { DelegationModal } from './DelegationModal';
|
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Delegation Modals',
|
|
||||||
component: Delegations,
|
|
||||||
} as ComponentMeta<typeof Delegations>;
|
|
||||||
|
|
||||||
const transaction = {
|
|
||||||
url: 'https://sandbox-blocks.nymtech.net/transactions/11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFB',
|
|
||||||
hash: '11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFB',
|
|
||||||
};
|
|
||||||
// Another transaction for Dark Theme to avoid duplicate key errors in rendering
|
|
||||||
const transactionForDarkTheme = {
|
|
||||||
url: 'https://sandbox-blocks.nymtech.net/transactions/11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFO',
|
|
||||||
hash: '11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CF0',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Content: FCWithChildren<{ children: React.ReactElement<any, any>; handleClick: () => void }> = ({
|
|
||||||
children,
|
|
||||||
handleClick,
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Your Delegations</h2>
|
|
||||||
<Button variant="contained" onClick={handleClick} sx={{ mb: 3 }}>
|
|
||||||
Show modal
|
|
||||||
</Button>
|
|
||||||
<Delegations items={items} explorerUrl={explorerUrl} />
|
|
||||||
</Paper>
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Loading = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="loading"
|
|
||||||
action="delegate"
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DelegateSuccess = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="success"
|
|
||||||
action="delegate"
|
|
||||||
message="You delegated 5 NYM"
|
|
||||||
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UndelegateSuccess = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="success"
|
|
||||||
action="undelegate"
|
|
||||||
message="You undelegated 5 NYM"
|
|
||||||
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RedeemSuccess = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="success"
|
|
||||||
action="redeem"
|
|
||||||
message="42 NYM"
|
|
||||||
transactions={
|
|
||||||
theme.palette.mode === 'light'
|
|
||||||
? [transaction, transaction]
|
|
||||||
: [transactionForDarkTheme, transactionForDarkTheme]
|
|
||||||
}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RedeemWithVestedSuccess = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="success"
|
|
||||||
action="redeem"
|
|
||||||
message="42 NYM"
|
|
||||||
transactions={
|
|
||||||
theme.palette.mode === 'light'
|
|
||||||
? [transaction, transaction]
|
|
||||||
: [transactionForDarkTheme, transactionForDarkTheme]
|
|
||||||
}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RedeemAllSuccess = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="success"
|
|
||||||
action="redeem-all"
|
|
||||||
message="42 NYM"
|
|
||||||
transactions={
|
|
||||||
theme.palette.mode === 'light'
|
|
||||||
? [transaction, transaction]
|
|
||||||
: [transactionForDarkTheme, transactionForDarkTheme]
|
|
||||||
}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Error = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Content handleClick={handleClick}>
|
|
||||||
<DelegationModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
status="error"
|
|
||||||
action="redeem-all"
|
|
||||||
message="Minim esse veniam Lorem id velit Lorem eu eu est. Excepteur labore sunt do proident proident sint aliquip consequat Lorem sint non nulla ad excepteur."
|
|
||||||
transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Paper } from '@mui/material';
|
|
||||||
import { Delegations } from './Delegations';
|
|
||||||
import { items } from './DelegationList.stories';
|
|
||||||
|
|
||||||
const explorerUrl = 'https://sandbox-explorer.nymtech.net';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Delegations',
|
|
||||||
component: Delegations,
|
|
||||||
} as ComponentMeta<typeof Delegations>;
|
|
||||||
|
|
||||||
export const Default = () => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Your Delegations</h2>
|
|
||||||
<Delegations items={items} explorerUrl={explorerUrl} />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Empty = () => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Your Delegations</h2>
|
|
||||||
<Delegations items={[]} explorerUrl={explorerUrl} />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Button, Paper, Typography } from '@mui/material';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { DelegateModal } from './DelegateModal';
|
|
||||||
import { UndelegateModal } from './UndelegateModal';
|
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Components/Action Modals',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Background: FCWithChildren<{ onOpen: () => void }> = ({ onOpen }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Lorem ipsum</h2>
|
|
||||||
<Button variant="contained" onClick={onOpen}>
|
|
||||||
Show modal
|
|
||||||
</Button>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt
|
|
||||||
velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa
|
|
||||||
exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad
|
|
||||||
tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor
|
|
||||||
culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut
|
|
||||||
mollit.
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat
|
|
||||||
duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo
|
|
||||||
est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation
|
|
||||||
in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea
|
|
||||||
aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis
|
|
||||||
excepteur minim culpa velit Lorem enim ad.
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim
|
|
||||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute
|
|
||||||
irure anim dolore ipsum et veniam.
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Delegate = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Background onOpen={() => setOpen(true)} />
|
|
||||||
<DelegateModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
denom="nym"
|
|
||||||
estimatedReward={50.423}
|
|
||||||
accountBalance="425.2345053"
|
|
||||||
nodeUptimePercentage={99.28394}
|
|
||||||
profitMarginPercentage="11.12334234"
|
|
||||||
rewardInterval="monthlyish"
|
|
||||||
hasVestingContract={false}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DelegateBelowMinimum = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Background onOpen={() => setOpen(true)} />
|
|
||||||
<DelegateModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
denom="nym"
|
|
||||||
estimatedReward={425.2345053}
|
|
||||||
nodeUptimePercentage={99.28394}
|
|
||||||
profitMarginPercentage="11.12334234"
|
|
||||||
rewardInterval="monthlyish"
|
|
||||||
initialAmount="0.1"
|
|
||||||
hasVestingContract={false}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DelegateMore = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Background onOpen={() => setOpen(true)} />
|
|
||||||
<DelegateModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="Delegate more"
|
|
||||||
buttonText="Delegate more"
|
|
||||||
denom="nym"
|
|
||||||
estimatedReward={50.423}
|
|
||||||
accountBalance="425.2345053"
|
|
||||||
nodeUptimePercentage={99.28394}
|
|
||||||
profitMarginPercentage="11.12334234"
|
|
||||||
rewardInterval="monthlyish"
|
|
||||||
hasVestingContract={false}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Undelegate = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Background onOpen={() => setOpen(true)} />
|
|
||||||
<UndelegateModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
currency="nym"
|
|
||||||
amount={150}
|
|
||||||
mixId={1234}
|
|
||||||
identityKey="AA6RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujyxx"
|
|
||||||
usesVestingContractTokens={false}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { Box } from '@mui/material';
|
|
||||||
import { BalanceWarning } from './FeeWarning';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Balance warning',
|
|
||||||
component: BalanceWarning,
|
|
||||||
} as ComponentMeta<typeof BalanceWarning>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof BalanceWarning> = (args) => (
|
|
||||||
<Box mt={2} height={800}>
|
|
||||||
<BalanceWarning {...args} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithWarning = Template.bind({});
|
|
||||||
WithWarning.args = {
|
|
||||||
fee: '200',
|
|
||||||
};
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { ErrorOutline } from '@mui/icons-material';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { Button } from '@mui/material';
|
|
||||||
import { ConfirmationModal } from './ConfirmationModal';
|
|
||||||
import { backDropStyles, dialogStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: dialogStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Modals/ConfirmationModal',
|
|
||||||
component: ConfirmationModal,
|
|
||||||
} as ComponentMeta<typeof ConfirmationModal>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof ConfirmationModal> = (args) => {
|
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button variant="outlined" onClick={() => setOpen(true)}>
|
|
||||||
Open confirmation dialog
|
|
||||||
</Button>
|
|
||||||
<ConfirmationModal
|
|
||||||
{...args}
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onConfirm={() => setOpen(false)}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
Dialog content.
|
|
||||||
</ConfirmationModal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const withError: ComponentStory<typeof ConfirmationModal> = () => {
|
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button variant="outlined" onClick={() => setOpen(true)}>
|
|
||||||
Open confirmation dialog
|
|
||||||
</Button>
|
|
||||||
<ConfirmationModal
|
|
||||||
title="An error occured"
|
|
||||||
confirmButton="Done"
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onConfirm={() => setOpen(false)}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<ErrorOutline color="error" />
|
|
||||||
</ConfirmationModal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
Default.args = {
|
|
||||||
title: 'Confirmation Modal',
|
|
||||||
subTitle: '',
|
|
||||||
fullWidth: true,
|
|
||||||
confirmButton: 'Confirm',
|
|
||||||
maxWidth: 'xs',
|
|
||||||
disabled: false,
|
|
||||||
};
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
import { Button, Paper, Typography } from '@mui/material';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { SimpleModal } from './SimpleModal';
|
|
||||||
import { ModalDivider } from './ModalDivider';
|
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Modals/Simple Modal',
|
|
||||||
component: SimpleModal,
|
|
||||||
} as ComponentMeta<typeof SimpleModal>;
|
|
||||||
|
|
||||||
const BasePage: FCWithChildren<{ children: React.ReactElement<any, any>; handleClick: () => void }> = ({
|
|
||||||
children,
|
|
||||||
handleClick,
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Lorem ipsum</h2>
|
|
||||||
<Button variant="contained" onClick={handleClick} sx={{ mb: 3 }}>
|
|
||||||
Show modal
|
|
||||||
</Button>
|
|
||||||
<Typography>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt
|
|
||||||
velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa
|
|
||||||
exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad
|
|
||||||
tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor
|
|
||||||
culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut
|
|
||||||
mollit.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat
|
|
||||||
duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo
|
|
||||||
est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation
|
|
||||||
in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea
|
|
||||||
aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis
|
|
||||||
excepteur minim culpa velit Lorem enim ad.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim
|
|
||||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute
|
|
||||||
irure anim dolore ipsum et veniam.
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This is a modal"
|
|
||||||
subHeader="This is a sub header"
|
|
||||||
okLabel="Click to continue"
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eius
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NoSubheader = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This is a modal"
|
|
||||||
okLabel="Kaplow!"
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hideCloseIcon = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
hideCloseIcon
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This is a modal"
|
|
||||||
okLabel="Kaplow!"
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hideCloseIconAndDisplayErrorIcon = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
hideCloseIcon
|
|
||||||
displayErrorIcon
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This modal announces an error !"
|
|
||||||
okLabel="Kaplow!"
|
|
||||||
backdropProps={backDropStyles(theme)}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
...modalStyles(theme),
|
|
||||||
}}
|
|
||||||
headerStyles={{
|
|
||||||
width: '100%',
|
|
||||||
mb: 3,
|
|
||||||
textAlign: 'center',
|
|
||||||
color: 'error.main',
|
|
||||||
}}
|
|
||||||
subHeaderStyles={{ textAlign: 'center', color: 'text.primary', fontSize: 14, fontWeight: 400 }}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const withBackButton = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
hideCloseIcon
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This is a modal"
|
|
||||||
okLabel="Primary action"
|
|
||||||
onBack={() => setOpen(false)}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const withBackButtonAndCustomLabel = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const handleClick = () => setOpen(true);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BasePage handleClick={handleClick}>
|
|
||||||
<SimpleModal
|
|
||||||
open={open}
|
|
||||||
hideCloseIcon
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
header="This is a modal"
|
|
||||||
okLabel="Primary action"
|
|
||||||
onBack={() => setOpen(false)}
|
|
||||||
backLabel="Cancel"
|
|
||||||
backButtonFullWidth
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
>
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
|
||||||
elit ut mollit.
|
|
||||||
</Typography>
|
|
||||||
<ModalDivider />
|
|
||||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
|
||||||
</Typography>
|
|
||||||
</SimpleModal>
|
|
||||||
</BasePage>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { Box } from '@mui/material';
|
|
||||||
import { MockMainContextProvider } from '../context/mocks/main';
|
|
||||||
import { NetworkSelector } from './NetworkSelector';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Network Selector',
|
|
||||||
component: NetworkSelector,
|
|
||||||
} as ComponentMeta<typeof NetworkSelector>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof NetworkSelector> = () => (
|
|
||||||
<Box mt={2} height={800}>
|
|
||||||
<MockMainContextProvider>
|
|
||||||
<NetworkSelector />
|
|
||||||
</MockMainContextProvider>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
@@ -2,10 +2,10 @@ import React, { useContext } from 'react';
|
|||||||
import { AppContext } from 'src/context';
|
import { AppContext } from 'src/context';
|
||||||
import { ReceiveModal } from './ReceiveModal';
|
import { ReceiveModal } from './ReceiveModal';
|
||||||
|
|
||||||
export const Receive = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => {
|
export const Receive = () => {
|
||||||
const { showReceiveModal, handleShowReceiveModal } = useContext(AppContext);
|
const { showReceiveModal, handleShowReceiveModal } = useContext(AppContext);
|
||||||
|
|
||||||
if (showReceiveModal) return <ReceiveModal onClose={handleShowReceiveModal} {...hasStorybookStyles} />;
|
if (showReceiveModal) return <ReceiveModal onClose={handleShowReceiveModal} />;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
import { Button, Paper } from '@mui/material';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { RedeemModal } from './RedeemModal';
|
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Rewards/Components/Redeem Modals',
|
|
||||||
component: RedeemModal,
|
|
||||||
} as ComponentMeta<typeof RedeemModal>;
|
|
||||||
|
|
||||||
const Content: FCWithChildren<{
|
|
||||||
setOpen: (value: boolean) => void;
|
|
||||||
}> = ({ setOpen }) => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, pt: 2, pb: 4 }}>
|
|
||||||
<h2>Lorem ipsum</h2>
|
|
||||||
<Button variant="contained" onClick={() => setOpen(true)}>
|
|
||||||
Show modal
|
|
||||||
</Button>
|
|
||||||
<p>
|
|
||||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt
|
|
||||||
velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa
|
|
||||||
exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad
|
|
||||||
tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor
|
|
||||||
culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut
|
|
||||||
mollit.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat
|
|
||||||
duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo
|
|
||||||
est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation in
|
|
||||||
duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea aliquip
|
|
||||||
consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis excepteur
|
|
||||||
minim culpa velit Lorem enim ad.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim
|
|
||||||
Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute
|
|
||||||
irure anim dolore ipsum et veniam.
|
|
||||||
</p>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const RedeemAllRewards = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Content setOpen={setOpen} />
|
|
||||||
<RedeemModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
message="Redeem all rewards"
|
|
||||||
denom="nym"
|
|
||||||
mixId={1234}
|
|
||||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
|
||||||
amount={425.65843}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
usesVestingTokens={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RedeemRewardForMixnode = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Content setOpen={setOpen} />
|
|
||||||
<RedeemModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
message="Claim rewards"
|
|
||||||
denom="nym"
|
|
||||||
mixId={1234}
|
|
||||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
|
||||||
amount={425.65843}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
usesVestingTokens={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FeeIsMoreThanAllRewards = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Content setOpen={setOpen} />
|
|
||||||
<RedeemModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={() => setOpen(false)}
|
|
||||||
message="Redeem all rewards"
|
|
||||||
denom="nym"
|
|
||||||
mixId={1234}
|
|
||||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
|
||||||
amount={0.001}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
usesVestingTokens={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FeeIsMoreThanMixnodeReward = () => {
|
|
||||||
const [open, setOpen] = React.useState<boolean>(false);
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Content setOpen={setOpen} />
|
|
||||||
<RedeemModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
onOk={async () => setOpen(false)}
|
|
||||||
mixId={1234}
|
|
||||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
|
||||||
message="Claim rewards"
|
|
||||||
denom="nym"
|
|
||||||
amount={0.001}
|
|
||||||
{...storybookStyles(theme)}
|
|
||||||
usesVestingTokens={false}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Paper } from '@mui/material';
|
|
||||||
import { RewardsSummary } from './RewardsSummary';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Rewards/Components/Rewards Summary',
|
|
||||||
component: RewardsSummary,
|
|
||||||
} as ComponentMeta<typeof RewardsSummary>;
|
|
||||||
|
|
||||||
export const Default = () => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
|
||||||
<RewardsSummary totalDelegation="860.123 NYM" totalRewards="4.86723 NYM" />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Empty = () => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
|
||||||
<RewardsSummary />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Loading = () => (
|
|
||||||
<Paper elevation={0} sx={{ px: 4, py: 2 }}>
|
|
||||||
<RewardsSummary isLoading />
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Typography } from '@mui/material';
|
|
||||||
import { SelectionChance } from '@nymproject/types';
|
|
||||||
|
|
||||||
const colorMap: { [key in SelectionChance]: string } = {
|
|
||||||
Low: 'error.main',
|
|
||||||
Good: 'warning.main',
|
|
||||||
High: 'success.main',
|
|
||||||
};
|
|
||||||
|
|
||||||
const textMap: { [key in SelectionChance]: string } = {
|
|
||||||
Low: 'Low',
|
|
||||||
Good: 'Good',
|
|
||||||
High: 'High',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InclusionProbability = ({ probability }: { probability: SelectionChance }) => (
|
|
||||||
<Typography sx={{ color: colorMap[probability] }}>{textMap[probability]}</Typography>
|
|
||||||
);
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import React, { useCallback } from 'react';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
|
||||||
import { Button, Grid, TextField, Typography } from '@mui/material';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
import { DefaultInputValues } from 'src/pages/bonding/node-settings/apy-playground';
|
|
||||||
import { inputValidationSchema } from './inputsValidationSchema';
|
|
||||||
|
|
||||||
export type InputFields = {
|
|
||||||
label: string;
|
|
||||||
name: 'profitMargin' | 'uptime' | 'bond' | 'delegations' | 'operatorCost' | 'uptime';
|
|
||||||
isPercentage?: boolean;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
export type CalculateArgs = {
|
|
||||||
bond: string;
|
|
||||||
delegations: string;
|
|
||||||
uptime: string;
|
|
||||||
profitMargin: string;
|
|
||||||
operatorCost: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const inputFields: InputFields = [
|
|
||||||
{ label: 'Profit margin', name: 'profitMargin', isPercentage: true },
|
|
||||||
{ label: 'Operator cost', name: 'operatorCost' },
|
|
||||||
{ label: 'Bond', name: 'bond' },
|
|
||||||
{ label: 'Delegations', name: 'delegations' },
|
|
||||||
{ label: 'Uptime', name: 'uptime', isPercentage: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Inputs = ({
|
|
||||||
onCalculate,
|
|
||||||
defaultValues,
|
|
||||||
}: {
|
|
||||||
onCalculate: (args: CalculateArgs) => Promise<void>;
|
|
||||||
defaultValues: DefaultInputValues;
|
|
||||||
}) => {
|
|
||||||
const handleCalculate = useCallback(
|
|
||||||
async (args: CalculateArgs) => {
|
|
||||||
onCalculate({
|
|
||||||
bond: args.bond,
|
|
||||||
delegations: args.delegations,
|
|
||||||
uptime: args.uptime,
|
|
||||||
profitMargin: args.profitMargin,
|
|
||||||
operatorCost: args.operatorCost,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[onCalculate],
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
formState: { errors },
|
|
||||||
} = useForm({
|
|
||||||
resolver: yupResolver(inputValidationSchema),
|
|
||||||
defaultValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{inputFields.map((field) => (
|
|
||||||
<Grid item xs={12} lg={2} key={field.name}>
|
|
||||||
<TextField
|
|
||||||
{...register(field.name)}
|
|
||||||
fullWidth
|
|
||||||
label={field.label}
|
|
||||||
name={field.name}
|
|
||||||
error={Boolean(errors[field.name])}
|
|
||||||
helperText={errors[field.name]?.message}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: <Typography sx={{ color: 'grey.600' }}>{field.isPercentage ? '%' : 'NYM'}</Typography>,
|
|
||||||
}}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}{' '}
|
|
||||||
<Grid item xs={12} lg={2}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
disableElevation
|
|
||||||
onClick={handleSubmit(handleCalculate)}
|
|
||||||
size="large"
|
|
||||||
fullWidth
|
|
||||||
disabled={Boolean(Object.keys(errors).length)}
|
|
||||||
>
|
|
||||||
Calculate
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, CardContent, Divider, Stack, Typography } from '@mui/material';
|
|
||||||
import { SelectionChance } from '@nymproject/types';
|
|
||||||
import { InclusionProbability } from './InclusionProbability';
|
|
||||||
|
|
||||||
const computeSelectionProbability = (saturation: number): SelectionChance => {
|
|
||||||
if (saturation < 5) return 'Low';
|
|
||||||
|
|
||||||
if (saturation > 5 && saturation < 15) return 'Good';
|
|
||||||
|
|
||||||
return 'High';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NodeDetails = ({ saturation }: { saturation?: string }) => {
|
|
||||||
if (!saturation) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card variant="outlined" sx={{ p: 1 }}>
|
|
||||||
<CardContent>
|
|
||||||
<Stack direction="row" justifyContent="space-between">
|
|
||||||
<Typography fontWeight="medium">Stake saturation</Typography>
|
|
||||||
<Typography>{saturation || '- '}%</Typography>
|
|
||||||
</Stack>
|
|
||||||
<Divider sx={{ my: 1 }} />
|
|
||||||
<Stack direction="row" justifyContent="space-between">
|
|
||||||
<Typography fontWeight="medium">Selection probability</Typography>
|
|
||||||
<InclusionProbability probability={computeSelectionProbability(parseInt(saturation, 10))} />
|
|
||||||
</Stack>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
export type Results = {
|
|
||||||
operator: {
|
|
||||||
daily: string;
|
|
||||||
monthly: string;
|
|
||||||
yearly: string;
|
|
||||||
};
|
|
||||||
delegator: {
|
|
||||||
daily: string;
|
|
||||||
monthly: string;
|
|
||||||
yearly: string;
|
|
||||||
};
|
|
||||||
total: {
|
|
||||||
daily: string;
|
|
||||||
monthly: string;
|
|
||||||
yearly: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableHeader = [
|
|
||||||
{ title: 'Estimated rewards', bold: true },
|
|
||||||
{ title: 'Per day' },
|
|
||||||
{ title: 'Per month' },
|
|
||||||
{ title: 'Per year' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ResultsTable = ({ results }: { results: Results }) => {
|
|
||||||
const tableRows = [
|
|
||||||
{ title: 'Total node reward', ...results.total },
|
|
||||||
{ title: 'Operator rewards', ...results.operator },
|
|
||||||
{ title: 'Delegator rewards', ...results.delegator },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card variant="outlined" sx={{ p: 1 }}>
|
|
||||||
<CardContent>
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{tableHeader.map((header) => (
|
|
||||||
<TableCell>
|
|
||||||
<Typography fontWeight={header.bold ? 'bold' : 'regular'}>{header.title}</Typography>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{tableRows.map((row) => (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>{row.title}</TableCell>
|
|
||||||
<TableCell>{row.daily}</TableCell>
|
|
||||||
<TableCell>{row.monthly}</TableCell>
|
|
||||||
<TableCell>{row.yearly}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import * as Yup from 'yup';
|
|
||||||
import { isGreaterThan, isLessThan } from 'src/utils';
|
|
||||||
|
|
||||||
export const inputValidationSchema = Yup.object().shape({
|
|
||||||
profitMargin: Yup.string()
|
|
||||||
.required('profit margin is a required field')
|
|
||||||
.test('Is valid profit margin value', (value, ctx) => {
|
|
||||||
const stringValueToNumber = Math.round(Number(value));
|
|
||||||
|
|
||||||
if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true;
|
|
||||||
return ctx.createError({ message: 'Profit margin must be a number from 0 and 100' });
|
|
||||||
}),
|
|
||||||
uptime: Yup.string()
|
|
||||||
.required()
|
|
||||||
.test('Is valid uptime value', (value, ctx) => {
|
|
||||||
const stringValueToNumber = Math.round(Number(value));
|
|
||||||
if (stringValueToNumber && isGreaterThan(stringValueToNumber, 0) && isLessThan(stringValueToNumber, 101))
|
|
||||||
return true;
|
|
||||||
return ctx.createError({ message: 'Uptime must be a number between 0 and 100' });
|
|
||||||
}),
|
|
||||||
bond: Yup.string()
|
|
||||||
.required()
|
|
||||||
.test('Is valid bond value', (value, ctx) => {
|
|
||||||
if (Number(value)) return true;
|
|
||||||
return ctx.createError({ message: 'Bond must be a valid number' });
|
|
||||||
}),
|
|
||||||
delegations: Yup.string()
|
|
||||||
.required()
|
|
||||||
.test('Is valid delegation value', (value, ctx) => {
|
|
||||||
if (Number(value)) return true;
|
|
||||||
return ctx.createError({ message: 'Delegations must be a valid number' });
|
|
||||||
}),
|
|
||||||
operatorCost: Yup.string()
|
|
||||||
.required('operator cost is a required field')
|
|
||||||
.test('Is valid operator cost value', (value, ctx) => {
|
|
||||||
const stringValueToNumber = Math.round(Number(value));
|
|
||||||
|
|
||||||
if (isLessThan(stringValueToNumber, 0))
|
|
||||||
return ctx.createError({ message: 'Operator cost must be a valid number' });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
import { useTheme, Theme } from '@mui/material/styles';
|
|
||||||
import { MockMainContextProvider } from 'src/context/mocks/main';
|
|
||||||
import { SendDetailsModal } from './SendDetailsModal';
|
|
||||||
import { SendSuccessModal } from './SendSuccessModal';
|
|
||||||
import { SendErrorModal } from './SendErrorModal';
|
|
||||||
import { SendInputModal } from './SendInputModal';
|
|
||||||
import { Send } from '.';
|
|
||||||
import { backDropStyles, modalStyles, dialogStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
|
|
||||||
const storybookStylesModal = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
const storybookStylesDialog = (theme: Theme) => ({
|
|
||||||
backdropProps: backDropStyles(theme),
|
|
||||||
sx: dialogStyles(theme),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Send/Components',
|
|
||||||
component: SendDetailsModal,
|
|
||||||
} as ComponentMeta<typeof SendDetailsModal>;
|
|
||||||
|
|
||||||
export const SendInput = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<SendInputModal
|
|
||||||
toAddress=""
|
|
||||||
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
|
||||||
denom="nym"
|
|
||||||
onNext={() => {}}
|
|
||||||
onClose={() => {}}
|
|
||||||
onAddressChange={() => {}}
|
|
||||||
onAmountChange={() => {}}
|
|
||||||
onUserFeesChange={() => {}}
|
|
||||||
onMemoChange={() => {}}
|
|
||||||
setShowMore={() => {}}
|
|
||||||
{...storybookStylesModal(theme)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendDetails = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<SendDetailsModal
|
|
||||||
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
|
||||||
toAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
|
||||||
fee={{ amount: { amount: '0.01', denom: 'nym' }, fee: { Auto: null } }}
|
|
||||||
denom="nym"
|
|
||||||
amount={{ amount: '100', denom: 'nym' }}
|
|
||||||
onPrev={() => {}}
|
|
||||||
onSend={() => {}}
|
|
||||||
onClose={() => {}}
|
|
||||||
{...storybookStylesModal(theme)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendSuccess = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<SendSuccessModal
|
|
||||||
txDetails={{ amount: '100 NYM', txUrl: 'dummtUrl.com' }}
|
|
||||||
onClose={() => {}}
|
|
||||||
{...storybookStylesDialog(theme)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendError = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return <SendErrorModal onClose={() => {}} {...storybookStylesModal(theme)} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SendFlow = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<MockMainContextProvider>
|
|
||||||
<Send hasStorybookStyles={{ backdropProps: { ...backDropStyles(theme) }, sx: modalStyles(theme) }} />
|
|
||||||
</MockMainContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,7 @@ import { SendInputModal } from './SendInputModal';
|
|||||||
import { SendSuccessModal } from './SendSuccessModal';
|
import { SendSuccessModal } from './SendSuccessModal';
|
||||||
import { TTransactionDetails } from './types';
|
import { TTransactionDetails } from './types';
|
||||||
|
|
||||||
export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void; hasStorybookStyles?: {} }) => {
|
export const SendModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
const [toAddress, setToAddress] = useState<string>('');
|
const [toAddress, setToAddress] = useState<string>('');
|
||||||
const [amount, setAmount] = useState<DecCoin>();
|
const [amount, setAmount] = useState<DecCoin>();
|
||||||
const [modal, setModal] = useState<'send' | 'send details'>('send');
|
const [modal, setModal] = useState<'send' | 'send details'>('send');
|
||||||
@@ -108,7 +108,6 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
|||||||
onSend={handleSend}
|
onSend={handleSend}
|
||||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||||
memo={memo}
|
memo={memo}
|
||||||
{...hasStorybookStyles}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,7 +129,6 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
|||||||
onUserFeesChange={(value) => setUserFees(value)}
|
onUserFeesChange={(value) => setUserFees(value)}
|
||||||
onMemoChange={(value) => setMemo(value)}
|
onMemoChange={(value) => setMemo(value)}
|
||||||
setShowMore={setShowMoreOptions}
|
setShowMore={setShowMoreOptions}
|
||||||
{...hasStorybookStyles}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React, { useContext } from 'react';
|
|||||||
import { AppContext } from 'src/context';
|
import { AppContext } from 'src/context';
|
||||||
import { SendModal } from './SendModal';
|
import { SendModal } from './SendModal';
|
||||||
|
|
||||||
export const Send = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => {
|
export const Send = () => {
|
||||||
const { showSendModal, handleShowSendModal } = useContext(AppContext);
|
const { showSendModal, handleShowSendModal } = useContext(AppContext);
|
||||||
|
|
||||||
if (showSendModal) return <SendModal onClose={handleShowSendModal} hasStorybookStyles={hasStorybookStyles} />;
|
if (showSendModal) return <SendModal onClose={handleShowSendModal} />;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { Box } from '@mui/material';
|
|
||||||
import { TokenPoolSelector } from './TokenPoolSelector';
|
|
||||||
import { MockMainContextProvider } from '../context/mocks/main';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Token pool',
|
|
||||||
component: TokenPoolSelector,
|
|
||||||
} as ComponentMeta<typeof TokenPoolSelector>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof TokenPoolSelector> = (args) => (
|
|
||||||
<Box mt={2} height={800}>
|
|
||||||
<MockMainContextProvider>
|
|
||||||
<TokenPoolSelector {...args} />
|
|
||||||
</MockMainContextProvider>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
Default.args = {
|
|
||||||
disabled: false,
|
|
||||||
onSelect: () => {},
|
|
||||||
};
|
|
||||||
@@ -118,25 +118,7 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDelegation = async (
|
const updateDelegation = async (newDelegation: DelegationWithEverything): Promise<TDelegationTransaction> => {
|
||||||
newDelegation: DelegationWithEverything,
|
|
||||||
ignorePendingForStorybook?: boolean,
|
|
||||||
): Promise<TDelegationTransaction> => {
|
|
||||||
if (ignorePendingForStorybook) {
|
|
||||||
mockDelegations = mockDelegations.map((d) => {
|
|
||||||
if (d.node_identity === newDelegation.node_identity) {
|
|
||||||
return { ...newDelegation };
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
});
|
|
||||||
await recalculate();
|
|
||||||
triggerStateUpdate();
|
|
||||||
return {
|
|
||||||
transactionUrl:
|
|
||||||
'https://sandbox-blocks.nymtech.net/transactions/55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await mockSleep(SLEEP_MS);
|
await mockSleep(SLEEP_MS);
|
||||||
mockDelegations = mockDelegations.map((d) => {
|
mockDelegations = mockDelegations.map((d) => {
|
||||||
if (d.node_identity === newDelegation.node_identity) {
|
if (d.node_identity === newDelegation.node_identity) {
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
|
||||||
import { Stack, TextField } from '@mui/material';
|
|
||||||
import { PasswordStrength } from './password-strength';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Wallet / Password Strength',
|
|
||||||
component: PasswordStrength,
|
|
||||||
} as ComponentMeta<typeof PasswordStrength>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof PasswordStrength> = ({ password, withWarnings, handleIsSafePassword }) => {
|
|
||||||
const [value, setValue] = React.useState(password);
|
|
||||||
return (
|
|
||||||
<Stack alignContent="center">
|
|
||||||
<TextField value={value} onChange={(e) => setValue(e.target.value)} sx={{ mb: 0.5 }} />
|
|
||||||
{!!password.length && (
|
|
||||||
<PasswordStrength handleIsSafePassword={handleIsSafePassword} withWarnings={withWarnings} password={password} />
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VeryStrong = Template.bind({});
|
|
||||||
VeryStrong.args = { password: 'fedgklnrf34£', withWarnings: true, handleIsSafePassword: () => undefined };
|
|
||||||
|
|
||||||
export const Strong = Template.bind({});
|
|
||||||
Strong.args = { password: '"56%abc123?@', withWarnings: true, handleIsSafePassword: () => undefined };
|
|
||||||
|
|
||||||
export const Average = Template.bind({});
|
|
||||||
Average.args = { password: '"abc123?', withWarnings: true, handleIsSafePassword: () => undefined };
|
|
||||||
|
|
||||||
export const Weak = Template.bind({});
|
|
||||||
Weak.args = { password: 'abc123?', withWarnings: true, handleIsSafePassword: () => undefined };
|
|
||||||
|
|
||||||
export const VeryWeak = Template.bind({});
|
|
||||||
VeryWeak.args = {
|
|
||||||
password: 'abc123',
|
|
||||||
withWarnings: true,
|
|
||||||
handleIsSafePassword: () => undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithName = Template.bind({});
|
|
||||||
WithName.args = {
|
|
||||||
password: 'fred',
|
|
||||||
withWarnings: true,
|
|
||||||
handleIsSafePassword: () => undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithSequence = Template.bind({});
|
|
||||||
WithSequence.args = {
|
|
||||||
password: '121212',
|
|
||||||
withWarnings: true,
|
|
||||||
handleIsSafePassword: () => undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
Default.args = {
|
|
||||||
password: 'abc123',
|
|
||||||
withWarnings: true,
|
|
||||||
handleIsSafePassword: () => undefined,
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { BondingPage } from './Bonding';
|
|
||||||
import { MockBondingContextProvider } from '../../context/mocks/bonding';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Bonding/Flows/Mock',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => (
|
|
||||||
<MockBondingContextProvider>
|
|
||||||
<BondingPage />
|
|
||||||
</MockBondingContextProvider>
|
|
||||||
);
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { Box, Card, CardContent, CardHeader, Grid, Typography } from '@mui/material';
|
|
||||||
import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable';
|
|
||||||
import { getDelegationSummary } from 'src/requests';
|
|
||||||
import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail';
|
|
||||||
import { CalculateArgs, Inputs } from 'src/components/RewardsPlayground/Inputs';
|
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
|
||||||
import { Console } from 'src/utils/console';
|
|
||||||
import { TBondedMixnode } from 'src/requests/mixnodeDetails';
|
|
||||||
import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils';
|
|
||||||
|
|
||||||
export type DefaultInputValues = {
|
|
||||||
profitMargin: string;
|
|
||||||
uptime: string;
|
|
||||||
bond: string;
|
|
||||||
delegations: string;
|
|
||||||
operatorCost: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ApyPlayground = ({ bondedNode }: { bondedNode: TBondedMixnode }) => {
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const [results, setResults] = useState({
|
|
||||||
total: { daily: '-', monthly: '-', yearly: '-' },
|
|
||||||
operator: { daily: '-', monthly: '-', yearly: '-' },
|
|
||||||
delegator: { daily: '-', monthly: '-', yearly: '-' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const [defaultInputValues, setDefaultInputValues] = useState<DefaultInputValues>();
|
|
||||||
const [stakeSaturation, setStakeSaturation] = useState<string>();
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
|
|
||||||
const initialise = async (node: TBondedMixnode) => {
|
|
||||||
try {
|
|
||||||
const delegations = await getDelegationSummary();
|
|
||||||
|
|
||||||
const { estimation } = await computeEstimate({
|
|
||||||
mixId: node.mixId,
|
|
||||||
uptime: node.uptime.toString(),
|
|
||||||
profitMargin: node.profitMargin,
|
|
||||||
operatorCost: node.operatorCost.amount,
|
|
||||||
totalDelegation: delegations.total_delegations.amount,
|
|
||||||
pledgeAmount: node.bond.amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
setResults(
|
|
||||||
handleCalculatePeriodRewards({
|
|
||||||
estimatedOperatorReward: estimation.operator,
|
|
||||||
estimatedDelegatorsReward: estimation.delegates,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
setStakeSaturation(node.stakeSaturation);
|
|
||||||
|
|
||||||
setDefaultInputValues({
|
|
||||||
profitMargin: node.profitMargin,
|
|
||||||
uptime: (node.uptime || 0).toString(),
|
|
||||||
bond: node.bond.amount || '',
|
|
||||||
delegations: delegations.total_delegations.amount,
|
|
||||||
operatorCost: node.operatorCost.amount,
|
|
||||||
});
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar(e as string, { variant: 'error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (bondedNode) {
|
|
||||||
initialise(bondedNode);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (isLoading) return <LoadingModal />;
|
|
||||||
|
|
||||||
const handleCalculateEstimate = async ({ bond, delegations, uptime, profitMargin, operatorCost }: CalculateArgs) => {
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
const { estimation, reward_params } = await computeEstimate({
|
|
||||||
mixId: bondedNode.mixId,
|
|
||||||
uptime,
|
|
||||||
profitMargin,
|
|
||||||
operatorCost,
|
|
||||||
totalDelegation: delegations,
|
|
||||||
pledgeAmount: bond,
|
|
||||||
});
|
|
||||||
|
|
||||||
const estimationResult = handleCalculatePeriodRewards({
|
|
||||||
estimatedOperatorReward: estimation.operator,
|
|
||||||
estimatedDelegatorsReward: estimation.delegates,
|
|
||||||
});
|
|
||||||
|
|
||||||
const computedStakeSaturation = computeStakeSaturation(
|
|
||||||
bond,
|
|
||||||
delegations,
|
|
||||||
reward_params.interval.stake_saturation_point,
|
|
||||||
);
|
|
||||||
|
|
||||||
setStakeSaturation(computedStakeSaturation);
|
|
||||||
setResults(estimationResult);
|
|
||||||
} catch (e) {
|
|
||||||
Console.log(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ p: 3 }}>
|
|
||||||
<Typography fontWeight="medium" sx={{ mb: 1 }}>
|
|
||||||
Playground
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" sx={{ color: 'grey.600', mb: 2 }}>
|
|
||||||
This is your parameters playground - change the parameters below to see the node specific estimations in the
|
|
||||||
table
|
|
||||||
</Typography>
|
|
||||||
{defaultInputValues && (
|
|
||||||
<Card variant="outlined" sx={{ p: 1, mb: 3 }}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography variant="body2" fontWeight="medium">
|
|
||||||
Estimation calculator
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CardContent>
|
|
||||||
<Inputs onCalculate={handleCalculateEstimate} defaultValues={defaultInputValues} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12} md={8}>
|
|
||||||
<ResultsTable results={results} />
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} md={4}>
|
|
||||||
<NodeDetails saturation={stakeSaturation} />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { decimalToPercentage, percentToDecimal } from '@nymproject/types';
|
|
||||||
import { computeMixnodeRewardEstimation } from 'src/requests';
|
|
||||||
|
|
||||||
const SCALE_FACTOR = 1_000_000;
|
|
||||||
|
|
||||||
export const computeStakeSaturation = (bond: string, delegations: string, stakeSaturationPoint: string) => {
|
|
||||||
const res = ((+bond + +delegations) * SCALE_FACTOR) / +stakeSaturationPoint;
|
|
||||||
return decimalToPercentage(res.toFixed(18).toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const computeEstimate = async ({
|
|
||||||
mixId,
|
|
||||||
uptime,
|
|
||||||
pledgeAmount,
|
|
||||||
totalDelegation,
|
|
||||||
profitMargin,
|
|
||||||
operatorCost,
|
|
||||||
}: {
|
|
||||||
mixId: number;
|
|
||||||
uptime: string;
|
|
||||||
pledgeAmount: string;
|
|
||||||
totalDelegation: string;
|
|
||||||
profitMargin: string;
|
|
||||||
operatorCost: string;
|
|
||||||
}) => {
|
|
||||||
const computedEstimate = await computeMixnodeRewardEstimation({
|
|
||||||
mixId,
|
|
||||||
performance: percentToDecimal(uptime),
|
|
||||||
pledgeAmount: Math.round(+pledgeAmount * SCALE_FACTOR),
|
|
||||||
totalDelegation: Math.round(+totalDelegation * SCALE_FACTOR),
|
|
||||||
profitMarginPercent: percentToDecimal(profitMargin),
|
|
||||||
intervalOperatingCost: { denom: 'unym', amount: Math.round(+operatorCost * SCALE_FACTOR).toString() },
|
|
||||||
});
|
|
||||||
|
|
||||||
return computedEstimate;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleCalculatePeriodRewards = ({
|
|
||||||
estimatedOperatorReward,
|
|
||||||
estimatedDelegatorsReward,
|
|
||||||
}: {
|
|
||||||
estimatedOperatorReward: string;
|
|
||||||
estimatedDelegatorsReward: string;
|
|
||||||
}) => {
|
|
||||||
const dailyOperatorReward = (+estimatedOperatorReward / SCALE_FACTOR) * 24; // epoch_reward * 1 epoch_per_hour * 24 hours
|
|
||||||
const dailyDelegatorReward = (+estimatedDelegatorsReward / SCALE_FACTOR) * 24;
|
|
||||||
const dailyTotal = dailyOperatorReward + dailyDelegatorReward;
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: {
|
|
||||||
daily: dailyTotal.toFixed(3).toString(),
|
|
||||||
monthly: (dailyTotal * 30).toFixed(3).toString(),
|
|
||||||
yearly: (dailyTotal * 365).toFixed(3).toString(),
|
|
||||||
},
|
|
||||||
operator: {
|
|
||||||
daily: dailyOperatorReward.toFixed(3).toString(),
|
|
||||||
monthly: (dailyOperatorReward * 30).toFixed(3).toString(),
|
|
||||||
yearly: (dailyOperatorReward * 365).toFixed(3).toString(),
|
|
||||||
},
|
|
||||||
delegator: {
|
|
||||||
daily: dailyDelegatorReward.toFixed(3).toString(),
|
|
||||||
monthly: (dailyDelegatorReward * 30).toFixed(3).toString(),
|
|
||||||
yearly: (dailyDelegatorReward * 365).toFixed(3).toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -8,7 +8,7 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
|||||||
import { Results } from 'src/components/TestNode/Results';
|
import { Results } from 'src/components/TestNode/Results';
|
||||||
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
||||||
import { PrintResults } from 'src/components/TestNode/PrintResults';
|
import { PrintResults } from 'src/components/TestNode/PrintResults';
|
||||||
import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants';
|
import { MAINNET_VALIDATOR_URL } from 'src/constants';
|
||||||
import { TestStatus } from 'src/components/TestNode/types';
|
import { TestStatus } from 'src/components/TestNode/types';
|
||||||
import { isMixnode } from 'src/types';
|
import { isMixnode } from 'src/types';
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ export const NodeTestPage = () => {
|
|||||||
const loadNodeTestClient = useCallback(async () => {
|
const loadNodeTestClient = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
|
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
|
||||||
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : QA_VALIDATOR_URL;
|
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : 'https://rpc.nymtech.net/api/';
|
||||||
const client = await createNodeTesterClient();
|
const client = await createNodeTesterClient();
|
||||||
await client.tester.init(validator, nodeTesterId);
|
await client.tester.init(validator, nodeTesterId);
|
||||||
setNodeTestClient(client);
|
setNodeTestClient(client);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Tutorial } from '../../components/Buy/Tutorial';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Buy/Page',
|
|
||||||
component: Tutorial,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BuyPage = () => <Tutorial />;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { DelegationPage } from './index';
|
|
||||||
import { MockDelegationContextProvider } from '../../context/mocks/delegations';
|
|
||||||
import { MockRewardsContextProvider } from '../../context/mocks/rewards';
|
|
||||||
import { MockMainContextProvider } from '../../context/mocks/main';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Delegation/Flows/Mock',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default = () => (
|
|
||||||
<MockMainContextProvider>
|
|
||||||
<MockDelegationContextProvider>
|
|
||||||
<MockRewardsContextProvider>
|
|
||||||
<DelegationPage isStorybook />
|
|
||||||
</MockRewardsContextProvider>
|
|
||||||
</MockDelegationContextProvider>
|
|
||||||
</MockMainContextProvider>
|
|
||||||
);
|
|
||||||
@@ -19,18 +19,9 @@ import { UndelegateModal } from '../../components/Delegation/UndelegateModal';
|
|||||||
import { DelegationListItemActions } from '../../components/Delegation/DelegationActions';
|
import { DelegationListItemActions } from '../../components/Delegation/DelegationActions';
|
||||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
|
||||||
import { VestingWarningModal } from '../../components/VestingWarningModal';
|
import { VestingWarningModal } from '../../components/VestingWarningModal';
|
||||||
|
|
||||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
export const Delegation: FC = () => {
|
||||||
isStorybook
|
|
||||||
? {
|
|
||||||
backdropProps: { ...backDropStyles(theme), ...backdropProps },
|
|
||||||
sx: modalStyles(theme),
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|
||||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||||
const [showDelegateMoreModal, setShowDelegateMoreModal] = useState<boolean>(false);
|
const [showDelegateMoreModal, setShowDelegateMoreModal] = useState<boolean>(false);
|
||||||
const [showUndelegateModal, setShowUndelegateModal] = useState<boolean>(false);
|
const [showUndelegateModal, setShowUndelegateModal] = useState<boolean>(false);
|
||||||
@@ -472,7 +463,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
accountBalance={balance?.printable_balance}
|
accountBalance={balance?.printable_balance}
|
||||||
rewardInterval="weekly"
|
rewardInterval="weekly"
|
||||||
hasVestingContract={Boolean(originalVesting)}
|
hasVestingContract={Boolean(originalVesting)}
|
||||||
{...storybookStyles(theme, isStorybook)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -548,10 +538,10 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DelegationPage: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => (
|
export const DelegationPage: FC = () => (
|
||||||
<DelegationContextProvider>
|
<DelegationContextProvider>
|
||||||
<RewardsContextProvider>
|
<RewardsContextProvider>
|
||||||
<Delegation isStorybook={isStorybook} />
|
<Delegation />
|
||||||
</RewardsContextProvider>
|
</RewardsContextProvider>
|
||||||
</DelegationContextProvider>
|
</DelegationContextProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Meta } from '@storybook/addon-docs';
|
|
||||||
|
|
||||||
<Meta title="Introduction" />
|
|
||||||
|
|
||||||
# Nym Wallet Storybook
|
|
||||||
|
|
||||||
This is the Storybook for the Nym Wallet.
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentMeta } from '@storybook/react';
|
|
||||||
import { Playground } from '@nymproject/react/playground/Playground';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Playground',
|
|
||||||
component: Playground,
|
|
||||||
} as ComponentMeta<typeof Playground>;
|
|
||||||
|
|
||||||
export const AllControls = () => <Playground />;
|
|
||||||
@@ -5,13 +5,11 @@
|
|||||||
"noEmit": true
|
"noEmit": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
".storybook/*.js",
|
|
||||||
"webpack.*.js",
|
"webpack.*.js",
|
||||||
"src/**/*.js",
|
"src/**/*.js",
|
||||||
"src/**/*.jsx",
|
"src/**/*.jsx",
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx"
|
||||||
"src/**/*.stories.*",
|
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|||||||
Reference in New Issue
Block a user