fix: sync namespace member role updates

This commit is contained in:
yun-zhi-ztl 2026-03-17 11:30:57 +08:00
parent 99ff863681
commit 0904f0feda
5 changed files with 86 additions and 4 deletions

View file

@ -224,10 +224,10 @@ export function AddNamespaceMemberDialog({ slug, children }: AddNamespaceMemberD
) : null}
<DialogFooter className="sm:justify-center sm:space-x-3">
<Button variant="outline" onClick={() => handleOpenChange(false)}>
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)}>
{t('dialog.cancel')}
</Button>
<Button onClick={handleAddMember} disabled={addMemberMutation.isPending}>
<Button type="button" onClick={handleAddMember} disabled={addMemberMutation.isPending}>
{addMemberMutation.isPending ? t('members.addingMember') : t('members.addMember')}
</Button>
</DialogFooter>

View file

@ -197,6 +197,7 @@ export function NamespaceMembersPage() {
<option value="ADMIN">{t('members.roleAdmin')}</option>
</Select>
<Button
type="button"
variant="outline"
size="sm"
disabled={roleValue === member.role || isSavingRole}
@ -220,6 +221,7 @@ export function NamespaceMembersPage() {
</td>
<td className="p-4 text-right">
<Button
type="button"
variant="destructive"
size="sm"
disabled={!canManageMembers || isOwner || isRemoving}

View file

@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import type { SkillSummary, SkillDetail, SkillVersion, SkillVersionDetail, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember, ManagedNamespace, CreateNamespaceRequest, NamespaceCandidateUser, NamespaceRole } from '@/api/types'
import { fetchJson, fetchText, getCsrfHeaders, meApi, namespaceApi, promotionApi, skillLifecycleApi, WEB_API_PREFIX } from '@/api/client'
import { appendNamespaceMember, replaceNamespaceMemberRole } from '@/shared/lib/namespace-member-cache'
import { normalizeSearchQuery } from '@/shared/lib/search-query'
const PUBLISH_REQUEST_TIMEOUT_MS = 60_000
@ -268,7 +269,11 @@ export function useAddNamespaceMember() {
return useMutation({
mutationFn: addNamespaceMember,
onSuccess: (_data, variables) => {
onSuccess: (member, variables) => {
queryClient.setQueryData<NamespaceMember[]>(
['namespaces', variables.slug, 'members'],
(currentMembers) => appendNamespaceMember(currentMembers, member),
)
invalidateNamespaceQueries(queryClient, variables.slug)
},
})
@ -279,7 +284,11 @@ export function useUpdateNamespaceMemberRole() {
return useMutation({
mutationFn: updateNamespaceMemberRole,
onSuccess: (_data, variables) => {
onSuccess: (member, variables) => {
queryClient.setQueryData<NamespaceMember[]>(
['namespaces', variables.slug, 'members'],
(currentMembers) => replaceNamespaceMemberRole(currentMembers, variables.userId, member.role),
)
invalidateNamespaceQueries(queryClient, variables.slug)
},
})

View file

@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest'
import { appendNamespaceMember, replaceNamespaceMemberRole } from './namespace-member-cache'
import type { NamespaceMember } from '@/api/types'
const baseMember = (overrides: Partial<NamespaceMember>): NamespaceMember => ({
id: 1,
userId: 'user-1',
role: 'MEMBER',
createdAt: '2026-03-17T00:00:00',
...overrides,
})
describe('appendNamespaceMember', () => {
it('appends a newly added member with the returned role', () => {
const members = [baseMember({})]
const addedMember = baseMember({ id: 2, userId: 'user-2', role: 'ADMIN' })
expect(appendNamespaceMember(members, addedMember)).toEqual([
members[0],
addedMember,
])
})
it('replaces the existing member when the same user is returned again', () => {
const members = [baseMember({ role: 'MEMBER' })]
const updatedMember = baseMember({ role: 'ADMIN' })
expect(appendNamespaceMember(members, updatedMember)).toEqual([updatedMember])
})
})
describe('replaceNamespaceMemberRole', () => {
it('updates the member role in the current list', () => {
const members = [baseMember({}), baseMember({ id: 2, userId: 'user-2', role: 'MEMBER' })]
expect(replaceNamespaceMemberRole(members, 'user-2', 'ADMIN')).toEqual([
members[0],
{ ...members[1], role: 'ADMIN' },
])
})
})

View file

@ -0,0 +1,30 @@
import type { NamespaceMember } from '@/api/types'
export function appendNamespaceMember(
currentMembers: NamespaceMember[] | undefined,
nextMember: NamespaceMember,
) {
const members = currentMembers ?? []
const existingIndex = members.findIndex((member) => member.userId === nextMember.userId)
if (existingIndex === -1) {
return [...members, nextMember]
}
return members.map((member, index) => (index === existingIndex ? nextMember : member))
}
export function replaceNamespaceMemberRole(
currentMembers: NamespaceMember[] | undefined,
userId: string,
role: string,
) {
return (currentMembers ?? []).map((member) => (
member.userId === userId
? {
...member,
role,
}
: member
))
}