From 34fa83a86f956cf678136dfc7803012c43f0e05a Mon Sep 17 00:00:00 2001 From: Kaycee276 Date: Mon, 29 Jun 2026 17:24:22 +0100 Subject: [PATCH 1/4] fix(frontend): remove stray Token label from handleWithdraw --- frontend/src/app/streams/[id]/page.tsx | 1 - package-lock.json | 20 ++++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/streams/[id]/page.tsx b/frontend/src/app/streams/[id]/page.tsx index d022102..c6544f6 100644 --- a/frontend/src/app/streams/[id]/page.tsx +++ b/frontend/src/app/streams/[id]/page.tsx @@ -216,7 +216,6 @@ export default function StreamDetailsPage() { toast.error("Please connect your wallet"); return; } - Token: setWithdrawing(true); try { await withdrawFromStream(session, { streamId: BigInt(streamId) }); diff --git a/package-lock.json b/package-lock.json index ab95bf5..8437f8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -526,7 +526,7 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -534,7 +534,7 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -562,7 +562,7 @@ }, "node_modules/@babel/parser": { "version": "7.29.7", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -614,7 +614,7 @@ }, "node_modules/@babel/types": { "version": "7.29.7", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -3600,7 +3600,7 @@ }, "node_modules/@types/react-dom": { "version": "19.2.3", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -8309,7 +8309,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8371,7 +8370,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8393,7 +8391,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8415,7 +8412,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8437,7 +8433,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8479,7 +8474,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8501,7 +8495,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8521,7 +8514,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8782,7 +8774,7 @@ }, "node_modules/magicast": { "version": "0.3.5", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.4", From 9022440b51dbcb6ecb203c38159974c415acc716 Mon Sep 17 00:00:00 2001 From: Kaycee276 Date: Tue, 30 Jun 2026 13:24:47 +0100 Subject: [PATCH 2/4] refactor: delete unused stream details page component and clean up logger exports --- frontend/src/app/streams/[id]/page.tsx | 585 ------------------------- frontend/src/lib/logger.ts | 8 +- 2 files changed, 4 insertions(+), 589 deletions(-) diff --git a/frontend/src/app/streams/[id]/page.tsx b/frontend/src/app/streams/[id]/page.tsx index d470158..d7a4f34 100644 --- a/frontend/src/app/streams/[id]/page.tsx +++ b/frontend/src/app/streams/[id]/page.tsx @@ -1,588 +1,3 @@ -"use client"; - -import { useEffect, useState, useCallback, useMemo } from "react"; -import { useParams } from "next/navigation"; -import Link from "next/link"; -import { ArrowLeft, Pause, Play, X, Plus, Download, AlertTriangle } from "lucide-react"; -import { Button } from "@/components/ui/Button"; -import toast from "react-hot-toast"; -import { useWallet } from "@/context/wallet-context"; -import { useStreamEvents } from "@/hooks/useStreamEvents"; -import { - withdrawFromStream, - cancelStream, - topUpStream, - pauseStream, - resumeStream, - toBaseUnits, - toSorobanErrorMessage, -} from "@/lib/soroban"; -import { CancelConfirmModal } from "@/components/stream-creation/CancelConfirmModal"; -import type { BackendStreamEvent } from "@/lib/api-types"; -import { formatAmount, streamProgressPercent } from "@/utils/amount"; -import { shortenPublicKey } from "@/lib/wallet"; - -interface StreamDetail { - id: string; - streamId: number; - sender: string; - recipient: string; - tokenAddress: string; - tokenSymbol?: string; - depositedAmount: string; - withdrawnAmount: string; - ratePerSecond: string; - startTime: number; - endTime?: number; - lastUpdateTime: number; - isActive: boolean; - status: string; - isPaused?: boolean; - pausedAt?: string; - createdAt: string; - updatedAt: string; -} - -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/v1"; -const EVENTS_PER_PAGE = 10; - -// Token symbol mapping -const TOKEN_SYMBOLS: Record = { - "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCN": "XLM", - "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA": "USDC", - "CCWAMYJME4YOIUNAKVYEBYOG5I65QMKEX2NMN4OJAPXRPIF24ONPSHY": "EURC", -}; - -// Event type styling -const EVENT_STYLES: Record = { - CREATED: { color: "#22c55e", icon: "✓", label: "Created" }, - TOPPED_UP: { color: "#3b82f6", icon: "+", label: "Topped Up" }, - WITHDRAWN: { color: "#8b5cf6", icon: "↓", label: "Withdrawn" }, - CANCELLED: { color: "#ef4444", icon: "×", label: "Cancelled" }, - COMPLETED: { color: "#10b981", icon: "✓", label: "Completed" }, - PAUSED: { color: "#f59e0b", icon: "⏸", label: "Paused" }, - RESUMED: { color: "#06b6d4", icon: "▶", label: "Resumed" }, - FEE_COLLECTED: { color: "#6b7280", icon: "$", label: "Fee" }, - FEE_CONFIG_UPDATED: { color: "#475569", icon: "⚙", label: "Fee Config" }, - ADMIN_TRANSFERRED: { color: "#475569", icon: "👤", label: "Admin Transfer" }, -}; - -export default function StreamDetailsPage() { - const params = useParams(); - const streamId = params.id as string; - const { session, isHydrated } = useWallet(); - - const [stream, setStream] = useState(null); - const [events, setEvents] = useState([]); - const [eventsPage, setEventsPage] = useState(1); - const [eventsTotal, setEventsTotal] = useState(0); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - // Action states - const [withdrawing, setWithdrawing] = useState(false); - const [cancelling, setCancelling] = useState(false); - const [pausing, setPausing] = useState(false); - const [resuming, setResuming] = useState(false); - const [topUpAmount, setTopUpAmount] = useState(""); - const [showTopUp, setShowTopUp] = useState(false); - const [showCancelModal, setShowCancelModal] = useState(false); - - // Live claimable counter - const [liveClaimable, setLiveClaimable] = useState(0n); - - // SSE integration - const { events: streamEvents } = useStreamEvents({ - streamIds: [streamId], - autoReconnect: true, - }); - - // Fetch stream data - const fetchStream = useCallback(async (signal?: AbortSignal) => { - if (!streamId) return; - try { - const response = await fetch(`${API_BASE_URL}/streams/${streamId}`, { signal }); - if (!response.ok) throw new Error("Stream not found"); - const data = await response.json(); - setStream(data); - } catch (err) { - if (err instanceof Error && err.name === "AbortError") return; - setError(err instanceof Error ? err.message : "Failed to fetch stream"); - } - }, [streamId]); - - // Fetch events - const fetchEvents = useCallback(async (page: number, signal?: AbortSignal) => { - if (!streamId) return; - try { - const response = await fetch( - `${API_BASE_URL}/streams/${streamId}/events?page=${page}&limit=${EVENTS_PER_PAGE}`, - { signal } - ); - if (response.ok) { - const data = await response.json(); - setEvents(data.events || []); - setEventsTotal(data.total || 0); - } - } catch (err) { - if (err instanceof Error && err.name === "AbortError") return; - console.error("Failed to fetch events:", err); - } - }, [streamId]); - - // Initial load - useEffect(() => { - if (!isHydrated) return; - - const controller = new AbortController(); - - const loadData = async () => { - setLoading(true); - await Promise.all([fetchStream(controller.signal), fetchEvents(1, controller.signal)]); - setLoading(false); - }; - - loadData(); - - return () => controller.abort(); - }, [isHydrated, fetchStream, fetchEvents]); - - // Handle SSE events - useEffect(() => { - const controller = new AbortController(); - - const refreshStreamData = async () => { - if (streamEvents.length > 0) { - await Promise.all([fetchStream(controller.signal), fetchEvents(eventsPage, controller.signal)]); - } - }; - - refreshStreamData(); - - return () => controller.abort(); - }, [streamEvents, fetchStream, fetchEvents, eventsPage]); - - // Live claimable counter - useEffect(() => { - if (!stream) return; - - const ratePerSecond = BigInt(stream.ratePerSecond); - const withdrawn = BigInt(stream.withdrawnAmount); - const deposited = BigInt(stream.depositedAmount); - const lastUpdate = stream.lastUpdateTime; - - const updateClaimable = () => { - if (!stream.isActive || stream.isPaused) { - // Use the server's calculated value when stream is not active - setLiveClaimable(deposited - withdrawn); - return; - } - - const now = Math.floor(Date.now() / 1000); - const elapsed = BigInt(now - lastUpdate); - const accrued = elapsed * ratePerSecond; - const totalClaimable = deposited - withdrawn + accrued; - - // Cap at deposited amount - setLiveClaimable(totalClaimable > deposited ? deposited : totalClaimable); - }; - - updateClaimable(); - const interval = setInterval(updateClaimable, 1000); - - return () => clearInterval(interval); - }, [stream]); - - // User roles - const isSender = useMemo(() => { - if (!session || !stream) return false; - return session.publicKey === stream.sender; - }, [session, stream]); - - const isRecipient = useMemo(() => { - if (!session || !stream) return false; - return session.publicKey === stream.recipient; - }, [session, stream]); - - // Token symbol - const tokenSymbol = useMemo(() => { - if (!stream) return "??"; - return TOKEN_SYMBOLS[stream.tokenAddress] || stream.tokenAddress.slice(0, 4); - }, [stream]); - - // Handlers - const handleWithdraw = async () => { - if (!session) { - toast.error("Please connect your wallet"); - return; - } - setWithdrawing(true); - try { - await withdrawFromStream(session, { streamId: BigInt(streamId) }); - toast.success("Withdrawal successful!"); - await fetchStream(); - } catch (err) { - toast.error(toSorobanErrorMessage(err)); - } finally { - setWithdrawing(false); - } - }; - - const handleTopUp = async () => { - if (!session) { - toast.error("Please connect your wallet"); - return; - } - if (!topUpAmount || parseFloat(topUpAmount) <= 0) { - toast.error("Please enter a valid amount"); - return; - } - try { - const amount = toBaseUnits(topUpAmount); - await topUpStream(session, { streamId: BigInt(streamId), amount }); - toast.success("Stream topped up successfully!"); - setShowTopUp(false); - setTopUpAmount(""); - await fetchStream(); - } catch (err) { - toast.error(toSorobanErrorMessage(err)); - } - }; - - const handlePause = async () => { - if (!session) { - toast.error("Please connect your wallet"); - return; - } - setPausing(true); - try { - await pauseStream(session, { streamId: BigInt(streamId) }); - toast.success("Stream paused"); - await fetchStream(); - } catch (err) { - toast.error(toSorobanErrorMessage(err)); - } finally { - setPausing(false); - } - }; - - const handleResume = async () => { - if (!session) { - toast.error("Please connect your wallet"); - return; - } - setResuming(true); - try { - await resumeStream(session, { streamId: BigInt(streamId) }); - toast.success("Stream resumed"); - await fetchStream(); - } catch (err) { - toast.error(toSorobanErrorMessage(err)); - } finally { - setResuming(false); - } - }; - - const handleCancel = async () => { - if (!session) { - toast.error("Please connect your wallet"); - return; - } - setCancelling(true); - try { - await cancelStream(session, { streamId: BigInt(streamId) }); - toast.success("Stream cancelled"); - setShowCancelModal(false); - await fetchStream(); - } catch (err) { - toast.error(toSorobanErrorMessage(err)); - } finally { - setCancelling(false); - } - }; - - const totalPages = Math.ceil(eventsTotal / EVENTS_PER_PAGE); - - if (loading) { - return ( -
-
-
-
-

Loading stream details...

-
-
-
- ); - } - - if (error || !stream) { - return ( -
-
-
- -

{error || "Stream not found"}

- - ← Back to Dashboard - -
-
-
- ); - } - - const deposited = BigInt(stream.depositedAmount); - const withdrawn = BigInt(stream.withdrawnAmount); - const ratePerSecond = BigInt(stream.ratePerSecond); - const progressPercent = streamProgressPercent(withdrawn, deposited); - - return ( -
-
- {/* Header */} -
- - - -
-

Stream #{stream.streamId}

-

Stream Details

-
-
- -
-
- - {/* Stream Overview */} -
-
-
- - - -
-
- - - -
-
-
- - {/* Financial Overview */} -
- - - -
- - {/* Progress */} -
-

Stream Progress

-
-
-
-

- {formatAmount(withdrawn, 7)} / {formatAmount(deposited, 7)} {tokenSymbol} withdrawn -

-
- - {/* Actions */} - {(isSender || isRecipient) && stream.isActive && ( -
-

Actions

-
- {/* Recipient: Withdraw */} - {isRecipient && ( - - )} - - {/* Sender: Top Up */} - {isSender && ( - - )} - - {/* Sender: Pause/Resume */} - {isSender && ( - <> - {!stream.isPaused ? ( - - ) : ( - - )} - - )} - - {/* Sender: Cancel */} - {isSender && ( - - )} -
- - {/* Top Up Input */} - {showTopUp && ( -
- setTopUpAmount(e.target.value)} - className="flex-1 px-4 py-2 rounded-lg bg-black/40 border border-white/10 focus:border-accent outline-none" - /> - -
- )} -
- )} - - {/* Event History */} -
-

Event History

- - {events.length === 0 ? ( -

No events yet

- ) : ( - <> -
- {events.map((event) => ( - - ))} -
- - {/* Pagination */} - {totalPages > 1 && ( -
- - - Page {eventsPage} of {totalPages} - - -
- )} - - )} -
-
- - {/* Cancel Confirmation Modal */} - {showCancelModal && stream && ( - setShowCancelModal(false)} - /> - )} -
- ); -} - -// Helper Components -function StatusBadge({ status, isPaused }: { status: string; isPaused?: boolean }) { - const getStyles = () => { - if (isPaused) return "bg-yellow-500/20 text-yellow-400 border-yellow-500/30"; - switch (status.toLowerCase()) { - case "active": - return "bg-green-500/20 text-green-400 border-green-500/30"; - case "completed": - return "bg-blue-500/20 text-blue-400 border-blue-500/30"; - case "cancelled": - return "bg-red-500/20 text-red-400 border-red-500/30"; - default: - return "bg-slate-500/20 text-slate-400 border-slate-500/30"; - } import type { Metadata } from "next"; import StreamDetailsContent from "./stream-details-content"; diff --git a/frontend/src/lib/logger.ts b/frontend/src/lib/logger.ts index 496bfae..0d1ad79 100644 --- a/frontend/src/lib/logger.ts +++ b/frontend/src/lib/logger.ts @@ -2,16 +2,16 @@ const isDev = process.env.NODE_ENV !== "production"; export const logger = { debug: (...args: unknown[]) => { - if (isDev) console.debug(...args); // eslint-disable-line no-console + if (isDev) console.debug(...args); }, info: (...args: unknown[]) => { - if (isDev) console.info(...args); // eslint-disable-line no-console + if (isDev) console.info(...args); }, warn: (...args: unknown[]) => { - if (isDev) console.warn(...args); // eslint-disable-line no-console + if (isDev) console.warn(...args); }, // errors always surface, even in production error: (...args: unknown[]) => { - console.error(...args); // eslint-disable-line no-console + console.error(...args); }, }; From adb4a17b3b095c531ac7c6ce6b108f6a6ad3b118 Mon Sep 17 00:00:00 2001 From: Kaycee276 Date: Tue, 30 Jun 2026 13:33:38 +0100 Subject: [PATCH 3/4] feat: export Prisma namespace from prisma client for type availability --- backend/src/lib/prisma.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/lib/prisma.ts b/backend/src/lib/prisma.ts index 0ea5168..8fd8a53 100644 --- a/backend/src/lib/prisma.ts +++ b/backend/src/lib/prisma.ts @@ -1,7 +1,8 @@ import pg from 'pg'; import { PrismaPg } from '@prisma/adapter-pg'; -import { PrismaClient } from '../generated/prisma/index.js'; +import { PrismaClient, Prisma } from '../generated/prisma/index.js'; +export { Prisma }; const globalForPrisma = global as unknown as { prisma?: PrismaClient; pool?: pg.Pool; From 16bcfafeaf7ed4b229160fa294c5b121f6fb1c72 Mon Sep 17 00:00:00 2001 From: Kaycee276 Date: Tue, 30 Jun 2026 13:41:30 +0100 Subject: [PATCH 4/4] fix: type-only import for Prisma namespace to fix CI verbatimModuleSyntax error --- backend/src/lib/prisma.ts | 4 +--- backend/src/workers/soroban-event-worker.ts | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/lib/prisma.ts b/backend/src/lib/prisma.ts index 8fd8a53..d18c290 100644 --- a/backend/src/lib/prisma.ts +++ b/backend/src/lib/prisma.ts @@ -1,8 +1,6 @@ import pg from 'pg'; import { PrismaPg } from '@prisma/adapter-pg'; -import { PrismaClient, Prisma } from '../generated/prisma/index.js'; - -export { Prisma }; +import { PrismaClient } from '../generated/prisma/index.js'; const globalForPrisma = global as unknown as { prisma?: PrismaClient; pool?: pg.Pool; diff --git a/backend/src/workers/soroban-event-worker.ts b/backend/src/workers/soroban-event-worker.ts index e4e65f8..6c9b9aa 100644 --- a/backend/src/workers/soroban-event-worker.ts +++ b/backend/src/workers/soroban-event-worker.ts @@ -1,5 +1,6 @@ import { rpc, xdr, StrKey } from '@stellar/stellar-sdk'; -import { prisma, Prisma } from '../lib/prisma.js'; +import { prisma } from '../lib/prisma.js'; +import type { Prisma } from '../generated/prisma/index.js'; import { INDEXER_STATE_ID } from '../lib/indexer-state.js'; import { sseService } from '../services/sse.service.js'; import logger from '../logger.js';