Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.

Commit 29e8c7f

Browse files
authored
feat: complete UI and API integration for user group resource permissions (#2005)
1 parent 923ef93 commit 29e8c7f

12 files changed

Lines changed: 1141 additions & 58 deletions

‎moon/apps/web/components/AdminGroups/AdminGroupEditDialog.tsx‎

Lines changed: 666 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import React from 'react'
1+
import React, { useEffect, useState } from 'react'
22

33
import { UsersIcon } from '@gitmono/ui'
44

5+
import { AdminGroupEditDialog } from './AdminGroupEditDialog'
6+
57
interface GroupItem {
68
id: number
79
name: string
@@ -14,64 +16,106 @@ interface AdminGroupItemProps {
1416
group: GroupItem
1517
onDelete: (id: number) => void
1618
onManageMembers: (id: number) => void
17-
onEdit?: (id: number) => void
19+
onUpdate?: () => void
1820
}
1921

20-
export const AdminGroupItem = ({ group, onDelete, onManageMembers, onEdit }: AdminGroupItemProps) => {
21-
const createdDate = new Date(group.created_at * 1000).toLocaleDateString()
22+
export const AdminGroupItem = ({ group, onDelete, onManageMembers, onUpdate }: AdminGroupItemProps) => {
23+
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
24+
const [localGroup, setLocalGroup] = useState(group)
25+
const [isUpdating, setIsUpdating] = useState(false)
26+
const createdDate = new Date(localGroup.created_at * 1000).toLocaleDateString()
2227

23-
return (
24-
<div className='border-primary flex items-center justify-between border-b py-6 transition-colors duration-200 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50'>
25-
<div className='flex items-start'>
26-
<div className='mr-4 rounded-lg bg-blue-100 p-3 dark:bg-blue-900/30'>
27-
<UsersIcon className='h-6 w-6 text-blue-600 dark:text-blue-400' aria-hidden='true' />
28-
</div>
29-
<div>
30-
<p className='text-primary mb-1 text-lg font-bold'>{group.name}</p>
31-
<div className='text-tertiary space-y-1 text-sm'>
32-
{group.description && <p className='max-w-md text-gray-600 dark:text-gray-400'>{group.description}</p>}
33-
<p className='text-xs text-gray-500 dark:text-gray-500'>Created: {createdDate}</p>
34-
</div>
35-
</div>
36-
</div>
28+
useEffect(() => {
29+
setLocalGroup(group)
30+
setIsUpdating(false)
31+
}, [group])
32+
33+
const handleEditSuccess = (updatedData: { name: string; description: string | null }) => {
34+
setLocalGroup((prev) => ({
35+
...prev,
36+
name: updatedData.name,
37+
description: updatedData.description,
38+
updated_at: Math.floor(Date.now() / 1000)
39+
}))
3740

38-
<div className='flex min-w-0 flex-col gap-3'>
39-
{/* Group operations - first row */}
40-
<div className='flex items-center gap-4'>
41-
<div className='w-[30%]'>
42-
<span className='text-sm font-medium text-gray-600 dark:text-gray-400'>Group:</span>
41+
setIsUpdating(true)
42+
43+
onUpdate?.()
44+
45+
setTimeout(() => {
46+
setIsUpdating(false)
47+
}, 3000)
48+
}
49+
50+
return (
51+
<>
52+
<div className='border-primary flex items-center justify-between border-b py-6 transition-colors duration-200 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50'>
53+
<div className='flex items-start'>
54+
<div className='mr-4 rounded-lg bg-blue-100 p-3 dark:bg-blue-900/30'>
55+
<UsersIcon className='h-6 w-6 text-blue-600 dark:text-blue-400' aria-hidden='true' />
4356
</div>
44-
<div className='flex w-[70%] justify-between gap-2'>
45-
<button
46-
className='border-primary flex-1 rounded-md border px-4 py-1 text-sm font-semibold text-blue-600 transition-colors duration-200 hover:bg-blue-600 hover:text-white'
47-
onClick={() => onEdit?.(group.id)}
48-
>
49-
Edit
50-
</button>
51-
<button
52-
className='border-primary flex-1 rounded-md border px-4 py-1 text-sm font-semibold text-red-500 transition-colors duration-200 hover:bg-red-500 hover:text-white'
53-
onClick={() => onDelete(group.id)}
54-
>
55-
Delete
56-
</button>
57+
<div className='flex-1'>
58+
<div className='flex items-center gap-2'>
59+
<p className='text-primary mb-1 text-lg font-bold'>{localGroup.name}</p>
60+
{isUpdating && (
61+
<div className='flex items-center gap-1'>
62+
<div className='h-3 w-3 animate-spin rounded-full border border-green-500 border-t-transparent'></div>
63+
<span className='text-xs text-green-600'>Updated</span>
64+
</div>
65+
)}
66+
</div>
67+
<div className='text-tertiary space-y-1 text-sm'>
68+
{localGroup.description && (
69+
<p className='max-w-md text-gray-600 dark:text-gray-400'>{localGroup.description}</p>
70+
)}
71+
<p className='text-xs text-gray-500 dark:text-gray-500'>Created: {createdDate}</p>
72+
</div>
5773
</div>
5874
</div>
5975

60-
{/* Member operations - second row */}
61-
<div className='flex items-center gap-4'>
62-
<div className='w-[30%]'>
63-
<span className='text-sm font-medium text-gray-600 dark:text-gray-400'>Members:</span>
76+
<div className='flex min-w-0 flex-col gap-3'>
77+
<div className='flex items-center gap-4'>
78+
<div className='w-[30%]'>
79+
<span className='text-sm font-medium text-gray-600 dark:text-gray-400'>Group:</span>
80+
</div>
81+
<div className='flex w-[70%] justify-between gap-2'>
82+
<button
83+
className='border-primary flex-1 rounded-md border px-4 py-1 text-sm font-semibold text-blue-600 transition-colors duration-200 hover:bg-blue-600 hover:text-white'
84+
onClick={() => setIsEditDialogOpen(true)}
85+
>
86+
Edit
87+
</button>
88+
<button
89+
className='border-primary flex-1 rounded-md border px-4 py-1 text-sm font-semibold text-red-500 transition-colors duration-200 hover:bg-red-500 hover:text-white'
90+
onClick={() => onDelete(group.id)}
91+
>
92+
Delete
93+
</button>
94+
</div>
6495
</div>
65-
<div className='flex w-[70%] justify-between'>
66-
<button
67-
className='border-primary w-full rounded-md border px-4 py-1 text-sm font-semibold text-purple-600 transition-colors duration-200 hover:bg-purple-600 hover:text-white'
68-
onClick={() => onManageMembers(group.id)}
69-
>
70-
Manage Members
71-
</button>
96+
97+
<div className='flex items-center gap-4'>
98+
<div className='w-[30%]'>
99+
<span className='text-sm font-medium text-gray-600 dark:text-gray-400'>Members:</span>
100+
</div>
101+
<div className='flex w-[70%] justify-between'>
102+
<button
103+
className='border-primary w-full rounded-md border px-4 py-1 text-sm font-semibold text-purple-600 transition-colors duration-200 hover:bg-purple-600 hover:text-white'
104+
onClick={() => onManageMembers(group.id)}
105+
>
106+
Manage Members
107+
</button>
108+
</div>
72109
</div>
73110
</div>
74111
</div>
75-
</div>
112+
113+
<AdminGroupEditDialog
114+
open={isEditDialogOpen}
115+
onOpenChange={setIsEditDialogOpen}
116+
groupId={localGroup.id}
117+
onSuccess={handleEditSuccess}
118+
/>
119+
</>
76120
)
77121
}

‎moon/apps/web/components/AdminGroups/AdminGroupsList.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface AdminGroupsListProps {
1919
isError: boolean
2020
onDelete: (id: number) => void
2121
onManageMembers: (id: number) => void
22-
onEdit?: (id: number) => void
22+
onUpdate?: () => void
2323
}
2424

2525
export const AdminGroupsList = ({
@@ -29,7 +29,7 @@ export const AdminGroupsList = ({
2929
isError,
3030
onDelete,
3131
onManageMembers,
32-
onEdit
32+
onUpdate
3333
}: AdminGroupsListProps) => {
3434
return (
3535
<section>
@@ -54,7 +54,7 @@ export const AdminGroupsList = ({
5454
group={group}
5555
onDelete={onDelete}
5656
onManageMembers={onManageMembers}
57-
onEdit={onEdit}
57+
onUpdate={onUpdate}
5858
/>
5959
))}
6060
</div>

‎moon/apps/web/components/AdminGroups/index.tsx‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ export const AdminGroups = () => {
3030
setManageMembersGroupId(groupId)
3131
}
3232

33-
const handleEditGroup = (_groupId: number) => {
34-
// TODO: Implement edit functionality
35-
// console.log('Edit group:', groupId)
36-
}
37-
3833
return (
3934
<div className='border-primary bg-primary dark:bg-tertiary text-secondary mx-auto max-w-4xl rounded-lg border p-8 font-sans'>
4035
<header className='flex items-center justify-between pb-4'>
@@ -60,7 +55,6 @@ export const AdminGroups = () => {
6055
isError={isError}
6156
onDelete={handleDeleteGroup}
6257
onManageMembers={handleManageMembers}
63-
onEdit={handleEditGroup}
6458
/>
6559

6660
<CreateGroupDialog isOpen={isCreateDialogOpen} onClose={() => setIsCreateDialogOpen(false)} />
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query'
2+
3+
import type { DeleteApiAdminResourcesPermissionsData, RequestParams } from '@gitmono/types'
4+
5+
import { legacyApiClient } from '@/utils/queryClient'
6+
7+
export function useDeleteResourcePermissions() {
8+
const queryClient = useQueryClient()
9+
10+
return useMutation<
11+
DeleteApiAdminResourcesPermissionsData,
12+
Error,
13+
{ resourceType: string; resourceId: string; params?: RequestParams }
14+
>({
15+
mutationFn: async ({ resourceType, resourceId, params }) => {
16+
const response = await legacyApiClient.v1
17+
.deleteApiAdminResourcesPermissions()
18+
.request(resourceType, resourceId, params)
19+
20+
if (response && typeof response === 'object' && 'req_result' in response && !response.req_result) {
21+
throw new Error(response.err_message || 'Delete failed')
22+
}
23+
24+
return response
25+
},
26+
onSuccess: () => {
27+
queryClient.invalidateQueries({
28+
queryKey: legacyApiClient.v1.getApiAdminResourcesPermissions().baseKey
29+
})
30+
}
31+
})
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useQuery } from '@tanstack/react-query'
2+
3+
import type { GetApiAdminGroupsByGroupIdData, RequestParams } from '@gitmono/types'
4+
5+
import { legacyApiClient } from '@/utils/queryClient'
6+
7+
interface UseGetAdminGroupByIdOptions extends RequestParams {
8+
enabled?: boolean
9+
}
10+
11+
export function useGetAdminGroupById(groupId: number, options?: UseGetAdminGroupByIdOptions) {
12+
const { enabled = true, ...params } = options || {}
13+
14+
return useQuery<GetApiAdminGroupsByGroupIdData, Error>({
15+
queryKey: [...legacyApiClient.v1.getApiAdminGroupsByGroupId().requestKey(groupId), params],
16+
queryFn: () => legacyApiClient.v1.getApiAdminGroupsByGroupId().request(groupId, params),
17+
enabled: enabled && !!groupId,
18+
staleTime: 0,
19+
retry: false
20+
})
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useQuery } from '@tanstack/react-query'
2+
3+
import type { GetApiAdminResourcesPermissionsData, RequestParams } from '@gitmono/types'
4+
5+
import { legacyApiClient } from '@/utils/queryClient'
6+
7+
interface UseGetAdminResourcePermissionsOptions extends RequestParams {
8+
enabled?: boolean
9+
}
10+
11+
export function useGetAdminResourcePermissions(
12+
resourceType: string,
13+
resourceId: string,
14+
options?: UseGetAdminResourcePermissionsOptions
15+
) {
16+
const { enabled = true, ...params } = options || {}
17+
18+
return useQuery<GetApiAdminResourcesPermissionsData, Error>({
19+
queryKey: [...legacyApiClient.v1.getApiAdminResourcesPermissions().requestKey(resourceType, resourceId), params],
20+
queryFn: () => legacyApiClient.v1.getApiAdminResourcesPermissions().request(resourceType, resourceId, params),
21+
enabled: enabled && !!resourceType && !!resourceId,
22+
staleTime: 0,
23+
retry: false
24+
})
25+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query'
2+
3+
import type { PostApiAdminResourcesPermissionsData, RequestParams, SetPermissionsRequest } from '@gitmono/types'
4+
5+
import { legacyApiClient } from '@/utils/queryClient'
6+
7+
interface PostResourcePermissionsParams {
8+
resourceType: string
9+
resourceId: string
10+
data: SetPermissionsRequest
11+
params?: RequestParams
12+
}
13+
14+
export function usePostResourcePermissions() {
15+
const queryClient = useQueryClient()
16+
17+
return useMutation<PostApiAdminResourcesPermissionsData, Error, PostResourcePermissionsParams>({
18+
mutationFn: async ({ resourceType, resourceId, data, params }) => {
19+
const response = await legacyApiClient.v1
20+
.postApiAdminResourcesPermissions()
21+
.request(resourceType, resourceId, data, params)
22+
23+
if (response && typeof response === 'object' && 'req_result' in response && !response.req_result) {
24+
throw new Error(response.err_message || 'Set permissions failed')
25+
}
26+
27+
return response
28+
},
29+
onSuccess: (_, variables) => {
30+
queryClient.invalidateQueries({
31+
queryKey: legacyApiClient.v1
32+
.getApiAdminResourcesPermissions()
33+
.requestKey(variables.resourceType, variables.resourceId)
34+
})
35+
36+
queryClient.invalidateQueries({
37+
queryKey: legacyApiClient.v1.getApiAdminResourcesPermissions().baseKey
38+
})
39+
}
40+
})
41+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query'
2+
3+
import type { PutApiAdminGroupsByGroupIdData, RequestParams, UpdateGroupRequest } from '@gitmono/types'
4+
5+
import { legacyApiClient } from '@/utils/queryClient'
6+
7+
export function useUpdateAdminGroup() {
8+
const queryClient = useQueryClient()
9+
10+
return useMutation<
11+
PutApiAdminGroupsByGroupIdData,
12+
Error,
13+
{ groupId: number; data: UpdateGroupRequest; params?: RequestParams }
14+
>({
15+
mutationFn: async ({ groupId, data, params }) => {
16+
const response = await legacyApiClient.v1.putApiAdminGroupsByGroupId().request(groupId, data, params)
17+
18+
if (response && typeof response === 'object' && 'req_result' in response && !response.req_result) {
19+
throw new Error(response.err_message || 'Update failed')
20+
}
21+
22+
return response
23+
},
24+
onSuccess: () => {
25+
queryClient.invalidateQueries({
26+
queryKey: legacyApiClient.v1.postApiAdminGroupsList().baseKey
27+
})
28+
}
29+
})
30+
}

0 commit comments

Comments
 (0)