1-
21import { useState , useEffect , useMemo } from "react" ;
2+ import { Moon , Sun } from "lucide-react" ;
33import { CampaignDetailPanel } from "./components/CampaignDetailPanel" ;
44import { FundedConfetti } from "./components/FundedConfetti" ;
55import { KeyboardShortcutsOverlay } from "./components/KeyboardShortcutsOverlay" ;
@@ -148,6 +148,9 @@ function App() {
148148 null ,
149149 ) ;
150150 const [ invalidUrlCampaignId , setInvalidUrlCampaignId ] = useState < string | null > ( null ) ;
151+ const [ transactionPreview , setTransactionPreview ] =
152+ useState < TransactionPreviewState | null > ( null ) ;
153+ const [ confettiBurst , setConfettiBurst ] = useState < ConfettiBurst | null > ( null ) ;
151154
152155 const handleTransactionPreview = ( data : TransactionPreviewData ) : Promise < boolean > => {
153156 return new Promise ( ( resolve ) => {
@@ -297,6 +300,18 @@ function App() {
297300 } ) ;
298301 } , [ addToast , selectedCampaignId ] ) ;
299302
303+ useEffect ( ( ) => {
304+ if ( ! connectedWallet ) return ;
305+ const stop = watchFreighterAccount ( ( address ) => {
306+ if ( address && address !== connectedWallet ) {
307+ addToast ( `Switched to ${ address . slice ( 0 , 16 ) } ...` , "success" ) ;
308+ } else if ( ! address ) {
309+ addToast ( "Wallet disconnected." , "success" ) ;
310+ }
311+ } ) ;
312+ return stop ;
313+ } , [ connectedWallet , addToast ] ) ;
314+
300315 const selectedCampaign = useMemo ( ( ) => {
301316 const summaryCampaign =
302317 campaigns . find ( ( campaign ) => campaign . id === selectedCampaignId ) ?? null ;
@@ -357,7 +372,12 @@ function App() {
357372 }
358373 }
359374
375+ function handleDisconnectWallet ( ) {
376+ freighter . disconnect ( ) ;
377+ addToast ( "Wallet disconnected." , "success" ) ;
378+ }
360379
380+ async function handlePledge ( campaignId : string , amount : number , assetCode : string ) {
361381 if ( ! connectedWallet ) {
362382 addToast ( "Connect Freighter before submitting a pledge." , "error" ) ;
363383 return ;
@@ -368,13 +388,17 @@ function App() {
368388 return ;
369389 }
370390
391+ const previousCampaign = campaigns . find ( ( c ) => c . id === campaignId ) ?? null ;
392+ setPendingPledgeCampaignId ( campaignId ) ;
371393
372394 try {
373395 const transactionResult = await submitFreighterPledge ( {
374396 campaignId,
375397 contributor : connectedWallet ,
376398 amount,
377-
399+ assetCode,
400+ config : appConfig ,
401+ onPreview : handleTransactionPreview ,
378402 } ) ;
379403
380404 await reconcilePledge ( campaignId , {
@@ -428,7 +452,7 @@ function App() {
428452 const transactionResult = await submitFreighterClaim ( {
429453 campaignId : campaign . id ,
430454 creator : connectedWallet ,
431- config : appConfig as AppConfig ,
455+ config : appConfig ,
432456 onPreview : handleTransactionPreview ,
433457 } ) ;
434458
@@ -449,13 +473,39 @@ function App() {
449473 addToast ( getErrorMessage ( error ) , "error" ) ;
450474 }
451475 }
476+
452477 async function handleSoftDelete ( campaignId : string ) {
453478 if ( ! confirm ( `Soft delete campaign #${ campaignId } ? Data preserved, hidden from lists.` ) ) {
454479 return ;
455480 }
456481
457482 setActionError ( null ) ;
458483 setActionMessage ( "Soft deleting..." ) ;
484+
485+ try {
486+ await softDeleteCampaign ( campaignId ) ;
487+ await refreshCampaigns ( ) ;
488+ setActionMessage ( "Campaign soft deleted." ) ;
489+ } catch ( error ) {
490+ setActionError ( toApiError ( error ) ) ;
491+ setActionMessage ( null ) ;
492+ }
493+ }
494+
495+ async function handleRefund ( campaignId : string , contributor : string ) {
496+ setActionError ( null ) ;
497+ setActionMessage ( "Preparing Soroban refund transaction..." ) ;
498+
499+ try {
500+ const sorobanReceipt = await submitRefundTransaction ( campaignId , contributor ) ;
501+ await refundCampaign ( campaignId , contributor , sorobanReceipt ) ;
502+ await refreshCampaigns ( campaignId ) ;
503+ await refreshSelectedData ( campaignId ) ;
504+ setActionMessage ( "Contributor refunded successfully." ) ;
505+ } catch ( error ) {
506+ setActionError ( toApiError ( error ) ) ;
507+ setActionMessage ( null ) ;
508+ }
459509 }
460510
461511 function handleSelect ( campaignId : string ) {
@@ -468,7 +518,34 @@ function App() {
468518 }
469519
470520 return (
471-
521+ < div className = "app-shell" >
522+ < header className = "hero" >
523+ < div className = "hero-topline" >
524+ < p className = "eyebrow" > Soroban crowdfunding MVP</ p >
525+ < div style = { { display : "flex" , alignItems : "center" , gap : "0.5rem" } } >
526+ < WalletWidget
527+ status = { freighter . status }
528+ publicKey = { freighter . publicKey }
529+ error = { freighter . error }
530+ onConnect = { handleConnectWallet }
531+ />
532+ < button
533+ type = "button"
534+ className = "btn-ghost theme-toggle"
535+ onClick = { handleThemeToggle }
536+ aria-label = { `Switch to ${ themeMode === "dark" ? "light" : "dark" } mode` }
537+ title = { `Switch to ${ themeMode === "dark" ? "light" : "dark" } mode` }
538+ >
539+ { themeMode === "dark" ? < Sun size = { 18 } /> : < Moon size = { 18 } /> }
540+ </ button >
541+ </ div >
542+ </ div >
543+ < h1 > Stellar Goal Vault</ h1 >
544+ < p className = "hero-copy" >
545+ Create funding goals, collect pledges, and reconcile claim and refund flows
546+ against the backend contract integration.
547+ </ p >
548+ </ header >
472549
473550 < section className = "metric-grid animate-fade-in" >
474551 < article className = "metric-card" >
@@ -548,6 +625,32 @@ function App() {
548625
549626 < ToastContainer toasts = { toasts } onDismiss = { dismiss } />
550627
628+ { transactionPreview && (
629+ < TransactionPreviewModal
630+ preview = { transactionPreview . data }
631+ onConfirm = { ( ) => {
632+ transactionPreview . resolve ( true ) ;
633+ setTransactionPreview ( null ) ;
634+ } }
635+ onCancel = { ( ) => {
636+ transactionPreview . resolve ( false ) ;
637+ setTransactionPreview ( null ) ;
638+ } }
639+ />
640+ ) }
641+
642+ { confettiBurst && (
643+ < FundedConfetti
644+ key = { confettiBurst . id }
645+ campaignTitle = { confettiBurst . campaignTitle }
646+ onComplete = { ( ) => setConfettiBurst ( null ) }
647+ />
648+ ) }
649+
650+ { isShortcutsOpen && (
651+ < KeyboardShortcutsOverlay isOpen = { isShortcutsOpen } onClose = { ( ) => setIsShortcutsOpen ( false ) } />
652+ ) }
653+
551654 { import . meta. env . DEV && (
552655 < footer style = { { padding : "1rem" , textAlign : "center" , borderTop : "1px solid var(--border-color)" , marginTop : "2rem" } } >
553656 < p style = { { margin : 0 , fontSize : "0.875rem" , color : "var(--text-secondary)" } } >
0 commit comments