@@ -28,16 +28,31 @@ export function AddForm({ carts, initial }: { carts: CartOpt[]; initial: ParsedS
2828 const [ busy , setBusy ] = useState ( false ) ;
2929 const [ cartBusy , setCartBusy ] = useState ( false ) ;
3030 const [ uploading , setUploading ] = useState ( false ) ;
31+ const [ pasteText , setPasteText ] = useState ( "" ) ;
3132 const [ added , setAdded ] = useState < CartOpt | null > ( null ) ;
3233 const [ newCartTitle , setNewCartTitle ] = useState ( "" ) ;
3334
34- const onPaste = ( raw : string ) => {
35+ // Apply pasted/typed text → extract link, title, price into the form.
36+ const applyPasted = ( raw : string ) => {
37+ setPasteText ( raw ) ;
3538 const p = parseShare ( { text : raw , url : raw } ) ;
3639 if ( p . productUrl ) setOriginalUrl ( p . productUrl ) ;
3740 if ( p . title ) setTitle ( p . title ) ;
3841 if ( p . priceText ) setPriceText ( p . priceText ) ;
3942 } ;
4043
44+ // Explicit clipboard read — the reliable path on iOS Safari, where a
45+ // textarea's onChange often doesn't fire for a long-press "Paste".
46+ const pasteFromClipboard = async ( ) => {
47+ try {
48+ const t = await navigator . clipboard . readText ( ) ;
49+ if ( t . trim ( ) ) applyPasted ( t ) ;
50+ else toast . error ( "Clipboard is empty — copy the product link first." ) ;
51+ } catch {
52+ toast . error ( "Couldn't read the clipboard — paste into the box instead." ) ;
53+ }
54+ } ;
55+
4156 const onPickPhoto = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
4257 const file = e . target . files ?. [ 0 ] ;
4358 e . target . value = "" ; // allow re-picking the same file
@@ -125,17 +140,30 @@ export function AddForm({ carts, initial }: { carts: CartOpt[]; initial: ParsedS
125140
126141 return (
127142 < div className = "space-y-4" >
128- { ! originalUrl && (
129- < label className = "block" >
130- < span className = "block text-sm font-medium mb-1" > Paste a product link</ span >
131- < textarea
132- rows = { 2 }
133- onChange = { ( e ) => onPaste ( e . target . value ) }
134- placeholder = { 'Paste the link or the whole “Check out this product…” text' }
135- className = { inputCls }
136- />
137- </ label >
138- ) }
143+ < div className = "block" >
144+ < span className = "block text-sm font-medium mb-1" > Paste a product link</ span >
145+ < textarea
146+ rows = { 2 }
147+ value = { pasteText }
148+ onChange = { ( e ) => applyPasted ( e . target . value ) }
149+ onPaste = { ( e ) => {
150+ // Capture the paste directly — iOS Safari doesn't reliably fire
151+ // onChange for a long-press paste. preventDefault so we set the
152+ // value ourselves (no double insert).
153+ e . preventDefault ( ) ;
154+ applyPasted ( e . clipboardData . getData ( "text" ) ) ;
155+ } }
156+ placeholder = { 'Paste the link or the whole “Check out this product…” text' }
157+ className = { inputCls }
158+ />
159+ < button
160+ type = "button"
161+ onClick = { pasteFromClipboard }
162+ className = "mt-2 inline-flex items-center gap-2 rounded-full border border-ink px-4 py-2 text-sm font-medium hover:bg-paper"
163+ >
164+ 📋 Paste from clipboard
165+ </ button >
166+ </ div >
139167
140168 < label className = "block" >
141169 < span className = "block text-sm font-medium mb-1" > Cart</ span >
0 commit comments