diff --git a/packages/adena-extension/src/components/atoms/web-copy-button/index.tsx b/packages/adena-extension/src/components/atoms/web-copy-button/index.tsx index db133cb73..16e91b50a 100644 --- a/packages/adena-extension/src/components/atoms/web-copy-button/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-copy-button/index.tsx @@ -1,17 +1,19 @@ -import React, { CSSProperties, useCallback, useMemo, useState } from 'react'; +import React, { CSSProperties, useCallback, useEffect, useMemo, useState } from 'react'; import styled, { css, FlattenSimpleInterpolation, useTheme } from 'styled-components'; -import { WebText } from '../web-text'; -import { Row, View } from '../base'; import IconCopy from '@assets/web/icon-copy'; +import { Row, View } from '../base'; +import { WebText } from '../web-text'; interface WebCopyButtonProps { width?: CSSProperties['width']; height?: CSSProperties['height']; copyText: string; + clearClipboardTimeout?: number; + onCopy?: () => void; } -const StyledContainer = styled(Row) <{ clicked: boolean }>` +const StyledContainer = styled(Row)<{ clicked: boolean }>` display: flex; padding: 0 14px 0 14px; gap: 4px; @@ -21,7 +23,7 @@ const StyledContainer = styled(Row) <{ clicked: boolean }>` border: 1px solid #212429; background: transparent; cursor: pointer; - user-select:none; + user-select: none; svg { width: 16px; @@ -39,15 +41,23 @@ const StyledContainer = styled(Row) <{ clicked: boolean }>` } } - ${({ clicked }): FlattenSimpleInterpolation | string => clicked ? css` - background: rgba(255, 255, 255, 0.08); - `: ''} + ${({ clicked }): FlattenSimpleInterpolation | string => + clicked + ? css` + background: rgba(255, 255, 255, 0.08); + ` + : ''} `; +const CLEAR_CLIPBOARD_TIMEOUT = 30_000; // 30 seconds +const COPY_TOOLTIP_DISPLAY_TIMEOUT = 2_000; // 2 seconds + export const WebCopyButton: React.FC = ({ width = 'fit-content', height = 32, copyText, + clearClipboardTimeout = CLEAR_CLIPBOARD_TIMEOUT, + onCopy, }) => { const theme = useTheme(); const [clicked, setClicked] = useState(false); @@ -58,7 +68,7 @@ export const WebCopyButton: React.FC = ({ return 'Copied!'; } return 'Copy'; - }, [clicked]) + }, [clicked]); const activated = useMemo(() => { return mouseover || clicked; @@ -69,11 +79,14 @@ export const WebCopyButton: React.FC = ({ return; } setClicked(true); + navigator.clipboard.writeText(copyText); + onCopy && onCopy(); + setTimeout(() => { setClicked(false); - }, 2000); - }, [clicked, copyText]); + }, COPY_TOOLTIP_DISPLAY_TIMEOUT); + }, [clicked, copyText, onCopy]); const onMouseOver = useCallback(() => { setMouseover(true); @@ -83,6 +96,20 @@ export const WebCopyButton: React.FC = ({ setMouseover(false); }, []); + useEffect(() => { + if (!clicked) { + return; + } + + const timeout = setTimeout(() => { + navigator?.clipboard?.writeText(''); + }, clearClipboardTimeout); + + return () => { + clearTimeout(timeout); + }; + }, [clicked, clearClipboardTimeout]); + return ( = ({ onMouseOut={onMouseLeave} > {clicked ? ( - + {buttonStr} ) : ( @@ -107,14 +131,11 @@ export const WebCopyButton: React.FC = ({ - + {buttonStr} )} ); -} \ No newline at end of file +}; diff --git a/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx b/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx index 2851a0946..4f5691934 100644 --- a/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx +++ b/packages/adena-extension/src/pages/web/wallet-create-screen/get-mnemonic-step.tsx @@ -1,13 +1,13 @@ -import { ReactElement, useState } from 'react'; +import { ReactElement, useMemo, useState } from 'react'; import styled, { useTheme } from 'styled-components'; import IconWarning from '@assets/web/warning.svg'; import { Row, View, WebButton, WebCheckBox, WebImg, WebText } from '@components/atoms'; +import { WebCopyButton } from '@components/atoms/web-copy-button'; +import { WebHoldButton } from '@components/atoms/web-hold-button'; import { WebSeedBox } from '@components/molecules'; import { UseWalletCreateReturn } from '@hooks/web/use-wallet-create-screen'; -import { WebHoldButton } from '@components/atoms/web-hold-button'; -import { WebCopyButton } from '@components/atoms/web-copy-button'; const StyledContainer = styled(View)` width: 100%; @@ -36,6 +36,18 @@ const GetMnemonicStep = ({ const [ableToReveal, setAbleToReveal] = useState(false); const [agreeAbleToReveals, setAgreeAbleToReveals] = useState(false); const [checkSavedMnemonic, setCheckSavedMnemonic] = useState(false); + const [copied, setCopied] = useState(false); + + const warningCopiedMessage = useMemo(() => { + if (!copied) { + return ''; + } + return 'You have copied sensitive info. Make sure you do not paste it in public or shared environments, and clear your clipboard as soon as you’ve used it.'; + }, [copied]); + + const onCopy = (): void => { + setCopied(true); + }; return ( @@ -47,6 +59,15 @@ const GetMnemonicStep = ({ This phrase is the only way to recover this wallet. DO NOT share it with anyone. + + {warningCopiedMessage && ( + + + + {warningCopiedMessage} + + + )} @@ -56,7 +77,7 @@ const GetMnemonicStep = ({ <> setShowBlur(!response)} /> - + = ({ exportType, exp const theme = useTheme(); const [blur, setBlur] = useState(true); const [initializedDone, setInitializedDone] = useState(false); + const [copied, setCopied] = useState(false); const title = useMemo(() => { if (exportType === 'PRIVATE_KEY') { @@ -63,6 +64,13 @@ const WalletExportResult: React.FC = ({ exportType, exp return 'Your seed phrase is the only way to recover your wallet. Keep it somewhere safe and secret.'; }, [exportType]); + const warningCopiedMessage = useMemo(() => { + if (!copied) { + return ''; + } + return 'You have copied sensitive info. Make sure you do not paste it in public or shared environments, and clear your clipboard as soon as you’ve used it.'; + }, [copied]); + const seeds = useMemo((): string[] => { if (exportType !== 'SEED_PHRASE' || !exportData) { return []; @@ -97,6 +105,10 @@ const WalletExportResult: React.FC = ({ exportType, exp }); }; + const onCopy = (): void => { + setCopied(true); + }; + return ( @@ -107,6 +119,15 @@ const WalletExportResult: React.FC = ({ exportType, exp {warningMessage} + + {warningCopiedMessage && ( + + + + {warningCopiedMessage} + + + )} @@ -116,7 +137,7 @@ const WalletExportResult: React.FC = ({ exportType, exp )} - +