import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { GroupType, GroupMemberRole, GroupInviteOption, } from 'tuikit-atomicx-react/chat'; import { useUIContext, useGroupStore, useLoginStore, useContactStore } from 'tuikit-atomicx-react/chat'; import { Dialog, IconCopy, Toast, useUIKit } from '@tencentcloud/uikit-base-component-react'; import cs from 'classnames'; import { View } from 'tuikit-atomicx-react/chat'; import { copyTextToClipboard } from '../../../utils'; import { GroupPermission, hasGroupPermission } from '../../../utils/chatGroupPermission'; import { UserPicker } from 'tuikit-atomicx-react/chat'; import { Divider } from '../Divider'; import { SettingItem } from '../SettingItem'; import { GroupActions } from './GroupActions'; import classes from './GroupChatSetting.module.scss'; import { GroupManagement } from './GroupManagement'; import { GroupManagementEntry } from './GroupManagementEntry'; import { GroupMembers } from './GroupMembers'; import { PersonalSettings } from './PersonalSettings'; import type { IUserPickerRow, IUserPickerRef } from 'tuikit-atomicx-react/chat'; import type { GroupInfo, GroupMember } from 'tuikit-atomicx-react/chat'; interface GroupChatSettingProps { className?: string; onHeaderVisibleChange?: (visible: boolean) => void; } enum ViewMode { MAIN = 'main', GROUP_MANAGEMENT = 'group_management', } const GroupChatSetting = forwardRef((props, ref) => { const { className, onHeaderVisibleChange } = props; const ctx = useUIContext(); const { activeConversationID, memberList, hasMoreMembers, loadMembers, loadMoreMembers, addMember, deleteMember } = ctx; const { joinedGroupList, updateProfile, loadJoinedGroups, getGroupInfo } = useGroupStore(); const { loginUserInfo } = useLoginStore(); const { friendList, loadFriends } = useContactStore(); const { t } = useUIKit(); const currentGroupID = useMemo(() => activeConversationID?.startsWith('GROUP') ? activeConversationID.slice(5) : undefined, [activeConversationID]); // Full GroupInfo (including selfRole, notification etc.) fetched on demand const [fullGroupInfo, setFullGroupInfo] = useState(undefined); useEffect(() => { loadJoinedGroups(); loadFriends(); }, []); useEffect(() => { if (!currentGroupID) { setFullGroupInfo(undefined); return; } getGroupInfo(currentGroupID).then(setFullGroupInfo).catch(() => setFullGroupInfo(undefined)); }, [currentGroupID]); // Keep fullGroupInfo in sync with joinedGroupList updates (e.g. group name change) useEffect(() => { if (!fullGroupInfo || !currentGroupID) { return; } const currentGroup = joinedGroupList.find(g => g.groupID === currentGroupID); if (currentGroup) { setFullGroupInfo(prev => ({ ...prev, ...currentGroup })); } else { // No longer in group (quit/dismissed) — clear selfRole so isInGroup becomes false setFullGroupInfo(prev => prev ? { ...prev, selfRole: undefined } : undefined); } }, [joinedGroupList, currentGroupID]); const groupID = fullGroupInfo?.groupID; const groupName = fullGroupInfo?.groupName; const notification = fullGroupInfo?.notification; const groupType = fullGroupInfo?.groupType; const inviteOption = fullGroupInfo?.inviteOption; const isInGroup = fullGroupInfo?.selfRole !== undefined; const currentUserRole = fullGroupInfo?.selfRole; const currentUserID = loginUserInfo?.userID; const prevGroupID = useRef(''); const userPickerRef = useRef(null); const [loading, setLoading] = useState(false); const [currentView, setCurrentView] = useState(ViewMode.MAIN); const [isShowUserPickerDialog, setIsShowUserPickerDialog] = useState(false); const [memberDataSource, setMemberDataSource] = useState([]); const [userPickerLockedItems, setUserPickerLockedItems] = useState([]); const [memberActionType, setMemberActionType] = useState<'remove' | 'add' | null>(null); useEffect(() => { onHeaderVisibleChange?.(currentView !== ViewMode.GROUP_MANAGEMENT); }, [currentView, onHeaderVisibleChange]); useEffect(() => { return () => { onHeaderVisibleChange?.(true); }; }, [onHeaderVisibleChange]); const canEditName = hasGroupPermission(GroupPermission.EDIT_GROUP_PROFILE_NAME, currentUserRole, groupType) && isInGroup; const canEditNotification = hasGroupPermission(GroupPermission.EDIT_GROUP_PROFILE_NOTIFICATION, currentUserRole, groupType) && isInGroup; const canRemoveMember = hasGroupPermission(GroupPermission.REMOVE_MEMBER, currentUserRole, groupType); const getGroupTypeText = useCallback(() => { if (!groupType) { return t('ChatSetting.group_type_unknown'); } const groupTypeTextMap: Record = { [GroupType.Work]: t('ChatSetting.group_type_work'), [GroupType.Public]: t('ChatSetting.group_type_public'), [GroupType.Meeting]: t('ChatSetting.group_type_meeting'), [GroupType.Community]: t('ChatSetting.group_type_community'), [GroupType.AVChatRoom]: t('ChatSetting.group_type_avchatroom'), }; return groupTypeTextMap[groupType] || t('ChatSetting.group_type_unknown'); }, [groupType, t]); const validateGroupName = useCallback((value: string, originalValue?: string) => { if (typeof value !== 'string') { return t('ChatSetting.group_name_required_string'); } if (value.length === 0) { return t('ChatSetting.group_name_required'); } if (value.length > 30) { return t('ChatSetting.group_name_max_length'); } if (value === (originalValue || '')) { return t('ChatSetting.group_name_unchanged'); } return null; }, [t]); const validateNotification = useCallback((value: string, originalValue?: string) => { if (typeof value !== 'string') { return t('ChatSetting.group_notification_required_string'); } if (value.length > 130) { return t('ChatSetting.group_notification_max_length'); } if (value === (originalValue || '')) { return t('ChatSetting.group_notification_unchanged'); } return null; }, [t]); const handleGroupNameConfirm = useCallback(async (value: string) => { if (!currentGroupID) { return; } try { await updateProfile({ groupID: currentGroupID, groupName: value }); Toast.success({ message: t('ChatSetting.group_name_update_success') }); } catch { Toast.error({ message: t('ChatSetting.group_name_update_failed') }); } }, [currentGroupID, t, updateProfile]); const handleNotificationConfirm = useCallback(async (value: string) => { if (!currentGroupID) { return; } try { await updateProfile({ groupID: currentGroupID, notification: value }); Toast.success({ message: t('ChatSetting.group_notification_update_success') }); } catch { Toast.error({ message: t('ChatSetting.group_notification_update_failed') }); } }, [currentGroupID, t, updateProfile]); const handleCopyGroupID = useCallback(() => { if (!groupID) { return; } copyTextToClipboard(groupID).then(() => { Toast.success({ message: t('ChatSetting.copied') }); }); }, [groupID, t]); // Reset state when switching groups useEffect(() => { if (currentGroupID && prevGroupID.current !== currentGroupID) { setLoading(false); setCurrentView(ViewMode.MAIN); prevGroupID.current = currentGroupID; } }, [currentGroupID]); // Load members once we confirm we're in the group useEffect(() => { if (currentGroupID && isInGroup) { loadMembers(); } }, [currentGroupID, isInGroup]); const handleLoadMoreMembers = useCallback(async () => { if (loading || !hasMoreMembers || !currentGroupID) { return; } setLoading(true); try { await loadMoreMembers(); } catch { Toast.error({ message: t('ChatSetting.failed_to_load_more_members') }); } finally { setLoading(false); } }, [hasMoreMembers, currentGroupID, loading, t]); const onUserPickerDialogOpen = useCallback((action: 'remove' | 'add') => { if (!memberList.length) { return; } if (action === 'remove') { setMemberActionType('remove'); setMemberDataSource(memberList.map((member: GroupMember) => { let label = member.nickname || member.userID; if (label.length > 20) { label = `${label.slice(0, 20)}...`; } label = `${label} (${t(`ChatSetting.group_member_role_${(member.role ?? '').toLowerCase()}`)})`; if (member.userID === currentUserID) { label = `${label} (${t('ChatSetting.me')})`; } return { key: member.userID, label, avatarUrl: member.avatarURL ?? '' }; })); setUserPickerLockedItems( memberList .filter((member: GroupMember) => member.userID === currentUserID || member.role === GroupMemberRole.Owner || (currentUserRole === GroupMemberRole.Admin && member.role === GroupMemberRole.Admin)) .map((member: GroupMember) => ({ key: member.userID, label: member.nickname || member.userID, avatarUrl: member.avatarURL ?? '', })), ); setIsShowUserPickerDialog(true); return; } setMemberActionType('add'); setMemberDataSource(friendList.map(friend => ({ key: friend.userID, label: friend.nickname || friend.userID, avatarUrl: friend.avatarURL ?? '', }))); setUserPickerLockedItems( memberList.map((member: GroupMember) => ({ key: member.userID, label: member.nickname || member.userID, avatarUrl: member.avatarURL ?? '', })), ); setIsShowUserPickerDialog(true); }, [memberList, currentUserID, currentUserRole, friendList, t]); const onUserPickerConfirm = useCallback(() => { const selectedItems = userPickerRef.current?.getSelectedItems(); if (memberActionType === 'remove') { if (selectedItems && selectedItems.length > 0) { deleteMember(selectedItems.map(item => item.key)) .then(() => { Toast.success({ message: t('ChatSetting.group_member_remove_success') }); }) .catch(() => { Toast.error({ message: t('ChatSetting.group_member_remove_failed') }); }); } } else if (memberActionType === 'add') { if (selectedItems && selectedItems.length > 0) { addMember(selectedItems.map(item => item.key)) .then((result) => { if (result.successUserIDList.length < selectedItems.length) { Toast.warning({ message: t('ChatSetting.group_member_add_partially_failed') }); } else { Toast.success({ message: t('ChatSetting.group_member_add_success') }); } }) .catch(() => { Toast.error({ message: t('ChatSetting.group_member_add_failed') }); }); } } setMemberActionType(null); setIsShowUserPickerDialog(false); }, [addMember, deleteMember, memberActionType, t]); const userPickerDialogTitle = useMemo(() => memberActionType === 'remove' ? t('ChatSetting.remove_member_dialog_title') : t('ChatSetting.add_member_dialog_title'), [memberActionType, t]); if (!groupID) { return null; } if (currentView === ViewMode.GROUP_MANAGEMENT) { return ( setCurrentView(ViewMode.MAIN)} /> ); } return ( {t('ChatSetting.group_id')} {groupID} {isInGroup && ( <> onUserPickerDialogOpen('remove')} onAddButtonClick={() => onUserPickerDialogOpen('add')} /> )} {isInGroup && setCurrentView(ViewMode.GROUP_MANAGEMENT)} />} {isInGroup && ( <> setIsShowUserPickerDialog(false)} onCancel={() => setIsShowUserPickerDialog(false)} onConfirm={onUserPickerConfirm} style={{ height: '70vh' }} confirmText={t('ChatSetting.confirm')} cancelText={t('ChatSetting.cancel')} > )} ); }); GroupChatSetting.displayName = 'GroupChatSetting'; export { GroupChatSetting }; export type { GroupChatSettingProps };