1- import React , { useCallback , useEffect , useMemo } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
22import { useTranslation } from 'react-i18next' ;
33import { useNavigate , useParams } from 'react-router-dom' ;
44import { debounce } from 'lodash' ;
@@ -17,18 +17,21 @@ import {
1717 SelectCSD ,
1818 SpaceBetween ,
1919 StatusIndicator ,
20+ Toggle ,
2021} from 'components' ;
2122import { HotspotIds } from 'layouts/AppLayout/TutorialPanel/constants' ;
2223
2324import { useBreadcrumbs , useHelpPanel , useNotifications } from 'hooks' ;
2425import { riseRouterException } from 'libs' ;
2526import { ROUTES } from 'routes' ;
26- import { useGetProjectQuery , useUpdateProjectMembersMutation } from 'services/project' ;
27+ import { useGetProjectQuery , useUpdateProjectMembersMutation , useUpdateProjectMutation } from 'services/project' ;
2728
2829import { useCheckAvailableProjectPermission } from 'pages/Project/hooks/useCheckAvailableProjectPermission' ;
2930import { useConfigProjectCliCommand } from 'pages/Project/hooks/useConfigProjectCliComand' ;
3031import { useDeleteProject } from 'pages/Project/hooks/useDeleteProject' ;
3132import { ProjectMembers } from 'pages/Project/Members' ;
33+ import { getProjectRoleByUserName } from 'pages/Project/utils' ;
34+ import { useGetUserDataQuery } from 'services/user' ;
3235
3336import { useBackendsTable } from '../../Backends/hooks' ;
3437import { BackendsTable } from '../../Backends/Table' ;
@@ -51,23 +54,40 @@ export const ProjectSettings: React.FC = () => {
5154
5255 const [ pushNotification ] = useNotifications ( ) ;
5356 const [ updateProjectMembers ] = useUpdateProjectMembersMutation ( ) ;
57+ const [ updateProject ] = useUpdateProjectMutation ( ) ;
5458 const { deleteProject, isDeleting } = useDeleteProject ( ) ;
59+ const { data : currentUser } = useGetUserDataQuery ( { } ) ;
5560
5661 const { data, isLoading, error } = useGetProjectQuery ( { name : paramProjectName } ) ;
5762
5863 useEffect ( ( ) => {
59- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
60- // @ts -ignore
61- if ( error ?. status === 404 ) {
64+ if ( error && 'status' in error && error . status === 404 ) {
6265 riseRouterException ( ) ;
6366 }
6467 } , [ error ] ) ;
6568
69+ const currentUserRole = data ? getProjectRoleByUserName ( data , currentUser ?. username ?? '' ) : null ;
70+ const isProjectMember = currentUserRole !== null ;
71+
6672 const currentOwner = {
6773 label : data ?. owner . username ,
6874 value : data ?. owner . username ,
6975 } ;
7076
77+ const visibilityOptions = [
78+ { label : t ( 'projects.edit.visibility.private' ) || '' , value : 'private' } ,
79+ { label : t ( 'projects.edit.visibility.public' ) || '' , value : 'public' } ,
80+ ] ;
81+
82+ const currentVisibility = data ?. isPublic ? 'public' : 'private' ;
83+ const [ selectedVisibility , setSelectedVisibility ] = useState (
84+ data ?. isPublic ? visibilityOptions [ 1 ] : visibilityOptions [ 0 ]
85+ ) ;
86+
87+ useEffect ( ( ) => {
88+ setSelectedVisibility ( data ?. isPublic ? visibilityOptions [ 1 ] : visibilityOptions [ 0 ] ) ;
89+ } , [ data ] ) ;
90+
7191 const {
7292 data : backendsData ,
7393 isDeleting : isDeletingBackend ,
@@ -103,7 +123,7 @@ export const ProjectSettings: React.FC = () => {
103123 content : t ( 'projects.edit.update_members_success' ) ,
104124 } ) ;
105125 } )
106- . catch ( ( error ) => {
126+ . catch ( ( error : any ) => {
107127 pushNotification ( {
108128 type : 'error' ,
109129 content : t ( 'common.server_error' , { error : error ?. data ?. detail ?. msg } ) ,
@@ -113,14 +133,38 @@ export const ProjectSettings: React.FC = () => {
113133
114134 const debouncedMembersHandler = useCallback ( debounce ( changeMembersHandler , 1000 ) , [ ] ) ;
115135
136+ const changeVisibilityHandler = ( is_public : boolean ) => {
137+ updateProject ( {
138+ project_name : paramProjectName ,
139+ is_public : is_public ,
140+ } )
141+ . unwrap ( )
142+ . then ( ( ) => {
143+ pushNotification ( {
144+ type : 'success' ,
145+ content : t ( 'projects.edit.update_visibility_success' ) ,
146+ } ) ;
147+ } )
148+ . catch ( ( error : any ) => {
149+ pushNotification ( {
150+ type : 'error' ,
151+ content : t ( 'common.server_error' , { error : error ?. data ?. detail ?. msg } ) ,
152+ } ) ;
153+ } ) ;
154+ } ;
155+
116156 const isDisabledButtons = useMemo < boolean > ( ( ) => {
117157 return isDeleting || ! data || ! isAvailableDeletingPermission ( data ) ;
118158 } , [ data , isDeleting , isAvailableDeletingPermission ] ) ;
119159
120160 const deleteProjectHandler = ( ) => {
121161 if ( ! data ) return ;
122162
123- deleteProject ( data ) . then ( ( ) => navigate ( ROUTES . PROJECT . LIST ) ) ;
163+ deleteProject ( data )
164+ . then ( ( ) => navigate ( ROUTES . PROJECT . LIST ) )
165+ . catch ( ( error : any ) => {
166+ console . error ( 'Delete project failed:' , error ) ;
167+ } ) ;
124168 } ;
125169
126170 if ( isLoadingPage )
@@ -134,42 +178,44 @@ export const ProjectSettings: React.FC = () => {
134178 < >
135179 { data && backendsData && gatewaysData && (
136180 < SpaceBetween size = "l" >
137- < Container
138- header = {
139- < Header variant = "h2" info = { < InfoLink onFollow = { ( ) => openHelpPanel ( CLI_INFO ) } /> } >
140- { t ( 'projects.edit.cli' ) }
141- </ Header >
142- }
143- >
144- < SpaceBetween size = "s" >
145- < Box variant = "p" color = "text-body-secondary" >
146- Run the following commands to set up the CLI for this project
147- </ Box >
148-
149- < div className = { styles . codeWrapper } >
150- < Hotspot hotspotId = { HotspotIds . CONFIGURE_CLI_COMMAND } >
151- < Code className = { styles . code } > { configCliCommand } </ Code >
152-
153- < div className = { styles . copy } >
154- < Popover
155- dismissButton = { false }
156- position = "top"
157- size = "small"
158- triggerType = "custom"
159- content = { < StatusIndicator type = "success" > { t ( 'common.copied' ) } </ StatusIndicator > }
160- >
161- < Button
162- formAction = "none"
163- iconName = "copy"
164- variant = "normal"
165- onClick = { copyCliCommand }
166- />
167- </ Popover >
168- </ div >
169- </ Hotspot >
170- </ div >
171- </ SpaceBetween >
172- </ Container >
181+ { isProjectMember && (
182+ < Container
183+ header = {
184+ < Header variant = "h2" info = { < InfoLink onFollow = { ( ) => openHelpPanel ( CLI_INFO ) } /> } >
185+ { t ( 'projects.edit.cli' ) }
186+ </ Header >
187+ }
188+ >
189+ < SpaceBetween size = "s" >
190+ < Box variant = "p" color = "text-body-secondary" >
191+ Run the following commands to set up the CLI for this project
192+ </ Box >
193+
194+ < div className = { styles . codeWrapper } >
195+ < Hotspot hotspotId = { HotspotIds . CONFIGURE_CLI_COMMAND } >
196+ < Code className = { styles . code } > { configCliCommand } </ Code >
197+
198+ < div className = { styles . copy } >
199+ < Popover
200+ dismissButton = { false }
201+ position = "top"
202+ size = "small"
203+ triggerType = "custom"
204+ content = { < StatusIndicator type = "success" > { t ( 'common.copied' ) } </ StatusIndicator > }
205+ >
206+ < Button
207+ formAction = "none"
208+ iconName = "copy"
209+ variant = "normal"
210+ onClick = { copyCliCommand }
211+ />
212+ </ Popover >
213+ </ div >
214+ </ Hotspot >
215+ </ div >
216+ </ SpaceBetween >
217+ </ Container >
218+ ) }
173219
174220 < BackendsTable
175221 backends = { backendsData }
@@ -190,6 +236,7 @@ export const ProjectSettings: React.FC = () => {
190236 members = { data . members }
191237 readonly = { ! isProjectManager ( data ) }
192238 isAdmin = { isProjectAdmin ( data ) }
239+ project = { data }
193240 />
194241
195242 < Container header = { < Header variant = "h2" > { t ( 'common.danger_zone' ) } </ Header > } >
@@ -216,6 +263,43 @@ export const ProjectSettings: React.FC = () => {
216263 </ >
217264 ) }
218265
266+ { isAvailableProjectManaging && (
267+ < >
268+ < Box variant = "h5" color = "text-body-secondary" >
269+ { t ( 'projects.edit.project_visibility' ) }
270+ </ Box >
271+
272+ < div >
273+ < ButtonWithConfirmation
274+ variant = "danger-normal"
275+ disabled = { ! isProjectManager ( data ) }
276+ formAction = "none"
277+ onClick = { ( ) => changeVisibilityHandler ( selectedVisibility . value === 'public' ) }
278+ confirmTitle = { t ( 'projects.edit.update_visibility_confirm_title' ) }
279+ confirmButtonLabel = { t ( 'projects.edit.change_visibility' ) }
280+ confirmContent = {
281+ < SpaceBetween size = "s" >
282+ < Box variant = "p" color = "text-body-secondary" >
283+ { t ( 'projects.edit.update_visibility_confirm_message' ) }
284+ </ Box >
285+ < div className = { styles . dangerSectionField } >
286+ < SelectCSD
287+ options = { visibilityOptions }
288+ selectedOption = { selectedVisibility }
289+ onChange = { ( event ) => setSelectedVisibility ( event . detail . selectedOption as { label : string ; value : string } ) }
290+ expandToViewport = { true }
291+ filteringType = "auto"
292+ />
293+ </ div >
294+ </ SpaceBetween >
295+ }
296+ >
297+ { t ( 'projects.edit.change_visibility' ) }
298+ </ ButtonWithConfirmation >
299+ </ div >
300+ </ >
301+ ) }
302+
219303 < Box variant = "h5" color = "text-body-secondary" >
220304 { t ( 'projects.edit.owner' ) }
221305 </ Box >
0 commit comments