Skip to content

Commit b68a941

Browse files
feat: add Equal Split toggle to new invoice form
1 parent 109e79a commit b68a941

2 files changed

Lines changed: 78 additions & 8 deletions

File tree

src/app/invoice/new/page.tsx

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ export default function NewInvoicePage() {
2626
);
2727
const [submitting, setSubmitting] = useState(false);
2828
const [error, setError] = useState<string | null>(null);
29+
const [equalSplit, setEqualSplit] = useState(false);
30+
const [totalAmount, setTotalAmount] = useState("");
31+
32+
const perRecipientAmount =
33+
equalSplit && totalAmount && recipients.length > 0
34+
? (parseFloat(totalAmount) / recipients.length).toFixed(7)
35+
: undefined;
2936

3037
const handleSubmit = async (e: React.FormEvent) => {
3138
e.preventDefault();
@@ -39,7 +46,7 @@ export default function NewInvoicePage() {
3946
creator,
4047
recipients: recipients.map((r) => ({
4148
address: r.address,
42-
amount: parseAmount(r.amount),
49+
amount: parseAmount(equalSplit ? (perRecipientAmount ?? "0") : r.amount),
4350
})),
4451
token,
4552
deadline: deadlineFromDays(deadlineDays),
@@ -58,12 +65,61 @@ export default function NewInvoicePage() {
5865
<h1 className="text-3xl font-bold mb-8">Create Invoice</h1>
5966

6067
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
68+
{/* Equal Split toggle */}
69+
<div className="flex items-center justify-between rounded-lg bg-gray-800 border border-gray-700 px-4 py-3">
70+
<span className="text-sm font-medium text-gray-300">Equal Split</span>
71+
<button
72+
type="button"
73+
role="switch"
74+
aria-checked={equalSplit}
75+
onClick={() => setEqualSplit((v) => !v)}
76+
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 ${
77+
equalSplit ? "bg-indigo-600" : "bg-gray-600"
78+
}`}
79+
>
80+
<span
81+
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
82+
equalSplit ? "translate-x-6" : "translate-x-1"
83+
}`}
84+
/>
85+
</button>
86+
</div>
87+
88+
{/* Total amount input (equal split mode) */}
89+
{equalSplit && (
90+
<div>
91+
<label className="block text-sm font-medium text-gray-300 mb-1">
92+
Total Amount (USDC)
93+
</label>
94+
<input
95+
type="number"
96+
placeholder="0.00"
97+
step="0.0000001"
98+
min="0.0000001"
99+
value={totalAmount}
100+
onChange={(e) => setTotalAmount(e.target.value)}
101+
required
102+
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
103+
/>
104+
{perRecipientAmount && (
105+
<p className="mt-1 text-xs text-gray-400">
106+
{perRecipientAmount} USDC per recipient
107+
</p>
108+
)}
109+
</div>
110+
)}
111+
61112
{/* Recipients */}
62113
<div>
63114
<label className="block text-sm font-medium text-gray-300 mb-2">
64-
Recipients &amp; Amounts (USDC)
115+
Recipients {equalSplit ? "" : "& Amounts (USDC)"}
65116
</label>
66-
<RecipientForm recipients={recipients} onChange={setRecipients} />
117+
<RecipientForm
118+
recipients={recipients}
119+
onChange={setRecipients}
120+
equalSplit={equalSplit}
121+
amountOverride={perRecipientAmount}
122+
/>
67123
</div>
68124

69125
{/* Token address */}

src/components/RecipientForm.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ interface RecipientRow {
88
interface Props {
99
recipients: RecipientRow[];
1010
onChange: (rows: RecipientRow[]) => void;
11+
equalSplit?: boolean;
12+
amountOverride?: string;
1113
}
1214

1315
/**
1416
* RecipientForm — dynamic add/remove rows for recipients and split amounts.
1517
*/
16-
export default function RecipientForm({ recipients, onChange }: Props) {
18+
export default function RecipientForm({
19+
recipients,
20+
onChange,
21+
equalSplit = false,
22+
amountOverride,
23+
}: Props) {
1724
const update = (index: number, field: keyof RecipientRow, value: string) => {
1825
const next = recipients.map((r, i) =>
1926
i === index ? { ...r, [field]: value } : r
@@ -37,18 +44,25 @@ export default function RecipientForm({ recipients, onChange }: Props) {
3744
onChange={(e) => update(i, "address", e.target.value)}
3845
required
3946
aria-label={`Recipient ${i + 1} address`}
40-
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono"
47+
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono min-w-0"
4148
/>
4249
<input
4350
type="number"
4451
placeholder="USDC"
4552
step="0.0000001"
4653
min="0.0000001"
47-
value={row.amount}
48-
onChange={(e) => update(i, "amount", e.target.value)}
54+
value={equalSplit ? (amountOverride ?? "") : row.amount}
55+
onChange={
56+
equalSplit ? undefined : (e) => update(i, "amount", e.target.value)
57+
}
58+
readOnly={equalSplit}
4959
required
5060
aria-label={`Recipient ${i + 1} amount`}
51-
className="w-28 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
61+
className={`w-28 bg-gray-800 border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 ${
62+
equalSplit
63+
? "border-gray-600 text-gray-400 cursor-not-allowed"
64+
: "border-gray-700"
65+
}`}
5266
/>
5367
{recipients.length > 1 && (
5468
<button

0 commit comments

Comments
 (0)