LibreChat/client/src/components/Sharing/AccessRolesPicker.tsx
Danny Avila 81b32e400a
🔧 refactor: Organize Sharing/Agent Components and Improve Type Safety
refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids, rename enums to PascalCase

refactor: organize Sharing/Agent components, improve type safety for resource types and access role ids

chore: move sharing related components to dedicated "Sharing" directory

chore: remove PublicSharingToggle component and update index exports

chore: move non-sidepanel agent components to `~/components/Agents`

chore: move AgentCategoryDisplay component with tests

chore: remove commented out code

refactor: change PERMISSION_BITS from const to enum for better type safety

refactor: reorganize imports in GenericGrantAccessDialog and update index exports for hooks

refactor: update type definitions to use ACCESS_ROLE_IDS for improved type safety

refactor: remove unused canAccessPromptResource middleware and related code

refactor: remove unused prompt access roles from createAccessRoleMethods

refactor: update resourceType in AclEntry type definition to remove unused 'prompt' value

refactor: introduce ResourceType enum and update resourceType usage across data provider files for improved type safety

refactor: update resourceType usage to ResourceType enum across sharing and permissions components for improved type safety

refactor: standardize resourceType usage to ResourceType enum across agent and prompt models, permissions controller, and middleware for enhanced type safety

refactor: update resourceType references from PROMPT_GROUP to PROMPTGROUP for consistency across models, middleware, and components

refactor: standardize access role IDs and resource type usage across agent, file, and prompt models for improved type safety and consistency

chore: add typedefs for TUpdateResourcePermissionsRequest and TUpdateResourcePermissionsResponse to enhance type definitions

chore: move SearchPicker to PeoplePicker dir

refactor: implement debouncing for query changes in SearchPicker for improved performance

chore: fix typing, import order for agent admin settings

fix: agent admin settings, prevent agent form submission

refactor: rename `ACCESS_ROLE_IDS` to `AccessRoleIds`

refactor: replace PermissionBits with PERMISSION_BITS

refactor: replace PERMISSION_BITS with PermissionBits
2025-08-13 16:24:20 -04:00

97 lines
3.4 KiB
TypeScript

import React from 'react';
import * as Ariakit from '@ariakit/react';
import { ChevronDown } from 'lucide-react';
import { DropdownPopup } from '@librechat/client';
import { AccessRoleIds, ResourceType } from 'librechat-data-provider';
import { useGetAccessRolesQuery } from 'librechat-data-provider/react-query';
import type { AccessRole } from 'librechat-data-provider';
import type * as t from '~/common';
import { cn, getRoleLocalizationKeys } from '~/utils';
import { useLocalize } from '~/hooks';
interface AccessRolesPickerProps {
resourceType?: ResourceType;
selectedRoleId?: AccessRoleIds;
onRoleChange: (roleId: AccessRoleIds) => void;
className?: string;
}
export default function AccessRolesPicker({
resourceType = ResourceType.AGENT,
selectedRoleId = AccessRoleIds.AGENT_VIEWER,
onRoleChange,
className = '',
}: AccessRolesPickerProps) {
const localize = useLocalize();
const [isOpen, setIsOpen] = React.useState(false);
const { data: accessRoles, isLoading: rolesLoading } = useGetAccessRolesQuery(resourceType);
/** Helper function to get localized role name and description */
const getLocalizedRoleInfo = (roleId: AccessRoleIds) => {
const keys = getRoleLocalizationKeys(roleId);
return {
name: localize(keys.name),
description: localize(keys.description),
};
};
const selectedRole = accessRoles?.find((role) => role.accessRoleId === selectedRoleId);
const selectedRoleInfo = selectedRole ? getLocalizedRoleInfo(selectedRole.accessRoleId) : null;
if (rolesLoading || !accessRoles) {
return (
<div className={className}>
<div className="flex items-center justify-center py-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-border-light border-t-blue-600"></div>
<span className="ml-2 text-sm text-text-secondary">{localize('com_ui_loading')}</span>
</div>
</div>
);
}
const dropdownItems: t.MenuItemProps[] = accessRoles.map((role: AccessRole) => {
const localizedInfo = getLocalizedRoleInfo(role.accessRoleId);
return {
id: role.accessRoleId,
label: localizedInfo.name,
onClick: () => {
onRoleChange(role.accessRoleId);
setIsOpen(false);
},
render: (props) => (
<button {...props}>
<div className="flex flex-col items-start gap-0.5 text-left">
<span className="font-medium text-text-primary">{localizedInfo.name}</span>
<span className="text-xs text-text-secondary">{localizedInfo.description}</span>
</div>
</button>
),
};
});
return (
<div className={className}>
<DropdownPopup
menuId="access-roles-menu"
isOpen={isOpen}
setIsOpen={setIsOpen}
trigger={
<Ariakit.MenuButton
aria-label={selectedRoleInfo?.description || 'Select role'}
className={cn(
'flex items-center justify-between gap-2 rounded-lg border border-border-light bg-surface-primary px-3 py-2 text-sm transition-colors hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-ring-primary',
'min-w-[200px]',
)}
>
<span className="font-medium">
{selectedRoleInfo?.name || localize('com_ui_select')}
</span>
<ChevronDown className="h-4 w-4 text-text-secondary" />
</Ariakit.MenuButton>
}
items={dropdownItems}
className="w-[280px]"
/>
</div>
);
}