Allow adding to a group by phone number
This commit is contained in:
parent
76a1a805ef
commit
9568d5792e
49 changed files with 1842 additions and 693 deletions
|
@ -9,12 +9,18 @@ import { storiesOf } from '@storybook/react';
|
|||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { sleep } from '../../../util/sleep';
|
||||
import { makeLookup } from '../../../util/makeLookup';
|
||||
import { deconstructLookup } from '../../../util/deconstructLookup';
|
||||
import { setupI18n } from '../../../util/setupI18n';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import enMessages from '../../../../_locales/en/messages.json';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { AddGroupMembersModal } from './AddGroupMembersModal';
|
||||
import { ChooseGroupMembersModal } from './AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
import { ConfirmAdditionsModal } from './AddGroupMembersModal/ConfirmAdditionsModal';
|
||||
import { RequestState } from './util';
|
||||
import { ThemeType } from '../../../types/Util';
|
||||
import { makeFakeLookupConversationWithoutUuid } from '../../../test-both/helpers/fakeLookupConversationWithoutUuid';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -24,14 +30,23 @@ const story = storiesOf(
|
|||
);
|
||||
|
||||
const allCandidateContacts = times(50, () => getDefaultConversation());
|
||||
let allCandidateContactsLookup = makeLookup(allCandidateContacts, 'id');
|
||||
|
||||
const lookupConversationWithoutUuid = makeFakeLookupConversationWithoutUuid(
|
||||
convo => {
|
||||
allCandidateContacts.push(convo);
|
||||
allCandidateContactsLookup = makeLookup(allCandidateContacts, 'id');
|
||||
}
|
||||
);
|
||||
|
||||
type PropsType = ComponentProps<typeof AddGroupMembersModal>;
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
candidateContacts: allCandidateContacts,
|
||||
const createProps = (
|
||||
overrideProps: Partial<PropsType> = {},
|
||||
candidateContacts: Array<ConversationType> = []
|
||||
): PropsType => ({
|
||||
clearRequestError: action('clearRequestError'),
|
||||
conversationIdsAlreadyInGroup: new Set(),
|
||||
getPreferredBadge: () => undefined,
|
||||
groupTitle: 'Tahoe Trip',
|
||||
i18n,
|
||||
onClose: action('onClose'),
|
||||
|
@ -39,7 +54,38 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
action('onMakeRequest')(conversationIds);
|
||||
},
|
||||
requestState: RequestState.Inactive,
|
||||
theme: ThemeType.light,
|
||||
renderChooseGroupMembersModal: props => {
|
||||
const { selectedConversationIds } = props;
|
||||
return (
|
||||
<ChooseGroupMembersModal
|
||||
{...props}
|
||||
candidateContacts={candidateContacts}
|
||||
selectedContacts={deconstructLookup(
|
||||
allCandidateContactsLookup,
|
||||
selectedConversationIds
|
||||
)}
|
||||
regionCode="US"
|
||||
getPreferredBadge={() => undefined}
|
||||
theme={ThemeType.light}
|
||||
i18n={i18n}
|
||||
lookupConversationWithoutUuid={lookupConversationWithoutUuid}
|
||||
showUserNotFoundModal={action('showUserNotFoundModal')}
|
||||
/>
|
||||
);
|
||||
},
|
||||
renderConfirmAdditionsModal: props => {
|
||||
const { selectedConversationIds } = props;
|
||||
return (
|
||||
<ConfirmAdditionsModal
|
||||
{...props}
|
||||
i18n={i18n}
|
||||
selectedContacts={deconstructLookup(
|
||||
allCandidateContactsLookup,
|
||||
selectedConversationIds
|
||||
)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
...overrideProps,
|
||||
});
|
||||
|
||||
|
@ -47,18 +93,12 @@ story.add('Default', () => <AddGroupMembersModal {...createProps()} />);
|
|||
|
||||
story.add('Only 3 contacts', () => (
|
||||
<AddGroupMembersModal
|
||||
{...createProps({
|
||||
candidateContacts: allCandidateContacts.slice(0, 3),
|
||||
})}
|
||||
{...createProps({}, allCandidateContacts.slice(0, 3))}
|
||||
/>
|
||||
));
|
||||
|
||||
story.add('No candidate contacts', () => (
|
||||
<AddGroupMembersModal
|
||||
{...createProps({
|
||||
candidateContacts: [],
|
||||
})}
|
||||
/>
|
||||
<AddGroupMembersModal {...createProps({}, [])} />
|
||||
));
|
||||
|
||||
story.add('Everyone already added', () => (
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FunctionComponent } from 'react';
|
||||
import React, { useMemo, useReducer } from 'react';
|
||||
import React, { useReducer } from 'react';
|
||||
import { without } from 'lodash';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../../types/Util';
|
||||
import type { LocalizerType } from '../../../types/Util';
|
||||
import {
|
||||
AddGroupMemberErrorDialog,
|
||||
AddGroupMemberErrorDialogMode,
|
||||
} from '../../AddGroupMemberErrorDialog';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
|
||||
import type { SmartChooseGroupMembersModalPropsType } from '../../../state/smart/ChooseGroupMembersModal';
|
||||
import type { SmartConfirmAdditionsModalPropsType } from '../../../state/smart/ConfirmAdditionsModal';
|
||||
import {
|
||||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
|
@ -20,24 +20,24 @@ import {
|
|||
toggleSelectedContactForGroupAddition,
|
||||
OneTimeModalState,
|
||||
} from '../../../groups/toggleSelectedContactForGroupAddition';
|
||||
import { makeLookup } from '../../../util/makeLookup';
|
||||
import { deconstructLookup } from '../../../util/deconstructLookup';
|
||||
import { missingCaseError } from '../../../util/missingCaseError';
|
||||
import type { RequestState } from './util';
|
||||
import { ChooseGroupMembersModal } from './AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
import { ConfirmAdditionsModal } from './AddGroupMembersModal/ConfirmAdditionsModal';
|
||||
|
||||
type PropsType = {
|
||||
candidateContacts: ReadonlyArray<ConversationType>;
|
||||
clearRequestError: () => void;
|
||||
conversationIdsAlreadyInGroup: Set<string>;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
groupTitle: string;
|
||||
i18n: LocalizerType;
|
||||
makeRequest: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||
onClose: () => void;
|
||||
requestState: RequestState;
|
||||
theme: ThemeType;
|
||||
|
||||
renderChooseGroupMembersModal: (
|
||||
props: SmartChooseGroupMembersModalPropsType
|
||||
) => JSX.Element;
|
||||
renderConfirmAdditionsModal: (
|
||||
props: SmartConfirmAdditionsModalPropsType
|
||||
) => JSX.Element;
|
||||
};
|
||||
|
||||
enum Stage {
|
||||
|
@ -135,16 +135,15 @@ function reducer(
|
|||
}
|
||||
|
||||
export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
|
||||
candidateContacts,
|
||||
clearRequestError,
|
||||
conversationIdsAlreadyInGroup,
|
||||
getPreferredBadge,
|
||||
groupTitle,
|
||||
i18n,
|
||||
onClose,
|
||||
makeRequest,
|
||||
requestState,
|
||||
theme,
|
||||
renderChooseGroupMembersModal,
|
||||
renderConfirmAdditionsModal,
|
||||
}) => {
|
||||
const maxGroupSize = getMaximumNumberOfContacts();
|
||||
const maxRecommendedGroupSize = getRecommendedMaximumNumberOfContacts();
|
||||
|
@ -175,16 +174,6 @@ export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
stage: Stage.ChoosingContacts,
|
||||
});
|
||||
|
||||
const contactLookup = useMemo(
|
||||
() => makeLookup(candidateContacts, 'id'),
|
||||
[candidateContacts]
|
||||
);
|
||||
|
||||
const selectedContacts = deconstructLookup(
|
||||
contactLookup,
|
||||
selectedConversationIds
|
||||
);
|
||||
|
||||
if (maximumGroupSizeModalState === OneTimeModalState.Showing) {
|
||||
return (
|
||||
<AddGroupMemberErrorDialog
|
||||
|
@ -239,23 +228,17 @@ export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ChooseGroupMembersModal
|
||||
candidateContacts={candidateContacts}
|
||||
confirmAdds={confirmAdds}
|
||||
conversationIdsAlreadyInGroup={conversationIdsAlreadyInGroup}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
maxGroupSize={maxGroupSize}
|
||||
onClose={onClose}
|
||||
removeSelectedContact={removeSelectedContact}
|
||||
searchTerm={searchTerm}
|
||||
selectedContacts={selectedContacts}
|
||||
setSearchTerm={setSearchTerm}
|
||||
theme={theme}
|
||||
toggleSelectedContact={toggleSelectedContact}
|
||||
/>
|
||||
);
|
||||
return renderChooseGroupMembersModal({
|
||||
confirmAdds,
|
||||
selectedConversationIds,
|
||||
conversationIdsAlreadyInGroup,
|
||||
maxGroupSize,
|
||||
onClose,
|
||||
removeSelectedContact,
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
toggleSelectedContact,
|
||||
});
|
||||
}
|
||||
case Stage.ConfirmingAdds: {
|
||||
const onCloseConfirmationDialog = () => {
|
||||
|
@ -263,18 +246,15 @@ export const AddGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
clearRequestError();
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmAdditionsModal
|
||||
groupTitle={groupTitle}
|
||||
i18n={i18n}
|
||||
makeRequest={() => {
|
||||
makeRequest(selectedConversationIds);
|
||||
}}
|
||||
onClose={onCloseConfirmationDialog}
|
||||
requestState={requestState}
|
||||
selectedContacts={selectedContacts}
|
||||
/>
|
||||
);
|
||||
return renderConfirmAdditionsModal({
|
||||
groupTitle,
|
||||
makeRequest: () => {
|
||||
makeRequest(selectedConversationIds);
|
||||
},
|
||||
onClose: onCloseConfirmationDialog,
|
||||
requestState,
|
||||
selectedConversationIds,
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw missingCaseError(stage);
|
||||
|
|
|
@ -2,7 +2,14 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FunctionComponent } from 'react';
|
||||
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import type { MeasuredComponentProps } from 'react-measure';
|
||||
import Measure from 'react-measure';
|
||||
|
||||
|
@ -11,9 +18,16 @@ import { assert } from '../../../../util/assert';
|
|||
import { refMerger } from '../../../../util/refMerger';
|
||||
import { useRestoreFocus } from '../../../../hooks/useRestoreFocus';
|
||||
import { missingCaseError } from '../../../../util/missingCaseError';
|
||||
import type { LookupConversationWithoutUuidActionsType } from '../../../../util/lookupConversationWithoutUuid';
|
||||
import { parseAndFormatPhoneNumber } from '../../../../util/libphonenumberInstance';
|
||||
import { filterAndSortConversationsByTitle } from '../../../../util/filterAndSortConversations';
|
||||
import type { ConversationType } from '../../../../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../../../../state/selectors/badges';
|
||||
import type {
|
||||
UUIDFetchStateKeyType,
|
||||
UUIDFetchStateType,
|
||||
} from '../../../../util/uuidFetchState';
|
||||
import { isFetchingByE164 } from '../../../../util/uuidFetchState';
|
||||
import { ModalHost } from '../../../ModalHost';
|
||||
import { ContactPills } from '../../../ContactPills';
|
||||
import { ContactPill } from '../../../ContactPill';
|
||||
|
@ -23,24 +37,37 @@ import { ContactCheckboxDisabledReason } from '../../../conversationList/Contact
|
|||
import { Button, ButtonVariant } from '../../../Button';
|
||||
import { SearchInput } from '../../../SearchInput';
|
||||
|
||||
type PropsType = {
|
||||
export type StatePropsType = {
|
||||
regionCode: string | undefined;
|
||||
candidateContacts: ReadonlyArray<ConversationType>;
|
||||
confirmAdds: () => void;
|
||||
conversationIdsAlreadyInGroup: Set<string>;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
i18n: LocalizerType;
|
||||
theme: ThemeType;
|
||||
maxGroupSize: number;
|
||||
onClose: () => void;
|
||||
removeSelectedContact: (_: string) => void;
|
||||
searchTerm: string;
|
||||
selectedContacts: ReadonlyArray<ConversationType>;
|
||||
|
||||
confirmAdds: () => void;
|
||||
onClose: () => void;
|
||||
removeSelectedContact: (_: string) => void;
|
||||
setSearchTerm: (_: string) => void;
|
||||
theme: ThemeType;
|
||||
toggleSelectedContact: (conversationId: string) => void;
|
||||
};
|
||||
} & Pick<
|
||||
LookupConversationWithoutUuidActionsType,
|
||||
'lookupConversationWithoutUuid'
|
||||
>;
|
||||
|
||||
type ActionPropsType = Omit<
|
||||
LookupConversationWithoutUuidActionsType,
|
||||
'setIsFetchingUUID' | 'lookupConversationWithoutUuid'
|
||||
>;
|
||||
|
||||
type PropsType = StatePropsType & ActionPropsType;
|
||||
|
||||
// TODO: This should use <Modal>. See DESKTOP-1038.
|
||||
export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
||||
regionCode,
|
||||
candidateContacts,
|
||||
confirmAdds,
|
||||
conversationIdsAlreadyInGroup,
|
||||
|
@ -54,9 +81,24 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
setSearchTerm,
|
||||
theme,
|
||||
toggleSelectedContact,
|
||||
lookupConversationWithoutUuid,
|
||||
showUserNotFoundModal,
|
||||
}) => {
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
||||
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||
|
||||
let isPhoneNumberChecked = false;
|
||||
if (phoneNumber) {
|
||||
isPhoneNumberChecked =
|
||||
phoneNumber.isValid &&
|
||||
selectedContacts.some(contact => contact.e164 === phoneNumber.e164);
|
||||
}
|
||||
|
||||
const isPhoneNumberVisible =
|
||||
phoneNumber &&
|
||||
candidateContacts.every(contact => contact.e164 !== phoneNumber.e164);
|
||||
|
||||
const inputRef = useRef<null | HTMLInputElement>(null);
|
||||
|
||||
const numberOfContactsAlreadyInGroup = conversationIdsAlreadyInGroup.size;
|
||||
|
@ -72,7 +114,7 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
const canContinue = Boolean(selectedContacts.length);
|
||||
|
||||
const [filteredContacts, setFilteredContacts] = useState(
|
||||
filterAndSortConversationsByTitle(candidateContacts, '')
|
||||
filterAndSortConversationsByTitle(candidateContacts, '', regionCode)
|
||||
);
|
||||
const normalizedSearchTerm = searchTerm.trim();
|
||||
useEffect(() => {
|
||||
|
@ -80,38 +122,106 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
setFilteredContacts(
|
||||
filterAndSortConversationsByTitle(
|
||||
candidateContacts,
|
||||
normalizedSearchTerm
|
||||
normalizedSearchTerm,
|
||||
regionCode
|
||||
)
|
||||
);
|
||||
}, 200);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [candidateContacts, normalizedSearchTerm, setFilteredContacts]);
|
||||
}, [
|
||||
candidateContacts,
|
||||
normalizedSearchTerm,
|
||||
setFilteredContacts,
|
||||
regionCode,
|
||||
]);
|
||||
|
||||
const rowCount = filteredContacts.length;
|
||||
const [uuidFetchState, setUuidFetchState] = useState<UUIDFetchStateType>({});
|
||||
|
||||
const setIsFetchingUUID = useCallback(
|
||||
(identifier: UUIDFetchStateKeyType, isFetching: boolean) => {
|
||||
setUuidFetchState(prevState => {
|
||||
return isFetching
|
||||
? {
|
||||
...prevState,
|
||||
[identifier]: isFetching,
|
||||
}
|
||||
: omit(prevState, identifier);
|
||||
});
|
||||
},
|
||||
[setUuidFetchState]
|
||||
);
|
||||
|
||||
let rowCount = 0;
|
||||
if (filteredContacts.length) {
|
||||
rowCount += filteredContacts.length;
|
||||
}
|
||||
if (isPhoneNumberVisible) {
|
||||
// "Contacts" header
|
||||
if (filteredContacts.length) {
|
||||
rowCount += 1;
|
||||
}
|
||||
|
||||
// "Find by phone number" + phone number
|
||||
rowCount += 2;
|
||||
}
|
||||
const getRow = (index: number): undefined | Row => {
|
||||
const contact = filteredContacts[index];
|
||||
if (!contact) {
|
||||
return undefined;
|
||||
let virtualIndex = index;
|
||||
|
||||
if (isPhoneNumberVisible && filteredContacts.length) {
|
||||
if (virtualIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
i18nKey: 'contactsHeader',
|
||||
};
|
||||
}
|
||||
|
||||
virtualIndex -= 1;
|
||||
}
|
||||
|
||||
const isSelected = selectedConversationIdsSet.has(contact.id);
|
||||
const isAlreadyInGroup = conversationIdsAlreadyInGroup.has(contact.id);
|
||||
if (virtualIndex < filteredContacts.length) {
|
||||
const contact = filteredContacts[virtualIndex];
|
||||
|
||||
let disabledReason: undefined | ContactCheckboxDisabledReason;
|
||||
if (isAlreadyInGroup) {
|
||||
disabledReason = ContactCheckboxDisabledReason.AlreadyAdded;
|
||||
} else if (hasSelectedMaximumNumberOfContacts && !isSelected) {
|
||||
disabledReason = ContactCheckboxDisabledReason.MaximumContactsSelected;
|
||||
const isSelected = selectedConversationIdsSet.has(contact.id);
|
||||
const isAlreadyInGroup = conversationIdsAlreadyInGroup.has(contact.id);
|
||||
|
||||
let disabledReason: undefined | ContactCheckboxDisabledReason;
|
||||
if (isAlreadyInGroup) {
|
||||
disabledReason = ContactCheckboxDisabledReason.AlreadyAdded;
|
||||
} else if (hasSelectedMaximumNumberOfContacts && !isSelected) {
|
||||
disabledReason = ContactCheckboxDisabledReason.MaximumContactsSelected;
|
||||
}
|
||||
|
||||
return {
|
||||
type: RowType.ContactCheckbox,
|
||||
contact,
|
||||
isChecked: isSelected || isAlreadyInGroup,
|
||||
disabledReason,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: RowType.ContactCheckbox,
|
||||
contact,
|
||||
isChecked: isSelected || isAlreadyInGroup,
|
||||
disabledReason,
|
||||
};
|
||||
virtualIndex -= filteredContacts.length;
|
||||
|
||||
if (isPhoneNumberVisible) {
|
||||
if (virtualIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
i18nKey: 'findByPhoneNumberHeader',
|
||||
};
|
||||
}
|
||||
if (virtualIndex === 1) {
|
||||
return {
|
||||
type: RowType.PhoneNumberCheckbox,
|
||||
isChecked: isPhoneNumberChecked,
|
||||
isFetching: isFetchingByE164(uuidFetchState, phoneNumber.e164),
|
||||
phoneNumber,
|
||||
};
|
||||
}
|
||||
virtualIndex -= 2;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -207,6 +317,12 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
throw missingCaseError(disabledReason);
|
||||
}
|
||||
}}
|
||||
lookupConversationWithoutUuid={
|
||||
lookupConversationWithoutUuid
|
||||
}
|
||||
showUserNotFoundModal={showUserNotFoundModal}
|
||||
setIsFetchingUUID={setIsFetchingUUID}
|
||||
showConversation={shouldNeverBeCalled}
|
||||
onSelectConversation={shouldNeverBeCalled}
|
||||
renderMessageSearchResult={() => {
|
||||
shouldNeverBeCalled();
|
||||
|
@ -215,8 +331,6 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
|||
rowCount={rowCount}
|
||||
shouldRecomputeRowHeights={false}
|
||||
showChooseGroupMembers={shouldNeverBeCalled}
|
||||
startNewConversationFromPhoneNumber={shouldNeverBeCalled}
|
||||
startNewConversationFromUsername={shouldNeverBeCalled}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Intl } from '../../../Intl';
|
|||
import { Emojify } from '../../Emojify';
|
||||
import { ContactName } from '../../ContactName';
|
||||
|
||||
type PropsType = {
|
||||
export type StatePropsType = {
|
||||
groupTitle: string;
|
||||
i18n: LocalizerType;
|
||||
makeRequest: () => void;
|
||||
|
@ -24,6 +24,8 @@ type PropsType = {
|
|||
selectedContacts: ReadonlyArray<ConversationType>;
|
||||
};
|
||||
|
||||
type PropsType = StatePropsType;
|
||||
|
||||
export const ConfirmAdditionsModal: FunctionComponent<PropsType> = ({
|
||||
groupTitle,
|
||||
i18n,
|
||||
|
|
|
@ -12,8 +12,11 @@ import { CapabilityError } from '../../../types/errors';
|
|||
import enMessages from '../../../../_locales/en/messages.json';
|
||||
import type { Props } from './ConversationDetails';
|
||||
import { ConversationDetails } from './ConversationDetails';
|
||||
import { ChooseGroupMembersModal } from './AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
import { ConfirmAdditionsModal } from './AddGroupMembersModal/ConfirmAdditionsModal';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { makeFakeLookupConversationWithoutUuid } from '../../../test-both/helpers/fakeLookupConversationWithoutUuid';
|
||||
import { ThemeType } from '../../../types/Util';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
@ -33,13 +36,14 @@ const conversation: ConversationType = getDefaultConversation({
|
|||
conversationColor: 'ultramarine' as const,
|
||||
});
|
||||
|
||||
const allCandidateContacts = times(10, () => getDefaultConversation());
|
||||
|
||||
const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
|
||||
addMembers: async () => {
|
||||
action('addMembers');
|
||||
},
|
||||
areWeASubscriber: false,
|
||||
canEditGroupInfo: false,
|
||||
candidateContactsToAdd: times(10, () => getDefaultConversation()),
|
||||
conversation: expireTimer
|
||||
? {
|
||||
...conversation,
|
||||
|
@ -97,6 +101,26 @@ const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
|
|||
),
|
||||
searchInConversation: action('searchInConversation'),
|
||||
theme: ThemeType.light,
|
||||
renderChooseGroupMembersModal: props => {
|
||||
return (
|
||||
<ChooseGroupMembersModal
|
||||
{...props}
|
||||
candidateContacts={allCandidateContacts}
|
||||
selectedContacts={[]}
|
||||
regionCode="US"
|
||||
getPreferredBadge={() => undefined}
|
||||
theme={ThemeType.light}
|
||||
i18n={i18n}
|
||||
lookupConversationWithoutUuid={makeFakeLookupConversationWithoutUuid()}
|
||||
showUserNotFoundModal={action('showUserNotFoundModal')}
|
||||
/>
|
||||
);
|
||||
},
|
||||
renderConfirmAdditionsModal: props => {
|
||||
return (
|
||||
<ConfirmAdditionsModal {...props} selectedContacts={[]} i18n={i18n} />
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
story.add('Basic', () => {
|
||||
|
|
|
@ -8,6 +8,8 @@ import { Button, ButtonIconType, ButtonVariant } from '../../Button';
|
|||
import { Tooltip } from '../../Tooltip';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import type { PreferredBadgeSelectorType } from '../../../state/selectors/badges';
|
||||
import type { SmartChooseGroupMembersModalPropsType } from '../../../state/smart/ChooseGroupMembersModal';
|
||||
import type { SmartConfirmAdditionsModalPropsType } from '../../../state/smart/ConfirmAdditionsModal';
|
||||
import { assert } from '../../../util/assert';
|
||||
import { getMutedUntilText } from '../../../util/getMutedUntilText';
|
||||
|
||||
|
@ -59,7 +61,6 @@ export type StateProps = {
|
|||
areWeASubscriber: boolean;
|
||||
badges?: ReadonlyArray<BadgeType>;
|
||||
canEditGroupInfo: boolean;
|
||||
candidateContactsToAdd: Array<ConversationType>;
|
||||
conversation?: ConversationType;
|
||||
hasGroupLink: boolean;
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
|
@ -97,6 +98,12 @@ export type StateProps = {
|
|||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
||||
onOutgoingAudioCallInConversation: () => unknown;
|
||||
onOutgoingVideoCallInConversation: () => unknown;
|
||||
renderChooseGroupMembersModal: (
|
||||
props: SmartChooseGroupMembersModalPropsType
|
||||
) => JSX.Element;
|
||||
renderConfirmAdditionsModal: (
|
||||
props: SmartConfirmAdditionsModalPropsType
|
||||
) => JSX.Element;
|
||||
};
|
||||
|
||||
type ActionProps = {
|
||||
|
@ -115,7 +122,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
areWeASubscriber,
|
||||
badges,
|
||||
canEditGroupInfo,
|
||||
candidateContactsToAdd,
|
||||
conversation,
|
||||
deleteAvatarFromDisk,
|
||||
hasGroupLink,
|
||||
|
@ -133,6 +139,8 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
onUnblock,
|
||||
pendingApprovalMemberships,
|
||||
pendingMemberships,
|
||||
renderChooseGroupMembersModal,
|
||||
renderConfirmAdditionsModal,
|
||||
replaceAvatar,
|
||||
saveAvatarToDisk,
|
||||
searchInConversation,
|
||||
|
@ -228,7 +236,8 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
case ModalState.AddingGroupMembers:
|
||||
modalNode = (
|
||||
<AddGroupMembersModal
|
||||
candidateContacts={candidateContactsToAdd}
|
||||
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
|
||||
renderConfirmAdditionsModal={renderConfirmAdditionsModal}
|
||||
clearRequestError={() => {
|
||||
setAddGroupMembersRequestState(oldRequestState => {
|
||||
assert(
|
||||
|
@ -241,7 +250,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
conversationIdsAlreadyInGroup={
|
||||
new Set(memberships.map(membership => membership.member.id))
|
||||
}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
groupTitle={conversation.title}
|
||||
i18n={i18n}
|
||||
makeRequest={async conversationIds => {
|
||||
|
@ -265,7 +273,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
setEditGroupAttributesRequestState(RequestState.Inactive);
|
||||
}}
|
||||
requestState={addGroupMembersRequestState}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue