1
- import React , { useState } from 'react' ;
1
+ import React , { useEffect , useMemo , useState } from 'react' ;
2
2
import { useTranslation } from 'react-i18next' ;
3
- import { Navigate , useParams } from 'react-router-dom' ;
3
+ import { Navigate , useNavigate , useParams } from 'react-router-dom' ;
4
4
import { useAtomValue } from 'jotai' ;
5
- import { ContractAddress , HexString } from '@concordium/web-sdk' ;
5
+ import {
6
+ AccountAddress ,
7
+ AccountTransactionType ,
8
+ ContractAddress ,
9
+ HexString ,
10
+ TransactionHash ,
11
+ } from '@concordium/web-sdk' ;
6
12
7
13
import Page from '@popup/popupX/shared/Page' ;
8
14
import Web3IdCard from '@popup/popupX/shared/Web3IdCard' ;
9
15
import { storedVerifiableCredentialsAtom } from '@popup/store/verifiable-credential' ;
10
- import { absoluteRoutes } from '@popup/popupX/constants/routes' ;
11
- import { networkConfigurationAtom } from '@popup/store/settings' ;
12
- import { createCredentialId } from '@shared/utils/verifiable-credential-helpers' ;
16
+ import { absoluteRoutes , submittedTransactionRoute } from '@popup/popupX/constants/routes' ;
17
+ import { grpcClientAtom , networkConfigurationAtom } from '@popup/store/settings' ;
18
+ import {
19
+ CredentialQueryResponse ,
20
+ buildRevokeTransaction ,
21
+ buildRevokeTransactionParameters ,
22
+ createCredentialId ,
23
+ getCredentialHolderId ,
24
+ getCredentialRegistryContractAddress ,
25
+ getRevokeTransactionExecutionEnergyEstimate ,
26
+ } from '@shared/utils/verifiable-credential-helpers' ;
13
27
import Button from '@popup/popupX/shared/Button' ;
14
28
import Stop from '@assets/svgX/stop.svg' ;
15
29
import Info from '@assets/svgX/info.svg' ;
30
+ import { useCredentialEntry } from '@popup/popupX/shared/utils/verifiable-credentials' ;
31
+ import FullscreenNotice , { FullscreenNoticeProps } from '@popup/popupX/shared/FullscreenNotice' ;
32
+ import { displayNameOrSplitAddress , useHdWallet , useSelectedCredential } from '@popup/shared/utils/account-helpers' ;
33
+ import { fetchContractName } from '@shared/utils/token-helpers' ;
34
+ import Text from '@popup/popupX/shared/Text' ;
35
+ import { ConfirmedCredential , CreationStatus , VerifiableCredential } from '@shared/storage/types' ;
36
+ import { displayAsCcd , noOp , useAsyncMemo } from 'wallet-common-helpers' ;
37
+ import {
38
+ TransactionSubmitError ,
39
+ TransactionSubmitErrorType ,
40
+ useGetTransactionFee ,
41
+ useTransactionSubmit ,
42
+ } from '@popup/shared/utils/transaction-helpers' ;
43
+ import ErrorMessage from '@popup/popupX/shared/Form/ErrorMessage' ;
44
+
45
+ type ConfirmRevocationProps = FullscreenNoticeProps & {
46
+ credential : VerifiableCredential ;
47
+ entry : CredentialQueryResponse | undefined ;
48
+ walletCred : ConfirmedCredential ;
49
+ } ;
50
+
51
+ function ConfirmRevocation ( { credential, entry, walletCred, ...props } : ConfirmRevocationProps ) {
52
+ const { t } = useTranslation ( 'x' , { keyPrefix : 'web3Id.details.confirmRevoke' } ) ;
53
+ const hdWallet = useHdWallet ( ) ;
54
+ const client = useAtomValue ( grpcClientAtom ) ;
55
+ const getFee = useGetTransactionFee ( ) ;
56
+ const nav = useNavigate ( ) ;
57
+ const submitTransaction = useTransactionSubmit (
58
+ AccountAddress . fromBase58 ( walletCred . address ) ,
59
+ AccountTransactionType . Update
60
+ ) ;
61
+ const [ error , setError ] = useState < Error > ( ) ;
62
+
63
+ const payload = useAsyncMemo (
64
+ async ( ) => {
65
+ if ( entry === undefined || hdWallet === undefined || credential === undefined ) {
66
+ return undefined ;
67
+ }
68
+
69
+ const contractAddress = getCredentialRegistryContractAddress ( credential . id ) ;
70
+ const credentialId = getCredentialHolderId ( credential . id ) ;
71
+ const contractName = await fetchContractName ( client , contractAddress . index , contractAddress . subindex ) ;
72
+ if ( contractName === undefined ) {
73
+ throw new Error ( `Unable to find contract name for address: ${ contractAddress } ` ) ;
74
+ }
75
+
76
+ const signingKey = hdWallet
77
+ . getVerifiableCredentialSigningKey ( contractAddress , credential . index )
78
+ . toString ( 'hex' ) ;
79
+
80
+ const parameters = await buildRevokeTransactionParameters (
81
+ contractAddress ,
82
+ credentialId ,
83
+ entry . revocationNonce ,
84
+ signingKey
85
+ ) ;
86
+ const maxExecutionEnergy = await getRevokeTransactionExecutionEnergyEstimate (
87
+ client ,
88
+ contractName ,
89
+ parameters
90
+ ) ;
91
+ return buildRevokeTransaction ( contractAddress , contractName , credentialId , maxExecutionEnergy , parameters ) ;
92
+ } ,
93
+ noOp ,
94
+ [ client , credential , hdWallet , entry ]
95
+ ) ;
96
+ const fee = useMemo (
97
+ ( ) => ( payload === undefined ? undefined : getFee ( AccountTransactionType . Update , payload ) ) ,
98
+ [ payload , getFee ]
99
+ ) ;
100
+
101
+ const submit = async ( ) => {
102
+ if ( fee === undefined || payload === undefined ) {
103
+ throw Error ( 'Fee could not be calculated' ) ;
104
+ }
105
+ try {
106
+ const tx = await submitTransaction ( payload , fee ) ;
107
+ nav ( submittedTransactionRoute ( TransactionHash . fromHexString ( tx ) ) ) ;
108
+ } catch ( e ) {
109
+ if ( e instanceof Error ) {
110
+ setError ( e ) ;
111
+ }
112
+ }
113
+ } ;
114
+
115
+ useEffect ( ( ) => {
116
+ setError ( undefined ) ;
117
+ } , [ props . open ] ) ;
118
+
119
+ return (
120
+ < FullscreenNotice { ...props } >
121
+ < Page >
122
+ < Page . Top heading = { t ( 'title' ) } />
123
+ < Text . Capture > { t ( 'description' ) } </ Text . Capture >
124
+ < Text . Capture className = "m-t-30" >
125
+ { t ( 'fee' ) }
126
+ < br />
127
+ { fee === undefined ? '...' : displayAsCcd ( fee , false , true ) }
128
+ </ Text . Capture >
129
+ < Text . Capture className = "m-t-5" >
130
+ { t ( 'account' ) }
131
+ < br />
132
+ { displayNameOrSplitAddress ( walletCred ) }
133
+ </ Text . Capture >
134
+ { error instanceof TransactionSubmitError &&
135
+ error . type === TransactionSubmitErrorType . InsufficientFunds && (
136
+ < ErrorMessage className = "m-t-10 text-center" > { t ( 'error.insufficientFunds' ) } </ ErrorMessage >
137
+ ) }
138
+ < Page . Footer >
139
+ < Button . Main label = { t ( 'buttonContinue' ) } onClick = { submit } disabled = { payload === undefined } />
140
+ < Button . Main label = { t ( 'buttonCancel' ) } onClick = { props . onClose } />
141
+ </ Page . Footer >
142
+ </ Page >
143
+ </ FullscreenNotice >
144
+ ) ;
145
+ }
16
146
17
147
type Props = {
18
148
contract : ContractAddress . Type ;
@@ -25,24 +155,42 @@ function Web3IdDetailsParsed({ id, contract }: Props) {
25
155
const net = useAtomValue ( networkConfigurationAtom ) ;
26
156
const credential = verifiableCredentials . value . find ( ( c ) => c . id === createCredentialId ( id , contract , net ) ) ;
27
157
const [ showInfo , setShowInfo ] = useState ( false ) ;
158
+ const [ showConfirm , setShowConfirm ] = useState ( false ) ;
159
+ const credentialEntry = useCredentialEntry ( credential ) ;
160
+ const walletCredential = useSelectedCredential ( ) ;
28
161
29
162
if ( verifiableCredentials . loading ) return null ;
30
163
if ( credential === undefined ) throw new Error ( 'Expected to find credential' ) ;
31
164
32
165
return (
33
- < Page >
34
- < Page . Top heading = { t ( 'title' ) } >
35
- < Button . Icon icon = { < Stop /> } />
36
- < Button . Icon
37
- className = "web3-id-details-x__info"
38
- icon = { < Info /> }
39
- onClick = { ( ) => setShowInfo ( ( v ) => ! v ) }
166
+ < >
167
+ { walletCredential ?. status === CreationStatus . Confirmed && (
168
+ < ConfirmRevocation
169
+ open = { showConfirm }
170
+ onClose = { ( ) => setShowConfirm ( false ) }
171
+ credential = { credential }
172
+ entry = { credentialEntry }
173
+ walletCred = { walletCredential }
40
174
/>
41
- </ Page . Top >
42
- < Page . Main >
43
- < Web3IdCard showInfo = { showInfo } credential = { credential } />
44
- </ Page . Main >
45
- </ Page >
175
+ ) }
176
+ < Page >
177
+ < Page . Top heading = { t ( 'title' ) } >
178
+ { walletCredential ?. status === CreationStatus . Confirmed &&
179
+ credentialEntry ?. credentialInfo . holderRevocable &&
180
+ credentialEntry !== undefined && (
181
+ < Button . Icon icon = { < Stop /> } onClick = { ( ) => setShowConfirm ( true ) } />
182
+ ) }
183
+ < Button . Icon
184
+ className = "web3-id-details-x__info"
185
+ icon = { < Info /> }
186
+ onClick = { ( ) => setShowInfo ( ( v ) => ! v ) }
187
+ />
188
+ </ Page . Top >
189
+ < Page . Main >
190
+ < Web3IdCard showInfo = { showInfo } credential = { credential } />
191
+ </ Page . Main >
192
+ </ Page >
193
+ </ >
46
194
) ;
47
195
}
48
196
0 commit comments