|
|
|
@ -116,49 +116,55 @@ export const OverlayClosedGroupV2 = () => {
|
|
|
|
|
const privateContactsPubkeys = useContactsToInviteToGroup();
|
|
|
|
|
const isCreatingGroup = useIsCreatingGroupFromUIPending();
|
|
|
|
|
const [groupName, setGroupName] = useState('');
|
|
|
|
|
const [groupNameError, setGroupNameError] = useState<string | undefined>();
|
|
|
|
|
const forceUpdate = useUpdate();
|
|
|
|
|
const {
|
|
|
|
|
uniqueValues: members,
|
|
|
|
|
uniqueValues: selectedMemberIds,
|
|
|
|
|
addTo: addToSelected,
|
|
|
|
|
removeFrom: removeFromSelected,
|
|
|
|
|
} = useSet<string>([]);
|
|
|
|
|
const isSearch = useIsSearching();
|
|
|
|
|
const searchTerm = useSelector(getSearchTerm);
|
|
|
|
|
const searchResultContactsOnly = useSelector(getSearchResultsContactOnly);
|
|
|
|
|
|
|
|
|
|
function closeOverlay() {
|
|
|
|
|
dispatch(clearSearch());
|
|
|
|
|
dispatch(resetLeftOverlayMode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onEnterPressed() {
|
|
|
|
|
setGroupNameError(undefined);
|
|
|
|
|
setGroupName('');
|
|
|
|
|
if (isCreatingGroup) {
|
|
|
|
|
window?.log?.warn('Closed group creation already in progress');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate groupName and groupMembers length
|
|
|
|
|
if (groupName.length === 0) {
|
|
|
|
|
ToastUtils.pushToastError('invalidGroupName', window.i18n('groupNameEnterPlease'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (groupName.length > LIBSESSION_CONSTANTS.BASE_GROUP_MAX_NAME_LENGTH) {
|
|
|
|
|
ToastUtils.pushToastError('invalidGroupName', window.i18n('groupNameEnterShorter'));
|
|
|
|
|
setGroupNameError(window.i18n('groupNameEnterShorter'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// >= because we add ourself as a member AFTER this. so a 10 member group is already invalid as it will be 11 with us
|
|
|
|
|
// the same is valid with groups count < 1
|
|
|
|
|
|
|
|
|
|
if (members.length < 1) {
|
|
|
|
|
if (selectedMemberIds.length < 1) {
|
|
|
|
|
ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('groupCreateErrorNoMembers'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (members.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) {
|
|
|
|
|
if (selectedMemberIds.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) {
|
|
|
|
|
ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('groupAddMemberMaximum'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// trigger the add through redux.
|
|
|
|
|
dispatch(
|
|
|
|
|
groupInfoActions.initNewGroupInWrapper({
|
|
|
|
|
members: concat(members, [us]),
|
|
|
|
|
members: concat(selectedMemberIds, [us]),
|
|
|
|
|
groupName,
|
|
|
|
|
us,
|
|
|
|
|
}) as any
|
|
|
|
@ -166,16 +172,26 @@ export const OverlayClosedGroupV2 = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useKey('Escape', closeOverlay);
|
|
|
|
|
|
|
|
|
|
const noContactsForClosedGroup = privateContactsPubkeys.length === 0;
|
|
|
|
|
|
|
|
|
|
const contactsToRender = isSearch ? searchResultContactsOnly : privateContactsPubkeys;
|
|
|
|
|
|
|
|
|
|
const disableCreateButton = !members.length && !groupName.length;
|
|
|
|
|
const noContactsForClosedGroup = isEmpty(searchTerm) && contactsToRender.length === 0;
|
|
|
|
|
|
|
|
|
|
const disableCreateButton = isCreatingGroup || (!selectedMemberIds.length && !groupName.length);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="module-left-pane-overlay">
|
|
|
|
|
<div className="create-group-name-input">
|
|
|
|
|
<StyledLeftPaneOverlay
|
|
|
|
|
container={true}
|
|
|
|
|
flexDirection={'column'}
|
|
|
|
|
flexGrow={1}
|
|
|
|
|
alignItems={'center'}
|
|
|
|
|
>
|
|
|
|
|
<Flex
|
|
|
|
|
container={true}
|
|
|
|
|
width={'100%'}
|
|
|
|
|
flexDirection="column"
|
|
|
|
|
alignItems="center"
|
|
|
|
|
padding={'var(--margins-md)'}
|
|
|
|
|
>
|
|
|
|
|
<SessionInput
|
|
|
|
|
autoFocus={true}
|
|
|
|
|
type="text"
|
|
|
|
@ -183,65 +199,74 @@ export const OverlayClosedGroupV2 = () => {
|
|
|
|
|
value={groupName}
|
|
|
|
|
onValueChanged={setGroupName}
|
|
|
|
|
onEnterPressed={onEnterPressed}
|
|
|
|
|
// error={groupNameError} // TODO fix error handling with new groups (we are deprecating that UI soon)
|
|
|
|
|
error={groupNameError}
|
|
|
|
|
maxLength={LIBSESSION_CONSTANTS.BASE_GROUP_MAX_NAME_LENGTH}
|
|
|
|
|
textSize="md"
|
|
|
|
|
centerText={true}
|
|
|
|
|
monospaced={true}
|
|
|
|
|
isTextArea={true}
|
|
|
|
|
inputDataTestId="new-closed-group-name"
|
|
|
|
|
editable={!noContactsForClosedGroup}
|
|
|
|
|
editable={!noContactsForClosedGroup && !isCreatingGroup}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<SessionSpinner loading={isCreatingGroup} />
|
|
|
|
|
{/* TODO: localize those strings once out releasing those buttons for real Remove after QA */}
|
|
|
|
|
{hasClosedGroupV2QAButtons() && (
|
|
|
|
|
<>
|
|
|
|
|
<span style={{ display: 'flex', alignItems: 'center' }}>
|
|
|
|
|
Invite as admin?{' '}
|
|
|
|
|
<SessionToggle
|
|
|
|
|
active={window.sessionFeatureFlags.useGroupV2InviteAsAdmin}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
window.sessionFeatureFlags.useGroupV2InviteAsAdmin =
|
|
|
|
|
!window.sessionFeatureFlags.useGroupV2InviteAsAdmin;
|
|
|
|
|
forceUpdate();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</span>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
<SpacerMD />
|
|
|
|
|
{/* TODO: localize those strings once out releasing those buttons for real Remove after QA */}
|
|
|
|
|
{hasClosedGroupV2QAButtons() && (
|
|
|
|
|
<>
|
|
|
|
|
<span style={{ display: 'flex', alignItems: 'center' }}>
|
|
|
|
|
Invite as admin?{' '}
|
|
|
|
|
<SessionToggle
|
|
|
|
|
active={window.sessionFeatureFlags.useGroupV2InviteAsAdmin}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
window.sessionFeatureFlags.useGroupV2InviteAsAdmin =
|
|
|
|
|
!window.sessionFeatureFlags.useGroupV2InviteAsAdmin;
|
|
|
|
|
forceUpdate();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</span>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<SessionSpinner loading={isCreatingGroup} />
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
</Flex>
|
|
|
|
|
|
|
|
|
|
<SessionSearchInput />
|
|
|
|
|
{!noContactsForClosedGroup && window.sessionFeatureFlags.useClosedGroupV2 && (
|
|
|
|
|
<GroupInviteRequiredVersionBanner />
|
|
|
|
|
)}
|
|
|
|
|
{!noContactsForClosedGroup && <GroupInviteRequiredVersionBanner />}
|
|
|
|
|
|
|
|
|
|
<StyledGroupMemberListContainer>
|
|
|
|
|
{noContactsForClosedGroup ? (
|
|
|
|
|
<NoContacts />
|
|
|
|
|
) : searchTerm && !contactsToRender.length ? (
|
|
|
|
|
<StyledNoResults>
|
|
|
|
|
<Localizer token="searchMatchesNoneSpecific" args={{ query: searchTerm }} />
|
|
|
|
|
</StyledNoResults>
|
|
|
|
|
) : (
|
|
|
|
|
contactsToRender.map((memberPubkey: string) => (
|
|
|
|
|
<MemberListItem
|
|
|
|
|
key={`member-list-${memberPubkey}`}
|
|
|
|
|
pubkey={memberPubkey}
|
|
|
|
|
isSelected={members.some(m => m === memberPubkey)}
|
|
|
|
|
key={memberPubkey}
|
|
|
|
|
isSelected={selectedMemberIds.includes(memberPubkey)}
|
|
|
|
|
onSelect={addToSelected}
|
|
|
|
|
onUnselect={removeFromSelected}
|
|
|
|
|
disableBg={true}
|
|
|
|
|
withBorder={false}
|
|
|
|
|
disabled={isCreatingGroup}
|
|
|
|
|
maxNameWidth="100%"
|
|
|
|
|
/>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</StyledGroupMemberListContainer>
|
|
|
|
|
|
|
|
|
|
<SpacerLG style={{ flexShrink: 0 }} />
|
|
|
|
|
<SessionButton
|
|
|
|
|
text={window.i18n('create')}
|
|
|
|
|
disabled={disableCreateButton}
|
|
|
|
|
onClick={onEnterPressed}
|
|
|
|
|
dataTestId="create-group-button"
|
|
|
|
|
margin="auto 0 var(--margins-lg) 0 " // just to keep that button at the bottom of the overlay (even with an empty list)
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<Flex container={true} width={'100%'} flexDirection="column" padding={'var(--margins-md)'}>
|
|
|
|
|
<SessionButton
|
|
|
|
|
text={window.i18n('create')}
|
|
|
|
|
disabled={disableCreateButton}
|
|
|
|
|
onClick={onEnterPressed}
|
|
|
|
|
dataTestId="create-group-button"
|
|
|
|
|
margin="auto 0 0" // just to keep that button at the bottom of the overlay (even with an empty list)
|
|
|
|
|
/>
|
|
|
|
|
</Flex>
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
</StyledLeftPaneOverlay>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|