@@ -7,9 +7,11 @@ import {
77 Card ,
88 Group ,
99 LoadingOverlay ,
10+ Modal ,
1011 Stack ,
1112 Tabs ,
1213 Text ,
14+ TextInput ,
1315 ThemeIcon ,
1416 Tooltip ,
1517} from "@mantine/core" ;
@@ -31,6 +33,7 @@ import {
3133 IconEye ,
3234 IconFileCheck ,
3335 IconFileText ,
36+ IconHexagonPlus ,
3437 IconHome ,
3538 IconReportSearch ,
3639 IconShieldCheck ,
@@ -39,8 +42,14 @@ import {
3942import _ from "lodash" ;
4043import React , { useCallback , useMemo , useState } from "react" ;
4144import { Link } from "react-router-dom" ;
42- import { formatCacheTtl , updateAssetObject } from "../../api/assets" ;
45+ import {
46+ formatCacheTtl ,
47+ updateAssetObject ,
48+ mintAssetNft ,
49+ } from "../../api/assets" ;
4350import { Asset , AssetModel } from "../../api/types" ;
51+ import axios from "axios" ;
52+ import { buildApiUrl } from "../../api/utils" ;
4453import {
4554 buildKeycloakAuthProvider ,
4655 IIdentity ,
@@ -127,6 +136,55 @@ export const AssetObjectShow: React.FC<IResourceComponentsProps> = () => {
127136 const { verificationCount } = useVerificationCount ( assetObjectModel ) ;
128137
129138 const [ isUpdatingName , setIsUpdatingName ] = useState ( false ) ;
139+ const [ isMintModalOpen , setIsMintModalOpen ] = useState ( false ) ;
140+ const [ mintLicense , setMintLicense ] = useState ( "" ) ;
141+ const [ isMinting , setIsMinting ] = useState ( false ) ;
142+
143+ const handleMintNft = useCallback ( async ( ) => {
144+ if ( ! assetObjectModel || ! mintLicense . trim ( ) ) return ;
145+
146+ setIsMinting ( true ) ;
147+
148+ try {
149+ const { task_id } = await mintAssetNft ( {
150+ objectKeyOrId : assetObjectModel . data . id ,
151+ license : mintLicense . trim ( ) ,
152+ } ) ;
153+
154+ // Poll the existing proof task endpoint until the task finishes
155+ const poll = async ( ) : Promise < void > => {
156+ const url = buildApiUrl ( "asset" , "proof" , "task" , String ( task_id ) ) ;
157+ const res = await axios . get ( url ) ;
158+ if ( res . data ?. finished_at ) {
159+ if ( res . data ?. error ) {
160+ throw new Error ( res . data . error ) ;
161+ }
162+ return ;
163+ }
164+ await new Promise ( ( r ) => setTimeout ( r , 3000 ) ) ;
165+ return poll ( ) ;
166+ } ;
167+
168+ await poll ( ) ;
169+
170+ open ?.( {
171+ message : t ( "assetObjects.mintSuccess" , "NFT minted successfully" ) ,
172+ type : "success" ,
173+ } ) ;
174+
175+ setIsMintModalOpen ( false ) ;
176+ setMintLicense ( "" ) ;
177+ await queryResult . refetch ( ) ;
178+ } catch ( error ) {
179+ _ . partial (
180+ catchErrorAndShow ,
181+ open ,
182+ t ( "assetObjects.mintError" , "NFT minting failed" ) ,
183+ ) ( error ) ;
184+ } finally {
185+ setIsMinting ( false ) ;
186+ }
187+ } , [ assetObjectModel , mintLicense , open , t , queryResult ] ) ;
130188
131189 const handleNameUpdate = useCallback (
132190 async ( newName : string ) => {
@@ -300,6 +358,43 @@ export const AssetObjectShow: React.FC<IResourceComponentsProps> = () => {
300358 "We are checking the integrity of this dataset object. This may take a while." ,
301359 ) }
302360 />
361+ < LoadingOverlayWithMessage
362+ visible = { isMinting }
363+ message = { t (
364+ "assetObjects.loadingMint" ,
365+ "Minting NFT on the blockchain. This may take a minute." ,
366+ ) }
367+ />
368+ < Modal
369+ opened = { isMintModalOpen }
370+ onClose = { ( ) => {
371+ if ( ! isMinting ) {
372+ setIsMintModalOpen ( false ) ;
373+ setMintLicense ( "" ) ;
374+ }
375+ } }
376+ title = { t ( "assetObjects.mintNft.title" , "Mint NFT" ) }
377+ >
378+ < TextInput
379+ label = { t ( "assetObjects.mintNft.license" , "License" ) }
380+ placeholder = "e.g. CC-BY-4.0"
381+ value = { mintLicense }
382+ onChange = { ( e ) => setMintLicense ( e . currentTarget . value ) }
383+ required
384+ mb = "md"
385+ />
386+ < Button
387+ fullWidth
388+ variant = "light"
389+ color = "violet"
390+ leftIcon = { < IconHexagonPlus size = { 18 } /> }
391+ loading = { isMinting }
392+ disabled = { ! mintLicense . trim ( ) }
393+ onClick = { handleMintNft }
394+ >
395+ { t ( "assetObjects.mintNft.submit" , "Mint NFT" ) }
396+ </ Button >
397+ </ Modal >
303398 { ! ! result && (
304399 < IntegrityModal
305400 integrityResult = { result }
@@ -464,6 +559,24 @@ export const AssetObjectShow: React.FC<IResourceComponentsProps> = () => {
464559 ) }
465560 </ Button >
466561 </ Tooltip >
562+
563+ { assetObjectModel . data . proof_id && (
564+ < Tooltip
565+ label = { t (
566+ "assetObjects.actions.mintNft" ,
567+ "Mint NFT for this object" ,
568+ ) }
569+ >
570+ < Button
571+ variant = "light"
572+ color = "violet"
573+ leftIcon = { < IconHexagonPlus size = { 18 } /> }
574+ onClick = { ( ) => setIsMintModalOpen ( true ) }
575+ >
576+ { t ( "assetObjects.actions.mintNft" , "Mint NFT" ) }
577+ </ Button >
578+ </ Tooltip >
579+ ) }
467580 </ Group >
468581 </ Group >
469582 </ Stack >
0 commit comments