diff --git a/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx b/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx index 8aa4c7fdbb..4eaaf6966b 100644 --- a/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx +++ b/client/src/components/Sharing/PeoplePicker/PeoplePicker.tsx @@ -2,10 +2,10 @@ import React, { useState, useMemo } from 'react'; import { PrincipalType } from 'librechat-data-provider'; import type { TPrincipal, PrincipalSearchParams } from 'librechat-data-provider'; import { useSearchPrincipalsQuery } from 'librechat-data-provider/react-query'; +import { useLocalize, usePeoplePickerPermissions } from '~/hooks'; import PeoplePickerSearchItem from './PeoplePickerSearchItem'; import SelectedPrincipalsList from './SelectedPrincipalsList'; import { SearchPicker } from './SearchPicker'; -import { useLocalize } from '~/hooks'; interface PeoplePickerProps { onSelectionChange: (principals: TPrincipal[]) => void; @@ -21,6 +21,7 @@ export default function PeoplePicker({ typeFilter = null, }: PeoplePickerProps) { const localize = useLocalize(); + const { canViewUsers, canViewGroups, canViewRoles } = usePeoplePickerPermissions(); const [searchQuery, setSearchQuery] = useState(''); const [selectedShares, setSelectedShares] = useState([]); @@ -55,6 +56,28 @@ export default function PeoplePicker({ console.error('Principal search error:', error); } + /** Get appropriate label based on permissions */ + const getSearchLabel = () => { + const permissions = [canViewUsers, canViewGroups, canViewRoles]; + const permissionCount = permissions.filter(Boolean).length; + + if (permissionCount === 3) { + return localize('com_ui_search_users_groups_roles'); + } else if (permissionCount === 2) { + if (canViewUsers && canViewGroups) { + return localize('com_ui_search_users_groups'); + } + } else if (canViewUsers) { + return localize('com_ui_search_users'); + } else if (canViewGroups) { + return localize('com_ui_search_groups'); + } else if (canViewRoles) { + return localize('com_ui_search_roles'); + } + + return localize('com_ui_search_users_groups'); + }; + return (
@@ -84,7 +107,7 @@ export default function PeoplePicker({ }); setSearchQuery(''); }} - label={localize('com_ui_search_users_groups')} + label={getSearchLabel()} isLoading={isLoading} />
diff --git a/client/src/components/Sharing/PeoplePicker/PeoplePickerSearchItem.tsx b/client/src/components/Sharing/PeoplePicker/PeoplePickerSearchItem.tsx index a4071d0be0..03166fcddb 100644 --- a/client/src/components/Sharing/PeoplePicker/PeoplePickerSearchItem.tsx +++ b/client/src/components/Sharing/PeoplePicker/PeoplePickerSearchItem.tsx @@ -1,8 +1,9 @@ import React, { forwardRef } from 'react'; +import { PrincipalType } from 'librechat-data-provider'; import type { TPrincipal } from 'librechat-data-provider'; -import { cn } from '~/utils'; +import PrincipalAvatar from '~/components/Sharing/PrincipalAvatar'; import { useLocalize } from '~/hooks'; -import PrincipalAvatar from '../PrincipalAvatar'; +import { cn } from '~/utils'; interface PeoplePickerSearchItemProps extends React.HTMLAttributes { principal: TPrincipal; @@ -16,10 +17,37 @@ const PeoplePickerSearchItem = forwardRef { + switch (type) { + case PrincipalType.USER: + return { + className: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', + label: localize('com_ui_user'), + }; + case PrincipalType.GROUP: + return { + className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + label: localize('com_ui_group'), + }; + case PrincipalType.ROLE: + return { + className: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300', + label: localize('com_ui_role'), + }; + default: + return { + className: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300', + label: type, + }; + } + }; + + const badgeConfig = getBadgeConfig(); + return (
- {type === 'user' ? localize('com_ui_user') : localize('com_ui_group')} + {badgeConfig.label}
diff --git a/client/src/components/Sharing/PeoplePicker/SelectedPrincipalsList.tsx b/client/src/components/Sharing/PeoplePicker/SelectedPrincipalsList.tsx index b323aceb35..4659dc6ec0 100644 --- a/client/src/components/Sharing/PeoplePicker/SelectedPrincipalsList.tsx +++ b/client/src/components/Sharing/PeoplePicker/SelectedPrincipalsList.tsx @@ -3,8 +3,8 @@ import * as Menu from '@ariakit/react/menu'; import { Button, DropdownPopup } from '@librechat/client'; import { Users, X, ExternalLink, ChevronDown } from 'lucide-react'; import type { TPrincipal, TAccessRole, AccessRoleIds } from 'librechat-data-provider'; +import PrincipalAvatar from '~/components/Sharing/PrincipalAvatar'; import { getRoleLocalizationKeys } from '~/utils'; -import PrincipalAvatar from '../PrincipalAvatar'; import { useLocalize } from '~/hooks'; interface SelectedPrincipalsListProps { @@ -36,7 +36,7 @@ export default function SelectedPrincipalsList({
-

{localize('com_ui_search_above_to_add')}

+

{localize('com_ui_search_above_to_add_all')}

); diff --git a/client/src/components/Sharing/PrincipalAvatar.tsx b/client/src/components/Sharing/PrincipalAvatar.tsx index 2914f2f821..a96a66b036 100644 --- a/client/src/components/Sharing/PrincipalAvatar.tsx +++ b/client/src/components/Sharing/PrincipalAvatar.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { Users, User } from 'lucide-react'; +import { Users, User, Shield } from 'lucide-react'; +import { PrincipalType } from 'librechat-data-provider'; import type { TPrincipal } from 'librechat-data-provider'; import { cn } from '~/utils'; @@ -17,7 +18,6 @@ export default function PrincipalAvatar({ const { avatar, type, name } = principal; const displayName = name || 'Unknown'; - // Size variants const sizeClasses = { sm: 'h-6 w-6', md: 'h-8 w-8', @@ -33,7 +33,38 @@ export default function PrincipalAvatar({ const avatarSizeClass = sizeClasses[size]; const iconSizeClass = iconSizeClasses[size]; - // Avatar or icon logic + /** Get icon component and styling based on type */ + const getIconConfig = () => { + switch (type) { + case PrincipalType.USER: + return { + Icon: User, + containerClass: 'bg-blue-100 dark:bg-blue-900', + iconClass: 'text-blue-600 dark:text-blue-400', + }; + case PrincipalType.GROUP: + return { + Icon: Users, + containerClass: 'bg-green-100 dark:bg-green-900', + iconClass: 'text-green-600 dark:text-green-400', + }; + case PrincipalType.ROLE: + return { + Icon: Shield, + containerClass: 'bg-purple-100 dark:bg-purple-900', + iconClass: 'text-purple-600 dark:text-purple-400', + }; + default: + return { + Icon: User, + containerClass: 'bg-gray-100 dark:bg-gray-900', + iconClass: 'text-gray-600 dark:text-gray-400', + }; + } + }; + + const { Icon, containerClass, iconClass } = getIconConfig(); + if (avatar) { return (
@@ -50,52 +81,31 @@ export default function PrincipalAvatar({ /> {/* Hidden fallback icon that shows if image fails */}
- {type === 'user' ? ( -
- -
- ) : ( -
- -
- )} +
+ +
); } - // Fallback icon based on type return (
- {type === 'user' ? ( -
- -
- ) : ( -
- -
- )} +
+ +
); } diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index b8543b8973..186f95ee94 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -693,10 +693,16 @@ "com_ui_grant_access": "Grant Access", "com_ui_granting": "Granting...", "com_ui_search_users_groups": "Search Users and Groups", + "com_ui_search_users_groups_roles": "Search Users, Groups, and Roles", + "com_ui_search_users": "Search Users", + "com_ui_search_groups": "Search Groups", + "com_ui_search_roles": "Search Roles", "com_ui_search_default_placeholder": "Search by name or email (min 2 chars)", "com_ui_user": "User", "com_ui_group": "Group", + "com_ui_role": "Role", "com_ui_search_above_to_add": "Search above to add users or groups", + "com_ui_search_above_to_add_all": "Search above to add users, groups, or roles", "com_ui_azure_ad": "Entra ID", "com_ui_remove_user": "Remove {{0}}", "com_ui_create": "Create",