feat(wallet): buy page bootstrap

This commit is contained in:
pierre
2022-09-12 16:20:51 +02:00
committed by durch
parent cb2a89dceb
commit 8a21f183f9
17 changed files with 338 additions and 5 deletions
@@ -0,0 +1,12 @@
import React from 'react';
import { DeclinedModal } from './DeclinedModal';
export default {
title: 'Buy/DeclinedModal',
component: DeclinedModal,
};
export const Terms = () => (
<DeclinedModal onOk={async () => console.log('user has accepted')} onClose={async () => console.log('closed')} />
);
@@ -0,0 +1,15 @@
import React from 'react';
import { Typography } from '@mui/material';
import { ConfirmationModal } from '../Modals/ConfirmationModal';
export const DeclinedModal = ({ onOk, onClose }: { onOk: () => Promise<void>; onClose: () => void }) => (
<ConfirmationModal
open
title="Buy Nym Terms and Conditions"
confirmButton="Go back to Terms and Conditions"
onConfirm={onOk}
onClose={onClose}
>
<Typography>Cant procced to buy tokens without acceptance</Typography>
</ConfirmationModal>
);
@@ -0,0 +1,35 @@
import React, { useState } from 'react';
import { Button } from '@mui/material';
import { MockBuyContextProvider } from 'src/context/mocks/buy';
import { SignMessageModal } from './SignMessageModal';
export default {
title: 'Buy/SignMessage',
component: SignMessageModal,
};
export const SignMessage = () => {
const [open, setOpen] = useState(false);
return (
<MockBuyContextProvider>
<Button
variant="contained"
color="primary"
onClick={() => {
setOpen(true);
}}
>
Open Sign Modal
</Button>
{open && (
<SignMessageModal
onClose={() => {
setOpen(false);
}}
/>
)}
</MockBuyContextProvider>
);
};
@@ -0,0 +1,77 @@
import * as React from 'react';
import { useState } from 'react';
import { Stack, TextField, Typography } from '@mui/material';
import { useBuyContext } from 'src/context';
import { SimpleModal } from '../Modals/SimpleModal';
import { ErrorModal } from '../Modals/ErrorModal';
import { CopyToClipboard } from '../CopyToClipboard';
export const SignMessageModal = ({ onClose }: { onClose: () => void }) => {
const [message, setMessage] = useState<string>();
const [signature, setSignature] = useState<string>();
const { signMessage, loading, refresh, error } = useBuyContext();
const handleSign = async () => {
if (!message) {
return;
}
signMessage(message).then((sig) => {
setSignature(sig);
});
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setMessage(event.target.value);
};
if (error) {
<ErrorModal
open
message={`An error occured: ${error}`}
onClose={() => {
refresh();
}}
/>;
}
return (
<SimpleModal
open
header="Sign message"
okLabel="Sign Message"
onOk={handleSign}
onClose={onClose}
okDisabled={loading}
>
<Stack gap={2}>
<TextField
id="outlined-multiline-static"
label="Message"
multiline
rows={8}
placeholder="Paste your message here"
fullWidth
value={message}
onChange={handleChange}
/>
<TextField
id="outlined-multiline-static"
multiline
rows={3}
value={signature}
placeholder="Signature"
fullWidth
disabled
/>
<Stack direction="row" alignItems="center" alignSelf="flex-end">
<Typography variant="body2" component="span" fontWeight={400} sx={{ mr: 1, color: 'text.primary' }}>
Copy signature
</Typography>
<CopyToClipboard text={signature} />
</Stack>
</Stack>
</SimpleModal>
);
};
@@ -0,0 +1,10 @@
import React from 'react';
import { Tutorial } from './Tutorial';
export default {
title: 'Buy/Page',
component: Tutorial,
};
export const BuyPage = () => <Tutorial />;
@@ -0,0 +1,24 @@
import React from 'react';
import { Box, Button, Stack, Typography } from '@mui/material';
import { NymCard } from '../NymCard';
export const Tutorial = () => (
<Box>
<Stack direction="row" justifyContent="space-between" sx={{ mb: 3, mt: 2 }}>
<Box>
<Typography variant="h5" sx={{ fontWeight: 600, mb: 1 }}>
How to buy NYM with Bity?
</Typography>
<Typography variant="subtitle1">Follow these 3 steps below to quickly and easily buy NYM tokens</Typography>
</Box>
<Button variant="contained" color="primary" onClick={() => {}}>
Buy Nym
</Button>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="space-between" gap={1}>
<NymCard title="1. Define the purchase amount">Card 1</NymCard>
<NymCard title="2. Sign a message with your Nym wallet">Card 2</NymCard>
<NymCard title="3. Transfer funds and receive NYM">Card 3</NymCard>
</Stack>
</Box>
);
@@ -35,6 +35,7 @@ export const CopyToClipboard = ({ text = '', iconButton }: { text?: string; icon
sx={{
color: 'text.primary',
}}
disabled={!text}
>
{!copied ? <ContentCopy sx={{ fontSize: 14 }} /> : <Check color="success" sx={{ fontSize: 14 }} />}
</IconButton>
@@ -51,6 +52,7 @@ export const CopyToClipboard = ({ text = '', iconButton }: { text?: string; icon
}}
onClick={() => handleCopy(text)}
endIcon={copied && <Check sx={{ color: (theme) => theme.palette.success.light }} />}
disabled={!text}
>
{!copied ? 'Copy' : 'Copied'}
</Button>
@@ -225,3 +225,36 @@ export const withBackButton = () => {
</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>
);
};
@@ -20,6 +20,8 @@ export const SimpleModal: React.FC<{
header: string | React.ReactNode;
subHeader?: string;
okLabel: string;
backLabel?: string;
backButtonFullWidth?: boolean;
okDisabled?: boolean;
sx?: SxProps;
backdropProps?: object;
@@ -38,6 +40,8 @@ export const SimpleModal: React.FC<{
header,
subHeader,
okLabel,
backLabel,
backButtonFullWidth,
sx,
children,
backdropProps,
@@ -71,7 +75,7 @@ export const SimpleModal: React.FC<{
{(onOk || onBack) && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2, width: buttonFullWidth ? '100%' : null }}>
{onBack && <StyledBackButton onBack={onBack} />}
{onBack && <StyledBackButton onBack={onBack} label={backLabel} fullWidth={backButtonFullWidth} />}
{onOk && (
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled}>
{okLabel}
+6
View File
@@ -54,6 +54,12 @@ export const Nav = () => {
mode: 'admin',
onClick: () => navigate('/admin'),
},
{
label: 'Buy',
route: '/buy',
Icon: Bonding,
onClick: () => navigate('/buy'),
},
]);
return (
+13 -3
View File
@@ -2,8 +2,18 @@ import React from 'react';
import { Button, SxProps } from '@mui/material';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
export const StyledBackButton = ({ onBack, sx }: { onBack: () => void; sx?: SxProps }) => (
<Button disableFocusRipple size="large" variant="outlined" onClick={onBack} sx={sx}>
<ArrowBackIosNewIcon fontSize="small" />
export const StyledBackButton = ({
onBack,
label,
fullWidth,
sx,
}: {
onBack: () => void;
label?: string;
fullWidth?: boolean;
sx?: SxProps;
}) => (
<Button disableFocusRipple size="large" fullWidth={fullWidth} variant="outlined" onClick={onBack} sx={sx}>
{label || <ArrowBackIosNewIcon fontSize="small" />}
</Button>
);
+58
View File
@@ -0,0 +1,58 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { sign } from 'src/requests';
import { Console } from 'src/utils/console';
// import { AppContext } from './main';
export type TBuyContext = {
loading: boolean;
error?: string;
signMessage: (message: string) => Promise<string | undefined>;
refresh: () => Promise<void>;
};
export const BuyContext = createContext<TBuyContext>({
loading: false,
signMessage: async () => '',
refresh: async () => undefined,
});
export const BuyContextProvider = ({ children }: { children?: React.ReactNode }): JSX.Element => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const refresh = useCallback(async () => {
setError(undefined);
}, []);
useEffect(() => {
refresh();
}, [refresh]);
const signMessage = async (message: string) => {
let signature;
setLoading(true);
try {
signature = await sign(message);
} catch (e: any) {
Console.log(`Sign message operation failed: ${e}`);
setError(`Sign message operation failed: ${e}`);
} finally {
setLoading(false);
}
return signature;
};
const memoizedValue = useMemo(
() => ({
loading,
error,
refresh,
signMessage,
}),
[loading, error],
);
return <BuyContext.Provider value={memoizedValue}>{children}</BuyContext.Provider>;
};
export const useBuyContext = () => useContext<TBuyContext>(BuyContext);
+1
View File
@@ -2,3 +2,4 @@ export * from './main';
export * from './auth';
export * from './accounts';
export * from './bonding';
export * from './buy';
+35
View File
@@ -0,0 +1,35 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { BuyContext } from '../buy';
import { mockSleep } from './utils';
export const MockBuyContextProvider = ({ children }: { children?: React.ReactNode }): JSX.Element => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
const refresh = useCallback(async () => {
setError(undefined);
}, []);
const signMessage = async (message: string) => {
setLoading(true);
await mockSleep(1042);
setLoading(false);
return `imagineareallyrealisticsignaturehash${message}`;
};
useEffect(() => {
refresh();
}, [refresh]);
const memoizedValue = useMemo(
() => ({
loading,
error,
refresh,
signMessage,
}),
[loading, error],
);
return <BuyContext.Provider value={memoizedValue}>{children}</BuyContext.Provider>;
};
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
import { BuyContextProvider } from 'src/context';
import { Tutorial } from 'src/components/Buy/Tutorial';
export const BuyPage = () => (
<BuyContextProvider>
<Tutorial />
</BuyContextProvider>
);
+1
View File
@@ -4,3 +4,4 @@ export * from './balance';
export * from './bonding';
export * from './delegation';
export * from './internal-docs';
export * from './buy';
+2 -1
View File
@@ -4,7 +4,7 @@ import { ApplicationLayout } from 'src/layouts';
import { Terminal } from 'src/pages/terminal';
import { Send } from 'src/components/Send';
import { Receive } from '../components/Receive';
import { Balance, InternalDocs, DelegationPage, Admin, BondingPage, NodeSettingsPage } from '../pages';
import { Balance, InternalDocs, DelegationPage, Admin, BondingPage, NodeSettingsPage, BuyPage } from '../pages';
export const AppRoutes = () => (
<ApplicationLayout>
@@ -18,6 +18,7 @@ export const AppRoutes = () => (
<Route path="/delegation" element={<DelegationPage />} />
<Route path="/docs" element={<InternalDocs />} />
<Route path="/admin" element={<Admin />} />
<Route path="/buy" element={<BuyPage />} />
</Routes>
</ApplicationLayout>
);