- {/* Header */}
@@ -85,18 +53,74 @@ export const InvestorDashboardPage: React.FC = () => {
-
-
- Connected: {walletAddress}
-
+ {wallet.isConnected && wallet.publicKey ? (
+
+
+ Connected: {truncateAddress(wallet.publicKey)}
+
+ ) : (
+
+
+ Wallet not connected
+
+ )}
- {investments === null ? (
+ {!configured && (
+
+
+ Soroban RPC not configured
+
+
+ Set VITE_SOROBAN_RPC_URL and{' '}
+
+ VITE_PRODUCTION_ESCROW_CONTRACT_ID
+ {' '}
+ to load your on-chain investments.
+
+
+ )}
+
+ {configured && !wallet.isConnected && (
+
+
+ Connect to view your investments
+
+
+ Connect your Stellar wallet to see campaigns you have funded and to
+ claim refunds or returns on-chain.
+
+
+
+ )}
+
+ {configured && wallet.isConnected && portfolioQuery.isLoading && (
- ) : (
+ )}
+
+ {configured && wallet.isConnected && portfolioQuery.isError && (
+
+
+ Couldn't load your investments from the network. Try reloading
+ the page.
+
+
+ )}
+
+ {configured && wallet.isConnected && portfolioQuery.isSuccess && (
<>
{
)}
);
-};
+}
export default InvestorDashboardPage;
diff --git a/client/src/pages/__tests__/InvestorDashboardPage.test.tsx b/client/src/pages/__tests__/InvestorDashboardPage.test.tsx
new file mode 100644
index 0000000..546e067
--- /dev/null
+++ b/client/src/pages/__tests__/InvestorDashboardPage.test.tsx
@@ -0,0 +1,196 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+import { InvestorDashboardPage } from '../InvestorDashboardPage';
+import type { FundedInvestment } from '../../lib/soroban/investorService';
+import * as investorService from '../../lib/soroban/investorService';
+
+const WALLET = 'GINVESTOR000000000000000000000000000000000000000000000B2';
+
+const mockUseWallet = vi.fn();
+vi.mock('../../context/WalletContext', () => ({
+ useWallet: () => mockUseWallet(),
+ truncateAddress: (addr: string) =>
+ addr.length <= 12 ? addr : `${addr.slice(0, 6)}...${addr.slice(-4)}`,
+}));
+
+const mockUseClaimRefund = vi.fn();
+const mockUseClaimReturn = vi.fn();
+vi.mock('../../hooks/contract', () => ({
+ useClaimRefund: () => mockUseClaimRefund(),
+ useClaimReturn: () => mockUseClaimReturn(),
+}));
+
+const mockUseInvestorPortfolio = vi.fn();
+vi.mock('../../hooks/useInvestorPortfolio', () => ({
+ useInvestorPortfolio: (...args: unknown[]) =>
+ mockUseInvestorPortfolio(...args),
+}));
+
+const mockIsEscrowConfigured = vi.fn();
+vi.mock('../../lib/soroban/config', () => ({
+ isEscrowConfigured: () => mockIsEscrowConfigured(),
+}));
+
+const SETTLED_INVESTMENT: FundedInvestment = {
+ campaignId: '42',
+ title: 'On-chain Maize PIP',
+ amountContributed: 5000,
+ status: 'Settled',
+ claimableAmount: 1250,
+ claimed: false,
+ walletAddress: WALLET,
+ fundedAt: '2026-06-01T14:30:00.000Z',
+};
+
+const FAILED_INVESTMENT: FundedInvestment = {
+ campaignId: '7',
+ title: 'Failed Fertilizer PIP',
+ amountContributed: 1500,
+ status: 'Failed',
+ claimableAmount: 1500,
+ claimed: false,
+ walletAddress: WALLET,
+ fundedAt: '2026-05-20T09:15:00.000Z',
+};
+
+function mockWallet(publicKey: string | null) {
+ mockUseWallet.mockReturnValue({
+ publicKey,
+ isConnected: publicKey !== null,
+ isConnecting: false,
+ error: null,
+ connect: vi.fn(),
+ disconnect: vi.fn(),
+ clearError: vi.fn(),
+ signTransaction: vi.fn(),
+ });
+}
+
+function renderPage() {
+ return render(
+