@@ -8,11 +8,14 @@ import { searchAddressHistory, searchAmountHistory } from "@/lib/invoiceHistory"
88import { searchRecipients , touchRecipient , type RecipientEntry } from "@/lib/recipients" ;
99import { truncateAddress } from "@stellar-split/sdk" ;
1010import CsvRecipientImport from "@/components/CsvRecipientImport" ;
11+ import { useEmailValidation } from "@/hooks/useEmailValidation" ;
12+ import EmailField from "@/components/EmailField" ;
1113
1214export interface RecipientRow {
1315 address : string ;
1416 amount : string ;
1517 label ?: string ;
18+ email ?: string ;
1619}
1720
1821interface Props {
@@ -67,9 +70,9 @@ export default function RecipientForm({
6770 } ) . filter ( ( r ) => r . address ) ;
6871 } ;
6972
70- const updateRow = ( index : number , address : string , label ?: string ) => {
73+ const updateRow = ( index : number , address : string , label ?: string , email ?: string ) => {
7174 const next = recipients . map ( ( r , i ) =>
72- i === index ? { ...r , address, label : label !== undefined ? label : r . label } : r
75+ i === index ? { ...r , address, label : label !== undefined ? label : r . label , email : email !== undefined ? email : r . email } : r
7376 ) ;
7477 onChange ( next ) ;
7578 } ;
@@ -168,71 +171,84 @@ export default function RecipientForm({
168171 return (
169172 < div className = "flex flex-col gap-3" >
170173 { recipients . map ( ( row , i ) => (
171- < div key = { i } className = "flex flex-col sm:flex-row gap-2 items-stretch sm:items-start min-w-0" >
172- < Avatar
173- address = { row . address }
174- email = { emailByAddress [ row . address ] }
175- size = { 32 }
176- className = "mt-1.5 hidden sm:inline-flex"
177- />
178-
179- < div className = "relative flex-1 min-w-0 w-full" >
180- < AddressBookPicker
181- value = { row . address }
182- label = { row . label }
183- onChange = { ( address , label ) => updateRow ( i , address , label ) }
184- placeholder = "G... or name*domain.com address"
185- ariaLabel = { `Recipient ${ i + 1 } address` }
174+ < div key = { i } className = "flex flex-col gap-2" >
175+ < div className = "flex flex-col sm:flex-row gap-2 items-stretch sm:items-start min-w-0" >
176+ < Avatar
177+ address = { row . address }
178+ email = { emailByAddress [ row . address ] }
179+ size = { 32 }
180+ className = "mt-1.5 hidden sm:inline-flex"
186181 />
187- </ div >
188182
189- < div className = "relative w-full sm:w-28" >
190- < input
191- type = "number"
192- placeholder = "USDC"
193- step = "0.0000001"
194- min = "0.0000001"
195- value = { equalSplit ? ( amountOverride ?? "" ) : row . amount }
196- onChange = {
197- equalSplit ? undefined : ( e ) => handleAmountChange ( i , e . target . value )
198- }
199- onFocus = { ( ) => ! equalSplit && handleAmountFocus ( i ) }
200- readOnly = { equalSplit }
201- required
202- aria-label = { `Recipient ${ i + 1 } amount` }
203- className = { `w-full bg-gray-800 border rounded-lg px-3 py-2 min-h-11 text-sm text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 ${
204- equalSplit
205- ? "border-gray-600 text-gray-400 cursor-not-allowed"
206- : "border-gray-700"
207- } `}
208- />
209- { activeField === "amount" && activeIndex === i && amountSuggestions . length > 0 && ! equalSplit && (
210- < ul className = "absolute z-10 right-0 w-full bg-gray-800 border border-gray-700 rounded-lg mt-1 max-h-40 overflow-y-auto shadow-lg" >
211- { amountSuggestions . map ( ( amount ) => (
212- < li key = { amount } >
213- < button
214- type = "button"
215- onMouseDown = { ( ) => selectAmountSuggestion ( i , amount ) }
216- className = "w-full min-h-11 text-left px-3 py-2 text-sm hover:bg-gray-700 font-mono text-gray-200"
217- >
218- { amount } USDC
219- </ button >
220- </ li >
221- ) ) }
222- </ ul >
183+ < div className = "relative flex-1 min-w-0 w-full" >
184+ < AddressBookPicker
185+ value = { row . address }
186+ label = { row . label }
187+ onChange = { ( address , label ) => updateRow ( i , address , label , row . email ) }
188+ placeholder = "G... or name*domain.com address"
189+ ariaLabel = { `Recipient ${ i + 1 } address` }
190+ />
191+ </ div >
192+
193+ < div className = "relative w-full sm:w-28" >
194+ < input
195+ type = "number"
196+ placeholder = "USDC"
197+ step = "0.0000001"
198+ min = "0.0000001"
199+ value = { equalSplit ? ( amountOverride ?? "" ) : row . amount }
200+ onChange = {
201+ equalSplit ? undefined : ( e ) => handleAmountChange ( i , e . target . value )
202+ }
203+ onFocus = { ( ) => ! equalSplit && handleAmountFocus ( i ) }
204+ readOnly = { equalSplit }
205+ required
206+ aria-label = { `Recipient ${ i + 1 } amount` }
207+ className = { `w-full bg-gray-800 border rounded-lg px-3 py-2 min-h-11 text-sm text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 ${
208+ equalSplit
209+ ? "border-gray-600 text-gray-400 cursor-not-allowed"
210+ : "border-gray-700"
211+ } `}
212+ />
213+ { activeField === "amount" && activeIndex === i && amountSuggestions . length > 0 && ! equalSplit && (
214+ < ul className = "absolute z-10 right-0 w-full bg-gray-800 border border-gray-700 rounded-lg mt-1 max-h-40 overflow-y-auto shadow-lg" >
215+ { amountSuggestions . map ( ( amount ) => (
216+ < li key = { amount } >
217+ < button
218+ type = "button"
219+ onMouseDown = { ( ) => selectAmountSuggestion ( i , amount ) }
220+ className = "w-full min-h-11 text-left px-3 py-2 text-sm hover:bg-gray-700 font-mono text-gray-200"
221+ >
222+ { amount } USDC
223+ </ button >
224+ </ li >
225+ ) ) }
226+ </ ul >
227+ ) }
228+ </ div >
229+
230+ { recipients . length > 1 && (
231+ < button
232+ type = "button"
233+ onClick = { ( ) => removeRow ( i ) }
234+ aria-label = { `Remove recipient ${ i + 1 } ` }
235+ className = "min-h-11 px-3 py-2 rounded-lg bg-gray-700 hover:bg-red-700 text-sm transition-colors sm:self-start focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
236+ >
237+ ✕
238+ </ button >
223239 ) }
224240 </ div >
225241
226- { recipients . length > 1 && (
227- < button
228- type = "button"
229- onClick = { ( ) => removeRow ( i ) }
230- aria-label = { `Remove recipient ${ i + 1 } ` }
231- className = "min-h-11 px-3 py-2 rounded-lg bg-gray-700 hover:bg-red-700 text-sm transition-colors sm:self-start focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500"
232- >
233- ✕
234- </ button >
235- ) }
242+ < div className = "flex items-center gap-2 min-w-0 w-full" >
243+ < div className = "hidden sm:block w-8" />
244+ < div className = "flex-1" >
245+ < EmailField
246+ email = { row . email || "" }
247+ onEmailChange = { ( email ) => updateRow ( i , row . address , row . label , email ) }
248+ onBlur = { ( ) => { } }
249+ />
250+ </ div >
251+ </ div >
236252 </ div >
237253 ) ) }
238254
0 commit comments