Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AnalyticsDashboardPage } from './pages/AnalyticsDashboardPage';
import { AdminDashboardPage } from './pages/AdminDashboardPage';
import { ActivityFeedPage } from './pages/ActivityFeedPage';
import { CampaignDetailPage } from './pages/CampaignDetailPage';
import { CreateCampaignPage, CampaignsPage } from './pages';
import './App.css';

export default function App() {
Expand Down Expand Up @@ -52,6 +53,8 @@ export default function App() {
{/* AppLayout routes */}
<Route element={<AppLayout />}>
<Route path="/activity" element={<ActivityFeedPage />} />
<Route path="/campaigns" element={<CampaignsPage />} />
<Route path="/campaigns/new" element={<CreateCampaignPage />} />
<Route path="/campaigns/:id" element={<CampaignDetailPage />} />
</Route>
</Routes>
Expand Down
51 changes: 22 additions & 29 deletions client/src/components/campaign/FundCampaignModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,39 +106,38 @@ export const FundCampaignModal: React.FC<FundCampaignModalProps> = ({
title={
<>
Fund Campaign
<span className="mt-1 block text-sm font-normal text-slate-500 dark:text-slate-400">
<span className="mt-1 block text-sm font-normal text-soil-500 dark:text-soil-400">
{campaignTitle}
</span>
</>
}
size="md"
>
{/* Success View */}
{successResult ? (
<div className="space-y-4 py-2 text-center">
<div
className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-emerald-100 text-2xl font-bold text-emerald-700 dark:bg-emerald-950 dark:text-emerald-400"
className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-leaf-100 text-2xl font-bold text-leaf-700 dark:bg-leaf-950 dark:text-leaf-400"
aria-hidden="true"
>
</div>
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">
<h3 className="text-lg font-semibold text-soil-900 dark:text-soil-50">
Contribution Successful!
</h3>
<p className="text-sm text-slate-600 dark:text-slate-300">
<p className="text-sm text-soil-600 dark:text-soil-300">
You contributed{' '}
<span className="font-semibold text-emerald-700 dark:text-emerald-400">
<span className="font-semibold text-leaf-700 dark:text-leaf-400">
${numAmount.toLocaleString()}
</span>{' '}
to {campaignTitle}.
</p>

{successResult.txHash && (
<div className="rounded-xl bg-slate-50 p-3 text-left dark:bg-slate-800/60">
<span className="block font-mono text-xs text-slate-600 dark:text-slate-400">
<div className="rounded-xl bg-soil-50 p-3 text-left dark:bg-soil-800/60">
<span className="block font-mono text-xs text-soil-600 dark:text-soil-400">
Transaction Hash
</span>
<span className="break-all font-mono text-xs text-slate-700 dark:text-slate-300">
<span className="break-all font-mono text-xs text-soil-700 dark:text-soil-300">
{successResult.txHash}
</span>
</div>
Expand All @@ -148,36 +147,33 @@ export const FundCampaignModal: React.FC<FundCampaignModalProps> = ({
<button
type="button"
onClick={resetAndClose}
className="w-full rounded-xl bg-emerald-700 px-4 py-3 font-medium text-white transition hover:bg-emerald-800"
className="w-full rounded-xl bg-leaf-600 px-4 py-3 font-medium text-white transition hover:bg-leaf-700"
>
Done
</button>
</div>
</div>
) : (
/* Input Form View */
<form onSubmit={handleSubmit} className="space-y-4">
{/* Stats bar */}
<div className="grid grid-cols-2 gap-3 rounded-xl border border-slate-100 bg-slate-50 p-3 dark:border-slate-800 dark:bg-slate-800/40">
<div className="grid grid-cols-2 gap-3 rounded-xl border border-soil-100 bg-soil-50 p-3 dark:border-soil-800 dark:bg-soil-800/40">
<div>
<span className="block text-xs text-slate-600 dark:text-slate-400">
<span className="block text-xs text-soil-600 dark:text-soil-400">
Remaining Target
</span>
<span className="text-sm font-semibold text-slate-900 dark:text-white">
<span className="text-sm font-semibold text-soil-900 dark:text-soil-50">
${remainingTarget.toLocaleString()}
</span>
</div>
<div>
<span className="block text-xs text-slate-600 dark:text-slate-400">
<span className="block text-xs text-soil-600 dark:text-soil-400">
Est. Share
</span>
<span className="text-sm font-semibold text-emerald-700 dark:text-emerald-400">
<span className="text-sm font-semibold text-leaf-700 dark:text-leaf-400">
{estimatedShare}%
</span>
</div>
</div>

{/* Error banner */}
{error && (
<div
id="contribution-amount-error"
Expand All @@ -188,17 +184,16 @@ export const FundCampaignModal: React.FC<FundCampaignModalProps> = ({
</div>
)}

{/* Input field */}
<div>
<label
htmlFor="contribution-amount"
className="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
className="mb-1 block text-sm font-medium text-soil-700 dark:text-soil-300"
>
Contribution Amount (USDC)
</label>
<div className="relative">
<span
className="absolute left-3 top-1/2 -translate-y-1/2 font-semibold text-slate-600 dark:text-slate-400"
className="absolute left-3 top-1/2 -translate-y-1/2 font-semibold text-soil-600 dark:text-soil-400"
aria-hidden="true"
>
$
Expand All @@ -219,41 +214,39 @@ export const FundCampaignModal: React.FC<FundCampaignModalProps> = ({
aria-describedby={
error ? 'contribution-amount-error' : undefined
}
className="w-full rounded-xl border border-slate-300 bg-white py-2.5 pl-8 pr-4 text-slate-900 outline-none transition focus:border-transparent focus:ring-2 focus:ring-emerald-500 dark:border-slate-700 dark:bg-slate-800 dark:text-white"
className="w-full rounded-xl border border-soil-300 bg-white py-2.5 pl-8 pr-4 text-soil-900 outline-none transition focus:border-transparent focus:ring-2 focus:ring-leaf-500 dark:border-soil-700 dark:bg-soil-800 dark:text-soil-50"
/>
</div>
</div>

{/* Quick selectors */}
<div className="flex items-center gap-2">
<span className="text-xs text-slate-600 dark:text-slate-400">
<span className="text-xs text-soil-600 dark:text-soil-400">
Quick fill:
</span>
{[25, 50, 100].map((pct) => (
<button
type="button"
key={pct}
onClick={() => handlePercentageSelect(pct)}
className="rounded-lg border border-slate-200 px-2.5 py-1 text-xs text-slate-600 transition hover:bg-slate-100 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800"
className="rounded-lg border border-soil-200 px-2.5 py-1 text-xs text-soil-600 transition hover:bg-soil-100 dark:border-soil-700 dark:text-soil-300 dark:hover:bg-soil-800"
>
{pct}%
</button>
))}
</div>

{/* Footer Buttons */}
<div className="flex items-center justify-end gap-3 border-t border-slate-100 pt-3 dark:border-slate-800">
<div className="flex items-center justify-end gap-3 border-t border-soil-100 pt-3 dark:border-soil-800">
<button
type="button"
onClick={resetAndClose}
className="rounded-xl border border-slate-300 px-4 py-2.5 font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800"
className="rounded-xl border border-soil-300 px-4 py-2.5 font-medium text-soil-700 transition hover:bg-soil-50 dark:border-soil-700 dark:text-soil-300 dark:hover:bg-soil-800"
>
Cancel
</button>
<button
type="submit"
disabled={loading || remainingTarget <= 0}
className="rounded-xl bg-emerald-700 px-5 py-2.5 font-semibold text-white shadow-sm transition hover:bg-emerald-800 disabled:opacity-50"
className="rounded-xl bg-leaf-600 px-5 py-2.5 font-semibold text-white shadow-sm transition hover:bg-leaf-700 disabled:opacity-50"
>
{loading ? 'Confirming...' : 'Confirm Contribution'}
</button>
Expand Down
40 changes: 12 additions & 28 deletions client/src/components/investor/InvestmentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import React, { useState } from 'react';
import type { FundedInvestment } from '../../lib/soroban/investorService';
import { StatusBadge } from '../campaign/StatusBadge';

export interface InvestmentCardProps {
investment: FundedInvestment;
onClaimRefund: (campaignId: string) => Promise<void>;
onClaimReturn: (campaignId: string) => Promise<void>;
}

const statusBadgeStyles: Record<string, string> = {
Active: 'bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300',
Funding:
'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300',
Settled:
'bg-purple-100 text-purple-800 dark:bg-purple-950 dark:text-purple-300',
Resolved: 'bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300',
Failed: 'bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-300',
};

export const InvestmentCard: React.FC<InvestmentCardProps> = ({
investment,
onClaimRefund,
Expand Down Expand Up @@ -51,36 +42,29 @@ export const InvestmentCard: React.FC<InvestmentCardProps> = ({
!investment.claimed;

return (
<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-6 shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4 transition hover:border-slate-300 dark:hover:border-slate-700">
<div className="flex flex-col justify-between gap-4 rounded-campaign border border-soil-200 bg-white p-6 shadow-campaign transition hover:border-soil-300 dark:border-soil-800 dark:bg-soil-900 md:flex-row md:items-center">
<div className="space-y-2">
<div className="flex items-center gap-3">
<span
className={`px-3 py-1 text-xs font-semibold rounded-full ${
statusBadgeStyles[investment.status] ||
'bg-slate-100 text-slate-800'
}`}
>
{investment.status}
</span>
<span className="text-xs font-mono text-slate-600 dark:text-slate-400">
<StatusBadge status={investment.status} />
<span className="font-mono text-xs text-soil-600 dark:text-soil-400">
ID: {investment.campaignId}
</span>
</div>

<h3 className="text-lg font-bold text-slate-900 dark:text-white">
<h3 className="text-lg font-bold text-soil-900 dark:text-soil-50">
{investment.title}
</h3>

<div className="flex items-center gap-6 text-sm text-slate-600 dark:text-slate-400">
<div className="flex items-center gap-6 text-sm text-soil-600 dark:text-soil-400">
<div>
Contributed:{' '}
<span className="font-semibold text-slate-900 dark:text-white">
<span className="font-semibold text-soil-900 dark:text-soil-50">
${investment.amountContributed.toLocaleString()}
</span>
</div>
<div>
Claimable:{' '}
<span className="font-semibold text-emerald-700 dark:text-emerald-400">
<span className="font-semibold text-leaf-700 dark:text-leaf-400">
${investment.claimableAmount.toLocaleString()}
</span>
</div>
Expand All @@ -89,15 +73,15 @@ export const InvestmentCard: React.FC<InvestmentCardProps> = ({

<div className="flex items-center justify-end">
{investment.claimed ? (
<span className="rounded-xl border border-slate-200 bg-slate-100 px-4 py-2 text-xs font-semibold text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400">
<span className="rounded-xl border border-soil-200 bg-soil-100 px-4 py-2 text-xs font-semibold text-soil-600 dark:border-soil-700 dark:bg-soil-800 dark:text-soil-400">
<span aria-hidden="true">✓</span> Claimed
</span>
) : isRefundable ? (
<button
type="button"
onClick={() => void handleClaim()}
disabled={claiming}
className="rounded-xl bg-amber-700 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-amber-800 disabled:opacity-50"
className="rounded-xl bg-status-harvested-dark px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-bark-800 disabled:opacity-50"
>
{claiming ? 'Claiming...' : 'Claim Refund'}
</button>
Expand All @@ -106,12 +90,12 @@ export const InvestmentCard: React.FC<InvestmentCardProps> = ({
type="button"
onClick={() => void handleClaim()}
disabled={claiming}
className="rounded-xl bg-emerald-700 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-emerald-800 disabled:opacity-50"
className="rounded-xl bg-leaf-600 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-leaf-700 disabled:opacity-50"
>
{claiming ? 'Claiming...' : 'Claim Return'}
</button>
) : (
<span className="text-xs italic text-slate-600 dark:text-slate-400">
<span className="text-xs italic text-soil-600 dark:text-soil-400">
No payout pending
</span>
)}
Expand Down
51 changes: 15 additions & 36 deletions client/src/components/investor/InvestorSummaryStats.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import type { PortfolioStats } from '../../lib/soroban/investorService';
import { StatTile } from '../analytics/StatTile';

export interface InvestorSummaryStatsProps {
stats: PortfolioStats;
Expand All @@ -11,42 +12,20 @@ export const InvestorSummaryStats: React.FC<InvestorSummaryStatsProps> = ({
totalCampaigns,
}) => {
return (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm">
<span className="text-xs font-medium text-slate-600 dark:text-slate-400 block uppercase tracking-wider">
Funded Projects
</span>
<span className="text-2xl font-bold text-slate-900 dark:text-white mt-1 block">
{totalCampaigns}
</span>
</div>

<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm">
<span className="text-xs font-medium text-slate-600 dark:text-slate-400 block uppercase tracking-wider">
Total Contributed
</span>
<span className="text-2xl font-bold text-slate-900 dark:text-white mt-1 block">
${stats.totalInvested.toLocaleString()}
</span>
</div>

<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm">
<span className="text-xs font-medium text-slate-600 dark:text-slate-400 block uppercase tracking-wider">
Claimable / Pending
</span>
<span className="text-2xl font-bold text-amber-600 dark:text-amber-400 mt-1 block">
${stats.totalPending.toLocaleString()}
</span>
</div>

<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl p-5 shadow-sm">
<span className="text-xs font-medium text-slate-600 dark:text-slate-400 block uppercase tracking-wider">
Total Claimed
</span>
<span className="text-2xl font-bold text-emerald-600 dark:text-emerald-400 mt-1 block">
${stats.totalClaimed.toLocaleString()}
</span>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
<StatTile label="Funded Projects" value={String(totalCampaigns)} />
<StatTile
label="Total Contributed"
value={`$${stats.totalInvested.toLocaleString()}`}
/>
<StatTile
label="Claimable / Pending"
value={`$${stats.totalPending.toLocaleString()}`}
/>
<StatTile
label="Total Claimed"
value={`$${stats.totalClaimed.toLocaleString()}`}
/>
</div>
);
};
Loading
Loading