Skip to content

Commit 5612b75

Browse files
committed
Merge branch 'cursor/analyze-and-update-client-code-for-contract-changes-55e2' of github.com:greenpill-dev-guild/cookie-jar into release/3.1.0
2 parents 048e852 + 911eb14 commit 5612b75

File tree

12 files changed

+5172
-438
lines changed

12 files changed

+5172
-438
lines changed

client/components/jar/StreamingPanel.tsx

Lines changed: 176 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import {
2020
Settings,
2121
ExternalLink
2222
} from "lucide-react";
23-
import { formatUnits, parseUnits, isAddress } from "viem";
23+
import { formatUnits, isAddress } from "viem";
2424
import { formatAddress } from "@/lib/app/utils";
2525
import { useStreamingData } from "@/hooks/jar/useStreamingData";
2626
import { useStreamingActions } from "@/hooks/jar/useStreamingActions";
27+
import { useSuperfluidAccountInfo } from "@/hooks/jar/useSuperfluidAccountInfo";
28+
import { useSuperfluidTokenInfo } from "@/hooks/blockchain/useSuperfluidTokenInfo";
2729
import { StreamProcessingCard } from "./StreamProcessingCard";
2830

2931

@@ -45,7 +47,7 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
4547
const [newStreamToken, setNewStreamToken] = useState("");
4648
const [newStreamRate, setNewStreamRate] = useState("");
4749

48-
// Use real contract hooks
50+
// Use real Superfluid SDK hooks
4951
const {
5052
streams,
5153
// streamingConfig: config,
@@ -57,18 +59,23 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
5759

5860
const {
5961
createSuperStream,
60-
isRegistering,
61-
isApproving,
62-
isProcessing,
62+
isCreating,
63+
updateSuperStream,
64+
deleteSuperStream,
6365
} = useStreamingActions(jarAddress);
6466

67+
const { data: accountInfo } = useSuperfluidAccountInfo(jarAddress);
68+
6569
const handleCreateSuperStream = async () => {
66-
await createSuperStream(newStreamToken, newStreamRate);
70+
try {
71+
await createSuperStream(newStreamToken, newStreamRate);
6772

68-
// Reset form on success
69-
setNewStreamSender("");
70-
setNewStreamToken("");
71-
setNewStreamRate("");
73+
// Reset form on success
74+
setNewStreamToken("");
75+
setNewStreamRate("");
76+
} catch (error) {
77+
console.error("Failed to create stream:", error);
78+
}
7279
};
7380

7481
if (isLoadingStreams && !streams.length) {
@@ -93,13 +100,65 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
93100
</CardHeader>
94101

95102
<CardContent>
96-
<Tabs defaultValue="active" className="space-y-4">
103+
<Tabs defaultValue="overview" className="space-y-4">
97104
<TabsList className="grid w-full grid-cols-3">
105+
<TabsTrigger value="overview">Overview</TabsTrigger>
98106
<TabsTrigger value="active">Active Streams</TabsTrigger>
99107
{isAdmin && <TabsTrigger value="manage">Manage</TabsTrigger>}
100-
<TabsTrigger value="config">Configuration</TabsTrigger>
101108
</TabsList>
102109

110+
<TabsContent value="overview" className="space-y-4">
111+
{accountInfo && (
112+
<Card>
113+
<CardHeader>
114+
<CardTitle className="text-lg">Jar Flow Overview</CardTitle>
115+
</CardHeader>
116+
<CardContent>
117+
<div className="grid grid-cols-2 gap-4 text-sm">
118+
<div>
119+
<span className="text-gray-600">Net Flow Rate:</span>
120+
<div className="font-mono text-green-600">
121+
{accountInfo.formattedNetFlowRate} ETH/s
122+
</div>
123+
</div>
124+
<div>
125+
<span className="text-gray-600">Total Deposit:</span>
126+
<div className="font-mono text-blue-600">
127+
{accountInfo.formattedTotalDeposit} ETH
128+
</div>
129+
</div>
130+
</div>
131+
</CardContent>
132+
</Card>
133+
)}
134+
135+
<Card>
136+
<CardHeader>
137+
<CardTitle className="text-lg">Stream Statistics</CardTitle>
138+
</CardHeader>
139+
<CardContent>
140+
<div className="grid grid-cols-3 gap-4 text-center">
141+
<div>
142+
<div className="text-2xl font-bold text-blue-600">{streams.length}</div>
143+
<div className="text-sm text-gray-600">Active Streams</div>
144+
</div>
145+
<div>
146+
<div className="text-2xl font-bold text-green-600">
147+
{streams.reduce((sum, stream) => sum + Number(formatUnits(stream.ratePerSecond, 18)), 0).toFixed(3)}
148+
</div>
149+
<div className="text-sm text-gray-600">Total Rate (ETH/s)</div>
150+
</div>
151+
<div>
152+
<div className="text-2xl font-bold text-purple-600">
153+
{streams.reduce((sum, stream) => sum + Number(formatUnits(stream.totalStreamed, 18)), 0).toFixed(3)}
154+
</div>
155+
<div className="text-sm text-gray-600">Total Streamed (ETH)</div>
156+
</div>
157+
</div>
158+
</CardContent>
159+
</Card>
160+
</TabsContent>
161+
103162
<TabsContent value="active" className="space-y-4">
104163
{streams.length === 0 ? (
105164
<div className="text-center py-8 text-gray-500">
@@ -108,78 +167,14 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
108167
</div>
109168
) : (
110169
streams.map((stream) => (
111-
<Card key={stream.id} className="border-l-4 border-l-blue-500">
112-
<CardContent className="p-4">
113-
<div className="flex items-start justify-between">
114-
<div className="space-y-2">
115-
<div className="flex items-center gap-2">
116-
<Badge variant={stream.isApproved ? "default" : "secondary"}>
117-
{stream.isApproved ? "Active" : "Pending Approval"}
118-
</Badge>
119-
<span className="text-sm font-mono">
120-
Stream #{stream.id}
121-
</span>
122-
</div>
123-
124-
<div className="text-sm space-y-1">
125-
<p>
126-
<span className="font-medium">From:</span>{" "}
127-
<span className="font-mono">{formatAddress(stream.sender)}</span>
128-
</p>
129-
<p>
130-
<span className="font-medium">Rate:</span>{" "}
131-
{formatStreamRate(stream.ratePerSecond, 18)}
132-
</p>
133-
<p>
134-
<span className="font-medium">Total Streamed:</span>{" "}
135-
{formatUnits(stream.totalStreamed, 18)}
136-
</p>
137-
</div>
138-
139-
{stream.isApproved && (
140-
<div className="bg-green-50 p-2 rounded text-sm">
141-
<p className="text-green-700 font-medium">
142-
Claimable: {formatUnits(calculateClaimable(stream), 18)} tokens
143-
</p>
144-
</div>
145-
)}
146-
</div>
147-
148-
<div className="flex gap-2">
149-
{/* {!stream.isApproved && isAdmin && (
150-
<Button
151-
size="sm"
152-
onClick={() => handleApproveStream(stream.id)}
153-
disabled={isApproving}
154-
>
155-
{isApproving ? (
156-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-1"></div>
157-
) : (
158-
<CheckCircle className="h-4 w-4 mr-1" />
159-
)}
160-
Approve
161-
</Button>
162-
)} */}
163-
{/*
164-
{stream.isApproved && (
165-
<Button
166-
size="sm"
167-
variant="outline"
168-
onClick={() => handleProcessStream(stream.id)}
169-
disabled={isProcessing}
170-
>
171-
{isProcessing ? (
172-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400 mr-1"></div>
173-
) : (
174-
<Play className="h-4 w-4 mr-1" />
175-
)}
176-
Process
177-
</Button>
178-
)} */}
179-
</div>
180-
</div>
181-
</CardContent>
182-
</Card>
170+
<StreamCard
171+
key={stream.id}
172+
stream={stream}
173+
onUpdate={(rate) => updateSuperStream(stream.token, rate)}
174+
onDelete={() => deleteSuperStream(stream.token)}
175+
isUpdating={isUpdating}
176+
isDeleting={isDeleting}
177+
/>
183178
))
184179
)}
185180
</TabsContent>
@@ -215,10 +210,10 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
215210

216211
<Button
217212
onClick={handleCreateSuperStream}
218-
disabled={isRegistering}
213+
disabled={isCreating}
219214
className="w-full"
220215
>
221-
{isRegistering ? (
216+
{isCreating ? (
222217
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
223218
) : (
224219
<Settings className="h-4 w-4 mr-2" />
@@ -234,3 +229,98 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
234229
</Card>
235230
);
236231
};
232+
233+
interface StreamCardProps {
234+
stream: any; // Using any for now, should be properly typed
235+
onUpdate: (rate: string) => void;
236+
onDelete: () => void;
237+
isUpdating: boolean;
238+
isDeleting: boolean;
239+
}
240+
241+
const StreamCard: React.FC<StreamCardProps> = ({
242+
stream,
243+
onUpdate,
244+
onDelete,
245+
isUpdating,
246+
isDeleting
247+
}) => {
248+
const { data: tokenInfo } = useSuperfluidTokenInfo(stream.token);
249+
250+
return (
251+
<Card className="border-l-4 border-l-blue-500">
252+
<CardContent className="p-4">
253+
<div className="flex items-start justify-between">
254+
<div className="space-y-2 flex-1">
255+
<div className="flex items-center gap-2">
256+
<Badge variant={stream.isActive ? "default" : "secondary"}>
257+
{stream.isActive ? "Active" : "Inactive"}
258+
</Badge>
259+
<span className="text-sm font-mono text-gray-600">
260+
{stream.tokenSymbol || "TOKEN"}
261+
</span>
262+
</div>
263+
264+
<div className="text-sm space-y-1">
265+
<p>
266+
<span className="font-medium">From:</span>{" "}
267+
<span className="font-mono">{formatAddress(stream.sender)}</span>
268+
</p>
269+
<p>
270+
<span className="font-medium">Rate:</span>{" "}
271+
{formatStreamRate(stream.ratePerSecond, 18)}
272+
</p>
273+
<p>
274+
<span className="font-medium">Total Streamed:</span>{" "}
275+
{formatUnits(stream.totalStreamed, 18)}
276+
</p>
277+
</div>
278+
279+
{stream.isActive && (
280+
<div className="bg-green-50 p-2 rounded text-sm">
281+
<p className="text-green-700 font-medium">
282+
Claimable: {formatUnits(calculateClaimable(stream), 18)} tokens
283+
</p>
284+
</div>
285+
)}
286+
</div>
287+
288+
<div className="flex gap-2 ml-4">
289+
{isAdmin && (
290+
<>
291+
<Button
292+
size="sm"
293+
variant="outline"
294+
onClick={() => {
295+
const newRate = prompt("Enter new flow rate (wei per second):", stream.ratePerSecond.toString());
296+
if (newRate) onUpdate(newRate);
297+
}}
298+
disabled={isUpdating}
299+
>
300+
{isUpdating ? (
301+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400"></div>
302+
) : (
303+
<Settings className="h-4 w-4" />
304+
)}
305+
</Button>
306+
<Button
307+
size="sm"
308+
variant="outline"
309+
onClick={onDelete}
310+
disabled={isDeleting}
311+
className="text-red-600 hover:text-red-700"
312+
>
313+
{isDeleting ? (
314+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-red-400"></div>
315+
) : (
316+
<Pause className="h-4 w-4" />
317+
)}
318+
</Button>
319+
</>
320+
)}
321+
</div>
322+
</div>
323+
</CardContent>
324+
</Card>
325+
);
326+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { useQuery } from "@tanstack/react-query";
4+
import { useChainId } from "wagmi";
5+
import { createSuperfluidFramework, isSuperfluidSupported } from "@/lib/blockchain/superfluid-config";
6+
7+
/**
8+
* Hook for initializing and caching Superfluid Framework
9+
* This creates the framework once per chain and caches it
10+
*/
11+
export const useSuperfluidFramework = () => {
12+
const chainId = useChainId();
13+
14+
return useQuery({
15+
queryKey: ["superfluidFramework", chainId],
16+
queryFn: async () => {
17+
if (!isSuperfluidSupported(chainId)) {
18+
throw new Error(`Superfluid not supported on chain ${chainId}`);
19+
}
20+
21+
// Create and cache the framework instance
22+
return await createSuperfluidFramework(chainId);
23+
},
24+
staleTime: Infinity, // Framework doesn't change during session
25+
gcTime: 30 * 60 * 1000, // Keep in cache for 30 minutes
26+
enabled: !!chainId && isSuperfluidSupported(chainId),
27+
retry: (failureCount, error) => {
28+
// Don't retry if chain is not supported
29+
if (error.message.includes("not supported")) return false;
30+
// Retry up to 3 times for network errors
31+
return failureCount < 3;
32+
},
33+
});
34+
};

0 commit comments

Comments
 (0)