Skip to content

Commit

Permalink
feat: Ability to display/export the private key
Browse files Browse the repository at this point in the history
  • Loading branch information
devchenyan committed Dec 28, 2024
1 parent 0d64329 commit f7e99b8
Show file tree
Hide file tree
Showing 22 changed files with 384 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@
}
}

.privateKey {
background: transparent;
border: none;
cursor: pointer;
&:hover {
svg {
g,
path {
stroke: var(--primary-color);
}
}
}
}

@media screen and (max-width: 1330px) {
.container {
.balance {
Expand Down
23 changes: 22 additions & 1 deletion packages/neuron-ui/src/components/AddressBook/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next'
import { useState as useGlobalState, useDispatch } from 'states'
import Dialog from 'widgets/Dialog'
import CopyZone from 'widgets/CopyZone'
import { Copy } from 'widgets/Icons/icon'
import ViewPrivateKey from 'components/ViewPrivateKey'
import { Copy, PrivateKey } from 'widgets/Icons/icon'
import Table, { TableProps, SortType } from 'widgets/Table'
import { shannonToCKBFormatter, useLocalDescription } from 'utils'
import { HIDE_BALANCE } from 'utils/const'
Expand Down Expand Up @@ -44,6 +45,7 @@ const AddressBook = ({ onClose }: { onClose?: () => void }) => {

const dispatch = useDispatch()
const { onChangeEditStatus, onSubmitDescription } = useLocalDescription('address', walletId, dispatch)
const [viewPrivateKeyAddress, setViewPrivateKeyAddress] = useState('')

const columns = useMemo<TableProps<State.Address>['columns']>(
() => [
Expand Down Expand Up @@ -149,6 +151,21 @@ const AddressBook = ({ onClose }: { onClose?: () => void }) => {
return 0
},
},
{
title: '',
dataIndex: 'key',
align: 'left',
width: '40px',
render(_, __, { address }) {
return (
<Tooltip tip={t('addresses.view-private-key')} placement="left">
<button type="button" className={styles.privateKey} onClick={() => setViewPrivateKeyAddress(address)}>
<PrivateKey />
</button>
</Tooltip>
)
},
},
],
[t]
)
Expand Down Expand Up @@ -179,6 +196,10 @@ const AddressBook = ({ onClose }: { onClose?: () => void }) => {
}
/>
</div>

{!!viewPrivateKeyAddress && (
<ViewPrivateKey address={viewPrivateKeyAddress} onClose={() => setViewPrivateKeyAddress('')} />
)}
</div>
</Dialog>
)
Expand Down
36 changes: 23 additions & 13 deletions packages/neuron-ui/src/components/Receive/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Button from 'widgets/Button'
import CopyZone from 'widgets/CopyZone'
import QRCode from 'widgets/QRCode'
import Tooltip from 'widgets/Tooltip'
import { AddressTransform, Download, Copy, Attention, SuccessNoBorder } from 'widgets/Icons/icon'
import ViewPrivateKey from 'components/ViewPrivateKey'
import { AddressTransform, Download, Copy, Attention, SuccessNoBorder, PrivateKey } from 'widgets/Icons/icon'
import VerifyHardwareAddress from './VerifyHardwareAddress'
import styles from './receive.module.scss'
import { useCopyAndDownloadQrCode, useSwitchAddress } from './hooks'
Expand All @@ -29,6 +30,7 @@ export const AddressQrCodeWithCopyZone = ({
)

const [isCopySuccess, setIsCopySuccess] = useState(false)
const [showViewPrivateKey, setShowViewPrivateKey] = useState(false)
const timer = useRef<ReturnType<typeof setTimeout>>()
const { ref, onCopyQrCode, onDownloadQrCode, showCopySuccess } = useCopyAndDownloadQrCode()

Expand Down Expand Up @@ -70,19 +72,27 @@ export const AddressQrCodeWithCopyZone = ({
<CopyZone content={showAddress} className={styles.showAddress}>
{showAddress}
</CopyZone>
<button
type="button"
className={styles.addressToggle}
onClick={onClick}
title={transformLabel}
onFocus={stopPropagation}
onMouseOver={stopPropagation}
onMouseUp={stopPropagation}
>
<AddressTransform />
{transformLabel}
</button>
<div className={styles.actionWrap}>
<button
type="button"
className={styles.addressToggle}
onClick={onClick}
title={transformLabel}
onFocus={stopPropagation}
onMouseOver={stopPropagation}
onMouseUp={stopPropagation}
>
<AddressTransform />
{transformLabel}
</button>
<button type="button" className={styles.privateKey} onClick={() => setShowViewPrivateKey(true)}>
<PrivateKey />
{t('addresses.view-private-key')}
</button>
</div>
</div>

{showViewPrivateKey && <ViewPrivateKey address={showAddress} onClose={() => setShowViewPrivateKey(false)} />}
</div>
)
}
Expand Down
49 changes: 33 additions & 16 deletions packages/neuron-ui/src/components/Receive/receive.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,43 @@
color: var(--main-text-color);
}

.addressToggle {
width: 100%;
.actionWrap {
margin-top: 8px;
appearance: none;
border: none;
background: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
color: var(--primary-color);
line-height: normal;
cursor: pointer;
gap: 32px;

svg {
pointer-events: none;
margin-right: 5px;
button {
appearance: none;
border: none;
background: none;
font-size: 12px;
font-family: PingFang SC;
font-style: normal;
font-weight: 500;
color: var(--primary-color);
line-height: normal;
cursor: pointer;
display: flex;
align-items: center;
}

.addressToggle {
svg {
pointer-events: none;
margin-right: 5px;
}
}

.privateKey {
svg {
width: 16px;
margin-right: 3px;
g,
path {
stroke: var(--primary-color);
}
}
}
}

Expand Down
145 changes: 145 additions & 0 deletions packages/neuron-ui/src/components/ViewPrivateKey/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useState as useGlobalState } from 'states'
import Dialog from 'widgets/Dialog'
import TextField from 'widgets/TextField'
import Alert from 'widgets/Alert'
import { errorFormatter, useCopy, isSuccessResponse } from 'utils'
import { Attention, Copy } from 'widgets/Icons/icon'
import { getPrivateKeyByAddress } from 'services/remote'
import styles from './viewPrivateKey.module.scss'

const ViewPrivateKey = ({ onClose, address }: { onClose?: () => void; address?: string }) => {
const [t] = useTranslation()
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [privateKey, setPrivateKey] = useState('')
const [isLoading, setIsLoading] = useState(false)
const { copied, onCopy, copyTimes } = useCopy()
const {
wallet: { id: walletID = '' },
} = useGlobalState()

useEffect(() => {
setPassword('')
setError('')
}, [setError, setPassword])

const onChange = useCallback(
(e: React.SyntheticEvent<HTMLInputElement>) => {
const { value } = e.target as HTMLInputElement
setPassword(value)
setError('')
},
[setPassword, setError]
)

const onSubmit = useCallback(
async (e?: React.FormEvent) => {
if (e) {
e.preventDefault()
}
if (!password) {
return
}
setIsLoading(true)
try {
const res = await getPrivateKeyByAddress({
walletID,
address,
password,
})

setIsLoading(false)

if (!isSuccessResponse(res)) {
setError(errorFormatter(res.message, t))
return
}
setPrivateKey(res.result)
} catch (err) {
setIsLoading(false)
}
},
[walletID, password, setError, t]
)

if (privateKey) {
return (
<Dialog
show
title={t('addresses.view-private-key')}
onConfirm={onClose}
onCancel={onClose}
showCancel={false}
confirmText={t('common.close')}
className={styles.dialog}
>
<div>
<div className={styles.tip}>
<Attention />
{t('addresses.view-private-key-tip')}
</div>

<TextField
className={styles.passwordInput}
placeholder={t('password-request.placeholder')}
width="100%"
label={<span className={styles.label}>{t('addresses.private-key')}</span>}
value={privateKey}
field="password"
type="password"
disabled
suffix={
<div className={styles.copy}>
<Copy onClick={() => onCopy(privateKey)} />
</div>
}
/>

{copied ? (
<Alert status="success" className={styles.notice} key={copyTimes.toString()}>
{t('common.copied')}
</Alert>
) : null}
</div>
</Dialog>
)
}
return (
<Dialog
show
title={t('addresses.view-private-key')}
onCancel={onClose}
onConfirm={onSubmit}
confirmText={t('wizard.next')}
isLoading={isLoading}
disabled={!password || isLoading}
className={styles.dialog}
>
<div>
<div className={styles.tip}>
<Attention />
{t('addresses.view-private-key-tip')}
</div>

<TextField
className={styles.passwordInput}
placeholder={t('password-request.placeholder')}
width="100%"
label={t('wizard.password')}
value={password}
field="password"
type="password"
onChange={onChange}
autoFocus
error={error}
/>
</div>
</Dialog>
)
}

ViewPrivateKey.displayName = 'ViewPrivateKey'

export default ViewPrivateKey
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@import '../../styles/mixin.scss';

.passwordInput {
margin-top: 16px;
}

.dialog {
width: 700px;
}

.tip {
color: var(--warn-text-color);
background: var(--warn-background-color);
margin: -20px -16px 0;
display: flex;
align-items: center;
justify-content: center;
height: 32px;
font-size: 12px;
gap: 4px;
font-weight: 500;
border-bottom: 1px solid var(--warn-border-color);
}

.label {
font-weight: 500;
color: var(--main-text-color);
font-size: 14px;
}

.copy {
display: flex;
align-items: center;
margin-left: 6px;
}

.notice {
@include dialog-copy-animation;
}
Loading

1 comment on commit f7e99b8

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging for test is done in 12526642673

Please sign in to comment.