diff --git a/frontend/src/app/activity/page.tsx b/frontend/src/app/activity/page.tsx index 4339af04..79b6d7e1 100644 --- a/frontend/src/app/activity/page.tsx +++ b/frontend/src/app/activity/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useWallet } from "@/context/wallet-context"; import { ActivityHistory } from "@/components/dashboard/ActivityHistory"; import { BackendStreamEvent } from "@/lib/api-types"; diff --git a/frontend/src/components/FAQ.tsx b/frontend/src/components/FAQ.tsx index 649ad3b3..195d6a4b 100644 --- a/frontend/src/components/FAQ.tsx +++ b/frontend/src/components/FAQ.tsx @@ -1,4 +1,4 @@ -import React from 'react'; + import { Card } from './ui/Card'; const faqs = [ diff --git a/frontend/src/components/Features.tsx b/frontend/src/components/Features.tsx index 175408ac..74a146a4 100644 --- a/frontend/src/components/Features.tsx +++ b/frontend/src/components/Features.tsx @@ -1,4 +1,4 @@ -import React from 'react'; + import { Card } from './ui/Card'; const featureList = [ diff --git a/frontend/src/components/Hero.tsx b/frontend/src/components/Hero.tsx index 925aaff7..1d33f846 100644 --- a/frontend/src/components/Hero.tsx +++ b/frontend/src/components/Hero.tsx @@ -1,4 +1,4 @@ -import React from 'react'; + import { Button } from './ui/Button'; import { MobileMockup } from './MobileMockup'; diff --git a/frontend/src/components/HowItWorks.tsx b/frontend/src/components/HowItWorks.tsx index 6df11696..0f4f80ab 100644 --- a/frontend/src/components/HowItWorks.tsx +++ b/frontend/src/components/HowItWorks.tsx @@ -1,4 +1,4 @@ -import React from 'react'; + const steps = [ { diff --git a/frontend/src/components/MobileMockup.tsx b/frontend/src/components/MobileMockup.tsx index 32f218f0..28464c37 100644 --- a/frontend/src/components/MobileMockup.tsx +++ b/frontend/src/components/MobileMockup.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; export const MobileMockup = () => { const [streamedAmount, setStreamedAmount] = useState(12540.00); diff --git a/frontend/src/components/Stats.tsx b/frontend/src/components/Stats.tsx index 9ed9a9c6..e5dc86a0 100644 --- a/frontend/src/components/Stats.tsx +++ b/frontend/src/components/Stats.tsx @@ -1,4 +1,4 @@ -import React from 'react'; + const stats = [ { label: 'Total Value Locked', value: '$240M+', gradient: 'text-gradient' }, diff --git a/frontend/src/components/dashboard/SSEStatusIndicator.tsx b/frontend/src/components/dashboard/SSEStatusIndicator.tsx index 0a6697f1..41f95613 100644 --- a/frontend/src/components/dashboard/SSEStatusIndicator.tsx +++ b/frontend/src/components/dashboard/SSEStatusIndicator.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { AlertCircle } from "lucide-react"; interface SSEStatusIndicatorProps { diff --git a/frontend/src/components/dashboard/dashboard-view.tsx b/frontend/src/components/dashboard/dashboard-view.tsx index f09a5f18..cf97aa71 100644 --- a/frontend/src/components/dashboard/dashboard-view.tsx +++ b/frontend/src/components/dashboard/dashboard-view.tsx @@ -454,13 +454,15 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) { React.useEffect(() => { if (streamEvents.length > 0) { const latestEvent = streamEvents[0]; - const relevantTypes = ["created", "topped_up", "withdrawn", "cancelled", "completed", "paused", "resumed"]; - if (relevantTypes.includes(latestEvent.type)) { - fetchDashboardData(session.publicKey) - .then(setSnapshot) - .catch((err) => { - setSnapshotError(err instanceof Error ? err.message : "Failed to refresh dashboard"); - }); + if (latestEvent) { + const relevantTypes = ["created", "topped_up", "withdrawn", "cancelled", "completed", "paused", "resumed"]; + if (relevantTypes.includes(latestEvent.type)) { + fetchDashboardData(session.publicKey) + .then(setSnapshot) + .catch((err) => { + setSnapshotError(err instanceof Error ? err.message : "Failed to refresh dashboard"); + }); + } } } }, [streamEvents, session.publicKey]); @@ -626,7 +628,7 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) { }; const addStreamLocally = (data: StreamFormData) => { - const newStream: Stream = { id: `stream-${Date.now()}`, date: new Date().toISOString().split("T")[0], recipient: shortenPublicKey(data.recipient), amount: parseFloat(data.amount), token: data.token, status: "Active", deposited: parseFloat(data.amount), withdrawn: 0, ratePerSecond: 0, lastUpdateTime: Math.floor(Date.now() / 1000), isActive: true }; + const newStream: Stream = { id: `stream-${Date.now()}`, date: new Date().toISOString().split("T")[0] ?? "", recipient: shortenPublicKey(data.recipient), amount: parseFloat(data.amount), token: data.token, status: "Active", deposited: parseFloat(data.amount), withdrawn: 0, ratePerSecond: 0, lastUpdateTime: Math.floor(Date.now() / 1000), isActive: true }; setSnapshot((prev) => { if (!prev) return prev; return { ...prev, outgoingStreams: [newStream, ...prev.outgoingStreams], activeStreamsCount: prev.activeStreamsCount + 1 }; }); }; diff --git a/frontend/src/components/streamCreationForm.test.tsx b/frontend/src/components/streamCreationForm.test.tsx index e47d9e4e..54739609 100644 --- a/frontend/src/components/streamCreationForm.test.tsx +++ b/frontend/src/components/streamCreationForm.test.tsx @@ -6,7 +6,7 @@ vi.mock("next/navigation", () => ({ })); import { StreamCreationWizard } from "./stream-creation/StreamCreationWizard"; -import React from "react"; + test("StreamCreationWizard — validation errors shown", () => { const mockOnClose = vi.fn(); diff --git a/frontend/src/components/streams/IncomingStreamCard.tsx b/frontend/src/components/streams/IncomingStreamCard.tsx index 59898556..0588d4fe 100644 --- a/frontend/src/components/streams/IncomingStreamCard.tsx +++ b/frontend/src/components/streams/IncomingStreamCard.tsx @@ -1,6 +1,5 @@ "use client"; -import React from "react"; import { Button } from "@/components/ui/Button"; import { useStreamingAmount } from "@/hooks/useStreamingAmount"; import type { diff --git a/frontend/src/components/ui/Skeleton.tsx b/frontend/src/components/ui/Skeleton.tsx index 3377e22f..31754be2 100644 --- a/frontend/src/components/ui/Skeleton.tsx +++ b/frontend/src/components/ui/Skeleton.tsx @@ -1,4 +1,4 @@ -import React from "react"; + export function Skeleton({ className = "" }: { className?: string }) { return ( diff --git a/frontend/src/components/wallet/WalletButton.tsx b/frontend/src/components/wallet/WalletButton.tsx index 16357719..d308fbf1 100644 --- a/frontend/src/components/wallet/WalletButton.tsx +++ b/frontend/src/components/wallet/WalletButton.tsx @@ -13,7 +13,7 @@ * - "Disconnect" button */ -import React, { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect } from "react"; import { useWallet } from "@/context/wallet-context"; import { shortenPublicKey, diff --git a/frontend/src/hooks/useModalDialog.ts b/frontend/src/hooks/useModalDialog.ts index 06b8545d..297d5cab 100644 --- a/frontend/src/hooks/useModalDialog.ts +++ b/frontend/src/hooks/useModalDialog.ts @@ -60,14 +60,16 @@ export function useModalDialog({ const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; - if (event.shiftKey && document.activeElement === firstElement) { - event.preventDefault(); - lastElement.focus(); - } + if (firstElement && lastElement) { + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + } - if (!event.shiftKey && document.activeElement === lastElement) { - event.preventDefault(); - firstElement.focus(); + if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } } }; diff --git a/frontend/src/lib/amount.ts b/frontend/src/lib/amount.ts index 56fddca8..efc22c19 100644 --- a/frontend/src/lib/amount.ts +++ b/frontend/src/lib/amount.ts @@ -94,7 +94,7 @@ export function hasValidPrecision(input: string, decimals: number): boolean { if (!/^\d*\.?\d*$/.test(cleanInput)) return false; if (cleanInput.includes('.')) { - const fractionalPart = cleanInput.split('.')[1]; + const fractionalPart = cleanInput.split('.')[1] ?? ''; return fractionalPart.length <= decimals; } diff --git a/frontend/src/lib/dashboard.ts b/frontend/src/lib/dashboard.ts index 3a771bce..cf5b2cdc 100644 --- a/frontend/src/lib/dashboard.ts +++ b/frontend/src/lib/dashboard.ts @@ -119,7 +119,7 @@ export function mapBackendStreamToFrontend(s: BackendStream, counterparty: strin status: mapStreamStatus(s), deposited, withdrawn, - date: new Date(s.startTime * 1000).toISOString().split("T")[0], + date: new Date(s.startTime * 1000).toISOString().split("T")[0] ?? "", ratePerSecond, lastUpdateTime: s.lastUpdateTime, isActive: s.isActive, diff --git a/frontend/src/lib/soroban.ts b/frontend/src/lib/soroban.ts index 81366846..a3911839 100644 --- a/frontend/src/lib/soroban.ts +++ b/frontend/src/lib/soroban.ts @@ -101,14 +101,14 @@ export function fromBaseUnits(value: bigint | string, decimals = 7): string { return fraction.length > 0 ? `${whole}.${fraction}` : whole.toString(); } -export const TOKEN_ADDRESSES: Record = { +export const TOKEN_ADDRESSES = { USDC: process.env.NEXT_PUBLIC_USDC_ADDRESS ?? "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA", XLM: process.env.NEXT_PUBLIC_XLM_ADDRESS ?? "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCN", EURC: process.env.NEXT_PUBLIC_EURC_ADDRESS ?? "CCWAMYJME4YOIUNAKVYEBYOG5I65QMKEX2NMN4OJAPXRPIF24ONPSHY", -}; +} as const; export function getTokenAddress(symbol: string): string { - const address = TOKEN_ADDRESSES[symbol.toUpperCase()]; + const address = (TOKEN_ADDRESSES as Record)[symbol.toUpperCase()]; if (!address) { throw new SorobanCallError(`Unsupported token: ${symbol}`, "Unknown"); } diff --git a/frontend/src/utils/csvExport.ts b/frontend/src/utils/csvExport.ts index 0b19e16a..6dc527a6 100644 --- a/frontend/src/utils/csvExport.ts +++ b/frontend/src/utils/csvExport.ts @@ -19,7 +19,9 @@ export const convertArrayToCSV = ( if (!arr || arr.length === 0) return ''; const separator = ','; - const keys = Object.keys(arr[0]); + const firstRow = arr[0]; + if (!firstRow) return ''; + const keys = Object.keys(firstRow); return [ keys.join(separator), diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 343beaf5..86b8d82c 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -7,6 +7,8 @@ "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, "noEmit": true, "esModuleInterop": true, "module": "esnext",