Skip to content

Commit bfdddb9

Browse files
committed
feat: HardWallet
1 parent fa5a711 commit bfdddb9

File tree

4 files changed

+191
-61
lines changed

4 files changed

+191
-61
lines changed

packages/neuron-ui/src/components/RecycleUDTCellDialog/index.tsx

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@ import { useState as useGlobalState } from 'states'
44
import { sudtValueToAmount, shannonToCKBFormatter } from 'utils/formatters'
55
import Dialog from 'widgets/Dialog'
66
import AlertDialog from 'widgets/AlertDialog'
7+
import Button from 'widgets/Button'
78
import TextField from 'widgets/TextField'
89
import { isErrorWithI18n } from 'exceptions'
9-
import { getUDTTokenInfoAndBalance, generateRecycleUDTCellTx, openExternal, sendTx } from 'services/remote'
10+
import Hardware from 'widgets/Icons/Hardware.png'
11+
import Alert from 'widgets/Alert'
12+
import { useHardWallet, usePassword } from 'components/CellManagement/hooks'
13+
import {
14+
getUDTTokenInfoAndBalance,
15+
generateRecycleUDTCellTx,
16+
openExternal,
17+
signAndBroadcastTransaction,
18+
OfflineSignStatus,
19+
OfflineSignType,
20+
} from 'services/remote'
1021
import {
1122
UDTType,
1223
addressToScript,
@@ -24,7 +35,7 @@ export interface DataProps {
2435
udtType: UDTType
2536
}
2637

27-
type DialogType = 'ready' | 'inProgress' | 'success'
38+
type DialogType = 'ready' | 'inProgress' | 'verify' | 'success'
2839

2940
const RecycleUDTCellDialog = ({
3041
data,
@@ -36,19 +47,30 @@ const RecycleUDTCellDialog = ({
3647
onConfirm?: () => void
3748
}) => {
3849
const {
39-
wallet: { id: walletID = '', addresses },
50+
wallet,
4051
settings: { networks },
4152
chain: { networkID },
4253
} = useGlobalState()
4354
const [t] = useTranslation()
44-
const [password, setPassword] = useState('')
45-
const [passwordError, setPasswordError] = useState('')
4655
const [receiver, setReceiver] = useState('')
4756
const [isCurrentWallet, setIsCurrentWallet] = useState(false)
4857
const [isLoading, setIsLoading] = useState(false)
4958
const [dialogType, setDialogType] = useState<DialogType>('ready')
5059
const [txHash, setTxHash] = useState('')
5160
const [info, setInfo] = useState<Controller.GetUDTTokenInfoAndBalance.Response | null>(null)
61+
const { id: walletID = '', addresses, device } = wallet
62+
const { password, error, onPasswordChange, setError } = usePassword()
63+
const {
64+
isReconnecting,
65+
isNotAvailable,
66+
reconnect,
67+
verifyDeviceStatus,
68+
errorMessage: hardwalletError,
69+
setError: setHardwalletError,
70+
} = useHardWallet({
71+
wallet,
72+
t,
73+
})
5274

5375
const { address: holder, tokenID, udtType } = data
5476

@@ -80,15 +102,6 @@ const RecycleUDTCellDialog = ({
80102
})
81103
}, [])
82104

83-
const onPasswordChange = useCallback(
84-
(e: React.SyntheticEvent<HTMLInputElement>) => {
85-
const { value } = e.target as HTMLInputElement
86-
setPassword(value)
87-
setPasswordError('')
88-
},
89-
[setPassword, setPasswordError]
90-
)
91-
92105
const onAddressChange = useCallback(
93106
(e: React.SyntheticEvent<HTMLInputElement>) => {
94107
const { value } = e.target as HTMLInputElement
@@ -119,45 +132,53 @@ const RecycleUDTCellDialog = ({
119132
openExternal(`${explorerUrl}/transaction/${txHash}`)
120133
}, [isMainnet, txHash])
121134

135+
const handleVerify = useCallback(async () => {
136+
await verifyDeviceStatus()
137+
setDialogType('verify')
138+
}, [setDialogType])
139+
122140
const onSubmit = useCallback(
123-
(e?: React.FormEvent) => {
141+
async (e?: React.FormEvent) => {
124142
if (e) {
125143
e.preventDefault()
126144
}
127-
if (!password) {
128-
return
129-
}
130145
setIsLoading(true)
131-
generateRecycleUDTCellTx({
146+
147+
const errFunc = wallet.device ? setHardwalletError : setError
148+
149+
const txRes = await generateRecycleUDTCellTx({
132150
walletId: walletID,
133151
tokenID,
134152
holder,
135153
receiver: addressToScript(receiver, { isMainnet }).args,
136154
udtType,
137-
}).then(txRes => {
138-
if (!isSuccessResponse(txRes)) {
139-
setPasswordError(errorFormatter(txRes.message, t))
140-
setIsLoading(false)
141-
return
142-
}
143-
sendTx({
144-
walletID,
145-
tx: txRes.result,
146-
password,
147-
}).then(res => {
148-
if (!isSuccessResponse(res)) {
149-
setPasswordError(errorFormatter(res.message, t))
150-
setIsLoading(false)
151-
return
152-
}
153-
setIsLoading(false)
154-
onConfirm?.()
155-
setTxHash(res.result)
156-
setDialogType('success')
157-
})
158155
})
156+
if (!isSuccessResponse(txRes)) {
157+
errFunc(errorFormatter(txRes.message, t))
158+
setIsLoading(false)
159+
return
160+
}
161+
const tx = txRes.result
162+
163+
const res = await signAndBroadcastTransaction({
164+
transaction: tx,
165+
status: OfflineSignStatus.Unsigned,
166+
type: OfflineSignType.Regular,
167+
walletID,
168+
password,
169+
})
170+
if (!isSuccessResponse(res)) {
171+
errFunc(errorFormatter(res.message, t))
172+
setIsLoading(false)
173+
return
174+
}
175+
176+
setIsLoading(false)
177+
setTxHash(res.result)
178+
onConfirm?.()
179+
setDialogType('success')
159180
},
160-
[walletID, password, setPasswordError, setTxHash, holder, t]
181+
[walletID, password, setError, setTxHash, holder, t, receiver]
161182
)
162183

163184
if (dialogType === 'ready' || !info) {
@@ -189,15 +210,44 @@ const RecycleUDTCellDialog = ({
189210
)
190211
}
191212

213+
if (dialogType === 'verify') {
214+
return (
215+
<Dialog
216+
show
217+
title={t('s-udt.recycle-dialog.title')}
218+
onCancel={onClose}
219+
showFooter={false}
220+
className={styles.verifyDialog}
221+
>
222+
<div>
223+
<img src={Hardware} alt="hard-wallet" className={styles.hardWalletImg} />
224+
</div>
225+
<div className={styles.lockActions}>
226+
<Button onClick={onClose} type="cancel">
227+
{t('common.cancel')}
228+
</Button>
229+
<Button onClick={isNotAvailable ? reconnect : onSubmit} loading={isLoading || isReconnecting} type="primary">
230+
{isNotAvailable || isReconnecting ? t('s-udt.recycle-dialog.connect-wallet') : t('cell-manage.verify')}
231+
</Button>
232+
</div>
233+
{hardwalletError ? (
234+
<Alert status="error" className={styles.hardwalletErr}>
235+
{hardwalletError}
236+
</Alert>
237+
) : null}
238+
</Dialog>
239+
)
240+
}
241+
192242
return (
193243
<Dialog
194244
show
195245
title={t('s-udt.recycle-dialog.title')}
196246
onCancel={onClose}
197-
onConfirm={onSubmit}
247+
onConfirm={device ? handleVerify : onSubmit}
198248
confirmText={t('wizard.next')}
199249
isLoading={isLoading}
200-
disabled={!password || !receiver || !!receiveAddressError || isLoading}
250+
disabled={(!password && !device) || !receiver || !!receiveAddressError}
201251
className={styles.dialog}
202252
>
203253
<div>
@@ -228,18 +278,20 @@ const RecycleUDTCellDialog = ({
228278
/>
229279
</div>
230280

231-
<TextField
232-
className={styles.inputField}
233-
placeholder={t('password-request.placeholder')}
234-
width="100%"
235-
label={t('wizard.password')}
236-
value={password}
237-
field="password"
238-
type="password"
239-
onChange={onPasswordChange}
240-
autoFocus
241-
error={passwordError}
242-
/>
281+
{!device && (
282+
<TextField
283+
className={styles.inputField}
284+
placeholder={t('password-request.placeholder')}
285+
width="100%"
286+
label={t('wizard.password')}
287+
value={password}
288+
field="password"
289+
type="password"
290+
onChange={onPasswordChange}
291+
autoFocus
292+
error={error}
293+
/>
294+
)}
243295
</div>
244296
</Dialog>
245297
)

packages/neuron-ui/src/components/RecycleUDTCellDialog/recycleUDTCellDialog.module.scss

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,45 @@
4949
.notice {
5050
@include dialog-copy-animation;
5151
}
52+
53+
.verifyDialog {
54+
width: 648px;
55+
text-align: center;
56+
57+
.passwordInput {
58+
margin-top: 12px;
59+
}
60+
61+
.canNotUse {
62+
display: inline-flex;
63+
align-items: center;
64+
background: color-mix(in srgb, #fc8800 5%, var(--warn-background-color));
65+
border: 1px solid #fc880033;
66+
border-radius: 4px;
67+
color: var(--warn-text-color);
68+
font-size: 12px;
69+
text-align: center;
70+
padding: 8px 36px;
71+
72+
& > svg {
73+
margin-right: 4px;
74+
}
75+
}
76+
77+
.hardWalletImg {
78+
width: 88px;
79+
height: 88px;
80+
}
81+
82+
.lockActions {
83+
margin: 16px 0;
84+
display: flex;
85+
justify-content: center;
86+
gap: 16px;
87+
}
88+
89+
.hardwalletErr {
90+
justify-content: center;
91+
margin-top: 12px;
92+
}
93+
}

packages/neuron-ui/src/components/SUDTAccountPile/index.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { sudtValueToAmount } from 'utils/formatters'
66
import Tooltip from 'widgets/Tooltip'
77
import { ReactComponent as Send } from 'widgets/Icons/SendStroke.svg'
88
import { ReactComponent as Receive } from 'widgets/Icons/ReceiveStroke.svg'
9-
import { ArrowNext } from 'widgets/Icons/icon'
9+
import { ArrowNext, Recycle } from 'widgets/Icons/icon'
1010
import styles from './sUDTAccountPile.module.scss'
1111

1212
export interface SUDTAccountPileProps {
@@ -73,11 +73,6 @@ const SUDTAccountPile = ({
7373
</div>
7474
</div>
7575

76-
<button type="button" data-id={accountId} data-role="recycle" onClick={onClick} disabled={disabled}>
77-
<Send />
78-
{t('s-udt.account-list.send')}
79-
</button>
80-
8176
<div className={styles.footer}>
8277
{accountName ? (
8378
<div className={styles.actions}>
@@ -97,6 +92,21 @@ const SUDTAccountPile = ({
9792
)}
9893
</div>
9994
</div>
95+
96+
{!isCKB && !disabled && (
97+
<button
98+
type="button"
99+
data-id={accountId}
100+
className={styles.recycleBtn}
101+
data-role="recycle"
102+
onClick={onClick}
103+
disabled={disabled}
104+
>
105+
<Tooltip tip={t('cell-manage.recycle')} showTriangle placement="top" isTriggerNextToChild>
106+
<Recycle />
107+
</Tooltip>
108+
</button>
109+
)}
100110
</div>
101111
)
102112
}

packages/neuron-ui/src/components/SUDTAccountPile/sUDTAccountPile.module.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
padding: 16px 0 16px 16px;
77
border-radius: 16px;
88
column-gap: 16px;
9+
position: relative;
910

1011
.info {
1112
flex: 1;
@@ -71,4 +72,29 @@
7172
}
7273
}
7374
}
75+
76+
.recycleBtn {
77+
position: absolute;
78+
right: 4px;
79+
top: 14px;
80+
border: none;
81+
height: 24px;
82+
width: 36px;
83+
background: var(--table-head-background-color);
84+
border-radius: 8px;
85+
display: flex;
86+
align-items: center;
87+
justify-content: center;
88+
margin-right: 10px;
89+
cursor: pointer;
90+
91+
&:hover {
92+
svg {
93+
g,
94+
path {
95+
fill: var(--primary-color);
96+
}
97+
}
98+
}
99+
}
74100
}

0 commit comments

Comments
 (0)