@@ -14,18 +14,20 @@ import Link from 'next/link'
1414import { useRouter , useSearchParams } from 'next/navigation'
1515import { omit } from 'rambda'
1616import React , { useState , useEffect } from 'react'
17- import { HiHome , HiPencil } from 'react-icons/hi'
17+ import { HiHome , HiOutlineX , HiPencil } from 'react-icons/hi'
1818import { MdOpenInNew } from 'react-icons/md'
1919import { toast } from 'react-toastify'
2020import { CustomPagination } from '@/components/common/CustomPagination'
2121import withAdmin from '@/components/common/HOC/authAdmin'
2222import {
2323 Node ,
2424 NodeStatus ,
25+ useAdminUpdateNode ,
2526 useGetUser ,
2627 useListAllNodes ,
2728 useUpdateNode ,
2829} from '@/src/api/generated'
30+ import { UNCLAIMED_ADMIN_PUBLISHER_ID } from '@/src/constants'
2931import { useNextTranslation } from '@/src/hooks/i18n'
3032
3133function NodeList ( ) {
@@ -38,6 +40,7 @@ function NodeList() {
3840 tags : '' ,
3941 category : '' ,
4042 } )
43+ const [ unclaimingNode , setUnclaimingNode ] = useState < Node | null > ( null )
4144 const queryClient = useQueryClient ( )
4245 const { data : user } = useGetUser ( )
4346
@@ -121,6 +124,7 @@ function NodeList() {
121124 } )
122125
123126 const updateNodeMutation = useUpdateNode ( )
127+ const adminUpdateNodeMutation = useAdminUpdateNode ( )
124128
125129 React . useEffect ( ( ) => {
126130 if ( getAllNodesQuery . isError ) {
@@ -217,6 +221,32 @@ function NodeList() {
217221 }
218222 }
219223
224+ const handleUnclaim = async ( ) => {
225+ if ( ! unclaimingNode || ! unclaimingNode . id ) {
226+ toast . error ( t ( 'Unable to unclaim: missing node information' ) )
227+ return
228+ }
229+
230+ try {
231+ await adminUpdateNodeMutation . mutateAsync ( {
232+ nodeId : unclaimingNode . id ,
233+ data : {
234+ ...unclaimingNode ,
235+ publisher : {
236+ id : UNCLAIMED_ADMIN_PUBLISHER_ID ,
237+ } ,
238+ } ,
239+ } )
240+
241+ toast . success ( t ( 'Node successfully unclaimed' ) )
242+ setUnclaimingNode ( null )
243+ queryClient . invalidateQueries ( { queryKey : [ '/nodes' ] } )
244+ } catch ( error ) {
245+ console . error ( 'Error unclaiming node:' , error )
246+ toast . error ( t ( 'Error unclaiming node' ) )
247+ }
248+ }
249+
220250 if ( getAllNodesQuery . isLoading ) {
221251 return (
222252 < div className = "flex items-center justify-center h-screen" >
@@ -400,18 +430,30 @@ function NodeList() {
400430 </ span >
401431 </ Table . Cell >
402432 < Table . Cell className = "dark" >
403- < Button
404- size = "sm"
405- onClick = { ( ) => openEditModal ( node ) }
406- disabled = { ! node . publisher ?. id }
407- title = {
408- ! node . publisher ?. id
409- ? t ( 'No publisher information available' )
410- : t ( 'Edit node' )
411- }
412- >
413- < HiPencil className = "w-4 h-4" />
414- </ Button >
433+ < div className = "flex gap-2" >
434+ < Button
435+ size = "sm"
436+ onClick = { ( ) => openEditModal ( node ) }
437+ disabled = { ! node . publisher ?. id }
438+ title = {
439+ ! node . publisher ?. id
440+ ? t ( 'No publisher information available' )
441+ : t ( 'Edit node' )
442+ }
443+ >
444+ < HiPencil className = "w-4 h-4" />
445+ </ Button >
446+ { node . publisher ?. id && (
447+ < Button
448+ size = "sm"
449+ color = "warning"
450+ onClick = { ( ) => setUnclaimingNode ( node ) }
451+ title = { t ( 'Unclaim node' ) }
452+ >
453+ < HiOutlineX className = "w-4 h-4" />
454+ </ Button >
455+ ) }
456+ </ div >
415457 </ Table . Cell >
416458 </ Table . Row >
417459 ) ) }
@@ -555,6 +597,58 @@ function NodeList() {
555597 </ Button >
556598 </ Modal . Footer >
557599 </ Modal >
600+
601+ { /* Unclaim Confirmation Modal */ }
602+ < Modal
603+ show = { ! ! unclaimingNode }
604+ onClose = { ( ) => setUnclaimingNode ( null ) }
605+ size = "md"
606+ className = "dark"
607+ >
608+ < Modal . Header className = "dark" >
609+ { t ( 'Confirm Unclaim Node' ) }
610+ </ Modal . Header >
611+ < Modal . Body className = "dark" >
612+ < div className = "space-y-4" >
613+ < p className = "text-gray-300" >
614+ { t (
615+ 'Are you sure you want to unclaim this node? This will remove the publisher association, allowing the original author to claim it under a different publisher.'
616+ ) }
617+ </ p >
618+ < div className = "bg-gray-700 p-4 rounded" >
619+ < div className = "font-semibold text-white" >
620+ { unclaimingNode ?. name }
621+ </ div >
622+ < div className = "text-sm text-gray-400" > @{ unclaimingNode ?. id } </ div >
623+ { unclaimingNode ?. publisher && (
624+ < div className = "text-sm text-gray-400 mt-2" >
625+ { t ( 'Current Publisher' ) } : { unclaimingNode . publisher . name } (
626+ { unclaimingNode . publisher . id } )
627+ </ div >
628+ ) }
629+ </ div >
630+ < div className = "text-yellow-500 text-sm" >
631+ ⚠️ { t ( 'This action cannot be undone automatically.' ) }
632+ </ div >
633+ </ div >
634+ </ Modal . Body >
635+ < Modal . Footer className = "dark" >
636+ < Button
637+ color = "warning"
638+ onClick = { handleUnclaim }
639+ disabled = { adminUpdateNodeMutation . isPending }
640+ >
641+ { adminUpdateNodeMutation . isPending ? (
642+ < Spinner size = "sm" />
643+ ) : (
644+ t ( 'Unclaim Node' )
645+ ) }
646+ </ Button >
647+ < Button color = "gray" onClick = { ( ) => setUnclaimingNode ( null ) } >
648+ { t ( 'Cancel' ) }
649+ </ Button >
650+ </ Modal . Footer >
651+ </ Modal >
558652 </ div >
559653 )
560654}
0 commit comments