Refactor(frontend):change the role-name user to member (#12146)

Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
This commit is contained in:
Bharath A V
2025-12-29 17:45:27 +05:30
committed by GitHub
parent a0b958b221
commit 01c91e4c6a
15 changed files with 83 additions and 80 deletions

View File

@@ -54,7 +54,7 @@ describe("UserContextMenu", () => {
});
it("should render the default context items for a user", () => {
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
screen.getByTestId("org-selector");
screen.getByText("ACCOUNT_SETTINGS$LOGOUT");
@@ -83,7 +83,7 @@ describe("UserContextMenu", () => {
},
});
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
// Wait for config to load and verify that navigation items are rendered (except organization-members/org which are filtered out)
const expectedItems = SAAS_NAV_ITEMS.filter(
@@ -99,14 +99,14 @@ describe("UserContextMenu", () => {
});
it("should not display Organization Members menu item for regular users (filtered out)", () => {
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
// Organization Members is filtered out from nav items for all users
expect(screen.queryByText("Organization Members")).not.toBeInTheDocument();
});
it("should render a documentation link", () => {
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
const docsLink = screen.getByText("SIDEBAR$DOCS").closest("a");
expect(docsLink).toHaveAttribute("href", "https://docs.openhands.dev");
@@ -131,7 +131,7 @@ describe("UserContextMenu", () => {
});
it("should render OSS_NAV_ITEMS when in OSS mode", async () => {
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
// Wait for the config to load and OSS nav items to appear
await waitFor(() => {
@@ -147,7 +147,7 @@ describe("UserContextMenu", () => {
});
it("should not display Organization Members menu item in OSS mode", async () => {
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
// Wait for the config to load
await waitFor(() => {
@@ -177,7 +177,7 @@ describe("UserContextMenu", () => {
},
});
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
await waitFor(() => {
// Other nav items should still be visible
@@ -204,7 +204,7 @@ describe("UserContextMenu", () => {
},
});
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
await waitFor(() => {
expect(
@@ -234,7 +234,7 @@ describe("UserContextMenu", () => {
it("should call the logout handler when Logout is clicked", async () => {
const logoutSpy = vi.spyOn(AuthService, "logout");
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
const logoutButton = screen.getByText("ACCOUNT_SETTINGS$LOGOUT");
await userEvent.click(logoutButton);
@@ -257,7 +257,7 @@ describe("UserContextMenu", () => {
},
});
renderUserContextMenu({ type: "user", onClose: vi.fn });
renderUserContextMenu({ type: "member", onClose: vi.fn });
// Wait for config to load and test a few representative nav items have the correct href
await waitFor(() => {
@@ -298,7 +298,7 @@ describe("UserContextMenu", () => {
it("should call the onClose handler when clicking outside the context menu", async () => {
const onCloseMock = vi.fn();
renderUserContextMenu({ type: "user", onClose: onCloseMock });
renderUserContextMenu({ type: "member", onClose: onCloseMock });
const contextMenu = screen.getByTestId("user-context-menu");
await userEvent.click(contextMenu);
@@ -350,7 +350,7 @@ describe("UserContextMenu", () => {
test("the user can change orgs", async () => {
const onCloseMock = vi.fn();
renderUserContextMenu({ type: "user", onClose: onCloseMock });
renderUserContextMenu({ type: "member", onClose: onCloseMock });
const orgSelector = screen.getByTestId("org-selector");
expect(orgSelector).toBeInTheDocument();
@@ -369,7 +369,7 @@ describe("UserContextMenu", () => {
it("should have Personal Account as the default selected option with null value", async () => {
const onCloseMock = vi.fn();
renderUserContextMenu({ type: "user", onClose: onCloseMock });
renderUserContextMenu({ type: "member", onClose: onCloseMock });
const orgSelector = screen.getByTestId("org-selector");

View File

@@ -95,7 +95,7 @@ describe("Manage Org Route", () => {
org_id: string;
user_id: string;
email: string;
role: "owner" | "admin" | "user";
role: "owner" | "admin" | "member";
llm_api_key: string;
max_iterations: number;
llm_model: string;
@@ -680,9 +680,9 @@ describe("Manage Org Route", () => {
expect(deleteButton).not.toBeDisabled();
});
it.each<{ role: "admin" | "user"; roleName: string }>([
it.each<{ role: "admin" | "member"; roleName: string }>([
{ role: "admin", roleName: "Admin" },
{ role: "user", roleName: "User" },
{ role: "member", roleName: "Member" },
])(
"should not show delete organization button when user lacks canDeleteOrganization permission ($roleName role)",
async ({ role }) => {

View File

@@ -39,7 +39,7 @@ const RouteStub = createRoutesStub([
},
{
Component: () => <div data-testid="user-settings" />,
path: "/settings/user",
path: "/settings/member",
},
],
},
@@ -147,7 +147,7 @@ describe("Manage Organization Members Route", () => {
org_id: string;
user_id: string;
email: string;
role: "owner" | "admin" | "user";
role: "owner" | "admin" | "member";
llm_api_key: string;
max_iterations: number;
llm_model: string;
@@ -173,7 +173,7 @@ describe("Manage Organization Members Route", () => {
org_id: string;
user_id: string;
email: string;
role: "owner" | "admin" | "user";
role: "owner" | "admin" | "member";
llm_api_key: string;
max_iterations: number;
llm_model: string;
@@ -222,7 +222,7 @@ describe("Manage Organization Members Route", () => {
const expectAllRoleOptionsPresent = (dropdown: HTMLElement) => {
expect(within(dropdown).getByText(/owner/i)).toBeInTheDocument();
expect(within(dropdown).getByText(/admin/i)).toBeInTheDocument();
expect(within(dropdown).getByText(/user/i)).toBeInTheDocument();
expect(within(dropdown).getByText(/member/i)).toBeInTheDocument();
};
// Helper function to close dropdown by clicking outside
@@ -305,11 +305,11 @@ describe("Manage Organization Members Route", () => {
const memberListItems = await screen.findAllByTestId("member-item");
const userRoleMember = memberListItems[2]; // third member is "user"
let userCombobox = within(userRoleMember).getByText(/^User$/i);
let userCombobox = within(userRoleMember).getByText(/^Member$/i);
expect(userCombobox).toBeInTheDocument();
// Change role from user to admin
await changeMemberRole(userRoleMember, "user", "admin");
await changeMemberRole(userRoleMember, "member", "admin");
expect(updateMemberRoleSpy).toHaveBeenCalledExactlyOnceWith({
userId: "3", // assuming the third member is the one being updated
@@ -323,16 +323,16 @@ describe("Manage Organization Members Route", () => {
expect(userCombobox).toBeInTheDocument();
// Revert the role back to user
await changeMemberRole(userRoleMember, "admin", "user");
await changeMemberRole(userRoleMember, "admin", "member");
expect(updateMemberRoleSpy).toHaveBeenNthCalledWith(2, {
userId: "3",
orgId: "1",
role: "user",
role: "member",
});
// Verify the role has been reverted in the UI
userCombobox = within(userRoleMember).getByText(/^User$/i);
userCombobox = within(userRoleMember).getByText(/^Member$/i);
expect(userCombobox).toBeInTheDocument();
});
@@ -410,7 +410,7 @@ describe("Manage Organization Members Route", () => {
const userEmail = within(userRoleMember).getByText("charlie@acme.org");
expect(userEmail).toBeInTheDocument();
const userCombobox = within(userRoleMember).getByText(/^User$/i);
const userCombobox = within(userRoleMember).getByText(/^Member$/i);
await userEvent.click(userCombobox);
const dropdown = within(userRoleMember).getByTestId(
@@ -487,7 +487,7 @@ describe("Manage Organization Members Route", () => {
org_id: "1",
user_id: "4",
email: "tom@acme.org",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -510,7 +510,7 @@ describe("Manage Organization Members Route", () => {
expect(invitedBadge).toBeInTheDocument();
// should not have a role combobox
await userEvent.click(within(invitedMember).getByText(/^User$/i));
await userEvent.click(within(invitedMember).getByText(/^Member$/i));
expect(
within(invitedMember).queryByTestId(
"organization-member-role-context-menu",
@@ -553,7 +553,7 @@ describe("Manage Organization Members Route", () => {
org_id: "1",
user_id: "1",
email: "test@example.com",
role: "user" as const,
role: "member" as const,
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -638,7 +638,7 @@ describe("Manage Organization Members Route", () => {
// Test with user member
const userMember = await findMemberByEmail("charlie@acme.org");
const userDropdown = await openRoleDropdown(userMember, "user");
const userDropdown = await openRoleDropdown(userMember, "member");
// Verify all three role options are present for user member
expectAllRoleOptionsPresent(userDropdown);
@@ -665,7 +665,7 @@ describe("Manage Organization Members Route", () => {
// Check user member dropdown
const userMember = memberListItems[2]; // user member
const userDropdown = await openRoleDropdown(userMember, "user");
const userDropdown = await openRoleDropdown(userMember, "member");
expectOwnerOptionNotPresent(userDropdown);
await closeDropdown();
@@ -674,7 +674,7 @@ describe("Manage Organization Members Route", () => {
const anotherUserMember = memberListItems[3]; // another user member
const anotherUserDropdown = await openRoleDropdown(
anotherUserMember,
"user",
"member",
);
expectOwnerOptionNotPresent(anotherUserDropdown);
}
@@ -713,7 +713,7 @@ describe("Manage Organization Members Route", () => {
// Test changing user to owner
const userMember = await findMemberByEmail("charlie@acme.org");
await changeMemberRole(userMember, "user", "owner");
await changeMemberRole(userMember, "member", "owner");
expect(updateMemberRoleSpy).toHaveBeenNthCalledWith(2, {
userId: "3",
@@ -765,16 +765,16 @@ describe("Manage Organization Members Route", () => {
},
orgIndex: 2, // org 3
memberEmail: "stephan@all-hands.dev",
currentRole: "user",
newRole: "user",
currentRole: "member",
newRole: "member",
expectedApiCall: {
userId: "9",
orgId: "3",
role: "user" as const,
role: "member" as const,
},
},
{
description: "Admin should be able to change user's role to admin",
description: "Admin should be able to change member's role to admin",
userData: {
org_id: "3",
user_id: "7", // Ray is admin in org 3
@@ -789,7 +789,7 @@ describe("Manage Organization Members Route", () => {
},
orgIndex: 2, // org 3
memberEmail: "stephan@all-hands.dev",
currentRole: "user",
currentRole: "member",
newRole: "admin",
expectedApiCall: {
userId: "9",

View File

@@ -51,12 +51,14 @@ export function OrganizationMemberListItem({
>
{email}
</span>
{status === "invited" && (
<span className="text-xs text-tertiary-light border border-tertiary px-2 py-1 rounded-lg">
{t(I18nKey.ORG$STATUS_INVITED)}
</span>
)}
</div>
<div className="relative">
<span
onClick={handleRoleClick}
@@ -68,6 +70,7 @@ export function OrganizationMemberListItem({
{role}
{hasPermissionToChangeRole && <ChevronDown size={14} />}
</span>
{roleSelectionIsPermitted && contextMenuOpen && (
<OrganizationMemberRoleContextMenu
onClose={() => setContextMenuOpen(false)}

View File

@@ -94,15 +94,15 @@ export function OrganizationMemberRoleContextMenu({
/>
</ContextMenuListItem>
)}
{availableRolesToChangeTo.includes("user") && (
{availableRolesToChangeTo.includes("member") && (
<ContextMenuListItem
testId="user-option"
onClick={(event) => handleRoleChangeClick(event, "user")}
testId="member-option"
onClick={(event) => handleRoleChangeClick(event, "member")}
className={contextMenuListItemClassName}
>
<ContextMenuIconText
icon={<UserIcon width={16} height={16} className="text-white" />}
text={t(I18nKey.ORG$ROLE_USER)}
text={t(I18nKey.ORG$ROLE_MEMBER)}
className="capitalize"
/>
</ContextMenuListItem>

View File

@@ -28,7 +28,7 @@ export function SettingsNavigation({
const { t } = useTranslation();
const isUser = me?.role === "user";
const isUser = me?.role === "member";
return (
<>

View File

@@ -52,7 +52,7 @@ export function UserActions({ user, isLoading }: UserActionsProps) {
)}
>
<UserContextMenu
type={me?.role || "user"}
type={me?.role || "member"}
onClose={closeAccountMenu}
/>
</div>

View File

@@ -52,7 +52,7 @@ export function UserContextMenu({ type, onClose }: UserContextMenuProps) {
const [inviteMemberModalIsOpen, setInviteMemberModalIsOpen] =
React.useState(false);
const isUser = type === "user";
const isMember = type === "member";
const handleLogout = () => {
logout();
@@ -120,7 +120,7 @@ export function UserContextMenu({ type, onClose }: UserContextMenuProps) {
/>
</div>
{!isUser && (
{!isMember && (
<>
<ContextMenuListItem
onClick={handleInviteMemberClick}

View File

@@ -971,7 +971,7 @@ export enum I18nKey {
ORG$EMAILS = "ORG$EMAILS",
ORG$STATUS_INVITED = "ORG$STATUS_INVITED",
ORG$ROLE_ADMIN = "ORG$ROLE_ADMIN",
ORG$ROLE_USER = "ORG$ROLE_USER",
ORG$ROLE_MEMBER = "ORG$ROLE_MEMBER",
ORG$ROLE_OWNER = "ORG$ROLE_OWNER",
ORG$REMOVE = "ORG$REMOVE",
ORG$ACCOUNT = "ORG$ACCOUNT",

View File

@@ -15535,21 +15535,21 @@
"de": "Admin",
"uk": "адміністратор"
},
"ORG$ROLE_USER": {
"en": "user",
"ja": "ユーザー",
"zh-CN": "用户",
"zh-TW": "用戶",
"ko-KR": "사용자",
"no": "bruker",
"it": "utente",
"pt": "usuário",
"es": "usuario",
"ar": "مستخدم",
"fr": "utilisateur",
"tr": "kullanıcı",
"de": "Benutzer",
"uk": "користувач"
"ORG$ROLE_MEMBER": {
"en": "member",
"ja": "メンバー",
"zh-CN": "成员",
"zh-TW": "成員",
"ko-KR": "멤버",
"no": "medlem",
"it": "membro",
"pt": "membro",
"es": "miembro",
"ar": "عضو",
"fr": "membre",
"tr": "üye",
"de": "Mitglied",
"uk": "учасник"
},
"ORG$ROLE_OWNER": {
"en": "owner",

View File

@@ -89,7 +89,7 @@ const INITIAL_MOCK_MEMBERS: Record<string, OrganizationMember[]> = {
org_id: "1",
user_id: "3",
email: "charlie@acme.org",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -103,7 +103,7 @@ const INITIAL_MOCK_MEMBERS: Record<string, OrganizationMember[]> = {
org_id: "2",
user_id: "4",
email: "tony@gamma.org",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -153,7 +153,7 @@ const INITIAL_MOCK_MEMBERS: Record<string, OrganizationMember[]> = {
org_id: "3",
user_id: "8",
email: "chuck@all-hands.dev",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -165,7 +165,7 @@ const INITIAL_MOCK_MEMBERS: Record<string, OrganizationMember[]> = {
org_id: "3",
user_id: "9",
email: "stephan@all-hands.dev",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -177,7 +177,7 @@ const INITIAL_MOCK_MEMBERS: Record<string, OrganizationMember[]> = {
org_id: "3",
user_id: "10",
email: "tim@all-hands.dev",
role: "user",
role: "member",
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",
@@ -224,19 +224,19 @@ export const ORG_HANDLERS = [
);
}
let role: OrganizationUserRole = "user";
let role: OrganizationUserRole = "member";
switch (orgId) {
case "1":
role = "owner";
break;
case "2":
role = "user";
role = "member";
break;
case "3":
role = "admin";
break;
default:
role = "user";
role = "member";
}
const me: OrganizationMember = {
@@ -430,7 +430,7 @@ export const ORG_HANDLERS = [
org_id: orgId,
user_id: String(members.length + index + 1),
email,
role: "user" as const,
role: "member" as const,
llm_api_key: "**********",
max_iterations: 20,
llm_model: "gpt-4",

View File

@@ -218,7 +218,7 @@ export const clientLoader = async () => {
queryClient.setQueryData(["organizations", selectedOrgId, "me"], me);
}
if (!me || me.role === "user") {
if (!me || me.role === "member") {
// if user is USER role, redirect to user settings
return redirect("/settings/user");
}

View File

@@ -27,7 +27,7 @@ export const clientLoader = async () => {
queryClient.setQueryData(["organizations", selectedOrgId, "me"], me);
}
if (!me || me.role === "user") {
if (!me || me.role === "member") {
// if user is USER role, redirect to user settings
return redirect("/settings/user");
}
@@ -44,7 +44,7 @@ function ManageOrganizationMembers() {
const [inviteModalOpen, setInviteModalOpen] = React.useState(false);
const currentUserRole = user?.role || "user";
const currentUserRole = user?.role || "member";
const hasPermissionToInvite = rolePermissions[currentUserRole].includes(
"invite_user_to_organization",
);
@@ -87,8 +87,8 @@ function ManageOrganizationMembers() {
if (userPermissions.includes("change_user_role:admin")) {
availableRoles.push("admin");
}
if (userPermissions.includes("change_user_role:user")) {
availableRoles.push("user");
if (userPermissions.includes("change_user_role:member")) {
availableRoles.push("member");
}
return availableRoles;

View File

@@ -1,4 +1,4 @@
export type OrganizationUserRole = "user" | "admin" | "owner";
export type OrganizationUserRole = "member" | "admin" | "owner";
export interface Organization {
id: string;

View File

@@ -24,18 +24,18 @@ const ownerPerms: UserPermission[] = [
"add_credits",
"change_user_role:owner",
"change_user_role:admin",
"change_user_role:user",
"change_user_role:member",
];
const adminPerms: UserPermission[] = [
"invite_user_to_organization",
"add_credits",
"change_user_role:admin",
"change_user_role:user",
"change_user_role:member",
];
const userPerms: UserPermission[] = [];
export const rolePermissions: Record<OrganizationUserRole, UserPermission[]> = {
owner: ownerPerms,
admin: adminPerms,
user: userPerms,
member: userPerms,
};