diff --git a/frontend/components/dashboard/StreamDetailsModal.tsx b/frontend/components/dashboard/StreamDetailsModal.tsx new file mode 100644 index 0000000..0686a89 --- /dev/null +++ b/frontend/components/dashboard/StreamDetailsModal.tsx @@ -0,0 +1,145 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { Button } from "@/components/ui/Button"; +import type { Stream } from "@/lib/dashboard"; +import { shortenPublicKey } from "@/lib/wallet"; + +interface StreamDetailsModalProps { + stream: Stream; + onClose: () => void; + onCancelClick: () => void; + onTopUpClick: () => void; +} + +export const StreamDetailsModal: React.FC = ({ + stream, + onClose, + onCancelClick, + onTopUpClick, +}) => { + // Escape key support + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handleEscape); + return () => window.removeEventListener("keydown", handleEscape); + }, [onClose]); + + const progress = (stream.withdrawn / stream.deposited) * 100; + const remaining = stream.deposited - stream.withdrawn; + + return ( +
{ + if (e.target === e.currentTarget) onClose(); + }} + > +
+ {/* Header */} +
+
+

Stream Details

+

ID: {stream.id}

+
+ +
+ +
+ {/* Main Info */} +
+
+ +
+ {stream.recipient} + +
+
+ +
+
+ + + {stream.status} + +
+
+ + {stream.token} +
+
+ +
+ + +
+ {stream.withdrawn} + of {stream.deposited} {stream.token} +
+ +
+
+
+ +

+ {remaining} {stream.token} remaining to be streamed +

+
+
+ + {/* Actions & Meta */} +
+
+ +

{stream.date}

+
+ +
+

Actions

+ + +
+ +
+ Note: Cancelling a stream will return any unspent funds ({remaining} {stream.token}) to your wallet. This action cannot be undone. +
+
+
+
+
+ ); +}; diff --git a/frontend/components/dashboard/dashboard-view.tsx b/frontend/components/dashboard/dashboard-view.tsx index e0e0082..e3f147e 100644 --- a/frontend/components/dashboard/dashboard-view.tsx +++ b/frontend/components/dashboard/dashboard-view.tsx @@ -40,6 +40,7 @@ import { } from "../stream-creation/StreamCreationWizard"; import { TopUpModal } from "../stream-creation/TopUpModal"; import { CancelConfirmModal } from "../stream-creation/CancelConfirmModal"; +import { StreamDetailsModal } from "./StreamDetailsModal"; import { Button } from "../ui/Button"; // ─── Types ──────────────────────────────────────────────────────────────────── @@ -58,7 +59,8 @@ interface SidebarItem { type ModalState = | null | { type: "topup"; stream: Stream } - | { type: "cancel"; stream: Stream }; + | { type: "cancel"; stream: Stream } + | { type: "details"; stream: Stream }; interface StreamFormValues { recipient: string; @@ -197,6 +199,7 @@ function renderStreams( snapshot: DashboardSnapshot | null, onTopUp: (stream: Stream) => void, onCancel: (stream: Stream) => void, + onShowDetails: (stream: Stream) => void, ) { if (!snapshot) return null; return ( @@ -220,7 +223,15 @@ function renderStreams( {snapshot.outgoingStreams .filter((s) => s.status === "Active") .map((stream) => ( - + { + // Prevent row click if clicking buttons + if ((e.target as HTMLElement).closest('button')) return; + onShowDetails(stream); + }} + > {stream.date} {stream.recipient} @@ -622,7 +633,12 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) {
{renderStats(snapshot)} {renderAnalytics(snapshot)} - {renderStreams(snapshot, (stream: Stream) => setModal({ type: "topup", stream }), (stream: Stream) => setModal({ type: "cancel", stream }))} + {renderStreams( + snapshot, + (stream: Stream) => setModal({ type: "topup", stream }), + (stream: Stream) => setModal({ type: "cancel", stream }), + (stream: Stream) => setModal({ type: "details", stream }) + )} {renderRecentActivity(snapshot)}
); @@ -911,6 +927,18 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) { /> ) } + + {/* Stream Details Modal */} + { + modal?.type === "details" && ( + setModal(null)} + onCancelClick={() => setModal({ type: "cancel", stream: modal.stream })} + onTopUpClick={() => setModal({ type: "topup", stream: modal.stream })} + /> + ) + } ); }