@@ -4,55 +4,44 @@ import { useState } from 'react'
44import { ActivityRow } from '@sim/emcn/activity-row'
55import { Button } from '@sim/emcn/button'
66import { Checkbox } from '@sim/emcn/checkbox'
7- import { EmptyState } from '@sim/emcn/empty-state'
87import { TextField } from '@sim/emcn/field'
98import { Modal } from '@sim/emcn/modal'
109import { MultiSelect } from '@sim/emcn/picker/multi-select'
1110import { SearchField } from '@sim/emcn/search-field'
12- import { SettingsRow , SettingsSection } from '@sim/emcn/settings-section'
13- import { FailureNotice } from '@sim/emcn/status'
14- import { getErrorMessage } from '@sim/utils/errors'
15- import Link from 'next/link'
11+ import { SettingsRow } from '@sim/emcn/settings-section'
12+ import { SettingsList } from '@/app/(product)/_components/settings-parts/settings-list'
1613import { SettingsPageHeader } from '@/app/(product)/_components/settings-parts/settings-page'
1714import {
1815 ResourceList ,
1916 SectionStack ,
17+ SectionToolbar ,
2018} from '@/app/(product)/_components/settings-parts/settings-parts'
19+ import { SettingsRead } from '@/app/(product)/_components/settings-parts/settings-read'
2120import type { SettingsSectionProps } from '@/app/(product)/o/[organizationId]/settings/_components/section-registry'
22- import { GroupDetail } from '@/app/(product)/o/[organizationId]/settings/_components/sections/access-control/group-detail'
2321import {
24- type PermissionGroup ,
22+ GroupDetail ,
23+ groupSummary ,
24+ } from '@/app/(product)/o/[organizationId]/settings/_components/sections/access-control/group-detail'
25+ import {
2526 useAccessControlParams ,
2627 useCreatePermissionGroup ,
2728 useOrganizationWorkspaces ,
2829 usePermissionGroups ,
2930} from '@/platform/lib/data/access-control'
30- import { surfaceHref } from '@/platform/lib/data/organization-model'
31- import { usePlatformHref } from '@/platform/lib/platform-href'
32- import { plural } from '@/platform/shared/plural'
33-
34- function groupSummary ( group : PermissionGroup ) : string {
35- if ( group . isDefault ) return 'Everyone in the organization'
36- const members = group . memberCount === 0 ? 'All members' : plural ( group . memberCount , 'member' )
37- return `${ members } · ${ plural ( group . workspaces . length , 'workspace' ) } `
38- }
3931
4032interface CreateGroupModalProps {
4133 organizationId : string
4234 open : boolean
4335 onOpenChange : ( open : boolean ) => void
44- workspaceOptions : { value : string ; label : string } [ ]
45- workspacesLoading : boolean
4636}
4737
48- function CreateGroupModal ( {
49- organizationId,
50- open,
51- onOpenChange,
52- workspaceOptions,
53- workspacesLoading,
54- } : CreateGroupModalProps ) {
38+ function CreateGroupModal ( { organizationId, open, onOpenChange } : CreateGroupModalProps ) {
5539 const createGroup = useCreatePermissionGroup ( )
40+ const workspaces = useOrganizationWorkspaces ( organizationId , open )
41+ const workspaceOptions = ( workspaces . data ?? [ ] ) . map ( ( workspace ) => ( {
42+ value : workspace . id ,
43+ label : workspace . name ,
44+ } ) )
5645 const [ name , setName ] = useState ( '' )
5746 const [ description , setDescription ] = useState ( '' )
5847 const [ isDefault , setIsDefault ] = useState ( false )
@@ -73,7 +62,7 @@ function CreateGroupModal({
7362 open = { open }
7463 onOpenChange = { change }
7564 title = 'Create permission group'
76- description = 'Give the group a name and choose the workspaces it governs. '
65+ description = 'Give the group a name and choose the workspaces it governs'
7766 confirmLabel = { createGroup . isPending ? 'Creating…' : 'Create group' }
7867 confirmDisabled = { ! name . trim ( ) || ( ! isDefault && workspaceIds . length === 0 ) }
7968 onConfirm = { async ( ) => {
@@ -109,7 +98,7 @@ function CreateGroupModal({
10998 options = { workspaceOptions }
11099 value = { workspaceIds }
111100 onValueChange = { setWorkspaceIds }
112- disabled = { isDefault || workspacesLoading }
101+ disabled = { isDefault || workspaces . data === undefined }
113102 />
114103 </ Modal >
115104 )
@@ -118,13 +107,9 @@ function CreateGroupModal({
118107export default function AccessControlSection ( { organization } : SettingsSectionProps ) {
119108 const organizationId = organization . organization . id
120109 const groups = usePermissionGroups ( organizationId )
121- const workspaces = useOrganizationWorkspaces ( organizationId )
122110 const params = useAccessControlParams ( )
123- const hrefFor = usePlatformHref ( )
124111 const [ creating , setCreating ] = useState ( false )
125112 const permissionGroups = groups . data ?? [ ]
126- const organizationWorkspaces = workspaces . data ?? [ ]
127- const workspaceOptions = organizationWorkspaces . map ( ( ws ) => ( { value : ws . id , label : ws . name } ) )
128113 const query = params . search . trim ( ) . toLowerCase ( )
129114 const filtered = query
130115 ? permissionGroups . filter ( ( group ) => group . name . toLowerCase ( ) . includes ( query ) )
@@ -133,109 +118,83 @@ export default function AccessControlSection({ organization }: SettingsSectionPr
133118 ? permissionGroups . find ( ( group ) => group . id === params . groupId )
134119 : undefined
135120
136- if ( groups . isPending ) return params . groupId ? null : < SettingsPageHeader />
137- if ( groups . error && groups . data === undefined ) {
138- return (
139- < >
140- < SettingsPageHeader />
141- < FailureNotice
142- message = { getErrorMessage ( groups . error , 'Failed to load permission groups' ) }
143- retrying = { groups . isFetching }
144- onRetry = { ( ) => void groups . refetch ( ) }
145- />
146- </ >
147- )
148- }
149-
150- const workspacesError = workspaces . error && workspaces . data === undefined && (
151- < FailureNotice
152- message = { getErrorMessage ( workspaces . error , 'Failed to load workspaces' ) }
153- retrying = { workspaces . isFetching }
154- onRetry = { ( ) => void workspaces . refetch ( ) }
155- />
156- )
157-
158- const refreshError = groups . error && (
159- < FailureNotice
160- message = { getErrorMessage ( groups . error , 'Failed to refresh permission groups' ) }
161- retrying = { groups . isFetching }
162- onRetry = { ( ) => void groups . refetch ( ) }
163- />
164- )
121+ /** A valid deep link never flashes the list before jumping to it; a dead id falls back to it. */
122+ if ( params . groupId && groups . isPending ) return null
165123
166124 if ( selectedGroup ) {
167125 return (
168- < SectionStack >
169- { refreshError }
170- { workspacesError }
171- < GroupDetail
172- key = { selectedGroup . id }
173- group = { selectedGroup }
174- organizationId = { organizationId }
175- hosted = { organization . deployment . hosted }
176- projectsEnabled = { organization . featureFlags . projects }
177- workspaceOptions = { workspaceOptions }
178- workspacesLoading = { workspaces . isPending }
179- onBack = { params . closeGroup }
180- />
181- </ SectionStack >
126+ < GroupDetail
127+ key = { selectedGroup . id }
128+ group = { selectedGroup }
129+ organizationId = { organizationId }
130+ hosted = { organization . deployment . hosted }
131+ projectsEnabled = { organization . featureFlags . projects }
132+ onBack = { params . closeGroup }
133+ />
182134 )
183135 }
184136
137+ const createGroup = (
138+ < Button size = 'compact' icon = 'I074' onClick = { ( ) => setCreating ( true ) } >
139+ Create group
140+ </ Button >
141+ )
142+
185143 return (
186144 < SectionStack >
187145 < SettingsPageHeader />
188- { refreshError }
189- { workspacesError }
190- < SearchField
191- label = 'Search permission groups'
192- placeholder = 'Search permission groups…'
193- value = { params . search }
194- onValueChange = { params . setSearch }
195- />
196- { permissionGroups . length === 0 ? (
197- < EmptyState title = 'No permission groups yet' description = 'Create a group to get started.' />
198- ) : filtered . length === 0 ? (
199- < EmptyState title = 'No matching groups' />
200- ) : (
201- < SettingsSection title = { `Permission groups ${ permissionGroups . length } ` } >
202- < ResourceList >
203- { filtered . map ( ( group ) => (
204- < ActivityRow
205- key = { group . id }
206- title = { group . name }
207- detail = { groupSummary ( group ) }
208- textMetadata = { group . isDefault ? 'Default' : undefined }
209- onOpen = { ( ) => params . openGroup ( group . id ) }
210- />
211- ) ) }
212- </ ResourceList >
213- </ SettingsSection >
146+ { permissionGroups . length > 0 && (
147+ < SectionToolbar
148+ grow = {
149+ < SearchField
150+ label = 'Search permission groups'
151+ placeholder = 'Search permission groups…'
152+ value = { params . search }
153+ onValueChange = { params . setSearch }
154+ />
155+ }
156+ />
214157 ) }
215- < SettingsSection title = 'Manage' >
216- < SettingsRow label = 'New permission group' description = 'Limit what a set of members can use' >
217- < Button size = 'compact' icon = 'I074' onClick = { ( ) => setCreating ( true ) } >
218- Create group
219- </ Button >
220- </ SettingsRow >
221- < SettingsRow
222- label = 'Access requests'
223- description = 'Review requests to use restricted features'
224- >
225- < Button
226- size = 'compact'
227- render = { < Link href = { hrefFor ( surfaceHref ( organization , '/settings/requests' ) ) } /> }
158+ < SettingsRead query = { groups } failureMessage = 'Failed to load permission groups' >
159+ { ( list ) => (
160+ < SettingsList
161+ title = 'Permission groups'
162+ empty = {
163+ list . length === 0
164+ ? {
165+ title : 'No permission groups yet' ,
166+ description : 'Limit what a set of members can use' ,
167+ action : createGroup ,
168+ }
169+ : filtered . length === 0
170+ ? { title : 'No matching groups' }
171+ : undefined
172+ }
228173 >
229- Review requests
230- </ Button >
231- </ SettingsRow >
232- </ SettingsSection >
174+ < ResourceList >
175+ { filtered . map ( ( group ) => (
176+ < ActivityRow
177+ key = { group . id }
178+ title = { group . name }
179+ detail = { groupSummary ( group ) }
180+ textMetadata = { group . isDefault ? 'Default' : undefined }
181+ onOpen = { ( ) => params . openGroup ( group . id ) }
182+ />
183+ ) ) }
184+ </ ResourceList >
185+ < SettingsRow
186+ label = 'New permission group'
187+ description = 'Limit what a set of members can use'
188+ >
189+ { createGroup }
190+ </ SettingsRow >
191+ </ SettingsList >
192+ ) }
193+ </ SettingsRead >
233194 < CreateGroupModal
234195 organizationId = { organizationId }
235196 open = { creating }
236197 onOpenChange = { setCreating }
237- workspaceOptions = { workspaceOptions }
238- workspacesLoading = { workspaces . isPending }
239198 />
240199 </ SectionStack >
241200 )
0 commit comments