1+ import { Fragment , useState } from 'react' ;
2+ import { Dialog , Transition } from '@headlessui/react' ;
3+ import { XMarkIcon } from '@heroicons/react/24/outline' ;
4+ import { ShareIcon , CheckCircleIcon , ExclamationCircleIcon } from '@heroicons/react/20/solid' ;
5+ import UserSearchCombobox from './UserSearchCombobox' ;
6+ import { shareItem } from '../utils/swr/fetchers' ;
7+ import useGraffiticodeAuth from '../hooks/use-graffiticode-auth' ;
8+
9+ interface ShareItemDialogProps {
10+ isOpen : boolean ;
11+ onClose : ( ) => void ;
12+ itemId : string ;
13+ itemName : string ;
14+ sharedWith ?: string [ ] ;
15+ onShareSuccess ?: ( ) => void ;
16+ }
17+
18+ export default function ShareItemDialog ( {
19+ isOpen,
20+ onClose,
21+ itemId,
22+ itemName,
23+ sharedWith = [ ] ,
24+ onShareSuccess
25+ } : ShareItemDialogProps ) {
26+ const { user } = useGraffiticodeAuth ( ) ;
27+ const [ selectedUser , setSelectedUser ] = useState < any > ( null ) ;
28+ const [ isSharing , setIsSharing ] = useState ( false ) ;
29+ const [ shareResult , setShareResult ] = useState < { success : boolean ; message : string } | null > ( null ) ;
30+
31+ const handleShare = async ( ) => {
32+ if ( ! selectedUser || ! user ) return ;
33+
34+ setIsSharing ( true ) ;
35+ setShareResult ( null ) ;
36+
37+ try {
38+ const result = await shareItem ( {
39+ user,
40+ itemId,
41+ targetUserId : selectedUser . id
42+ } ) ;
43+
44+ setShareResult ( {
45+ success : result . success ,
46+ message : result . message || ( result . success ? 'Item shared successfully!' : 'Failed to share item' )
47+ } ) ;
48+
49+ if ( result . success ) {
50+ // Clear selection for next share
51+ setSelectedUser ( null ) ;
52+ // Notify parent of success
53+ if ( onShareSuccess ) {
54+ onShareSuccess ( ) ;
55+ }
56+ // Auto-close after success
57+ setTimeout ( ( ) => {
58+ onClose ( ) ;
59+ // Reset state after dialog closes
60+ setTimeout ( ( ) => {
61+ setShareResult ( null ) ;
62+ } , 300 ) ;
63+ } , 1500 ) ;
64+ }
65+ } catch ( error ) {
66+ setShareResult ( {
67+ success : false ,
68+ message : 'An error occurred while sharing the item'
69+ } ) ;
70+ } finally {
71+ setIsSharing ( false ) ;
72+ }
73+ } ;
74+
75+ const handleClose = ( ) => {
76+ // Reset state when closing
77+ setSelectedUser ( null ) ;
78+ setShareResult ( null ) ;
79+ onClose ( ) ;
80+ } ;
81+
82+ return (
83+ < Transition . Root show = { isOpen } as = { Fragment } >
84+ < Dialog as = "div" className = "relative z-50" onClose = { handleClose } >
85+ < Transition . Child
86+ as = { Fragment }
87+ enter = "ease-out duration-300"
88+ enterFrom = "opacity-0"
89+ enterTo = "opacity-100"
90+ leave = "ease-in duration-200"
91+ leaveFrom = "opacity-100"
92+ leaveTo = "opacity-0"
93+ >
94+ < div className = "fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
95+ </ Transition . Child >
96+
97+ < div className = "fixed inset-0 z-10 overflow-y-auto" >
98+ < div className = "flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0" >
99+ < Transition . Child
100+ as = { Fragment }
101+ enter = "ease-out duration-300"
102+ enterFrom = "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
103+ enterTo = "opacity-100 translate-y-0 sm:scale-100"
104+ leave = "ease-in duration-200"
105+ leaveFrom = "opacity-100 translate-y-0 sm:scale-100"
106+ leaveTo = "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
107+ >
108+ < Dialog . Panel className = "relative transform overflow-hidden rounded-none bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6" >
109+ < div className = "absolute right-0 top-0 hidden pr-4 pt-4 sm:block" >
110+ < button
111+ type = "button"
112+ className = "rounded-none bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
113+ onClick = { handleClose }
114+ >
115+ < span className = "sr-only" > Close</ span >
116+ < XMarkIcon className = "h-6 w-6" aria-hidden = "true" />
117+ </ button >
118+ </ div >
119+ < div className = "sm:flex sm:items-start" >
120+ < div className = "mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-none bg-gray-100 sm:mx-0 sm:h-10 sm:w-10" >
121+ < ShareIcon className = "h-6 w-6 text-gray-600" aria-hidden = "true" />
122+ </ div >
123+ < div className = "mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left w-full" >
124+ < Dialog . Title as = "h3" className = "text-base font-semibold leading-6 text-gray-900" >
125+ Share Item
126+ </ Dialog . Title >
127+ < div className = "mt-2" >
128+ < p className = "text-sm text-gray-500" >
129+ Share “{ itemName } ” with another user. The item will be copied to their account.
130+ </ p >
131+ </ div >
132+
133+ { sharedWith . length > 0 && (
134+ < div className = "mt-4" >
135+ < p className = "text-xs font-semibold text-gray-600 mb-1" > Already shared with:</ p >
136+ < div className = "text-xs text-gray-500" >
137+ { sharedWith . map ( ( userId , index ) => (
138+ < span key = { userId } >
139+ { userId }
140+ { index < sharedWith . length - 1 && ', ' }
141+ </ span >
142+ ) ) }
143+ </ div >
144+ </ div >
145+ ) }
146+
147+ < div className = "mt-4" >
148+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
149+ Select User
150+ </ label >
151+ < UserSearchCombobox
152+ selectedUser = { selectedUser }
153+ onSelectUser = { setSelectedUser }
154+ placeholder = "Search by user ID or email..."
155+ />
156+ </ div >
157+
158+ { shareResult && (
159+ < div className = { `mt-4 p-3 rounded-none ${ shareResult . success ? 'bg-green-50' : 'bg-red-50' } ` } >
160+ < div className = "flex" >
161+ < div className = "flex-shrink-0" >
162+ { shareResult . success ? (
163+ < CheckCircleIcon className = "h-5 w-5 text-green-400" aria-hidden = "true" />
164+ ) : (
165+ < ExclamationCircleIcon className = "h-5 w-5 text-red-400" aria-hidden = "true" />
166+ ) }
167+ </ div >
168+ < div className = "ml-3" >
169+ < p className = { `text-sm ${ shareResult . success ? 'text-green-800' : 'text-red-800' } ` } >
170+ { shareResult . message }
171+ </ p >
172+ </ div >
173+ </ div >
174+ </ div >
175+ ) }
176+ </ div >
177+ </ div >
178+ < div className = "mt-5 sm:mt-4 sm:flex sm:flex-row-reverse" >
179+ < button
180+ type = "button"
181+ disabled = { ! selectedUser || isSharing }
182+ onClick = { handleShare }
183+ className = "inline-flex w-full justify-center rounded-none bg-gray-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-700 disabled:bg-gray-300 disabled:cursor-not-allowed sm:ml-3 sm:w-auto"
184+ >
185+ { isSharing ? 'Sharing...' : 'Share' }
186+ </ button >
187+ < button
188+ type = "button"
189+ onClick = { handleClose }
190+ className = "mt-3 inline-flex w-full justify-center rounded-none bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
191+ >
192+ Cancel
193+ </ button >
194+ </ div >
195+ </ Dialog . Panel >
196+ </ Transition . Child >
197+ </ div >
198+ </ div >
199+ </ Dialog >
200+ </ Transition . Root >
201+ ) ;
202+ }
0 commit comments