Skip to content

Commit aedb6bf

Browse files
committed
fix: resolve build errors from merge conflicts in App.tsx, CampaignDetailPanel, and contract
1 parent 53979ae commit aedb6bf

4 files changed

Lines changed: 146 additions & 8 deletions

File tree

contracts/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use soroban_sdk::{
88
};
99

1010
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
11+
pub const MIN_CONTRIBUTION: i128 = 100;
1112

1213
#[contracttype]
1314
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -180,7 +181,8 @@ impl StellarGoalVaultContract {
180181
// Update campaign pledged amount (valuation)
181182
campaign.pledged_amount += amount;
182183

183-
184+
let balance_key = DataKey::CampaignTokenBalance(campaign_id, token.clone());
185+
let current_balance: i128 = env.storage().persistent().get(&balance_key).unwrap_or(0);
184186
env.storage()
185187
.persistent()
186188
.set(&balance_key, &(current_balance + amount));

frontend/src/App.tsx

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
21
import { useState, useEffect, useMemo } from "react";
2+
import { Moon, Sun } from "lucide-react";
33
import { CampaignDetailPanel } from "./components/CampaignDetailPanel";
44
import { FundedConfetti } from "./components/FundedConfetti";
55
import { 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)" }}>

frontend/src/components/CampaignDetailPanel.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ interface CampaignDetailPanelProps {
1515
isLoading?: boolean;
1616
isPledgePending?: boolean;
1717
onConnectWallet?: () => Promise<void>;
18-
18+
onDisconnectWallet?: () => void;
19+
onPledge?: (campaignId: string, amount: number, assetCode: string) => Promise<void>;
1920
onClaim?: (campaign: Campaign) => Promise<void>;
2021
onSoftDelete?: (campaignId: string) => Promise<void>;
2122
onRefund?: (campaignId: string, contributor: string) => Promise<void>;
@@ -197,7 +198,33 @@ export function CampaignDetailPanel({
197198
</div>
198199
<div className="wallet-connected">
199200
{connectedWallet ? (
200-
201+
<>
202+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
203+
<strong className="mono">{connectedWallet.slice(0, 16)}...</strong>
204+
<CopyButton
205+
value={connectedWallet}
206+
ariaLabel="Copy connected wallet address"
207+
/>
208+
</div>
209+
<button
210+
className="btn-ghost"
211+
type="button"
212+
onClick={onDisconnectWallet}
213+
disabled={isSubmitting}
214+
>
215+
Disconnect
216+
</button>
217+
</>
218+
) : (
219+
<button
220+
className="btn-ghost"
221+
type="button"
222+
onClick={() => { void onConnectWallet(); }}
223+
disabled={isSubmitting || isConnectingWallet}
224+
>
225+
{isConnectingWallet ? "Connecting..." : "Connect Freighter"}
226+
</button>
227+
)}
201228
</div>
202229
</div>
203230

frontend/src/hooks/useFreighter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface UseFreighterResult {
88
status: FreighterStatus;
99
publicKey: string | null;
1010
connect: (networkPassphrase: string) => Promise<string | null>;
11+
disconnect: () => void;
1112
error: string | null;
1213
}
1314

@@ -40,5 +41,10 @@ export function useFreighter(): UseFreighterResult {
4041
}
4142
}, []);
4243

43-
return { status, publicKey, connect, error };
44+
const disconnect = useCallback(() => {
45+
setPublicKey(null);
46+
setStatus("available");
47+
}, []);
48+
49+
return { status, publicKey, connect, disconnect, error };
4450
}

0 commit comments

Comments
 (0)