1- import { SelectChangeEvent } from '@mui/material' ;
1+ import { CircularProgress , SelectChangeEvent } from '@mui/material' ;
22import React , { useEffect , useState } from 'react' ;
33import {
44 Avatar ,
@@ -50,10 +50,80 @@ interface User {
5050interface AccessListProps {
5151 accessList : User [ ] ;
5252 ownerData : User ;
53- handleDelete : ( email : string ) => void ;
53+ handleDelete : ( email : string ) => Promise < { error : string } > ;
5454 hostURL ?: string | null ;
5555}
5656
57+ interface AccessListActorProps {
58+ actor : User ;
59+ isOwner : boolean ;
60+ hostURL ?: string | null ;
61+ handleDelete : ( email : string ) => Promise < { error : string } > ;
62+ }
63+
64+ const AccessListActor : React . FC < AccessListActorProps > = ( { actor : actorData , hostURL, isOwner, handleDelete } ) => {
65+ const theme = useTheme ( )
66+ const [ isRevoking , setIsRevoking ] = useState ( false )
67+ const revokeAcess = async ( ) => {
68+ setIsRevoking ( true )
69+ try {
70+ const res = await handleDelete ( actorData . email )
71+ }
72+ finally {
73+ setIsRevoking ( false )
74+ }
75+ }
76+
77+ const openInNewTab = ( url : string ) => {
78+ window . open ( url , '_blank' , 'noreferrer' ) ;
79+ } ;
80+
81+ return (
82+ < ListItem key = { actorData . id } style = { { paddingLeft : '0' } } >
83+ < ListItemAvatar >
84+ < Avatar
85+ alt = { actorData . first_name }
86+ src = { actorData . avatar_url }
87+ imgProps = { { referrerPolicy : 'no-referrer' } }
88+ onClick = { ( ) => {
89+ hostURL && openInNewTab ( `${ hostURL } /user/${ actorData . id } ` ) ;
90+ } }
91+ />
92+ </ ListItemAvatar >
93+ < ListItemText
94+ primary = { `${ actorData . first_name || '' } ${ actorData . last_name || '' } ` }
95+ secondary = { actorData . email }
96+ secondaryTypographyProps = { {
97+ sx : {
98+ color : theme . palette . background . neutral ?. pressed
99+ }
100+ } }
101+ />
102+ < ListItemSecondaryAction >
103+ { isOwner && (
104+ < div > Owner</ div >
105+ ) }
106+
107+ { ! isOwner && ! isRevoking && (
108+ < CustomTooltip title = "Remove Access" placement = "top" arrow >
109+ < IconButton
110+ edge = "end"
111+ aria-label = "delete"
112+ onClick = { revokeAcess }
113+ >
114+ < DeleteIcon fill = { theme . palette . background . neutral ?. default } />
115+ </ IconButton >
116+ </ CustomTooltip >
117+ ) }
118+
119+ { isRevoking && < CircularProgress size = { 24 } sx = { { color : theme . palette . icon . default } } /> }
120+
121+ </ ListItemSecondaryAction >
122+ </ ListItem > )
123+ }
124+
125+
126+
57127/**
58128 * Custom component to show users list with delete icon and owner tag
59129 */
@@ -63,11 +133,7 @@ const AccessList: React.FC<AccessListProps> = ({
63133 handleDelete,
64134 hostURL
65135} : AccessListProps ) => {
66- const openInNewTab = ( url : string ) => {
67- window . open ( url , '_blank' , 'noreferrer' ) ;
68- } ;
69136
70- const theme = useTheme ( ) ;
71137
72138 return (
73139 < >
@@ -78,44 +144,8 @@ const AccessList: React.FC<AccessListProps> = ({
78144 ) }
79145 < ListWrapper >
80146 < List dense >
81- { accessList . map ( ( actorData ) => (
82- < ListItem key = { actorData . id } style = { { paddingLeft : '0' } } >
83- < ListItemAvatar >
84- < Avatar
85- alt = { actorData . first_name }
86- src = { actorData . avatar_url }
87- imgProps = { { referrerPolicy : 'no-referrer' } }
88- onClick = { ( ) => {
89- hostURL && openInNewTab ( `${ hostURL } /user/${ actorData . id } ` ) ;
90- } }
91- />
92- </ ListItemAvatar >
93- < ListItemText
94- primary = { `${ actorData . first_name || '' } ${ actorData . last_name || '' } ` }
95- secondary = { actorData . email }
96- secondaryTypographyProps = { {
97- sx : {
98- color : theme . palette . background . neutral ?. pressed
99- }
100- } }
101- />
102- < ListItemSecondaryAction >
103- { ownerData . id === actorData . id ? (
104- < div > Owner</ div >
105- ) : (
106- < CustomTooltip title = "Remove Access" placement = "top" arrow >
107- < IconButton
108- edge = "end"
109- aria-label = "delete"
110- onClick = { ( ) => handleDelete ( actorData . email ) }
111- >
112- < DeleteIcon fill = { theme . palette . background . neutral ?. default } />
113- </ IconButton >
114- </ CustomTooltip >
115- ) }
116- </ ListItemSecondaryAction >
117- </ ListItem >
118- ) ) }
147+ { accessList . map ( ( actorData ) => ( < AccessListActor hostURL = { hostURL } key = { actorData . id }
148+ actor = { actorData } handleDelete = { handleDelete } isOwner = { ownerData . id == actorData . id } /> ) ) }
119149 </ List >
120150 </ ListWrapper >
121151 </ >
@@ -139,8 +169,6 @@ interface ShareModalProps {
139169 ownerData : User ;
140170 /** Function to fetch the list of users who have access to the resource */
141171 fetchAccessActors : ( ) => Promise < User [ ] > ;
142- /** Function to handle the sharing of the resource with specified users and options */
143- handleShare : ( shareUserData : User [ ] , selectedOption : string | undefined ) => void ;
144172 /** Optional URL of the host application. Defaults to `null` if not provided */
145173 hostURL ?: string | null ;
146174 /**
@@ -157,11 +185,12 @@ interface ShareModalProps {
157185 */
158186 fetchSuggestions : ( value : string ) => Promise < User [ ] > ;
159187 handleCopy : ( ) => void ;
160- handleUpdateVisibility : ( value : string ) => Promise < { error :string } > ,
161- isUpdatingVisibility : boolean ,
162- handleShareWithNewUsers : ( newUsers : User [ ] ) => Promise < { error :string } > ,
163- // isSharing : boolean
164- canShareWithNewUsers : boolean
188+ handleUpdateVisibility : ( value : string ) => Promise < { error : string } > ,
189+ isUpdatingVisibility : boolean ,
190+ handleShareWithNewUsers : ( newUsers : User [ ] ) => Promise < { error : string } > ,
191+ canShareWithNewUsers : boolean ,
192+ handleRevokeAccess : ( revokedUsser : User [ ] ) => Promise < { error : string } >
193+ canRevokeAccess : boolean ,
165194}
166195
167196/**
@@ -174,54 +203,45 @@ const ShareModal: React.FC<ShareModalProps> = ({
174203 dataName,
175204 ownerData,
176205 fetchAccessActors,
177- handleShare,
178206 hostURL = null ,
179207 handleCopy,
180208 handleUpdateVisibility,
181209 isUpdatingVisibility,
182210 canShareWithNewUsers,
211+ handleRevokeAccess,
183212 handleShareWithNewUsers,
184213 isVisibilitySelectorDisabled = false ,
185214 fetchSuggestions
186215} : ShareModalProps ) : JSX . Element => {
187- console . log ( "new share modal new" )
188- const theme = useTheme ( ) ;
216+ const theme = useTheme ( ) ;
189217 const [ openMenu , setMenu ] = useState < boolean > ( false ) ;
190- const [ selectedOption , setOption ] = useState < string | undefined > ( selectedResource ?. visibility ) ;
191218 const [ shareUserData , setShareUserData ] = useState < User [ ] > ( [ ] ) ;
219+ const [ resourceVisibility , setVisibility ] = useState ( selectedResource . visibility )
192220
193- const handleDelete = ( email : string ) => {
194- setShareUserData ( ( prevData ) => prevData . filter ( ( user ) => user . email !== email ) ) ;
221+ const handleDelete = async ( email : string ) => {
222+ const revoked = shareUserData . find ( user => user . email == email )
223+ if ( ! revoked ) {
224+ console . error ( "cant revoke user without acesss" )
225+ return { error :"" }
226+ }
227+ return handleRevokeAccess ( [ revoked ] )
195228 } ;
196229
197- const handleOptionClick = ( event : SelectChangeEvent < unknown > ) => {
230+ const handleOptionClick = async ( event : SelectChangeEvent < unknown > ) => {
198231 const value = event . target . value as string ;
199- setOption ( value ) ;
200- handleUpdateVisibility ( value )
201- } ;
202-
203- const handleMenuClose = ( ) => setMenu ( false ) ;
204-
205- const isShareDisabled = ( ) => {
206- // Ensure at least one user other than the owner is selected
207- const otherUsersSelected = shareUserData . some ( ( user ) => user . id !== ownerData . id ) ;
208-
209- const existingAccessIds = shareUserData . map ( ( user ) => user . id ) ;
210- const ownerDataId = ownerData ?. id ;
232+ if ( value == resourceVisibility ) {
233+ console . error ( "visibility is already " , value )
234+ return
235+ }
211236
212- if ( ownerDataId ) {
213- existingAccessIds . push ( ownerDataId ) ;
237+ const res = await handleUpdateVisibility ( value )
238+ if ( ! res ?. error ) {
239+ setVisibility ( value )
214240 }
241+ } ;
215242
216- const hasMismatchedUsers = ! shareUserData . every ( ( user ) => existingAccessIds . includes ( user . id ) ) ;
243+ const handleMenuClose = ( ) => setMenu ( false ) ;
217244
218- return (
219- ! otherUsersSelected || // Disable if no other users are selected
220- ( shareUserData . length === existingAccessIds . length &&
221- ! hasMismatchedUsers &&
222- selectedOption === selectedResource ?. visibility )
223- ) ;
224- } ;
225245
226246 useEffect ( ( ) => {
227247 const fetchActors = async ( ) => {
@@ -231,11 +251,7 @@ const ShareModal: React.FC<ShareModalProps> = ({
231251 fetchActors ( ) ;
232252 } , [ fetchAccessActors ] ) ;
233253
234- useEffect ( ( ) => {
235- if ( selectedResource ) {
236- setOption ( selectedResource ?. visibility ) ;
237- }
238- } , [ selectedResource ] ) ;
254+
239255
240256 return (
241257 < div style = { { marginBottom : '1rem' } } >
@@ -270,7 +286,7 @@ const ShareModal: React.FC<ShareModalProps> = ({
270286 hostURL = { hostURL }
271287 />
272288
273- { selectedResource ?. visibility !== 'published' && (
289+ { resourceVisibility !== 'published' && (
274290 < >
275291 < CustomListItemText >
276292 < Typography variant = "h6" > General Access</ Typography >
@@ -279,14 +295,20 @@ const ShareModal: React.FC<ShareModalProps> = ({
279295 < FormControlWrapper size = "small" >
280296 < div style = { { display : 'flex' , justifyContent : 'start' , alignItems : 'center' } } >
281297 < VisibilityIconWrapper >
282- { selectedOption === SHARE_MODE . PUBLIC ? (
298+
299+ { isUpdatingVisibility && < CircularProgress size = { 24 } /> }
300+
301+
302+ { ! isUpdatingVisibility && resourceVisibility === SHARE_MODE . PUBLIC && (
283303 < PublicIcon
284304 width = { 24 }
285305 height = { 24 }
286306 fill = { theme . palette . icon . default }
287307 stroke = { theme . palette . mode === 'dark' ? WHITE : BLACK }
288308 />
289- ) : (
309+ ) }
310+
311+ { ! isUpdatingVisibility && resourceVisibility == SHARE_MODE . PRIVATE && (
290312 < LockIcon
291313 width = { 24 }
292314 height = { 24 }
@@ -298,19 +320,19 @@ const ShareModal: React.FC<ShareModalProps> = ({
298320 < div style = { { display : 'flex' , flexDirection : 'column' } } >
299321 < CustomSelect
300322 variant = "outlined"
301- defaultValue = { selectedOption }
323+ value = { resourceVisibility }
302324 labelId = "share-menu-select"
303325 id = "share-menu"
304326 open = { openMenu }
305327 onClose = { handleMenuClose }
306328 onOpen = { ( ) => setMenu ( true ) }
307329 onChange = { handleOptionClick }
308- disabled = { isVisibilitySelectorDisabled }
330+ disabled = { isVisibilitySelectorDisabled || isUpdatingVisibility }
309331 >
310332 { Object . values ( SHARE_MODE ) . map ( ( option ) => (
311333 < MenuItem
312334 key = { option }
313- selected = { option === selectedOption }
335+ selected = { option === resourceVisibility }
314336 value = { option }
315337 >
316338 { option . charAt ( 0 ) . toUpperCase ( ) + option . slice ( 1 ) }
@@ -324,7 +346,7 @@ const ShareModal: React.FC<ShareModalProps> = ({
324346 component = "span"
325347 variant = "body2"
326348 >
327- { selectedOption === SHARE_MODE . PRIVATE ? options . PRIVATE : options . PUBLIC }
349+ { resourceVisibility === SHARE_MODE . PRIVATE ? options . PRIVATE : options . PUBLIC }
328350 </ Typography >
329351 </ div >
330352 </ div >
0 commit comments