Add address copy button (#300)
* Add address copy button * CR requests implemented * Fix lint Co-authored-by: VladStarostenko <38181661+VladStarostenko@users.noreply.github.com>
This commit is contained in:
parent
1af44b4264
commit
e3407782ed
@ -22,6 +22,7 @@
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.6",
|
||||
"@types/react-copy-to-clipboard": "^4.3.0",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-router": "^5.1.4",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Button, ButtonArea, Checkbox, MnemonicSeed, VerticalSpace, Warning } from '../../components';
|
||||
import useToast from '../../hooks/useToast';
|
||||
|
||||
interface Props {
|
||||
onNextStep: () => void;
|
||||
@ -23,13 +24,18 @@ const onCopy = (): void => {
|
||||
|
||||
function Mnemonic ({ onNextStep, seed }: Props): React.ReactElement<Props> {
|
||||
const [isMnemonicSaved, setIsMnemonicSaved] = useState(false);
|
||||
const { show } = useToast();
|
||||
const _onCopy = useCallback((): void => {
|
||||
onCopy();
|
||||
show('Copied');
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Warning>Please write down your wallet’s mnemonic seed and keep it in a safe place. <br />
|
||||
Mnemonic seed is used to restore your wallet. Keep it carefully in case you lose your assets.</Warning>
|
||||
<MnemonicSeed
|
||||
onCopy={onCopy}
|
||||
onCopy={_onCopy}
|
||||
seed={seed}
|
||||
/>
|
||||
<VerticalSpace />
|
||||
|
||||
@ -11,6 +11,7 @@ import { setSS58Format } from '@polkadot/util-crypto';
|
||||
|
||||
import { Loading } from '../components';
|
||||
import { AccountContext, ActionContext, AuthorizeReqContext, MediaContext, MetadataReqContext, SigningReqContext } from '../components/contexts';
|
||||
import ToastProvider from '../components/Toast/ToastProvider';
|
||||
import { subscribeAccounts, subscribeAuthorizeRequests, subscribeMetadataRequests, subscribeSigningRequests } from '../messaging';
|
||||
import Accounts from './Accounts';
|
||||
import Authorize from './Authorize';
|
||||
@ -100,20 +101,22 @@ export default function Popup (): React.ReactElement<{}> {
|
||||
<MediaContext.Provider value={cameraOn && mediaAllowed}>
|
||||
<MetadataReqContext.Provider value={metaRequests}>
|
||||
<SigningReqContext.Provider value={signRequests}>
|
||||
<Switch>
|
||||
<Route path='/account/create'><CreateAccount /></Route>
|
||||
<Route path='/account/forget/:address'><Forget /></Route>
|
||||
<Route path='/account/export/:address'><Export /></Route>
|
||||
<Route path='/account/import-qr'><ImportQr /></Route>
|
||||
<Route path='/account/import-seed'><ImportSeed /></Route>
|
||||
<Route path='/account/derive/:address'><Derive /></Route>
|
||||
<Route
|
||||
exact
|
||||
path='/'
|
||||
>
|
||||
<Root />
|
||||
</Route>
|
||||
</Switch>
|
||||
<ToastProvider>
|
||||
<Switch>
|
||||
<Route path='/account/create'><CreateAccount /></Route>
|
||||
<Route path='/account/forget/:address'><Forget /></Route>
|
||||
<Route path='/account/export/:address'><Export /></Route>
|
||||
<Route path='/account/import-qr'><ImportQr /></Route>
|
||||
<Route path='/account/import-seed'><ImportSeed /></Route>
|
||||
<Route path='/account/derive/:address'><Derive /></Route>
|
||||
<Route
|
||||
exact
|
||||
path='/'
|
||||
>
|
||||
<Root />
|
||||
</Route>
|
||||
</Switch>
|
||||
</ToastProvider>
|
||||
</SigningReqContext.Provider>
|
||||
</MetadataReqContext.Provider>
|
||||
</MediaContext.Provider>
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
import { AccountJson } from '@polkadot/extension-base/background/types';
|
||||
import { Chain } from '@polkadot/extension-chains/types';
|
||||
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import styled from 'styled-components';
|
||||
import settings from '@polkadot/ui-settings';
|
||||
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
@ -13,11 +14,13 @@ import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||
import { AccountContext } from './contexts';
|
||||
import Identicon from './Identicon';
|
||||
import Svg from './Svg';
|
||||
import Menu from './Menu';
|
||||
|
||||
import Menu from './Menu';
|
||||
import CopyImg from '../assets/copy.svg';
|
||||
import DetailsImg from '../assets/details.svg';
|
||||
import useOutsideClick from '../hooks/useOutsideClick';
|
||||
import useMetadata from '../hooks/useMetadata';
|
||||
import useToast from '../hooks/useToast';
|
||||
|
||||
interface Props {
|
||||
address?: string | null;
|
||||
@ -69,6 +72,7 @@ function Address ({ actions, address, children, className, genesisHash, name }:
|
||||
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
const { show } = useToast();
|
||||
|
||||
useOutsideClick(actionsRef, () => (showActionsMenu && setShowActionsMenu(!showActionsMenu)));
|
||||
|
||||
@ -91,7 +95,8 @@ function Address ({ actions, address, children, className, genesisHash, name }:
|
||||
}, [showActionsMenu]);
|
||||
|
||||
const theme = ((chain && chain.icon) || 'polkadot') as 'polkadot';
|
||||
const _onClick = (): void => setShowActionsMenu(!showActionsMenu);
|
||||
const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]);
|
||||
const _onCopy = useCallback((): void => show('Copied'), [show]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -104,10 +109,18 @@ function Address ({ actions, address, children, className, genesisHash, name }:
|
||||
/>
|
||||
<Info>
|
||||
<Name>{name || (account && account.name) || '<unknown>'}</Name>
|
||||
<FullAddress>{formatted || '<unknown>'}</FullAddress>
|
||||
{chain?.genesisHash && (
|
||||
<Banner>{chain.name}</Banner>
|
||||
)}
|
||||
<CopyAddress>
|
||||
<FullAddress>{formatted || '<unknown>'}</FullAddress>
|
||||
{chain?.genesisHash && (
|
||||
<Banner>{chain.name}</Banner>
|
||||
)}
|
||||
<CopyToClipboard text={(formatted && formatted) || ''} >
|
||||
<Svg
|
||||
onClick={_onCopy}
|
||||
src={CopyImg}
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
</CopyAddress>
|
||||
</Info>
|
||||
{actions && (
|
||||
<>
|
||||
@ -134,6 +147,22 @@ function Address ({ actions, address, children, className, genesisHash, name }:
|
||||
);
|
||||
}
|
||||
|
||||
const CopyAddress = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
& ${Svg} {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 10px;
|
||||
background: ${({ theme }): string => theme.accountDotsIconColor};
|
||||
&:hover {
|
||||
background: ${({ theme }): string => theme.labelColor};
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const AccountInfoRow = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -158,7 +187,7 @@ const Name = styled.div`
|
||||
`;
|
||||
|
||||
const FullAddress = styled.div`
|
||||
width: 300px;
|
||||
width: 270px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: ${({ theme }): string => theme.labelColor};
|
||||
|
||||
35
packages/extension-ui/src/components/Toast/Toast.tsx
Normal file
35
packages/extension-ui/src/components/Toast/Toast.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
content: React.ReactChild;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Toast ({ className, content }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<p className='snackbar-content'>{content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(Toast)<{visible: boolean}>`
|
||||
position: fixed;
|
||||
display: ${({ visible }): string => visible ? 'block' : 'none'};
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 7px;
|
||||
top: 460px;
|
||||
left: calc(50% - 50px);
|
||||
&& {
|
||||
margin: auto;
|
||||
border-radius: 25px;
|
||||
background: ${({ theme }): string => theme.highlightedAreaBackground};
|
||||
}
|
||||
`;
|
||||
41
packages/extension-ui/src/components/Toast/ToastProvider.tsx
Normal file
41
packages/extension-ui/src/components/Toast/ToastProvider.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ToastContext } from '..';
|
||||
import Toast from './Toast';
|
||||
|
||||
interface ToastProviderProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const TOAST_TIMEOUT = 1500;
|
||||
|
||||
const ToastProvider = ({ children }: ToastProviderProps): React.ReactElement<ToastProviderProps> => {
|
||||
const [content, setContent] = useState('');
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const show = useCallback((message: string): () => void => {
|
||||
const timerId = setTimeout(() => setVisible(false), TOAST_TIMEOUT);
|
||||
|
||||
setContent(message);
|
||||
setVisible(true);
|
||||
|
||||
return (): void => clearTimeout(timerId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ show }}>
|
||||
{children}
|
||||
<Toast
|
||||
content={content}
|
||||
visible={visible}
|
||||
/>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToastProvider;
|
||||
|
||||
ToastProvider.displayName = 'Toast';
|
||||
@ -17,6 +17,7 @@ const MediaContext = React.createContext<boolean>(false);
|
||||
const MetadataReqContext = React.createContext<MetadataRequest[]>([]);
|
||||
const SigningReqContext = React.createContext<SigningRequest[]>([]);
|
||||
const ThemeSwitchContext = React.createContext<(theme: AvailableThemes) => void>(noop);
|
||||
const ToastContext = React.createContext<({show: (message: string) => void})>({ show: noop });
|
||||
|
||||
export {
|
||||
AccountContext,
|
||||
@ -25,5 +26,6 @@ export {
|
||||
MediaContext,
|
||||
MetadataReqContext,
|
||||
SigningReqContext,
|
||||
ThemeSwitchContext
|
||||
ThemeSwitchContext,
|
||||
ToastContext
|
||||
};
|
||||
|
||||
10
packages/extension-ui/src/hooks/useToast.tsx
Normal file
10
packages/extension-ui/src/hooks/useToast.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright 2019 @polkadot/extension-ui authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { ToastContext } from '../components/contexts';
|
||||
|
||||
export default function useToast (): {show: (message: string) => void} {
|
||||
return useContext(ToastContext);
|
||||
}
|
||||
10
yarn.lock
10
yarn.lock
@ -2917,6 +2917,7 @@ __metadata:
|
||||
"@types/bn.js": ^4.11.6
|
||||
"@types/enzyme": ^3.10.5
|
||||
"@types/enzyme-adapter-react-16": ^1.0.6
|
||||
"@types/react-copy-to-clipboard": ^4.3.0
|
||||
"@types/react-dom": ^16.9.5
|
||||
"@types/react-router": ^5.1.4
|
||||
"@types/react-router-dom": ^5.1.3
|
||||
@ -3503,6 +3504,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-copy-to-clipboard@npm:^4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "@types/react-copy-to-clipboard@npm:4.3.0"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 2/09816ba8a92fefbcc62abd4eea27881804c3ad0b70b52dcaf6717087ed95533d2afdbda3c78d5e3c5c41ebce13b19ca99aa710f3db258db393c2eada59d19c83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-dom@npm:^16.9.5":
|
||||
version: 16.9.6
|
||||
resolution: "@types/react-dom@npm:16.9.6"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user