Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Hot/cold combination of a wallet doesn't work smooth #3000

Merged
merged 15 commits into from
Jan 29, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.main {
tr {
td {
font-size: 14px;
line-height: 26px;
color: var(--main-text-color);
svg {
width: 14px;
height: 14px;
margin-right: 4px;
position: relative;
top: 2px;
}
}
.first {
padding-right: 24px;
color: var(--input-second-color);
}
}
}

.warningDialog {
width: 680px;
.content {
display: flex;
justify-content: center;
margin-top: 24px;
color: var(--main-text-color);
}
}

.textarea {
color: var(--main-text-color);
margin-top: 17px;
width: 648px;
height: 206px;
resize: none;
background: var(--secondary-background-color);
border: 1px solid var(--divide-line-color);
border-radius: 8px;
padding: 16px;
}
153 changes: 153 additions & 0 deletions packages/neuron-ui/src/components/BroadcastTransaction/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { isSuccessResponse, RoutePath, isMainnet as isMainnetUtil, useDidMount, useGoBack, getExplorerUrl } from 'utils'
import Dialog from 'widgets/Dialog'
import AlertDialog from 'widgets/AlertDialog'
import { useDispatch, useState as useGlobalState } from 'states'
import {
broadcastTransactionOnly,
getCurrentWallet,
OfflineSignStatus,
openExternal,
getLiveCells,
} from 'services/remote'
import { ReactComponent as HardWalletIcon } from 'widgets/Icons/HardWallet.svg'

import styles from './broadcastTransaction.module.scss'

const BroadcastTransaction = () => {
const {
app: { loadedTransaction = {} },
chain: { networkID },
settings: { networks },
} = useGlobalState()

const [wallet, setWallet] = useState<State.Wallet | null>(null)
devchenyan marked this conversation as resolved.
Show resolved Hide resolved
const [isBroadCasting, setIsBroadcasting] = useState(false)
Keith-CY marked this conversation as resolved.
Show resolved Hide resolved
const [broadCastedTxHash, setBroadCastedTxHash] = useState('')
Keith-CY marked this conversation as resolved.
Show resolved Hide resolved
const [t] = useTranslation()
const dispatch = useDispatch()
const [errMsg, setErrMsg] = useState('')
const isMainnet = isMainnetUtil(networks, networkID)

const { filePath, json } = loadedTransaction

const jsonContent = useMemo(() => {
return JSON.stringify(json, null, 2)
}, [json])

const signStatus: OfflineSignStatus = json.status

const isSigned = useMemo(() => signStatus === OfflineSignStatus.Signed, [signStatus])

const onBack = useGoBack()

const navigate = useNavigate()
const onBroadcast = useCallback(async () => {
if (broadCastedTxHash) {
openExternal(`${getExplorerUrl(isMainnet)}/transaction/${broadCastedTxHash}`)
return
}

setIsBroadcasting(true)

try {
const res = await broadcastTransactionOnly({
...json,
})
if (isSuccessResponse(res)) {
getLiveCells().then(cellRes => {
Keith-CY marked this conversation as resolved.
Show resolved Hide resolved
if (isSuccessResponse(cellRes) && cellRes.result) {
const cellWithTransaction = cellRes.result.find(item => item.outPoint.txHash === res.result)

if (cellWithTransaction) {
navigate(RoutePath.History)
}
}

if (res.result) {
setBroadCastedTxHash(res.result)
}
})
} else {
setErrMsg(typeof res.message === 'string' ? res.message : res.message.content || '')
}
} finally {
setIsBroadcasting(false)
}
}, [wallet, json, navigate, dispatch, broadCastedTxHash, setBroadCastedTxHash])

useDidMount(() => {
getCurrentWallet().then(res => {
devchenyan marked this conversation as resolved.
Show resolved Hide resolved
if (isSuccessResponse(res)) {
setWallet(res.result)
}
})
})

return (
<>
<Dialog
show={isSigned}
title={t('offline-sign.broadcast-transaction')}
cancelText={t('offline-sign.actions.cancel')}
onCancel={onBack}
confirmText={
broadCastedTxHash ? t('offline-sign.actions.view-in-explorer') : t('offline-sign.actions.broadcast')
}
isLoading={isBroadCasting}
onConfirm={onBroadcast}
>
<div className={styles.main}>
<table>
<tbody>
<tr>
<td className={styles.first}>{t('offline-sign.json-file')}</td>
<td>{filePath}</td>
</tr>
<tr>
<td className={styles.first}>{t('offline-sign.status.label')}</td>
<td>{t('offline-sign.status.signed')}</td>
</tr>
<tr>
<td className={styles.first}>{t('offline-sign.wallet')}</td>
<td>
{wallet?.device ? <HardWalletIcon /> : null}
<span>{wallet?.name ?? ''}</span>
</td>
</tr>
<tr>
<td className={styles.first}>{t('offline-sign.content')}</td>
</tr>
</tbody>
</table>
<textarea disabled value={jsonContent} className={styles.textarea} />
</div>
</Dialog>

<Dialog
show={!isSigned}
className={styles.warningDialog}
title={t('offline-sign.import-unsigned-transaction')}
cancelText={t('offline-sign.actions.cancel')}
onCancel={onBack}
onConfirm={onBack}
>
<div className={styles.content}>{t('offline-sign.import-unsigned-transaction-detail')}</div>
</Dialog>

<AlertDialog
show={!!errMsg}
title={t('message-types.alert')}
message={errMsg}
type="failed"
onCancel={() => setErrMsg('')}
/>
</>
)
}

BroadcastTransaction.displayName = 'BroadcastTransaction'

export default BroadcastTransaction
60 changes: 28 additions & 32 deletions packages/neuron-ui/src/components/OfflineSign/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { isSuccessResponse, RoutePath, useDidMount, useGoBack } from 'utils'
import { isSuccessResponse, useDidMount, useGoBack } from 'utils'
import Dialog from 'widgets/Dialog'
import AlertDialog from 'widgets/AlertDialog'
import { useDispatch, useState as useGlobalState } from 'states'
import { broadcastTransaction, getCurrentWallet, OfflineSignStatus } from 'services/remote'
import { useState as useGlobalState } from 'states'
import { getCurrentWallet, OfflineSignStatus } from 'services/remote'
import { ReactComponent as HardWalletIcon } from 'widgets/Icons/HardWallet.svg'
import OfflineSignDialog from '../OfflineSignDialog'

Expand All @@ -18,9 +17,7 @@ const OfflineSign = () => {

const [wallet, setWallet] = useState<State.Wallet | null>(null)
const [isSigning, setIsSigning] = useState(false)
const [isBroadCasting, setIsBroadcasting] = useState(false)
const [t] = useTranslation()
const dispatch = useDispatch()
const [errMsg, setErrMsg] = useState('')

const { filePath, json } = loadedTransaction
Expand All @@ -31,6 +28,8 @@ const OfflineSign = () => {

const signStatus: OfflineSignStatus = json.status

const isSigned = useMemo(() => signStatus === OfflineSignStatus.Signed, [signStatus])

const status = useMemo(() => {
switch (signStatus) {
case OfflineSignStatus.Unsigned:
Expand All @@ -48,24 +47,6 @@ const OfflineSign = () => {
setIsSigning(true)
}, [setIsSigning])

const navigate = useNavigate()
const onBroadcast = useCallback(async () => {
setIsBroadcasting(true)
try {
const res = await broadcastTransaction({
...json,
walletID: wallet!.id,
})
if (isSuccessResponse(res)) {
navigate(RoutePath.History)
} else {
setErrMsg(typeof res.message === 'string' ? res.message : res.message.content || '')
}
} finally {
setIsBroadcasting(false)
}
}, [wallet, json, navigate, dispatch])

useDidMount(() => {
getCurrentWallet().then(res => {
if (isSuccessResponse(res)) {
Expand All @@ -76,26 +57,29 @@ const OfflineSign = () => {

const signDialogOnDismiss = useCallback(() => {
setIsSigning(false)
}, [])
}, [setIsSigning])

if (isSigning && wallet) {
return (
<OfflineSignDialog isBroadcast={false} wallet={wallet} offlineSignJSON={json} onDismiss={signDialogOnDismiss} />
<OfflineSignDialog
isBroadcast={false}
wallet={wallet}
offlineSignJSON={json}
onDismiss={signDialogOnDismiss}
onCompleted={onBack}
/>
)
}

return (
<>
<Dialog
show={!isSigning}
show={!isSigned}
title={t('offline-sign.title')}
cancelText={t('offline-sign.actions.cancel')}
onCancel={onBack}
confirmText={
signStatus === OfflineSignStatus.Signed ? t('offline-sign.actions.broadcast') : t('offline-sign.actions.sign')
}
isLoading={signStatus === OfflineSignStatus.Signed && isBroadCasting}
onConfirm={signStatus === OfflineSignStatus.Signed ? onBroadcast : onSign}
confirmText={t('offline-sign.actions.sign')}
onConfirm={onSign}
>
<div className={styles.main}>
<table>
Expand Down Expand Up @@ -123,6 +107,18 @@ const OfflineSign = () => {
<textarea disabled value={jsonContent} className={styles.textarea} />
</div>
</Dialog>

<Dialog
show={isSigned}
className={styles.warningDialog}
title={t('offline-sign.import-signed-transaction')}
cancelText={t('offline-sign.actions.cancel')}
onCancel={onBack}
onConfirm={onBack}
>
<div className={styles.content}>{t('offline-sign.import-signed-transaction-detail')}</div>
</Dialog>

<AlertDialog
show={!!errMsg}
title={t('message-types.alert')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
}
}

.warningDialog {
width: 680px;
.content {
display: flex;
justify-content: center;
margin-top: 24px;
color: var(--main-text-color);
}
}

.textarea {
color: var(--main-text-color);
margin-top: 17px;
Expand Down
7 changes: 4 additions & 3 deletions packages/neuron-ui/src/components/OfflineSignDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ interface SignDialogProps {
wallet: State.Wallet
offlineSignJSON: OfflineSignJSON
onDismiss: () => void
onCompleted: () => void
}

const OfflineSignDialog = ({ isBroadcast, wallet, offlineSignJSON, onDismiss }: SignDialogProps) => {
const OfflineSignDialog = ({ isBroadcast, wallet, offlineSignJSON, onDismiss, onCompleted }: SignDialogProps) => {
const {
app: {
send: { description },
Expand Down Expand Up @@ -65,8 +66,8 @@ const OfflineSignDialog = ({ isBroadcast, wallet, offlineSignJSON, onDismiss }:
type: AppActions.UpdateLoadedTransaction,
payload: res.result!,
})
onDismiss()
}, [offlineSignJSON, dispatch, onDismiss, t, password, walletID])
onCompleted()
}, [offlineSignJSON, dispatch, onCompleted, t, password, walletID])

const onSubmit = useCallback(
async (e?: React.FormEvent) => {
Expand Down
10 changes: 8 additions & 2 deletions packages/neuron-ui/src/locales/en.json
devchenyan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@
"partially-signed": "Partially Signed",
"signed": "Signed"
},
"import-signed-transaction": "Import a signed transaction",
"import-signed-transaction-detail": "You have imported a signed transaction, please confirm and try again.",
"wallet": "Wallet:",
"content": "Content:",
"export": "Export Tx",
Expand All @@ -120,8 +122,12 @@
"actions": {
"broadcast": "Broadcast",
"sign": "Sign and export",
"cancel": "Cancel"
}
"cancel": "Cancel",
"view-in-explorer": "View in Explorer"
},
"broadcast-transaction": "Broadcast Transaction",
"import-unsigned-transaction": "Import an unsigned transaction",
Keith-CY marked this conversation as resolved.
Show resolved Hide resolved
"import-unsigned-transaction-detail": "You have imported an unsigned transaction, please confirm and try again."
},
"overview": {
"date": "Date",
Expand Down
Loading