11"use client" ;
22
3- import { Badge } from "@/components/ui/badge" ;
3+ import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage" ;
4+ import { Spinner } from "@/components/ui/Spinner/Spinner" ;
45import { Button } from "@/components/ui/button" ;
5- import { Card } from "@/components/ui/card" ;
6+ import {
7+ Dialog ,
8+ DialogContent ,
9+ DialogHeader ,
10+ DialogTitle ,
11+ DialogTrigger ,
12+ } from "@/components/ui/dialog" ;
613import { useDashboardRouter } from "@/lib/DashboardRouter" ;
714import { useResolveContractAbi } from "@3rdweb-sdk/react/hooks/useResolveContractAbi" ;
8- import {
9- Divider ,
10- Flex ,
11- Modal ,
12- ModalBody ,
13- ModalCloseButton ,
14- ModalContent ,
15- ModalHeader ,
16- ModalOverlay ,
17- Spinner ,
18- useDisclosure ,
19- } from "@chakra-ui/react" ;
2015import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
2116import { SourcesPanel } from "components/contract-components/shared/sources-panel" ;
2217import { useContractSources } from "contract-ui/hooks/useContractSources" ;
23- import { CircleCheckIcon , CircleXIcon } from "lucide-react" ;
24- import { useMemo , useState } from "react" ;
18+ import {
19+ CircleCheckIcon ,
20+ CircleXIcon ,
21+ RefreshCcwIcon ,
22+ ShieldCheckIcon ,
23+ } from "lucide-react" ;
24+ import { useMemo } from "react" ;
2525import { toast } from "sonner" ;
2626import type { ThirdwebContract } from "thirdweb" ;
27- import { Heading } from "tw-components" ;
2827
29- interface ContractSourcesPageProps {
30- contract : ThirdwebContract ;
31- }
32-
33- interface VerificationResult {
28+ type VerificationResult = {
3429 explorerUrl : string ;
3530 success : boolean ;
3631 alreadyVerified : boolean ;
3732 error ?: string ;
38- }
33+ } ;
3934
4035export async function verifyContract ( contract : ThirdwebContract ) {
4136 try {
@@ -63,111 +58,80 @@ export async function verifyContract(contract: ThirdwebContract) {
6358 }
6459}
6560
66- interface ConnectorModalProps {
67- isOpen : boolean ;
68- onClose : ( ) => void ;
61+ function VerifyContractModalContent ( {
62+ contract ,
63+ } : {
6964 contract : ThirdwebContract ;
70- }
71-
72- const VerifyContractModal : React . FC <
73- ConnectorModalProps & { resetSignal : number }
74- > = ( { isOpen, onClose, contract, resetSignal } ) => {
65+ } ) {
7566 const verifyQuery = useQuery ( {
76- queryKey : [
77- "verify-contract" ,
78- contract . chain . id ,
79- contract . address ,
80- resetSignal ,
81- ] ,
67+ queryKey : [ "verify-contract" , contract . chain . id , contract . address ] ,
8268 queryFn : ( ) => verifyContract ( contract ) ,
83- enabled : isOpen ,
8469 } ) ;
8570
8671 return (
87- < Modal isOpen = { isOpen } onClose = { onClose } isCentered >
88- < ModalOverlay />
89- < ModalContent
90- className = "!bg-background rounded-lg border border-border"
91- pb = { 2 }
92- mx = { { base : 4 , md : 0 } }
93- >
94- < ModalHeader >
95- < Flex gap = { 2 } align = "center" >
96- < Heading size = "subtitle.md" > Contract Verification</ Heading >
97- < Badge > beta</ Badge >
98- </ Flex >
99- </ ModalHeader >
100- < ModalCloseButton mt = { 2 } />
101- < Divider mb = { 4 } />
102- < ModalBody py = { 4 } >
103- < Flex flexDir = "column" >
104- { verifyQuery . isPending && (
105- < Flex gap = { 2 } align = "center" >
106- < Spinner color = "purple.500" size = "sm" />
107- < Heading size = "label.md" > Verifying...</ Heading >
108- </ Flex >
109- ) }
110- { verifyQuery ?. error ? (
111- < Flex gap = { 2 } align = "center" >
112- < CircleXIcon className = "size-4 text-red-600" />
113- < Heading size = "label.md" >
114- { verifyQuery ?. error . toString ( ) }
115- </ Heading >
116- </ Flex >
117- ) : null }
72+ < div className = "flex flex-col p-6" >
73+ { verifyQuery . isPending && (
74+ < div className = "flex min-h-24 items-center justify-center" >
75+ < div className = "flex items-center gap-2" >
76+ < Spinner className = "size-4" />
77+ < p className = "font-medium text-sm" > Verifying</ p >
78+ </ div >
79+ </ div >
80+ ) }
11881
119- { verifyQuery . data ?. results
120- ? verifyQuery . data ?. results . map (
121- ( result : VerificationResult , index : number ) => (
122- // biome-ignore lint/suspicious/noArrayIndexKey: FIXME
123- < Flex key = { index } gap = { 2 } align = "center" mb = { 4 } >
124- { result . success && (
125- < >
126- < CircleCheckIcon className = "size-4 text-green-600" />
127- { result . alreadyVerified && (
128- < Heading size = "label.md" >
129- { result . explorerUrl } : Already verified
130- </ Heading >
131- ) }
132- { ! result . alreadyVerified && (
133- < Heading size = "label.md" >
134- { result . explorerUrl } : Verification successful
135- </ Heading >
136- ) }
137- </ >
138- ) }
139- { ! result . success && (
140- < >
141- < CircleXIcon className = "size-4 text-red-600" />
142- < Heading size = "label.md" >
143- { `${ result . explorerUrl } : Verification failed` }
144- </ Heading >
145- </ >
146- ) }
147- </ Flex >
148- ) ,
149- )
150- : null }
151- </ Flex >
152- </ ModalBody >
153- </ ModalContent >
154- </ Modal >
155- ) ;
156- } ;
157-
158- export const ContractSourcesPage : React . FC < ContractSourcesPageProps > = ( {
159- contract,
160- } ) => {
161- const [ resetSignal , setResetSignal ] = useState ( 0 ) ;
82+ { verifyQuery ?. error ? (
83+ < div className = "flex min-h-24 items-center justify-center" >
84+ < div className = "flex items-center gap-2" >
85+ < CircleXIcon className = "size-4 text-red-600" />
86+ < p className = "font-medium text-sm" >
87+ { verifyQuery ?. error . toString ( ) }
88+ </ p >
89+ </ div >
90+ </ div >
91+ ) : null }
16292
163- const { isOpen, onOpen, onClose } = useDisclosure ( ) ;
93+ { verifyQuery . data ?. results ? (
94+ < div className = "flex flex-col gap-2" >
95+ { verifyQuery . data . results . map (
96+ ( result : VerificationResult , index : number ) => (
97+ // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
98+ < div key = { index } className = "flex items-center gap-2" >
99+ { result . success && (
100+ < >
101+ < CircleCheckIcon className = "size-4 text-green-600" />
102+ { result . alreadyVerified && (
103+ < p className = "font-medium text-sm" >
104+ { result . explorerUrl } : Already verified
105+ </ p >
106+ ) }
107+ { ! result . alreadyVerified && (
108+ < p className = "font-medium text-sm" >
109+ { result . explorerUrl } : Verification successful
110+ </ p >
111+ ) }
112+ </ >
113+ ) }
164114
165- const handleClose = ( ) => {
166- onClose ( ) ;
167- // Increment to reset the query in the child component
168- setResetSignal ( ( prev : number ) => prev + 1 ) ;
169- } ;
115+ { ! result . success && (
116+ < >
117+ < CircleXIcon className = "size-4 text-red-600" />
118+ < p className = "font-medium text-sm" >
119+ { `${ result . explorerUrl } : Verification failed` }
120+ </ p >
121+ </ >
122+ ) }
123+ </ div >
124+ ) ,
125+ ) }
126+ </ div >
127+ ) : null }
128+ </ div >
129+ ) ;
130+ }
170131
132+ export function ContractSourcesPage ( {
133+ contract,
134+ } : { contract : ThirdwebContract } ) {
171135 const contractSourcesQuery = useContractSources ( contract ) ;
172136 const abiQuery = useResolveContractAbi ( contract ) ;
173137
@@ -187,44 +151,49 @@ export const ContractSourcesPage: React.FC<ContractSourcesPageProps> = ({
187151 . reverse ( ) ;
188152 } , [ contractSourcesQuery . data ] ) ;
189153
190- if ( ! contractSourcesQuery || contractSourcesQuery ?. isPending ) {
191- return (
192- < Flex direction = "row" align = "center" gap = { 2 } >
193- < Spinner color = "purple.500" size = "xs" />
194- < Heading size = "title.sm" > Loading...</ Heading >
195- </ Flex >
196- ) ;
154+ if ( ! contractSourcesQuery || contractSourcesQuery . isPending ) {
155+ return < GenericLoadingPage /> ;
197156 }
198157
199158 return (
200- < >
201- < VerifyContractModal
202- isOpen = { isOpen }
203- onClose = { ( ) => handleClose ( ) }
204- contract = { contract }
205- resetSignal = { resetSignal }
206- />
207-
208- < Flex direction = "column" gap = { 8 } >
209- < Flex direction = "row" alignItems = "center" gap = { 2 } >
210- < Heading size = "title.sm" flex = { 1 } >
211- Sources
212- </ Heading >
159+ < div >
160+ < div className = "flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between" >
161+ < div >
162+ < h2 className = "font-semibold text-2xl tracking-tight" > Sources</ h2 >
163+ < p className = "text-muted-foreground text-sm" >
164+ View ABI and source code for the contract
165+ </ p >
166+ </ div >
167+ < div className = "flex items-center gap-3" >
213168 < RefreshContractMetadataButton
214169 chainId = { contract . chain . id }
215170 contractAddress = { contract . address }
216171 />
217- < Button variant = "primary" onClick = { onOpen } >
218- Verify contract
219- </ Button >
220- </ Flex >
221- < Card >
222- < SourcesPanel sources = { sources } abi = { abiQuery . data } />
223- </ Card >
224- </ Flex >
225- </ >
172+
173+ < Dialog >
174+ < DialogTrigger asChild >
175+ < Button className = "gap-2" size = "sm" >
176+ < ShieldCheckIcon className = "size-4" />
177+ Verify contract
178+ </ Button >
179+ </ DialogTrigger >
180+ < DialogContent className = "gap-0 overflow-hidden p-0" >
181+ < DialogHeader className = "border-b p-6" >
182+ < DialogTitle > Verify Contract</ DialogTitle >
183+ </ DialogHeader >
184+ < VerifyContractModalContent contract = { contract } />
185+ </ DialogContent >
186+ </ Dialog >
187+ </ div >
188+ </ div >
189+
190+ < div className = "h-4" />
191+ < div className = "rounded-lg border bg-card" >
192+ < SourcesPanel sources = { sources } abi = { abiQuery . data } />
193+ </ div >
194+ </ div >
226195 ) ;
227- } ;
196+ }
228197
229198function RefreshContractMetadataButton ( props : {
230199 chainId : number ;
@@ -270,14 +239,19 @@ function RefreshContractMetadataButton(props: {
270239 onClick = { ( ) => {
271240 toast . promise ( contractCacheMutation . mutateAsync ( ) , {
272241 duration : 5000 ,
273- loading : "Refreshing contract data..." ,
274- success : ( ) => "Contract data refreshed!" ,
242+ success : ( ) => "Contract refreshed successfully" ,
275243 error : ( e ) => e ?. message || "Failed to refresh contract data." ,
276244 } ) ;
277245 } }
278- className = "w-[182px]"
246+ size = "sm"
247+ className = "gap-2 bg-card"
279248 >
280- { contractCacheMutation . isPending ? < Spinner /> : "Refresh Contract Data" }
249+ { contractCacheMutation . isPending ? (
250+ < Spinner className = "size-4" />
251+ ) : (
252+ < RefreshCcwIcon className = "size-4" />
253+ ) }
254+ Refresh Contract < span className = "max-sm:hidden" > Data </ span >
281255 </ Button >
282256 ) ;
283257}
0 commit comments