Skip to content
Closed
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
4 changes: 2 additions & 2 deletions frontend/app/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { WalletEntry } from "@/components/wallet/wallet-entry";
import { OutgoingStreams } from "@/components/OutgoingStreams";

export default function AppDashboardPage() {
return <WalletEntry />;
return <OutgoingStreams />;
}
216 changes: 216 additions & 0 deletions frontend/components/OutgoingStreams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
"use client";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/Button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/Card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Edit2, ExternalLink, Pause, X } from "lucide-react";
import { useState } from "react";

// Mock data for outgoing streams
const mockStreams = [
{
id: "1",
recipient: "0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45",
recipientEns: "alice.eth",
token: "USDC",
rate: "100.00",
ratePeriod: "month",
remainingBalance: "2500.50",
status: "active",
startDate: "2024-01-15",
},
{
id: "2",
recipient: "0x8ba1f109551bD432803012645Hac136c82C31A01",
recipientEns: "bob.eth",
token: "ETH",
rate: "0.05",
ratePeriod: "day",
remainingBalance: "1.25",
status: "active",
startDate: "2024-01-10",
},
{
id: "3",
recipient: "0x9876543210987654321098765432109876543210",
recipientEns: "charlie.eth",
token: "DAI",
rate: "50.00",
ratePeriod: "month",
remainingBalance: "1200.00",
status: "paused",
startDate: "2024-01-05",
},
{
id: "4",
recipient: "0x1234567890123456789012345678901234567890",
recipientEns: "david.eth",
token: "USDT",
rate: "75.00",
ratePeriod: "month",
remainingBalance: "875.25",
status: "active",
startDate: "2024-01-20",
},
];

export function OutgoingStreams() {
const [streams, setStreams] = useState(mockStreams);

const handleCancel = (streamId: string) => {
setStreams(streams.filter((stream) => stream.id !== streamId));
};

const handlePause = (streamId: string) => {
setStreams(
streams.map((stream) =>
stream.id === streamId
? {
...stream,
status: stream.status === "active" ? "paused" : "active",
}
: stream,
),
);
};

const handleModify = (streamId: string) => {
// TODO: Open modify modal
console.log("Modify stream:", streamId);
};

const formatAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

const getStatusBadge = (status: string) => {
return (
<Badge variant={status === "active" ? "default" : "secondary"}>
{status === "active" ? "Active" : "Paused"}
</Badge>
);
};

return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Outgoing Streams</h1>
<p className="text-muted-foreground">
Manage your active payment streams and liabilities
</p>
</div>
<Button>Create Stream</Button>
</div>

<Card>
<CardHeader>
<CardTitle>Active Streams</CardTitle>
<CardDescription>
You have {streams.filter((s) => s.status === "active").length}{" "}
active streams
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Recipient</TableHead>
<TableHead>Token</TableHead>
<TableHead>Rate</TableHead>
<TableHead>Remaining Balance</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{streams.map((stream) => (
<TableRow key={stream.id}>
<TableCell>
<div className="flex items-center gap-2">
<div>
<div className="font-medium">{stream.recipientEns}</div>
<div className="text-sm text-muted-foreground flex items-center gap-1">
{formatAddress(stream.recipient)}
<Button
variant="ghost"
size="sm"
className="h-auto p-0"
onClick={() =>
navigator.clipboard.writeText(stream.recipient)
}
>
<ExternalLink className="h-3 w-3" />
</Button>
</div>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline">{stream.token}</Badge>
</TableCell>
<TableCell>
<div>
<div className="font-medium">
{stream.rate} {stream.token}
</div>
<div className="text-sm text-muted-foreground">
per {stream.ratePeriod}
</div>
</div>
</TableCell>
<TableCell>
<div className="font-medium">
{stream.remainingBalance} {stream.token}
</div>
</TableCell>
<TableCell>{getStatusBadge(stream.status)}</TableCell>
<TableCell className="text-right">
<div className="flex items-center gap-2 justify-end">
<Button
variant="outline"
size="sm"
onClick={() => handlePause(stream.id)}
>
<Pause className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleModify(stream.id)}
>
<Edit2 className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleCancel(stream.id)}
className="text-red-500 border-red-500 hover:bg-red-500 hover:text-white"
>
<X className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}
92 changes: 74 additions & 18 deletions frontend/components/ui/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,82 @@
import React from 'react';
import React from "react";

interface CardProps {
children: React.ReactNode;
className?: string;
glow?: boolean;
hover?: boolean;
children: React.ReactNode;
className?: string;
glow?: boolean;
hover?: boolean;
}

export const Card: React.FC<CardProps> = ({
children,
className = '',
glow = false,
hover = true,
children,
className = "",
glow = false,
hover = true,
}) => {
return (
<div className={`
glass-card rounded-2xl p-6 transition-all duration-300
${glow ? 'glow-card' : ''}
${hover ? 'hover:-translate-y-1 hover:border-accent/30 hover:shadow-lg hover:shadow-accent/5' : ''}
return (
<div
className={`
glass-card rounded-2xl transition-all duration-300
${glow ? "glow-card" : ""}
${hover ? "hover:-translate-y-1 hover:border-accent/30 hover:shadow-lg hover:shadow-accent/5" : ""}
${className}
`}>
{children}
</div>
);
`}
>
{children}
</div>
);
};

interface CardHeaderProps {
children: React.ReactNode;
className?: string;
}

export const CardHeader: React.FC<CardHeaderProps> = ({
children,
className = "",
}) => {
return <div className={`pb-4 ${className}`}>{children}</div>;
};

interface CardTitleProps {
children: React.ReactNode;
className?: string;
}

export const CardTitle: React.FC<CardTitleProps> = ({
children,
className = "",
}) => {
return (
<h3 className={`text-lg font-semibold leading-tight ${className}`}>
{children}
</h3>
);
};

interface CardDescriptionProps {
children: React.ReactNode;
className?: string;
}

export const CardDescription: React.FC<CardDescriptionProps> = ({
children,
className = "",
}) => {
return (
<p className={`text-sm text-muted-foreground ${className}`}>{children}</p>
);
};

interface CardContentProps {
children: React.ReactNode;
className?: string;
}

export const CardContent: React.FC<CardContentProps> = ({
children,
className = "",
}) => {
return <div className={`${className}`}>{children}</div>;
};
25 changes: 25 additions & 0 deletions frontend/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

interface BadgeProps {
children: React.ReactNode;
variant?: 'default' | 'secondary' | 'outline';
className?: string;
}

export const Badge: React.FC<BadgeProps> = ({
children,
variant = 'default',
className = ''
}) => {
const variants = {
default: 'bg-accent text-background',
secondary: 'bg-glass border border-glass-border text-foreground',
outline: 'border border-glass-border text-foreground bg-transparent',
};

return (
<span className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${variants[variant]} ${className}`}>
{children}
</span>
);
};
Loading
Loading