Skip to content

Commit 7aab5ca

Browse files
committed
Change Modules table to cards UI (#5051)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces a new `ModuleCard` component and its associated storybook entries, enhancing the UI for displaying module information in a dashboard. It also refactors the `InstalledModulesTable` to utilize the new `ModuleCard` component for better modularity and readability. ### Detailed summary - Added `ModuleCard` component in `module-card.tsx`. - Created storybook entries for `ModuleCard` in `module-card.stories.tsx`. - Refactored `InstalledModulesTable` to render `ModuleCard` instead of a table. - Removed unused skeleton and row components from `InstalledModulesTable`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 83a2412 commit 7aab5ca

File tree

3 files changed

+423
-270
lines changed

3 files changed

+423
-270
lines changed
Lines changed: 15 additions & 270 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,11 @@
11
"use client";
22

3-
import { WalletAddress } from "@/components/blocks/wallet-address";
4-
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
53
import { ScrollShadow } from "@/components/ui/ScrollShadow/ScrollShadow";
6-
import { Spinner } from "@/components/ui/Spinner/Spinner";
74
import { 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";
317
import type { Account } from "thirdweb/wallets";
32-
import { useModuleContractInfo } from "./moduleContractInfo";
8+
import { ModuleCard } from "./module-card";
339

3410
export 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

Comments
 (0)