11import type { SupportedIdl } from '@entities/idl' ;
22import { Button } from '@shared/ui/button' ;
3+ import {
4+ Dialog ,
5+ DialogClose ,
6+ DialogContent ,
7+ DialogDescription ,
8+ DialogFooter ,
9+ DialogHeader ,
10+ DialogTitle ,
11+ } from '@shared/ui/dialog' ;
312import { Input } from '@shared/ui/input' ;
413import { Label } from '@shared/ui/label' ;
514import { Switch } from '@shared/ui/switch' ;
6- import { useMemo , useState } from 'react' ;
7- import { Code , Download , Search } from 'react-feather' ;
15+ import { useEffect , useMemo , useRef , useState } from 'react' ;
16+ import { AlertCircle , Code , Download , ExternalLink , Search } from 'react-feather' ;
817
918import { WalletProvider } from '@/app/providers/wallet-provider' ;
1019import { triggerDownload } from '@/app/shared/lib/triggerDownload' ;
1120
21+ import { type IdlVariant } from '../model/use-idl-last-transaction-date' ;
1222import { IdlRenderer } from './IdlRenderer' ;
1323
1424export function IdlSection ( {
1525 idl,
1626 badge,
1727 programId,
28+ idlSource,
29+ network,
1830 searchStr,
1931 onSearchChange,
2032} : {
2133 idl : SupportedIdl ;
2234 badge : React . ReactNode ;
2335 programId : string ;
36+ idlSource : IdlVariant ;
37+ network : string ;
2438 searchStr : string ;
2539 onSearchChange : ( str : string ) => void ;
2640} ) {
2741 const [ isExpanded , setIsExpanded ] = useState ( false ) ;
2842 const [ isRawIdlView , setIsRawIdlView ] = useState ( false ) ;
43+ const [ isCastawayDialogOpen , setIsCastawayDialogOpen ] = useState ( false ) ;
44+ const downloadDropdownRef = useRef < HTMLButtonElement > ( null ) ;
2945
3046 const idlBase64 = useMemo ( ( ) => {
3147 return Buffer . from ( JSON . stringify ( idl , null , 2 ) ) . toString ( 'base64' ) ;
3248 } , [ idl ] ) ;
49+ const castawayUrl = useMemo ( ( ) => {
50+ const params = new URLSearchParams ( { idlSource, network, program : programId } ) ;
51+ return `https://www.castaway.lol/?${ params . toString ( ) } ` ;
52+ } , [ idlSource , network , programId ] ) ;
53+
54+ useEffect ( ( ) => {
55+ if ( ! downloadDropdownRef . current ) {
56+ return ;
57+ }
58+
59+ let isMounted = true ;
60+ let dropdown : { dispose : ( ) => void } | null = null ;
61+
62+ void import ( 'bootstrap/js/dist/dropdown' ) . then ( module => {
63+ if ( ! isMounted || ! downloadDropdownRef . current ) {
64+ return ;
65+ }
66+
67+ const BsDropdown = module . default ;
68+ dropdown = new BsDropdown ( downloadDropdownRef . current , {
69+ popperConfig ( ) {
70+ return { strategy : 'fixed' as const } ;
71+ } ,
72+ } ) ;
73+ } ) ;
74+
75+ return ( ) => {
76+ isMounted = false ;
77+ dropdown ?. dispose ( ) ;
78+ } ;
79+ } , [ ] ) ;
3380
3481 const handleDownloadIdl = ( ) => triggerDownload ( idlBase64 , `${ programId } -idl.json` ) ;
82+ const handleOpenCastawayDialog = ( ) => setIsCastawayDialogOpen ( true ) ;
83+ const handleCastawayContinue = ( ) => {
84+ window . open ( castawayUrl , '_blank' , 'noopener,noreferrer' ) ;
85+ setIsCastawayDialogOpen ( false ) ;
86+ } ;
3587
3688 return (
3789 < >
@@ -58,10 +110,55 @@ export function IdlSection({
58110 </ div >
59111 ) }
60112 < div className = "e-flex e-items-center e-gap-2" >
61- < Button variant = "outline" size = "sm" onClick = { handleDownloadIdl } >
62- < Download size = { 12 } />
63- Download
64- </ Button >
113+ < div className = "dropdown e-overflow-visible" >
114+ < Button
115+ variant = "outline"
116+ size = "sm"
117+ ref = { downloadDropdownRef }
118+ data-bs-toggle = "dropdown"
119+ type = "button"
120+ aria-label = "Download"
121+ >
122+ < Download size = { 12 } />
123+ Download
124+ </ Button >
125+ < div className = "dropdown-menu-end dropdown-menu e-z-10" >
126+ < div className = "d-flex e-flex-col" >
127+ < Button onClick = { handleDownloadIdl } > Download IDL</ Button >
128+ < Button onClick = { handleOpenCastawayDialog } > Generate SDK</ Button >
129+ </ div >
130+ </ div >
131+ </ div >
132+
133+ < Dialog open = { isCastawayDialogOpen } onOpenChange = { setIsCastawayDialogOpen } >
134+ < DialogContent >
135+ < DialogHeader >
136+ < DialogTitle className = "e-flex e-items-center e-gap-2" >
137+ < AlertCircle className = "e-text-destructive" size = { 16 } />
138+ Leaving Solana Explorer
139+ </ DialogTitle >
140+ </ DialogHeader >
141+ < div className = "e-space-y-2 e-pl-6" >
142+ < DialogDescription >
143+ You are now leaving Explorer and going to Castaway.
144+ </ DialogDescription >
145+ < DialogDescription className = "e-break-all e-font-mono e-text-xs" >
146+ { castawayUrl }
147+ </ DialogDescription >
148+ </ div >
149+ < DialogFooter >
150+ < DialogClose asChild >
151+ < Button variant = "outline" size = "sm" >
152+ Cancel
153+ </ Button >
154+ </ DialogClose >
155+ < Button variant = "default" size = "sm" onClick = { handleCastawayContinue } >
156+ Continue
157+ < ExternalLink size = { 12 } />
158+ </ Button >
159+ </ DialogFooter >
160+ </ DialogContent >
161+ </ Dialog >
65162
66163 < Button
67164 variant = { isRawIdlView ? 'accent' : 'outline' }
0 commit comments