From 29a66bbc19492da82253535fad922f2dbe184c70 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Thu, 19 Jan 2023 17:18:51 -0500 Subject: [PATCH] MM-42835_Invite People - add email+user invites --- app/actions/remote/team.ts | 16 +++------ app/screens/invite/invite.tsx | 21 +++++++++--- app/screens/invite/selection.tsx | 49 --------------------------- app/screens/invite/summary.tsx | 28 ++++++++------- app/screens/invite/summary_report.tsx | 33 +++++++++--------- app/screens/navigation.ts | 2 +- assets/base/i18n/en.json | 4 +-- 7 files changed, 57 insertions(+), 96 deletions(-) diff --git a/app/actions/remote/team.ts b/app/actions/remote/team.ts index 8adc65eaff..90d68c09e2 100644 --- a/app/actions/remote/team.ts +++ b/app/actions/remote/team.ts @@ -508,19 +508,13 @@ export async function getTeamMembersByIds(serverUrl: string, teamId: string, use if (!fetchOnly) { setTeamLoading(serverUrl, true); - const teamMemberships: TeamMembership[] = []; - const roles: Record = {}; + const roles = []; - for (const member of members) { - teamMemberships.push(member); - member.roles.split(' ').forEach((role) => { - if (!roles[role]) { - roles[role] = true; - } - }); + for (const {roles: memberRoles} of members) { + roles.push(...memberRoles.split(' ')); } - fetchRolesIfNeeded(serverUrl, Object.getOwnPropertyNames(roles)); + fetchRolesIfNeeded(serverUrl, Array.from(new Set(roles))); const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; @@ -529,7 +523,7 @@ export async function getTeamMembersByIds(serverUrl: string, teamId: string, use const models: Model[] = (await Promise.all([ operator.handleTeam({teams: [team], prepareRecordsOnly: true}), - operator.handleTeamMemberships({teamMemberships, prepareRecordsOnly: true}), + operator.handleTeamMemberships({teamMemberships: members, prepareRecordsOnly: true}), ])).flat(); await operator.batchRecords(models); diff --git a/app/screens/invite/invite.tsx b/app/screens/invite/invite.tsx index 6adbd54ff4..a1b6f57291 100644 --- a/app/screens/invite/invite.tsx +++ b/app/screens/invite/invite.tsx @@ -115,6 +115,7 @@ export default function Invite({ const modalPosition = useModalPosition(mainView); const searchTimeoutId = useRef(null); + const retryTimeoutId = useRef(null); const [term, setTerm] = useState(''); const [searchResults, setSearchResults] = useState([]); @@ -137,7 +138,7 @@ export default function Invite({ return; } - const {data} = await searchProfiles(serverUrl, searchTerm.toLowerCase(), {allow_inactive: true}); + const {data} = await searchProfiles(serverUrl, searchTerm.toLowerCase()); const results: SearchResult[] = data ?? []; if (!results.length && isEmail(searchTerm.trim())) { @@ -193,7 +194,7 @@ export default function Invite({ setSendError(''); setStage(Stage.LOADING); - setTimeout(() => { + retryTimeoutId.current = setTimeout(() => { handleSend(); }, TIMEOUT_MILLISECONDS); }; @@ -243,9 +244,9 @@ export default function Invite({ for (const userId of userIds) { if (isGuest((selectedIds[userId] as UserProfile).roles)) { - notSent.push({userId, reason: formatMessage({id: 'invite.members.user-is-guest', defaultMessage: 'Contact your admin to make this guest a full member'})}); + notSent.push({userId, reason: formatMessage({id: 'invite.members.user_is_guest', defaultMessage: 'Contact your admin to make this guest a full member'})}); } else if (currentMemberIds[userId]) { - notSent.push({userId, reason: formatMessage({id: 'invite.members.already-member', defaultMessage: 'This person is already a team member'})}); + notSent.push({userId, reason: formatMessage({id: 'invite.members.already_member', defaultMessage: 'This person is already a team member'})}); } else { usersToAdd.push(userId); } @@ -338,6 +339,18 @@ export default function Invite({ }); }, [componentId, locale, theme, stage]); + useEffect(() => { + return () => { + if (searchTimeoutId.current) { + clearTimeout(searchTimeoutId.current); + } + + if (retryTimeoutId.current) { + clearTimeout(retryTimeoutId.current); + } + }; + }, []); + const handleRemoveItem = useCallback((id: string) => { const newSelectedIds = Object.assign({}, selectedIds); diff --git a/app/screens/invite/selection.tsx b/app/screens/invite/selection.tsx index eb42a63a48..5be704b19d 100644 --- a/app/screens/invite/selection.tsx +++ b/app/screens/invite/selection.tsx @@ -23,7 +23,6 @@ import {useTheme} from '@context/theme'; import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete'; import {useIsTablet, useKeyboardHeight} from '@hooks/device'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; -import {typography} from '@utils/typography'; import {SearchResult} from './invite'; import SelectedEmail from './selected_email'; @@ -56,54 +55,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { display: 'flex', flex: 1, }, - teamContainer: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - width: '100%', - paddingVertical: 16, - paddingHorizontal: 20, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.04), - }, - iconContainer: { - width: 40, - height: 40, - }, - textContainer: { - display: 'flex', - flexDirection: 'column', - }, - teamText: { - color: theme.centerChannelColor, - marginLeft: 12, - ...typography('Body', 200, 'SemiBold'), - }, - serverText: { - color: changeOpacity(theme.centerChannelColor, 0.72), - marginLeft: 12, - ...typography('Body', 75, 'Regular'), - }, - shareLink: { - display: 'flex', - marginLeft: 'auto', - }, - shareLinkButton: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - height: 40, - paddingHorizontal: 20, - backgroundColor: changeOpacity(theme.buttonBg, 0.08), - borderRadius: 4, - }, - shareLinkText: { - color: theme.buttonBg, - ...typography('Body', 100, 'SemiBold'), - paddingLeft: 7, - }, - shareLinkIcon: { - color: theme.buttonBg, - }, searchList: { left: 20, right: 20, diff --git a/app/screens/invite/summary.tsx b/app/screens/invite/summary.tsx index 13c449ddb2..a1590e71e9 100644 --- a/app/screens/invite/summary.tsx +++ b/app/screens/invite/summary.tsx @@ -262,18 +262,22 @@ export default function Summary({ ) : ( <> - - + {notSent.length > 0 && ( + + )} + {sent.length > 0 && ( + + )} )} diff --git a/app/screens/invite/summary_report.tsx b/app/screens/invite/summary_report.tsx index ec7a781b13..588d50b072 100644 --- a/app/screens/invite/summary_report.tsx +++ b/app/screens/invite/summary_report.tsx @@ -14,9 +14,12 @@ import {typography} from '@utils/typography'; import {SearchResult, InviteResult} from './invite'; import TextItem, {TextItemType} from './text_item'; +const COLOR_SUCCESS = '#3db887'; +const COLOR_ERROR = '#d24b4e'; + const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { return { - summaryInvitationsContainer: { + container: { display: 'flex', flexDirection: 'column', borderWidth: 1, @@ -25,29 +28,29 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { marginBottom: 16, paddingVertical: 8, }, - summaryInvitationsTitle: { + title: { display: 'flex', flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, paddingVertical: 12, }, - summaryInvitationsTitleText: { + titleText: { marginLeft: 12, ...typography('Heading', 300, 'SemiBold'), color: theme.centerChannelColor, }, - summaryInvitationsItem: { + item: { display: 'flex', flexDirection: 'column', paddingVertical: 12, }, - summaryInvitationsUser: { + user: { paddingTop: 0, paddingBottom: 0, height: 'auto', }, - summaryInvitationsReason: { + reason: { paddingLeft: 56, paddingRight: 20, ...typography('Body', 75, 'Regular'), @@ -80,10 +83,6 @@ export default function SummaryReport({ const count = invites.length; - if (!count) { - return null; - } - const sent = type === SummaryReportType.SENT; const message = sent ? ( formatMessage( @@ -105,16 +104,16 @@ export default function SummaryReport({ return ( - + - + {message} @@ -124,7 +123,7 @@ export default function SummaryReport({ return ( {typeof item === 'string' ? ( )} - + {reason} diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 9229ddb599..e3698876dc 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -703,7 +703,7 @@ export function setButtons(componentId: string, buttons: NavButtons = {leftButto mergeNavigationOptions(componentId, options); } -export function showOverlay(name: string, passProps = {}, options = {}) { +export function showOverlay(name: string, passProps = {}, options: Options = {}) { if (!isScreenRegistered(name)) { return; } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index f6f7094b31..1e0b3859c0 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -327,8 +327,8 @@ "intro.welcome.public": "Add some more team members to the channel or start a conversation below.", "invite_people_to_team.message": "Here’s a link to collaborate and communicate with us on Mattermost.", "invite_people_to_team.title": "Join the {team} team", - "invite.members.already-member": "This person is already a team member", - "invite.members.user-is-guest": "Contact your admin to make this guest a full member", + "invite.members.already_member": "This person is already a team member", + "invite.members.user_is_guest": "Contact your admin to make this guest a full member", "invite.search.email_invite": "invite", "invite.search.no_results": "No one found matching", "invite.searchPlaceholder": "Type a name or email address…",