Skip to content
Open
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
66 changes: 66 additions & 0 deletions frontend/src/components/dashboard/DashboardActivity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { EmptyState } from "./dashboard-view";
import { ActivityIcon } from "./dashboard-view";
import { Button } from "@/components/ui/Button";

interface DashboardActivityProps {
recentActivity: any[];

Check failure on line 6 in frontend/src/components/dashboard/DashboardActivity.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
onCreateStream: () => void;
}

function formatActivityTime(timestamp: string): string {
const date = new Date(timestamp);
if (Number.isNaN(date.getTime())) return timestamp;
return new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
timeStyle: "short",
}).format(date);
}

export function DashboardActivity({
recentActivity,
onCreateStream,
}: DashboardActivityProps) {
if (recentActivity.length === 0) {
return (
<EmptyState
icon={<ActivityIcon />}
title="No stream activity yet"
description="Transactions will appear here once you start creating or receiving payment streams."
action={
<Button onClick={onCreateStream} variant="ghost">
Create a Stream
</Button>
}
/>
);
}

return (
<section className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>Recent Activity</h3>
<span>{recentActivity.length} items</span>
</div>
<ul className="activity-list">
{// @ts-expect-error activity uses any for dynamic stream data
recentActivity.map((activity: any) => {

Check failure on line 46 in frontend/src/components/dashboard/DashboardActivity.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
const amountPrefix = activity.direction === "received" ? "+" : "-";
const amountClass = activity.direction === "received" ? "is-positive" : "is-negative";
return (
<li key={activity.id} className="activity-item">
<div>
<strong>{activity.title}</strong>
<p>{activity.description}</p>
<small>{formatActivityTime(activity.timestamp)}</small>
</div>
<span className={amountClass}>
{amountPrefix}
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(activity.amount)}
</span>
</li>
);
})}
</ul>
</section>
);
}
36 changes: 36 additions & 0 deletions frontend/src/components/dashboard/DashboardIncoming.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import IncomingStreams from "../IncomingStreams";
import type { Stream } from "@/lib/dashboard";
import { EmptyState } from "./dashboard-view";
import { ActivityIcon, BoltIcon, InboxIcon } from "./dashboard-view";

Check warning on line 4 in frontend/src/components/dashboard/DashboardIncoming.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'BoltIcon' is defined but never used

Check warning on line 4 in frontend/src/components/dashboard/DashboardIncoming.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'ActivityIcon' is defined but never used
import { Button } from "@/components/ui/Button";

Check warning on line 5 in frontend/src/components/dashboard/DashboardIncoming.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'Button' is defined but never used

interface DashboardIncomingProps {
incomingStreams: Stream[];
onWithdraw: (stream: Stream) => Promise<void>;
withdrawingStreamId: string | null;
}

export function DashboardIncoming({
incomingStreams,
onWithdraw,
withdrawingStreamId,
}: DashboardIncomingProps) {
if (incomingStreams.length === 0) {
return (
<EmptyState
icon={<InboxIcon />}
title="No incoming streams yet"
description="No streams are sending you funds yet. Share your wallet address with a sender to receive streaming payments."
/>
);
}
return (
<div className="mt-8">
<IncomingStreams
streams={incomingStreams}
onWithdraw={onWithdraw}
withdrawingStreamId={withdrawingStreamId}
/>
</div>
);
}
106 changes: 106 additions & 0 deletions frontend/src/components/dashboard/DashboardOutgoing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { EmptyState } from "./dashboard-view";
import { BoltIcon } from "./dashboard-view";
import { Button } from "@/components/ui/Button";

interface DashboardOutgoingProps {
outgoingStreams: any[];

Check failure on line 6 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
onTopUp: (stream: any) => void;

Check failure on line 7 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
onCancel: (stream: any) => void;

Check failure on line 8 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
onShowDetails: (stream: any) => void;

Check failure on line 9 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
setShowWizard: () => void;
}

export function DashboardOutgoing({
outgoingStreams,
onTopUp,
onCancel,
onShowDetails,
setShowWizard,
}: DashboardOutgoingProps) {
const activeOutgoing = outgoingStreams.filter((s: any) => s.status === "Active");

Check failure on line 20 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type

if (activeOutgoing.length === 0) {
return (
<EmptyState
icon={<BoltIcon />}
title="No active outgoing streams"
description="You don't have any active outgoing payment streams. Create one to start streaming tokens to a recipient."
action={
<Button onClick={setShowWizard} glow>
Create a Stream
</Button>
}
/>
);
}

return (
<div className="mt-8">
<section className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>My Active Streams</h3>
<span>{activeOutgoing.length} total</span>
</div>
<div className="overflow-x-auto">
<table className="dashboard-table">
<thead>
<tr>
<th>Date</th>
<th>Recipient</th>
<th>Deposited</th>
<th>Withdrawn</th>
<th className="text-right">Actions</th>
</tr>
</thead>
<tbody>
{activeOutgoing.map((stream: any) => (

Check failure on line 56 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
<tr
key={stream.id}
className="cursor-pointer hover:bg-white/5"
onClick={(e: any) => {

Check failure on line 60 in frontend/src/components/dashboard/DashboardOutgoing.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Unexpected any. Specify a different type
if ((e.target as HTMLElement).closest("button")) return;
onShowDetails(stream);
}}
>
<td>{stream.date}</td>
<td>
<code className="text-xs">{stream.recipient}</code>
</td>
<td className="font-semibold text-accent">
{stream.deposited} {stream.token}
</td>
<td className="text-slate-400">
{stream.withdrawn} {stream.token}
</td>
<td className="text-right">
<div className="flex items-center justify-end gap-2">
<Button
className="secondary-button py-1 px-3 text-sm h-auto inline-flex items-center"
>
Details
</Button>
<Button
type="button"
className="secondary-button py-1 px-3 text-sm h-auto"
onClick={() => onTopUp(stream)}
>
Add Funds
</Button>
<Button
type="button"
className="py-1 px-3 text-sm rounded-full border border-red-500/40 text-red-400 hover:bg-red-500/10 transition-colors font-semibold"
onClick={() => onCancel(stream)}
>
Cancel
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</div>
);
}
129 changes: 129 additions & 0 deletions frontend/src/components/dashboard/DashboardOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"use client";

import { DashboardSnapshot, fetchDashboardData, dashboardQueryKey } from "@/lib/dashboard";
import { EmptyState } from "./dashboard-view";
import { ActivityIcon, BoltIcon, InboxIcon } from "./dashboard-view";

Check warning on line 5 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'InboxIcon' is defined but never used

Check warning on line 5 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'BoltIcon' is defined but never used
import { Button } from "@/components/ui/Button";

function renderStats(snapshot: DashboardSnapshot | null) {
if (!snapshot) return null;
return (
<div className="dashboard-stats-grid">
<div className="dashboard-panel">
<h3>Total Sent</h3>
<p className="text-2xl font-bold">
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(snapshot.totalSent)}
</p>
</div>
<div className="dashboard-panel">
<h3>Total Received</h3>
<p className="text-2xl font-bold">
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(snapshot.totalReceived)}
</p>
</div>
<div className="dashboard-panel">
<h3>Total Value Locked</h3>
<p className="text-2xl font-bold">
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(snapshot.totalValueLocked)}
</p>
</div>
</div>
);
}

function renderRecentActivity(
snapshot: DashboardSnapshot | null,
onCreateStream: () => void,
) {
if (!snapshot) return null;

if (snapshot.recentActivity.length === 0) {
return (
<EmptyState
icon={<ActivityIcon />}
title="No stream activity yet"
description="Transactions will appear here once you start creating or receiving payment streams."
action={
<Button onClick={onCreateStream} variant="ghost">
Create a Stream
</Button>
}
/>
);
}

return (
<section className="dashboard-panel">
<div className="dashboard-panel__header">
<h3>Recent Activity</h3>
<span>{snapshot.recentActivity.length} items</span>
</div>
<ul className="activity-list">

Check failure on line 61 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

Comments inside children section of tag should be placed inside braces
// @ts-expect-error unused
{snapshot.recentActivity.map((activity: any) => {
const amountPrefix = activity.direction === "received" ? "+" : "-";
const amountClass = activity.direction === "received" ? "is-positive" : "is-negative";
return (
<li key={activity.id} className="activity-item">
<div>
<strong>{activity.title}</strong>
<p>{activity.description}</p>
<small>{new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeStyle: "short" }).format(new Date(activity.timestamp))}</small>
</div>
<span className={amountClass}>
{amountPrefix}
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(activity.amount)}
</span>
</li>
);
})}
</ul>
</section>
);
}

interface DashboardOverviewProps {
snapshot: DashboardSnapshot | null;
isSnapshotLoading: boolean;
snapshotError: string | null;
// @ts-expect-error unused
// session uses any for dynamic wallet data
session: any;
onDisconnect: () => void;
setShowWizard: () => void;
// @ts-expect-error unused
// queryClient uses any for dynamic query state
queryClient: any;
}

export function DashboardOverview({
snapshot,
isSnapshotLoading,

Check warning on line 101 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'isSnapshotLoading' is defined but never used
snapshotError,

Check warning on line 102 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'snapshotError' is defined but never used
session,
onDisconnect,

Check warning on line 104 in frontend/src/components/dashboard/DashboardOverview.tsx

View workflow job for this annotation

GitHub Actions / Frontend CI

'onDisconnect' is defined but never used
setShowWizard,
queryClient,
}: DashboardOverviewProps) {
React.useEffect(() => {
fetchDashboardData(session.publicKey)
.then((next: DashboardSnapshot) => {
queryClient.setQueryData(dashboardQueryKey(session.publicKey), next);
})
.catch((err) => {
queryClient.setQueryData(dashboardQueryKey(session.publicKey), null);
});
}, [session.publicKey, queryClient]);

const hasNoStreams =
!snapshot ||
(snapshot.outgoingStreams.length === 0 &&
snapshot.incomingStreams.length === 0);

return (
<div className="dashboard-content-stack mt-8">
{renderStats(snapshot)}
{renderRecentActivity(snapshot, () => setShowWizard())}
</div>
);
}
53 changes: 53 additions & 0 deletions frontend/src/components/dashboard/DashboardPaused.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EmptyState } from "./dashboard-view";
import { BoltIcon } from "./dashboard-view";

interface DashboardPausedProps {
outgoingStreams: any[];
incomingStreams: any[];
}

export function DashboardPaused({
outgoingStreams,
incomingStreams,
}: DashboardPausedProps) {
const pausedStreams = [
...outgoingStreams.filter((s: any) => s.status === "Paused"),
...incomingStreams.filter((s: any) => s.status === "Paused"),
];

if (pausedStreams.length === 0) {
return (
<div className="glass-card p-12 rounded-3xl border-slate-800 text-center text-slate-400 mt-8">
No paused streams found.
</div>
);
}
return (
<div className="mt-8 glass-card rounded-3xl border-slate-800 overflow-hidden">
<table className="dashboard-table w-full">
<thead>
<tr>
<th>Stream ID</th>
<th>Counterparty</th>
<th>Token</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{pausedStreams.map((s: any) => (
<tr key={s.id}>
<td>#{s.id}</td>
<td className="font-mono text-xs">{s.recipient}</td>
<td>{s.token}</td>
<td>
<span className="px-2 py-1 rounded-full bg-yellow-500/10 text-yellow-500 text-xs font-bold">
Paused
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Loading
Loading