Skip to content
Merged
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
142 changes: 142 additions & 0 deletions frontend/app/streams/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@

import { notFound } from "next/navigation";
import LiveCounter from "@/components/Livecounter";
import ProgressBar from "@/components/Progressbar";


interface Transaction {
id: string;
date: string;
amount: number;
type: "withdrawal";
}

interface Stream {
id: string;
name: string;
recipient: string;
streamedAmount: number;
totalAmount: number;
transactions: Transaction[];
}

const MOCK_STREAMS: Record<string, Stream> = {
"1": {
id: "1",
name: "Developer Grant — Q1",
recipient: "0xAbC…1234",
streamedAmount: 3200,
totalAmount: 5000,
transactions: [
{ id: "tx1", date: "2026-02-20", amount: 1000, type: "withdrawal" },
{ id: "tx2", date: "2026-02-18", amount: 1200, type: "withdrawal" },
{ id: "tx3", date: "2026-02-15", amount: 1000, type: "withdrawal" },
],
},
"2": {
id: "2",
name: "Marketing Budget Stream",
recipient: "0xDeF…5678",
streamedAmount: 800,
totalAmount: 2000,
transactions: [
{ id: "tx1", date: "2026-02-21", amount: 500, type: "withdrawal" },
{ id: "tx2", date: "2026-02-19", amount: 300, type: "withdrawal" },
],
},
};

interface PageProps {
params: Promise<{ id: string }>;
}

export default async function StreamDetailsPage({ params }: PageProps) {
const { id } = await params;
const stream = MOCK_STREAMS[id];

if (!stream) notFound();

const percentage = Math.round((stream.streamedAmount / stream.totalAmount) * 100);

return (
<main style={{ minHeight: "100vh", padding: "clamp(1rem, 3vw, 2rem)" }}>
<div style={{ maxWidth: 720, margin: "0 auto", display: "grid", gap: "1rem" }}>

{/* Header */}
<div>
<p className="kicker">Stream #{stream.id}</p>
<h1 style={{ margin: "0.4rem 0 0", fontSize: "clamp(1.6rem, 3vw, 2.2rem)", lineHeight: 1.1 }}>
Stream Details
</h1>
</div>

{/* Identity card */}
<div className="dashboard-panel">
<h2 style={{ margin: "0 0 0.4rem", fontSize: "1.15rem" }}>{stream.name}</h2>
<p style={{ margin: 0, color: "var(--text-muted)" }}>
Recipient:{" "}
<code
style={{
background: "rgba(19,38,61,0.07)",
borderRadius: "0.4rem",
padding: "0.15rem 0.45rem",
fontSize: "0.85rem",
fontFamily: "var(--font-mono, monospace)",
}}
>
{stream.recipient}
</code>
</p>
</div>

{/* 1️⃣ Progress bar */}
<div className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>Streamed Progress</h3>
</div>
<ProgressBar
percentage={percentage}
label={`${stream.streamedAmount.toLocaleString()} / ${stream.totalAmount.toLocaleString()} tokens`}
/>
</div>

{/* 3️⃣ Live counter */}
<div className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>Live Balance</h3>
</div>
<LiveCounter initial={stream.streamedAmount} label="Accumulated balance" />
</div>

{/* 2️⃣ Transaction history */}
<div className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>Transaction History</h3>
<span>{stream.transactions.length} withdrawals</span>
</div>

{stream.transactions.length === 0 ? (
<div className="mini-empty-state">
<p>No transactions yet.</p>
</div>
) : (
<ul className="activity-list">
{stream.transactions.map((tx) => (
<li key={tx.id} className="activity-item">
<div>
<strong>Withdrawal</strong>
<p>{tx.date}</p>
</div>
<span className="is-negative">
-{tx.amount.toLocaleString()} tokens
</span>
</li>
))}
</ul>
)}
</div>

</div>
</main>
);
}
67 changes: 67 additions & 0 deletions frontend/components/Livecounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";

import { useEffect, useState } from "react";

interface LiveCounterProps {
initial: number;
label?: string;
}

export default function LiveCounter({ initial, label = "Live Streamed" }: LiveCounterProps) {
const [amount, setAmount] = useState(initial);

useEffect(() => {
const interval = setInterval(() => {
setAmount((prev) => prev + 1);
}, 1000);

return () => clearInterval(interval);
}, []);

return (
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
<span
style={{
position: "relative",
display: "inline-flex",
width: "0.75rem",
height: "0.75rem",
}}
>
<span
style={{
position: "absolute",
inset: 0,
borderRadius: "999px",
background: "#10b981",
opacity: 0.75,
animation: "pulse-slow 4s cubic-bezier(0.4, 0, 0.6, 1) infinite",
}}
/>
<span
style={{
position: "relative",
display: "inline-flex",
width: "0.75rem",
height: "0.75rem",
borderRadius: "999px",
background: "#10b981",
}}
/>
</span>

<p style={{ margin: 0, fontSize: "0.92rem", color: "var(--text-muted)" }}>
{label}:{" "}
<strong
style={{
fontSize: "1rem",
color: "var(--text-main)",
fontVariantNumeric: "tabular-nums",
}}
>
{amount.toLocaleString()}
</strong>
</p>
</div>
);
}
52 changes: 52 additions & 0 deletions frontend/components/Progressbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
interface ProgressBarProps {
percentage: number;
label?: string;
}

export default function ProgressBar({ percentage, label }: ProgressBarProps) {
const clamped = Math.min(100, Math.max(0, percentage));

return (
<div style={{ display: "grid", gap: "0.5rem" }}>
{label && (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
fontSize: "0.88rem",
color: "var(--text-muted)",
}}
>
<span>{label}</span>
<span style={{ fontWeight: 600, color: "var(--text-main)" }}>{clamped}%</span>
</div>
)}

{/* Track */}
<div
role="progressbar"
aria-valuenow={clamped}
aria-valuemin={0}
aria-valuemax={100}
style={{
width: "100%",
height: "0.6rem",
background: "rgba(19,38,61,0.1)",
borderRadius: "999px",
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${clamped}%`,
background: "linear-gradient(90deg, var(--accent-strong), var(--wallet-accent))",
borderRadius: "999px",
transition: "width 500ms ease",
}}
/>
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-hot-toast": "^2.6.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
Expand All @@ -28,4 +29,4 @@
"tailwindcss": "^4",
"typescript": "^5"
}
}
}
Loading
Loading