diff --git a/webapp/channels/src/actions/command.ts b/webapp/channels/src/actions/command.ts index be13db328d..524e261761 100644 --- a/webapp/channels/src/actions/command.ts +++ b/webapp/channels/src/actions/command.ts @@ -86,7 +86,10 @@ export function executeCommand(message: string, args: CommandArgs): ActionFuncAs dispatch(GlobalActions.sendEphemeralPost('/leave is not supported in reply threads. Use it in the center channel instead.', args.channel_id, args.root_id)); return {data: true}; } - const channel = getCurrentChannel(state) || {}; + const channel = getCurrentChannel(state); + if (!channel) { + return {data: false}; + } if (channel.type === Constants.PRIVATE_CHANNEL) { dispatch(openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}})); return {data: true}; diff --git a/webapp/channels/src/actions/post_actions.ts b/webapp/channels/src/actions/post_actions.ts index 168d28bf83..708dd35951 100644 --- a/webapp/channels/src/actions/post_actions.ts +++ b/webapp/channels/src/actions/post_actions.ts @@ -290,7 +290,7 @@ export function setEditingPost(postId = '', refocusId = '', title = '', isRHS = const license = state.entities.general.license; const userId = getCurrentUserId(state); const channel = getChannel(state, post.channel_id); - const teamId = channel.team_id || ''; + const teamId = channel?.team_id || ''; const canEditNow = canEditPost(state, config, license, teamId, post.channel_id, userId, post); diff --git a/webapp/channels/src/actions/views/channel.ts b/webapp/channels/src/actions/views/channel.ts index be97a233aa..7efec68449 100644 --- a/webapp/channels/src/actions/views/channel.ts +++ b/webapp/channels/src/actions/views/channel.ts @@ -64,14 +64,14 @@ import type {GlobalState} from 'types/store'; export function goToLastViewedChannel(): ActionFuncAsync { return async (dispatch, getState) => { const state = getState(); - const currentChannel = getCurrentChannel(state) || {}; + const currentChannel = getCurrentChannel(state); const channelsInTeam = getChannelsNameMapInCurrentTeam(state); const directChannel = getAllDirectChannelsNameMapInCurrentTeam(state); const channels = Object.assign({}, channelsInTeam, directChannel); let channelToSwitchTo = getChannelByName(channels, getLastViewedChannelName(state)); - if (currentChannel.id === channelToSwitchTo!.id) { + if (currentChannel?.id === channelToSwitchTo!.id) { channelToSwitchTo = getChannelByName(channels, getRedirectChannelNameForTeam(state, getCurrentTeamId(state))); } @@ -83,7 +83,10 @@ export function switchToChannelById(channelId: string): ActionFuncAsync { return async (dispatch, getState) => { const state = getState(); const channel = getChannel(state, channelId); - return dispatch(switchToChannel(channel)); + if (channel) { + return dispatch(switchToChannel(channel)); + } + return {data: true}; }; } @@ -119,6 +122,9 @@ export function switchToChannel(channel: Channel & {userId?: string}): ActionFun getHistory().push(`${teamUrl}/messages/@${channel.name}`); } else if (channel.type === Constants.GM_CHANNEL) { const gmChannel = getChannel(state, channel.id); + if (!gmChannel?.name) { + return {error: true}; + } getHistory().push(`${teamUrl}/channels/${gmChannel.name}`); } else if (channel.type === Constants.THREADS) { getHistory().push(`${teamUrl}/${channel.name}`); diff --git a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_details.tsx b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_details.tsx index 36ce42792b..b6946c5c4e 100644 --- a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_details.tsx +++ b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_details.tsx @@ -40,7 +40,7 @@ import SaveChangesPanel from '../../save_changes_panel'; export interface ChannelDetailsProps { channelID: string; - channel: Channel; + channel?: Channel; team?: Team; groups: Group[]; totalGroups: number; @@ -108,9 +108,9 @@ export default class ChannelDetails extends React.PureComponent 0, + isLocalArchived: props.channel?.delete_at !== 0, showArchiveConfirmModal: false, }; } componentDidUpdate(prevProps: ChannelDetailsProps) { const {channel, totalGroups, actions} = this.props; - if (channel.id !== prevProps.channel.id || totalGroups !== prevProps.totalGroups) { + if (channel?.id !== prevProps.channel?.id || totalGroups !== prevProps.totalGroups) { this.setState({ totalGroups, - isSynced: Boolean(channel.group_constrained), - isPublic: channel.type === Constants.OPEN_CHANNEL, - isDefault: channel.name === Constants.DEFAULT_CHANNEL, - isLocalArchived: channel.delete_at > 0, + isSynced: Boolean(channel?.group_constrained), + isPublic: channel?.type === Constants.OPEN_CHANNEL, + isDefault: channel?.name === Constants.DEFAULT_CHANNEL, + isLocalArchived: channel?.delete_at !== 0, }); } // If we don't have the team and channel on mount, we need to request the team after we load the channel - if (!prevProps.team?.id && !prevProps.channel.team_id && channel.team_id) { + if (!prevProps.team?.id && !prevProps.channel?.team_id && channel?.team_id) { actions.getTeam(channel.team_id). then(async (data: any) => { if (data.data && data.data.scheme_id) { @@ -167,7 +167,7 @@ export default class ChannelDetails extends React.PureComponent { if (data.data && data.data.scheme_id) { @@ -206,7 +206,7 @@ export default class ChannelDetails extends React.PureComponent { const {channel} = this.props; - const isOriginallyPublic = channel.type === Constants.OPEN_CHANNEL; + const isOriginallyPublic = channel?.type === Constants.OPEN_CHANNEL; this.setState( { saveNeeded: true, @@ -355,7 +355,7 @@ export default class ChannelDetails extends React.PureComponent { + const {groups: origGroups, channelID, actions, channel} = this.props; + + if (!channel) { + return; + } + this.setState({showConvertConfirmModal: false, showRemoveConfirmModal: false, showConvertAndRemoveConfirmModal: false, showArchiveConfirmModal: false, saving: true}); const {groups, isSynced, isPublic, isPrivacyChanging, channelPermissions, usersToAdd, usersToRemove, rolesToUpdate} = this.state; let serverError: JSX.Element | undefined; let saveNeeded = false; - const {groups: origGroups, channelID, actions, channel} = this.props; if (this.channelToBeArchived()) { const result = await actions.deleteChannel(channel.id); @@ -607,13 +612,13 @@ export default class ChannelDetails extends React.PureComponent { const {isLocalArchived} = this.state; - const isServerArchived = this.props.channel.delete_at !== 0; + const isServerArchived = this.props.channel?.delete_at !== 0; return isLocalArchived && !isServerArchived; }; private channelToBeRestored = (): boolean => { const {isLocalArchived} = this.state; - const isServerArchived = this.props.channel.delete_at !== 0; + const isServerArchived = this.props.channel?.delete_at !== 0; return !isLocalArchived && isServerArchived; }; @@ -681,7 +686,7 @@ export default class ChannelDetails extends React.PureComponent { + public render = () => { const { totalGroups, saving, @@ -703,6 +708,11 @@ export default class ChannelDetails extends React.PureComponent !groups.find((g: Group) => g.id === og.id); const removedGroups = this.props.groups.filter(missingGroup); const nonArchivedContent = ( @@ -722,7 +732,7 @@ export default class ChannelDetails extends React.PureComponent } diff --git a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/channel_members.tsx b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/channel_members.tsx index 389d659ad3..1316924a6c 100644 --- a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/channel_members.tsx +++ b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/channel_members.tsx @@ -23,7 +23,7 @@ import Constants, {ModalIdentifiers} from 'utils/constants'; type Props = { channelId: string; - channel: Channel; + channel?: Channel; filters: GetFilteredUsersStatsOpts; users: UserProfile[]; diff --git a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/index.ts b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/index.ts index 6b1068067b..0ea498e327 100644 --- a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/index.ts +++ b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/channel_members/index.ts @@ -54,7 +54,7 @@ function makeMapStateToProps() { const config = getConfig(state); const channelMembers = getChannelMembersInChannels(state)[channelId] || {}; - const channel = getChannel(state, channelId) || {channel_id: channelId}; + const channel = getChannel(state, channelId); const searchTerm = state.views.search.userGridSearch?.term || ''; const filters = getUserGridFilters(state); diff --git a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/index.ts b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/index.ts index 0c15318f80..76f9a89337 100644 --- a/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/index.ts +++ b/webapp/channels/src/components/admin_console/team_channel_settings/channel/details/index.ts @@ -62,8 +62,8 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) { const guestAccountsEnabled = config.EnableGuestAccounts === 'true'; const channelID = ownProps.match.params.channel_id; - const channel = getChannel(state, channelID) || {}; - const team = getTeam(state, channel.team_id); + const channel = getChannel(state, channelID); + const team = channel ? getTeam(state, channel.team_id) : undefined; const groups = getGroupsAssociatedToChannel(state, channelID); const totalGroups = groups.length; const allGroups = getAllGroups(state); diff --git a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx index b9291e1d1e..3dfac87435 100644 --- a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx +++ b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx @@ -84,7 +84,7 @@ export type Props = { currentChannelMembersCount: number; // Data used in multiple places of the component - currentChannel: Channel; + currentChannel?: Channel; //Data used for DM prewritten messages currentChannelTeammateUsername?: string; @@ -239,7 +239,7 @@ type State = { uploadsProgressPercent: {[clientID: string]: FilePreviewInfo}; renderScrollbar: boolean; scrollbarWidth: number; - currentChannel: Channel; + currentChannel?: Channel; errorClass: string | null; serverError: (ServerError & {submittedMessage?: string}) | null; postError?: React.ReactNode; @@ -271,7 +271,7 @@ class AdvancedCreatePost extends React.PureComponent { currentChannel: props.currentChannel, }; if ( - props.currentChannel.id !== state.currentChannel.id || + props.currentChannel?.id !== state.currentChannel?.id || (props.isRemoteDraft && props.draft.message !== state.message) ) { updatedState = { @@ -321,14 +321,14 @@ class AdvancedCreatePost extends React.PureComponent { componentDidUpdate(prevProps: Props, prevState: State) { const {currentChannel, actions} = this.props; - if (prevProps.currentChannel.id !== currentChannel.id) { + if (prevProps.currentChannel?.id !== currentChannel?.id) { this.lastChannelSwitchAt = Date.now(); this.focusTextbox(); this.saveDraftWithShow(prevProps); this.getChannelMemberCountsByGroup(); } - if (currentChannel.id !== prevProps.currentChannel.id) { + if (currentChannel?.id !== prevProps.currentChannel?.id) { actions.setShowPreview(false); } @@ -356,7 +356,7 @@ class AdvancedCreatePost extends React.PureComponent { getChannelMemberCountsByGroup = () => { const {useLDAPGroupMentions, useCustomGroupMentions, currentChannel, actions, draft} = this.props; - if ((useLDAPGroupMentions || useCustomGroupMentions) && currentChannel.id) { + if ((useLDAPGroupMentions || useCustomGroupMentions) && currentChannel?.id) { const mentions = mentionsMinusSpecialMentionsInText(draft.message); if (mentions.length === 1) { @@ -458,7 +458,11 @@ class AdvancedCreatePost extends React.PureComponent { }; doSubmit = async (e?: React.FormEvent) => { - const channelId = this.props.currentChannel.id; + const channelId = this.props.currentChannel?.id; + if (!channelId) { + return; + } + if (e) { e.preventDefault(); } @@ -628,6 +632,10 @@ class AdvancedCreatePost extends React.PureComponent { useCustomGroupMentions, } = this.props; + if (!updateChannel) { + return; + } + this.setShowPreview(false); this.isDraftSubmitting = true; @@ -671,7 +679,7 @@ class AdvancedCreatePost extends React.PureComponent { } } - const {data} = await this.props.actions.getChannelTimezones(this.props.currentChannel.id); + const {data} = await this.props.actions.getChannelTimezones(updateChannel.id); channelTimezoneCount = data ? data.length : 0; } @@ -748,6 +756,10 @@ class AdvancedCreatePost extends React.PureComponent { useCustomGroupMentions, } = this.props; + if (!currentChannel) { + return {data: false}; + } + let post = originalPost; post.channel_id = currentChannel.id; @@ -864,8 +876,10 @@ class AdvancedCreatePost extends React.PureComponent { }; emitTypingEvent = () => { - const channelId = this.props.currentChannel.id; - GlobalActions.emitLocalUserTypingEvent(channelId, ''); + const channelId = this.props.currentChannel?.id; + if (channelId) { + GlobalActions.emitLocalUserTypingEvent(channelId, ''); + } }; handleChange = (e: React.ChangeEvent) => { @@ -889,11 +903,15 @@ class AdvancedCreatePost extends React.PureComponent { this.handleDraftChange(draft); }; - handleDraftChange = (draft: PostDraft, channelId = this.props.currentChannel.id, instant = false) => { + handleDraftChange = (draft: PostDraft, channelId = this.props.currentChannel?.id, instant = false) => { if (this.saveDraftFrame) { clearTimeout(this.saveDraftFrame); } + if (!channelId) { + return; + } + if (instant) { this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, draft, channelId); } else { @@ -905,7 +923,10 @@ class AdvancedCreatePost extends React.PureComponent { this.draftsForChannel[channelId] = draft; }; - removeDraft = (channelId = this.props.currentChannel.id) => { + removeDraft = (channelId = this.props.currentChannel?.id) => { + if (!channelId) { + return; + } this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, null, channelId); this.draftsForChannel[channelId] = null; }; @@ -986,6 +1007,9 @@ class AdvancedCreatePost extends React.PureComponent { }; removePreview = (id: string) => { + if (!this.props.currentChannel) { + return; + } let modifiedDraft = {} as PostDraft; const draft = {...this.props.draft}; @@ -1264,6 +1288,9 @@ class AdvancedCreatePost extends React.PureComponent { }; handlePostPriorityApply = (settings?: PostPriorityMetadata) => { + if (!this.props.currentChannel) { + return; + } const updatedDraft = { ...this.props.draft, }; @@ -1309,7 +1336,7 @@ class AdvancedCreatePost extends React.PureComponent { return true; } - if (currentChannel.type === Constants.DM_CHANNEL) { + if (currentChannel?.type === Constants.DM_CHANNEL) { return true; } diff --git a/webapp/channels/src/components/advanced_create_post/index.ts b/webapp/channels/src/components/advanced_create_post/index.ts index 7a81c8f0e8..0644a71237 100644 --- a/webapp/channels/src/components/advanced_create_post/index.ts +++ b/webapp/channels/src/components/advanced_create_post/index.ts @@ -65,12 +65,12 @@ function makeMapStateToProps() { return (state: GlobalState) => { const config = getConfig(state); const license = getLicense(state); - const currentChannel = getCurrentChannel(state) || {}; - const currentChannelTeammateUsername = getUser(state, currentChannel.teammate_id || '')?.username; - const draft = getChannelDraft(state, currentChannel.id); - const isRemoteDraft = state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`] || false; + const currentChannel = getCurrentChannel(state); + const currentChannelTeammateUsername = currentChannel ? getUser(state, currentChannel.teammate_id || '')?.username : undefined; + const draft = getChannelDraft(state, currentChannel?.id || ''); + const isRemoteDraft = (currentChannel && state.views.drafts.remotes[`${StoragePrefixes.DRAFT}${currentChannel.id}`]) || false; const latestReplyablePostId = getLatestReplyablePostId(state); - const currentChannelMembersCount = getCurrentChannelStats(state) ? getCurrentChannelStats(state).member_count : 1; + const currentChannelMembersCount = getCurrentChannelStats(state)?.member_count ?? 1; const enableEmojiPicker = config.EnableEmojiPicker === 'true'; const enableGifPicker = config.EnableGifPicker === 'true'; const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true'; @@ -82,9 +82,11 @@ function makeMapStateToProps() { const isLDAPEnabled = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true'; const useCustomGroupMentions = isCustomGroupsEnabled(state) && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS); const useLDAPGroupMentions = isLDAPEnabled && haveICurrentChannelPermission(state, Permissions.USE_GROUP_MENTIONS); - const channelMemberCountsByGroup = selectChannelMemberCountsByGroup(state, currentChannel.id); + const channelMemberCountsByGroup = currentChannel ? selectChannelMemberCountsByGroup(state, currentChannel.id) : {}; const currentTeamId = getCurrentTeamId(state); - const groupsWithAllowReference = useLDAPGroupMentions || useCustomGroupMentions ? getAssociatedGroupsForReferenceByMention(state, currentTeamId, currentChannel.id) : null; + const groupsWithAllowReference = (currentChannel && (useLDAPGroupMentions || useCustomGroupMentions)) ? + getAssociatedGroupsForReferenceByMention(state, currentTeamId, currentChannel.id) : + null; const enableTutorial = config.EnableTutorial === 'true'; const tutorialStep = getInt(state, TutorialTourName.ONBOARDING_TUTORIAL_STEP, currentUserId, 0); diff --git a/webapp/channels/src/components/at_sum_members_mention/notification_from_members_modal.tsx b/webapp/channels/src/components/at_sum_members_mention/notification_from_members_modal.tsx index 70dbb84f5f..91e6513440 100644 --- a/webapp/channels/src/components/at_sum_members_mention/notification_from_members_modal.tsx +++ b/webapp/channels/src/components/at_sum_members_mention/notification_from_members_modal.tsx @@ -113,6 +113,10 @@ function NotificationFromMembersModal(props: Props) { return null; } + if (!channel) { + return null; + } + const modalTitle = formatMessage({id: 'postypes.custom_open_pricing_modal_post_renderer.membersThatRequested', defaultMessage: 'Members that requested '}); const modalHeaderText = ( diff --git a/webapp/channels/src/components/channel_header/channel_header.tsx b/webapp/channels/src/components/channel_header/channel_header.tsx index 8fa732c8fc..6fefe23a8d 100644 --- a/webapp/channels/src/components/channel_header/channel_header.tsx +++ b/webapp/channels/src/components/channel_header/channel_header.tsx @@ -48,7 +48,7 @@ const popoverMarkdownOptions = {singleline: false, mentionHighlight: false, atMe export type Props = { teamId: string; currentUser: UserProfile; - channel: Channel; + channel?: Channel; memberCount?: number; channelMember?: ChannelMembership; dmUser?: UserProfile; @@ -169,7 +169,7 @@ class ChannelHeader extends React.PureComponent { showChannelFiles = () => { if (this.props.rhsState === RHSStates.CHANNEL_FILES) { this.props.actions.closeRightHandSide(); - } else { + } else if (this.props.channel) { this.props.actions.showChannelFiles(this.props.channel.id); } }; @@ -180,6 +180,10 @@ class ChannelHeader extends React.PureComponent { } const {actions, channel} = this.props; + if (!channel) { + return; + } + const modalData = { modalId: ModalIdentifiers.EDIT_CHANNEL_HEADER, dialogType: EditChannelHeaderModal, @@ -212,7 +216,7 @@ class ChannelHeader extends React.PureComponent { toggleChannelMembersRHS = () => { if (this.props.rhsState === RHSStates.CHANNEL_MEMBERS) { this.props.actions.closeRightHandSide(); - } else { + } else if (this.props.channel) { this.props.actions.showChannelMembers(this.props.channel.id); } }; @@ -259,6 +263,10 @@ class ChannelHeader extends React.PureComponent { hasGuests, hideGuestTags, } = this.props; + if (!channel) { + return null; + } + const {formatMessage} = this.props.intl; const ariaLabelChannelHeader = this.props.intl.formatMessage({id: 'accessibility.sections.channelHeader', defaultMessage: 'channel header region'}); diff --git a/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx b/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx index df7c7f639c..eb4aec2ff8 100644 --- a/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx +++ b/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx @@ -19,17 +19,20 @@ const ChannelHeaderTitleFavorite = () => { const dispatch = useDispatch(); const isFavorite = useSelector(isCurrentChannelFavorite); const channel = useSelector(getCurrentChannel); - const channelIsArchived = channel.delete_at !== 0; + const channelIsArchived = (channel?.delete_at ?? 0) > 0; const toggleFavoriteRef = useRef(null); const toggleFavoriteCallback = useCallback((e: React.MouseEvent) => { e.stopPropagation(); + if (!channel) { + return; + } if (isFavorite) { dispatch(unfavoriteChannel(channel.id)); } else { dispatch(favoriteChannel(channel.id)); } - }, [isFavorite, channel.id]); + }, [isFavorite, channel?.id]); const removeTooltipLink = useCallback(() => { // Bootstrap adds the attr dynamically, removing it to prevent a11y readout diff --git a/webapp/channels/src/components/channel_header/index.ts b/webapp/channels/src/components/channel_header/index.ts index 35bceda339..dd37e150de 100644 --- a/webapp/channels/src/components/channel_header/index.ts +++ b/webapp/channels/src/components/channel_header/index.ts @@ -49,16 +49,13 @@ import type {GlobalState} from 'types/store'; import ChannelHeader from './channel_header'; -const EMPTY_CHANNEL = {}; -const EMPTY_CHANNEL_STATS = {member_count: 0, guest_count: 0, pinnedpost_count: 0, files_count: 0}; - function makeMapStateToProps() { const doGetProfilesInChannel = makeGetProfilesInChannel(); const getCustomStatus = makeGetCustomStatus(); let timestampUnits: string[] = []; return function mapStateToProps(state: GlobalState) { - const channel = getCurrentChannel(state) || EMPTY_CHANNEL; + const channel = getCurrentChannel(state); const user = getCurrentUser(state); const teams = getMyTeams(state); const hasMoreThanOneTeam = teams.length > 1; @@ -77,7 +74,7 @@ function makeMapStateToProps() { } else if (channel && channel.type === General.GM_CHANNEL) { gmMembers = doGetProfilesInChannel(state, channel.id); } - const stats = getCurrentChannelStats(state) || EMPTY_CHANNEL_STATS; + const stats = getCurrentChannelStats(state); let isLastActiveEnabled = false; if (dmUser) { @@ -89,7 +86,7 @@ function makeMapStateToProps() { teamId: getCurrentTeamId(state), channel, channelMember: getMyCurrentChannelMembership(state), - memberCount: stats.member_count, + memberCount: stats?.member_count || 0, currentUser: user, dmUser, gmMembers, @@ -98,8 +95,8 @@ function makeMapStateToProps() { isReadOnly: false, isMuted: isCurrentChannelMuted(state), isQuickSwitcherOpen: isModalOpen(state, ModalIdentifiers.QUICK_SWITCH), - hasGuests: stats.guest_count > 0, - pinnedPostsCount: stats.pinnedpost_count, + hasGuests: stats ? stats.guest_count > 0 : false, + pinnedPostsCount: stats?.pinnedpost_count || 0, hasMoreThanOneTeam, currentRelativeTeamUrl: getCurrentRelativeTeamUrl(state), announcementBarCount: getAnnouncementBarCount(state), diff --git a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx index cca1ad357f..a7f736785a 100644 --- a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx +++ b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx @@ -42,7 +42,7 @@ import MenuItemViewPinnedPosts from './menu_items/view_pinned_posts'; export type Props = { user: UserProfile; - channel: Channel; + channel?: Channel; isDefault: boolean; isFavorite: boolean; isReadonly: boolean; @@ -69,6 +69,10 @@ export default class ChannelHeaderDropdown extends React.PureComponent { isLicensedForLDAPGroups, } = this.props; + if (!channel) { + return null; + } + const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; const isGroupConstrained = channel.group_constrained === true; const channelMembersPermission = isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS; @@ -92,7 +96,7 @@ export default class ChannelHeaderDropdown extends React.PureComponent { key={item.id + '_pluginmenuitem'} onClick={() => { if (item.action) { - item.action(this.props.channel.id); + item.action(channel.id); } }} text={item.text} diff --git a/webapp/channels/src/components/channel_header_dropdown/index.ts b/webapp/channels/src/components/channel_header_dropdown/index.ts index a264f08778..59844aa654 100644 --- a/webapp/channels/src/components/channel_header_dropdown/index.ts +++ b/webapp/channels/src/components/channel_header_dropdown/index.ts @@ -37,7 +37,7 @@ const getTeammateId = createSelector( getCurrentChannel, getCurrentUserId, (channel, currentUserId) => { - if (channel.type !== Constants.DM_CHANNEL) { + if (channel?.type !== Constants.DM_CHANNEL) { return null; } diff --git a/webapp/channels/src/components/channel_header_dropdown/mobile_channel_header_dropdown.tsx b/webapp/channels/src/components/channel_header_dropdown/mobile_channel_header_dropdown.tsx index 5362e0474e..174b191f96 100644 --- a/webapp/channels/src/components/channel_header_dropdown/mobile_channel_header_dropdown.tsx +++ b/webapp/channels/src/components/channel_header_dropdown/mobile_channel_header_dropdown.tsx @@ -18,7 +18,7 @@ import MobileChannelHeaderDropdownAnimation from './mobile_channel_header_dropdo type Props = { user: UserProfile; - channel: Channel; + channel?: Channel; teammateId: string | null; teammateIsBot?: boolean; teammateStatus?: string; @@ -36,6 +36,10 @@ const MobileChannelHeaderDropdown = ({ const intl = useIntl(); const getChannelTitle = () => { + if (!channel) { + return ''; + } + if (channel.type === Constants.DM_CHANNEL) { if (user.id === teammateId) { return ( diff --git a/webapp/channels/src/components/channel_info_rhs/index.ts b/webapp/channels/src/components/channel_info_rhs/index.ts index 4c2e90a54e..bf1165f23f 100644 --- a/webapp/channels/src/components/channel_info_rhs/index.ts +++ b/webapp/channels/src/components/channel_info_rhs/index.ts @@ -46,9 +46,9 @@ function mapStateToProps(state: GlobalState) { const isInvitingPeople = isModalOpen(state, ModalIdentifiers.CHANNEL_INVITE) || isModalOpen(state, ModalIdentifiers.CREATE_DM_CHANNEL); const isMobile = getIsMobileView(state); - const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; - const canManageMembers = haveIChannelPermission(state, currentTeam?.id, channel.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS); - const canManageProperties = haveIChannelPermission(state, currentTeam?.id, channel.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES); + const isPrivate = channel?.type === Constants.PRIVATE_CHANNEL; + const canManageMembers = haveIChannelPermission(state, currentTeam?.id, channel?.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS); + const canManageProperties = haveIChannelPermission(state, currentTeam?.id, channel?.id, isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_PROPERTIES : Permissions.MANAGE_PUBLIC_CHANNEL_PROPERTIES); const channelMembers = getProfilesInCurrentChannel(state); @@ -67,7 +67,7 @@ function mapStateToProps(state: GlobalState) { channelMembers, } as Props; - if (channel.type === Constants.DM_CHANNEL) { + if (channel?.type === Constants.DM_CHANNEL) { const user = getUser(state, getUserIdFromChannelId(channel.name, currentUser.id)); props.dmUser = { user, diff --git a/webapp/channels/src/components/create_team/create_team.tsx b/webapp/channels/src/components/create_team/create_team.tsx index fab687b7c0..1d30831a76 100644 --- a/webapp/channels/src/components/create_team/create_team.tsx +++ b/webapp/channels/src/components/create_team/create_team.tsx @@ -26,7 +26,7 @@ export type Props = { /* * Object containing information on the current selected channel, used to define BackButton's url */ - currentChannel: Channel; + currentChannel?: Channel; /* * String containing the custom branding's text diff --git a/webapp/channels/src/components/dot_menu/index.ts b/webapp/channels/src/components/dot_menu/index.ts index c01b709d61..0fb723253a 100644 --- a/webapp/channels/src/components/dot_menu/index.ts +++ b/webapp/channels/src/components/dot_menu/index.ts @@ -67,7 +67,7 @@ function makeMapStateToProps() { const userId = getCurrentUserId(state); const channel = getChannel(state, post.channel_id); const currentTeam = getCurrentTeam(state); - const team = getTeam(state, channel.team_id); + const team = channel ? getTeam(state, channel.team_id) : undefined; const teamUrl = `${getSiteURL()}/${team?.name || currentTeam?.name}`; const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false); @@ -123,7 +123,7 @@ function makeMapStateToProps() { isMobileView: getIsMobileView(state), timezone: getCurrentTimezone(state), isMilitaryTime, - canMove: canWrangler(state, channel.type, threadReplyCount), + canMove: channel ? canWrangler(state, channel.type, threadReplyCount) : false, }; }; } diff --git a/webapp/channels/src/components/drafts/channel_draft/channel_draft.tsx b/webapp/channels/src/components/drafts/channel_draft/channel_draft.tsx index afc00ac836..bc7059dd39 100644 --- a/webapp/channels/src/components/drafts/channel_draft/channel_draft.tsx +++ b/webapp/channels/src/components/drafts/channel_draft/channel_draft.tsx @@ -27,7 +27,7 @@ import PanelBody from '../panel/panel_body'; import Header from '../panel/panel_header'; type Props = { - channel: Channel; + channel?: Channel; channelUrl: string; displayName: string; draftId: string; @@ -51,6 +51,7 @@ function ChannelDraft({ user, value, isRemote, + id: channelId, }: Props) { const dispatch = useDispatch(); const history = useHistory(); @@ -60,16 +61,20 @@ function ChannelDraft({ }, [history, channelUrl]); const handleOnDelete = useCallback((id: string) => { - dispatch(removeDraft(id, channel.id)); - }, [dispatch, channel.id]); + dispatch(removeDraft(id, channelId)); + }, [dispatch, channelId]); const doSubmit = useCallback((id: string, post: Post) => { dispatch(createPost(post, value.fileInfos)); - dispatch(removeDraft(id, channel.id)); + dispatch(removeDraft(id, channelId)); history.push(channelUrl); - }, [dispatch, history, value.fileInfos, channel.id, channelUrl]); + }, [dispatch, history, value.fileInfos, channelId, channelUrl]); const showPersistNotificationModal = useCallback((id: string, post: Post) => { + if (!channel) { + return; + } + dispatch(openModal({ modalId: ModalIdentifiers.PERSIST_NOTIFICATION_CONFIRM_MODAL, dialogType: PersistNotificationConfirmModal, @@ -80,7 +85,7 @@ function ChannelDraft({ onConfirm: () => doSubmit(id, post), }, })); - }, [channel.type, dispatch, doSubmit]); + }, [channel, dispatch, doSubmit]); const handleOnSend = useCallback(async (id: string) => { const post = {} as Post; @@ -135,7 +140,7 @@ function ChannelDraft({ remote={isRemote || false} /> { - if (thread) { - return makeOnSubmit(channel.id, thread.id, ''); + if (thread?.id) { + return makeOnSubmit(value.channelId, thread.id, ''); } return () => Promise.resolve({data: true}); - }, [channel.id, thread?.id]); + }, [value.channelId, thread?.id]); const handleOnDelete = useCallback((id: string) => { - dispatch(removeDraft(id, channel.id, rootId)); - }, [channel.id, rootId]); + dispatch(removeDraft(id, value.channelId, rootId)); + }, [value.channelId, rootId, dispatch]); const handleOnEdit = useCallback(() => { - dispatch(selectPost({id: rootId, channel_id: channel.id} as Post)); - }, [channel]); + dispatch(selectPost({id: rootId, channel_id: value.channelId} as Post)); + }, [value.channelId, dispatch, rootId]); const handleOnSend = useCallback(async (id: string) => { await dispatch(onSubmit(value)); handleOnDelete(id); handleOnEdit(); - }, [value, onSubmit]); + }, [value, onSubmit, dispatch, handleOnDelete, handleOnEdit]); - if (!thread) { + if (!thread || !channel) { return null; } diff --git a/webapp/channels/src/components/edit_post/index.ts b/webapp/channels/src/components/edit_post/index.ts index 5092128d5c..b473c5ba8b 100644 --- a/webapp/channels/src/components/edit_post/index.ts +++ b/webapp/channels/src/components/edit_post/index.ts @@ -55,7 +55,7 @@ function mapStateToProps(state: GlobalState) { teamId, channelId, maxPostSize: parseInt(config.MaxPostSize || '0', 10) || Constants.DEFAULT_CHARACTER_LIMIT, - readOnlyChannel: !isCurrentUserSystemAdmin(state) && channel.name === Constants.DEFAULT_CHANNEL, + readOnlyChannel: !isCurrentUserSystemAdmin(state) && channel?.name === Constants.DEFAULT_CHANNEL, useChannelMentions, isRHSOpened: getIsRhsOpen(state), isEditHistoryShowing: getRhsState(state) === RHSStates.EDIT_HISTORY, diff --git a/webapp/channels/src/components/file_search_results/index.tsx b/webapp/channels/src/components/file_search_results/index.tsx index d35395bd26..c17dddc5e4 100644 --- a/webapp/channels/src/components/file_search_results/index.tsx +++ b/webapp/channels/src/components/file_search_results/index.tsx @@ -29,7 +29,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) { return { channelDisplayName: '', - channelType: channel.type, + channelType: channel?.type, }; } diff --git a/webapp/channels/src/components/forward_post_modal/index.ts b/webapp/channels/src/components/forward_post_modal/index.ts deleted file mode 100644 index a254309398..0000000000 --- a/webapp/channels/src/components/forward_post_modal/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {connect} from 'react-redux'; -import type {ConnectedProps} from 'react-redux'; -import {bindActionCreators} from 'redux'; -import type {Dispatch} from 'redux'; - -import type {Channel} from '@mattermost/types/channels'; -import type {Post} from '@mattermost/types/posts'; - -import type {ActionResult} from 'mattermost-redux/types/actions'; - -import {openDirectChannelToUserId} from 'actions/channel_actions'; -import {joinChannelById, switchToChannel} from 'actions/views/channel'; -import {forwardPost} from 'actions/views/posts'; - -import ForwardPostModal from './forward_post_modal'; - -export type PropsFromRedux = ConnectedProps; - -export type ActionProps = { - - // join the selected channel when necessary - joinChannelById: (channelId: string) => Promise; - - // switch to the selected channel - switchToChannel: (channel: Channel) => Promise; - - // switch to the selected channel - openDirectChannelToUserId: (userId: string) => Promise>; - - // action called to forward the post with an optional comment - forwardPost: (post: Post, channelId: Channel, message?: string) => Promise; -} - -export type OwnProps = { - - // The function called immediately after the modal is hidden - onExited?: () => void; - - // the post that is going to be forwarded - post: Post; -}; - -function mapDispatchToProps(dispatch: Dispatch) { - return { - actions: bindActionCreators({ - joinChannelById, - switchToChannel, - forwardPost, - openDirectChannelToUserId, - }, dispatch), - }; -} -const connector = connect(null, mapDispatchToProps); - -export default connector(ForwardPostModal); diff --git a/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx b/webapp/channels/src/components/forward_post_modal/index.tsx similarity index 87% rename from webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx rename to webapp/channels/src/components/forward_post_modal/index.tsx index 130bc0cabe..7499c25510 100644 --- a/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx +++ b/webapp/channels/src/components/forward_post_modal/index.tsx @@ -4,11 +4,11 @@ import classNames from 'classnames'; import React, {useCallback, useRef, useState} from 'react'; import {FormattedList, FormattedMessage, useIntl} from 'react-intl'; -import {useSelector} from 'react-redux'; +import {useDispatch, useSelector} from 'react-redux'; import type {ValueType} from 'react-select'; import {GenericModal} from '@mattermost/components'; -import type {PostPreviewMetadata} from '@mattermost/types/posts'; +import type {Post, PostPreviewMetadata} from '@mattermost/types/posts'; import {General, Permissions} from 'mattermost-redux/constants'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; @@ -16,6 +16,9 @@ import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles' import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; import type {ActionResult} from 'mattermost-redux/types/actions'; +import {openDirectChannelToUserId} from 'actions/channel_actions'; +import {joinChannelById, switchToChannel} from 'actions/views/channel'; +import {forwardPost} from 'actions/views/posts'; import {getPermalinkURL} from 'selectors/urls'; import NotificationBox from 'components/notification_box'; @@ -30,16 +33,22 @@ import ForwardPostChannelSelect, {makeSelectedChannelOption} from './forward_pos import type {ChannelOption} from './forward_post_channel_select'; import ForwardPostCommentInput from './forward_post_comment_input'; -import type {ActionProps, OwnProps, PropsFromRedux} from './index'; - import './forward_post_modal.scss'; -export type Props = PropsFromRedux & OwnProps & { actions: ActionProps }; +type Props = { + + // The function called immediately after the modal is hidden + onExited?: () => void; + + // the post that is going to be forwarded + post: Post; +}; const noop = () => {}; -const ForwardPostModal = ({onExited, post, actions}: Props) => { +const ForwardPostModal = ({onExited, post}: Props) => { const {formatMessage} = useIntl(); + const dispatch = useDispatch(); const getChannel = makeGetChannel(); @@ -49,7 +58,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { const relativePermaLink = useSelector((state: GlobalState) => (currentTeam ? getPermalinkURL(state, currentTeam.id, post.id) : '')); const permaLink = `${getSiteURL()}${relativePermaLink}`; - const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL; + const isPrivateConversation = channel?.type !== Constants.OPEN_CHANNEL; const [comment, setComment] = useState(''); const [bodyHeight, setBodyHeight] = useState(0); @@ -77,7 +86,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { const canPostInSelectedChannel = useSelector( (state: GlobalState) => { - const channelId = isPrivateConversation ? channel.id : selectedChannelId; + const channelId = isPrivateConversation ? post.channel_id : selectedChannelId; const isDMChannel = selectedChannel?.details?.type === Constants.DM_CHANNEL; const teamId = isPrivateConversation ? currentTeam?.id : selectedChannel?.details?.team_id; @@ -123,15 +132,15 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { post, post_id: post.id, team_name: currentTeam?.name || '', - channel_display_name: channel.display_name, - channel_type: channel.type, - channel_id: channel.id, + channel_display_name: channel?.display_name || '', + channel_type: channel?.type || 'O', + channel_id: post.channel_id, }; let notification; if (isPrivateConversation) { let notificationText; - if (channel.type === General.PRIVATE_CHANNEL) { + if (channel?.type === General.PRIVATE_CHANNEL) { const channelName = `~${channel.display_name}`; notificationText = ( { /> ); } else { - const allParticipants = channel.display_name.split(', '); + const allParticipants = channel?.display_name.split(', ') || []; const participants = allParticipants.map((participant) => {participant}); notificationText = ( @@ -179,6 +188,10 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { return Promise.resolve(); } + if (!channel) { + return Promise.resolve(); + } + const channelToForward = isPrivateConversation ? makeSelectedChannelOption(channel) : selectedChannel; if (!channelToForward) { @@ -189,7 +202,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { return Promise.resolve().then(() => { if (type === Constants.DM_CHANNEL && userId) { - return actions.openDirectChannelToUserId(userId); + return dispatch(openDirectChannelToUserId(userId)); } return {data: false} as ActionResult; }).then(({data}) => { @@ -197,20 +210,20 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { channelToForward.details.id = data.id; } - return actions.forwardPost( + return dispatch(forwardPost( post, channelToForward.details, comment, - ); + )); }).then(() => { if (type === Constants.MENTION_MORE_CHANNELS && type === Constants.OPEN_CHANNEL) { - return actions.joinChannelById(channelToForward.details.id); + return dispatch(joinChannelById(channelToForward.details.id)); } return {data: false}; }).then(() => { // only switch channels when we are not in a private conversation if (!isPrivateConversation) { - return actions.switchToChannel(channelToForward.details); + return dispatch(switchToChannel(channelToForward.details)); } return {data: false}; }).then(() => { @@ -227,7 +240,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { defaultMessage: 'Originally posted in ~{channel}', }, { - channel: channel.display_name, + channel: channel?.display_name || '', }); return ( diff --git a/webapp/channels/src/components/invitation_modal/invitation_modal.tsx b/webapp/channels/src/components/invitation_modal/invitation_modal.tsx index 1277f428f0..160d90ac6d 100644 --- a/webapp/channels/src/components/invitation_modal/invitation_modal.tsx +++ b/webapp/channels/src/components/invitation_modal/invitation_modal.tsx @@ -58,7 +58,7 @@ export type Props = { ) => Promise>; }; currentTeam?: Team; - currentChannel: Channel; + currentChannel?: Channel; townSquareDisplayName: string; invitableChannels: Channel[]; emailInvitationsEnabled: boolean; diff --git a/webapp/channels/src/components/invitation_modal/invite_view.tsx b/webapp/channels/src/components/invitation_modal/invite_view.tsx index 4cbbfebfa9..95aaa5c517 100644 --- a/webapp/channels/src/components/invitation_modal/invite_view.tsx +++ b/webapp/channels/src/components/invitation_modal/invite_view.tsx @@ -56,7 +56,7 @@ export type Props = InviteState & { onChannelsInputChange: (channelsInputValue: string) => void; onClose: () => void; currentTeam: Team; - currentChannel: Channel; + currentChannel?: Channel; setCustomMessage: (message: string) => void; toggleCustomMessage: () => void; channelsLoader: (value: string, callback?: (channels: Channel[]) => void) => Promise; diff --git a/webapp/channels/src/components/logged_in/index.ts b/webapp/channels/src/components/logged_in/index.ts index 3cb7f3120d..82f07380b0 100644 --- a/webapp/channels/src/components/logged_in/index.ts +++ b/webapp/channels/src/components/logged_in/index.ts @@ -48,11 +48,14 @@ const getChannelURLAction = (channelId: string, teamId: string, url: string): Th const state = getState(); if (url && isPermalinkURL(url)) { - return getHistory().push(url); + getHistory().push(url); + return; } const channel = getChannel(state, channelId); - return getHistory().push(getChannelURL(state, channel, teamId)); + if (channel) { + getHistory().push(getChannelURL(state, channel, teamId)); + } }; function mapDispatchToProps(dispatch: Dispatch) { diff --git a/webapp/channels/src/components/member_list_channel/index.ts b/webapp/channels/src/components/member_list_channel/index.ts index 9d726bcb63..24f6707a6a 100644 --- a/webapp/channels/src/components/member_list_channel/index.ts +++ b/webapp/channels/src/components/member_list_channel/index.ts @@ -41,8 +41,14 @@ const getUsersAndActionsToDisplay = createSelector( channelMember: ChannelMembership; }; } = {}; - const usersToDisplay = []; + const usersToDisplay: UserProfile[] = []; + if (!channel) { + return { + usersToDisplay, + actionUserProps, + }; + } for (let i = 0; i < users.length; i++) { const user = users[i]; diff --git a/webapp/channels/src/components/move_thread_modal/move_thread_modal.tsx b/webapp/channels/src/components/move_thread_modal/move_thread_modal.tsx index 7429a24437..73d960979c 100644 --- a/webapp/channels/src/components/move_thread_modal/move_thread_modal.tsx +++ b/webapp/channels/src/components/move_thread_modal/move_thread_modal.tsx @@ -104,10 +104,10 @@ const MoveThreadModal = ({onExited, post, actions}: Props) => { post, post_id: post.id, team_name: currentTeam?.name || '', - channel_display_name: originalChannel.display_name, - channel_type: originalChannel.type, - channel_id: originalChannel.id, - }), [post, currentTeam?.name, originalChannel.display_name, originalChannel.type, originalChannel.id]); + channel_display_name: originalChannel?.display_name || '', + channel_type: originalChannel?.type || 'O', + channel_id: originalChannel?.id || '', + }), [post, currentTeam?.name, originalChannel?.display_name, originalChannel?.type, originalChannel?.id]); const notificationText = formatMessage({ id: 'move_thread_modal.notification.dm_or_gm', @@ -180,7 +180,7 @@ const MoveThreadModal = ({onExited, post, actions}: Props) => { defaultMessage: 'Originally posted in ~{channelName}', }, { - channelName: originalChannel.display_name, + channelName: originalChannel?.display_name || '', }); return ( diff --git a/webapp/channels/src/components/post_edit_history/index.ts b/webapp/channels/src/components/post_edit_history/index.ts index 6098062484..c860d67dbf 100644 --- a/webapp/channels/src/components/post_edit_history/index.ts +++ b/webapp/channels/src/components/post_edit_history/index.ts @@ -16,7 +16,8 @@ import PostEditHistory from './post_edit_history'; function mapStateToProps(state: GlobalState) { const selectedPostId = getSelectedPostId(state) || ''; const originalPost = getPost(state, selectedPostId); - const channelDisplayName = getCurrentChannel(state) ? getCurrentChannel(state).display_name : getChannel(state, originalPost.channel_id).display_name; + const channel = getCurrentChannel(state) ?? getChannel(state, originalPost.channel_id); + const channelDisplayName = channel?.display_name || ''; return { channelDisplayName, diff --git a/webapp/channels/src/components/post_markdown/post_markdown.tsx b/webapp/channels/src/components/post_markdown/post_markdown.tsx index 89a53b9c16..b667ac3ec1 100644 --- a/webapp/channels/src/components/post_markdown/post_markdown.tsx +++ b/webapp/channels/src/components/post_markdown/post_markdown.tsx @@ -65,13 +65,13 @@ export default class PostMarkdown extends React.PureComponent { let message = this.props.message; if (this.props.post) { - const renderedSystemMessage = renderSystemMessage(this.props.post, + const renderedSystemMessage = this.props.channel ? renderSystemMessage(this.props.post, this.props.currentTeam?.name ?? '', this.props.channel, this.props.hideGuestTags, this.props.isUserCanManageMembers, this.props.isMilitaryTime, - this.props.timezone); + this.props.timezone) : null; if (renderedSystemMessage) { return
{renderedSystemMessage}
; } diff --git a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx index 387a1a03f3..8f8c6bf62f 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx @@ -34,7 +34,7 @@ import PluggableIntroButtons from './pluggable_intro_buttons'; type Props = { currentUserId: string; - channel: Channel; + channel?: Channel; fullWidth: boolean; locale: string; channelProfiles: UserProfileType[]; @@ -59,6 +59,10 @@ type Props = { export default class ChannelIntroMessage extends React.PureComponent { toggleFavorite = () => { + if (!this.props.channel) { + return; + } + if (this.props.isFavorite) { this.props.actions.unfavoriteChannel(this.props.channel.id); } else { @@ -98,6 +102,10 @@ export default class ChannelIntroMessage extends React.PureComponent { centeredIntro = 'channel-intro--centered'; } + if (!channel) { + return null; + } + if (channel.type === Constants.DM_CHANNEL) { return createDMIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, teammate, teammateName); } else if (channel.type === Constants.GM_CHANNEL) { diff --git a/webapp/channels/src/components/post_view/channel_intro_message/index.ts b/webapp/channels/src/components/post_view/channel_intro_message/index.ts index d78186fa66..c0921d0e5c 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/index.ts +++ b/webapp/channels/src/components/post_view/channel_intro_message/index.ts @@ -28,11 +28,11 @@ function mapStateToProps(state: GlobalState) { const enableUserCreation = config.EnableUserCreation === 'true'; const isReadOnly = false; const team = getCurrentTeam(state); - const channel = getCurrentChannel(state) || {}; + const channel = getCurrentChannel(state); const channelMember = getMyCurrentChannelMembership(state); - const teammate = getDirectTeammate(state, channel.id); + const teammate = channel ? getDirectTeammate(state, channel.id) : undefined; const currentUser = getCurrentUser(state); - const creator = getUser(state, channel.creator_id); + const creator = channel ? getUser(state, channel.creator_id) : undefined; const usersLimit = 10; diff --git a/webapp/channels/src/components/post_view/post_message_preview/index.ts b/webapp/channels/src/components/post_view/post_message_preview/index.ts index 66c5150cbd..3b6048108e 100644 --- a/webapp/channels/src/components/post_view/post_message_preview/index.ts +++ b/webapp/channels/src/components/post_view/post_message_preview/index.ts @@ -49,7 +49,7 @@ function makeMapStateToProps() { } if (ownProps.metadata.channel_type === General.DM_CHANNEL) { - channelDisplayName = getChannel(state, {id: ownProps.metadata.channel_id}).display_name; + channelDisplayName = getChannel(state, {id: ownProps.metadata.channel_id})?.display_name || ''; } return { diff --git a/webapp/channels/src/components/rhs_thread/rhs_thread.tsx b/webapp/channels/src/components/rhs_thread/rhs_thread.tsx index 5c9da10141..4dad101b21 100644 --- a/webapp/channels/src/components/rhs_thread/rhs_thread.tsx +++ b/webapp/channels/src/components/rhs_thread/rhs_thread.tsx @@ -18,7 +18,7 @@ import type {FakePost, RhsState} from 'types/store/rhs'; type Props = { currentTeam?: Team; posts: Post[]; - channel: Channel | null; + channel?: Channel; selected: Post | FakePost; previousRhsState?: RhsState; } diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/index.ts b/webapp/channels/src/components/sidebar/sidebar_channel/index.ts index 9a0352d9d1..58e0018a85 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/index.ts +++ b/webapp/channels/src/components/sidebar/sidebar_channel/index.ts @@ -37,11 +37,11 @@ function makeMapStateToProps() { const currentChannelId = getCurrentChannelId(state); - const unreadCount = getUnreadCount(state, channel.id); + const unreadCount = getUnreadCount(state, channel?.id || ''); return { channel, - isCurrentChannel: channel.id === currentChannelId, + isCurrentChannel: channel?.id === currentChannelId, currentTeamName: currentTeam?.name, unreadMentions: unreadCount.mentions, isUnread: unreadCount.showUnread, diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel.tsx index 94a2f05f09..11736a6130 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel.tsx @@ -32,6 +32,9 @@ function SidebarChannel({ autoSortedCategoryIds, }: Props) { const [show, setShow] = useState(true); + if (!channel) { + return null; + } if (!currentTeamName) { return null; @@ -43,7 +46,7 @@ function SidebarChannel({ function setRef(refMethod?: (element: HTMLLIElement) => void) { return (ref: HTMLLIElement) => { - setChannelRef(channel.id, ref); + setChannelRef(channel?.id || '', ref); refMethod?.(ref); }; } diff --git a/webapp/channels/src/components/sidebar_right/sidebar_right.tsx b/webapp/channels/src/components/sidebar_right/sidebar_right.tsx index 3dddcff8e7..0f2a3297d0 100644 --- a/webapp/channels/src/components/sidebar_right/sidebar_right.tsx +++ b/webapp/channels/src/components/sidebar_right/sidebar_right.tsx @@ -30,7 +30,7 @@ import type {RhsState} from 'types/store/rhs'; export type Props = { isExpanded: boolean; isOpen: boolean; - channel: Channel; + channel?: Channel; team?: Team; teamId: Team['id']; productId: ProductIdentifier; @@ -44,7 +44,7 @@ export type Props = { isPluginView: boolean; isPostEditHistory: boolean; previousRhsState: RhsState; - rhsChannel: Channel; + rhsChannel?: Channel; selectedPostId: string; selectedPostCardId: string; actions: { @@ -150,11 +150,11 @@ export default class SidebarRight extends React.PureComponent { } const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props; - if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel.id !== prevProps.rhsChannel.id) { + if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) { actions.showPinnedPosts(rhsChannel.id); } - if (isChannelFiles && prevProps.isChannelFiles === isChannelFiles && rhsChannel.id !== prevProps.rhsChannel.id) { + if (isChannelFiles && prevProps.isChannelFiles === isChannelFiles && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) { actions.showChannelFiles(rhsChannel.id); } diff --git a/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser.ts b/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser.ts index ee350afcb1..ddff63c23e 100644 --- a/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser.ts +++ b/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser.ts @@ -1452,7 +1452,7 @@ export class AppCommandParser { }; // getChannel gets the channel in which the user is typing the command - private getChannel = (): Channel | null => { + private getChannel = (): Channel | undefined => { const state = this.store.getState(); return selectChannel(state, this.channelID); }; diff --git a/webapp/channels/src/components/threading/thread_viewer/index.ts b/webapp/channels/src/components/threading/thread_viewer/index.ts index ed95fad076..7233feb3ca 100644 --- a/webapp/channels/src/components/threading/thread_viewer/index.ts +++ b/webapp/channels/src/components/threading/thread_viewer/index.ts @@ -50,7 +50,7 @@ function makeMapStateToProps() { let postIds: string[] = []; let userThread: UserThread | null = null; - let channel: Channel | null = null; + let channel: Channel | undefined; if (selected) { postIds = getPostIdsForThread(state, selected.id); diff --git a/webapp/channels/src/components/threading/thread_viewer/thread_viewer.tsx b/webapp/channels/src/components/threading/thread_viewer/thread_viewer.tsx index 48912156e7..b57aed56d5 100644 --- a/webapp/channels/src/components/threading/thread_viewer/thread_viewer.tsx +++ b/webapp/channels/src/components/threading/thread_viewer/thread_viewer.tsx @@ -31,7 +31,7 @@ export type Props = Attrs & { isCollapsedThreadsEnabled: boolean; appsEnabled: boolean; userThread?: UserThread | null; - channel: Channel | null; + channel?: Channel; selected?: Post | FakePost; currentUserId: string; currentTeamId: string; diff --git a/webapp/channels/src/components/toast_wrapper/index.ts b/webapp/channels/src/components/toast_wrapper/index.ts index 6d29816ed6..a1a01b9664 100644 --- a/webapp/channels/src/components/toast_wrapper/index.ts +++ b/webapp/channels/src/components/toast_wrapper/index.ts @@ -36,7 +36,7 @@ export function makeGetRootPosts() { return Object.values(allPosts).filter((post) => { return ( post.root_id === '' && - post.channel_id === channel.id && + post.channel_id === channel?.id && post.state !== Posts.POST_DELETED ); }).reduce((map: Record, obj) => { diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts index edd8c90f88..e42076b6cb 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts @@ -1279,7 +1279,7 @@ export function favoriteChannel(channelId: string): ActionFuncAsync { return async (dispatch, getState) => { const state = getState(); const channel = getChannelSelector(state, channelId); - const category = getCategoryInTeamByType(state, channel.team_id || getCurrentTeamId(state), CategoryTypes.FAVORITES); + const category = getCategoryInTeamByType(state, channel?.team_id || getCurrentTeamId(state), CategoryTypes.FAVORITES); Client4.trackEvent('action', 'action_channels_favorite'); @@ -1296,6 +1296,10 @@ export function unfavoriteChannel(channelId: string): ActionFuncAsync { return async (dispatch, getState) => { const state = getState(); const channel = getChannelSelector(state, channelId); + if (!channel) { + return {data: false}; + } + const category = getCategoryInTeamByType( state, channel.team_id || getCurrentTeamId(state), diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/threads.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/threads.ts index 6a9cd7194a..9d32ba77bc 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/threads.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/threads.ts @@ -424,13 +424,16 @@ export function decrementThreadCounts(post: ExtendedPost): ActionFunc { const channel = getChannel(state, post.channel_id); const teamId = channel?.team_id || getCurrentTeamId(state); - dispatch({ - type: ThreadTypes.DECREMENT_THREAD_COUNTS, - teamId, - replies: thread.unread_replies, - mentions: thread.unread_mentions, - channelType: channel.type, - }); + if (channel) { + dispatch({ + type: ThreadTypes.DECREMENT_THREAD_COUNTS, + teamId, + replies: thread.unread_replies, + mentions: thread.unread_mentions, + channelType: channel.type, + }); + } + return {data: true}; }; } diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts index 4ed755c263..44f06e31a0 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts @@ -151,7 +151,7 @@ export function getChannelMember(state: GlobalState, channelId: string, userId: // - The display_name set to the other user(s) names, following the Teammate Name Display setting // - The teammate_id for DM channels // - The status of the other user in a DM channel -export function makeGetChannel(): (state: GlobalState, props: {id: string}) => Channel { +export function makeGetChannel(): (state: GlobalState, props: {id: string}) => Channel | undefined { return createSelector( 'makeGetChannel', getCurrentUserId, @@ -183,11 +183,11 @@ export function makeGetChannel(): (state: GlobalState, props: {id: string}) => C // getChannel returns a channel as it exists in the store without filling in any additional details such as the // display_name for DM/GM channels. -export function getChannel(state: GlobalState, id: string) { +export function getChannel(state: GlobalState, id: string): Channel | undefined { return getAllChannels(state)[id]; } -export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership { +export function getMyChannelMembership(state: GlobalState, channelId: string): ChannelMembership | undefined { return getMyChannelMemberships(state)[channelId]; } @@ -205,7 +205,7 @@ export function makeGetChannelsForIds(): (state: GlobalState, ids: string[]) => ); } -export const getCurrentChannel: (state: GlobalState) => Channel = createSelector( +export const getCurrentChannel: (state: GlobalState) => Channel | undefined = createSelector( 'getCurrentChannel', getAllChannels, getCurrentChannelId, @@ -255,7 +255,7 @@ export const getMyChannelMember: (state: GlobalState, channelId: string) => Chan }, ); -export const getCurrentChannelStats: (state: GlobalState) => ChannelStats = createSelector( +export const getCurrentChannelStats: (state: GlobalState) => ChannelStats | undefined = createSelector( 'getCurrentChannelStats', getAllChannelStats, getCurrentChannelId, @@ -296,7 +296,7 @@ export const isMutedChannel: (state: GlobalState, channelId: string) => boolean export const isCurrentChannelArchived: (state: GlobalState) => boolean = createSelector( 'isCurrentChannelArchived', getCurrentChannel, - (channel) => channel.delete_at !== 0, + (channel) => channel?.delete_at !== 0, ); export const isCurrentChannelDefault: (state: GlobalState) => boolean = createSelector( @@ -313,15 +313,15 @@ export function isChannelReadOnlyById(state: GlobalState, channelId: string): bo return isChannelReadOnly(state, getChannel(state, channelId)); } -export function isChannelReadOnly(state: GlobalState, channel: Channel): boolean { - return channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state); +export function isChannelReadOnly(state: GlobalState, channel?: Channel): boolean { + return Boolean(channel && channel.name === General.DEFAULT_CHANNEL && !isCurrentUserSystemAdmin(state)); } export function getChannelMessageCounts(state: GlobalState): RelationOneToOne { return state.entities.channels.messageCounts; } -export function getChannelMessageCount(state: GlobalState, channelId: string): ChannelMessageCount { +export function getChannelMessageCount(state: GlobalState, channelId: string): ChannelMessageCount | undefined { return getChannelMessageCounts(state)[channelId]; } @@ -334,8 +334,8 @@ export const countCurrentChannelUnreadMessages: (state: GlobalState) => number = getCurrentChannelMessageCount, getMyCurrentChannelMembership, isCollapsedThreadsEnabled, - (messageCount: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => { - if (!membership) { + (messageCount?: ChannelMessageCount, membership?: ChannelMembership, crtEnabled?: boolean): number => { + if (!membership || !messageCount) { return 0; } return crtEnabled ? messageCount.root - membership.msg_count_root : messageCount.total - membership.msg_count; @@ -348,7 +348,7 @@ export function makeGetChannelUnreadCount(): (state: GlobalState, channelId: str (state: GlobalState, channelId: string) => getChannelMessageCount(state, channelId), (state: GlobalState, channelId: string) => getMyChannelMembership(state, channelId), isCollapsedThreadsEnabled, - (messageCount: ChannelMessageCount, member: ChannelMembership, crtEnabled) => + (messageCount: ChannelMessageCount | undefined, member: ChannelMembership | undefined, crtEnabled) => calculateUnreadCount(messageCount, member, crtEnabled), ); } @@ -878,7 +878,7 @@ export const canManageChannelMembers: (state: GlobalState) => boolean = createSe Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS, ), ( - channel: Channel, + channel: Channel | undefined, managePrivateMembers: boolean, managePublicMembers: boolean, ): boolean => { diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/roles.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/roles.ts index 322cf2a541..160c56a820 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/roles.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/roles.ts @@ -208,7 +208,7 @@ export const haveIGroupPermission: (state: GlobalState, groupID: string, permiss }, ); -export function haveIChannelPermission(state: GlobalState, teamId: string | undefined, channelId: string, permission: string): boolean { +export function haveIChannelPermission(state: GlobalState, teamId: string | undefined, channelId: string | undefined, permission: string): boolean { if (getMySystemPermissions(state).has(permission)) { return true; } @@ -217,7 +217,11 @@ export function haveIChannelPermission(state: GlobalState, teamId: string | unde return true; } - return getMyPermissionsByChannel(state)[channelId]?.has(permission); + if (channelId) { + return getMyPermissionsByChannel(state)[channelId]?.has(permission); + } + + return false; } export function haveICurrentTeamPermission(state: GlobalState, permission: string): boolean { diff --git a/webapp/channels/src/packages/mattermost-redux/src/utils/channel_utils.ts b/webapp/channels/src/packages/mattermost-redux/src/utils/channel_utils.ts index 2e138b9e70..43c3952154 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/utils/channel_utils.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/utils/channel_utils.ts @@ -151,8 +151,8 @@ export function getGroupDisplayNameFromUserIds(userIds: Set, profiles: I return names.sort(sortUsernames).join(', '); } -export function isDefault(channel: Channel): boolean { - return channel.name === General.DEFAULT_CHANNEL; +export function isDefault(channel?: Channel): boolean { + return channel?.name === General.DEFAULT_CHANNEL; } export function completeDirectGroupInfo(usersState: UsersState, teammateNameDisplay: string, channel: Channel, omitCurrentUser = true) { diff --git a/webapp/channels/src/plugins/call_button/call_button.tsx b/webapp/channels/src/plugins/call_button/call_button.tsx index 9c36556b18..895f216ddd 100644 --- a/webapp/channels/src/plugins/call_button/call_button.tsx +++ b/webapp/channels/src/plugins/call_button/call_button.tsx @@ -20,7 +20,7 @@ import type {PluginComponent} from 'types/store/plugins'; import './call_button.scss'; type Props = { - currentChannel: Channel; + currentChannel?: Channel; channelMember?: ChannelMembership; pluginCallComponents: PluginComponent[]; sidebarOpen: boolean; diff --git a/webapp/channels/src/utils/channel_utils.tsx b/webapp/channels/src/utils/channel_utils.tsx index d0d260e71d..02e65936f5 100644 --- a/webapp/channels/src/utils/channel_utils.tsx +++ b/webapp/channels/src/utils/channel_utils.tsx @@ -61,7 +61,7 @@ export function findNextUnreadChannelId(curChannelId: string, allChannelIds: str return -1; } -export function isArchivedChannel(channel: Channel) { +export function isArchivedChannel(channel?: Channel) { return Boolean(channel && channel.delete_at !== 0); } diff --git a/webapp/channels/src/utils/post_utils.ts b/webapp/channels/src/utils/post_utils.ts index fbb3c675e0..cdddba9b00 100644 --- a/webapp/channels/src/utils/post_utils.ts +++ b/webapp/channels/src/utils/post_utils.ts @@ -95,7 +95,7 @@ export function getImageSrc(src: string, hasImageProxy = false): string { return src; } -export function canDeletePost(state: GlobalState, post: Post, channel: Channel): boolean { +export function canDeletePost(state: GlobalState, post: Post, channel?: Channel): boolean { if (post.type === Constants.PostTypes.FAKE_PARENT_DELETED) { return false; } @@ -645,6 +645,9 @@ export function areConsecutivePostsBySameUser(post: Post, previousPost: Post): b // Note: In the case of DM_CHANNEL, users must be fetched beforehand. export function getPostURL(state: GlobalState, post: Post): string { const channel = getChannel(state, post.channel_id); + if (!channel) { + return ''; + } const currentUserId = getCurrentUserId(state); const team = getTeam(state, channel.team_id || getCurrentTeamId(state)); if (!team) {