Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfde3bb2e4 | |||
| 4ef59bb95b |
@@ -0,0 +1,75 @@
|
||||
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,6 +5,8 @@
|
||||
>
|
||||
> ➡️➡️➡️➡️➡️ **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 }}
|
||||
>
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* 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,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -0,0 +1,113 @@
|
||||
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.`));
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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];
|
||||
@@ -0,0 +1,21 @@
|
||||
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,7 +10,10 @@
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"prebuild": "yarn --cwd .. build",
|
||||
"prestorybook": "yarn --cwd .. build",
|
||||
"prewebpack:dev": "yarn --cwd .. build",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"storybook:build": "build-storybook",
|
||||
"tauri:build": "yarn tauri build",
|
||||
"tauri:dev": "yarn tauri dev",
|
||||
"tauri:buildx86": "yarn tauri build --target x86_64-apple-darwin",
|
||||
@@ -32,6 +35,7 @@
|
||||
"@nymproject/node-tester": "^1.2.3",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@tauri-apps/api": "^2.4.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
||||
"@tauri-apps/plugin-opener": "^2.2.6",
|
||||
@@ -68,6 +72,7 @@
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/cli": "^2.4.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
@@ -100,7 +105,8 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"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-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "NymWallet"
|
||||
version = "1.2.18"
|
||||
version = "1.2.19"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"productName": "NymWallet",
|
||||
"mainBinaryName": "NymWallet",
|
||||
"version": "1.2.18",
|
||||
"version": "1.2.19",
|
||||
"identifier": "net.nymtech.wallet",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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>
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
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 { Box, Typography, Grid, Link, Card, CardContent, Stack } from '@mui/material';
|
||||
import { NymCard } from '..';
|
||||
import BitfinexIcon from 'src/svg-icons/bitfinex.svg';
|
||||
import KrakenIcon from 'src/svg-icons/kraken.svg';
|
||||
import BybitIcon from 'src/svg-icons/bybit.svg';
|
||||
import GateIcon from 'src/svg-icons/gate22.svg';
|
||||
import HTXIcon from 'src/svg-icons/htx.svg';
|
||||
import { NymCard } from '..';
|
||||
|
||||
const ExchangeCard = ({
|
||||
name,
|
||||
tokenType,
|
||||
url,
|
||||
IconComponent,
|
||||
const ExchangeCard = ({
|
||||
name,
|
||||
tokenType,
|
||||
url,
|
||||
IconComponent
|
||||
}: {
|
||||
name: string;
|
||||
tokenType: string;
|
||||
url: string;
|
||||
IconComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
||||
}) => (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
height: '100%',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: 2,
|
||||
},
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ p: 3 }}>
|
||||
@@ -51,17 +51,17 @@ const ExchangeCard = ({
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||
{tokenType}
|
||||
</Typography>
|
||||
<Link
|
||||
href={url}
|
||||
target="_blank"
|
||||
<Link
|
||||
href={url}
|
||||
target="_blank"
|
||||
variant="body2"
|
||||
data-testid="link-get-nym"
|
||||
sx={{
|
||||
sx={{
|
||||
textDecoration: 'underline',
|
||||
fontWeight: 500,
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
}
|
||||
}}
|
||||
>
|
||||
GET NYM
|
||||
@@ -78,40 +78,44 @@ export const Tutorial = () => {
|
||||
name: 'Bitfinex',
|
||||
tokenType: 'Native NYM, ERC-20',
|
||||
url: 'https://www.bitfinex.com/',
|
||||
IconComponent: BitfinexIcon,
|
||||
IconComponent: BitfinexIcon
|
||||
},
|
||||
{
|
||||
name: 'Kraken',
|
||||
tokenType: 'Native NYM',
|
||||
url: 'https://www.kraken.com/',
|
||||
IconComponent: KrakenIcon,
|
||||
IconComponent: KrakenIcon
|
||||
},
|
||||
{
|
||||
name: 'Bybit',
|
||||
tokenType: 'ERC-20',
|
||||
url: 'https://www.bybit.com/en/',
|
||||
IconComponent: BybitIcon,
|
||||
IconComponent: BybitIcon
|
||||
},
|
||||
{
|
||||
name: 'Gate.io',
|
||||
tokenType: 'ERC-20',
|
||||
url: 'https://www.gate.io/',
|
||||
IconComponent: GateIcon,
|
||||
IconComponent: GateIcon
|
||||
},
|
||||
{
|
||||
name: 'HTX',
|
||||
tokenType: 'ERC-20',
|
||||
url: 'https://www.htx.com/',
|
||||
IconComponent: HTXIcon,
|
||||
IconComponent: HTXIcon
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<NymCard borderless title="Where you can get NYM tokens" sx={{ mt: 4 }}>
|
||||
<NymCard
|
||||
borderless
|
||||
title="Where you can get NYM tokens"
|
||||
sx={{ mt: 4 }}
|
||||
>
|
||||
<Typography mb={3} fontSize={14} sx={{ color: 'text.secondary' }}>
|
||||
You can get NYM tokens from these exchanges
|
||||
</Typography>
|
||||
|
||||
|
||||
<Grid container spacing={3}>
|
||||
{exchanges.map((exchange) => (
|
||||
<Grid item xs={12} md={6} lg={4} key={exchange.name}>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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,6 +5,15 @@ import { useTheme, Theme } from '@mui/material/styles';
|
||||
import { SimpleModal } from './Modals/SimpleModal';
|
||||
import { ModalFee } from './Modals/ModalFee';
|
||||
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<{
|
||||
open: boolean;
|
||||
@@ -14,9 +23,9 @@ export const ConfirmTx: FCWithChildren<{
|
||||
onConfirm: () => Promise<void>;
|
||||
onClose?: () => void;
|
||||
onPrev: () => void;
|
||||
|
||||
isStorybook?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children }) => {
|
||||
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children, isStorybook }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<SimpleModal
|
||||
@@ -27,6 +36,7 @@ export const ConfirmTx: FCWithChildren<{
|
||||
onOk={onConfirm}
|
||||
onClose={onClose}
|
||||
onBack={onPrev}
|
||||
{...storybookStyles(theme, isStorybook)}
|
||||
>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
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 }} />
|
||||
);
|
||||
@@ -0,0 +1,311 @@
|
||||
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} />;
|
||||
@@ -0,0 +1,190 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
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>
|
||||
);
|
||||
@@ -0,0 +1,144 @@
|
||||
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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
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',
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,260 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
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 { ReceiveModal } from './ReceiveModal';
|
||||
|
||||
export const Receive = () => {
|
||||
export const Receive = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => {
|
||||
const { showReceiveModal, handleShowReceiveModal } = useContext(AppContext);
|
||||
|
||||
if (showReceiveModal) return <ReceiveModal onClose={handleShowReceiveModal} />;
|
||||
if (showReceiveModal) return <ReceiveModal onClose={handleShowReceiveModal} {...hasStorybookStyles} />;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
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>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
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>
|
||||
);
|
||||
@@ -0,0 +1,91 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
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 { TTransactionDetails } from './types';
|
||||
|
||||
export const SendModal = ({ onClose }: { onClose: () => void }) => {
|
||||
export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void; hasStorybookStyles?: {} }) => {
|
||||
const [toAddress, setToAddress] = useState<string>('');
|
||||
const [amount, setAmount] = useState<DecCoin>();
|
||||
const [modal, setModal] = useState<'send' | 'send details'>('send');
|
||||
@@ -108,6 +108,7 @@ export const SendModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onSend={handleSend}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
memo={memo}
|
||||
{...hasStorybookStyles}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -129,6 +130,7 @@ export const SendModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onUserFeesChange={(value) => setUserFees(value)}
|
||||
onMemoChange={(value) => setMemo(value)}
|
||||
setShowMore={setShowMoreOptions}
|
||||
{...hasStorybookStyles}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { useContext } from 'react';
|
||||
import { AppContext } from 'src/context';
|
||||
import { SendModal } from './SendModal';
|
||||
|
||||
export const Send = () => {
|
||||
export const Send = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => {
|
||||
const { showSendModal, handleShowSendModal } = useContext(AppContext);
|
||||
|
||||
if (showSendModal) return <SendModal onClose={handleShowSendModal} />;
|
||||
if (showSendModal) return <SendModal onClose={handleShowSendModal} hasStorybookStyles={hasStorybookStyles} />;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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,7 +118,25 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const updateDelegation = async (newDelegation: DelegationWithEverything): Promise<TDelegationTransaction> => {
|
||||
const updateDelegation = async (
|
||||
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);
|
||||
mockDelegations = mockDelegations.map((d) => {
|
||||
if (d.node_identity === newDelegation.node_identity) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
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>
|
||||
);
|
||||
@@ -0,0 +1,140 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
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(),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Tutorial } from '../../components/Buy/Tutorial';
|
||||
|
||||
export default {
|
||||
title: 'Buy/Page',
|
||||
component: Tutorial,
|
||||
};
|
||||
|
||||
export const BuyPage = () => <Tutorial />;
|
||||
@@ -0,0 +1,19 @@
|
||||
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,9 +19,18 @@ import { UndelegateModal } from '../../components/Delegation/UndelegateModal';
|
||||
import { DelegationListItemActions } from '../../components/Delegation/DelegationActions';
|
||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
||||
import { VestingWarningModal } from '../../components/VestingWarningModal';
|
||||
|
||||
export const Delegation: FC = () => {
|
||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||
isStorybook
|
||||
? {
|
||||
backdropProps: { ...backDropStyles(theme), ...backdropProps },
|
||||
sx: modalStyles(theme),
|
||||
}
|
||||
: {};
|
||||
|
||||
export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||
const [showDelegateMoreModal, setShowDelegateMoreModal] = useState<boolean>(false);
|
||||
const [showUndelegateModal, setShowUndelegateModal] = useState<boolean>(false);
|
||||
@@ -463,6 +472,7 @@ export const Delegation: FC = () => {
|
||||
accountBalance={balance?.printable_balance}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={Boolean(originalVesting)}
|
||||
{...storybookStyles(theme, isStorybook)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -538,10 +548,10 @@ export const Delegation: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const DelegationPage: FC = () => (
|
||||
export const DelegationPage: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => (
|
||||
<DelegationContextProvider>
|
||||
<RewardsContextProvider>
|
||||
<Delegation />
|
||||
<Delegation isStorybook={isStorybook} />
|
||||
</RewardsContextProvider>
|
||||
</DelegationContextProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="Introduction" />
|
||||
|
||||
# Nym Wallet Storybook
|
||||
|
||||
This is the Storybook for the Nym Wallet.
|
||||
@@ -0,0 +1,10 @@
|
||||
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,11 +5,13 @@
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
".storybook/*.js",
|
||||
"webpack.*.js",
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.stories.*",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
Reference in New Issue
Block a user