Skip to content

Commit 6076f66

Browse files
committed
add Recipient CSV input
1 parent 74ac032 commit 6076f66

File tree

5 files changed

+143
-4
lines changed

5 files changed

+143
-4
lines changed

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"lucide-react": "^0.439.0",
6666
"next": "^14.2.4",
6767
"next-auth": "^4.24.7",
68+
"papaparse": "^5.5.3",
6869
"react": "^18.3.1",
6970
"react-dom": "^18.3.1",
7071
"react-dropzone": "^14.3.5",
@@ -89,6 +90,7 @@
8990
"@types/jsonld": "^1.5.15",
9091
"@types/jsonwebtoken": "^9.0.9",
9192
"@types/node": "^20.14.10",
93+
"@types/papaparse": "^5.3.16",
9294
"@types/react": "^18.3.3",
9395
"@types/react-dom": "^18.3.0",
9496
"@types/swagger-jsdoc": "^6.0.4",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { ChangeEvent, useRef } from "react";
2+
import Papa from "papaparse";
3+
import { useDropzone } from "react-dropzone";
4+
import { useWalletsStore } from "@/lib/zustand/wallets";
5+
import { toast } from "@/hooks/use-toast";
6+
7+
type RecipientCsvProps = {
8+
setRecipientAddresses: (value: string[]) => void;
9+
setAmounts: (value: string[]) => void;
10+
setAssets: (value: string[]) => void;
11+
recipientAddresses: string[];
12+
amounts: string[];
13+
assets: string[];
14+
};
15+
16+
export default function RecipientCsv({
17+
setRecipientAddresses,
18+
setAmounts,
19+
setAssets,
20+
recipientAddresses,
21+
amounts,
22+
assets,
23+
}: RecipientCsvProps) {
24+
const walletAssets = useWalletsStore((state) => state.walletAssets);
25+
const onDrop = (acceptedFiles: File[]) => {
26+
const file = acceptedFiles[0];
27+
if (!file) return;
28+
29+
Papa.parse<string[]>(file, {
30+
complete: (results: Papa.ParseResult<string[]>) => {
31+
const rows = results.data as string[][];
32+
const newAddresses: string[] = [];
33+
const newAmounts: string[] = [];
34+
const newAssets: string[] = [];
35+
36+
rows.forEach((row, index) => {
37+
if (index === 0) return; // skip header row
38+
const [address, unit, amount] = row;
39+
if (address && amount) {
40+
const resolvedUnit = (!unit || unit === "ADA") ? "ADA" : unit;
41+
const walletUnitList = walletAssets.map(a => a.unit).concat("ADA");
42+
43+
if (!walletUnitList.includes(resolvedUnit)) {
44+
toast({
45+
title: "Unknown Asset",
46+
description: `Unit "${resolvedUnit}" not found in your wallet.`,
47+
variant: "destructive",
48+
});
49+
return;
50+
}
51+
52+
newAddresses.push(address);
53+
newAmounts.push(amount);
54+
newAssets.push(resolvedUnit);
55+
}
56+
});
57+
58+
setRecipientAddresses([...recipientAddresses, ...newAddresses]);
59+
setAmounts([...amounts, ...newAmounts]);
60+
setAssets([...assets, ...newAssets]);
61+
},
62+
skipEmptyLines: true,
63+
});
64+
};
65+
66+
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { "text/csv": [".csv"] } });
67+
68+
return (
69+
<>
70+
<div
71+
{...getRootProps()}
72+
className="flex items-center justify-center border-2 border-dashed border-gray-400 p-4 rounded-md cursor-pointer hover:bg-gray-100"
73+
>
74+
<input {...getInputProps()} />
75+
{isDragActive ? (
76+
<p>Drop the CSV file here ...</p>
77+
) : (
78+
<p>Drag & drop a CSV file here, or click to select one</p>
79+
)}
80+
</div>
81+
<div className="mt-2">
82+
<button
83+
className="text-blue-500 hover:underline text-sm"
84+
onClick={() => {
85+
const headers = "address,unit,amount\n";
86+
const rows = recipientAddresses.map((address, index) => {
87+
const unit = assets[index] === "ADA" ? "" : assets[index];
88+
const amount = amounts[index] || "";
89+
return `${address},${unit},${amount}`;
90+
});
91+
const csvContent = headers + rows.join("\n");
92+
93+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
94+
const url = URL.createObjectURL(blob);
95+
96+
const link = document.createElement("a");
97+
link.setAttribute("href", url);
98+
link.setAttribute("download", "recipients.csv");
99+
document.body.appendChild(link);
100+
link.click();
101+
document.body.removeChild(link);
102+
}}
103+
>
104+
Download current recipients as CSV
105+
</button>
106+
</div>
107+
</>
108+
);
109+
}

src/components/pages/wallet/new-transaction/RecipientRow.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ function RecipientRow({
159159
const newAmounts = [...amounts];
160160
newAmounts.splice(index, 1);
161161
setAmounts(newAmounts);
162+
const newAssets = [...assets];
163+
newAssets.splice(index, 1);
164+
setAssets(newAssets);
162165
}}
163166
>
164167
<X className="h-4 w-4" />

src/components/pages/wallet/new-transaction/index.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
} from "@meshsdk/core";
1111
import { useWallet } from "@meshsdk/react";
1212

13-
// import { api } from "@/utils/api";
14-
1513
import useTransaction from "@/hooks/useTransaction";
1614
import { toast, useToast } from "@/hooks/use-toast";
1715
import useAppWallet from "@/hooks/useAppWallet";
@@ -49,6 +47,7 @@ import {
4947
} from "@/components/ui/hover-card";
5048
import UTxOSelector from "./utxoSelector";
5149
import RecipientRow from "./RecipientRow";
50+
import RecipientCsv from "./RecipientCsv";
5251

5352
export default function PageNewTransaction() {
5453
const { connected } = useWallet();
@@ -101,7 +100,8 @@ export default function PageNewTransaction() {
101100
if (!connected) throw new Error("Wallet not connected");
102101
if (!appWallet) throw new Error("Wallet not found");
103102
if (!userAddress) throw new Error("User address not found");
104-
103+
console.log(amounts)
104+
console.log(assets)
105105
setLoading(true);
106106
setError(undefined);
107107

@@ -264,6 +264,14 @@ export default function PageNewTransaction() {
264264
<SectionTitle>New Transaction</SectionTitle>
265265

266266
<CardUI title="Recipients" cardClassName="w-full">
267+
<RecipientCsv
268+
setRecipientAddresses={setRecipientAddresses}
269+
setAmounts={setAmounts}
270+
setAssets={setAssets}
271+
recipientAddresses={recipientAddresses}
272+
amounts={amounts}
273+
assets={assets}
274+
/>
267275
<Table>
268276
<TableHeader>
269277
<TableRow>
@@ -434,4 +442,3 @@ export default function PageNewTransaction() {
434442
</main>
435443
);
436444
}
437-

0 commit comments

Comments
 (0)