From 0904f0feda07084571911e58b68b13fff433bb62 Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Tue, 17 Mar 2026 11:30:57 +0800 Subject: [PATCH] fix: sync namespace member role updates --- .../namespace/add-namespace-member-dialog.tsx | 4 +- web/src/pages/dashboard/namespace-members.tsx | 2 + web/src/shared/hooks/use-skill-queries.ts | 13 +++++- .../shared/lib/namespace-member-cache.test.ts | 41 +++++++++++++++++++ web/src/shared/lib/namespace-member-cache.ts | 30 ++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 web/src/shared/lib/namespace-member-cache.test.ts create mode 100644 web/src/shared/lib/namespace-member-cache.ts diff --git a/web/src/features/namespace/add-namespace-member-dialog.tsx b/web/src/features/namespace/add-namespace-member-dialog.tsx index 11279d55..4dd077d2 100644 --- a/web/src/features/namespace/add-namespace-member-dialog.tsx +++ b/web/src/features/namespace/add-namespace-member-dialog.tsx @@ -224,10 +224,10 @@ export function AddNamespaceMemberDialog({ slug, children }: AddNamespaceMemberD ) : null} - handleOpenChange(false)}> + handleOpenChange(false)}> {t('dialog.cancel')} - + {addMemberMutation.isPending ? t('members.addingMember') : t('members.addMember')} diff --git a/web/src/pages/dashboard/namespace-members.tsx b/web/src/pages/dashboard/namespace-members.tsx index d2e92bd0..42a9510b 100644 --- a/web/src/pages/dashboard/namespace-members.tsx +++ b/web/src/pages/dashboard/namespace-members.tsx @@ -197,6 +197,7 @@ export function NamespaceMembersPage() { {t('members.roleAdmin')} { + onSuccess: (member, variables) => { + queryClient.setQueryData( + ['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( + ['namespaces', variables.slug, 'members'], + (currentMembers) => replaceNamespaceMemberRole(currentMembers, variables.userId, member.role), + ) invalidateNamespaceQueries(queryClient, variables.slug) }, }) diff --git a/web/src/shared/lib/namespace-member-cache.test.ts b/web/src/shared/lib/namespace-member-cache.test.ts new file mode 100644 index 00000000..5ef85cb8 --- /dev/null +++ b/web/src/shared/lib/namespace-member-cache.test.ts @@ -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 => ({ + 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' }, + ]) + }) +}) diff --git a/web/src/shared/lib/namespace-member-cache.ts b/web/src/shared/lib/namespace-member-cache.ts new file mode 100644 index 00000000..917cd5a1 --- /dev/null +++ b/web/src/shared/lib/namespace-member-cache.ts @@ -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 + )) +}