Adding guest accounts feature (#2990)
* MM-15059: Restict the permissions for guests (#2791) * MM-15057: Adding guest badge to identify guest users (#2774) * MM-15057: Adding guest badge to identify guest users * Adding Guest tags in the channel title * Adding i18n translations * Adding DM and GM guest warnings * Fixing check-style * Adding and fixing tests * Adding i18n text * Only showing the subtitle when there is enough space * Addressing new design changes * Fixing eslint * Addressing PR comments * Moving getChannelStats to the handleSelectChannel action * Adding the guest info in android landscape channel headers * simplified the guest warning text generation * Fixing i18n * Fixing leaving channel behavior for guests (#2989) * Fixing leaving channel behavior for guests * Fixing tests and adding a new one * fixing typo * Addressing PR comments * Addressing PR comments * Fixing tests
This commit is contained in:
parent
15989b79e6
commit
ee93e4fa59
31 changed files with 679 additions and 16 deletions
|
|
@ -12,6 +12,7 @@ import {
|
|||
markChannelAsRead,
|
||||
leaveChannel as serviceLeaveChannel, markChannelAsViewed,
|
||||
selectChannel,
|
||||
getChannelStats,
|
||||
} from 'mattermost-redux/actions/channels';
|
||||
import {
|
||||
getPosts,
|
||||
|
|
@ -380,6 +381,7 @@ export function handleSelectChannel(channelId, fromPushNotification = false) {
|
|||
|
||||
const actions = [
|
||||
selectChannel(channelId),
|
||||
getChannelStats(channelId),
|
||||
setChannelDisplayName(channel.display_name),
|
||||
{
|
||||
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class AtMentionItem extends PureComponent {
|
||||
|
|
@ -20,6 +21,7 @@ export default class AtMentionItem extends PureComponent {
|
|||
onPress: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
username: PropTypes.string,
|
||||
isGuest: PropTypes.bool,
|
||||
isBot: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -42,6 +44,7 @@ export default class AtMentionItem extends PureComponent {
|
|||
username,
|
||||
theme,
|
||||
isBot,
|
||||
isGuest,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
|
@ -66,6 +69,10 @@ export default class AtMentionItem extends PureComponent {
|
|||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{hasFullName && <Text style={style.rowUsername}>{' - '}</Text>}
|
||||
{hasFullName && <Text style={style.rowFullname}>{`${firstName} ${lastName}`}</Text>}
|
||||
</TouchableOpacity>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const user = getUser(state, ownProps.userId);
|
||||
|
||||
|
|
@ -17,6 +19,7 @@ function mapStateToProps(state, ownProps) {
|
|||
lastName: user.last_name,
|
||||
username: user.username,
|
||||
isBot: Boolean(user.is_bot),
|
||||
isGuest: isGuest(user),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ export default class ChannelMentionItem extends PureComponent {
|
|||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
@ -41,6 +43,7 @@ export default class ChannelMentionItem extends PureComponent {
|
|||
theme,
|
||||
type,
|
||||
isBot,
|
||||
isGuest,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
|
@ -60,6 +63,10 @@ export default class ChannelMentionItem extends PureComponent {
|
|||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ import {injectIntl, intlShape} from 'react-intl';
|
|||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
class ChannelIntro extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -91,12 +93,19 @@ class ChannelIntro extends PureComponent {
|
|||
>
|
||||
<View style={style.indicatorContainer}>
|
||||
<Text style={style.displayName}>
|
||||
{index === currentChannelMembers.length - 1 ? this.getDisplayName(member) : `${this.getDisplayName(member)}, `}
|
||||
{this.getDisplayName(member)}
|
||||
</Text>
|
||||
<BotTag
|
||||
show={Boolean(member.is_bot)}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest(member)}
|
||||
theme={theme}
|
||||
/>
|
||||
<Text style={style.displayName}>
|
||||
{index === currentChannelMembers.length - 1 ? '' : ', '}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -91,6 +91,37 @@ exports[`UserListRow should match snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -187,6 +218,37 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
|||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -285,6 +347,37 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
|
|
@ -302,3 +395,132 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
|||
</CustomListRow>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`UserListRow should match snapshot for guest user 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"marginHorizontal": 10,
|
||||
"overflow": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#3d3c40",
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
size={32}
|
||||
userId="21345"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
@user
|
||||
</Text>
|
||||
<BotTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={true}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</CustomListRow>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
|||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
import CustomListRow from 'app/components/custom_list/custom_list_row';
|
||||
|
||||
export default class UserListRow extends React.PureComponent {
|
||||
|
|
@ -91,6 +93,10 @@ export default class UserListRow extends React.PureComponent {
|
|||
show={Boolean(user.is_bot)}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest(user)}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{showTeammateDisplay &&
|
||||
|
|
|
|||
|
|
@ -58,6 +58,22 @@ describe('UserListRow', () => {
|
|||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for guest user', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
user: {
|
||||
...baseProps.user,
|
||||
roles: 'system_guest',
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<UserListRow {...newProps}/>,
|
||||
{context: {intl: {formatMessage}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for currentUser with (you) populated in list', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
|
|
|
|||
55
app/components/guest_tag.js
Normal file
55
app/components/guest_tag.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class GuestTag extends PureComponent {
|
||||
static defaultProps = {
|
||||
show: true,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
show: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
inTitle: PropTypes.bool,
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
const style = createStyleSheet(this.props.theme);
|
||||
|
||||
return (
|
||||
<FormattedText
|
||||
id='post_info.guest'
|
||||
defaultMessage='GUEST'
|
||||
style={[style.guest, this.props.inTitle ? style.guestTitle : null]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
guest: {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 2,
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
guestTitle: {
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.15),
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.6),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -14,6 +14,7 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
|||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {fromAutoResponder} from 'app/utils/general';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
import PostHeader from './post_header';
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ function makeMapStateToProps() {
|
|||
theme: getTheme(state),
|
||||
username: user.username,
|
||||
isBot: user.is_bot || false,
|
||||
isGuest: isGuest(user),
|
||||
userTimezone,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import FormattedTime from 'app/components/formatted_time';
|
|||
import FormattedDate from 'app/components/formatted_date';
|
||||
import ReplyIcon from 'app/components/reply_icon';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ export default class PostHeader extends PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
username: PropTypes.string,
|
||||
isBot: PropTypes.bool,
|
||||
isGuest: PropTypes.bool,
|
||||
userTimezone: PropTypes.string,
|
||||
enableTimezone: PropTypes.bool,
|
||||
};
|
||||
|
|
@ -64,6 +66,7 @@ export default class PostHeader extends PureComponent {
|
|||
fromAutoResponder,
|
||||
overrideUsername,
|
||||
isBot,
|
||||
isGuest,
|
||||
} = this.props;
|
||||
|
||||
if (fromWebHook) {
|
||||
|
|
@ -95,6 +98,19 @@ export default class PostHeader extends PureComponent {
|
|||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
} else if (isGuest) {
|
||||
return (
|
||||
<TouchableOpacity onPress={this.handleUsernamePress}>
|
||||
<View style={style.indicatorContainer}>
|
||||
<Text style={style.displayName}>
|
||||
{this.props.displayName}
|
||||
</Text>
|
||||
<GuestTag
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
} else if (fromAutoResponder) {
|
||||
let name = this.props.displayName;
|
||||
if (overrideUsername && enablePostUsernameOverride) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {getUserIdFromChannelName, isChannelMuted} from 'mattermost-redux/utils/c
|
|||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {getDraftForChannel} from 'app/selectors/views';
|
||||
import {isGuest as isGuestUser} from 'app/utils/users';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ function makeMapStateToProps() {
|
|||
|
||||
let displayName = channel.display_name;
|
||||
let isBot = false;
|
||||
let isGuest = false;
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
if (ownProps.isSearchResult) {
|
||||
|
|
@ -42,6 +44,7 @@ function makeMapStateToProps() {
|
|||
if (teammate && teammate.is_bot) {
|
||||
isBot = true;
|
||||
}
|
||||
isGuest = isGuestUser(teammate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +84,7 @@ function makeMapStateToProps() {
|
|||
theme: getTheme(state),
|
||||
unreadMsgs,
|
||||
isBot,
|
||||
isGuest,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ import {getTheme, getFavoritesPreferences, getSidebarPreferences} from 'mattermo
|
|||
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
|
||||
import {memoizeResult} from 'mattermost-redux/utils/helpers';
|
||||
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selectors/entities/general';
|
||||
import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
import Permissions from 'mattermost-redux/constants/permissions';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
|
|
@ -53,7 +55,16 @@ function mapStateToProps(state) {
|
|||
sidebarPrefs.favorite_at_top === 'true' && favoriteChannelIds.length,
|
||||
));
|
||||
|
||||
let canJoinPublicChannels = true;
|
||||
if (hasNewPermissions(state)) {
|
||||
canJoinPublicChannels = haveITeamPermission(state, {
|
||||
team: currentTeamId,
|
||||
permission: Permissions.JOIN_PUBLIC_CHANNELS,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
canJoinPublicChannels,
|
||||
canCreatePrivateChannels: showCreateOption(
|
||||
state,
|
||||
config,
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ let UnreadIndicator = null;
|
|||
|
||||
export default class List extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canJoinPublicChannels: PropTypes.bool.isRequired,
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
favoriteChannelIds: PropTypes.array.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
|
|
@ -47,6 +45,9 @@ export default class List extends PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
orderedChannelIds: PropTypes.array.isRequired,
|
||||
previewChannel: PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
showModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -96,7 +97,7 @@ export default class List extends PureComponent {
|
|||
}
|
||||
|
||||
getSectionConfigByType = (props, sectionType) => {
|
||||
const {canCreatePrivateChannels} = props;
|
||||
const {canCreatePrivateChannels, canJoinPublicChannels} = props;
|
||||
|
||||
switch (sectionType) {
|
||||
case SidebarSectionTypes.UNREADS:
|
||||
|
|
@ -111,7 +112,7 @@ export default class List extends PureComponent {
|
|||
};
|
||||
case SidebarSectionTypes.PUBLIC:
|
||||
return {
|
||||
action: this.goToMoreChannels,
|
||||
action: canJoinPublicChannels ? this.goToMoreChannels : null,
|
||||
id: t('sidebar.channels'),
|
||||
defaultMessage: 'PUBLIC CHANNELS',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export default class ChannelNavBar extends PureComponent {
|
|||
const padding = {paddingHorizontal: 0};
|
||||
|
||||
let height;
|
||||
let canHaveSubtitle = true;
|
||||
switch (Platform.OS) {
|
||||
case 'android':
|
||||
height = ANDROID_TOP_PORTRAIT;
|
||||
|
|
@ -75,10 +76,12 @@ export default class ChannelNavBar extends PureComponent {
|
|||
height -= 1;
|
||||
} else if (isLandscape) {
|
||||
height = IOS_TOP_LANDSCAPE;
|
||||
canHaveSubtitle = false;
|
||||
}
|
||||
|
||||
if (DeviceTypes.IS_IPHONE_X && isLandscape) {
|
||||
padding.paddingHorizontal = 10;
|
||||
canHaveSubtitle = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -94,7 +97,10 @@ export default class ChannelNavBar extends PureComponent {
|
|||
openDrawer={openChannelDrawer}
|
||||
visible={drawerButtonVisible}
|
||||
/>
|
||||
<ChannelTitle onPress={onPress}/>
|
||||
<ChannelTitle
|
||||
onPress={onPress}
|
||||
canHaveSubtitle={canHaveSubtitle}
|
||||
/>
|
||||
<ChannelSearchButton
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,15 +12,22 @@ import {
|
|||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class ChannelTitle extends PureComponent {
|
||||
static propTypes = {
|
||||
currentChannelName: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
channelType: PropTypes.string,
|
||||
isChannelMuted: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object,
|
||||
isArchived: PropTypes.bool,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
hasGuests: PropTypes.bool.isRequired,
|
||||
canHaveSubtitle: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -42,10 +49,49 @@ export default class ChannelTitle extends PureComponent {
|
|||
return content;
|
||||
}
|
||||
|
||||
renderHasGuestsText = (style) => {
|
||||
const {channelType, isGuest, hasGuests, canHaveSubtitle} = this.props;
|
||||
if (!canHaveSubtitle) {
|
||||
return null;
|
||||
}
|
||||
if (!isGuest && !hasGuests) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let messageId;
|
||||
let defaultMessage;
|
||||
if (channelType === General.DM_CHANNEL) {
|
||||
messageId = t('channel.isGuest');
|
||||
defaultMessage = 'This person is a guest';
|
||||
} else if (channelType === General.GM_CHANNEL) {
|
||||
messageId = t('channel.hasGuests');
|
||||
defaultMessage = 'This group message has guests';
|
||||
} else if (channelType === General.OPEN_CHANNEL || channelType === General.PRIVATE_CHANNEL) {
|
||||
messageId = t('channel.channelHasGuests');
|
||||
defaultMessage = 'This channel has guests';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View style={style.guestsWrapper}>
|
||||
<FormattedText
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
id={messageId}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.guestsText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {currentChannelName, displayName, isChannelMuted, onPress, theme} = this.props;
|
||||
const channelName = displayName || currentChannelName;
|
||||
|
||||
const style = getStyle(theme);
|
||||
|
||||
const channelName = displayName || currentChannelName;
|
||||
const hasGuestsText = this.renderHasGuestsText(style);
|
||||
let icon;
|
||||
if (channelName) {
|
||||
icon = (
|
||||
|
|
@ -85,6 +131,7 @@ export default class ChannelTitle extends PureComponent {
|
|||
{icon}
|
||||
{mutedIcon}
|
||||
</View>
|
||||
{hasGuestsText}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
|
@ -124,5 +171,17 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
|
|||
color: theme.sidebarHeaderTextColor,
|
||||
paddingRight: 7,
|
||||
},
|
||||
guestsWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
width: '90%',
|
||||
},
|
||||
guestsText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 14,
|
||||
opacity: 0.6,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,22 +3,38 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentChannel, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannel, getMyCurrentChannelMembership, getCurrentChannelStats} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getUserIdFromChannelName, isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
import ChannelTitle from './channel_title';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const myChannelMember = getMyCurrentChannelMembership(state);
|
||||
const stats = getCurrentChannelStats(state) || {member_count: 0, guest_count: 0};
|
||||
|
||||
let isTeammateGuest = false;
|
||||
if (currentChannel && currentChannel.type === General.DM_CHANNEL) {
|
||||
const teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
|
||||
const teammate = getUser(state, teammateId);
|
||||
isTeammateGuest = isGuest(teammate);
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannelName: currentChannel ? currentChannel.display_name : '',
|
||||
isArchived: currentChannel ? currentChannel.delete_at !== 0 : false,
|
||||
displayName: state.views.channel.displayName,
|
||||
channelType: currentChannel?.type,
|
||||
isChannelMuted: isChannelMuted(myChannelMember),
|
||||
theme: getTheme(state),
|
||||
isGuest: isTeammateGuest,
|
||||
hasGuests: stats.guest_count > 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,9 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannel: PropTypes.object.isRequired,
|
||||
currentChannelCreatorName: PropTypes.string,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
currentChannelGuestCount: PropTypes.number,
|
||||
currentUserId: PropTypes.string,
|
||||
currentUserIsGuest: PropTypes.bool,
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isChannelMuted: PropTypes.bool.isRequired,
|
||||
|
|
@ -65,6 +67,10 @@ export default class ChannelInfo extends PureComponent {
|
|||
isBot: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentChannelGuestCount: 0,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
|
@ -333,11 +339,12 @@ export default class ChannelInfo extends PureComponent {
|
|||
|
||||
renderLeaveOrDeleteChannelRow = () => {
|
||||
const channel = this.props.currentChannel;
|
||||
const isGuest = this.props.currentUserIsGuest;
|
||||
const isDefaultChannel = channel.name === General.DEFAULT_CHANNEL;
|
||||
const isDirectMessage = channel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = channel.type === General.GM_CHANNEL;
|
||||
|
||||
return !isDefaultChannel && !isDirectMessage && !isGroupMessage;
|
||||
return (!isDefaultChannel && !isDirectMessage && !isGroupMessage) || (isDefaultChannel && isGuest);
|
||||
};
|
||||
|
||||
renderCloseDirect = () => {
|
||||
|
|
@ -471,6 +478,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
currentChannelGuestCount,
|
||||
status,
|
||||
theme,
|
||||
isBot,
|
||||
|
|
@ -512,6 +520,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
type={currentChannel.type}
|
||||
isArchived={currentChannel.delete_at !== 0}
|
||||
isBot={isBot}
|
||||
hasGuests={currentChannelGuestCount > 0}
|
||||
isGroupConstrained={currentChannel.group_constrained}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import {
|
|||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import FormattedDate from 'app/components/formatted_date';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
|
@ -20,6 +22,7 @@ import mattermostManaged from 'app/mattermost_managed';
|
|||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
export default class ChannelInfoHeader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -35,6 +38,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
type: PropTypes.string.isRequired,
|
||||
isArchived: PropTypes.bool.isRequired,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
hasGuests: PropTypes.bool.isRequired,
|
||||
isGroupConstrained: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
@ -42,6 +46,36 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
renderHasGuestText = (style) => {
|
||||
const {type, hasGuests} = this.props;
|
||||
if (!hasGuests) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let messageId;
|
||||
let defaultMessage;
|
||||
|
||||
if (type === General.GM_CHANNEL) {
|
||||
messageId = t('channel.hasGuests');
|
||||
defaultMessage = 'This group message has guests';
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
messageId = t('channel.isGuest');
|
||||
defaultMessage = 'This person is a guest';
|
||||
} else {
|
||||
messageId = t('channel.channelHasGuests');
|
||||
defaultMessage = 'This channel has guests';
|
||||
}
|
||||
return (
|
||||
<View style={style.section}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
id={messageId}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
handleLongPress = (text, actionText) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
|
|
@ -128,6 +162,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
{this.renderHasGuestText(style)}
|
||||
{purpose.length > 0 &&
|
||||
<View style={style.section}>
|
||||
<TouchableHighlight
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {getCurrentUserId, getUser, getStatusForUserId, getCurrentUserRoles} from
|
|||
import {areChannelMentionsIgnored, getUserIdFromChannelName, isChannelMuted, showDeleteOption, showManagementOptions} from 'mattermost-redux/utils/channel_utils';
|
||||
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
import {
|
||||
popTopScreen,
|
||||
|
|
@ -56,6 +57,7 @@ function mapStateToProps(state) {
|
|||
const currentChannelCreatorName = currentChannelCreator && currentChannelCreator.username;
|
||||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
const currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
|
||||
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
|
||||
const currentChannelMember = getMyCurrentChannelMembership(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const favoriteChannels = getSortedFavoriteChannelIds(state);
|
||||
|
|
@ -67,6 +69,7 @@ function mapStateToProps(state) {
|
|||
canManageUsers = false;
|
||||
}
|
||||
const currentUser = getUser(state, currentUserId);
|
||||
const currentUserIsGuest = isGuest(currentUser);
|
||||
|
||||
let status;
|
||||
let isBot = false;
|
||||
|
|
@ -77,6 +80,9 @@ function mapStateToProps(state) {
|
|||
if (teammate && teammate.is_bot) {
|
||||
isBot = true;
|
||||
}
|
||||
if (isGuest(teammate)) {
|
||||
currentChannelGuestCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const isAdmin = checkIsAdmin(roles);
|
||||
|
|
@ -94,7 +100,9 @@ function mapStateToProps(state) {
|
|||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
currentChannelGuestCount,
|
||||
currentUserId,
|
||||
currentUserIsGuest,
|
||||
isChannelMuted: isChannelMuted(currentChannelMember),
|
||||
ignoreChannelMentions: areChannelMentionsIgnored(currentChannelMember && currentChannelMember.notify_props, currentUser.notify_props),
|
||||
isCurrent,
|
||||
|
|
|
|||
|
|
@ -143,3 +143,37 @@ exports[`SelectTeam should match snapshot for teams 1`] = `
|
|||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`SelectTeam should match snapshot when user is a guest 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"marginHorizontal": 16,
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedText
|
||||
defaultMessage="Your guest account has no teams or channels assigned. Please contact an administrator."
|
||||
id="mobile.select_team.guest_cant_join_team"
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -7,16 +7,21 @@ import {connect} from 'react-redux';
|
|||
import {getTeams, joinTeam} from 'mattermost-redux/actions/teams';
|
||||
import {logout} from 'mattermost-redux/actions/users';
|
||||
import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {resetToChannel, dismissModal} from 'app/actions/navigation';
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
import SelectTeam from './select_team.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentUser = getCurrentUser(state);
|
||||
const currentUserIsGuest = isGuest(currentUser);
|
||||
return {
|
||||
teamsRequest: state.requests.teams.getTeams,
|
||||
teams: getJoinableTeams(state),
|
||||
currentUserIsGuest,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export default class SelectTeam extends PureComponent {
|
|||
}).isRequired,
|
||||
componentId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
currentUserIsGuest: PropTypes.bool.isRequired,
|
||||
userWithoutTeams: PropTypes.bool,
|
||||
teams: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object,
|
||||
|
|
@ -241,6 +242,21 @@ export default class SelectTeam extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.currentUserIsGuest) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar/>
|
||||
<View style={styles.headingContainer}>
|
||||
<FormattedText
|
||||
id='mobile.select_team.guest_cant_join_team'
|
||||
defaultMessage='Your guest account has no teams or channels assigned. Please contact an administrator.'
|
||||
style={styles.heading}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar/>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ describe('SelectTeam', () => {
|
|||
const baseProps = {
|
||||
actions,
|
||||
currentChannelId: 'someId',
|
||||
currentUserIsGuest: false,
|
||||
currentUrl: 'test',
|
||||
userWithoutTeams: false,
|
||||
teams: [],
|
||||
|
|
@ -79,4 +80,25 @@ describe('SelectTeam', () => {
|
|||
wrapper.update();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when user is a guest', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
currentUserIsGuest: true,
|
||||
teams: [{
|
||||
id: 'kemjcpu9bi877yegqjs18ndp4r',
|
||||
invite_id: 'ojsnudhqzbfzpk6e4n6ip1hwae',
|
||||
name: 'test',
|
||||
}],
|
||||
teamsRequest: {
|
||||
status: RequestStatus.SUCCESS,
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<SelectTeam {...props}/>,
|
||||
);
|
||||
await getTeams();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,6 +82,37 @@ exports[`user_profile should match snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<GuestTag
|
||||
show={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import FormattedTime from 'app/components/formatted_time';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
import UserProfileRow from './user_profile_row';
|
||||
import Config from 'assets/config';
|
||||
|
|
@ -129,6 +131,10 @@ export default class UserProfile extends PureComponent {
|
|||
show={Boolean(user.is_bot)}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest(user)}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Preferences from 'mattermost-redux/constants/preferences';
|
|||
|
||||
import UserProfile from './user_profile.js';
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
|
||||
jest.mock('react-intl');
|
||||
jest.mock('app/utils/theme', () => {
|
||||
|
|
@ -88,6 +89,32 @@ describe('user_profile', () => {
|
|||
)).toEqual(true);
|
||||
});
|
||||
|
||||
test('should contain guest tag', async () => {
|
||||
const guestUser = {
|
||||
email: 'test@test.com',
|
||||
first_name: 'test',
|
||||
id: '4hzdnk6mg7gepe7yze6m3domnc',
|
||||
last_name: 'fake',
|
||||
nickname: 'nick',
|
||||
username: 'fred',
|
||||
roles: 'system_guest',
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<UserProfile
|
||||
{...baseProps}
|
||||
user={guestUser}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.containsMatchingElement(
|
||||
<GuestTag
|
||||
show={true}
|
||||
theme={baseProps.theme}
|
||||
/>
|
||||
)).toEqual(true);
|
||||
});
|
||||
|
||||
test('should push EditProfile', async () => {
|
||||
const wrapper = shallow(
|
||||
<UserProfile
|
||||
|
|
|
|||
23
app/utils/users.js
Normal file
23
app/utils/users.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
// @flow
|
||||
|
||||
export function isInRole(roles, inRole) {
|
||||
if (roles) {
|
||||
var parts = roles.split(' ');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (parts[i] === inRole) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isGuest(user) {
|
||||
if (user && user.roles && isInRole(user.roles, 'system_guest')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -35,6 +35,9 @@
|
|||
"channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
|
||||
"channel_notifications.ignoreChannelMentions.settings": "Ignore @channel, @here, @all",
|
||||
"channel_notifications.muteChannel.settings": "Mute channel",
|
||||
"channel.channelHasGuests": "This channel has guests",
|
||||
"channel.hasGuests": "This group message has guests",
|
||||
"channel.isGuest": "This person is a guest",
|
||||
"combined_system_message.added_to_channel.many_expanded": "{users} and {lastUser} were **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one": "{firstUser} **added to the channel** by {actor}.",
|
||||
"combined_system_message.added_to_channel.one_you": "You were **added to the channel** by {actor}.",
|
||||
|
|
@ -416,6 +419,7 @@
|
|||
"mobile.search.no_results": "No Results Found",
|
||||
"mobile.search.on_modifier_description": "to find posts on a specific date",
|
||||
"mobile.search.recent_title": "Recent Searches",
|
||||
"mobile.select_team.guest_cant_join_team": "Your guest account has no teams or channels assigned. Please contact an administrator.",
|
||||
"mobile.select_team.join_open": "Open teams you can join",
|
||||
"mobile.select_team.no_teams": "There are no available teams for you to join.",
|
||||
"mobile.server_upgrade.button": "OK",
|
||||
|
|
@ -483,6 +487,7 @@
|
|||
"post_info.bot": "BOT",
|
||||
"post_info.del": "Delete",
|
||||
"post_info.edit": "Edit",
|
||||
"post_info.guest": "GUEST",
|
||||
"post_info.message.show_less": "Show less",
|
||||
"post_info.message.show_more": "Show more",
|
||||
"post_info.system": "System",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -13150,8 +13150,8 @@
|
|||
"integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A=="
|
||||
},
|
||||
"mattermost-redux": {
|
||||
"version": "github:mattermost/mattermost-redux#3277c79c0312a6b4dd4c9421d09d6aaa1c18307a",
|
||||
"from": "github:mattermost/mattermost-redux#3277c79c0312a6b4dd4c9421d09d6aaa1c18307a",
|
||||
"version": "github:mattermost/mattermost-redux#3e283f1d52cd70a9323e314b9d95757b9e216aea",
|
||||
"from": "github:mattermost/mattermost-redux#3e283f1d52cd70a9323e314b9d95757b9e216aea",
|
||||
"requires": {
|
||||
"deep-equal": "1.0.1",
|
||||
"eslint-plugin-header": "3.0.0",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"intl": "1.2.5",
|
||||
"jail-monkey": "2.2.0",
|
||||
"jsc-android": "241213.2.0",
|
||||
"mattermost-redux": "github:mattermost/mattermost-redux#3277c79c0312a6b4dd4c9421d09d6aaa1c18307a",
|
||||
"mattermost-redux": "github:mattermost/mattermost-redux#3e283f1d52cd70a9323e314b9d95757b9e216aea",
|
||||
"mime-db": "1.40.0",
|
||||
"moment-timezone": "0.5.25",
|
||||
"prop-types": "15.7.2",
|
||||
|
|
@ -146,4 +146,4 @@
|
|||
"node_modules/(?!react-native|jail-monkey)"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue