Compare commits

...

20 Commits

Author SHA1 Message Date
gala1234 e3c9b11258 clean status once modal closes 2022-04-11 14:11:52 +02:00
gala1234 e7179eaa46 adding seleting validator functionality 2022-04-11 13:43:19 +02:00
gala1234 84cf1c6a62 some ui 2022-04-11 12:40:28 +02:00
gala1234 f5e874f8d9 Merge branch 'develop' into feature/validator_selector 2022-04-11 12:04:45 +02:00
gala1234 183f2779f0 story wip 2022-04-05 16:30:53 +02:00
gala1234 92e56c0121 wip story 2022-04-05 14:43:50 +02:00
gala1234 550e0ce856 wip 2022-04-05 14:27:48 +02:00
gala1234 3b0215ccee Merge branch 'develop' into feature/validator_selector 2022-04-05 13:39:59 +02:00
gala1234 38a8621032 starting storybook implementation 2022-04-05 13:38:10 +02:00
gala1234 caa0bc4e1e some ui changes on modal 2022-04-05 10:10:36 +02:00
gala1234 abcb0cbf5e wip display validators settings on a modal 2022-04-04 12:30:11 +02:00
gala1234 cdbcfbe3bf wip creating settings modal 2022-04-01 17:44:17 +02:00
gala1234 b8ee730561 adding dummy urls on mainnet and some dropdown improvements 2022-03-31 10:01:46 +02:00
gala1234 26a067c14d playing with mui 2022-03-30 17:41:04 +02:00
gala1234 69fe8e5cce some indent fix 2022-03-30 17:13:10 +02:00
gala1234 4fbf1cd876 connexion to fetch validator urls 2022-03-30 17:03:00 +02:00
gala1234 c693412258 Merge branch 'feature/wallet-expose-validator-urls' into feature/validator_selector 2022-03-30 13:43:19 +02:00
gala1234 72825a2ad3 start validators fetching 2022-03-30 13:40:02 +02:00
gala1234 89a19815fc first validators dropdown implementation 2022-03-30 13:33:52 +02:00
Jon Häggblad 394f0d30bf wallet: expose validator urls to the frontent 2022-03-30 11:51:41 +02:00
17 changed files with 404 additions and 16 deletions
+6
View File
@@ -22,5 +22,11 @@ pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://rpc.nyx.nodes.guru/",
Some("https://api.nyx.nodes.guru/"),
), ValidatorDetails::new(
"https://test2.nyx.nodes.guru/",
Some("https://test1.nyx.nodes.guru/"),
), ValidatorDetails::new(
"https://test2.nyx.nodes.guru/",
Some("https://test1.nyx.nodes.guru/"),
)]
}
+11 -1
View File
@@ -4,7 +4,17 @@
*/
module.exports = {
invoke: (operation) => {
invoke: (operation, ...args) => {
switch(operation) {
case 'get_validator_nymd_urls': {
const { network } = args;
return new Promise(resolve => {
resolve({
urls: ['foo', 'bar'],
});
});
}
}
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.`));
+12 -1
View File
@@ -4,9 +4,11 @@ import { Logout } from '@mui/icons-material';
import { ClientContext } from '../context/main';
import { NetworkSelector } from './NetworkSelector';
import { Node as NodeIcon } from '../svg-icons/node';
import { Delegate as DelegateIcon } from '../svg-icons';
export const AppBar = () => {
const { showSettings, logOut, handleShowSettings } = useContext(ClientContext);
const { showSettings, showValidatorSettings, logOut, handleShowSettings, handleShowValidatorSettings } = useContext(ClientContext);
return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent' }}>
@@ -16,6 +18,15 @@ export const AppBar = () => {
<NetworkSelector />
</Grid>
<Grid item container justifyContent="flex-end" md={12} lg={5} spacing={2}>
<Grid item>
<IconButton
onClick={handleShowValidatorSettings}
sx={{ color: showValidatorSettings ? 'primary.main' : 'nym.background.dark' }}
size="small"
>
<DelegateIcon fontSize="small" />
</IconButton>
</Grid>
<Grid item>
<IconButton
onClick={handleShowSettings}
+55 -4
View File
@@ -2,10 +2,10 @@ import React, { useMemo, createContext, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useSnackbar } from 'notistack';
import { TLoginType } from 'src/pages/sign-in/types';
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
import { Account, Network, TCurrency, TMixnodeBondDetails, ValidatorUrls } from '../types';
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
import { config } from '../../config';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut, getValidatorUrls, selectValidatorNymdUrl } from '../requests';
import { currencyMap } from '../utils';
import { Console } from '../utils/console';
@@ -29,6 +29,8 @@ type TClientContext = {
userBalance: TUseuserBalance;
showAdmin: boolean;
showSettings: boolean;
showValidatorSettings: boolean;
validatorsUrl?: ValidatorUrls;
network?: Network;
currency?: TCurrency;
isLoading: boolean;
@@ -37,7 +39,10 @@ type TClientContext = {
setError: (value?: string) => void;
switchNetwork: (network: Network) => void;
getBondDetails: () => Promise<void>;
fetchValidatorsUrl: (network: Network) => Promise<void>;
selectValidatorNymd: (validatorNymd: string, network: Network) => Promise<void>;
handleShowSettings: () => void;
handleShowValidatorSettings: () => void;
handleShowAdmin: () => void;
logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void;
signInWithPassword: (password: string) => void;
@@ -49,10 +54,12 @@ export const ClientContext = createContext({} as TClientContext);
export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => {
const [clientDetails, setClientDetails] = useState<Account>();
const [mixnodeDetails, setMixnodeDetails] = useState<TMixnodeBondDetails | null>();
const [validatorsUrl, setValidatorsUrl] = useState<ValidatorUrls>();
const [network, setNetwork] = useState<Network | undefined>();
const [currency, setCurrency] = useState<TCurrency>();
const [showAdmin, setShowAdmin] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [showValidatorSettings, setShowValidatorSettings] = useState(false);
const [mode] = useState<'light' | 'dark'>('light');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>();
@@ -83,17 +90,49 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
}
};
const fetchValidatorsUrl = async (network: Network) => {
try {
if (network) {
const urls = await getValidatorUrls(network);
setValidatorsUrl(urls);
}
} catch (e) {
Console.error(e as string);
}
};
const selectValidatorNymd = async (validatorNymd: string, network: Network) => {
try {
if (network) {
const response = await selectValidatorNymdUrl(validatorNymd, network);
return response;
}
} catch (e) {
Console.error(e as string);
}
};
useEffect(() => {
const refreshAccount = async () => {
if (network) {
await loadAccount(network);
await getBondDetails();
await userBalance.fetchBalance();
await fetchValidatorsUrl(network);
}
};
refreshAccount();
}, [network]);
useEffect(() => {
const refreshValidators = async () => {
if (!validatorsUrl && network) {
await fetchValidatorsUrl(network);
}
};
refreshValidators();
}, [showValidatorSettings]);
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
if (value.length === 0) {
setError(`A ${type} must be provided`);
@@ -127,7 +166,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
};
const handleShowAdmin = () => setShowAdmin((show) => !show);
const handleShowSettings = () => setShowSettings((show) => !show);
const handleShowSettings = () => {
setShowSettings((show) => !show)
setShowValidatorSettings(false);
};
const handleShowValidatorSettings = () => {
setShowValidatorSettings((show) => !show)
setShowSettings(false);
};
const switchNetwork = (_network: Network) => setNetwork(_network);
const memoizedValue = useMemo(
@@ -140,6 +186,8 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
userBalance,
showAdmin,
showSettings,
showValidatorSettings,
validatorsUrl,
network,
currency,
setIsLoading,
@@ -147,12 +195,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
signInWithPassword,
switchNetwork,
getBondDetails,
fetchValidatorsUrl,
selectValidatorNymd,
handleShowSettings,
handleShowValidatorSettings,
handleShowAdmin,
logIn,
logOut,
}),
[mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency],
[mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, showValidatorSettings, validatorsUrl, selectValidatorNymd, network, currency],
);
return <ClientContext.Provider value={memoizedValue}>{children}</ClientContext.Provider>;
+2 -1
View File
@@ -6,7 +6,7 @@ import { SnackbarProvider } from 'notistack';
import { AppRoutes, SignInRoutes } from './routes';
import { ClientContext, ClientContextProvider } from './context/main';
import { ApplicationLayout } from './layouts';
import { Admin, Settings } from './pages';
import { Admin, Settings, ValidatorSettingsModal } from './pages';
import { ErrorFallback } from './components';
import { NymWalletTheme, WelcomeTheme } from './theme';
import { maximizeWindow } from './utils';
@@ -29,6 +29,7 @@ const App = () => {
<NymWalletTheme>
<ApplicationLayout>
<Settings />
<ValidatorSettingsModal />
<Admin />
<AppRoutes />
</ApplicationLayout>
+1
View File
@@ -9,3 +9,4 @@ export * from './sign-in';
export * from './settings';
export * from './unbond';
export * from './undelegate';
export * from './validators';
@@ -0,0 +1 @@
export { ValidatorSettingsModal } from './validatorModal';
@@ -0,0 +1,88 @@
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Button, Paper } from '@mui/material';
import { ValidatorSettingsModal } from './validatorModal';
export default {
title: 'Modals/Validator Setting Modal',
component: ValidatorSettingsModal,
} as ComponentMeta<typeof ValidatorSettingsModal>;
export const Default = () => {
const [open, setOpen] = React.useState<boolean>(true);
return (
<>
<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>
<ValidatorSettingsModal/>
</>
);
};
export const NoSubheader = () => {
const [open, setOpen] = React.useState<boolean>(true);
return (
<>
<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>
<ValidatorSettingsModal />
</>
);
};
@@ -0,0 +1,142 @@
import React, { useContext, useEffect, useState } from 'react';
import { Button, Box, Dialog, CircularProgress, Typography } from '@mui/material';
import { NymCard } from '../../components';
import { ClientContext } from '../../context/main';
import { ValidatorSelector } from './validatorSelector';
import { Delegate as DelegateIcon } from '../../svg-icons';
import { Console } from '../../utils/console';
export const ValidatorSettingsModal = () => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [validatorSelectedSuccessfully, setValidatorSelectedSuccessfully] = useState(false);
const [validator, setValidator] = useState('');
const { showValidatorSettings, getBondDetails, handleShowValidatorSettings, network, selectValidatorNymd } = useContext(ClientContext);
useEffect(() => {
if (showValidatorSettings) {
getBondDetails();
} else {
setValidatorSelectedSuccessfully(false);
};
}, [showValidatorSettings]);
const onDataChanged = (selectedValidator: string) => {
if (selectedValidator) {
setValidator(selectedValidator);
};
}
const handleSubmit = async () => {
setIsSubmitting(true);
try {
if (network) {
selectValidatorNymd(validator, network).then(res => console.log('res', res));
setValidatorSelectedSuccessfully(true);
}
} catch (e) {
Console.error(e as string);
} finally {
setIsSubmitting(false);
}
};
return showValidatorSettings ? (
<Dialog open onClose={handleShowValidatorSettings} maxWidth="md" fullWidth>
<NymCard
title={
<Box display="flex" alignItems="center">
<DelegateIcon sx={{ mr: 1 }} />
Settings
</Box>
}
noPadding
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
padding: 3,
borderTop: '1px solid',
borderColor: 'grey.300',
}}
>
<Typography
variant="h6"
sx={{
fontWeight: 600,
}}>
Wallet Settings
</Typography>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
p: 2,
pl: 3,
borderTop: '1px solid',
borderBottom: '1px solid',
borderColor: 'grey.300',
bgcolor: 'grey.200',
}}
>
<Typography
variant="h6"
sx={{
fontWeight: 600,
}}>
Validators
</Typography>
</Box>
<>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
minHeight: 300,
padding: 3,
}}
>
<ValidatorSelector
type="Validator API Url"
onChangeValidatorSelection={(selectedValidator) => onDataChanged(selectedValidator)}
/>
{validatorSelectedSuccessfully && (
<Typography sx={{ pt: 2, fontSize: 12, color: (theme) => theme.palette.success.light }}>
Successfully selected the validator: {validator}
</Typography>
)}
</Box>
</>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: 3,
bgcolor: 'grey.200',
borderTop: '1px solid',
borderColor: 'grey.300',
}}
>
<Button
size="large"
variant="contained"
data-testid="validatorsSettings-button"
color="primary"
disableElevation
onClick={() => handleSubmit()}
disabled={isSubmitting}
endIcon={isSubmitting && <CircularProgress size={20} />}
>
Save Changes
</Button>
</Box>
</NymCard>
</Dialog>
) : null;
};
@@ -0,0 +1,49 @@
import React, { useContext, useEffect, useState } from 'react';
import { FormControl, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent, Typography, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { ClientContext } from '../../context/main';
type TValidatorUrl = string;
export const ValidatorSelector: React.FC<{ onChangeValidatorSelection: (validator: TValidatorUrl) => void, type: string }> = ({
onChangeValidatorSelection,
}) => {
const [selectedValidator, setSelectedValidator] = useState<TValidatorUrl>('');
const {
validatorsUrl
} = useContext(ClientContext);
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down('sm'));
useEffect(() => {
onChangeValidatorSelection(selectedValidator);
}, [selectedValidator]);
return (
<FormControl fullWidth>
<InputLabel id="validatorSelect_label">Validator API Url</InputLabel>
<Select
labelId="validatorSelect_label"
id="validatorSelect"
sx={{
width: '100%'
}}
value={selectedValidator || ''}
label="Choose a Validator"
onChange={(e: SelectChangeEvent) => {
setSelectedValidator(e.target.value as TValidatorUrl);
}}
renderValue={(value) => <Typography sx={{ textTransform: 'capitalize' }}>{value}</Typography>}
>
{
validatorsUrl && validatorsUrl.urls.map((validator) => (
<MenuItem value={validator} key={validator}>
<ListItemText>{validator}</ListItemText>
</MenuItem>
))
}
</Select>
</FormControl>
)
};
+1
View File
@@ -5,3 +5,4 @@ export * from './contract';
export * from './vesting';
export * from './network';
export * from './queries';
export * from './validators';
+16
View File
@@ -0,0 +1,16 @@
import { invoke } from '@tauri-apps/api';
import { Network } from '../types';
import {
ValidatorUrls
} from '../types';
export const getValidatorUrls = async (network: Network): Promise<ValidatorUrls> => {
const res: ValidatorUrls = await invoke('get_validator_nymd_urls', { network });
return res;
};
export const selectValidatorNymdUrl = async (validator: string, network: Network): Promise<void> => {
const res: void = await invoke('select_validator_nymd_url', { url: validator, network });
return res;
};
@@ -0,0 +1,10 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Playground } from '@nymproject/react';
export default {
title: 'Playground',
component: Playground,
} as ComponentMeta<typeof Playground>;
export const AllControls = () => <Playground />;
+1
View File
@@ -23,3 +23,4 @@ export * from './vestingperiod';
export * from './pendingundelegate';
export * from './delegationevent';
export * from './epoch';
export * from './validatorurls';
+1 -1
View File
@@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api';
import { appWindow } from '@tauri-apps/api/window';
import bs58 from 'bs58';
import { valid } from 'semver';
import { userBalance, majorToMinor, getLockedCoins, getSpendableCoins } from '../requests';
import { userBalance, majorToMinor, getLockedCoins, getSpendableCoins, getValidatorUrls } from '../requests';
import { Coin, Network, TCurrency } from '../types';
import { Console } from './console';
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './components';
export * from './hooks';
export { Playground } from './playground';
export { Playground } from './playground/Playground';