diff --git a/frontend/src/app/incoming/incoming-content.tsx b/frontend/src/app/incoming/incoming-content.tsx
index a5af7ede..0e924422 100644
--- a/frontend/src/app/incoming/incoming-content.tsx
+++ b/frontend/src/app/incoming/incoming-content.tsx
@@ -1,9 +1,8 @@
"use client";
-import React from "react";
import toast from "react-hot-toast";
import TransactionTracker, {
- type TransactionStatus,
+ useTransactionTracker,
} from "@/components/TransactionTracker";
import { IncomingStreamCard } from "@/components/streams/IncomingStreamCard";
import { Skeleton } from "@/components/ui/Skeleton";
@@ -17,13 +16,6 @@ import {
useWithdrawIncomingStream,
} from "@/hooks/useIncomingStreams";
-interface TrackerState {
- status: TransactionStatus;
- txHash?: string;
- error?: string;
- streamId?: string;
-}
-
function LoadingCard() {
return (
@@ -41,48 +33,29 @@ function LoadingCard() {
export default function IncomingContent() {
const { session, status, isHydrated } = useWallet();
- const [tracker, setTracker] = React.useState
({
- status: "idle",
- });
+ const tracker = useTransactionTracker();
const incomingStreamsQuery = useIncomingStreams(session?.publicKey);
const withdrawMutation = useWithdrawIncomingStream(
session,
session?.publicKey,
{
- onSuccess: async (result, stream) => {
- setTracker({
- status: "submitted",
- txHash: result.txHash,
- streamId: String(stream.streamId),
- });
- toast.success(`Withdrawal submitted for stream #${stream.streamId}`);
-
- window.setTimeout(() => {
- setTracker((current) =>
- current.txHash === result.txHash
- ? { ...current, status: "confirmed" }
- : current,
- );
- }, 1500);
+ onSuccess: async (result, _stream) => {
+ tracker.submit(result.txHash);
+ toast.success(`Withdrawal submitted for stream #${_stream.streamId}`);
+ // Move to confirming — TransactionTracker polls the indexer from here
+ tracker.confirm();
},
- onError: (error, stream) => {
+ onError: (error) => {
const message = toSorobanErrorMessage(error);
- setTracker({
- status: "failed",
- error: message,
- streamId: String(stream.streamId),
- });
+ tracker.fail(message);
toast.error(message);
},
},
);
const handleWithdraw = async (stream: IncomingStreamRecord) => {
- setTracker({
- status: "signing",
- streamId: String(stream.streamId),
- });
+ tracker.start();
try {
await withdrawMutation.mutateAsync(stream);
@@ -192,7 +165,6 @@ export default function IncomingContent() {
action="withdraw"
txHash={tracker.txHash}
error={tracker.error}
- streamId={tracker.streamId}
/>
)}
diff --git a/frontend/src/app/streams/[id]/stream-details-content.tsx b/frontend/src/app/streams/[id]/stream-details-content.tsx
index 1c8cf265..216ea65a 100644
--- a/frontend/src/app/streams/[id]/stream-details-content.tsx
+++ b/frontend/src/app/streams/[id]/stream-details-content.tsx
@@ -9,6 +9,9 @@ import { Button } from "@/components/ui/Button";
import toast from "react-hot-toast";
import { useWallet } from "@/context/wallet-context";
import { useStreamEvents } from "@/hooks/useStreamEvents";
+import TransactionTracker, {
+ useTransactionTracker,
+} from "@/components/TransactionTracker";
import {
withdrawFromStream,
cancelStream,
@@ -68,6 +71,7 @@ const EVENT_STYLES: Record(null);
const [events, setEvents] = useState([]);
@@ -76,10 +80,6 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [withdrawing, setWithdrawing] = useState(false);
- const [cancelling, setCancelling] = useState(false);
- const [pausing, setPausing] = useState(false);
- const [resuming, setResuming] = useState(false);
const [topUpAmount, setTopUpAmount] = useState("");
const [showTopUp, setShowTopUp] = useState(false);
const [showCancelModal, setShowCancelModal] = useState(false);
@@ -200,15 +200,18 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
toast.error("Please connect your wallet");
return;
}
- setWithdrawing(true);
+ tracker.start();
try {
- await withdrawFromStream(session, { streamId: BigInt(streamId) });
+ const result = await withdrawFromStream(session, { streamId: BigInt(streamId) });
+ tracker.submit(result.txHash);
+ tracker.confirm();
toast.success("Withdrawal successful!");
await fetchStream();
+ tracker.succeed();
} catch (err) {
- toast.error(toSorobanErrorMessage(err));
- } finally {
- setWithdrawing(false);
+ const message = toSorobanErrorMessage(err);
+ tracker.fail(message);
+ toast.error(message);
}
};
@@ -221,15 +224,21 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
toast.error("Please enter a valid amount");
return;
}
+ tracker.start();
try {
const amount = toBaseUnits(topUpAmount);
- await topUpStream(session, { streamId: BigInt(streamId), amount });
+ const result = await topUpStream(session, { streamId: BigInt(streamId), amount });
+ tracker.submit(result.txHash);
+ tracker.confirm();
toast.success("Stream topped up successfully!");
setShowTopUp(false);
setTopUpAmount("");
await fetchStream();
+ tracker.succeed();
} catch (err) {
- toast.error(toSorobanErrorMessage(err));
+ const message = toSorobanErrorMessage(err);
+ tracker.fail(message);
+ toast.error(message);
}
};
@@ -238,15 +247,18 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
toast.error("Please connect your wallet");
return;
}
- setPausing(true);
+ tracker.start();
try {
- await pauseStream(session, { streamId: BigInt(streamId) });
+ const result = await pauseStream(session, { streamId: BigInt(streamId) });
+ tracker.submit(result.txHash);
+ tracker.confirm();
toast.success("Stream paused");
await fetchStream();
+ tracker.succeed();
} catch (err) {
- toast.error(toSorobanErrorMessage(err));
- } finally {
- setPausing(false);
+ const message = toSorobanErrorMessage(err);
+ tracker.fail(message);
+ toast.error(message);
}
};
@@ -255,15 +267,18 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
toast.error("Please connect your wallet");
return;
}
- setResuming(true);
+ tracker.start();
try {
- await resumeStream(session, { streamId: BigInt(streamId) });
+ const result = await resumeStream(session, { streamId: BigInt(streamId) });
+ tracker.submit(result.txHash);
+ tracker.confirm();
toast.success("Stream resumed");
await fetchStream();
+ tracker.succeed();
} catch (err) {
- toast.error(toSorobanErrorMessage(err));
- } finally {
- setResuming(false);
+ const message = toSorobanErrorMessage(err);
+ tracker.fail(message);
+ toast.error(message);
}
};
@@ -272,16 +287,19 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
toast.error("Please connect your wallet");
return;
}
- setCancelling(true);
+ tracker.start();
try {
- await cancelStream(session, { streamId: BigInt(streamId) });
+ const result = await cancelStream(session, { streamId: BigInt(streamId) });
+ tracker.submit(result.txHash);
+ tracker.confirm();
toast.success("Stream cancelled");
setShowCancelModal(false);
await fetchStream();
+ tracker.succeed();
} catch (err) {
- toast.error(toSorobanErrorMessage(err));
- } finally {
- setCancelling(false);
+ const message = toSorobanErrorMessage(err);
+ tracker.fail(message);
+ toast.error(message);
}
};
@@ -408,12 +426,12 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
{isRecipient && (
)}
@@ -433,22 +451,22 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
{!stream.isPaused ? (
) : (
)}
>
@@ -457,7 +475,7 @@ export default function StreamDetailsContent({ streamId }: { streamId: string })
{isSender && (
)}
+ {/* Transaction Tracker */}
+ {tracker.status !== "idle" && (
+
+
+
+ )}
+
{/* Event History */}
Event History