diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts
index e9297b915..c73fba935 100644
--- a/app/actions/remote/channel.ts
+++ b/app/actions/remote/channel.ts
@@ -1309,7 +1309,7 @@ export const convertGroupMessageToPrivateChannel = async (serverUrl: string, cha
return {updatedChannel};
} catch (error) {
- logDebug('error on convertGroupMessageToPrivateChannel', getFullErrorMessage(error));
+ logError('error on convertGroupMessageToPrivateChannel', getFullErrorMessage(error));
forceLogoutIfNecessary(serverUrl, error);
return {error};
} finally {
diff --git a/app/components/button/index.tsx b/app/components/button/index.tsx
index 9aa6c60d4..eb8bde1c3 100644
--- a/app/components/button/index.tsx
+++ b/app/components/button/index.tsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useMemo} from 'react';
+import React, {useMemo, type ReactNode} from 'react';
import {type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle} from 'react-native';
import RNButton from 'react-native-button';
@@ -21,6 +21,7 @@ type Props = ConditionalProps & {
testID?: string;
onPress: () => void;
text: string;
+ iconComponent?: ReactNode;
}
const styles = StyleSheet.create({
@@ -41,6 +42,7 @@ const Button = ({
testID,
iconName,
iconSize,
+ iconComponent,
}: Props) => {
const bgStyle = useMemo(() => [
buttonBackgroundStyle(theme, size, emphasis, buttonType, buttonState),
@@ -61,6 +63,21 @@ const Button = ({
[iconSize],
);
+ let icon: ReactNode;
+
+ if (iconComponent) {
+ icon = iconComponent;
+ } else if (iconName) {
+ icon = (
+
+ );
+ }
+
return (
- {Boolean(iconName) &&
-
- }
+ {icon}
{
const {formatMessage} = useIntl();
- const goToConvertToPrivateChannel = preventDoubleTap(async () => {
+ const goToConvertToPrivateChannel = useCallback(preventDoubleTap(async () => {
await dismissBottomSheet();
const title = formatMessage({id: 'channel_info.convert_gm_to_channel.screen_title', defaultMessage: 'Convert to Private Channel'});
goToScreen(Screens.CONVERT_GM_TO_CHANNEL, title, {channelId});
- });
+ }), [channelId]);
return (
{
+ // Gotta make sure we use profiles that are in members.
+ // See comment in fetchChannelMemberships for more details.
+
+ const usersById: {[id: string]: UserProfile} = {};
+ users.forEach((profile) => {
+ if (profile.id !== currentUserId) {
+ usersById[profile.id] = profile;
+ }
+ });
+
+ const filteredUsers: UserProfile[] = [];
+ members.forEach((member) => {
+ if (usersById[member.user_id]) {
+ filteredUsers.push(usersById[member.user_id]);
+ }
+ });
+
+ return filteredUsers;
+};
+
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
return {
loadingContainer: {
@@ -74,48 +95,29 @@ const ConvertGMToChannel = ({
};
}, []);
- const matchUserProfiles = (users: UserProfile[], members: ChannelMembership[]) => {
- // Gotta make sure we use profiles that are in members.
- // See comment in fetchChannelMemberships for more details.
-
- const usersById: {[id: string]: UserProfile} = {};
- users.forEach((profile) => {
- if (profile.id !== currentUserId) {
- usersById[profile.id] = profile;
- }
- });
-
- const filteredUsers: UserProfile[] = [];
- members.forEach((member) => {
- if (usersById[member.user_id]) {
- filteredUsers.push(usersById[member.user_id]);
- }
- });
-
- return filteredUsers;
- };
-
useEffect(() => {
mounted.current = true;
- const options: GetUsersOptions = {sort: 'admin', active: true, per_page: PER_PAGE_DEFAULT};
- fetchChannelMemberships(serverUrl, channelId, options, true).then(({users, members}) => {
- if (!mounted.current) {
- return;
- }
-
- if (users.length) {
- setProfiles(matchUserProfiles(users, members));
- }
-
- setChannelMembersFetched(true);
- });
-
return () => {
mounted.current = false;
};
}, []);
+ useEffect(() => {
+ const options: GetUsersOptions = {sort: 'admin', active: true, per_page: PER_PAGE_DEFAULT};
+ fetchChannelMemberships(serverUrl, channelId, options, true).then(({users, members}) => {
+ if (!mounted.current || !currentUserId) {
+ return;
+ }
+
+ if (users.length) {
+ setProfiles(matchUserProfiles(users, members, currentUserId));
+ }
+
+ setChannelMembersFetched(true);
+ });
+ }, [serverUrl, channelId, currentUserId]);
+
const showLoader = !loadingAnimationTimeout || !commonTeamsFetched || !channelMembersFetched;
if (showLoader) {
diff --git a/app/screens/convert_gm_to_channel/convert_gm_to_channel_form/convert_gm_to_channel_form.tsx b/app/screens/convert_gm_to_channel/convert_gm_to_channel_form/convert_gm_to_channel_form.tsx
index ec1b1839a..44beb20b7 100644
--- a/app/screens/convert_gm_to_channel/convert_gm_to_channel_form/convert_gm_to_channel_form.tsx
+++ b/app/screens/convert_gm_to_channel/convert_gm_to_channel_form/convert_gm_to_channel_form.tsx
@@ -6,12 +6,14 @@ import {useIntl} from 'react-intl';
import {Text, View} from 'react-native';
import {convertGroupMessageToPrivateChannel, switchToChannelById} from '@actions/remote/channel';
-import {isErrorWithMessage} from '@app/utils/errors';
+import Loading from '@app/components/loading';
+import {logError} from '@app/utils/log';
import Button from '@components/button';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
+import {isErrorWithMessage} from '@utils/errors';
import {preventDoubleTap} from '@utils/tap';
-import {makeStyleSheetFromTheme} from '@utils/theme';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {displayUsername} from '@utils/user';
import {ChannelNameInput} from '../channel_name_input';
@@ -32,6 +34,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
errorMessage: {
color: theme.dndIndicator,
},
+ loadingContainerStyle: {
+ marginRight: 10,
+ padding: 0,
+ top: -2,
+ },
};
});
@@ -58,10 +65,11 @@ export const ConvertGMToChannelForm = ({
const [selectedTeam, setSelectedTeam] = useState();
const [newChannelName, setNewChannelName] = useState('');
const [errorMessage, setErrorMessage] = useState('');
+ const [conversionInProgress, setConversionInProgress] = useState(false); // LOL revert this default value back to false
const {formatMessage} = useIntl();
const userDisplayNames = useMemo(() => profiles.map((profile) => displayUsername(profile, locale, teammateNameDisplay)), [profiles]);
- const submitButtonEnabled = selectedTeam && newChannelName.trim();
+ const submitButtonEnabled = !conversionInProgress && selectedTeam && newChannelName.trim();
useEffect(() => {
if (commonTeams.length > 0) {
@@ -74,6 +82,8 @@ export const ConvertGMToChannelForm = ({
return;
}
+ setConversionInProgress(true);
+
const {updatedChannel, error} = await convertGroupMessageToPrivateChannel(serverUrl, channelId, selectedTeam.id, newChannelName);
if (error) {
if (isErrorWithMessage(error)) {
@@ -82,16 +92,20 @@ export const ConvertGMToChannelForm = ({
setErrorMessage(formatMessage({id: 'channel_info.convert_gm_to_channel.conversion_error', defaultMessage: 'Something went wrong. Failed to convert Group Message to Private Channel.'}));
}
+ setConversionInProgress(false);
return;
}
if (!updatedChannel) {
+ logError('No updated channel received from server when converting GM to private channel');
setErrorMessage(formatMessage({id: 'channel_info.convert_gm_to_channel.conversion_error', defaultMessage: 'Something went wrong. Failed to convert Group Message to Private Channel.'}));
+ setConversionInProgress(false);
return;
}
setErrorMessage('');
switchToChannelById(serverUrl, updatedChannel.id, selectedTeam.id);
+ setConversionInProgress(false);
}), [selectedTeam, newChannelName, submitButtonEnabled]);
if (commonTeams.length === 0) {
@@ -105,11 +119,17 @@ export const ConvertGMToChannelForm = ({
defaultMessage: 'Conversation history will be visible to any channel members',
});
- const confirmButtonText = formatMessage({
+ const textConvert = formatMessage({
id: 'channel_info.convert_gm_to_channel.button_text',
defaultMessage: 'Convert to Private Channel',
});
+ const textConverting = formatMessage({
+ id: 'channel_info.convert_gm_to_channel.button_text_converting',
+ defaultMessage: 'Converting...',
+ });
+
+ const confirmButtonText = conversionInProgress ? textConverting : textConvert;
const defaultUserDisplayNames = intl.formatMessage({id: 'channel_info.convert_gm_to_channel.warning.body.yourself', defaultMessage: 'yourself'});
const memberNames = profiles.length > 0 ? intl.formatList(userDisplayNames) : defaultUserDisplayNames;
const messageBoxBody = intl.formatMessage({
@@ -119,6 +139,13 @@ export const ConvertGMToChannelForm = ({
memberNames,
});
+ const buttonIcon = conversionInProgress ? (
+
+ ) : null;
+
return (
{
errorMessage &&
diff --git a/app/screens/convert_gm_to_channel/team_selector.tsx b/app/screens/convert_gm_to_channel/team_selector.tsx
index 425415bbf..a66f1b443 100644
--- a/app/screens/convert_gm_to_channel/team_selector.tsx
+++ b/app/screens/convert_gm_to_channel/team_selector.tsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useCallback, useEffect, useState} from 'react';
+import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {Platform} from 'react-native';
@@ -19,6 +19,9 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
borderBottomWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.08),
},
+ labelContainerStyle: {
+ flexShrink: 0,
+ },
};
});
@@ -33,15 +36,14 @@ export const TeamSelector = ({commonTeams, onSelectTeam, selectedTeamId}: Props)
const theme = useTheme();
const styles = getStyleFromTheme(theme);
- const [selectedTeam, setSelectedTeam] = useState();
-
const label = formatMessage({id: 'channel_into.convert_gm_to_channel.team_selector.label', defaultMessage: 'Team'});
const placeholder = formatMessage({id: 'channel_into.convert_gm_to_channel.team_selector.placeholder', defaultMessage: 'Select a Team'});
+ const selectedTeam = useMemo(() => commonTeams.find((t) => t.id === selectedTeamId), [commonTeams, selectedTeamId]);
+
const selectTeam = useCallback((teamId: string) => {
const team = commonTeams.find((t) => t.id === teamId);
if (team) {
- setSelectedTeam(team);
onSelectTeam(team);
}
}, []);
@@ -52,13 +54,6 @@ export const TeamSelector = ({commonTeams, onSelectTeam, selectedTeamId}: Props)
goToScreen(Screens.TEAM_SELECTOR_LIST, title, {teams: commonTeams, selectTeam, selectedTeamId});
}), [commonTeams, selectTeam, selectedTeamId]);
- useEffect(() => {
- if (selectedTeamId && !selectedTeam) {
- const team = commonTeams.find((t) => t.id === selectedTeamId);
- setSelectedTeam(team);
- }
- }, [selectedTeamId]);
-
return (
);
};
diff --git a/app/screens/convert_gm_to_channel/team_selector_list/index.tsx b/app/screens/convert_gm_to_channel/team_selector_list/index.tsx
index d406f90b1..65dfd7bb4 100644
--- a/app/screens/convert_gm_to_channel/team_selector_list/index.tsx
+++ b/app/screens/convert_gm_to_channel/team_selector_list/index.tsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {debounce} from 'lodash';
import React, {useCallback, useMemo, useState} from 'react';
import {StyleSheet, View} from 'react-native';
@@ -27,18 +28,16 @@ type Props = {
const TeamSelectorList = ({teams, selectTeam}: Props) => {
const theme = useTheme();
-
+ const [filteredTeams, setFilteredTeam] = useState(teams);
const color = useMemo(() => changeOpacity(theme.centerChannelColor, 0.72), [theme]);
- const [filteredTeams, setFilteredTeam] = useState(teams);
-
- const handleOnChangeSearchText = useCallback((searchTerm: string) => {
+ const handleOnChangeSearchText = useCallback(debounce((searchTerm: string) => {
if (searchTerm === '') {
setFilteredTeam(teams);
} else {
setFilteredTeam(teams.filter((team) => team.display_name.includes(searchTerm) || team.name.includes(searchTerm)));
}
- }, [teams]);
+ }, 200), [teams]);
const handleOnPress = useCallback(preventDoubleTap((teamId: string) => {
selectTeam(teamId);
diff --git a/app/screens/index.tsx b/app/screens/index.tsx
index c820b098e..5707b3b4f 100644
--- a/app/screens/index.tsx
+++ b/app/screens/index.tsx
@@ -90,6 +90,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.CODE:
screen = withServerDatabase(require('@screens/code').default);
break;
+ case Screens.CONVERT_GM_TO_CHANNEL:
+ screen = withServerDatabase(require('@screens/convert_gm_to_channel').default);
+ break;
case Screens.CREATE_OR_EDIT_CHANNEL:
screen = withServerDatabase(require('@screens/create_or_edit_channel').default);
break;
@@ -242,6 +245,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.TABLE:
screen = withServerDatabase(require('@screens/table').default);
break;
+ case Screens.TEAM_SELECTOR_LIST:
+ screen = withServerDatabase(require('@screens/convert_gm_to_channel/team_selector_list').default);
+ break;
case Screens.TERMS_OF_SERVICE:
screen = withServerDatabase(require('@screens/terms_of_service').default);
break;
@@ -262,12 +268,6 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.CALL:
screen = withServerDatabase(require('@calls/screens/call_screen').default);
break;
- case Screens.CONVERT_GM_TO_CHANNEL:
- screen = withServerDatabase(require('@screens/convert_gm_to_channel').default);
- break;
- case Screens.TEAM_SELECTOR_LIST:
- screen = withServerDatabase(require('@screens/convert_gm_to_channel/team_selector_list').default);
- break;
}
if (screen) {
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index f7c88f508..b2e4c053e 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -1,5 +1,4 @@
{
- "\"channel_modal.name\": \"Name\",": "",
"about.date": "Build Date:",
"about.enterpriseEditione1": "Enterprise Edition",
"about.enterpriseEditionLearn": "Learn more about Enterprise Edition at ",
@@ -122,6 +121,7 @@
"channel_info.convert_failed": "We were unable to convert {displayName} to a private channel.",
"channel_info.convert_gm_to_channel": "Convert to a Private Channel",
"channel_info.convert_gm_to_channel.button_text": "Convert to Private Channel",
+ "channel_info.convert_gm_to_channel.button_text_converting": "Converting...",
"channel_info.convert_gm_to_channel.conversion_error": "Something went wrong. Failed to convert Group Message to Private Channel.",
"channel_info.convert_gm_to_channel.loading.footer": "Fetching details...",
"channel_info.convert_gm_to_channel.screen_title": "Convert to Private Channel",