11"use client" ;
22
3- import { WalletAddress } from "@/components/blocks/wallet-address" ;
4- import { CopyAddressButton } from "@/components/ui/CopyAddressButton" ;
53import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow" ;
6- import { Spinner } from "@/components/ui/Spinner/Spinner" ;
74import { Alert , AlertTitle } from "@/components/ui/alert" ;
8- import { Button } from "@/components/ui/button" ;
9- import {
10- Dialog ,
11- DialogContent ,
12- DialogDescription ,
13- DialogFooter ,
14- DialogHeader ,
15- DialogTitle ,
16- } from "@/components/ui/dialog" ;
17- import { Skeleton } from "@/components/ui/skeleton" ;
18- import { ToolTipLabel } from "@/components/ui/tooltip" ;
19- import { useMutation } from "@tanstack/react-query" ;
20- import { TransactionButton } from "components/buttons/TransactionButton" ;
21- import { CircleSlash , TrashIcon } from "lucide-react" ;
22- import { useState } from "react" ;
23- import { toast } from "sonner" ;
24- import {
25- type ContractOptions ,
26- getContract ,
27- sendTransaction ,
28- waitForReceipt ,
29- } from "thirdweb" ;
30- import { uninstallModuleByProxy } from "thirdweb/modules" ;
5+ import { CircleSlash } from "lucide-react" ;
6+ import type { ContractOptions } from "thirdweb" ;
317import type { Account } from "thirdweb/wallets" ;
32- import { useModuleContractInfo } from "./moduleContractInfo " ;
8+ import { ModuleCard } from "./module-card " ;
339
3410export const InstalledModulesTable = ( props : {
3511 contract : ContractOptions ;
@@ -38,7 +14,7 @@ export const InstalledModulesTable = (props: {
3814 isPending : boolean ;
3915 } ;
4016 refetchModules : ( ) => void ;
41- ownerAccount ? : Account ;
17+ ownerAccount : Account | undefined ;
4218} ) => {
4319 const { installedModules, ownerAccount } = props ;
4420
@@ -66,249 +42,18 @@ export const InstalledModulesTable = (props: {
6642 < >
6743 { sectionTitle }
6844 < ScrollShadow scrollableClassName = "rounded-lg" >
69- < table className = "w-full selection:bg-inverted selection:text-inverted-foreground" >
70- < thead >
71- < TableHeadingRow >
72- < TableHeading > Module Name </ TableHeading >
73- < TableHeading > Description </ TableHeading >
74- < TableHeading > Publisher Address </ TableHeading >
75- < TableHeading > Module Address </ TableHeading >
76- < TableHeading > Version </ TableHeading >
77- { ownerAccount && < TableHeading > Remove </ TableHeading > }
78- </ TableHeadingRow >
79- </ thead >
80-
81- < tbody >
82- { installedModules . isPending ? (
83- < >
84- < SkeletonRow ownerAccount = { ownerAccount } />
85- < SkeletonRow ownerAccount = { ownerAccount } />
86- < SkeletonRow ownerAccount = { ownerAccount } />
87- </ >
88- ) : (
89- < >
90- { installedModules . data ?. map ( ( e , i ) => (
91- < ModuleRow
92- // biome-ignore lint/suspicious/noArrayIndexKey: FIXME
93- key = { i }
94- moduleAddress = { e }
95- contract = { props . contract }
96- onRemoveModule = { props . refetchModules }
97- ownerAccount = { ownerAccount }
98- />
99- ) ) }
100- </ >
101- ) }
102- </ tbody >
103- </ table >
45+ < div className = "flex flex-col gap-6" >
46+ { installedModules . data ?. map ( ( moduleAddress ) => (
47+ < ModuleCard
48+ key = { moduleAddress }
49+ moduleAddress = { moduleAddress }
50+ contract = { props . contract }
51+ onRemoveModule = { props . refetchModules }
52+ ownerAccount = { ownerAccount }
53+ />
54+ ) ) }
55+ </ div >
10456 </ ScrollShadow >
10557 </ >
10658 ) ;
10759} ;
108-
109- function SkeletonRow ( props : { ownerAccount ?: Account } ) {
110- return (
111- < TableRow >
112- < TableData >
113- < Skeleton className = "h-6" />
114- </ TableData >
115- < TableData >
116- < Skeleton className = "h-6" />
117- </ TableData >
118- < TableData >
119- < Skeleton className = "h-6" />
120- </ TableData >
121- < TableData >
122- < Skeleton className = "h-6" />
123- </ TableData >
124-
125- { /* Version */ }
126- < TableData >
127- < Skeleton className = "h-6" />
128- </ TableData >
129-
130- { /* Remove */ }
131- { props . ownerAccount && (
132- < TableData >
133- < Skeleton className = "h-6" />
134- </ TableData >
135- ) }
136- </ TableRow >
137- ) ;
138- }
139-
140- function ModuleRow ( props : {
141- moduleAddress : string ;
142- contract : ContractOptions ;
143- onRemoveModule : ( ) => void ;
144- ownerAccount ?: Account ;
145- } ) {
146- const { contract, moduleAddress, ownerAccount } = props ;
147- const [ isUninstallModalOpen , setIsUninstallModalOpen ] = useState ( false ) ;
148-
149- const contractInfo = useModuleContractInfo (
150- getContract ( {
151- address : moduleAddress ,
152- chain : contract . chain ,
153- client : contract . client ,
154- } ) ,
155- ) ;
156-
157- const uninstallMutation = useMutation ( {
158- mutationFn : async ( account : Account ) => {
159- const uninstallTransaction = uninstallModuleByProxy ( {
160- contract,
161- chain : contract . chain ,
162- client : contract . client ,
163- moduleProxyAddress : moduleAddress ,
164- moduleData : "0x" ,
165- } ) ;
166-
167- const txResult = await sendTransaction ( {
168- transaction : uninstallTransaction ,
169- account,
170- } ) ;
171-
172- await waitForReceipt ( txResult ) ;
173- } ,
174- onSuccess ( ) {
175- toast . success ( "Module Removed successfully" ) ;
176- props . onRemoveModule ( ) ;
177- } ,
178- onError ( error ) {
179- toast . error ( "Failed to remove module" ) ;
180- console . error ( "Error during uninstallation:" , error ) ;
181- } ,
182- } ) ;
183-
184- const handleRemove = async ( ) => {
185- if ( ! ownerAccount ) {
186- return ;
187- }
188-
189- setIsUninstallModalOpen ( false ) ;
190- uninstallMutation . mutate ( ownerAccount ) ;
191- } ;
192-
193- if ( ! contractInfo ) {
194- return < SkeletonRow ownerAccount = { ownerAccount } /> ;
195- }
196-
197- return (
198- < TableRow >
199- < TableData >
200- < p > { contractInfo . name } </ p >
201- </ TableData >
202- < TableData >
203- < p > { contractInfo . description || "..." } </ p >
204- </ TableData >
205- < TableData >
206- < WalletAddress address = { contractInfo . publisher || "" } />
207- </ TableData >
208- < TableData >
209- < CopyAddressButton
210- className = "text-xs"
211- address = { moduleAddress || "" }
212- copyIconPosition = "left"
213- variant = "outline"
214- />
215- </ TableData >
216-
217- { /* Version */ }
218- < TableData >
219- < p > { contractInfo . version } </ p >
220- </ TableData >
221-
222- { /* Remove */ }
223- { ownerAccount && (
224- < TableData >
225- < div >
226- < ToolTipLabel label = "Remove Module" >
227- < Button
228- onClick = { ( ) => setIsUninstallModalOpen ( true ) }
229- variant = "outline"
230- className = "rounded-xl p-3 text-red-500"
231- >
232- { uninstallMutation . isPending ? (
233- < Spinner className = "size-4" />
234- ) : (
235- < TrashIcon className = "size-4" />
236- ) }
237- </ Button >
238- </ ToolTipLabel >
239- </ div >
240- </ TableData >
241- ) }
242-
243- < Dialog
244- open = { isUninstallModalOpen }
245- onOpenChange = { setIsUninstallModalOpen }
246- >
247- < DialogContent className = "z-[10001]" dialogOverlayClassName = "z-[10000]" >
248- < form
249- onSubmit = { ( e ) => {
250- e . preventDefault ( ) ;
251- handleRemove ( ) ;
252- } }
253- >
254- < DialogHeader >
255- < DialogTitle > Uninstall Module</ DialogTitle >
256- < DialogDescription >
257- Are you sure you want to uninstall{ " " }
258- < span className = "font-medium text-foreground " >
259- { contractInfo . name }
260- </ span > { " " }
261- ?
262- </ DialogDescription >
263- </ DialogHeader >
264-
265- < DialogFooter className = "mt-10 flex-row justify-end gap-3 md:gap-1" >
266- < Button
267- type = "button"
268- onClick = { ( ) => setIsUninstallModalOpen ( false ) }
269- variant = "outline"
270- >
271- Cancel
272- </ Button >
273-
274- < TransactionButton
275- txChainID = { contract . chain . id }
276- transactionCount = { 1 }
277- isLoading = { uninstallMutation . isPending }
278- type = "submit"
279- colorScheme = "red"
280- className = "flex"
281- >
282- Uninstall
283- </ TransactionButton >
284- </ DialogFooter >
285- </ form >
286- </ DialogContent >
287- </ Dialog >
288- </ TableRow >
289- ) ;
290- }
291-
292- function TableRow ( props : { children : React . ReactNode } ) {
293- return (
294- < tr className = "border-border border-b [&:last-child]:border-b-0" >
295- { props . children }
296- </ tr >
297- ) ;
298- }
299-
300- function TableData ( { children } : { children : React . ReactNode } ) {
301- return < td className = "px-3 py-4 text-sm" > { children } </ td > ;
302- }
303-
304- function TableHeading ( props : { children : React . ReactNode } ) {
305- return (
306- < th className = "min-w-[150px] border-border border-b px-3 py-3 text-left font-medium text-muted-foreground text-sm" >
307- { props . children }
308- </ th >
309- ) ;
310- }
311-
312- function TableHeadingRow ( { children } : { children : React . ReactNode } ) {
313- return < tr className = "relative bg-muted/50" > { children } </ tr > ;
314- }
0 commit comments