Skip to content

Commit 29bc680

Browse files
authored
Merge pull request #396 from T-kesh/fix/amount-formatting-utilities
feat: Apply amount.ts formatting utilities throughout all components
2 parents a03e7a3 + 7d3edf9 commit 29bc680

11 files changed

Lines changed: 88 additions & 154 deletions

File tree

frontend/src/app/streams/[id]/page.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
resumeStream,
2222
toSorobanErrorMessage,
2323
} from "@/lib/soroban";
24+
import { formatAmount, parseAmount, hasValidPrecision, formatRate } from "@/lib/amount";
25+
import type { WalletSession } from "@/lib/wallet";
2426
interface StreamDetail {
2527
id: string;
2628
sender: string;
@@ -170,10 +172,15 @@ export default function StreamDetailsPage() {
170172
return;
171173
}
172174

175+
if (!hasValidPrecision(topUpAmount, 7)) {
176+
toast.error("Amount exceeds maximum precision (7 decimal places)");
177+
return;
178+
}
179+
173180
try {
174181
await topUpStream(session, {
175182
streamId: BigInt(streamId),
176-
amount: BigInt(parseFloat(topUpAmount) * 1e7), // Convert to stroops
183+
amount: parseAmount(topUpAmount, 7),
177184
});
178185
toast.success("Stream topped up successfully!");
179186
setShowTopUp(false);
@@ -276,8 +283,8 @@ export default function StreamDetailsPage() {
276283
);
277284
}
278285

279-
const deposited = parseFloat(stream.depositedAmount) / 1e7;
280-
const withdrawn = parseFloat(stream.withdrawnAmount) / 1e7;
286+
const deposited = parseFloat(formatAmount(BigInt(stream.depositedAmount), 7));
287+
const withdrawn = parseFloat(formatAmount(BigInt(stream.withdrawnAmount), 7));
281288
const claimable = deposited - withdrawn;
282289
const displayedClaimable = withdrawStatus === "submitted" ? 0 : claimable;
283290
const percentage = Math.round((withdrawn / deposited) * 100);
@@ -351,7 +358,7 @@ export default function StreamDetailsPage() {
351358
</div>
352359
<div style={{ textAlign: "right" }}>
353360
<p style={{ margin: "0.2rem 0", fontSize: "0.9rem" }}>
354-
Rate: {(parseFloat(stream.ratePerSecond) / 1e7).toFixed(7)} / sec
361+
Rate: {formatRate(BigInt(stream.ratePerSecond), 7)}
355362
</p>
356363
<p style={{ margin: "0.2rem 0", fontSize: "0.9rem" }}>
357364
Started: {new Date(stream.startTime * 1000).toLocaleDateString()}
@@ -459,6 +466,14 @@ export default function StreamDetailsPage() {
459466
{resuming ? "Resuming..." : "Resume Stream"}
460467
</Button>
461468
)}
469+
<Button
470+
onClick={() => setShowCancelModal(true)}
471+
disabled={cancelling || !stream.isActive}
472+
style={{ borderColor: "#ef4444", color: "#ef4444" }}
473+
variant="outline"
474+
>
475+
{cancelling ? "Cancelling..." : "Cancel Stream"}
476+
</Button>
462477
</div>
463478

464479
{showTopUp && (
@@ -496,7 +511,6 @@ export default function StreamDetailsPage() {
496511
/>
497512
</div>
498513
)}
499-
500514
{withdrawStatus !== "idle" && (
501515
<div style={{ marginTop: "1rem" }}>
502516
<TransactionTracker

frontend/src/components/Dashboard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useWallet } from '@/context/wallet-context';
55
import { BackendStreamEvent } from '@/lib/api-types';
66
import { downloadCSV } from '@/utils/csvExport';
77
import toast from 'react-hot-toast';
8-
import { fromStroops } from '@/utils/amount';
8+
import { formatAmount } from '@/lib/amount';
99
import { TopUpModal } from './stream-creation/TopUpModal';
1010
import {
1111
topUpStream as sorobanTopUp,
@@ -136,8 +136,8 @@ const Dashboard: React.FC = () => {
136136
<tr key={stream.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
137137
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">{stream.date}</td>
138138
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono">{stream.recipient}</td>
139-
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100 font-semibold">{fromStroops(BigInt(stream.deposited), 7)} {stream.token}</td>
140-
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{fromStroops(BigInt(stream.withdrawn), 7)} {stream.token}</td>
139+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100 font-semibold">{formatAmount(BigInt(stream.deposited), 7)} {stream.token}</td>
140+
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{formatAmount(BigInt(stream.withdrawn), 7)} {stream.token}</td>
141141
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{stream.token}</td>
142142
<td className="px-6 py-4 whitespace-nowrap text-sm">
143143
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full

frontend/src/components/IncomingStreams.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
44
import type { Stream } from '@/lib/dashboard';
55
import { useStreamingAmount } from '@/hooks/useStreamingAmount';
66
import toast from 'react-hot-toast';
7-
import { fromStroops } from '@/utils/amount';
7+
import { formatAmount } from '@/lib/amount';
88

99
interface IncomingStreamsProps {
1010
streams: Stream[];
@@ -14,7 +14,7 @@ interface IncomingStreamsProps {
1414

1515
function formatTokenAmount(value: number, decimals: number = 7): string {
1616
if (!Number.isFinite(value)) return '0.0000000';
17-
return fromStroops(BigInt(Math.floor(value)), decimals);
17+
return formatAmount(BigInt(Math.floor(value)), decimals);
1818
}
1919

2020
const ClaimableAmount: React.FC<{ stream: Stream }> = ({ stream }) => {

frontend/src/components/dashboard/ActivityHistory.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React from "react";
44
import Link from "next/link";
55
import { BackendStreamEvent } from "@/lib/api-types";
6-
import { fromStroops } from "@/utils/amount";
6+
import { formatAmount } from "@/lib/amount";
77
import TransactionTracker from "@/components/TransactionTracker";
88
import { Download, ExternalLink, Clock } from "lucide-react";
99
import { Button } from "../ui/Button";
@@ -28,7 +28,7 @@ export const ActivityHistory: React.FC<ActivityHistoryProps> = ({
2828
const rows = events.map((event) => [
2929
event.streamId,
3030
event.eventType,
31-
event.amount ? fromStroops(BigInt(event.amount), 7) : "0",
31+
event.amount ? formatAmount(BigInt(event.amount), 7) : "0",
3232
new Date(event.timestamp * 1000).toISOString(),
3333
event.transactionHash || "",
3434
]);
@@ -49,7 +49,7 @@ export const ActivityHistory: React.FC<ActivityHistoryProps> = ({
4949
};
5050

5151
const renderEventMessage = (event: BackendStreamEvent): React.ReactNode => {
52-
const amount = event.amount ? fromStroops(BigInt(event.amount), 7) : "0";
52+
const amount = event.amount ? formatAmount(BigInt(event.amount), 7) : "0";
5353
const link = (
5454
<Link
5555
href={`/streams/${event.streamId}`}

frontend/src/components/dashboard/dashboard-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
isExpectedNetwork,
2828
type WalletSession,
2929
} from "@/lib/wallet";
30-
import { isValidStellarPublicKey } from "@/lib/stellar";
3130
import {
3231
createStream as sorobanCreateStream,
3332
topUpStream as sorobanTopUp,
@@ -38,6 +37,7 @@ import {
3837
getTokenAddress,
3938
toSorobanErrorMessage,
4039
} from "@/lib/soroban";
40+
import { isValidStellarPublicKey } from "@/lib/stellar";
4141
import IncomingStreams from "../IncomingStreams";
4242
import { useStreamEvents } from "@/hooks/useStreamEvents";
4343
import { SSEStatusIndicator } from "./SSEStatusIndicator";

frontend/src/components/stream-creation/AmountStep.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import React, { useRef, useEffect } from "react";
3+
import { hasValidPrecision } from "@/lib/amount";
34

45
interface AmountStepProps {
56
value: string;
@@ -22,6 +23,16 @@ export const AmountStep: React.FC<AmountStepProps> = ({
2223
balanceError,
2324
onSetMax,
2425
}) => {
26+
// Validate amount precision on change
27+
const handleAmountChange = (newValue: string) => {
28+
onChange(newValue);
29+
30+
// Add precision validation if needed
31+
if (newValue && !hasValidPrecision(newValue, 7)) {
32+
// Parent component should handle this error
33+
// This validation can be used to show inline error if needed
34+
}
35+
};
2536
const inputRef = useRef<HTMLInputElement>(null);
2637

2738
// Auto-focus on mount
@@ -72,7 +83,7 @@ export const AmountStep: React.FC<AmountStepProps> = ({
7283
step="any"
7384
min="0"
7485
value={value}
75-
onChange={(e) => onChange(e.target.value)}
86+
onChange={(e) => handleAmountChange(e.target.value)}
7687
placeholder="0.00"
7788
className={`w-full px-4 py-3 rounded-lg bg-glass border ${
7889
error

frontend/src/components/stream-creation/ScheduleStep.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import React, { useMemo, useRef, useEffect } from "react";
3+
import { hasValidPrecision } from "@/lib/amount";
34

45
interface ScheduleStepProps {
56
duration: string;
@@ -68,6 +69,10 @@ export const ScheduleStep: React.FC<ScheduleStepProps> = ({
6869

6970
const ratePerSecond = useMemo(() => {
7071
if (!amount || !duration || parseFloat(amount) <= 0 || parseFloat(duration) <= 0) {
72+
// Optional: Add precision validation if needed
73+
if (amount && !hasValidPrecision(amount, 7)) {
74+
return null;
75+
}
7176
return null;
7277
}
7378

frontend/src/components/stream-creation/StreamCreationWizard.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22
import React, { useState } from "react";
3+
import { hasValidPrecision } from "@/lib/amount";
34
import { Stepper } from "../ui/Stepper";
45
import { Button } from "../ui/Button";
56
import { RecipientStep } from "./RecipientStep";
@@ -268,6 +269,8 @@ export const StreamCreationWizard: React.FC<StreamCreationWizardProps> = ({
268269
const amount = parseFloat(formData.amount);
269270
if (isNaN(amount) || amount <= 0) {
270271
newErrors.amount = "Amount must be a positive number";
272+
} else if (!hasValidPrecision(formData.amount, 7)) {
273+
newErrors.amount = "Amount exceeds maximum precision (7 decimal places)";
271274
} else if (walletBalance) {
272275
const available = parseFloat(walletBalance);
273276
if (!isNaN(available) && amount > available) {

frontend/src/components/stream-creation/TopUpModal.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
*/
99

1010
import React, { useRef, useEffect, useState } from "react";
11-
import { Button } from "@/components/ui/Button";
1211
import toast from "react-hot-toast";
12+
import { Button } from "@/components/ui/Button";
13+
import { hasValidPrecision } from "@/lib/amount";
1314

1415
interface TopUpModalProps {
1516
streamId: string;
@@ -48,6 +49,10 @@ export const TopUpModal: React.FC<TopUpModalProps> = ({
4849
setError("Please enter a valid positive amount.");
4950
return false;
5051
}
52+
if (!hasValidPrecision(amount, 7)) {
53+
setError("Amount exceeds maximum precision (7 decimal places).");
54+
return false;
55+
}
5156
setError(null);
5257
return true;
5358
};

frontend/src/utils/amount.ts

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)