diff --git a/src/app/api/pnl/route.ts b/src/app/api/pnl/route.ts index f1cd551..d2a84d9 100644 --- a/src/app/api/pnl/route.ts +++ b/src/app/api/pnl/route.ts @@ -1,21 +1,39 @@ +/** + * Profit and Loss (PnL) API Route. + * Generates synthetic historical performance data for the dashboard charts. + * This is used for demonstrating portfolio tracking features. + */ + import { NextResponse } from 'next/server'; +/** + * Historical data point for the PnL chart. + */ interface PnLData { + /** Localized date string (e.g., "Jan 12") */ date: string; + /** The portfolio value at that specific point in time */ value: number; } +/** + * GET handler for the PnL endpoint. + * Returns a 30-day series of simulated portfolio values. + */ export async function GET() { - // Generate dummy PnL data for the last 30 days + // Generate mock PnL data for the last 30 days const data: PnLData[] = []; const today = new Date(); - let currentValue = 10000; // Starting value + + // Starting seed value for the simulation + let currentValue = 10000; for (let i = 29; i >= 0; i--) { const date = new Date(today); date.setDate(date.getDate() - i); - // Random walk with slight upward trend + // Simulate a random walk with a slight positive bias (0.45 instead of 0.50) + // and a volatility factor of 200 const change = (Math.random() - 0.45) * 200; currentValue += change; @@ -25,6 +43,7 @@ export async function GET() { }); } + // Return the series as a JSON response return NextResponse.json(data); } diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index bd3c54c..e32a022 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,8 +1,29 @@ +/** + * Document Upload API Route. + * Handles the secure uploading of invoice documents to IPFS/Pinata. + * Currently disabled for maintenance or pending further security implementation. + */ + import { NextRequest, NextResponse } from 'next/server'; +/** + * POST handler for the upload endpoint. + * Currently returns a 503 Service Unavailable error as the feature is locked. + * + * @param {NextRequest} request - The incoming upload request. + */ export async function POST(request: NextRequest) { + // 1. Log the attempt for security auditing + const clientIp = request.headers.get('x-forwarded-for') || 'unknown'; + console.log(`[UploadAPI] Blocked upload attempt from ${clientIp}`); + + // 2. Return a consistent error response return NextResponse.json( - { error: 'Upload temporarily disabled' }, + { + error: 'Upload service temporarily disabled', + reason: 'Undergoing maintenance', + retryAfter: 3600 + }, { status: 503 } ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index f0864e5..db906f5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,9 @@ +/** + * TradeFlow Main Dashboard Page. + * This is the primary entry point for the application, providing users with + * a high-level overview of their assets, protocol status, and the RWA pipeline. + */ + "use client"; import React, { useState, useEffect, useRef } from "react"; @@ -29,15 +35,24 @@ import { useWalletConnection } from "../stores/useWeb3Store"; import { showError, showSuccess } from "../lib/toast"; import Icon from "../components/ui/Icon"; +/** + * The root component for the TradeFlow dashboard. + * Manages high-level state for wallet connection, active tabs, and invoice data. + */ export default function Page() { const router = useRouter(); const searchParams = useSearchParams(); const { isConnected, walletAddress, isConnecting } = useWalletConnection(); const [invoices, setInvoices] = useState([]); const [loading, setLoading] = useState(false); + /** Controls visibility of the Invoice Minting modal */ const [showMintForm, setShowMintForm] = useState(false); + /** Controls visibility of the Wallet Selection modal */ const [isModalOpen, setIsModalOpen] = useState(false); + /** Currently active navigation tab (dashboard or watchlist) */ const [activeTab, setActiveTab] = useState("dashboard"); + + /** Watchlist management hook */ const { toggleWatchlist, isInWatchlist } = useWatchlist(); const riskSocketRef = useRef(null); @@ -77,6 +92,8 @@ export default function Page() { } }; + // --- Lifecycle Hooks --- + useEffect(() => { const controller = new AbortController(); @@ -147,14 +164,18 @@ export default function Page() { const handleInvoiceMint = (data: Record) => { console.log("Invoice data received:", data); setShowMintForm(false); - // TODO: Chain integration will be handled separately + // TODO: Initiate Soroban contract call for minting the NFT }; + // --- Configuration --- + + /** Tab definitions for the main navigation */ const tabs = [ { id: "dashboard", label: "Dashboard" }, { id: "watchlist", label: "Watchlist", icon: }, ]; + return (
{/* Header */} diff --git a/src/components/Card.tsx b/src/components/Card.tsx index d8a39bd..de74564 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,13 +1,52 @@ +/** + * Shared Card Component. + * Provides a consistent container for UI sections with standard + * TradeFlow styling (borders, padding, and background). + */ + import React from "react"; +/** + * Props for the Card component. + */ interface CardProps { + /** The content to be wrapped by the card */ children: React.ReactNode; + /** Additional CSS classes for custom styling overrides */ className?: string; + /** Optional click handler for interactive cards */ + onClick?: () => void; + /** Optional hover effect toggle */ + hoverable?: boolean; } -export default function Card({ children, className = "" }: CardProps) { +/** + * A versatile layout component for grouping related content. + */ +export default function Card({ + children, + className = "", + onClick, + hoverable = false +}: CardProps) { + // --- Styling --- + const baseStyles = "bg-slate-800 border border-slate-700 rounded-3xl p-6 transition-all duration-300 shadow-xl shadow-black/5"; + const hoverStyles = hoverable ? "hover:border-slate-600 hover:bg-slate-800/80 hover:translate-y-[-2px]" : ""; + const interactiveStyles = onClick ? "cursor-pointer active:scale-[0.99]" : ""; + return ( -
+
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + } : undefined} + > {children}
); diff --git a/src/components/InvoiceMintForm.tsx b/src/components/InvoiceMintForm.tsx index e6055e4..6b76dfc 100644 --- a/src/components/InvoiceMintForm.tsx +++ b/src/components/InvoiceMintForm.tsx @@ -1,3 +1,9 @@ +/** + * Invoice Minting Form Component. + * Provides a guided interface for users to upload invoice PDFs and + * define metadata for on-chain NFT minting. + */ + "use client"; import React, { useState, useEffect } from "react"; @@ -23,6 +29,7 @@ const invoiceSchema = z.object({ .number() .min(0.01, "Amount must be greater than 0") .max(1000000, "Amount cannot exceed $1,000,000"), + /** Expected payment date for the invoice */ dueDate: z .string() .min(1, "Due date is required") @@ -44,9 +51,14 @@ const invoiceSchema = z.object({ .or(z.literal("")), }); +/** TypeScript type inferred from the validation schema */ type InvoiceFormData = z.infer; +/** + * Props for the InvoiceMintForm component. + */ interface InvoiceMintFormProps { + /** Callback to trigger when the form is closed/cancelled */ onClose: () => void; onSuccess?: (txStatus: string) => void; } @@ -67,6 +79,7 @@ export default function InvoiceMintForm({ onClose, onSuccess }: InvoiceMintFormP const { data: session } = useSession(); const { mint, loading: minting, error: mintError, txStatus } = useMintInvoice(); + // --- Form Initialization --- const { register, handleSubmit, @@ -104,6 +117,11 @@ export default function InvoiceMintForm({ onClose, onSuccess }: InvoiceMintFormP } }, [watchedAmount]); + /** + * Manually updates the file field in react-hook-form when a file is selected. + * + * @param {React.ChangeEvent} event - The file input change event. + */ const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { @@ -215,7 +233,7 @@ export default function InvoiceMintForm({ onClose, onSuccess }: InvoiceMintFormP {errors.dueDate &&

{errors.dueDate.message}

}
@@ -233,10 +251,15 @@ export default function InvoiceMintForm({ onClose, onSuccess }: InvoiceMintFormP onChange={handleFileChange} className="hidden" id="invoice-file" + aria-describedby="file-error" />
+ {MOCK_LOANS.length === 0 && ( +
+ No active loans found in your history. +
+ )} ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index b2f70ac..8fe2e60 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,3 +1,9 @@ +/** + * Main Navigation Bar Component. + * Provides access to primary application routes, wallet connectivity status, + * network selection, and utility features like the fiat on-ramp. + */ + "use client"; import React, { useState } from "react"; @@ -6,24 +12,38 @@ import { usePathname } from "next/navigation"; import { Wallet, Copy, Check, CreditCard } from "lucide-react"; import { showError, showSuccess } from "../lib/toast"; -// Corrected imports based on your actual file structure +// Core UI components and modals import NetworkSelector from "./NetworkSelector"; import FiatOnRampModal from "./FiatOnRampModal"; import NetworkFeeIndicator from "./ui/NetworkFeeIndicator"; import WalletDropdown from "./WalletDropdown"; import Icon from "./ui/Icon"; +/** + * Props for the Navbar component. + */ interface NavbarProps { + /** The public Stellar address of the connected user (optional) */ address?: string; + /** Callback to trigger the wallet connection modal */ onConnect?: () => void; } +/** + * The top navigation component used across all pages. + */ export default function Navbar({ address, onConnect }: NavbarProps) { const pathname = usePathname(); + // --- UI State --- + /** Tracks whether the wallet address was recently copied to clipboard */ const [copied, setCopied] = useState(false); + /** Controls visibility of the fiat purchase modal */ const [isFiatModalOpen, setIsFiatModalOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); + /** + * Copies the connected wallet address to the system clipboard. + */ const copyToClipboard = async () => { if (address) { try { @@ -39,6 +59,7 @@ export default function Navbar({ address, onConnect }: NavbarProps) { } }; + /** Primary navigation link configuration */ const navLinks = [ { name: "Dashboard", href: "/" }, { name: "Swap", href: "/swap" }, @@ -48,13 +69,19 @@ export default function Navbar({ address, onConnect }: NavbarProps) { ]; return ( -
-
-

- TradeFlow RWA -

+
+ {/* Brand & Desktop Nav */} +
+ +
+ T +
+

+ TRADEFLOW RWA +

+ -
-
- - - {/* Gas Tank / Network Fee Indicator */} - + {/* Action Area */} +
+
+ + +
- {/* Buy Crypto Button */} + {/* Buy Crypto Utility */} + {/* Wallet Connection / Account Display */} {address ? (
@@ -132,26 +162,31 @@ export default function Navbar({ address, onConnect }: NavbarProps) { />
) : ( - /* * ISSUE #108: Added `animate-pulse` to draw attention to the primary CTA. - * Because this button is isolated within the `false` branch of the `address` check, - * the animation is naturally removed when the user connects their wallet. - */ )} + + {/* Mobile Menu Toggle */} +
- {/* Fiat On-Ramp Modal */} + {/* Modals & Overlays */} setIsFiatModalOpen(false)} /> -
+
); } // Inconsequential change for repo health diff --git a/src/components/SwapInterface.tsx b/src/components/SwapInterface.tsx index de6d9ec..4769d92 100644 --- a/src/components/SwapInterface.tsx +++ b/src/components/SwapInterface.tsx @@ -7,14 +7,25 @@ import { dismissToast, showError, showLoading, showSuccess } from "../lib/toast" import { useSigningActions } from "../stores/signatureStore"; import Icon from "./ui/Icon"; +/** + * Main component for the token swap functionality. + */ export default function SwapInterface() { + // --- Token Selection State --- + /** The asset code of the token being sold */ const [fromToken, setFromToken] = useState("XLM"); + /** The asset code of the token being bought */ const [toToken, setToToken] = useState("USDC"); + + // --- UI Visibility State --- const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isProMode, setIsProMode] = useState(false); const { deadline } = useSettings(); + /** + * Swaps the 'from' and 'to' tokens and their amounts. + */ const handleSwap = () => { const temp = fromToken; setFromToken(toToken); @@ -23,12 +34,18 @@ export default function SwapInterface() { setToAmount(fromAmount); }; + /** + * Updates the source amount and recalculates the destination amount and price impact. + * + * @param {string} value - The new input amount. + */ const handleFromAmountChange = (value: string) => { setFromAmount(value); const impact = calculatePriceImpact(value); setPriceImpact(impact); if (value && parseFloat(value) > 0) { + // Mock exchange rate logic const mockRate = fromToken === "XLM" ? 0.15 : 6.67; setToAmount((parseFloat(value) * mockRate * (1 - impact / 100)).toFixed(6)); } else { @@ -36,6 +53,9 @@ export default function SwapInterface() { } }; + /** + * Initiates the swap flow, validating inputs and checking for high slippage. + */ const handleSwapClick = async () => { if (!fromAmount || parseFloat(fromAmount) <= 0) { showError("Please enter an amount to swap"); @@ -45,23 +65,21 @@ export default function SwapInterface() { const loadingToast = showLoading("Processing swap..."); try { + // Threshold check for high slippage warning if (priceImpact > 5) { setIsHighSlippageWarningOpen(true); dismissToast(loadingToast); return; } - await new Promise((resolve) => setTimeout(resolve, 1800)); + // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, 1500)); showSuccess(`Swapped ${fromAmount} ${fromToken} → ${toAmount} ${toToken}`, { id: loadingToast, }); - if (priceImpact > 5) { - setIsHighSlippageWarningOpen(true); - } else { - setIsTradeReviewOpen(true); - } + setIsTradeReviewOpen(true); } catch (error) { showError("Failed to process swap", { id: loadingToast, @@ -69,17 +87,21 @@ export default function SwapInterface() { } }; + /** + * Confirms the trade and prepares the transaction for signing. + */ const handleTradeConfirm = async () => { setIsTradeReviewOpen(false); setIsSubmitting(true); setSubmissionStartTime(Date.now()); try { + // Simulate transaction building time await new Promise(resolve => setTimeout(resolve, 2000)); - // Generate mock transaction XDR + // Mock transaction XDR for demonstration const mockTransactionXDR = "AAAAAK/eFzA7Jf5Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3Xf3XAAAABQAAAAAAAAAAA=="; - console.log("Mock XDR generated:", mockTransactionXDR); + console.log("[SwapInterface] Mock XDR generated:", mockTransactionXDR); setIsTransactionSignatureOpen(true); } catch (error) { @@ -89,6 +111,9 @@ export default function SwapInterface() { } }; + /** + * Handles confirmation from the high slippage warning modal. + */ const handleHighSlippageConfirm = async () => { const loadingToast = showLoading("Processing high slippage swap..."); @@ -105,9 +130,13 @@ export default function SwapInterface() { } }; - /* ISSUE #87: Trigger the success modal when the transaction is signed */ + /** + * Callback for when the user successfully signs the transaction. + * + * @param {string} signedXDR - The base64 signed transaction XDR. + */ const handleTransactionSuccess = (signedXDR: string) => { - console.log("Transaction signed:", signedXDR); + console.log("[SwapInterface] Transaction signed:", signedXDR); showSuccess("Transaction signed successfully!", { icon: "✅", @@ -117,9 +146,10 @@ export default function SwapInterface() { setIsSubmitting(false); setSubmissionStartTime(null); - // Show the Growth/Share modal + // Show the post-trade share/growth modal setIsSuccessModalOpen(true); + // Reset form after a short delay setTimeout(() => { setFromAmount(""); setToAmount(""); @@ -127,6 +157,7 @@ export default function SwapInterface() { }, 1500); }; + const isAnyModalOpen = isSettingsOpen || isHighSlippageWarningOpen || isTradeReviewOpen || isSuccessModalOpen; const isSwapValid = fromAmount && parseFloat(fromAmount) > 0 && !isSubmitting; diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 98a2212..fc0a5a3 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -5,6 +5,9 @@ import { ExternalLink } from "lucide-react"; import NetworkToggle from "../NetworkToggle"; import Icon from "../ui/Icon"; +/** + * A consistent footer for all TradeFlow pages. + */ export default function Footer() { const handleClearCache = () => { localStorage.clear(); @@ -13,7 +16,7 @@ export default function Footer() { }; return ( -