diff --git a/dongle/app/admin/page.tsx b/dongle/app/admin/page.tsx index 5f6cacc..3050055 100644 --- a/dongle/app/admin/page.tsx +++ b/dongle/app/admin/page.tsx @@ -183,6 +183,7 @@ export default function AdminDashboard() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} /> )} diff --git a/dongle/app/profile/page.tsx b/dongle/app/profile/page.tsx index 40098da..9462b33 100644 --- a/dongle/app/profile/page.tsx +++ b/dongle/app/profile/page.tsx @@ -141,6 +141,7 @@ export default function ProfilePage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} /> )} diff --git a/dongle/app/projects/[id]/edit/page.tsx b/dongle/app/projects/[id]/edit/page.tsx index 02d4ad0..926bff3 100644 --- a/dongle/app/projects/[id]/edit/page.tsx +++ b/dongle/app/projects/[id]/edit/page.tsx @@ -77,6 +77,7 @@ export default function EditProjectPage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} /> )} diff --git a/dongle/app/projects/[id]/page.tsx b/dongle/app/projects/[id]/page.tsx index 5d09d8c..8dd10f6 100644 --- a/dongle/app/projects/[id]/page.tsx +++ b/dongle/app/projects/[id]/page.tsx @@ -83,6 +83,7 @@ export default function ProjectDetailPage() { const [reportingReview, setReportingReview] = useState(null); const [reviewSort, setReviewSort] = useState<"newest" | "highest" | "lowest" | "mine">("newest"); const [verificationStatus, setVerificationStatus] = useState<"NONE" | "PENDING" | "VERIFIED" | "REJECTED" | null>(null); + const [verificationError, setVerificationError] = useState(null); const [updates, setUpdates] = useState([]); const [isAddingUpdate, setIsAddingUpdate] = useState(false); const [editingUpdate, setEditingUpdate] = useState(null); @@ -117,6 +118,7 @@ export default function ProjectDetailPage() { // Fetch verification status with cancellation support const fetchVerification = async () => { + if (!cancelled) setVerificationError(null); try { const status = await sorobanService.getVerificationStatus(projectId, abortController.signal); if (!cancelled) { @@ -125,7 +127,9 @@ export default function ProjectDetailPage() { } catch (error) { if (!cancelled) { console.error("Failed to fetch verification status:", error); - setVerificationStatus("NONE"); + setVerificationError( + error instanceof Error ? error.message : "Failed to load verification status", + ); } } }; @@ -144,6 +148,21 @@ export default function ProjectDetailPage() { }; }, [projectId, gate.publicKey]); + const retryVerification = React.useCallback(() => { + setVerificationError(null); + setVerificationStatus(null); + const abortController = new AbortController(); + void sorobanService + .getVerificationStatus(projectId, abortController.signal) + .then(setVerificationStatus) + .catch((err) => { + console.error("Failed to fetch verification status:", err); + setVerificationError( + err instanceof Error ? err.message : "Failed to load verification status", + ); + }); + }, [projectId]); + const actualRating = React.useMemo(() => { if (reviews.length === 0) return project?.rating || 0; const sum = reviews.reduce((acc, r) => acc + r.rating, 0); @@ -794,6 +813,7 @@ export default function ProjectDetailPage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} compact /> @@ -825,7 +845,14 @@ export default function ProjectDetailPage() {
{/* Verification Status */}
-

Verification Status

+
+

Verification Status

+ {verificationError && ( + + )} +
diff --git a/dongle/app/projects/new/page.tsx b/dongle/app/projects/new/page.tsx index 55acfcb..3dd4b6d 100644 --- a/dongle/app/projects/new/page.tsx +++ b/dongle/app/projects/new/page.tsx @@ -32,6 +32,7 @@ export default function NewProjectPage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} /> )} diff --git a/dongle/app/reviews/page.tsx b/dongle/app/reviews/page.tsx index 5ddd8eb..6d5f8a8 100644 --- a/dongle/app/reviews/page.tsx +++ b/dongle/app/reviews/page.tsx @@ -280,6 +280,7 @@ export default function ReviewsPage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} compact />
diff --git a/dongle/app/verify/page.tsx b/dongle/app/verify/page.tsx index ab8c99a..251880e 100644 --- a/dongle/app/verify/page.tsx +++ b/dongle/app/verify/page.tsx @@ -70,6 +70,7 @@ export default function VerifyPage() { publicKey={gate.publicKey} onConnect={gate.connectWallet} onDisconnect={gate.disconnectWallet} + onRetry={gate.retryAccountLoad} /> ) diff --git a/dongle/components/verify/VerificationStatus.tsx b/dongle/components/verify/VerificationStatus.tsx index 8115d15..9d99b72 100644 --- a/dongle/components/verify/VerificationStatus.tsx +++ b/dongle/components/verify/VerificationStatus.tsx @@ -19,20 +19,23 @@ export default function VerificationStatus({ initialProjectId }: VerificationSta const [searchInput, setSearchInput] = useState(initialProjectId ?? ""); const [status, setStatus] = useState("NONE"); const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const isMountedRef = React.useRef(true); const fetchStatus = async (id: string) => { if (!id) return; setIsLoading(true); + setError(null); try { const result = await sorobanService.getVerificationStatus(id); if (!isMountedRef.current) return; setStatus(result); - } catch (error) { + } catch (err) { if (!isMountedRef.current) return; - console.error(error); - setStatus("NONE"); + const msg = err instanceof Error ? err.message : "Failed to check verification status"; + console.error(err); + setError(msg); } finally { if (isMountedRef.current) { setIsLoading(false); @@ -107,7 +110,25 @@ export default function VerificationStatus({ initialProjectId }: VerificationSta - {projectId && ( + {projectId && error && ( + +
+ +

Check Failed

+

{error}

+ +
+
+ )} + + {projectId && !error && (
{isLoading ? ( diff --git a/dongle/components/wallet/WalletStatePanel.tsx b/dongle/components/wallet/WalletStatePanel.tsx index dffb9f7..793053b 100644 --- a/dongle/components/wallet/WalletStatePanel.tsx +++ b/dongle/components/wallet/WalletStatePanel.tsx @@ -23,6 +23,7 @@ interface WalletStatePanelProps { publicKey?: string | null; onConnect?: () => void; onDisconnect?: () => void; + onRetry?: () => void; compact?: boolean; className?: string; } @@ -36,6 +37,7 @@ const STATE_ICONS: Record< disconnected: , "wrong-network": , "account-not-funded": , + "account-error": , }; export default function WalletStatePanel({ @@ -45,6 +47,7 @@ export default function WalletStatePanel({ publicKey, onConnect, onDisconnect, + onRetry, compact = false, className = "", }: WalletStatePanelProps) { @@ -116,7 +119,11 @@ export default function WalletStatePanel({ renderAction( content.primaryAction, "primary", - state === "disconnected" ? onConnect : undefined, + state === "disconnected" + ? onConnect + : state === "account-error" + ? onRetry + : undefined, )} {content.secondaryAction && renderAction( diff --git a/dongle/components/wallet/wallet-states.ts b/dongle/components/wallet/wallet-states.ts index 14d9298..e2d97c0 100644 --- a/dongle/components/wallet/wallet-states.ts +++ b/dongle/components/wallet/wallet-states.ts @@ -10,7 +10,8 @@ export type WalletPageState = | "disconnected" | "wrong-network" | "account-loading" - | "account-not-funded"; + | "account-not-funded" + | "account-error"; export interface WalletStateContent { title: string; @@ -104,6 +105,15 @@ export function getWalletStateContent( label: "Disconnect Wallet", }, }; + case "account-error": + return { + title: "Failed to Load Account", + description: + "Could not retrieve your Stellar account data. This is usually a temporary network issue.", + primaryAction: { + label: "Try Again", + }, + }; default: return { title: "Wallet Required", diff --git a/dongle/hooks/useWalletPageGate.ts b/dongle/hooks/useWalletPageGate.ts index 942cf3d..3e8e8f0 100644 --- a/dongle/hooks/useWalletPageGate.ts +++ b/dongle/hooks/useWalletPageGate.ts @@ -19,6 +19,7 @@ export interface WalletPageGateResult { connectWallet: () => Promise; disconnectWallet: () => void; isConnecting: boolean; + retryAccountLoad: (() => Promise) | undefined; } /** @@ -41,7 +42,7 @@ export function useWalletPageGate( disconnectWallet, } = useWallet(); - const { loading: accountLoading, error: accountError } = useStellarAccount(); + const { loading: accountLoading, error: accountError, refetch: refetchAccount } = useStellarAccount(); if (isFreighterAvailable === false) { return { @@ -51,6 +52,7 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; } @@ -62,6 +64,7 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; } @@ -73,6 +76,7 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; } @@ -84,6 +88,7 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; } @@ -96,6 +101,7 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; } @@ -107,6 +113,19 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, + }; + } + + if (accountError) { + return { + state: "account-error", + publicKey, + walletNetworkLabel, + connectWallet, + disconnectWallet, + isConnecting, + retryAccountLoad: refetchAccount, }; } } @@ -118,5 +137,6 @@ export function useWalletPageGate( connectWallet, disconnectWallet, isConnecting, + retryAccountLoad: undefined, }; }