summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/action_creators/global_actions.jsx (renamed from web/react/dispatcher/event_helpers.jsx)30
-rw-r--r--web/react/components/activity_log_modal.jsx36
-rw-r--r--web/react/components/admin_console/admin_controller.jsx11
-rw-r--r--web/react/components/admin_console/admin_navbar_dropdown.jsx19
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx4
-rw-r--r--web/react/components/admin_console/admin_sidebar_header.jsx5
-rw-r--r--web/react/components/admin_console/user_item.jsx2
-rw-r--r--web/react/components/audit_table.jsx21
-rw-r--r--web/react/components/center_panel.jsx47
-rw-r--r--web/react/components/channel_header.jsx32
-rw-r--r--web/react/components/channel_invite_modal.jsx37
-rw-r--r--web/react/components/channel_loader.jsx204
-rw-r--r--web/react/components/channel_notifications_modal.jsx107
-rw-r--r--web/react/components/channel_view.jsx26
-rw-r--r--web/react/components/claim/claim_account.jsx87
-rw-r--r--web/react/components/claim/sso_to_email.jsx2
-rw-r--r--web/react/components/create_post.jsx6
-rw-r--r--web/react/components/delete_channel_modal.jsx4
-rw-r--r--web/react/components/do_verify_email.jsx82
-rw-r--r--web/react/components/docs.jsx41
-rw-r--r--web/react/components/edit_post_modal.jsx4
-rw-r--r--web/react/components/email_verify.jsx108
-rw-r--r--web/react/components/file_attachment.jsx4
-rw-r--r--web/react/components/find_team.jsx135
-rw-r--r--web/react/components/invite_member_modal.jsx4
-rw-r--r--web/react/components/logged_in.jsx224
-rw-r--r--web/react/components/login.jsx222
-rw-r--r--web/react/components/login_email.jsx11
-rw-r--r--web/react/components/navbar.jsx20
-rw-r--r--web/react/components/navbar_dropdown.jsx88
-rw-r--r--web/react/components/needs_team.jsx20
-rw-r--r--web/react/components/not_logged_in.jsx70
-rw-r--r--web/react/components/password_reset.jsx47
-rw-r--r--web/react/components/password_reset_form.jsx105
-rw-r--r--web/react/components/password_reset_send_link.jsx186
-rw-r--r--web/react/components/popover_list_members.jsx2
-rw-r--r--web/react/components/post.jsx19
-rw-r--r--web/react/components/post_body.jsx10
-rw-r--r--web/react/components/post_focus_view.jsx22
-rw-r--r--web/react/components/post_header.jsx17
-rw-r--r--web/react/components/post_info.jsx56
-rw-r--r--web/react/components/posts_view.jsx17
-rw-r--r--web/react/components/posts_view_container.jsx19
-rw-r--r--web/react/components/rhs_comment.jsx8
-rw-r--r--web/react/components/rhs_root_post.jsx8
-rw-r--r--web/react/components/root.jsx90
-rw-r--r--web/react/components/search_results_item.jsx9
-rw-r--r--web/react/components/should_verify_email.jsx111
-rw-r--r--web/react/components/sidebar.jsx30
-rw-r--r--web/react/components/sidebar_header.jsx14
-rw-r--r--web/react/components/sidebar_right.jsx9
-rw-r--r--web/react/components/sidebar_right_menu.jsx39
-rw-r--r--web/react/components/signup_team.jsx159
-rw-r--r--web/react/components/signup_team_complete.jsx121
-rw-r--r--web/react/components/signup_team_complete/components/signup_team_complete.jsx79
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx (renamed from web/react/components/team_signup_display_name_page.jsx)6
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_email_item.jsx (renamed from web/react/components/team_signup_email_item.jsx)2
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_finished.jsx15
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_password_page.jsx (renamed from web/react/components/team_signup_password_page.jsx)15
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx (renamed from web/react/components/team_signup_send_invites_page.jsx)2
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_url_page.jsx (renamed from web/react/components/team_signup_url_page.jsx)8
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_username_page.jsx (renamed from web/react/components/team_signup_username_page.jsx)8
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx (renamed from web/react/components/team_signup_welcome_page.jsx)12
-rw-r--r--web/react/components/signup_team_confirm.jsx47
-rw-r--r--web/react/components/signup_user_complete.jsx331
-rw-r--r--web/react/components/suggestion/at_mention_provider.jsx2
-rw-r--r--web/react/components/suggestion/suggestion_box.jsx12
-rw-r--r--web/react/components/suggestion/suggestion_list.jsx4
-rw-r--r--web/react/components/team_members_modal.jsx32
-rw-r--r--web/react/components/team_settings.jsx3
-rw-r--r--web/react/components/team_signup_with_email.jsx7
-rw-r--r--web/react/components/user_list_row.jsx2
-rw-r--r--web/react/components/user_profile.jsx29
-rw-r--r--web/react/components/user_settings/manage_languages.jsx3
-rw-r--r--web/react/components/user_settings/user_settings_developer.jsx4
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx15
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx16
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx29
-rw-r--r--web/react/package.json2
-rw-r--r--web/react/pages/admin_console.jsx71
-rw-r--r--web/react/pages/channel.jsx97
-rw-r--r--web/react/pages/claim_account.jsx68
-rw-r--r--web/react/pages/docs.jsx64
-rw-r--r--web/react/pages/find_team.jsx62
-rw-r--r--web/react/pages/home.jsx16
-rw-r--r--web/react/pages/login.jsx66
-rw-r--r--web/react/pages/password_reset.jsx68
-rw-r--r--web/react/pages/root.jsx290
-rw-r--r--web/react/pages/signup_team.jsx76
-rw-r--r--web/react/pages/signup_team_complete.jsx66
-rw-r--r--web/react/pages/signup_team_confirm.jsx64
-rw-r--r--web/react/pages/signup_user_complete.jsx69
-rw-r--r--web/react/pages/verify.jsx67
-rw-r--r--web/react/stores/admin_store.jsx10
-rw-r--r--web/react/stores/analytics_store.jsx4
-rw-r--r--web/react/stores/browser_store.jsx6
-rw-r--r--web/react/stores/channel_store.jsx4
-rw-r--r--web/react/stores/file_store.jsx7
-rw-r--r--web/react/stores/localization_store.jsx60
-rw-r--r--web/react/stores/modal_store.jsx4
-rw-r--r--web/react/stores/post_store.jsx4
-rw-r--r--web/react/stores/search_store.jsx4
-rw-r--r--web/react/stores/socket_store.jsx14
-rw-r--r--web/react/stores/suggestion_store.jsx7
-rw-r--r--web/react/stores/team_store.jsx42
-rw-r--r--web/react/stores/user_store.jsx66
-rw-r--r--web/react/utils/async_client.jsx55
-rw-r--r--web/react/utils/channel_intro_messages.jsx49
-rw-r--r--web/react/utils/client.jsx179
-rw-r--r--web/react/utils/constants.jsx5
-rw-r--r--web/react/utils/utils.jsx33
111 files changed, 2494 insertions, 2699 deletions
diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/action_creators/global_actions.jsx
index 367347d4b..4375d6c87 100644
--- a/web/react/dispatcher/event_helpers.jsx
+++ b/web/react/action_creators/global_actions.jsx
@@ -220,3 +220,33 @@ export function sendEphemeralPost(message, channelId) {
emitPostRecievedEvent(post);
}
+
+export function loadTeamRequiredPage() {
+ AsyncClient.getAllTeams();
+}
+
+export function newLocalizationSelected(locale) {
+ Client.getTranslations(
+ locale,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_LOCALE,
+ locale,
+ translations: data
+ });
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getTranslations');
+ }
+ );
+}
+
+export function viewLoggedIn() {
+ AsyncClient.getChannels();
+ AsyncClient.getChannelExtraInfo();
+ AsyncClient.getMyTeam();
+ AsyncClient.getMe();
+
+ // Clear pending posts (shouldn't have pending posts if we are loading)
+ PostStore.clearPendingPosts();
+}
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 95b4caa12..db366f8ed 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -8,7 +8,7 @@ const Modal = ReactBootstrap.Modal;
import LoadingScreen from './loading_screen.jsx';
import * as Utils from '../utils/utils.jsx';
-import {FormattedMessage} from 'mm-intl';
+import {FormattedMessage, FormattedTime, FormattedDate} from 'mm-intl';
export default class ActivityLogModal extends React.Component {
constructor(props) {
@@ -144,8 +144,21 @@ export default class ActivityLogModal extends React.Component {
id='activity_log.firstTime'
defaultMessage='First time active: {date}, {time}'
values={{
- date: firstAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={firstAccessTime}
+ day='2-digit'
+ month='long'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={firstAccessTime}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
}}
/>
</div>
@@ -206,8 +219,21 @@ export default class ActivityLogModal extends React.Component {
id='activity_log.lastActivity'
defaultMessage='Last activity: {date}, {time}'
values={{
- date: lastAccessTime.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: lastAccessTime.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={lastAccessTime}
+ day='2-digit'
+ month='long'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={lastAccessTime}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
}}
/>
</div>
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index 32ed70a99..4c4f21f08 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -6,7 +6,6 @@ import AdminStore from '../../stores/admin_store.jsx';
import TeamStore from '../../stores/team_store.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import LoadingScreen from '../loading_screen.jsx';
-import * as Utils from '../../utils/utils.jsx';
import EmailSettingsTab from './email_settings.jsx';
import LogSettingsTab from './log_settings.jsx';
@@ -50,11 +49,6 @@ export default class AdminController extends React.Component {
selected: props.tab || 'system_analytics',
selectedTeam: props.teamId || null
};
-
- if (!props.tab) {
- var tokenIndex = Utils.getUrlParameter('session_token_index');
- history.replaceState(null, null, `/admin_console/${this.state.selected}?session_token_index=${tokenIndex}`);
- }
}
componentDidMount() {
@@ -63,6 +57,9 @@ export default class AdminController extends React.Component {
AdminStore.addAllTeamsChangeListener(this.onAllTeamsListenerChange);
AsyncClient.getAllTeams();
+
+ $('[data-toggle="tooltip"]').tooltip();
+ $('[data-toggle="popover"]').popover();
}
componentWillUnmount() {
@@ -175,7 +172,7 @@ export default class AdminController extends React.Component {
}
return (
- <div>
+ <div id='admin_controller'>
<div
className='sidebar--menu'
id='sidebar-menu'
diff --git a/web/react/components/admin_console/admin_navbar_dropdown.jsx b/web/react/components/admin_console/admin_navbar_dropdown.jsx
index dc0b3c4cb..ae95f5a3a 100644
--- a/web/react/components/admin_console/admin_navbar_dropdown.jsx
+++ b/web/react/components/admin_console/admin_navbar_dropdown.jsx
@@ -2,13 +2,14 @@
// See License.txt for license information.
import * as Utils from '../../utils/utils.jsx';
-import * as Client from '../../utils/client.jsx';
import TeamStore from '../../stores/team_store.jsx';
import Constants from '../../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
+import {Link} from 'react-router';
+
function getStateFromStores() {
return {currentTeam: TeamStore.getCurrent()};
}
@@ -18,16 +19,9 @@ export default class AdminNavbarDropdown extends React.Component {
super(props);
this.blockToggle = false;
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
-
this.state = getStateFromStores();
}
- handleLogoutClick(e) {
- e.preventDefault();
- Client.logout();
- }
-
componentDidMount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
this.blockToggle = true;
@@ -78,15 +72,12 @@ export default class AdminNavbarDropdown extends React.Component {
</a>
</li>
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
<FormattedMessage
id='admin.nav.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
<li className='divider'></li>
<li>
@@ -116,4 +107,4 @@ export default class AdminNavbarDropdown extends React.Component {
</ul>
);
}
-} \ No newline at end of file
+}
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 6621e5743..c2f31f569 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -3,7 +3,6 @@
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import SelectTeamModal from './select_team_modal.jsx';
-import * as Utils from '../../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -30,8 +29,6 @@ export default class AdminSidebar extends React.Component {
handleClick(name, teamId, e) {
e.preventDefault();
this.props.selectTab(name, teamId);
- var tokenIndex = Utils.getUrlParameter('session_token_index');
- history.pushState({name, teamId}, null, `/admin_console/${name}/${teamId || ''}?session_token_index=${tokenIndex}`);
}
isSelected(name, teamId) {
@@ -73,7 +70,6 @@ export default class AdminSidebar extends React.Component {
}
teamSelectedModal(teamId) {
- this.props.selectedTeams[teamId] = 'true';
this.setState({showSelectModal: false});
this.props.addSelectedTeam(teamId);
this.forceUpdate();
diff --git a/web/react/components/admin_console/admin_sidebar_header.jsx b/web/react/components/admin_console/admin_sidebar_header.jsx
index 8c9f74934..f1281c6ee 100644
--- a/web/react/components/admin_console/admin_sidebar_header.jsx
+++ b/web/react/components/admin_console/admin_sidebar_header.jsx
@@ -3,7 +3,6 @@
import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
import UserStore from '../../stores/user_store.jsx';
-import * as Utils from '../../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -39,7 +38,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
/>
);
}
@@ -65,4 +64,4 @@ export default class SidebarHeader extends React.Component {
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 4af350bcd..7d6cfb5c3 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -366,7 +366,7 @@ export default class UserItem extends React.Component {
<td className='row member-div padding--equal'>
<img
className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
height='36'
width='36'
/>
diff --git a/web/react/components/audit_table.jsx b/web/react/components/audit_table.jsx
index 47eee6d3f..917093840 100644
--- a/web/react/components/audit_table.jsx
+++ b/web/react/components/audit_table.jsx
@@ -5,7 +5,7 @@ import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import * as Utils from '../utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate, FormattedTime} from 'mm-intl';
const holders = defineMessages({
sessionRevoked: {
@@ -598,8 +598,23 @@ export function formatAuditInfo(audit, formatMessage) {
}
const date = new Date(audit.create_at);
- let auditInfo = {};
- auditInfo.timestamp = date.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + date.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'});
+ const auditInfo = {};
+ auditInfo.timestamp = (
+ <div>
+ <FormattedDate
+ value={date}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ {' - '}
+ <FormattedTime
+ value={date}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ </div>
+ );
auditInfo.userId = audit.user_id;
auditInfo.desc = auditDesc;
auditInfo.ip = audit.ip_address;
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 2422588cf..2ea840c1e 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -25,40 +25,43 @@ export default class CenterPanel extends React.Component {
constructor(props) {
super(props);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onChannelChange = this.onChannelChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.validState = this.validState.bind(this);
+ this.onStoresChange = this.onStoresChange.bind(this);
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- this.state = {
- showTutorialScreens: tutorialStep === TutorialSteps.INTRO_SCREENS,
+ return {
+ showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS,
showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS,
user: UserStore.getCurrentUser(),
+ channel: ChannelStore.getCurrent(),
profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))
};
}
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- ChannelStore.addChangeListener(this.onChannelChange);
- UserStore.addChangeListener(this.onUserChange);
- }
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- ChannelStore.removeChangeListener(this.onChannelChange);
- UserStore.removeChangeListener(this.onUserChange);
+ validState() {
+ return this.state.user && this.state.channel && this.state.profiles;
}
- onPreferenceChange() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- this.setState({showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS});
+ onStoresChange() {
+ this.setState(this.getStateFromStores());
}
- onChannelChange() {
- this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS});
+ componentDidMount() {
+ PreferenceStore.addChangeListener(this.onStoresChange);
+ ChannelStore.addChangeListener(this.onStoresChange);
+ UserStore.addChangeListener(this.onStoresChange);
}
- onUserChange() {
- this.setState({user: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
+ componentWillUnmount() {
+ PreferenceStore.removeChangeListener(this.onStoresChange);
+ ChannelStore.removeChangeListener(this.onStoresChange);
+ UserStore.removeChangeListener(this.onStoresChange);
}
render() {
- const channel = ChannelStore.getCurrent();
+ if (!this.validState()) {
+ return null;
+ }
+ const channel = this.state.channel;
var handleClick = null;
let postsContainer;
let createPost;
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 51be13dcf..882c575f0 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -57,20 +57,33 @@ export default class ChannelHeader extends React.Component {
memberChannel: ChannelStore.getCurrentMember(),
users: extraInfo.members,
userCount: extraInfo.member_count,
- searchVisible: SearchStore.getSearchResults() !== null
+ searchVisible: SearchStore.getSearchResults() !== null,
+ currentUser: UserStore.getCurrentUser()
};
}
+ validState() {
+ if (!this.state.channel ||
+ !this.state.memberChannel ||
+ !this.state.users ||
+ !this.state.userCount ||
+ !this.state.currentUser) {
+ return false;
+ }
+ return true;
+ }
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
SearchStore.addSearchChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
+ UserStore.addChangeListener(this.onListenerChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
SearchStore.removeSearchChangeListener(this.onListenerChange);
PreferenceStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
}
onListenerChange() {
const newState = this.getStateFromStores();
@@ -98,7 +111,7 @@ export default class ChannelHeader extends React.Component {
searchMentions(e) {
e.preventDefault();
- const user = this.props.user;
+ const user = this.state.currentUser;
let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
@@ -134,7 +147,7 @@ export default class ChannelHeader extends React.Component {
});
}
render() {
- if (this.state.channel === null) {
+ if (!this.validState()) {
return null;
}
@@ -163,8 +176,8 @@ export default class ChannelHeader extends React.Component {
</Popover>
);
let channelTitle = channel.display_name;
- const currentId = this.props.user.id;
- const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.props.user.roles);
+ const currentId = this.state.currentUser.id;
+ const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.currentUser.roles);
const isDirect = (this.state.channel.type === 'D');
if (isDirect) {
@@ -252,7 +265,7 @@ export default class ChannelHeader extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelInviteModal}
- dialogProps={{channel}}
+ dialogProps={{channel, currentUser: this.state.currentUser}}
>
<FormattedMessage
id='chanel_header.addMembers'
@@ -331,7 +344,11 @@ export default class ChannelHeader extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelNotificationsModal}
- dialogProps={{channel}}
+ dialogProps={{
+ channel,
+ channelMember: this.state.memberChannel,
+ currentUser: this.state.currentUser
+ }}
>
<FormattedMessage
id='channel_header.notificationPreferences'
@@ -497,5 +514,4 @@ export default class ChannelHeader extends React.Component {
}
ChannelHeader.propTypes = {
- user: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 6c8d51abb..4157812a9 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -4,8 +4,8 @@
import FilteredUserList from './filtered_user_list.jsx';
import LoadingScreen from './loading_screen.jsx';
-import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
+import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
@@ -16,18 +16,15 @@ import {FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
export default class ChannelInviteModal extends React.Component {
- constructor() {
- super();
+ constructor(props) {
+ super(props);
this.onListenerChange = this.onListenerChange.bind(this);
this.handleInvite = this.handleInvite.bind(this);
-
+ this.getStateFromStores = this.getStateFromStores.bind(this);
this.createInviteButton = this.createInviteButton.bind(this);
- // the state gets populated when the modal is shown
- this.state = {
- loading: true
- };
+ this.state = this.getStateFromStores();
}
shouldComponentUpdate(nextProps, nextState) {
if (!this.props.show && !nextProps.show) {
@@ -63,6 +60,20 @@ export default class ChannelInviteModal extends React.Component {
};
}
+ const currentUser = UserStore.getCurrentUser();
+ if (!currentUser) {
+ return {
+ loading: true
+ };
+ }
+
+ const currentMember = ChannelStore.getCurrentMember();
+ if (!currentMember) {
+ return {
+ loading: true
+ };
+ }
+
const memberIds = extraInfo.members.map((user) => user.id);
var nonmembers = [];
@@ -78,7 +89,9 @@ export default class ChannelInviteModal extends React.Component {
return {
nonmembers,
- loading: false
+ loading: false,
+ currentUser,
+ currentMember
};
}
componentWillReceiveProps(nextProps) {
@@ -93,6 +106,11 @@ export default class ChannelInviteModal extends React.Component {
UserStore.removeChangeListener(this.onListenerChange);
}
}
+ componentWillUnmount() {
+ ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
+ ChannelStore.removeChangeListener(this.onListenerChange);
+ UserStore.removeChangeListener(this.onListenerChange);
+ }
onListenerChange() {
var newState = this.getStateFromStores();
if (!Utils.areObjectsEqual(this.state, newState)) {
@@ -144,7 +162,6 @@ export default class ChannelInviteModal extends React.Component {
if (Utils.windowHeight() <= 1200) {
maxHeight = Utils.windowHeight() - 300;
}
-
content = (
<FilteredUserList
style={{maxHeight}}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
deleted file mode 100644
index e47f2aa50..000000000
--- a/web/react/components/channel_loader.jsx
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-/* This is a special React control with the sole purpose of making all the AsyncClient calls
- to the server on page load. This is to prevent other React controls from spamming
- AsyncClient with requests. */
-
-import * as AsyncClient from '../utils/async_client.jsx';
-import * as Client from '../utils/client.jsx';
-import SocketStore from '../stores/socket_store.jsx';
-import ChannelStore from '../stores/channel_store.jsx';
-import PostStore from '../stores/post_store.jsx';
-import UserStore from '../stores/user_store.jsx';
-import PreferenceStore from '../stores/preference_store.jsx';
-
-import * as Utils from '../utils/utils.jsx';
-import Constants from '../utils/constants.jsx';
-
-import {intlShape, injectIntl, defineMessages} from 'mm-intl';
-
-const holders = defineMessages({
- socketError: {
- id: 'channel_loader.socketError',
- defaultMessage: 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.'
- },
- someone: {
- id: 'channel_loader.someone',
- defaultMessage: 'Someone'
- },
- posted: {
- id: 'channel_loader.posted',
- defaultMessage: 'Posted'
- },
- uploadedImage: {
- id: 'channel_loader.uploadedImage',
- defaultMessage: ' uploaded an image'
- },
- uploadedFile: {
- id: 'channel_loader.uploadedFile',
- defaultMessage: ' uploaded a file'
- },
- something: {
- id: 'channel_loader.something',
- defaultMessage: ' did something new'
- },
- wrote: {
- id: 'channel_loader.wrote',
- defaultMessage: ' wrote: '
- },
- connectionError: {
- id: 'channel_loader.connection_error',
- defaultMessage: 'There appears to be a problem with your internet connection.'
- },
- unknownError: {
- id: 'channel_loader.unknown_error',
- defaultMessage: 'We received an unexpected status code from the server.'
- }
-});
-
-class ChannelLoader extends React.Component {
- constructor(props) {
- super(props);
-
- this.intervalId = null;
-
- this.onSocketChange = this.onSocketChange.bind(this);
-
- const {formatMessage} = this.props.intl;
- SocketStore.setTranslations({
- socketError: formatMessage(holders.socketError),
- someone: formatMessage(holders.someone),
- posted: formatMessage(holders.posted),
- uploadedImage: formatMessage(holders.uploadedImage),
- uploadedFile: formatMessage(holders.uploadedFile),
- something: formatMessage(holders.something),
- wrote: formatMessage(holders.wrote)
- });
-
- Client.setTranslations({
- connectionError: formatMessage(holders.connectionError),
- unknownError: formatMessage(holders.unknownError)
- });
-
- this.state = {};
- }
- componentDidMount() {
- /* Initial aysnc loads */
- AsyncClient.getPosts(ChannelStore.getCurrentId());
- AsyncClient.getChannels();
- AsyncClient.getChannelExtraInfo();
- AsyncClient.findTeams();
- AsyncClient.getMyTeam();
- setTimeout(() => AsyncClient.getStatuses(), 3000); // temporary until statuses are reworked a bit
-
- /* Perform pending post clean-up */
- PostStore.clearPendingPosts();
-
- /* Set up interval functions */
- this.intervalId = setInterval(() => AsyncClient.getStatuses(), 30000);
-
- /* Device tracking setup */
- var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
- if (iOS) {
- $('body').addClass('ios');
- }
-
- /* Set up tracking for whether the window is active */
- window.isActive = true;
-
- $(window).on('focus', function windowFocus() {
- AsyncClient.updateLastViewedAt();
- ChannelStore.resetCounts(ChannelStore.getCurrentId());
- ChannelStore.emitChange();
- window.isActive = true;
- });
-
- $(window).on('blur', function windowBlur() {
- window.isActive = false;
- });
-
- /* Start global change listeners setup */
- SocketStore.addChangeListener(this.onSocketChange);
-
- /* Update CSS classes to match user theme */
- var user = UserStore.getCurrentUser();
-
- if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
- Utils.applyTheme(user.theme_props);
- } else {
- Utils.applyTheme(Constants.THEMES.default);
- }
-
- // if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx
- const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
- Utils.applyFont(selectedFont);
-
- $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
- } else {
- $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
- }
- });
-
- /* Prevent backspace from navigating back a page */
- $(window).on('keydown.preventBackspace', (e) => {
- if (e.which === 8 && !$(e.target).is('input, textarea')) {
- e.preventDefault();
- }
- });
- }
- componentWillUnmount() {
- clearInterval(this.intervalId);
-
- $(window).off('focus');
- $(window).off('blur');
-
- SocketStore.removeChangeListener(this.onSocketChange);
-
- $('body').off('click.userpopover');
- $('body').off('mouseenter mouseleave', '.post');
- $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
-
- $('.modal').off('show.bs.modal');
-
- $(window).off('keydown.preventBackspace');
- }
- onSocketChange(msg) {
- if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
- UserStore.setStatus(msg.user_id, 'online');
- }
- }
- render() {
- return <div/>;
- }
-}
-
-ChannelLoader.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(ChannelLoader);
diff --git a/web/react/components/channel_notifications_modal.jsx b/web/react/components/channel_notifications_modal.jsx
index 7048434f8..acefaf024 100644
--- a/web/react/components/channel_notifications_modal.jsx
+++ b/web/react/components/channel_notifications_modal.jsx
@@ -6,7 +6,6 @@ import SettingItemMin from './setting_item_min.jsx';
import SettingItemMax from './setting_item_max.jsx';
import * as Client from '../utils/client.jsx';
-import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -15,7 +14,6 @@ export default class ChannelNotificationsModal extends React.Component {
constructor(props) {
super(props);
- this.onListenerChange = this.onListenerChange.bind(this);
this.updateSection = this.updateSection.bind(this);
this.handleSubmitNotifyLevel = this.handleSubmitNotifyLevel.bind(this);
@@ -26,58 +24,41 @@ export default class ChannelNotificationsModal extends React.Component {
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
- const member = ChannelStore.getMember(props.channel.id);
this.state = {
- notifyLevel: member.notify_props.desktop,
- markUnreadLevel: member.notify_props.mark_unread,
- channelId: ChannelStore.getCurrentId(),
- activeSection: ''
+ activeSection: '',
+ notifyLevel: '',
+ unreadLevel: ''
};
}
+ updateSection(section) {
+ this.setState({activeSection: section});
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
- this.onListenerChange();
- ChannelStore.addChangeListener(this.onListenerChange);
- } else {
- ChannelStore.removeChangeListener(this.onListenerChange);
+ this.setState({
+ notifyLevel: nextProps.channelMember.notify_props.desktop,
+ unreadLevel: nextProps.channelMember.notify_props.mark_unread
+ });
}
}
- onListenerChange() {
- const curChannelId = ChannelStore.getCurrentId();
-
- if (!curChannelId) {
- return;
- }
-
- const newState = {channelId: curChannelId};
- const member = ChannelStore.getMember(curChannelId);
-
- if (member.notify_props.desktop !== this.state.notifyLevel || member.notify_props.mark_unread !== this.state.mark_unread) {
- newState.notifyLevel = member.notify_props.desktop;
- newState.markUnreadLevel = member.notify_props.mark_unread;
- }
-
- this.setState(newState);
- }
- updateSection(section) {
- this.setState({activeSection: section});
- }
handleSubmitNotifyLevel() {
- var channelId = this.state.channelId;
+ var channelId = this.props.channel.id;
var notifyLevel = this.state.notifyLevel;
- if (ChannelStore.getMember(channelId).notify_props.desktop === notifyLevel) {
+ if (this.props.channelMember.notify_props.desktop === notifyLevel) {
this.updateSection('');
return;
}
var data = {};
data.channel_id = channelId;
- data.user_id = UserStore.getCurrentId();
+ data.user_id = this.props.currentUser.id;
data.desktop = notifyLevel;
+ //TODO: This should be moved to event_helpers
Client.updateNotifyProps(data,
() => {
+ // YUCK
var member = ChannelStore.getMember(channelId);
member.notify_props.desktop = notifyLevel;
ChannelStore.setChannelMember(member);
@@ -92,11 +73,8 @@ export default class ChannelNotificationsModal extends React.Component {
this.setState({notifyLevel});
}
createNotifyLevelSection(serverError) {
- var handleUpdateSection;
-
- const user = UserStore.getCurrentUser();
- const globalNotifyLevel = user.notify_props.desktop;
-
+ // Get glabal user setting for notifications
+ const globalNotifyLevel = this.props.currentUser.notify_props.desktop;
let globalNotifyLevelName;
if (globalNotifyLevel === 'all') {
globalNotifyLevelName = (
@@ -128,13 +106,15 @@ export default class ChannelNotificationsModal extends React.Component {
/>
);
+ const notificationLevel = this.state.notifyLevel;
+
if (this.state.activeSection === 'desktop') {
- var notifyActive = [false, false, false, false];
- if (this.state.notifyLevel === 'default') {
+ const notifyActive = [false, false, false, false];
+ if (notificationLevel === 'default') {
notifyActive[0] = true;
- } else if (this.state.notifyLevel === 'all') {
+ } else if (notificationLevel === 'all') {
notifyActive[1] = true;
- } else if (this.state.notifyLevel === 'mention') {
+ } else if (notificationLevel === 'mention') {
notifyActive[2] = true;
} else {
notifyActive[3] = true;
@@ -196,7 +176,7 @@ export default class ChannelNotificationsModal extends React.Component {
</div>
);
- handleUpdateSection = function updateSection(e) {
+ const handleUpdateSection = function updateSection(e) {
this.updateSection('');
this.onListenerChange();
e.preventDefault();
@@ -224,7 +204,7 @@ export default class ChannelNotificationsModal extends React.Component {
}
var describe;
- if (this.state.notifyLevel === 'default') {
+ if (notificationLevel === 'default') {
describe = (
<FormattedMessage
id='channel_notifications.globalDefault'
@@ -233,45 +213,44 @@ export default class ChannelNotificationsModal extends React.Component {
}}
/>
);
- } else if (this.state.notifyLevel === 'mention') {
+ } else if (notificationLevel === 'mention') {
describe = (<FormattedMessage id='channel_notifications.onlyMentions'/>);
- } else if (this.state.notifyLevel === 'all') {
+ } else if (notificationLevel === 'all') {
describe = (<FormattedMessage id='channel_notifications.allActivity'/>);
} else {
describe = (<FormattedMessage id='channel_notifications.never'/>);
}
- handleUpdateSection = function updateSection(e) {
- this.updateSection('desktop');
- e.preventDefault();
- }.bind(this);
-
return (
<SettingItemMin
title={sendDesktop}
describe={describe}
- updateSection={handleUpdateSection}
+ updateSection={() => {
+ this.updateSection('desktop');
+ }}
/>
);
}
handleSubmitMarkUnreadLevel() {
- const channelId = this.state.channelId;
- const markUnreadLevel = this.state.markUnreadLevel;
+ const channelId = this.props.channel.id;
+ const markUnreadLevel = this.state.unreadLevel;
- if (ChannelStore.getMember(channelId).notify_props.mark_unread === markUnreadLevel) {
+ if (this.props.channelMember.notify_props.mark_unread === markUnreadLevel) {
this.updateSection('');
return;
}
const data = {
channel_id: channelId,
- user_id: UserStore.getCurrentId(),
+ user_id: this.props.currentUser.id,
mark_unread: markUnreadLevel
};
+ //TODO: This should be fixed, moved to event_helpers
Client.updateNotifyProps(data,
() => {
+ // Yuck...
var member = ChannelStore.getMember(channelId);
member.notify_props.mark_unread = markUnreadLevel;
ChannelStore.setChannelMember(member);
@@ -283,8 +262,8 @@ export default class ChannelNotificationsModal extends React.Component {
);
}
- handleUpdateMarkUnreadLevel(markUnreadLevel) {
- this.setState({markUnreadLevel});
+ handleUpdateMarkUnreadLevel(unreadLevel) {
+ this.setState({unreadLevel});
}
createMarkUnreadLevelSection(serverError) {
@@ -303,7 +282,7 @@ export default class ChannelNotificationsModal extends React.Component {
<label>
<input
type='radio'
- checked={this.state.markUnreadLevel === 'all'}
+ checked={this.state.unreadLevel === 'all'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
/>
<FormattedMessage
@@ -317,7 +296,7 @@ export default class ChannelNotificationsModal extends React.Component {
<label>
<input
type='radio'
- checked={this.state.markUnreadLevel === 'mention'}
+ checked={this.state.unreadLevel === 'mention'}
onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
/>
<FormattedMessage id='channel_notifications.onlyMentions'/>
@@ -355,7 +334,7 @@ export default class ChannelNotificationsModal extends React.Component {
} else {
let describe;
- if (!this.state.markUnreadLevel || this.state.markUnreadLevel === 'all') {
+ if (!this.state.unreadLevel || this.state.unreadLevel === 'all') {
describe = (
<FormattedMessage
id='channel_notifications.allUnread'
@@ -430,5 +409,7 @@ export default class ChannelNotificationsModal extends React.Component {
ChannelNotificationsModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
- channel: React.PropTypes.object.isRequired
+ channel: React.PropTypes.object.isRequired,
+ channelMember: React.PropTypes.object.isRequired,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/channel_view.jsx b/web/react/components/channel_view.jsx
index 9c4131292..76744d6d7 100644
--- a/web/react/components/channel_view.jsx
+++ b/web/react/components/channel_view.jsx
@@ -2,34 +2,11 @@
// See License.txt for license information.
import CenterPanel from '../components/center_panel.jsx';
-import Sidebar from '../components/sidebar.jsx';
-import SidebarRight from '../components/sidebar_right.jsx';
-import SidebarRightMenu from '../components/sidebar_right_menu.jsx';
export default class ChannelView extends React.Component {
render() {
return (
- <div className='container-fluid'>
- <div
- className='sidebar--right'
- id='sidebar-right'
- >
- <SidebarRight/>
- </div>
- <div
- className='sidebar--menu'
- id='sidebar-menu'
- >
- <SidebarRightMenu/>
- </div>
- <div
- className='sidebar--left'
- id='sidebar-left'
- >
- <Sidebar/>
- </div>
- <CenterPanel/>
- </div>
+ <CenterPanel/>
);
}
}
@@ -37,4 +14,5 @@ ChannelView.defaultProps = {
};
ChannelView.propTypes = {
+ params: React.PropTypes.object
};
diff --git a/web/react/components/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx
index 5b3b584ee..42fd8dafa 100644
--- a/web/react/components/claim/claim_account.jsx
+++ b/web/react/components/claim/claim_account.jsx
@@ -3,6 +3,7 @@
import EmailToSSO from './email_to_sso.jsx';
import SSOToEmail from './sso_to_email.jsx';
+import TeamStore from '../../stores/team_store.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -10,11 +11,46 @@ export default class ClaimAccount extends React.Component {
constructor(props) {
super(props);
+ this.onTeamChange = this.onTeamChange.bind(this);
+ this.updateStateFromStores = this.updateStateFromStores.bind(this);
+
this.state = {};
}
+ componentWillMount() {
+ this.setState({
+ email: this.props.location.query.email,
+ newType: this.props.location.query.new_type,
+ oldType: this.props.location.query.old_type,
+ teamName: this.props.params.team,
+ teamDisplayName: ''
+ });
+ this.updateStateFromStores();
+ }
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ }
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+ updateStateFromStores() {
+ const team = TeamStore.getByName(this.state.teamName);
+ let displayName = '';
+ if (team) {
+ displayName = team.displayName;
+ }
+ this.setState({
+ teamDisplayName: displayName
+ });
+ }
+ onTeamChange() {
+ this.updateStateFromStores();
+ }
render() {
+ if (this.state.teamDisplayName === '') {
+ return (<div/>);
+ }
let content;
- if (this.props.email === '') {
+ if (this.state.email === '') {
content = (
<p>
<FormattedMessage
@@ -23,36 +59,55 @@ export default class ClaimAccount extends React.Component {
/>
</p>
);
- } else if (this.props.currentType === '' && this.props.newType !== '') {
+ } else if (this.state.oldType === '' && this.state.newType !== '') {
content = (
<EmailToSSO
- email={this.props.email}
- type={this.props.newType}
- teamName={this.props.teamName}
- teamDisplayName={this.props.teamDisplayName}
+ email={this.state.email}
+ type={this.state.newType}
+ teamName={this.state.teamName}
+ teamDisplayName={this.state.teamDisplayName}
/>
);
} else {
content = (
<SSOToEmail
- email={this.props.email}
- currentType={this.props.currentType}
- teamName={this.props.teamName}
- teamDisplayName={this.props.teamDisplayName}
+ email={this.state.email}
+ currentType={this.state.oldType}
+ teamName={this.state.teamName}
+ teamDisplayName={this.state.teamDisplayName}
/>
);
}
- return content;
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <div id='claim'>
+ {content}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
}
}
ClaimAccount.defaultProps = {
};
ClaimAccount.propTypes = {
- currentType: React.PropTypes.string.isRequired,
- newType: React.PropTypes.string.isRequired,
- email: React.PropTypes.string.isRequired,
- teamName: React.PropTypes.string.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
+ params: React.PropTypes.object.isRequired,
+ location: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx
index 74137082a..a16efb57b 100644
--- a/web/react/components/claim/sso_to_email.jsx
+++ b/web/react/components/claim/sso_to_email.jsx
@@ -159,7 +159,7 @@ SSOToEmail.propTypes = {
currentType: React.PropTypes.string.isRequired,
email: React.PropTypes.string.isRequired,
teamName: React.PropTypes.string.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
+ teamDisplayName: React.PropTypes.string
};
export default injectIntl(SSOToEmail);
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 62319b1a7..69cc74842 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -9,7 +9,7 @@ import PostDeletedModal from './post_deleted_modal.jsx';
import TutorialTip from './tutorial/tutorial_tip.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
import * as Utils from '../utils/utils.jsx';
@@ -165,7 +165,7 @@ class CreatePost extends React.Component {
const channel = ChannelStore.get(this.state.channelId);
- EventHelpers.emitUserPostedEvent(post);
+ GlobalActions.emitUserPostedEvent(post);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
Client.createPost(post, channel,
@@ -177,7 +177,7 @@ class CreatePost extends React.Component {
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
- EventHelpers.emitPostRecievedEvent(data);
+ GlobalActions.emitPostRecievedEvent(data);
},
(err) => {
if (err.id === 'api.post.create_post.root_id.app_error') {
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index d9113bc9f..70e7a67a8 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -9,6 +9,8 @@ import Constants from '../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
+
export default class DeleteChannelModal extends React.Component {
constructor(props) {
super(props);
@@ -21,11 +23,11 @@ export default class DeleteChannelModal extends React.Component {
return;
}
+ browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
Client.deleteChannel(
this.props.channel.id,
() => {
AsyncClient.getChannels(true);
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
(err) => {
AsyncClient.dispatchError(err, 'handleDelete');
diff --git a/web/react/components/do_verify_email.jsx b/web/react/components/do_verify_email.jsx
new file mode 100644
index 000000000..df98bf463
--- /dev/null
+++ b/web/react/components/do_verify_email.jsx
@@ -0,0 +1,82 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+import * as Client from '../utils/client.jsx';
+import LoadingScreen from './loading_screen.jsx';
+
+import {browserHistory} from 'react-router';
+
+export default class DoVerifyEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ verifyStatus: 'pending',
+ serverError: ''
+ };
+ }
+ componentWillMount() {
+ const uid = this.props.location.query.uid;
+ const hid = this.props.location.query.hid;
+ const teamName = this.props.location.query.teamname;
+ const email = this.props.location.query.email;
+
+ Client.verifyEmail(
+ () => {
+ browserHistory.push('/' + teamName + '/login?extra=verified&email=' + email);
+ },
+ (err) => {
+ this.setState({verifyStatus: 'failure', serverError: err.message});
+ },
+ uid,
+ hid
+ );
+ }
+ render() {
+ if (this.state.verifyStatus !== 'failure') {
+ return (<LoadingScreen/>);
+ }
+
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='email_verify.almost'
+ defaultMessage='{siteName}: You are almost done'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h3>
+ <div>
+ <p>
+ <FormattedMessage id='email_verify.verifyFailed'/>
+ </p>
+ <p className='alert alert-danger'>
+ <i className='fa fa-times'/>
+ {this.state.serverError}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+DoVerifyEmail.defaultProps = {
+};
+DoVerifyEmail.propTypes = {
+ location: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/docs.jsx b/web/react/components/docs.jsx
deleted file mode 100644
index 6d3a109c2..000000000
--- a/web/react/components/docs.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as TextFormatting from '../utils/text_formatting.jsx';
-import UserStore from '../stores/user_store.jsx';
-
-export default class Docs extends React.Component {
- constructor(props) {
- super(props);
- UserStore.setCurrentUser(global.window.mm_user || {});
-
- this.state = {text: ''};
- const errorState = {text: '## 404'};
-
- if (props.site) {
- $.get(`/static/help/${props.site}_${global.window.mm_locale}.md`).then((response) => {
- this.setState({text: response});
- }, () => {
- this.setState(errorState);
- });
- } else {
- this.setState(errorState);
- }
- }
-
- render() {
- return (
- <div
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.text)}}
- >
- </div>
- );
- }
-}
-
-Docs.defaultProps = {
- site: ''
-};
-Docs.propTypes = {
- site: React.PropTypes.string
-};
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 380ca7bde..f02239fcf 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -3,7 +3,7 @@
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Textbox from './textbox.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import PostStore from '../stores/post_store.jsx';
@@ -45,7 +45,7 @@ class EditPostModal extends React.Component {
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
$('#edit_post').modal('hide');
- EventHelpers.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
+ GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
return;
}
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
deleted file mode 100644
index 702a20eba..000000000
--- a/web/react/components/email_verify.jsx
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-export default class EmailVerify extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleResend = this.handleResend.bind(this);
-
- this.state = {};
- }
- handleResend() {
- const newAddress = window.location.href.replace('&resend_success=true', '');
- window.location.href = newAddress + '&resend=true';
- }
- render() {
- var title = '';
- var body = '';
- var resend = '';
- var resendConfirm = '';
- if (this.props.isVerified === 'true') {
- title = (
- <FormattedMessage
- id='email_verify.verified'
- defaultMessage='{siteName} Email Verified'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- );
- body = (
- <FormattedHTMLMessage
- id='email_verify.verifiedBody'
- defaultMessage='<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>'
- values={{
- url: this.props.teamURL + '?email=' + this.props.userEmail
- }}
- />
- );
- } else {
- title = (
- <FormattedMessage
- id='email_verify.almost'
- defaultMessage='{siteName}: You are almost done'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- );
- body = (
- <p>
- <FormattedMessage
- id='email_verify.notVerifiedBody'
- defaultMessage='Please verify your email address. Check your inbox for an email.'
- />
- </p>
- );
- resend = (
- <button
- onClick={this.handleResend}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='email_verify.resend'
- defaultMessage='Resend Email'
- />
- </button>
- );
- if (this.props.resendSuccess) {
- resendConfirm = (
- <div><br/><p className='alert alert-success'><i className='fa fa-check'></i>
- <FormattedMessage
- id='email_verify.sent'
- defaultMessage=' Verification email sent.'
- />
- </p></div>);
- }
- }
-
- return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>{title}</h3>
- <div>
- {body}
- {resend}
- {resendConfirm}
- </div>
- </div>
- </div>
- );
- }
-}
-
-EmailVerify.defaultProps = {
- isVerified: 'false',
- teamURL: '',
- userEmail: '',
- resendSuccess: 'false'
-};
-EmailVerify.propTypes = {
- isVerified: React.PropTypes.string,
- teamURL: React.PropTypes.string,
- userEmail: React.PropTypes.string,
- resendSuccess: React.PropTypes.string
-};
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index 2f6067b86..8abcac8c3 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -43,7 +43,7 @@ class FileAttachment extends React.Component {
if (type === 'image') {
var self = this; // Need this reference since we use the given "this"
- $('<img/>').attr('src', fileInfo.path + '_thumb.jpg?' + utils.getSessionIndex()).load(function loadWrapper(path, name) {
+ $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
return function loader() {
$(this).remove();
if (name in self.refs) {
@@ -114,7 +114,7 @@ class FileAttachment extends React.Component {
var re3 = new RegExp('\\)', 'g');
var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
- $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg?' + utils.getSessionIndex() + ')');
+ $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
}
}
removeBackgroundImage(name) {
diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx
deleted file mode 100644
index 3ff9787ad..000000000
--- a/web/react/components/find_team.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
-
-var holders = defineMessages({
- submitError: {
- id: 'find_team.submitError',
- defaultMessage: 'Please enter a valid email address'
- },
- placeholder: {
- id: 'find_team.placeholder',
- defaultMessage: 'you@domain.com'
- }
-});
-
-class FindTeam extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
-
- this.handleSubmit = this.handleSubmit.bind(this);
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- var state = { };
-
- var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
- if (!email || !utils.isEmail(email)) {
- state.email_error = this.props.intl.formatMessage(holders.submitError);
- this.setState(state);
- return;
- }
-
- state.email_error = '';
-
- client.findTeamsSendEmail(email,
- function success() {
- state.sent = true;
- this.setState(state);
- }.bind(this),
- function fail(err) {
- state.email_error = err.message;
- this.setState(state);
- }.bind(this)
- );
- }
-
- render() {
- var emailError = null;
- var emailErrorClass = 'form-group';
-
- if (this.state.email_error) {
- emailError = <label className='control-label'>{this.state.email_error}</label>;
- emailErrorClass = 'form-group has-error';
- }
-
- if (this.state.sent) {
- return (
- <div>
- <h4>
- <FormattedMessage
- id='find_team.findTitle'
- defaultMessage='Find Your Team'
- />
- </h4>
- <p>
- <FormattedMessage
- id='find_team.findDescription'
- defaultMessage='An email was sent with links to any teams to which you are a member.'
- />
- </p>
- </div>
- );
- }
-
- return (
- <div>
- <h4>
- <FormattedMessage
- id='find_team.findTitle'
- defaultMessage='Find Your Team'
- />
- </h4>
- <form onSubmit={this.handleSubmit}>
- <p>
- <FormattedMessage
- id='find_team.getLinks'
- defaultMessage='Get an email with links to any teams to which you are a member.'
- />
- </p>
- <div className='form-group'>
- <label className='control-label'>
- <FormattedMessage
- id='find_team.email'
- defaultMessage='Email'
- />
- </label>
- <div className={emailErrorClass}>
- <input
- type='text'
- ref='email'
- className='form-control'
- placeholder={this.props.intl.formatMessage(holders.placeholder)}
- maxLength='128'
- spellCheck='false'
- />
- {emailError}
- </div>
- </div>
- <button
- className='btn btn-md btn-primary'
- type='submit'
- >
- <FormattedMessage
- id='find_team.send'
- defaultMessage='Send'
- />
- </button>
- </form>
- </div>
- );
- }
-}
-
-FindTeam.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(FindTeam); \ No newline at end of file
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 184ba1357..71cd5b8b6 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -5,7 +5,7 @@ import * as utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
import * as Client from '../utils/client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import ModalStore from '../stores/modal_store.jsx';
import UserStore from '../stores/user_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -223,7 +223,7 @@ class InviteMemberModal extends React.Component {
showGetTeamInviteLinkModal() {
this.handleHide(false);
- EventHelpers.showGetTeamInviteLinkModal();
+ GlobalActions.showGetTeamInviteLinkModal();
}
render() {
diff --git a/web/react/components/logged_in.jsx b/web/react/components/logged_in.jsx
new file mode 100644
index 000000000..1ed3694e9
--- /dev/null
+++ b/web/react/components/logged_in.jsx
@@ -0,0 +1,224 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as AsyncClient from '../utils/async_client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import UserStore from '../stores/user_store.jsx';
+import SocketStore from '../stores/socket_store.jsx';
+import ChannelStore from '../stores/channel_store.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
+import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
+import ErrorBar from '../components/error_bar.jsx';
+
+import {browserHistory} from 'react-router';
+
+import SidebarRight from '../components/sidebar_right.jsx';
+import SidebarRightMenu from '../components/sidebar_right_menu.jsx';
+
+// Modals
+import GetPostLinkModal from '../components/get_post_link_modal.jsx';
+import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
+import EditPostModal from '../components/edit_post_modal.jsx';
+import DeletePostModal from '../components/delete_post_modal.jsx';
+import MoreChannelsModal from '../components/more_channels.jsx';
+import TeamSettingsModal from '../components/team_settings_modal.jsx';
+import RemovedFromChannelModal from '../components/removed_from_channel_modal.jsx';
+import RegisterAppModal from '../components/register_app_modal.jsx';
+import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
+import InviteMemberModal from '../components/invite_member_modal.jsx';
+import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
+
+const CLIENT_STATUS_INTERVAL = 30000;
+const BACKSPACE_CHAR = 8;
+
+export default class LoggedIn extends React.Component {
+ constructor(params) {
+ super(params);
+
+ this.onUserChanged = this.onUserChanged.bind(this);
+ }
+ onUserChanged() {
+ // Grab the current user
+ const user = UserStore.getCurrentUser();
+
+ // Update segment indentify
+ if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') {
+ global.window.analytics.identify(user.id, {
+ name: user.nickname,
+ email: user.email,
+ createdAt: user.create_at,
+ username: user.username,
+ team_id: user.team_id,
+ id: user.id
+ });
+ }
+
+ // Update CSS classes to match user theme
+ if (user) {
+ if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
+ Utils.applyTheme(user.theme_props);
+ } else {
+ Utils.applyTheme(Constants.THEMES.default);
+ }
+ }
+ }
+ onSocketChange(msg) {
+ if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
+ UserStore.setStatus(msg.user_id, 'online');
+ }
+ }
+ componentWillMount() {
+ // Emit view action
+ GlobalActions.viewLoggedIn();
+
+ // Listen for user
+ UserStore.addChangeListener(this.onUserChanged);
+
+ // Add listner for socker store
+ SocketStore.addChangeListener(this.onSocketChange);
+
+ // Get all statuses regularally. (Soon to be switched to websocket)
+ this.intervalId = setInterval(() => AsyncClient.getStatuses(), CLIENT_STATUS_INTERVAL);
+
+ // Force logout of all tabs if one tab is logged out
+ $(window).bind('storage', (e) => {
+ // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
+ if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected logout from a different tab'); //eslint-disable-line no-console
+ browserHistory.push('/' + this.props.params.team);
+ }
+
+ if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (window.BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected login from a different tab'); //eslint-disable-line no-console
+ location.reload();
+ }
+ });
+
+ // Because current CSS requires the root tag to have specific stuff
+ $('#root').attr('class', 'channel-view');
+
+ // ???
+ $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
+ } else {
+ $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
+ $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
+ }
+ });
+
+ // Device tracking setup
+ var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
+ if (iOS) {
+ $('body').addClass('ios');
+ }
+
+ // Set up tracking for whether the window is active
+ window.isActive = true;
+ $(window).on('focus', () => {
+ AsyncClient.updateLastViewedAt();
+ ChannelStore.resetCounts(ChannelStore.getCurrentId());
+ ChannelStore.emitChange();
+ window.isActive = true;
+ });
+ $(window).on('blur', () => {
+ window.isActive = false;
+ });
+
+ // if preferences have already been stored in local storage do not wait until preference store change is fired and handled in channel.jsx
+ const selectedFont = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT);
+ Utils.applyFont(selectedFont);
+
+ // Pervent backspace from navigating back a page
+ $(window).on('keydown.preventBackspace', (e) => {
+ if (e.which === BACKSPACE_CHAR && !$(e.target).is('input, textarea')) {
+ e.preventDefault();
+ }
+ });
+ }
+ componentWillUnmount() {
+ $('#root').attr('class', '');
+ clearInterval(this.intervalId);
+
+ $(window).off('focus');
+ $(window).off('blur');
+
+ SocketStore.removeChangeListener(this.onSocketChange);
+ UserStore.removeChangeListener(this.onUserChanged);
+
+ $('body').off('click.userpopover');
+ $('body').off('mouseenter mouseleave', '.post');
+ $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
+
+ $('.modal').off('show.bs.modal');
+
+ $(window).off('keydown.preventBackspace');
+ }
+ render() {
+ return (
+ <div className='channel-view'>
+ <ErrorBar/>
+ <div className='container-fluid'>
+ <SidebarRight/>
+ <SidebarRightMenu/>
+ {this.props.sidebar}
+ {this.props.center}
+
+ <GetPostLinkModal/>
+ <GetTeamInviteLinkModal/>
+ <InviteMemberModal/>
+ <ImportThemeModal/>
+ <TeamSettingsModal/>
+ <MoreChannelsModal/>
+ <EditPostModal/>
+ <DeletePostModal/>
+ <RemovedFromChannelModal/>
+ <RegisterAppModal/>
+ <SelectTeamModal/>
+ </div>
+ </div>
+ );
+ }
+}
+
+LoggedIn.defaultProps = {
+};
+
+LoggedIn.propTypes = {
+ children: React.PropTypes.object,
+ sidebar: React.PropTypes.object,
+ center: React.PropTypes.object,
+ params: React.PropTypes.object
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 581b8e0b5..30c8ffe4f 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -6,82 +6,118 @@ import LoginUsername from './login_username.jsx';
import LoginLdap from './login_ldap.jsx';
import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
import Constants from '../utils/constants.jsx';
+import TeamStore from '../stores/team_store.jsx';
import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
export default class Login extends React.Component {
constructor(props) {
super(props);
- this.state = {};
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamChange);
+ Client.getMeLoggedIn((data) => {
+ if (data && data.logged_in !== 'false') {
+ browserHistory.push('/' + this.props.params.team + '/channels/town-square');
+ }
+ });
+ }
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamChange);
+ }
+ getStateFromStores() {
+ return {
+ currentTeam: TeamStore.getByName(this.props.params.team)
+ };
+ }
+ onTeamChange() {
+ this.setState(this.getStateFromStores());
}
render() {
- const teamDisplayName = this.props.teamDisplayName;
- const teamName = this.props.teamName;
+ const currentTeam = this.state.currentTeam;
+ if (currentTeam == null) {
+ return <div/>;
+ }
+
+ const teamDisplayName = currentTeam.display_name;
+ const teamName = currentTeam.name;
let loginMessage = [];
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
loginMessage.push(
- <a
- className='btn btn-custom-login gitlab'
- key='gitlab'
- href={'/' + teamName + '/login/gitlab'}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.gitlab'
- defaultMessage='with GitLab'
- />
- </span>
- </a>
+ <a
+ className='btn btn-custom-login gitlab'
+ key='gitlab'
+ href={'/api/v1/oauth/gitlab/login?team=' + encodeURIComponent(teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='login.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
+ </a>
);
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
loginMessage.push(
- <a
- className='btn btn-custom-login google'
- key='google'
- href={'/' + teamName + '/login/google'}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.google'
- defaultMessage='with Google Apps'
- />
- </span>
- </a>
- );
+ <a
+ className='btn btn-custom-login google'
+ key='google'
+ href={'/api/v1/oauth/google/login?team=' + encodeURIComponent(teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='login.google'
+ defaultMessage='with Google Apps'
+ />
+ </span>
+ </a>
+ );
}
const extraParam = Utils.getUrlParameter('extra');
let extraBox = '';
if (extraParam) {
- let msg;
if (extraParam === Constants.SIGNIN_CHANGE) {
- msg = (
- <FormattedMessage
- id='login.changed'
- defaultMessage=' Sign-in method changed successfully'
- />
+ extraBox = (
+ <div className='alert alert-success'>
+ <i className='fa fa-check'/>
+ <FormattedMessage
+ id='login.changed'
+ defaultMessage=' Sign-in method changed successfully'
+ />
+ </div>
);
} else if (extraParam === Constants.SIGNIN_VERIFIED) {
- msg = (
- <FormattedMessage
- id='login.verified'
- defaultMessage=' Email Verified'
- />
- );
- }
-
- if (msg != null) {
extraBox = (
<div className='alert alert-success'>
<i className='fa fa-check'/>
- {msg}
+ <FormattedMessage
+ id='login.verified'
+ defaultMessage=' Email Verified'
+ />
+ </div>
+ );
+ } else if (extraParam === Constants.SESSION_EXPIRED) {
+ extraBox = (
+ <div className='alert alert-warning'>
+ <i className='fa fa-exclamation-triangle'/>
+ <FormattedMessage
+ id='login.session_expired'
+ defaultMessage=' Your session has expired. Please login again.'
+ />
</div>
);
}
@@ -91,7 +127,7 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableSignInWithEmail === 'true') {
emailSignup = (
<LoginEmail
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
@@ -125,7 +161,7 @@ export default class Login extends React.Component {
}
let userSignUp = null;
- if (this.props.inviteId) {
+ if (currentTeam.allow_open_invite) {
userSignUp = (
<div>
<span>
@@ -134,7 +170,7 @@ export default class Login extends React.Component {
defaultMessage="Don't have an account? "
/>
<a
- href={'/signup_user_complete/?id=' + this.props.inviteId}
+ href={'/signup_user_complete/?id=' + currentTeam.invite_id}
className='signup-team-login'
>
<FormattedMessage
@@ -168,73 +204,65 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableLdap === 'true') {
ldapLogin = (
<LoginLdap
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
- let findTeams = null;
- if (!Utils.isMobileApp()) {
- findTeams = (
- <div className='form-group margin--extra form-group--small'>
- <span>
- <a href='/find_team'>
- <FormattedMessage
- id='login.find'
- defaultMessage='Find your other teams'
- />
- </a></span>
- </div>
- );
- }
-
let usernameLogin = null;
if (global.window.mm_config.EnableSignInWithUsername === 'true') {
usernameLogin = (
<LoginUsername
- teamName={this.props.teamName}
+ teamName={teamName}
/>
);
}
return (
- <div className='signup-team__container'>
- <h5 className='margin--less'>
- <FormattedMessage
- id='login.signTo'
- defaultMessage='Sign in to:'
- />
- </h5>
- <h2 className='signup-team__name'>{teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>
- <FormattedMessage
- id='login.on'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h2>
- {extraBox}
- {loginMessage}
- {emailSignup}
- {usernameLogin}
- {ldapLogin}
- {userSignUp}
- {findTeams}
- {forgotPassword}
- {teamSignUp}
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='login.signTo'
+ defaultMessage='Sign in to:'
+ />
+ </h5>
+ <h2 className='signup-team__name'>{teamDisplayName}</h2>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='login.on'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
+ {extraBox}
+ {loginMessage}
+ {emailSignup}
+ {usernameLogin}
+ {ldapLogin}
+ {userSignUp}
+ {forgotPassword}
+ {teamSignUp}
+ </div>
+ </div>
</div>
);
}
}
Login.defaultProps = {
- teamName: '',
- teamDisplayName: ''
};
Login.propTypes = {
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- inviteId: React.PropTypes.string
+ params: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx
index cf1e1bc40..3e0d8919d 100644
--- a/web/react/components/login_email.jsx
+++ b/web/react/components/login_email.jsx
@@ -4,6 +4,7 @@
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
+import {browserHistory} from 'react-router';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
@@ -72,13 +73,7 @@ class LoginEmail extends React.Component {
Client.loginByEmail(name, email, password,
() => {
UserStore.setLastEmail(email);
-
- const redirect = Utils.getUrlParameter('redirect');
- if (redirect) {
- window.location.href = decodeURIComponent(redirect);
- } else {
- window.location.href = '/' + name + '/channels/town-square';
- }
+ browserHistory.push('/' + name + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
@@ -167,4 +162,4 @@ LoginEmail.propTypes = {
teamName: React.PropTypes.string.isRequired
};
-export default injectIntl(LoginEmail); \ No newline at end of file
+export default injectIntl(LoginEmail);
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index 93fe6c05a..974f026d0 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -56,9 +56,13 @@ export default class Navbar extends React.Component {
return {
channel: ChannelStore.getCurrent(),
member: ChannelStore.getCurrentMember(),
- users: ChannelStore.getCurrentExtraInfo().members
+ users: ChannelStore.getCurrentExtraInfo().members,
+ currentUser: UserStore.getCurrentUser()
};
}
+ stateValid() {
+ return this.state.channel && this.state.member && this.state.users && this.state.currentUser;
+ }
componentDidMount() {
ChannelStore.addChangeListener(this.onChange);
ChannelStore.addExtraInfoChangeListener(this.onChange);
@@ -201,7 +205,7 @@ export default class Navbar extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelInviteModal}
- dialogProps={{channel}}
+ dialogProps={{channel, currentUser: this.state.currentUser}}
>
<FormattedMessage
id='navbar.addMembers'
@@ -286,7 +290,11 @@ export default class Navbar extends React.Component {
<ToggleModalButton
role='menuitem'
dialogType={ChannelNotificationsModal}
- dialogProps={{channel}}
+ dialogProps={{
+ channel,
+ channelMember: this.state.member,
+ currentUser: this.state.currentUser
+ }}
>
<FormattedMessage
id='navbar.preferences'
@@ -412,7 +420,11 @@ export default class Navbar extends React.Component {
return buttons;
}
render() {
- var currentId = UserStore.getCurrentId();
+ if (!this.stateValid()) {
+ return null;
+ }
+
+ var currentId = this.state.currentUser.id;
var channel = this.state.channel;
var channelTitle = this.props.teamDisplayName;
var popoverContent;
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 0ddd6ff4f..12227fd13 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -2,10 +2,7 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
-import UserStore from '../stores/user_store.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import AboutBuildModal from './about_build_modal.jsx';
import TeamMembersModal from './team_members_modal.jsx';
@@ -15,38 +12,20 @@ import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import Constants from '../utils/constants.jsx';
import {FormattedMessage} from 'mm-intl';
-
-function getStateFromStores() {
- const teams = [];
- const teamsObject = UserStore.getTeams();
- for (const teamId in teamsObject) {
- if (teamsObject.hasOwnProperty(teamId)) {
- teams.push(teamsObject[teamId]);
- }
- }
-
- teams.sort(Utils.sortByDisplayName);
- return {teams};
-}
+import {Link} from 'react-router';
export default class NavbarDropdown extends React.Component {
constructor(props) {
super(props);
this.blockToggle = false;
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.handleAboutModal = this.handleAboutModal.bind(this);
- this.onListenerChange = this.onListenerChange.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- const state = getStateFromStores();
- state.showUserSettingsModal = false;
- state.showAboutModal = false;
- this.state = state;
- }
- handleLogoutClick(e) {
- e.preventDefault();
- client.logout();
+ this.state = {
+ showUserSettingsModal: false,
+ showAboutModal: false
+ };
}
handleAboutModal() {
this.setState({showAboutModal: true});
@@ -55,9 +34,6 @@ export default class NavbarDropdown extends React.Component {
this.setState({showAboutModal: false});
}
componentDidMount() {
- UserStore.addTeamsChangeListener(this.onListenerChange);
- TeamStore.addChangeListener(this.onListenerChange);
-
$(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
$('.sidebar--left .dropdown-menu').scrollTop(0);
this.blockToggle = true;
@@ -67,24 +43,15 @@ export default class NavbarDropdown extends React.Component {
});
}
componentWillUnmount() {
- UserStore.removeTeamsChangeListener(this.onListenerChange);
- TeamStore.removeChangeListener(this.onListenerChange);
-
$(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
}
- onListenerChange() {
- var newState = getStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
render() {
var teamLink = '';
var inviteLink = '';
var manageLink = '';
var sysAdminLink = '';
var adminDivider = '';
- var currentUser = UserStore.getCurrentUser();
+ var currentUser = this.props.currentUser;
var isAdmin = false;
var isSystemAdmin = false;
var teamSettings = null;
@@ -97,7 +64,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showInviteMemberModal}
+ onClick={GlobalActions.showInviteMemberModal}
>
<FormattedMessage
id='navbar_dropdown.inviteMember'
@@ -112,7 +79,7 @@ export default class NavbarDropdown extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
>
<FormattedMessage
id='navbar_dropdown.teamLink'
@@ -158,7 +125,7 @@ export default class NavbarDropdown extends React.Component {
sysAdminLink = (
<li>
<a
- href={'/admin_console?' + Utils.getSessionIndex()}
+ href={'/admin_console'}
>
<FormattedMessage
id='navbar_dropdown.console'
@@ -171,31 +138,6 @@ export default class NavbarDropdown extends React.Component {
var teams = [];
- if (this.state.teams.length > 1) {
- teams.push(
- <li
- className='divider'
- key='div'
- >
- </li>
- );
-
- this.state.teams.forEach((team) => {
- if (team.name !== this.props.teamName) {
- teams.push(
- <li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>
- <FormattedMessage
- id='navbar_dropdown.switchTeam'
- defaultMessage='Switch to {team}'
- values={{
- team: team.display_name
- }}
- />
- </a></li>);
- }
- });
- }
-
if (global.window.mm_config.EnableTeamCreation === 'true') {
teams.push(
<li key='newTeam_li'>
@@ -283,15 +225,12 @@ export default class NavbarDropdown extends React.Component {
{inviteLink}
{teamLink}
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={'/' + this.props.teamName + '/logout'}>
<FormattedMessage
id='navbar_dropdown.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
{adminDivider}
{teamSettings}
@@ -333,5 +272,6 @@ NavbarDropdown.defaultProps = {
NavbarDropdown.propTypes = {
teamType: React.PropTypes.string,
teamDisplayName: React.PropTypes.string,
- teamName: React.PropTypes.string
+ teamName: React.PropTypes.string,
+ currentUser: React.PropTypes.object
};
diff --git a/web/react/components/needs_team.jsx b/web/react/components/needs_team.jsx
new file mode 100644
index 000000000..33b9cd37e
--- /dev/null
+++ b/web/react/components/needs_team.jsx
@@ -0,0 +1,20 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+
+export default class NeedsTeam extends React.Component {
+ componentWillMount() {
+ GlobalActions.loadTeamRequiredPage();
+ }
+ render() {
+ return this.props.children;
+ }
+}
+
+NeedsTeam.defaultProps = {
+};
+
+NeedsTeam.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/not_logged_in.jsx b/web/react/components/not_logged_in.jsx
new file mode 100644
index 000000000..7af293e77
--- /dev/null
+++ b/web/react/components/not_logged_in.jsx
@@ -0,0 +1,70 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+
+export default class NotLoggedIn extends React.Component {
+ componentDidMount() {
+ $('body').attr('class', 'white');
+ $('#root').attr('class', 'container-fluid');
+ }
+ componentWillUnmount() {
+ $('body').attr('class', '');
+ $('#root').attr('class', '');
+ }
+ render() {
+ return (
+ <div className='inner__wrap'>
+ <div className='row content'>
+ {this.props.children}
+ <div className='footer-push'></div>
+ </div>
+ <div className='row footer'>
+ <div className='footer-pane col-xs-12'>
+ <div className='col-xs-12'>
+ <span className='pull-right footer-site-name'>{global.window.mm_config.SiteName}</span>
+ </div>
+ <div className='col-xs-12'>
+ <span className='pull-right footer-link copyright'>{'© 2015 Mattermost, Inc.'}</span>
+ <a
+ id='help_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.HelpLink}
+ >
+ <FormattedMessage id='web.footer.help'/>
+ </a>
+ <a
+ id='terms_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.TermsOfServiceLink}
+ >
+ <FormattedMessage id='web.footer.terms'/>
+ </a>
+ <a
+ id='privacy_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.PrivacyPolicyLink}
+ >
+ <FormattedMessage id='web.footer.privacy'/>
+ </a>
+ <a
+ id='about_link'
+ className='pull-right footer-link'
+ href={global.window.mm_config.AboutLink}
+ >
+ <FormattedMessage id='web.footer.about'/>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+NotLoggedIn.defaultProps = {
+};
+
+NotLoggedIn.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx
deleted file mode 100644
index 4c9bb6310..000000000
--- a/web/react/components/password_reset.jsx
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PasswordResetSendLink from './password_reset_send_link.jsx';
-import PasswordResetForm from './password_reset_form.jsx';
-
-export default class PasswordReset extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {};
- }
- render() {
- if (this.props.isReset === 'false') {
- return (
- <PasswordResetSendLink
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- />
- );
- }
-
- return (
- <PasswordResetForm
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- hash={this.props.hash}
- data={this.props.data}
- />
- );
- }
-}
-
-PasswordReset.defaultProps = {
- isReset: '',
- teamName: '',
- teamDisplayName: '',
- hash: '',
- data: ''
-};
-PasswordReset.propTypes = {
- isReset: React.PropTypes.string,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- hash: React.PropTypes.string,
- data: React.PropTypes.string
-};
diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx
index 380dbe973..cfd39e440 100644
--- a/web/react/components/password_reset_form.jsx
+++ b/web/react/components/password_reset_form.jsx
@@ -2,24 +2,11 @@
// See License.txt for license information.
import * as Client from '../utils/client.jsx';
+import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
-import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-const holders = defineMessages({
- error: {
- id: 'password_form.error',
- defaultMessage: 'Please enter at least {chars} characters.'
- },
- update: {
- id: 'password_form.update',
- defaultMessage: 'Your password has been updated successfully.'
- },
- pwd: {
- id: 'password_form.pwd',
- defaultMessage: 'Password'
- }
-});
+import {FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
class PasswordResetForm extends React.Component {
constructor(props) {
@@ -32,51 +19,50 @@ class PasswordResetForm extends React.Component {
handlePasswordReset(e) {
e.preventDefault();
- const {formatMessage} = this.props.intl;
- var state = {};
-
- var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
+ const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- state.error = formatMessage(holders.error, {chars: Constants.MIN_PASSWORD_LENGTH});
- this.setState(state);
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='password_form.error'
+ defaultMessage='Please enter at least {chars} characters.'
+ chars={Constants.MIN_PASSWORD_LENGTH}
+ />
+ )
+ });
return;
}
- state.error = null;
- this.setState(state);
+ this.setState({
+ error: null
+ });
- var data = {};
+ const data = {};
data.new_password = password;
- data.hash = this.props.hash;
- data.data = this.props.data;
- data.name = this.props.teamName;
+ data.hash = this.props.location.query.h;
+ data.data = this.props.location.query.d;
+ data.name = this.props.params.team;
Client.resetPassword(data,
- function resetSuccess() {
- this.setState({error: null, updateText: formatMessage(holders.update)});
- }.bind(this),
- function resetFailure(err) {
- this.setState({error: err.message, updateText: null});
- }.bind(this)
+ () => {
+ this.setState({error: null});
+ browserHistory.push('/' + this.props.params.team + '/login');
+ },
+ (err) => {
+ this.setState({error: err.message});
+ }
);
}
render() {
- var updateText = null;
- if (this.state.updateText) {
- updateText = (<div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText}
- <FormattedHTMLMessage
- id='password_form.click'
- defaultMessage='Click <a href={url}>here</a> to log in.'
- values={{
- url: '/' + this.props.teamName + '/login'
- }}
- />
- </label></div>);
- }
-
var error = null;
if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ error = (
+ <div className='form-group has-error'>
+ <label className='control-label'>
+ {this.state.error}
+ </label>
+ </div>
+ );
}
var formClass = 'form-group';
@@ -84,7 +70,6 @@ class PasswordResetForm extends React.Component {
formClass += ' has-error';
}
- const {formatMessage} = this.props.intl;
return (
<div className='col-sm-12'>
<div className='signup-team__container'>
@@ -98,9 +83,8 @@ class PasswordResetForm extends React.Component {
<p>
<FormattedMessage
id='password_form.enter'
- defaultMessage='Enter a new password for your {teamDisplayName} {siteName} account.'
+ defaultMessage='Enter a new password for your {siteName} account.'
values={{
- teamDisplayName: this.props.teamDisplayName,
siteName: global.window.mm_config.SiteName
}}
/>
@@ -111,7 +95,10 @@ class PasswordResetForm extends React.Component {
className='form-control'
name='password'
ref='password'
- placeholder={formatMessage(holders.pwd)}
+ placeholder={Utils.localizeMessage(
+ 'password_form.pwd',
+ 'Password'
+ )}
spellCheck='false'
/>
</div>
@@ -125,7 +112,6 @@ class PasswordResetForm extends React.Component {
defaultMessage='Change my password'
/>
</button>
- {updateText}
</form>
</div>
</div>
@@ -134,17 +120,10 @@ class PasswordResetForm extends React.Component {
}
PasswordResetForm.defaultProps = {
- teamName: '',
- teamDisplayName: '',
- hash: '',
- data: ''
};
PasswordResetForm.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string,
- hash: React.PropTypes.string,
- data: React.PropTypes.string
+ params: React.PropTypes.object.isRequired,
+ location: React.PropTypes.object.isRequired
};
-export default injectIntl(PasswordResetForm); \ No newline at end of file
+export default PasswordResetForm;
diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx
index 8cc8a050d..ce6253e16 100644
--- a/web/react/components/password_reset_send_link.jsx
+++ b/web/react/components/password_reset_send_link.jsx
@@ -4,26 +4,7 @@
import * as Utils from '../utils/utils.jsx';
import * as client from '../utils/client.jsx';
-import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
-
-const holders = defineMessages({
- error: {
- id: 'password_send.error',
- defaultMessage: 'Please enter a valid email address.'
- },
- link: {
- id: 'password_send.link',
- defaultMessage: '<p>A password reset link has been sent to <b>{email}</b> for your <b>{teamDisplayName}</b> team on {hostname}.</p>'
- },
- checkInbox: {
- id: 'password_send.checkInbox',
- defaultMessage: 'Please check your inbox.'
- },
- email: {
- id: 'password_send.email',
- defaultMessage: 'Email'
- }
-});
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
class PasswordResetSendLink extends React.Component {
constructor(props) {
@@ -31,48 +12,64 @@ class PasswordResetSendLink extends React.Component {
this.handleSendLink = this.handleSendLink.bind(this);
- this.state = {};
+ this.state = {
+ error: '',
+ updateText: ''
+ };
}
handleSendLink(e) {
e.preventDefault();
- var state = {};
- const {formatMessage} = this.props.intl;
var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !Utils.isEmail(email)) {
- state.error = formatMessage(holders.error);
- this.setState(state);
+ this.setState({
+ error: (
+ <FormattedMessage
+ id={'password_send.error'}
+ defaultMessage={'Please enter a valid email address.'}
+ />
+ )
+ });
return;
}
- state.error = null;
- this.setState(state);
+ // End of error checking clear error
+ this.setState({
+ error: ''
+ });
var data = {};
data.email = email;
- data.name = this.props.teamName;
-
+ data.name = this.props.params.team;
client.sendPasswordReset(data,
- function passwordResetSent() {
- this.setState({error: null, updateText: formatMessage(holders.link, {email: email, teamDisplayName: this.props.teamDisplayName, hostname: window.location.hostname}), moreUpdateText: formatMessage(holders.checkInbox)});
- $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
- }.bind(this),
- function passwordResetFailedToSend(err) {
- this.setState({error: err.message, update_text: null, moreUpdateText: null});
- }.bind(this)
- );
+ () => {
+ this.setState({
+ error: null,
+ updateText: (
+ <div className='reset-form alert alert-success'>
+ <FormattedHTMLMessage
+ id='password_send.link'
+ defaultMessage='<p>A password reset link has been sent to <b>{email}</b></p>'
+ email={email}
+ />
+ <FormattedMessage
+ id={'password_send.checkInbox'}
+ defaultMessage={'Please check your inbox.'}
+ />
+ </div>
+ )
+ });
+ $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
+ },
+ (err) => {
+ this.setState({
+ error: err.message,
+ update_text: null
+ });
+ }
+ );
}
render() {
- var updateText = null;
- if (this.state.updateText) {
- updateText = (
- <div className='reset-form alert alert-success'
- dangerouslySetInnerHTML={{__html: this.state.updateText + this.state.moreUpdateText}}
- >
- </div>
- );
- }
-
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
@@ -83,51 +80,60 @@ class PasswordResetSendLink extends React.Component {
formClass += ' has-error';
}
- const {formatMessage} = this.props.intl;
return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
<FormattedMessage
- id='password_send.title'
- defaultMessage='Password Reset'
+ id='web.header.back'
/>
- </h3>
- {updateText}
- <form
- onSubmit={this.handleSendLink}
- ref='reset_form'
- >
- <p>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
<FormattedMessage
- id='password_send.description'
- defaultMessage='To reset your password, enter the email address you used to sign up for {teamName}.'
- values={{
- teamName: this.props.teamDisplayName
- }}
+ id='password_send.title'
+ defaultMessage='Password Reset'
/>
- </p>
- <div className={formClass}>
- <input
- type='email'
- className='form-control'
- name='email'
- ref='email'
- placeholder={formatMessage(holders.email)}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
+ </h3>
+ {this.state.updateText}
+ <form
+ onSubmit={this.handleSendLink}
+ ref='reset_form'
>
- <FormattedMessage
- id='password_send.reset'
- defaultMessage='Reset my password'
- />
- </button>
- </form>
+ <p>
+ <FormattedMessage
+ id='password_send.description'
+ defaultMessage='To reset your password, enter the email address you used to sign up'
+ />
+ </p>
+ <div className={formClass}>
+ <input
+ type='email'
+ className='form-control'
+ name='email'
+ ref='email'
+ placeholder={Utils.localizeMessage(
+ 'password_send.email',
+ 'Email'
+ )}
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='password_send.reset'
+ defaultMessage='Reset my password'
+ />
+ </button>
+ </form>
+ </div>
</div>
</div>
);
@@ -135,13 +141,9 @@ class PasswordResetSendLink extends React.Component {
}
PasswordResetSendLink.defaultProps = {
- teamName: '',
- teamDisplayName: ''
};
PasswordResetSendLink.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ params: React.PropTypes.object.isRequired
};
-export default injectIntl(PasswordResetSendLink); \ No newline at end of file
+export default PasswordResetSendLink;
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index afff78bae..1943fb409 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -118,7 +118,7 @@ export default class PopoverListMembers extends React.Component {
className='profile-img rounded pull-left'
width='26px'
height='26px'
- src={`/api/v1/users/${m.id}/image?time=${m.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${m.id}/image?time=${m.update_at}`}
/>
<div className='pull-left'>
<div
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 57e919e45..3a855edf2 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -4,7 +4,6 @@
import PostHeader from './post_header.jsx';
import PostBody from './post_body.jsx';
-import UserStore from '../stores/user_store.jsx';
import PostStore from '../stores/post_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -128,7 +127,6 @@ export default class Post extends React.Component {
const post = this.props.post;
const parentPost = this.props.parentPost;
const posts = this.props.posts;
- const user = this.props.user || {};
if (!post.props) {
post.props = {};
@@ -156,13 +154,15 @@ export default class Post extends React.Component {
}
let currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
+ if (this.props.currentUser.id === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
currentUserCss = 'current--user';
}
- let timestamp = user.update_at;
- if (timestamp == null) {
- timestamp = UserStore.getCurrentUser().update_at;
+ let timestamp = 0;
+ if (!this.props.user || this.props.user.update_at == null) {
+ timestamp = this.props.currentUser.update_at;
+ } else {
+ timestamp = this.props.user.update_at;
}
let sameUserClass = '';
@@ -182,7 +182,7 @@ export default class Post extends React.Component {
let profilePic = null;
if (!this.props.hideProfilePic) {
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
@@ -218,6 +218,7 @@ export default class Post extends React.Component {
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
user={this.props.user}
+ currentUser={this.props.currentUser}
/>
<PostBody
post={post}
@@ -245,5 +246,7 @@ Post.propTypes = {
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool,
shouldHighlight: React.PropTypes.bool,
- displayNameType: React.PropTypes.string
+ displayNameType: React.PropTypes.string,
+ hasProfiles: React.PropTypes.bool,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 854cb095a..2fa4cebfe 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -80,12 +80,10 @@ class PostBody extends React.Component {
username = parentPost.props.override_username;
}
- if (global.window.mm_locale === 'en') {
- if (username.slice(-1) === 's') {
- apostrophe = '\'';
- } else {
- apostrophe = '\'s';
- }
+ if (username.slice(-1) === 's') {
+ apostrophe = '\'';
+ } else {
+ apostrophe = '\'s';
}
name = (
<a
diff --git a/web/react/components/post_focus_view.jsx b/web/react/components/post_focus_view.jsx
index 44a0bae09..fd654f502 100644
--- a/web/react/components/post_focus_view.jsx
+++ b/web/react/components/post_focus_view.jsx
@@ -5,7 +5,8 @@ import PostsView from './posts_view.jsx';
import PostStore from '../stores/post_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import UserStore from '../stores/user_store.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -15,6 +16,7 @@ export default class PostFocusView extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
@@ -26,18 +28,21 @@ export default class PostFocusView extends React.Component {
scrollPostId: focusedPostId,
postList: PostStore.getVisiblePosts(focusedPostId),
atTop: PostStore.getVisibilityAtTop(focusedPostId),
- atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
+ atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
+ currentUser: UserStore.getCurrentUser()
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChannelChange);
PostStore.addChangeListener(this.onPostsChange);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
PostStore.removeChangeListener(this.onPostsChange);
+ UserStore.removeChangeListener(this.onUserChange);
}
onChannelChange() {
@@ -46,6 +51,10 @@ export default class PostFocusView extends React.Component {
});
}
+ onUserChange() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
+ }
+
onPostsChange() {
const focusedPostId = PostStore.getFocusedPostId();
if (focusedPostId == null) {
@@ -65,11 +74,11 @@ export default class PostFocusView extends React.Component {
}
loadMorePostsTop() {
- EventHelpers.emitLoadMorePostsFocusedTopEvent();
+ GlobalActions.emitLoadMorePostsFocusedTopEvent();
}
loadMorePostsBottom() {
- EventHelpers.emitLoadMorePostsFocusedBottomEvent();
+ GlobalActions.emitLoadMorePostsFocusedBottomEvent();
}
getIntroMessage() {
@@ -89,6 +98,10 @@ export default class PostFocusView extends React.Component {
const postsToHighlight = {};
postsToHighlight[this.state.scrollPostId] = true;
+ if (!this.state.currentUser || !this.state.postList) {
+ return null;
+ }
+
return (
<div id='post-list'>
<PostsView
@@ -106,6 +119,7 @@ export default class PostFocusView extends React.Component {
messageSeparatorTime={0}
postsToHighlight={postsToHighlight}
profiles={this.props.profiles}
+ currentUser={this.state.currentUser}
/>
</div>
);
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index 2803fe387..966775dad 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -14,16 +14,15 @@ export default class PostHeader extends React.Component {
}
render() {
const post = this.props.post;
- const user = this.props.user;
- let userProfile = <UserProfile user={user}/>;
+ let userProfile = <UserProfile user={this.props.user}/>;
let botIndicator;
if (post.props && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = (
<UserProfile
- user={user}
+ user={this.props.user}
overwriteName={post.props.override_username}
disablePopover={true}
/>
@@ -54,6 +53,7 @@ export default class PostHeader extends React.Component {
allowReply='true'
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
+ currentUser={this.props.currentUser}
/>
</li>
</ul>
@@ -68,10 +68,11 @@ PostHeader.defaultProps = {
sameUser: false
};
PostHeader.propTypes = {
- post: React.PropTypes.object,
+ post: React.PropTypes.object.isRequired,
user: React.PropTypes.object,
- commentCount: React.PropTypes.number,
- isLastComment: React.PropTypes.bool,
- handleCommentClick: React.PropTypes.func,
- sameUser: React.PropTypes.bool
+ currentUser: React.PropTypes.object.isRequired,
+ commentCount: React.PropTypes.number.isRequired,
+ isLastComment: React.PropTypes.bool.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired,
+ sameUser: React.PropTypes.bool.isRequired
};
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index ffac6eaef..d0a4c828e 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -1,10 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import TimeSince from './time_since.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -27,8 +26,8 @@ export default class PostInfo extends React.Component {
}
createDropdown() {
var post = this.props.post;
- var isOwner = UserStore.getCurrentId() === post.user_id;
- var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles);
+ var isOwner = this.props.currentUser.id === post.user_id;
+ var isAdmin = Utils.isAdmin(this.props.currentUser.roles);
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || Utils.isPostEphemeral(post)) {
return '';
@@ -47,21 +46,21 @@ export default class PostInfo extends React.Component {
if (this.props.allowReply === 'true') {
dropdownContents.push(
- <li
- key='replyLink'
- role='presentation'
- >
- <a
- className='link__reply theme'
- href='#'
- onClick={this.props.handleCommentClick}
- >
- <FormattedMessage
- id='post_info.reply'
- defaultMessage='Reply'
- />
- </a>
- </li>
+ <li
+ key='replyLink'
+ role='presentation'
+ >
+ <a
+ className='link__reply theme'
+ href='#'
+ onClick={this.props.handleCommentClick}
+ >
+ <FormattedMessage
+ id='post_info.reply'
+ defaultMessage='Reply'
+ />
+ </a>
+ </li>
);
}
@@ -93,7 +92,7 @@ export default class PostInfo extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, dataComments)}
+ onClick={() => GlobalActions.showDeletePostModal(post, dataComments)}
>
<FormattedMessage
id='post_info.del'
@@ -157,11 +156,11 @@ export default class PostInfo extends React.Component {
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
removePost() {
- EventHelpers.emitRemovePost(this.props.post);
+ GlobalActions.emitRemovePost(this.props.post);
}
createRemovePostButton(post) {
if (!Utils.isPostEphemeral(post)) {
@@ -240,10 +239,11 @@ PostInfo.defaultProps = {
sameUser: false
};
PostInfo.propTypes = {
- post: React.PropTypes.object,
- commentCount: React.PropTypes.number,
- isLastComment: React.PropTypes.bool,
- allowReply: React.PropTypes.string,
- handleCommentClick: React.PropTypes.func,
- sameUser: React.PropTypes.bool
+ post: React.PropTypes.object.isRequired,
+ commentCount: React.PropTypes.number.isRequired,
+ isLastComment: React.PropTypes.bool.isRequired,
+ allowReply: React.PropTypes.string.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired,
+ sameUser: React.PropTypes.bool.isRequired,
+ currentUser: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index c2c739e9a..0a9232850 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -1,9 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import UserStore from '../stores/user_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as Utils from '../utils/utils.jsx';
import Post from './post.jsx';
import Constants from '../utils/constants.jsx';
@@ -144,7 +143,7 @@ export default class PostsView extends React.Component {
createPosts(posts, order) {
const postCtls = [];
let previousPostDay = new Date(0);
- const userId = UserStore.getCurrentId();
+ const userId = this.props.currentUser.id;
const profiles = this.props.profiles || {};
let renderedLastViewed = false;
@@ -230,8 +229,8 @@ export default class PostsView extends React.Component {
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
let profile;
- if (UserStore.getCurrentId() === post.user_id) {
- profile = UserStore.getCurrentUser();
+ if (this.props.currentUser.id === post.user_id) {
+ profile = this.props.currentUser;
} else {
profile = profiles[post.user_id];
}
@@ -248,9 +247,10 @@ export default class PostsView extends React.Component {
hideProfilePic={hideProfilePic}
isLastComment={isLastComment}
shouldHighlight={shouldHighlight}
- onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
+ onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
displayNameType={this.state.displayNameType}
user={profile}
+ currentUser={this.props.currentUser}
/>
);
@@ -525,7 +525,7 @@ PostsView.defaultProps = {
PostsView.propTypes = {
isActive: React.PropTypes.bool,
postList: React.PropTypes.object,
- profiles: React.PropTypes.object,
+ profiles: React.PropTypes.object.isRequired,
scrollPostId: React.PropTypes.string,
scrollType: React.PropTypes.number,
postViewScrolled: React.PropTypes.func.isRequired,
@@ -535,7 +535,8 @@ PostsView.propTypes = {
showMoreMessagesBottom: React.PropTypes.bool,
introText: React.PropTypes.element,
messageSeparatorTime: React.PropTypes.number,
- postsToHighlight: React.PropTypes.object
+ postsToHighlight: React.PropTypes.object,
+ currentUser: React.PropTypes.object.isRequired
};
function FloatingTimestamp({isScrolling, post}) {
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 976e03fab..b361779d2 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -6,9 +6,10 @@ import LoadingScreen from './loading_screen.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import PostStore from '../stores/post_store.jsx';
+import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -21,6 +22,7 @@ export default class PostsViewContainer extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onChannelLeave = this.onChannelLeave.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
@@ -28,7 +30,8 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = ChannelStore.getCurrentId();
const state = {
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
- scrollPost: null
+ scrollPost: null,
+ currentUser: UserStore.getCurrentUser()
};
if (currentChannelId) {
Object.assign(state, {
@@ -54,12 +57,17 @@ export default class PostsViewContainer extends React.Component {
ChannelStore.addLeaveListener(this.onChannelLeave);
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
ChannelStore.removeLeaveListener(this.onChannelLeave);
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.removeChangeListener(this.onUserChange);
+ }
+ onUserChange() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
}
handlePostsViewJumpRequest(type, post) {
switch (type) {
@@ -139,7 +147,7 @@ export default class PostsViewContainer extends React.Component {
return PostStore.getVisiblePosts(id);
}
loadMorePostsTop() {
- EventHelpers.emitLoadMorePostsEvent();
+ GlobalActions.emitLoadMorePostsEvent();
}
handlePostsViewScroll(atBottom) {
if (atBottom) {
@@ -165,6 +173,10 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = channels[this.state.currentChannelIndex];
const channel = ChannelStore.get(currentChannelId);
+ if (!this.state.currentUser || !channel) {
+ return null;
+ }
+
const postListCtls = [];
for (let i = 0; i < channels.length; i++) {
const isActive = (channels[i] === currentChannelId);
@@ -185,6 +197,7 @@ export default class PostsViewContainer extends React.Component {
introText={channel ? createChannelIntroMessage(channel) : null}
messageSeparatorTime={this.state.currentLastViewed}
profiles={this.props.profiles}
+ currentUser={this.state.currentUser}
/>
);
if (!postLists[i] && isActive) {
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 9588809eb..9183b761f 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -14,7 +14,7 @@ import * as AsyncClient from '../utils/async_client.jsx';
var ActionTypes = Constants.ActionTypes;
import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
@@ -70,7 +70,7 @@ class RhsComment extends React.Component {
}
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
componentDidMount() {
this.parseEmojis();
@@ -151,7 +151,7 @@ class RhsComment extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, 0)}
+ onClick={() => GlobalActions.showDeletePostModal(post, 0)}
>
<FormattedMessage
id='rhs_comment.del'
@@ -253,7 +253,7 @@ class RhsComment extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
height='36'
width='36'
/>
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index 023f3dd2d..fc1cd0b41 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -10,7 +10,7 @@ import * as Emoji from '../utils/emoticons.jsx';
import FileAttachmentList from './file_attachment_list.jsx';
import twemoji from 'twemoji';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
@@ -34,7 +34,7 @@ export default class RhsRootPost extends React.Component {
}
handlePermalink(e) {
e.preventDefault();
- EventHelpers.showGetPostLinkModal(this.props.post);
+ GlobalActions.showGetPostLinkModal(this.props.post);
}
componentDidMount() {
this.parseEmojis();
@@ -142,7 +142,7 @@ export default class RhsRootPost extends React.Component {
<a
href='#'
role='menuitem'
- onClick={() => EventHelpers.showDeletePostModal(post, this.props.commentCount)}
+ onClick={() => GlobalActions.showDeletePostModal(post, this.props.commentCount)}
>
<FormattedMessage
id='rhs_root.del'
@@ -211,7 +211,7 @@ export default class RhsRootPost extends React.Component {
);
}
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
diff --git a/web/react/components/root.jsx b/web/react/components/root.jsx
new file mode 100644
index 000000000..70038203b
--- /dev/null
+++ b/web/react/components/root.jsx
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import BrowserStore from '../stores/browser_store.jsx';
+import LocalizationStore from '../stores/localization_store.jsx';
+
+var IntlProvider = ReactIntl.IntlProvider;
+
+export default class Root extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ locale: 'en',
+ translations: null
+ };
+
+ this.localizationChanged = this.localizationChanged.bind(this);
+ }
+ localizationChanged() {
+ this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()});
+ }
+ componentWillMount() {
+ // Setup localization listener
+ LocalizationStore.addChangeListener(this.localizationChanged);
+
+ // Browser store check version
+ BrowserStore.checkVersion();
+
+ window.onerror = (msg, url, line, column, stack) => {
+ var l = {};
+ l.level = 'ERROR';
+ l.message = 'msg: ' + msg + ' row: ' + line + ' col: ' + column + ' stack: ' + stack + ' url: ' + url;
+
+ $.ajax({
+ url: '/api/v1/admin/log_client',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(l)
+ });
+
+ if (window.mm_config.EnableDeveloper === 'true') {
+ window.ErrorStore.storeLastError({message: 'DEVELOPER MODE: A javascript error has occured. Please use the javascript console to capture and report the error (row: ' + line + ' col: ' + column + ').'});
+ window.ErrorStore.emitChange();
+ }
+ };
+
+ // Ya....
+ /*eslint-disable */
+ if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") {
+ !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
+ analytics.load(window.mm_config.SegmentDeveloperKey);
+ analytics.page();
+ }}();
+ } else {
+ global.window.analytics = {};
+ global.window.analytics.page = function(){};
+ global.window.analytics.track = function(){};
+ }
+ /*eslint-enable */
+
+ // Get our localizaiton
+ GlobalActions.newLocalizationSelected('en');
+ }
+ componentWillUnmount() {
+ LocalizationStore.removeChangeListener(this.localizationChanged);
+ }
+ render() {
+ if (this.state.translations == null) {
+ return <div/>;
+ }
+
+ return (
+ <IntlProvider
+ locale={this.state.locale}
+ messages={this.state.translations}
+ key={this.state.locale}
+ >
+ {this.props.children}
+ </IntlProvider>
+ );
+ }
+}
+Root.defaultProps = {
+};
+
+Root.propTypes = {
+ children: React.PropTypes.object
+};
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 5ab864b7c..3a091bdd1 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -3,8 +3,7 @@
import UserStore from '../stores/user_store.jsx';
import UserProfile from './user_profile.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import * as TextFormatting from '../utils/text_formatting.jsx';
import Constants from '../utils/constants.jsx';
@@ -22,7 +21,7 @@ export default class SearchResultsItem extends React.Component {
handleClick(e) {
e.preventDefault();
- EventHelpers.emitPostFocusEvent(this.props.post.id);
+ GlobalActions.emitPostFocusEvent(this.props.post.id);
if ($(window).width() < 768) {
$('.sidebar--right').removeClass('move--left');
@@ -32,7 +31,7 @@ export default class SearchResultsItem extends React.Component {
handleFocusRHSClick(e) {
e.preventDefault();
- EventHelpers.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
+ GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
}
render() {
@@ -78,7 +77,7 @@ export default class SearchResultsItem extends React.Component {
<div className='post__content'>
<div className='post__img'>
<img
- src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex()}
+ src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp}
height='36'
width='36'
/>
diff --git a/web/react/components/should_verify_email.jsx b/web/react/components/should_verify_email.jsx
new file mode 100644
index 000000000..c473fe366
--- /dev/null
+++ b/web/react/components/should_verify_email.jsx
@@ -0,0 +1,111 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+import * as Client from '../utils/client.jsx';
+
+export default class ShouldVerifyEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleResend = this.handleResend.bind(this);
+
+ this.state = {
+ resendStatus: 'none'
+ };
+ }
+ handleResend() {
+ const teamName = this.props.location.query.teamname;
+ const email = this.props.location.query.email;
+
+ this.setState({resendStatus: 'sending'});
+
+ Client.resendVerification(() => {
+ this.setState({resendStatus: 'success'});
+ },
+ () => {
+ this.setState({resendStatus: 'failure'});
+ },
+ teamName,
+ email);
+ }
+ render() {
+ let resendConfirm = '';
+ if (this.state.resendStatus === 'success') {
+ resendConfirm = (
+ <div>
+ <br/>
+ <p className='alert alert-success'>
+ <i className='fa fa-check'/>
+ <FormattedMessage
+ id='email_verify.sent'
+ defaultMessage=' Verification email sent.'
+ />
+ </p>
+ </div>
+ );
+ }
+
+ if (this.state.resendStatus === 'failure') {
+ resendConfirm = (
+ <div>
+ <br/>
+ <p className='alert alert-danger'>
+ <i className='fa fa-times'/>
+ <FormattedMessage id='email_verify.failed'/>
+ </p>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='email_verify.almost'
+ defaultMessage='{siteName}: You are almost done'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h3>
+ <div>
+ <p>
+ <FormattedMessage
+ id='email_verify.notVerifiedBody'
+ defaultMessage='Please verify your email address. Check your inbox for an email.'
+ />
+ </p>
+ <button
+ onClick={this.handleResend}
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='email_verify.resend'
+ defaultMessage='Resend Email'
+ />
+ </button>
+ {resendConfirm}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+ShouldVerifyEmail.defaultProps = {
+};
+ShouldVerifyEmail.propTypes = {
+ location: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index c7dba306b..5c682d64b 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -129,7 +129,9 @@ export default class Sidebar extends React.Component {
directChannels,
hiddenDirectChannelCount,
unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
- showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER
+ showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
+ currentTeam: TeamStore.getCurrent(),
+ currentUser: UserStore.getCurrentUser()
};
}
@@ -179,7 +181,7 @@ export default class Sidebar extends React.Component {
}
updateTitle() {
const channel = ChannelStore.getCurrent();
- if (channel) {
+ if (channel && this.state.currentTeam) {
let currentSiteName = '';
if (global.window.mm_config.SiteName != null) {
currentSiteName = global.window.mm_config.SiteName;
@@ -196,7 +198,7 @@ export default class Sidebar extends React.Component {
const unread = this.getTotalUnreadCount();
const mentionTitle = unread.mentions > 0 ? '(' + unread.mentions + ') ' : '';
const unreadTitle = unread.msgs > 0 ? '* ' : '';
- document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + TeamStore.getCurrent().display_name + ' ' + currentSiteName;
+ document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + this.state.currentTeam.display_name + ' ' + currentSiteName;
}
}
onScroll() {
@@ -401,7 +403,6 @@ export default class Sidebar extends React.Component {
// set up click handler to switch channels (or create a new channel for non-existant ones)
var handleClick = null;
var href = '#';
- var teamURL = TeamStore.getCurrentTeamUrl();
if (!channel.fake) {
handleClick = function clickHandler(e) {
@@ -413,7 +414,7 @@ export default class Sidebar extends React.Component {
e.preventDefault();
};
- } else if (channel.fake && teamURL) {
+ } else if (channel.fake) {
// It's a direct message channel that doesn't exist yet so let's create it now
var otherUserId = Utils.getUserIdFromChannelName(channel);
@@ -434,7 +435,7 @@ export default class Sidebar extends React.Component {
},
() => {
this.setState({loadingDMChannel: -1});
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ window.location.href = '/' + this.state.currentTeam.name;
}
);
}
@@ -497,6 +498,11 @@ export default class Sidebar extends React.Component {
);
}
render() {
+ // Check if we have all info needed to render
+ if (this.state.currentTeam == null || this.state.currentUser == null) {
+ return (<div/>);
+ }
+
this.badgesActive = false;
// keep track of the first and last unread channels so we can use them to set the unread indicators
@@ -586,7 +592,10 @@ export default class Sidebar extends React.Component {
);
return (
- <div>
+ <div
+ className='sidebar--left'
+ id='sidebar-left'
+ >
<NewChannelFlow
show={showChannelModal}
channelType={this.state.newChannelModalType}
@@ -598,9 +607,10 @@ export default class Sidebar extends React.Component {
/>
<SidebarHeader
- teamDisplayName={TeamStore.getCurrent().display_name}
- teamName={TeamStore.getCurrent().name}
- teamType={TeamStore.getCurrent().type}
+ teamDisplayName={this.state.currentTeam.display_name}
+ teamName={this.state.currentTeam.name}
+ teamType={this.state.currentTeam.type}
+ currentUser={this.state.currentUser}
/>
<UnreadChannelIndicator
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 45b0a5fc4..00d30948a 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -4,10 +4,8 @@
import NavbarDropdown from './navbar_dropdown.jsx';
import TutorialTip from './tutorial/tutorial_tip.jsx';
-import UserStore from '../stores/user_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
-import * as Utils from '../utils/utils.jsx';
import Constants from '../utils/constants.jsx';
import {FormattedHTMLMessage} from 'mm-intl';
@@ -34,7 +32,7 @@ export default class SidebarHeader extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
getStateFromStores() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
+ const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, this.props.currentUser.id, 999);
return {showTutorialTip: tutorialStep === TutorialSteps.MENU_POPOVER};
}
@@ -77,7 +75,7 @@ export default class SidebarHeader extends React.Component {
);
}
render() {
- var me = UserStore.getCurrentUser();
+ var me = this.props.currentUser;
var profilePicture = null;
if (!me) {
@@ -88,7 +86,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
/>
);
}
@@ -124,6 +122,7 @@ export default class SidebarHeader extends React.Component {
teamType={this.props.teamType}
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
+ currentUser={this.props.currentUser}
/>
</div>
);
@@ -131,11 +130,12 @@ export default class SidebarHeader extends React.Component {
}
SidebarHeader.defaultProps = {
- teamDisplayName: global.window.mm_config.SiteName,
+ teamDisplayName: '',
teamType: ''
};
SidebarHeader.propTypes = {
teamDisplayName: React.PropTypes.string,
teamName: React.PropTypes.string,
- teamType: React.PropTypes.string
+ teamType: React.PropTypes.string,
+ currentUser: React.PropTypes.object
};
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index b81c0d099..14853d3a3 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -127,8 +127,13 @@ export default class SidebarRight extends React.Component {
}
return (
- <div className='sidebar-right-container'>
- {content}
+ <div
+ className='sidebar--right'
+ id='sidebar-right'
+ >
+ <div className='sidebar-right-container'>
+ {content}
+ </div>
</div>
);
}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 4d714e9f1..c7c5bcfd6 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -5,11 +5,11 @@ import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import UserStore from '../stores/user_store.jsx';
-import * as client from '../utils/client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-import * as utils from '../utils/utils.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import * as Utils from '../utils/utils.jsx';
import {FormattedMessage} from 'mm-intl';
+import {Link} from 'react-router';
export default class SidebarRightMenu extends React.Component {
componentDidMount() {
@@ -19,18 +19,11 @@ export default class SidebarRightMenu extends React.Component {
constructor(props) {
super(props);
- this.handleLogoutClick = this.handleLogoutClick.bind(this);
-
this.state = {
showUserSettingsModal: false
};
}
- handleLogoutClick(e) {
- e.preventDefault();
- client.logout();
- }
-
render() {
var teamLink = '';
var inviteLink = '';
@@ -42,14 +35,14 @@ export default class SidebarRightMenu extends React.Component {
var isSystemAdmin = false;
if (currentUser != null) {
- isAdmin = utils.isAdmin(currentUser.roles);
- isSystemAdmin = utils.isSystemAdmin(currentUser.roles);
+ isAdmin = Utils.isAdmin(currentUser.roles);
+ isSystemAdmin = Utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
<a
href='#'
- onClick={EventHelpers.showInviteMemberModal}
+ onClick={GlobalActions.showInviteMemberModal}
>
<i className='fa fa-user'></i>
<FormattedMessage
@@ -65,7 +58,7 @@ export default class SidebarRightMenu extends React.Component {
<li>
<a
href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
>
<i className='glyphicon glyphicon-link'></i>
<FormattedMessage
@@ -107,13 +100,13 @@ export default class SidebarRightMenu extends React.Component {
);
}
- if (isSystemAdmin && !utils.isMobile()) {
+ if (isSystemAdmin && !Utils.isMobile()) {
consoleLink = (
<li>
<a
- href={'/admin_console?' + utils.getSessionIndex()}
+ href={'/admin_console'}
>
- <i className='fa fa-wrench'></i>
+ <i className='fa fa-wrench'></i>
<FormattedMessage
id='sidebar_right_menu.console'
defaultMessage='System Console'
@@ -168,7 +161,10 @@ export default class SidebarRightMenu extends React.Component {
);
}
return (
- <div>
+ <div
+ className='sidebar--menu'
+ id='sidebar-menu'
+ >
<div className='team__header theme'>
<a
className='team__name'
@@ -196,16 +192,13 @@ export default class SidebarRightMenu extends React.Component {
{manageLink}
{consoleLink}
<li>
- <a
- href='#'
- onClick={this.handleLogoutClick}
- >
+ <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}>
<i className='fa fa-sign-out'></i>
<FormattedMessage
id='sidebar_right_menu.logout'
defaultMessage='Logout'
/>
- </a>
+ </Link>
</li>
<li className='divider'></li>
{helpLink}
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 26c46dad0..2adf8d111 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -6,6 +6,8 @@ import EmailSignUpPage from './team_signup_with_email.jsx';
import SSOSignupPage from './team_signup_with_sso.jsx';
import LdapSignUpPage from './team_signup_with_ldap.jsx';
import Constants from '../utils/constants.jsx';
+import TeamStore from '../stores/team_store.jsx';
+import * as AsyncClient from '../utils/async_client.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -14,6 +16,7 @@ export default class TeamSignUp extends React.Component {
super(props);
this.updatePage = this.updatePage.bind(this);
+ this.onTeamUpdate = this.onTeamUpdate.bind(this);
var count = 0;
@@ -46,11 +49,34 @@ export default class TeamSignUp extends React.Component {
this.setState({page});
}
+ componentWillMount() {
+ if (global.window.mm_config.EnableTeamListing === 'true') {
+ AsyncClient.getAllTeams();
+ this.onTeamUpdate();
+ }
+ }
+
+ componentDidMount() {
+ TeamStore.addChangeListener(this.onTeamUpdate);
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.onTeamUpdate);
+ }
+
+ onTeamUpdate() {
+ this.setState({
+ teams: TeamStore.getAll()
+ });
+ }
+
render() {
- var teamListing = null;
+ let teamListing = null;
if (global.window.mm_config.EnableTeamListing === 'true') {
- if (this.props.teams.length === 0) {
+ if (this.state.teams == null) {
+ teamListing = (<div/>);
+ } else if (this.state.teams.length === 0) {
if (global.window.mm_config.EnableTeamCreation !== 'true') {
teamListing = (
<div>
@@ -72,23 +98,26 @@ export default class TeamSignUp extends React.Component {
</h4>
<div className='signup-team-all'>
{
- this.props.teams.map((team) => {
- return (
- <div
- key={'team_' + team.name}
- className='signup-team-dir'
- >
- <a
- href={'/' + team.name}
+ Object.values(this.state.teams).map((team) => {
+ if (team.allow_team_listing) {
+ return (
+ <div
+ key={'team_' + team.name}
+ className='signup-team-dir'
>
- <span className='signup-team-dir__name'>{team.display_name}</span>
- <span
- className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
- aria-hidden='true'
- />
- </a>
- </div>
- );
+ <a
+ href={'/' + team.name}
+ >
+ <span className='signup-team-dir__name'>{team.display_name}</span>
+ <span
+ className='glyphicon glyphicon-menu-right right signup-team-dir__arrow'
+ aria-hidden='true'
+ />
+ </a>
+ </div>
+ );
+ }
+ return null;
})
}
</div>
@@ -103,42 +132,26 @@ export default class TeamSignUp extends React.Component {
}
}
+ let signupMethod = null;
+
if (global.window.mm_config.EnableTeamCreation !== 'true') {
if (teamListing == null) {
- return (
- <div>
- <FormattedMessage
- id='signup_team.disabled'
- defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
- />
- </div>
+ signupMethod = (
+ <FormattedMessage
+ id='signup_team.disabled'
+ defaultMessage='Team creation has been disabled. Please contact an administrator for access.'
+ />
);
}
-
- return (
- <div>
- {teamListing}
- </div>
- );
- }
-
- if (this.state.page === 'choose') {
- return (
- <div>
- {teamListing}
- <ChoosePage
- updatePage={this.updatePage}
- />
- </div>
+ } else if (this.state.page === 'choose') {
+ signupMethod = (
+ <ChoosePage
+ updatePage={this.updatePage}
+ />
);
- }
-
- if (this.state.page === 'email') {
- return (
- <div>
- {teamListing}
- <EmailSignUpPage/>
- </div>
+ } else if (this.state.page === 'email') {
+ signupMethod = (
+ <EmailSignUpPage/>
);
} else if (this.state.page === 'ldap') {
return (
@@ -148,35 +161,45 @@ export default class TeamSignUp extends React.Component {
</div>
);
} else if (this.state.page === 'gitlab') {
- return (
- <div>
- {teamListing}
- <SSOSignupPage service={Constants.GITLAB_SERVICE}/>
- </div>
+ signupMethod = (
+ <SSOSignupPage service={Constants.GITLAB_SERVICE}/>
);
} else if (this.state.page === 'google') {
- return (
- <div>
- {teamListing}
- <SSOSignupPage service={Constants.GOOGLE_SERVICE}/>
- </div>
+ signupMethod = (
+ <SSOSignupPage service={Constants.GOOGLE_SERVICE}/>
);
} else if (this.state.page === 'none') {
- return (
- <div>
- <FormattedMessage
- id='signup_team.none'
- defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
- />
- </div>
+ signupMethod = (
+ <FormattedMessage
+ id='signup_team.none'
+ defaultMessage='No team creation method has been enabled. Please contact an administrator for access.'
+ />
);
}
- return null;
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h1>{global.window.mm_config.SiteName}</h1>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='web.root.singup_info'
+ />
+ </h4>
+ <div id='signup-team'>
+ {teamListing}
+ {signupMethod}
+ </div>
+ </div>
+ </div>
+ );
}
}
TeamSignUp.propTypes = {
- teams: React.PropTypes.array
};
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
deleted file mode 100644
index 16553daeb..000000000
--- a/web/react/components/signup_team_complete.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import WelcomePage from './team_signup_welcome_page.jsx';
-import TeamDisplayNamePage from './team_signup_display_name_page.jsx';
-import TeamURLPage from './team_signup_url_page.jsx';
-import SendInivtesPage from './team_signup_send_invites_page.jsx';
-import UsernamePage from './team_signup_username_page.jsx';
-import PasswordPage from './team_signup_password_page.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
-
-import {FormattedMessage} from 'mm-intl';
-
-export default class SignupTeamComplete extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateParent = this.updateParent.bind(this);
-
- var initialState = BrowserStore.getGlobalItem(props.hash);
-
- if (!initialState) {
- initialState = {};
- initialState.wizard = 'welcome';
- initialState.team = {};
- initialState.team.email = this.props.email;
- initialState.team.allowed_domains = '';
- initialState.invites = [];
- initialState.invites.push('');
- initialState.invites.push('');
- initialState.invites.push('');
- initialState.user = {};
- initialState.hash = this.props.hash;
- initialState.data = this.props.data;
- }
-
- this.state = initialState;
- }
- updateParent(state, skipSet) {
- BrowserStore.setGlobalItem(this.props.hash, state);
-
- if (!skipSet) {
- this.setState(state);
- }
- }
- render() {
- if (this.state.wizard === 'welcome') {
- return (
- <WelcomePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'team_display_name') {
- return (
- <TeamDisplayNamePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'team_url') {
- return (
- <TeamURLPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'send_invites') {
- return (
- <SendInivtesPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'username') {
- return (
- <UsernamePage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- if (this.state.wizard === 'password') {
- return (
- <PasswordPage
- state={this.state}
- updateParent={this.updateParent}
- />
- );
- }
-
- return (
- <div>
- <FormattedMessage
- id='signup_team_complete.completed'
- defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
- />
- </div>
- );
- }
-}
-
-SignupTeamComplete.defaultProps = {
- hash: '',
- email: '',
- data: ''
-};
-SignupTeamComplete.propTypes = {
- hash: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string
-};
diff --git a/web/react/components/signup_team_complete/components/signup_team_complete.jsx b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
new file mode 100644
index 000000000..5ad21e941
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import BrowserStore from '../../../stores/browser_store.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
+import {browserHistory} from 'react-router';
+
+export default class SignupTeamComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateParent = this.updateParent.bind(this);
+ }
+ componentWillMount() {
+ const data = JSON.parse(this.props.location.query.d);
+ this.hash = this.props.location.query.h;
+
+ var initialState = BrowserStore.getGlobalItem(this.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.team = {};
+ initialState.team.email = data.email;
+ initialState.team.allowed_domains = '';
+ initialState.invites = [];
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.user = {};
+ initialState.hash = this.hash;
+ initialState.data = this.props.location.query.d;
+ }
+
+ this.setState(initialState);
+ }
+ componentDidMount() {
+ browserHistory.push('/signup_team_complete/welcome');
+ }
+ updateParent(state, skipSet) {
+ BrowserStore.setGlobalItem(this.hash, state);
+
+ if (!skipSet) {
+ this.setState(state);
+ browserHistory.push('/signup_team_complete/' + state.wizard);
+ }
+ }
+ render() {
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span classNameName='fa fa-chevron-left'/>
+ <FormattedMessage id='web.header.back'/>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <div id='signup-team-complete'>
+ {React.cloneElement(this.props.children, {
+ state: this.state,
+ updateParent: this.updateParent
+ })}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+SignupTeamComplete.defaultProps = {
+};
+SignupTeamComplete.propTypes = {
+ location: React.PropTypes.object,
+ children: React.PropTypes.node
+};
diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
index f07b50756..280e53ce4 100644
--- a/web/react/components/team_signup_display_name_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
@@ -1,8 +1,8 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
+import * as utils from '../../../utils/utils.jsx';
+import * as client from '../../../utils/client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
@@ -133,4 +133,4 @@ TeamSignupDisplayNamePage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupDisplayNamePage); \ No newline at end of file
+export default injectIntl(TeamSignupDisplayNamePage);
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
index 790ec2e5d..c87d6ec07 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
+import * as Utils from '../../../utils/utils.jsx';
import {intlShape, injectIntl, defineMessages} from 'mm-intl';
diff --git a/web/react/components/signup_team_complete/components/team_signup_finished.jsx b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
new file mode 100644
index 000000000..fc5f756e7
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+
+export default class FinishedPage extends React.Component {
+ render() {
+ return (
+ <FormattedMessage
+ id='signup_team_complete.completed'
+ defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
+ />
+ );
+ }
+}
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
index 06c04854f..490a11040 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
@@ -1,12 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Client from '../utils/client.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
-import UserStore from '../stores/user_store.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
+import UserStore from '../../../stores/user_store.jsx';
+import Constants from '../../../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
const holders = defineMessages({
passwordError: {
@@ -66,11 +67,11 @@ class TeamSignupPasswordPage extends React.Component {
props.state.wizard = 'finished';
props.updateParent(props.state, true);
- window.location.href = '/' + teamSignup.team.name + '/channels/town-square';
+ browserHistory.push('/' + teamSignup.team.name + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
- window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name);
+ browserHistory.push('/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name));
} else {
this.setState({serverError: err.message});
$('#finish-button').button('reset');
@@ -211,4 +212,4 @@ TeamSignupPasswordPage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupPasswordPage); \ No newline at end of file
+export default injectIntl(TeamSignupPasswordPage);
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
index 55cfe5114..5e987ef2c 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import EmailItem from './team_signup_email_item.jsx';
-import * as Client from '../utils/client.jsx';
+import * as Client from '../../../utils/client.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
index 2f6c3df49..ec50e2d25 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
@@ -202,4 +202,4 @@ TeamSignupUrlPage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupUrlPage); \ No newline at end of file
+export default injectIntl(TeamSignupUrlPage);
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
index 0fa9cb103..e56aa4cd7 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import Constants from '../utils/constants.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -161,4 +161,4 @@ TeamSignupUsernamePage.propTypes = {
updateParent: React.PropTypes.func
};
-export default injectIntl(TeamSignupUsernamePage); \ No newline at end of file
+export default injectIntl(TeamSignupUsernamePage);
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
index 9939c3ffd..97782e54a 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
@@ -1,12 +1,14 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
+
const holders = defineMessages({
storageError: {
id: 'team_signup_welcome.storageError',
@@ -73,7 +75,7 @@ class TeamSignupWelcomePage extends React.Component {
} else {
this.props.state.wizard = 'finished';
this.props.updateParent(this.props.state);
- window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(email);
+ browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email));
}
}.bind(this),
function error(err) {
@@ -229,4 +231,4 @@ TeamSignupWelcomePage.propTypes = {
state: React.PropTypes.object
};
-export default injectIntl(TeamSignupWelcomePage); \ No newline at end of file
+export default injectIntl(TeamSignupWelcomePage);
diff --git a/web/react/components/signup_team_confirm.jsx b/web/react/components/signup_team_confirm.jsx
index 290d8e503..1afbb3d30 100644
--- a/web/react/components/signup_team_confirm.jsx
+++ b/web/react/components/signup_team_confirm.jsx
@@ -6,30 +6,41 @@ import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
export default class SignupTeamConfirm extends React.Component {
render() {
return (
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='signup_team_confirm.title'
- defaultMessage='Sign up Complete'
- />
- </h3>
- <p>
- <FormattedHTMLMessage
- id='signup_team_confirm.checkEmail'
- defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
- values={{
- email: this.props.email
- }}
- />
- </p>
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.back'
+ />
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div classNameName='signup-team__container'>
+ <h3>
+ <FormattedMessage
+ id='signup_team_confirm.title'
+ defaultMessage='Sign up Complete'
+ />
+ </h3>
+ <p>
+ <FormattedHTMLMessage
+ id='signup_team_confirm.checkEmail'
+ defaultMessage='Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team'
+ values={{
+ email: this.props.location.query.email
+ }}
+ />
+ </p>
+ </div>
+ </div>
</div>
);
}
}
SignupTeamConfirm.defaultProps = {
- email: ''
};
SignupTeamConfirm.propTypes = {
- email: React.PropTypes.string
+ location: React.PropTypes.object
};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index dbec3d02d..d2128a50f 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -2,83 +2,130 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import * as client from '../utils/client.jsx';
+import * as Client from '../utils/client.jsx';
import UserStore from '../stores/user_store.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import Constants from '../utils/constants.jsx';
+import LoadingScreen from '../components/loading_screen.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
-
-const holders = defineMessages({
- required: {
- id: 'signup_user_completed.required',
- defaultMessage: 'This field is required'
- },
- validEmail: {
- id: 'signup_user_completed.validEmail',
- defaultMessage: 'Please enter a valid email address'
- },
- reserved: {
- id: 'signup_user_completed.reserved',
- defaultMessage: 'This username is reserved, please choose a new one.'
- },
- usernameLength: {
- id: 'signup_user_completed.usernameLength',
- defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\' and \'_\'.'
- },
- passwordLength: {
- id: 'signup_user_completed.passwordLength',
- defaultMessage: 'Please enter at least {min} characters'
- }
-});
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
class SignupUserComplete extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.inviteInfoRecieved = this.inviteInfoRecieved.bind(this);
+
+ this.state = {
+ data: '',
+ hash: '',
+ usedBefore: false,
+ email: '',
+ teamDisplayName: '',
+ teamName: '',
+ teamId: ''
+ };
+ }
+ componentWillMount() {
+ let data = this.props.location.query.d;
+ let hash = this.props.location.query.h;
+ const inviteId = this.props.location.query.id;
+ let usedBefore = false;
+ let email = '';
+ let teamDisplayName = '';
+ let teamName = '';
+ let teamId = '';
+
+ // If we have a hash in the url then we are attempting to access a private team
+ if (hash) {
+ const parsedData = JSON.parse(data);
+ usedBefore = BrowserStore.getGlobalItem(hash);
+ email = parsedData.email;
+ teamDisplayName = parsedData.display_name;
+ teamName = parsedData.name;
+ teamId = parsedData.id;
+ } else {
+ Client.getInviteInfo(this.inviteInfoRecieved, null, inviteId);
+ data = '';
+ hash = '';
+ }
- var initialState = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!initialState) {
- initialState = {};
- initialState.wizard = 'welcome';
- initialState.user = {};
- initialState.user.team_id = this.props.teamId;
- initialState.user.email = this.props.email;
- initialState.original_email = this.props.email;
+ this.setState({
+ data,
+ hash,
+ usedBefore,
+ email,
+ teamDisplayName,
+ teamName,
+ teamId
+ });
+ }
+ inviteInfoRecieved(data) {
+ if (!data) {
+ return;
}
- this.state = initialState;
+ this.setState({
+ teamDisplayName: data.display_name,
+ teamName: data.name,
+ teamId: data.id
+ });
}
handleSubmit(e) {
e.preventDefault();
- const {formatMessage} = this.props.intl;
const providedEmail = ReactDOM.findDOMNode(this.refs.email).value.trim();
if (!providedEmail) {
- this.setState({nameError: '', emailError: formatMessage(holders.required), passwordError: ''});
+ this.setState({
+ nameError: '',
+ emailError: (<FormattedMessage id='signup_user_completed.required'/>),
+ passwordError: '',
+ serverError: ''
+ });
return;
}
if (!Utils.isEmail(providedEmail)) {
- this.setState({nameError: '', emailError: formatMessage(holders.validEmail), passwordError: ''});
+ this.setState({
+ nameError: '',
+ emailError: (<FormattedMessage id='signup_user_completed.validEmail'/>),
+ passwordError: '',
+ serverError: ''
+ });
return;
}
const providedUsername = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!providedUsername) {
- this.setState({nameError: formatMessage(holders.required), emailError: '', passwordError: '', serverError: ''});
+ this.setState({
+ nameError: (<FormattedMessage id='signup_user_completed.required'/>),
+ emailError: '',
+ passwordError: '',
+ serverError: ''
+ });
return;
}
const usernameError = Utils.isValidUsername(providedUsername);
if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({nameError: formatMessage(holders.reserved), emailError: '', passwordError: '', serverError: ''});
+ this.setState({
+ nameError: (<FormattedMessage id='signup_user_completed.reserved'/>),
+ emailError: '',
+ passwordError: '',
+ serverError: ''
+ });
return;
} else if (usernameError) {
this.setState({
- nameError: formatMessage(holders.usernameLength, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}),
+ nameError: (
+ <FormattedMessage
+ id='signup_user_completed.usernameLength'
+ min={Constants.MIN_USERNAME_LENGTH}
+ max={Constants.MAX_USERNAME_LENGTH}
+ />
+ ),
emailError: '',
passwordError: '',
serverError: ''
@@ -88,41 +135,50 @@ class SignupUserComplete extends React.Component {
const providedPassword = ReactDOM.findDOMNode(this.refs.password).value.trim();
if (!providedPassword || providedPassword.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({nameError: '', emailError: '', passwordError: formatMessage(holders.passwordLength, {min: Constants.MIN_PASSWORD_LENGTH}), serverError: ''});
+ this.setState({
+ nameError: '',
+ emailError: '',
+ passwordError: (
+ <FormattedMessage
+ id='signup_user_completed.passwordLength'
+ min={Constants.MIN_PASSWORD_LENGTH}
+ />
+ ),
+ serverError: ''
+ });
return;
}
- const user = {
- team_id: this.props.teamId,
- email: providedEmail,
- username: providedUsername,
- password: providedPassword,
- allow_marketing: true
- };
-
this.setState({
- user,
nameError: '',
emailError: '',
passwordError: '',
serverError: ''
});
- client.createUser(user, this.props.data, this.props.hash,
+ const user = {
+ team_id: this.state.teamId,
+ email: providedEmail,
+ username: providedUsername,
+ password: providedPassword,
+ allow_marketing: true
+ };
+
+ Client.createUser(user, this.state.data, this.state.hash,
() => {
- client.track('signup', 'signup_user_02_complete');
+ Client.track('signup', 'signup_user_02_complete');
- client.loginByEmail(this.props.teamName, user.email, user.password,
+ Client.loginByEmail(this.state.teamName, user.email, user.password,
() => {
UserStore.setLastEmail(user.email);
- if (this.props.hash > 0) {
- BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
+ if (this.state.hash > 0) {
+ BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
}
- window.location.href = '/' + this.props.teamName + '/channels/town-square';
+ browserHistory.push('/' + this.state.teamName + '/channels/town-square');
},
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
- window.location.href = '/verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
+ browserHistory.push('/should_verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.state.teamName));
} else {
this.setState({serverError: err.message});
}
@@ -135,9 +191,10 @@ class SignupUserComplete extends React.Component {
);
}
render() {
- client.track('signup', 'signup_user_01_welcome');
+ Client.track('signup', 'signup_user_01_welcome');
- if (this.state.wizard === 'finished') {
+ // If we have been used then just display a message
+ if (this.state.usedBefore) {
return (
<div>
<FormattedMessage
@@ -148,6 +205,12 @@ class SignupUserComplete extends React.Component {
);
}
+ // If we haven't got a team id yet we are waiting for
+ // the client so just show the standard loading screen
+ if (this.state.teamId === '') {
+ return (<LoadingScreen/>);
+ }
+
// set up error labels
var emailError = null;
var emailHelpText = (
@@ -160,7 +223,7 @@ class SignupUserComplete extends React.Component {
);
var emailDivStyle = 'form-group';
if (this.state.emailError) {
- emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailError = (<label className='control-label'>{this.state.emailError}</label>);
emailHelpText = '';
emailDivStyle += ' has-error';
}
@@ -203,13 +266,13 @@ class SignupUserComplete extends React.Component {
// set up the email entry and hide it if an email was provided
var yourEmailIs = '';
- if (this.state.user.email) {
+ if (this.state.email) {
yourEmailIs = (
<FormattedHTMLMessage
id='signup_user_completed.emailIs'
defaultMessage="Your email address is <strong>{email}</strong>. You'll use this address to sign in to {siteName}."
values={{
- email: this.state.user.email,
+ email: this.state.email,
siteName: global.window.mm_config.SiteName
}}
/>
@@ -217,7 +280,7 @@ class SignupUserComplete extends React.Component {
}
var emailContainerStyle = 'margin--extra';
- if (this.state.original_email) {
+ if (this.state.email) {
emailContainerStyle = 'hidden';
}
@@ -234,7 +297,7 @@ class SignupUserComplete extends React.Component {
type='email'
ref='email'
className='form-control'
- defaultValue={this.state.user.email}
+ defaultValue={this.state.email}
placeholder=''
maxLength='128'
autoFocus={true}
@@ -249,20 +312,20 @@ class SignupUserComplete extends React.Component {
var signupMessage = [];
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
signupMessage.push(
- <a
- className='btn btn-custom-login gitlab'
- key='gitlab'
- href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
- >
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='signup_user_completed.gitlab'
- defaultMessage='with GitLab'
- />
- </span>
- </a>
- );
+ <a
+ className='btn btn-custom-login gitlab'
+ key='gitlab'
+ href={'/api/v1/oauth/gitlab/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
+ >
+ <span className='icon'/>
+ <span>
+ <FormattedMessage
+ id='signup_user_completed.gitlab'
+ defaultMessage='with GitLab'
+ />
+ </span>
+ </a>
+ );
}
if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
@@ -270,7 +333,7 @@ class SignupUserComplete extends React.Component {
<a
className='btn btn-custom-login google'
key='google'
- href={'/' + this.props.teamName + '/signup/google' + window.location.search}
+ href={'/api/v1/oauth/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)}
>
<span className='icon'/>
<span>
@@ -318,16 +381,16 @@ class SignupUserComplete extends React.Component {
/>
</strong></h5>
<div className={passwordDivStyle}>
- <input
- type='password'
- ref='password'
- className='form-control'
- placeholder=''
- maxLength='128'
- spellCheck='false'
- />
- {passwordError}
- </div>
+ <input
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ spellCheck='false'
+ />
+ {passwordError}
+ </div>
</div>
</div>
<p className='margin--extra'>
@@ -373,58 +436,56 @@ class SignupUserComplete extends React.Component {
return (
<div>
- <form>
- <img
- className='signup-team-logo'
- src='/static/images/logo.png'
- />
- <h5 className='margin--less'>
- <FormattedMessage
- id='signup_user_completed.welcome'
- defaultMessage='Welcome to:'
- />
- </h5>
- <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
- <h2 className='signup-team__subdomain'>
- <FormattedMessage
- id='signup_user_completed.onSite'
- defaultMessage='on {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h2>
- <h4 className='color--light'>
- <FormattedMessage
- id='signup_user_completed.lets'
- defaultMessage="Let's create your account"
- />
- </h4>
- {signupMessage}
- {emailSignup}
- {serverError}
- </form>
+ <div className='signup-header'>
+ <a href='/'>
+ <span classNameNameName='fa fa-chevron-left'/>
+ <FormattedMessage id='web.header.back'/>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container padding--less'>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h5 className='margin--less'>
+ <FormattedMessage
+ id='signup_user_completed.welcome'
+ defaultMessage='Welcome to:'
+ />
+ </h5>
+ <h2 className='signup-team__name'>{this.state.teamName}</h2>
+ <h2 className='signup-team__subdomain'>
+ <FormattedMessage
+ id='signup_user_completed.onSite'
+ defaultMessage='on {siteName}'
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </h2>
+ <h4 className='color--light'>
+ <FormattedMessage
+ id='signup_user_completed.lets'
+ defaultMessage="Let's create your account"
+ />
+ </h4>
+ {signupMessage}
+ {emailSignup}
+ {serverError}
+ </form>
+ </div>
+ </div>
</div>
);
}
}
SignupUserComplete.defaultProps = {
- teamName: '',
- hash: '',
- teamId: '',
- email: '',
- data: null,
- teamDisplayName: ''
};
SignupUserComplete.propTypes = {
- intl: intlShape.isRequired,
- teamName: React.PropTypes.string,
- hash: React.PropTypes.string,
- teamId: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string,
- teamDisplayName: React.PropTypes.string
+ location: React.PropTypes.object
};
-export default injectIntl(SignupUserComplete); \ No newline at end of file
+export default SignupUserComplete;
diff --git a/web/react/components/suggestion/at_mention_provider.jsx b/web/react/components/suggestion/at_mention_provider.jsx
index 064b75ac5..c5bd13c26 100644
--- a/web/react/components/suggestion/at_mention_provider.jsx
+++ b/web/react/components/suggestion/at_mention_provider.jsx
@@ -40,7 +40,7 @@ class AtMentionSuggestion extends React.Component {
icon = (
<img
className='mention-img'
- src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at}
/>
);
}
diff --git a/web/react/components/suggestion/suggestion_box.jsx b/web/react/components/suggestion/suggestion_box.jsx
index ea9f835eb..12b098cbd 100644
--- a/web/react/components/suggestion/suggestion_box.jsx
+++ b/web/react/components/suggestion/suggestion_box.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import Constants from '../../utils/constants.jsx';
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import SuggestionStore from '../../stores/suggestion_store.jsx';
import * as Utils from '../../utils/utils.jsx';
@@ -48,7 +48,7 @@ export default class SuggestionBox extends React.Component {
if (!(container.is(e.target) || container.has(e.target).length > 0)) {
// we can't just use blur for this because it fires and hides the children before
// their click handlers can be called
- EventHelpers.emitClearSuggestions(this.suggestionId);
+ GlobalActions.emitClearSuggestions(this.suggestionId);
}
}
@@ -57,7 +57,7 @@ export default class SuggestionBox extends React.Component {
const caret = Utils.getCaretPosition(textbox);
const pretext = textbox.value.substring(0, caret);
- EventHelpers.emitSuggestionPretextChanged(this.suggestionId, pretext);
+ GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
if (this.props.onUserInput) {
this.props.onUserInput(textbox.value);
@@ -89,13 +89,13 @@ export default class SuggestionBox extends React.Component {
handleKeyDown(e) {
if (SuggestionStore.hasSuggestions(this.suggestionId)) {
if (e.which === KeyCodes.UP) {
- EventHelpers.emitSelectPreviousSuggestion(this.suggestionId);
+ GlobalActions.emitSelectPreviousSuggestion(this.suggestionId);
e.preventDefault();
} else if (e.which === KeyCodes.DOWN) {
- EventHelpers.emitSelectNextSuggestion(this.suggestionId);
+ GlobalActions.emitSelectNextSuggestion(this.suggestionId);
e.preventDefault();
} else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.TAB) {
- EventHelpers.emitCompleteWordSuggestion(this.suggestionId);
+ GlobalActions.emitCompleteWordSuggestion(this.suggestionId);
e.preventDefault();
} else if (this.props.onKeyDown) {
this.props.onKeyDown(e);
diff --git a/web/react/components/suggestion/suggestion_list.jsx b/web/react/components/suggestion/suggestion_list.jsx
index e3ccd0f08..ccebeb990 100644
--- a/web/react/components/suggestion/suggestion_list.jsx
+++ b/web/react/components/suggestion/suggestion_list.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import SuggestionStore from '../../stores/suggestion_store.jsx';
export default class SuggestionList extends React.Component {
@@ -36,7 +36,7 @@ export default class SuggestionList extends React.Component {
}
handleItemClick(term, e) {
- EventHelpers.emitCompleteWordSuggestion(this.props.suggestionId, term);
+ GlobalActions.emitCompleteWordSuggestion(this.props.suggestionId, term);
e.preventDefault();
}
diff --git a/web/react/components/team_members_modal.jsx b/web/react/components/team_members_modal.jsx
index 9bdb16438..786e8f947 100644
--- a/web/react/components/team_members_modal.jsx
+++ b/web/react/components/team_members_modal.jsx
@@ -10,8 +10,36 @@ import {FormattedMessage} from 'mm-intl';
const Modal = ReactBootstrap.Modal;
export default class TeamMembersModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.teamChanged = this.teamChanged.bind(this);
+
+ this.state = {
+ team: TeamStore.getCurrent()
+ };
+ }
+ componentDidMount() {
+ if (this.props.show) {
+ this.onShow();
+ }
+
+ TeamStore.addChangeListener(this.teamChanged);
+ }
+
+ componentWillUnmount() {
+ TeamStore.removeChangeListener(this.teamChanged);
+ }
+
+ teamChanged() {
+ this.setState({team: TeamStore.getCurrent()});
+ }
+
render() {
- const team = TeamStore.getCurrent();
+ let teamDisplayName = '';
+ if (this.state.team) {
+ teamDisplayName = this.state.team.display_name;
+ }
let maxHeight = 1000;
if (Utils.windowHeight() <= 1200) {
@@ -29,7 +57,7 @@ export default class TeamMembersModal extends React.Component {
id='team_member_modal.members'
defaultMessage='{team} Members'
values={{
- team: team.display_name
+ team: teamDisplayName
}}
/>
</Modal.Header>
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index e3207d573..0eb9d1211 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -28,6 +28,9 @@ export default class TeamSettings extends React.Component {
}
}
render() {
+ if (!this.state.team) {
+ return null;
+ }
var result;
switch (this.props.activeTab) {
case 'general':
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index 7dd645b25..a81b22d90 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -5,6 +5,7 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
const holders = defineMessages({
emailError: {
@@ -47,9 +48,9 @@ class EmailSignUpPage extends React.Component {
Client.signupTeam(team.email,
(data) => {
if (data.follow_link) {
- window.location.href = data.follow_link;
+ browserHistory.push(data.follow_link);
} else {
- window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`;
+ browserHistory.push(`/signup_team_confirm/?email=${encodeURIComponent(team.email)}`);
}
},
(err) => {
@@ -117,4 +118,4 @@ EmailSignUpPage.propTypes = {
intl: intlShape.isRequired
};
-export default injectIntl(EmailSignUpPage); \ No newline at end of file
+export default injectIntl(EmailSignUpPage);
diff --git a/web/react/components/user_list_row.jsx b/web/react/components/user_list_row.jsx
index d8442e770..1ca40687f 100644
--- a/web/react/components/user_list_row.jsx
+++ b/web/react/components/user_list_row.jsx
@@ -32,7 +32,7 @@ export default function UserListRow({user, actions}) {
>
<img
className='profile-img'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}`}
/>
<div
className='user-list-item__details'
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 31b2b9907..e7a286b77 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -26,22 +26,27 @@ export default class UserProfile extends React.Component {
}
}
render() {
- var name = Utils.displayUsername(this.props.user.id);
- if (this.props.overwriteName) {
- name = this.props.overwriteName;
- } else if (!name) {
- name = '...';
+ let name = '...';
+ let email = '';
+ let profileImg = '';
+ if (this.props.user) {
+ name = Utils.displayUsername(this.props.user.id);
+ email = this.props.user.email;
+ profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
}
- if (this.props.disablePopover) {
- return <div>{name}</div>;
+ if (this.props.overwriteName) {
+ name = this.props.overwriteName;
}
- var profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at + '&' + Utils.getSessionIndex();
if (this.props.overwriteImage) {
profileImg = this.props.overwriteImage;
}
+ if (this.props.disablePopover) {
+ return <div>{name}</div>;
+ }
+
var dataContent = [];
dataContent.push(
<img
@@ -69,14 +74,14 @@ export default class UserProfile extends React.Component {
dataContent.push(
<div
data-toggle='tooltip'
- title={this.props.user.email}
+ title={email}
key='user-popover-email'
>
<a
- href={'mailto:' + this.props.user.email}
+ href={'mailto:' + email}
className='text-nowrap text-lowercase user-popover__email'
>
- {this.props.user.email}
+ {email}
</a>
</div>
);
@@ -114,7 +119,7 @@ UserProfile.defaultProps = {
disablePopover: false
};
UserProfile.propTypes = {
- user: React.PropTypes.object.isRequired,
+ user: React.PropTypes.object,
overwriteName: React.PropTypes.string,
overwriteImage: React.PropTypes.string,
disablePopover: React.PropTypes.bool
diff --git a/web/react/components/user_settings/manage_languages.jsx b/web/react/components/user_settings/manage_languages.jsx
index 2d1c74717..6b00a65c7 100644
--- a/web/react/components/user_settings/manage_languages.jsx
+++ b/web/react/components/user_settings/manage_languages.jsx
@@ -5,6 +5,7 @@ import SettingItemMax from '../setting_item_max.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -41,7 +42,7 @@ export default class ManageLanguage extends React.Component {
submitUser(user) {
Client.updateUser(user,
() => {
- window.location.reload(true);
+ GlobalActions.newLocalizationSelected(user.locale);
},
(err) => {
let serverError;
diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx
index 0acfd4a16..1dd564c8d 100644
--- a/web/react/components/user_settings/user_settings_developer.jsx
+++ b/web/react/components/user_settings/user_settings_developer.jsx
@@ -3,7 +3,7 @@
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
-import * as EventHelpers from '../../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../../action_creators/global_actions.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -28,7 +28,7 @@ class DeveloperTab extends React.Component {
}
register() {
this.props.closeModal();
- EventHelpers.showRegisterAppModal();
+ GlobalActions.showRegisterAppModal();
}
render() {
var appSection;
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index b0b1c414e..235892819 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -13,7 +13,7 @@ import Constants from '../../utils/constants.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'mm-intl';
const holders = defineMessages({
usernameReserved: {
@@ -712,7 +712,7 @@ class UserSettingsGeneralTab extends React.Component {
<SettingPicture
title={formatMessage(holders.profilePicture)}
submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
server_error={serverError}
client_error={clientError}
updateSection={(e) => {
@@ -729,7 +729,14 @@ class UserSettingsGeneralTab extends React.Component {
let minMessage = formatMessage(holders.uploadImage);
if (user.last_picture_update) {
minMessage = formatMessage(holders.imageUpdated, {
- date: new Date(user.last_picture_update).toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'})
+ date: (
+ <FormattedDate
+ value={new Date(user.last_picture_update)}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ )
});
}
pictureSection = (
@@ -805,4 +812,4 @@ UserSettingsGeneralTab.propTypes = {
collapseModal: React.PropTypes.func.isRequired
};
-export default injectIntl(UserSettingsGeneralTab); \ No newline at end of file
+export default injectIntl(UserSettingsGeneralTab);
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index fa3415988..0c4a3d526 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -73,27 +73,35 @@ class UserSettingsModal extends React.Component {
this.updateTab = this.updateTab.bind(this);
this.updateSection = this.updateSection.bind(this);
+ this.onUserChanged = this.onUserChanged.bind(this);
this.state = {
active_tab: 'general',
active_section: '',
showConfirmModal: false,
- enforceFocus: true
+ enforceFocus: true,
+ currentUser: UserStore.getCurrentUser()
};
this.requireConfirm = false;
}
+ onUserChanged() {
+ this.setState({currentUser: UserStore.getCurrentUser()});
+ }
+
componentDidMount() {
if (this.props.show) {
this.handleShow();
}
+ UserStore.addChangeListener(this.onUserChanged);
}
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
this.handleShow();
}
+ UserStore.removeChangeListener(this.onUserChanged);
}
handleShow() {
@@ -235,8 +243,10 @@ class UserSettingsModal extends React.Component {
render() {
const {formatMessage} = this.props.intl;
- var currentUser = UserStore.getCurrentUser();
- var isAdmin = Utils.isAdmin(currentUser.roles);
+ if (this.state.currentUser == null) {
+ return (<div/>);
+ }
+ var isAdmin = Utils.isAdmin(this.state.currentUser.roles);
var tabs = [];
tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index cba7ffdea..0b6b6c398 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -14,7 +14,7 @@ import * as AsyncClient from '../../utils/async_client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Constants from '../../utils/constants.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedTime, FormattedDate} from 'mm-intl';
const holders = defineMessages({
currentPasswordError: {
@@ -218,11 +218,24 @@ class SecurityTab extends React.Component {
var describe;
var d = new Date(this.props.user.last_password_update);
- const locale = global.window.mm_locale;
const hours12 = !Utils.isMilitaryTime();
describe = formatMessage(holders.lastUpdated, {
- date: d.toLocaleDateString(locale, {month: 'short', day: '2-digit', year: 'numeric'}),
- time: d.toLocaleTimeString(locale, {hour12: hours12, hour: '2-digit', minute: '2-digit'})
+ date: (
+ <FormattedDate
+ value={d}
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ ),
+ time: (
+ <FormattedTime
+ value={d}
+ hour12={hours12}
+ hour='2-digit'
+ minute='2-digit'
+ />
+ )
});
updateSectionStatus = function updateSection() {
@@ -251,7 +264,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email)}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service}
>
<FormattedMessage
id='user.settings.security.switchEmail'
@@ -269,7 +282,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GITLAB_SERVICE}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchGitlab'
@@ -287,7 +300,7 @@ class SecurityTab extends React.Component {
<div>
<a
className='btn btn-primary'
- href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&new_type=' + Constants.GOOGLE_SERVICE}
+ href={'/' + teamName + '/claim?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchGoogle'
@@ -456,4 +469,4 @@ SecurityTab.propTypes = {
setEnforceFocus: React.PropTypes.func.isRequired
};
-export default injectIntl(SecurityTab); \ No newline at end of file
+export default injectIntl(SecurityTab);
diff --git a/web/react/package.json b/web/react/package.json
index 07ffa0cdf..509c9967b 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -11,6 +11,8 @@
"marked": "mattermost/marked#cb85e5cc81bc7937dbb73c3c53d9532b1b97e3ca",
"mm-intl": "mattermost/mm-intl#805442fd474fa40cd586ddeda404dbbe8e60626d",
"object-assign": "4.0.1",
+ "react": "0.14.3",
+ "react-router": "2.0.0",
"twemoji": "1.4.1"
},
"devDependencies": {
diff --git a/web/react/pages/admin_console.jsx b/web/react/pages/admin_console.jsx
deleted file mode 100644
index 989936d9e..000000000
--- a/web/react/pages/admin_console.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ErrorBar from '../components/error_bar.jsx';
-import SelectTeamModal from '../components/admin_console/select_team_modal.jsx';
-import AdminController from '../components/admin_console/admin_controller.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <div>
- <ErrorBar/>
- <AdminController
- tab={this.props.map.ActiveTab}
- teamId={this.props.map.TeamId}
- />
- <SelectTeamModal/>
- </div>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_admin_console_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('admin_controller')
- );
-};
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
deleted file mode 100644
index bc78c049c..000000000
--- a/web/react/pages/channel.jsx
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChannelView from '../components/channel_view.jsx';
-import ChannelLoader from '../components/channel_loader.jsx';
-import ErrorBar from '../components/error_bar.jsx';
-import * as Client from '../utils/client.jsx';
-
-import GetPostLinkModal from '../components/get_post_link_modal.jsx';
-import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
-import EditPostModal from '../components/edit_post_modal.jsx';
-import DeletePostModal from '../components/delete_post_modal.jsx';
-import MoreChannelsModal from '../components/more_channels.jsx';
-import TeamSettingsModal from '../components/team_settings_modal.jsx';
-import RemovedFromChannelModal from '../components/removed_from_channel_modal.jsx';
-import RegisterAppModal from '../components/register_app_modal.jsx';
-import ImportThemeModal from '../components/user_settings/import_theme_modal.jsx';
-import InviteMemberModal from '../components/invite_member_modal.jsx';
-
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <div className='channel-view'>
- <ChannelLoader/>
- <ErrorBar/>
- <ChannelView/>
- <GetPostLinkModal/>
- <GetTeamInviteLinkModal/>
- <InviteMemberModal/>
- <ImportThemeModal/>
- <TeamSettingsModal/>
- <MoreChannelsModal/>
- <EditPostModal/>
- <DeletePostModal/>
- <RemovedFromChannelModal/>
- <RegisterAppModal/>
- </div>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_channel_page = function setup(props, team, channel) {
- if (props.PostId === '') {
- EventHelpers.emitChannelClickEvent(channel);
- } else {
- EventHelpers.emitPostFocusEvent(props.PostId);
- }
-
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('channel_view')
- );
-};
diff --git a/web/react/pages/claim_account.jsx b/web/react/pages/claim_account.jsx
deleted file mode 100644
index abbf72ea3..000000000
--- a/web/react/pages/claim_account.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ClaimAccount from '../components/claim/claim_account.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <ClaimAccount
- email={this.props.map.Email}
- currentType={this.props.map.CurrentType}
- newType={this.props.map.NewType}
- teamName={this.props.map.TeamName}
- teamDisplayName={this.props.map.TeamDisplayName}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_claim_account_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('claim')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/docs.jsx b/web/react/pages/docs.jsx
deleted file mode 100644
index 2e47e3e6a..000000000
--- a/web/react/pages/docs.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Docs from '../components/docs.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <Docs site={this.props.map.Site}/>
- </IntlProvider>
- );
- }
-}
-
-global.window.mm_user = global.window.mm_user || {};
-
-global.window.setup_documentation_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('docs')
- );
-};
diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx
deleted file mode 100644
index 93394fcde..000000000
--- a/web/react/pages/find_team.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import FindTeam from '../components/find_team.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <FindTeam/>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_find_team_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('find-team')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx
deleted file mode 100644
index ff81c4994..000000000
--- a/web/react/pages/home.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TeamStore from '../stores/team_store.jsx';
-import Constants from '../utils/constants.jsx';
-
-function setupHomePage() {
- var last = null;
- if (last == null || last.length === 0) {
- window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + Constants.DEFAULT_CHANNEL;
- } else {
- window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + last;
- }
-}
-
-global.window.setup_home_page = setupHomePage;
diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx
deleted file mode 100644
index ec9080945..000000000
--- a/web/react/pages/login.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Client from '../utils/client.jsx';
-import Login from '../components/login.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <Login
- teamDisplayName={this.props.map.TeamDisplayName}
- teamName={this.props.map.TeamName}
- inviteId={this.props.map.InviteId}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_login_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('login')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
deleted file mode 100644
index 7caff5034..000000000
--- a/web/react/pages/password_reset.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PasswordReset from '../components/password_reset.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <PasswordReset
- isReset={this.props.map.IsReset}
- teamDisplayName={this.props.map.TeamDisplayName}
- teamName={this.props.map.TeamName}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_password_reset_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('reset')
- );
-};
diff --git a/web/react/pages/root.jsx b/web/react/pages/root.jsx
new file mode 100644
index 000000000..d0b06e32e
--- /dev/null
+++ b/web/react/pages/root.jsx
@@ -0,0 +1,290 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
+import Root from '../components/root.jsx';
+import Login from '../components/login.jsx';
+import LoggedIn from '../components/logged_in.jsx';
+import NotLoggedIn from '../components/not_logged_in.jsx';
+import NeedsTeam from '../components/needs_team.jsx';
+import PasswordResetSendLink from '../components/password_reset_send_link.jsx';
+import PasswordResetForm from '../components/password_reset_form.jsx';
+import ChannelView from '../components/channel_view.jsx';
+import Sidebar from '../components/sidebar.jsx';
+import * as AsyncClient from '../utils/async_client.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
+import ChannelStore from '../stores/channel_store.jsx';
+import ErrorStore from '../stores/error_store.jsx';
+import BrowserStore from '../stores/browser_store.jsx';
+import SignupTeam from '../components/signup_team.jsx';
+import * as Client from '../utils/client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
+import SignupTeamConfirm from '../components/signup_team_confirm.jsx';
+import SignupUserComplete from '../components/signup_user_complete.jsx';
+import ShouldVerifyEmail from '../components/should_verify_email.jsx';
+import DoVerifyEmail from '../components/do_verify_email.jsx';
+import AdminConsole from '../components/admin_console/admin_controller.jsx';
+import ClaimAccount from '../components/claim/claim_account.jsx';
+
+import SignupTeamComplete from '../components/signup_team_complete/components/signup_team_complete.jsx';
+import WelcomePage from '../components/signup_team_complete/components/team_signup_welcome_page.jsx';
+import TeamDisplayNamePage from '../components/signup_team_complete/components/team_signup_display_name_page.jsx';
+import TeamURLPage from '../components/signup_team_complete/components/team_signup_url_page.jsx';
+import SendInivtesPage from '../components/signup_team_complete/components/team_signup_send_invites_page.jsx';
+import UsernamePage from '../components/signup_team_complete/components/team_signup_username_page.jsx';
+import PasswordPage from '../components/signup_team_complete/components/team_signup_password_page.jsx';
+import FinishedPage from '../components/signup_team_complete/components/team_signup_finished.jsx';
+
+// This is for anything that needs to be done for ALL react components.
+// This runs before we start to render anything.
+function preRenderSetup(callwhendone) {
+ const d1 = Client.getClientConfig(
+ (data, textStatus, xhr) => {
+ if (!data) {
+ return;
+ }
+
+ global.window.mm_config = data;
+
+ var serverVersion = xhr.getResponseHeader('X-Version-ID');
+
+ if (serverVersion !== BrowserStore.getLastServerVersion()) {
+ if (!BrowserStore.getLastServerVersion() || BrowserStore.getLastServerVersion() === '') {
+ BrowserStore.setLastServerVersion(serverVersion);
+ } else {
+ BrowserStore.setLastServerVersion(serverVersion);
+ window.location.reload(true);
+ console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
+ }
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getClientConfig');
+ }
+ );
+
+ const d2 = Client.getClientLicenceConfig(
+ (data) => {
+ if (!data) {
+ return;
+ }
+
+ global.window.mm_license = data;
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getClientLicenceConfig');
+ }
+ );
+
+ // Set these here so they don't fail in client.jsx track
+ global.window.analytics = {};
+ global.window.analytics.page = () => {
+ // Do Nothing
+ };
+ global.window.analytics.track = () => {
+ // Do Nothing
+ };
+
+ $.when(d1, d2).done(callwhendone);
+}
+
+function preLoggedIn(nextState, replace, callback) {
+ const d1 = Client.getAllPreferences(
+ (data) => {
+ if (!data) {
+ return;
+ }
+
+ PreferenceStore.setPreferences(data);
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getAllPreferences');
+ }
+ );
+
+ const d2 = AsyncClient.getChannels();
+
+ $.when(d1, d2).done(() => callback());
+}
+
+function onChannelChange(nextState) {
+ const channelName = nextState.params.channel;
+
+ // Make sure we have all the channels
+ AsyncClient.getChannels(true);
+
+ // Get our channel's ID
+ const channel = ChannelStore.getByName(channelName);
+
+ // User clicked channel
+ GlobalActions.emitChannelClickEvent(channel);
+}
+
+function onRootEnter(nextState, replace, callback) {
+ if (nextState.location.pathname === '/') {
+ Client.getMeLoggedIn((data) => {
+ if (!data || data.logged_in === 'false') {
+ replace({pathname: '/signup_team'});
+ callback();
+ } else {
+ replace({pathname: '/' + data.team_name + '/channels/town-square'});
+ callback();
+ }
+ });
+ return;
+ }
+
+ callback();
+}
+
+function onPermalinkEnter(nextState) {
+ const postId = nextState.params.postid;
+
+ GlobalActions.emitPostFocusEvent(postId);
+}
+
+function onLoggedOut(nextState) {
+ const teamName = nextState.params.team;
+ Client.logout(
+ () => {
+ browserHistory.push('/' + teamName + '/login');
+ BrowserStore.signalLogout();
+ BrowserStore.clear();
+ ErrorStore.clearLastError();
+ },
+ () => {
+ browserHistory.push('/' + teamName + '/login');
+ }
+ );
+}
+
+function renderRootComponent() {
+ ReactDOM.render((
+ <Router
+ history={browserHistory}
+ >
+ <Route
+ path='/'
+ component={Root}
+ onEnter={onRootEnter}
+ >
+ <Route
+ component={LoggedIn}
+ onEnter={preLoggedIn}
+ >
+ <Route
+ path=':team/channels/:channel'
+ onEnter={onChannelChange}
+ components={{
+ sidebar: Sidebar,
+ center: ChannelView
+ }}
+ />
+ <Route
+ path=':team/pl/:postid'
+ onEnter={onPermalinkEnter}
+ components={{
+ sidebar: Sidebar,
+ center: ChannelView
+ }}
+ />
+ <Route
+ path=':team/logout'
+ onEnter={onLoggedOut}
+ components={{
+ sidebar: null,
+ center: null
+ }}
+ />
+ <Route
+ path='admin_console'
+ components={{
+ sidebar: null,
+ center: AdminConsole
+ }}
+ />
+ </Route>
+ <Route component={NotLoggedIn}>
+ <Route
+ path='signup_team'
+ component={SignupTeam}
+ />
+ <Route
+ path='signup_team_complete'
+ component={SignupTeamComplete}
+ >
+ <IndexRoute component={FinishedPage}/>
+ <Route
+ path='welcome'
+ component={WelcomePage}
+ />
+ <Route
+ path='team_display_name'
+ component={TeamDisplayNamePage}
+ />
+ <Route
+ path='team_url'
+ component={TeamURLPage}
+ />
+ <Route
+ path='invites'
+ component={SendInivtesPage}
+ />
+ <Route
+ path='username'
+ component={UsernamePage}
+ />
+ <Route
+ path='password'
+ component={PasswordPage}
+ />
+ </Route>
+ <Route
+ path='signup_user_complete'
+ component={SignupUserComplete}
+ />
+ <Route
+ path='signup_team_confirm'
+ component={SignupTeamConfirm}
+ />
+ <Route
+ path='should_verify_email'
+ component={ShouldVerifyEmail}
+ />
+ <Route
+ path='do_verify_email'
+ component={DoVerifyEmail}
+ />
+ <Route
+ path=':team'
+ component={NeedsTeam}
+ >
+ <IndexRedirect to='login'/>
+ <Route
+ path='login'
+ component={Login}
+ />
+ <Route
+ path='claim'
+ component={ClaimAccount}
+ />
+ <Route
+ path='reset_password'
+ component={PasswordResetSendLink}
+ />
+ <Route
+ path='reset_password_complete'
+ component={PasswordResetForm}
+ />
+ </Route>
+ </Route>
+ </Route>
+ </Router>
+ ),
+ document.getElementById('root'));
+}
+
+global.window.setup_root = () => {
+ // Do the pre-render setup and call renderRootComponent when done
+ preRenderSetup(renderRootComponent);
+};
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
deleted file mode 100644
index f276c3ff7..000000000
--- a/web/react/pages/signup_team.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeam from '../components/signup_team.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired,
- teams: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeam teams={this.props.teams}/>
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_page = function setup(props) {
- var teams = [];
-
- for (var prop in props) {
- if (props.hasOwnProperty(prop)) {
- if (prop !== 'Title' && prop !== 'Locale' && prop !== 'Info') {
- teams.push({name: prop, display_name: props[prop]});
- }
- }
- }
-
- ReactDOM.render(
- <Root
- map={props}
- teams={teams}
- />,
- document.getElementById('signup-team')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
deleted file mode 100644
index 8c237f698..000000000
--- a/web/react/pages/signup_team_complete.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeamComplete from '../components/signup_team_complete.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeamComplete
- email={this.props.map.Email}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_complete_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-team-complete')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_team_confirm.jsx b/web/react/pages/signup_team_confirm.jsx
deleted file mode 100644
index 13c8f3fd0..000000000
--- a/web/react/pages/signup_team_confirm.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupTeamConfirm from '../components/signup_team_confirm.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupTeamConfirm
- email={this.props.map.Email}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_team_confirm_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-team-confirm')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx
deleted file mode 100644
index a14f2140b..000000000
--- a/web/react/pages/signup_user_complete.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SignupUserComplete from '../components/signup_user_complete.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <SignupUserComplete
- teamId={this.props.map.TeamId}
- teamName={this.props.map.TeamName}
- teamDisplayName={this.props.map.TeamDisplayName}
- email={this.props.map.Email}
- hash={this.props.map.Hash}
- data={this.props.map.Data}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setup_signup_user_complete_page = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('signup-user-complete')
- );
-}; \ No newline at end of file
diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx
deleted file mode 100644
index 6b336daa1..000000000
--- a/web/react/pages/verify.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import EmailVerify from '../components/email_verify.jsx';
-import * as Client from '../utils/client.jsx';
-
-var IntlProvider = ReactIntl.IntlProvider;
-
-class Root extends React.Component {
- constructor() {
- super();
- this.state = {
- translations: null,
- loaded: false
- };
- }
-
- static propTypes() {
- return {
- map: React.PropTypes.object.isRequired
- };
- }
-
- componentWillMount() {
- Client.getTranslations(
- this.props.map.Locale,
- (data) => {
- this.setState({
- translations: data,
- loaded: true
- });
- },
- () => {
- this.setState({
- loaded: true
- });
- }
- );
- }
-
- render() {
- if (!this.state.loaded) {
- return <div></div>;
- }
-
- return (
- <IntlProvider
- locale={this.props.map.Locale}
- messages={this.state.translations}
- >
- <EmailVerify
- isVerified={this.props.map.IsVerified}
- teamURL={this.props.map.TeamURL}
- userEmail={this.props.map.UserEmail}
- resendSuccess={this.props.map.ResendSuccess}
- />
- </IntlProvider>
- );
- }
-}
-
-global.window.setupVerifyPage = function setup(props) {
- ReactDOM.render(
- <Root map={props}/>,
- document.getElementById('verify')
- );
-};
diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx
index 5c911e94b..9f7f6e7ff 100644
--- a/web/react/stores/admin_store.jsx
+++ b/web/react/stores/admin_store.jsx
@@ -121,7 +121,11 @@ class AdminStoreClass extends EventEmitter {
}
getSelectedTeams() {
- return BrowserStore.getItem('seleted_teams');
+ const result = BrowserStore.getItem('seleted_teams');
+ if (!result) {
+ return {};
+ }
+ return result;
}
saveSelectedTeams(teams) {
@@ -156,7 +160,3 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
});
export default AdminStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.AdminStore = AdminStore;
-}
diff --git a/web/react/stores/analytics_store.jsx b/web/react/stores/analytics_store.jsx
index 0ad989206..ec827f6d7 100644
--- a/web/react/stores/analytics_store.jsx
+++ b/web/react/stores/analytics_store.jsx
@@ -83,7 +83,3 @@ AnalyticsStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default AnalyticsStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.AnalyticsStore = AnalyticsStore;
-}
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index 3417faaaf..3b35916b3 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -4,8 +4,8 @@
import {generateId} from '../utils/utils.jsx';
function getPrefix() {
- if (global.window.mm_user) {
- return global.window.mm_user.id + '_';
+ if (global.window.mm_current_user_id) {
+ return global.window.mm_current_user_id + '_';
}
return 'unknown_';
@@ -31,7 +31,9 @@ class BrowserStoreClass {
this.isSignallingLogout = this.isSignallingLogout.bind(this);
this.signalLogin = this.signalLogin.bind(this);
this.isSignallingLogin = this.isSignallingLogin.bind(this);
+ }
+ checkVersion() {
var currentVersion = sessionStorage.getItem('storage_version');
if (currentVersion !== global.window.mm_config.Version) {
sessionStorage.clear();
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index eac24b071..60cb10de7 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -350,7 +350,3 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default ChannelStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.ChannelStore = ChannelStore;
-}
diff --git a/web/react/stores/file_store.jsx b/web/react/stores/file_store.jsx
index c1fd0ef74..6d7e0f354 100644
--- a/web/react/stores/file_store.jsx
+++ b/web/react/stores/file_store.jsx
@@ -57,9 +57,4 @@ class FileStore extends EventEmitter {
}
}
-const instance = new FileStore();
-export default instance;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.FileStore = instance;
-}
+export default new FileStore();
diff --git a/web/react/stores/localization_store.jsx b/web/react/stores/localization_store.jsx
new file mode 100644
index 000000000..0e3a63724
--- /dev/null
+++ b/web/react/stores/localization_store.jsx
@@ -0,0 +1,60 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
+import EventEmitter from 'events';
+import Constants from '../utils/constants.jsx';
+const ActionTypes = Constants.ActionTypes;
+
+const CHANGE_EVENT = 'change';
+
+class LocalizationStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.currentLocale = 'en';
+ this.currentTranslations = null;
+ }
+
+ emitChange() {
+ this.emit(CHANGE_EVENT);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+
+ setCurrentLocale(locale, translations) {
+ this.currentLocale = locale;
+ this.currentTranslations = translations;
+ }
+
+ getLocale() {
+ return this.currentLocale;
+ }
+
+ getTranslations() {
+ return this.currentTranslations;
+ }
+}
+
+var LocalizationStore = new LocalizationStoreClass();
+LocalizationStore.setMaxListeners(0);
+
+LocalizationStore.dispatchToken = AppDispatcher.register((payload) => {
+ var action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECEIVED_LOCALE:
+ LocalizationStore.setCurrentLocale(action.locale, action.translations);
+ LocalizationStore.emitChange();
+ break;
+ default:
+ }
+});
+
+export default LocalizationStore;
diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx
index 1819fffc0..5ea38030b 100644
--- a/web/react/stores/modal_store.jsx
+++ b/web/react/stores/modal_store.jsx
@@ -45,7 +45,3 @@ class ModalStoreClass extends EventEmitter {
const ModalStore = new ModalStoreClass();
export default ModalStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.ModalStore = ModalStore;
-}
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 5cc3f300d..a6dfcd46f 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -608,7 +608,3 @@ function isPostListNull(pl) {
return false;
}
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.PostStore = PostStore;
-}
diff --git a/web/react/stores/search_store.jsx b/web/react/stores/search_store.jsx
index 96071665c..549f355ef 100644
--- a/web/react/stores/search_store.jsx
+++ b/web/react/stores/search_store.jsx
@@ -135,7 +135,3 @@ SearchStore.dispatchToken = AppDispatcher.register((payload) => {
});
export default SearchStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.SearchStore = SearchStore;
-}
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index 9b2b049b7..ad24a04cd 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -10,7 +10,7 @@ import EventEmitter from 'events';
import * as Utils from '../utils/utils.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
const SocketEvents = Constants.SocketEvents;
@@ -42,10 +42,6 @@ class SocketStoreClass extends EventEmitter {
return;
}
- if (!global.window.hasOwnProperty('mm_session_token_index')) {
- return;
- }
-
this.setMaxListeners(0);
if (window.WebSocket && !conn) {
@@ -54,7 +50,7 @@ class SocketStoreClass extends EventEmitter {
protocol = 'wss://';
}
- var connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket?' + Utils.getSessionIndex();
+ var connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket';
if (this.failCount === 0) {
console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console
@@ -204,7 +200,7 @@ class SocketStoreClass extends EventEmitter {
function handleNewPostEvent(msg, translations) {
// Store post
const post = JSON.parse(msg.props.post);
- EventHelpers.emitPostRecievedEvent(post);
+ GlobalActions.emitPostRecievedEvent(post);
// Update channel state
if (ChannelStore.getCurrentId() === msg.channel_id) {
@@ -291,7 +287,7 @@ function handlePostEditEvent(msg) {
function handlePostDeleteEvent(msg) {
const post = JSON.parse(msg.props.post);
- EventHelpers.emitPostDeletedEvent(post);
+ GlobalActions.emitPostDeletedEvent(post);
}
function handleNewUserEvent() {
@@ -337,7 +333,7 @@ function handleChannelViewedEvent(msg) {
function handlePreferenceChangedEvent(msg) {
const preference = JSON.parse(msg.props.preference);
- EventHelpers.emitPreferenceChangedEvent(preference);
+ GlobalActions.emitPreferenceChangedEvent(preference);
}
var SocketStore = new SocketStoreClass();
diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx
index 487bae843..efd2b76ed 100644
--- a/web/react/stores/suggestion_store.jsx
+++ b/web/react/stores/suggestion_store.jsx
@@ -258,9 +258,4 @@ class SuggestionStore extends EventEmitter {
}
}
-const instance = new SuggestionStore();
-export default instance;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.SuggestionStore = instance;
-}
+export default new SuggestionStore();
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 493d6bc4d..354a07b72 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -6,7 +6,6 @@ import EventEmitter from 'events';
import Constants from '../utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import BrowserStore from '../stores/browser_store.jsx';
const CHANGE_EVENT = 'change';
@@ -33,6 +32,9 @@ class TeamStoreClass extends EventEmitter {
this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
this.getCurrentInviteLink = this.getCurrentInviteLink.bind(this);
this.saveTeam = this.saveTeam.bind(this);
+
+ this.teams = {};
+ this.currentTeamId = '';
}
emitChange() {
@@ -65,11 +67,11 @@ class TeamStoreClass extends EventEmitter {
}
getAll() {
- return BrowserStore.getItem('user_teams', {});
+ return this.teams;
}
getCurrentId() {
- var team = global.window.mm_team;
+ var team = this.get(this.currentTeamId);
if (team) {
return team.id;
@@ -79,11 +81,13 @@ class TeamStoreClass extends EventEmitter {
}
getCurrent() {
- if (global.window.mm_team != null && this.get(global.window.mm_team.id) == null) {
- this.saveTeam(global.window.mm_team);
+ const team = this.teams[this.currentTeamId];
+
+ if (team) {
+ return team;
}
- return global.window.mm_team;
+ return null;
}
getCurrentTeamUrl() {
@@ -104,9 +108,16 @@ class TeamStoreClass extends EventEmitter {
}
saveTeam(team) {
- var teams = this.getAll();
- teams[team.id] = team;
- BrowserStore.setItem('user_teams', teams);
+ this.teams[team.id] = team;
+ }
+
+ saveTeams(teams) {
+ this.teams = teams;
+ }
+
+ saveMyTeam(team) {
+ this.saveTeam(team);
+ this.currentTeamId = team.id;
}
}
@@ -116,17 +127,16 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECEIVED_TEAM:
- TeamStore.saveTeam(action.team);
+ case ActionTypes.RECEIVED_MY_TEAM:
+ TeamStore.saveMyTeam(action.team);
+ TeamStore.emitChange();
+ break;
+ case ActionTypes.RECEIVED_ALL_TEAMS:
+ TeamStore.saveTeams(action.teams);
TeamStore.emitChange();
break;
-
default:
}
});
export default TeamStore;
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.TeamStore = TeamStore;
-}
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 9fcd2440e..c1e5c75dc 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -11,13 +11,13 @@ import BrowserStore from './browser_store.jsx';
const CHANGE_EVENT = 'change';
const CHANGE_EVENT_SESSIONS = 'change_sessions';
const CHANGE_EVENT_AUDITS = 'change_audits';
-const CHANGE_EVENT_TEAMS = 'change_teams';
const CHANGE_EVENT_STATUSES = 'change_statuses';
class UserStoreClass extends EventEmitter {
constructor() {
super();
this.profileCache = null;
+ this.currentUserId = '';
}
emitChange(userId) {
@@ -56,18 +56,6 @@ class UserStoreClass extends EventEmitter {
this.removeListener(CHANGE_EVENT_AUDITS, callback);
}
- emitTeamsChange() {
- this.emit(CHANGE_EVENT_TEAMS);
- }
-
- addTeamsChangeListener(callback) {
- this.on(CHANGE_EVENT_TEAMS, callback);
- }
-
- removeTeamsChangeListener(callback) {
- this.removeListener(CHANGE_EVENT_TEAMS, callback);
- }
-
emitStatusesChange() {
this.emit(CHANGE_EVENT_STATUSES);
}
@@ -81,26 +69,17 @@ class UserStoreClass extends EventEmitter {
}
getCurrentUser() {
- if (this.getProfiles()[global.window.mm_user.id] == null) {
- this.saveProfile(global.window.mm_user);
- }
-
- return global.window.mm_user;
+ return this.getProfiles()[this.currentUserId];
}
setCurrentUser(user) {
- var oldUser = global.window.mm_user;
-
- if (oldUser.id === user.id) {
- global.window.mm_user = user;
- this.saveProfile(user);
- } else {
- throw new Error('Problem with setCurrentUser old_user_id=' + oldUser.id + ' new_user_id=' + user.id);
- }
+ this.saveProfile(user);
+ this.currentUserId = user.id;
+ global.window.mm_current_user_id = this.currentUserId;
}
getCurrentId() {
- var user = global.window.mm_user;
+ var user = this.getCurrentUser();
if (user) {
return user.id;
@@ -200,11 +179,22 @@ class UserStoreClass extends EventEmitter {
saveProfiles(profiles) {
const currentId = this.getCurrentId();
- if (currentId in profiles) {
- delete profiles[currentId];
+ if (this.profileCache) {
+ const currentUser = this.profileCache[currentId];
+ if (currentUser) {
+ if (currentId in profiles) {
+ delete profiles[currentId];
+ }
+
+ this.profileCache = profiles;
+ this.profileCache[currentId] = currentUser;
+ } else {
+ this.profileCache = profiles;
+ }
+ } else {
+ this.profileCache = profiles;
}
- this.profileCache = profiles;
BrowserStore.setItem('profiles', profiles);
}
@@ -224,14 +214,6 @@ class UserStoreClass extends EventEmitter {
return BrowserStore.getItem('audits', {loading: true});
}
- setTeams(teams) {
- BrowserStore.setItem('teams', teams);
- }
-
- getTeams() {
- return BrowserStore.getItem('teams', []);
- }
-
getCurrentMentionKeys() {
return this.getMentionKeys(this.getCurrentId());
}
@@ -312,10 +294,6 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
UserStore.setAudits(action.audits);
UserStore.emitAuditsChange();
break;
- case ActionTypes.RECEIVED_TEAMS:
- UserStore.setTeams(action.teams);
- UserStore.emitTeamsChange();
- break;
case ActionTypes.RECEIVED_STATUSES:
UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange();
@@ -325,7 +303,3 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => {
});
export {UserStore as default};
-
-if (window.mm_config.EnableDeveloper === 'true') {
- window.UserStore = UserStore;
-}
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 7d5e1bd0f..b9770a6e9 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
import * as client from './client.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import BrowserStore from '../stores/browser_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -44,15 +45,19 @@ function isCallInProgress(callName) {
export function getChannels(checkVersion) {
if (isCallInProgress('getChannels')) {
- return;
+ return null;
}
callTracker.getChannels = utils.getTimestamp();
- client.getChannels(
+ return client.getChannels(
(data, textStatus, xhr) => {
callTracker.getChannels = 0;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
if (checkVersion) {
var serverVersion = xhr.getResponseHeader('X-Version-ID');
@@ -67,10 +72,6 @@ export function getChannels(checkVersion) {
}
}
- if (xhr.status === 304 || !data) {
- return;
- }
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNELS,
channels: data.channels,
@@ -392,36 +393,6 @@ export function getAllTeams() {
);
}
-export function findTeams(email) {
- if (isCallInProgress('findTeams_' + email)) {
- return;
- }
-
- var user = UserStore.getCurrentUser();
- if (user) {
- callTracker['findTeams_' + email] = utils.getTimestamp();
- client.findTeams(
- user.email,
- function findTeamsSuccess(data, textStatus, xhr) {
- callTracker['findTeams_' + email] = 0;
-
- if (xhr.status === 304 || !data) {
- return;
- }
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_TEAMS,
- teams: data
- });
- },
- function findTeamsFailure(err) {
- callTracker['findTeams_' + email] = 0;
- dispatchError(err, 'findTeams');
- }
- );
- }
-}
-
export function search(terms) {
if (isCallInProgress('search_' + String(terms))) {
return;
@@ -645,11 +616,11 @@ export function getPostsAfter(postId, offset, numPost) {
export function getMe() {
if (isCallInProgress('getMe')) {
- return;
+ return null;
}
callTracker.getMe = utils.getTimestamp();
- client.getMe(
+ return client.getMe(
(data, textStatus, xhr) => {
callTracker.getMe = 0;
@@ -661,6 +632,8 @@ export function getMe() {
type: ActionTypes.RECEIVED_ME,
me: data
});
+
+ GlobalActions.newLocalizationSelected(data.locale);
},
(err) => {
callTracker.getMe = 0;
@@ -706,11 +679,11 @@ export function getStatuses() {
export function getMyTeam() {
if (isCallInProgress('getMyTeam')) {
- return;
+ return null;
}
callTracker.getMyTeam = utils.getTimestamp();
- client.getMyTeam(
+ return client.getMyTeam(
function getMyTeamSuccess(data, textStatus, xhr) {
callTracker.getMyTeam = 0;
@@ -719,7 +692,7 @@ export function getMyTeam() {
}
AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_TEAM,
+ type: ActionTypes.RECEIVED_MY_TEAM,
team: data
});
},
diff --git a/web/react/utils/channel_intro_messages.jsx b/web/react/utils/channel_intro_messages.jsx
index ed94f94b8..94f3f0ce0 100644
--- a/web/react/utils/channel_intro_messages.jsx
+++ b/web/react/utils/channel_intro_messages.jsx
@@ -8,8 +8,7 @@ import ToggleModalButton from '../components/toggle_modal_button.jsx';
import UserProfile from '../components/user_profile.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import Constants from '../utils/constants.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import {FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'mm-intl';
@@ -40,7 +39,7 @@ export function createDMIntroMessage(channel) {
<div className='post-profile-img__container channel-intro-img'>
<img
className='post-profile-img'
- src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at + '&' + Utils.getSessionIndex()}
+ src={'/api/v1/users/' + teammate.id + '/image?time=' + teammate.update_at}
height='50'
width='50'
/>
@@ -93,37 +92,19 @@ export function createOffTopicIntroMessage(channel) {
}
export function createDefaultIntroMessage(channel) {
- const team = TeamStore.getCurrent();
- let inviteModalLink;
- if (team.type === Constants.INVITE_TEAM) {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={EventHelpers.showInviteMemberModal}
- >
- <i className='fa fa-user-plus'></i>
- <FormattedMessage
- id='intro_messages.inviteOthers'
- defaultMessage='Invite others to this team'
- />
- </a>
- );
- } else {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={EventHelpers.showGetTeamInviteLinkModal}
- >
- <i className='fa fa-user-plus'></i>
- <FormattedMessage
- id='intro_messages.inviteOthers'
- defaultMessage='Invite others to this team'
- />
- </a>
- );
- }
+ const inviteModalLink = (
+ <a
+ className='intro-links'
+ href='#'
+ onClick={GlobalActions.showGetTeamInviteLinkModal}
+ >
+ <i className='fa fa-user-plus'></i>
+ <FormattedMessage
+ id='intro_messages.inviteOthers'
+ defaultMessage='Invite others to this team'
+ />
+ </a>
+ );
return (
<div className='channel-intro'>
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 76d42137a..e00f28a14 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -1,8 +1,8 @@
// See License.txt for license information.
import BrowserStore from '../stores/browser_store.jsx';
-import TeamStore from '../stores/team_store.jsx';
-import ErrorStore from '../stores/error_store.jsx';
+
+import {browserHistory} from 'react-router';
let translations = {
connectionError: 'There appears to be a problem with your internet connection.',
@@ -50,10 +50,10 @@ function handleError(methodName, xhr, status, err) {
if (xhr.status === 401) {
if (window.location.href.indexOf('/channels') === 0) {
- window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
+ browserHistory.push('/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search));
} else {
- var teamURL = window.location.href.split('/channels')[0];
- window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
+ var teamURL = window.location.pathname.split('/channels')[0];
+ browserHistory.push(teamURL + '/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search));
}
}
@@ -289,13 +289,17 @@ export function switchToEmail(data, success, error) {
track('api', 'api_users_switch_to_email');
}
-export function logout() {
+export function logout(success, error) {
track('api', 'api_users_logout');
- var currentTeamUrl = TeamStore.getCurrentTeamUrl();
- BrowserStore.signalLogout();
- BrowserStore.clear();
- ErrorStore.clearLastError();
- window.location.href = currentTeamUrl + '/logout';
+ $.ajax({
+ url: '/api/v1/users/logout',
+ type: 'POST',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('logout', xhr, status, err);
+ error(e);
+ }
+ });
}
export function loginByEmail(name, email, password, success, error) {
@@ -437,7 +441,7 @@ export function getServerAudits(success, error) {
}
export function getConfig(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/admin/config',
dataType: 'json',
contentType: 'application/json',
@@ -457,7 +461,6 @@ export function getAnalytics(name, teamId, success, error) {
} else {
url += teamId + '/' + name;
}
-
$.ajax({
url,
dataType: 'json',
@@ -471,6 +474,34 @@ export function getAnalytics(name, teamId, success, error) {
});
}
+export function getClientConfig(success, error) {
+ return $.ajax({
+ url: '/api/v1/admin/client_props',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getClientConfig', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function getTeamAnalytics(teamId, name, success, error) {
+ $.ajax({
+ url: '/api/v1/admin/analytics/' + teamId + '/' + name,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: (xhr, status, err) => {
+ var e = handleError('getTeamAnalytics', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function saveConfig(config, success, error) {
$.ajax({
url: '/api/v1/admin/save_config',
@@ -529,6 +560,21 @@ export function getAllTeams(success, error) {
});
}
+export function getMeLoggedIn(success, error) {
+ return $.ajax({
+ cache: false,
+ url: '/api/v1/users/me_logged_in',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getMeLoggedIn', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function getMe(success, error) {
var currentUser = null;
$.ajax({
@@ -635,38 +681,6 @@ export function findTeamByName(teamName, success, error) {
});
}
-export function findTeamsSendEmail(email, success, error) {
- $.ajax({
- url: '/api/v1/teams/email_teams',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({email: email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('findTeamsSendEmail', xhr, status, err);
- error(e);
- }
- });
-
- track('api', 'api_teams_email_teams');
-}
-
-export function findTeams(email, success, error) {
- $.ajax({
- url: '/api/v1/teams/find_teams',
- dataType: 'json',
- contentType: 'application/json',
- type: 'POST',
- data: JSON.stringify({email: email}),
- success,
- error: function onError(xhr, status, err) {
- var e = handleError('findTeams', xhr, status, err);
- error(e);
- }
- });
-}
-
export function createChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/create',
@@ -835,7 +849,7 @@ export function updateLastViewedAt(channelId, success, error) {
}
export function getChannels(success, error) {
- $.ajax({
+ return $.ajax({
cache: false,
url: '/api/v1/channels/',
dataType: 'json',
@@ -901,7 +915,7 @@ export function getChannelExtraInfo(id, memberLimit, success, error) {
url += '/' + memberLimit;
}
- $.ajax({
+ return $.ajax({
url,
dataType: 'json',
contentType: 'application/json',
@@ -1018,7 +1032,7 @@ export function getPostsPage(channelId, offset, limit, success, error, complete)
}
export function getPosts(channelId, since, success, error, complete) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/channels/' + channelId + '/posts/' + since,
dataType: 'json',
type: 'GET',
@@ -1347,7 +1361,7 @@ export function getStatuses(ids, success, error) {
}
export function getMyTeam(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/teams/me',
dataType: 'json',
type: 'GET',
@@ -1437,7 +1451,7 @@ export function listIncomingHooks(success, error) {
}
export function getAllPreferences(success, error) {
- $.ajax({
+ return $.ajax({
url: '/api/v1/preferences/',
dataType: 'json',
type: 'GET',
@@ -1569,3 +1583,68 @@ export function removeLicenseFile(success, error) {
track('api', 'api_license_upload');
}
+
+export function getClientLicenceConfig(success, error) {
+ return $.ajax({
+ url: '/api/v1/license/client_config',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getClientLicenceConfig', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
+export function getInviteInfo(success, error, id) {
+ $.ajax({
+ url: '/api/v1/teams/get_invite_info',
+ type: 'POST',
+ dataType: 'json',
+ contentType: 'application/json',
+ data: JSON.stringify({invite_id: id}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getInviteInfo', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
+
+export function verifyEmail(success, error, uid, hid) {
+ $.ajax({
+ url: '/api/v1/users/verify_email',
+ type: 'POST',
+ contentType: 'application/json',
+ dataType: 'text',
+ data: JSON.stringify({uid, hid}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('verifyEmail', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
+
+export function resendVerification(success, error, teamName, email) {
+ $.ajax({
+ url: '/api/v1/users/resend_verification',
+ type: 'POST',
+ contentType: 'application/json',
+ dataType: 'text',
+ data: JSON.stringify({team_name: teamName, email}),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('resendVerification', xhr, status, err);
+ if (error) {
+ error(e);
+ }
+ }
+ });
+}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index daea9f43e..2cff4dbed 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -42,13 +42,15 @@ export default {
RECEIVED_MSG: null,
- RECEIVED_TEAM: null,
+ RECEIVED_MY_TEAM: null,
RECEIVED_CONFIG: null,
RECEIVED_LOGS: null,
RECEIVED_SERVER_AUDITS: null,
RECEIVED_ALL_TEAMS: null,
+ RECEIVED_LOCALE: null,
+
SHOW_SEARCH: null,
TOGGLE_IMPORT_THEME_MODAL: null,
@@ -143,6 +145,7 @@ export default {
EMAIL_SERVICE: 'email',
SIGNIN_CHANGE: 'signin_change',
SIGNIN_VERIFIED: 'verified',
+ SESSION_EXPIRED: 'expired',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_FOCUS_CONTEXT_RADIUS: 10,
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 6942a8e08..88777164b 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -2,9 +2,10 @@
// See License.txt for license information.
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as GlobalActions from '../action_creators/global_actions.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import UserStore from '../stores/user_store.jsx';
+import LocalizationStore from '../stores/localization_store.jsx';
import PreferenceStore from '../stores/preference_store.jsx';
import TeamStore from '../stores/team_store.jsx';
import Constants from '../utils/constants.jsx';
@@ -941,7 +942,7 @@ export function updateAddressBar(channelName) {
}
export function switchChannel(channel) {
- EventHelpers.emitChannelClickEvent(channel);
+ GlobalActions.emitChannelClickEvent(channel);
updateAddressBar(channel.name);
@@ -1130,8 +1131,8 @@ export function fileSizeToString(bytes) {
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
export function getFileUrl(filename, isDownload) {
- const downloadParam = isDownload ? '&download=1' : '';
- return getWindowLocationOrigin() + '/api/v1/files/get' + filename + '?' + getSessionIndex() + downloadParam;
+ const downloadParam = isDownload ? '?download=1' : '';
+ return getWindowLocationOrigin() + '/api/v1/files/get' + filename + downloadParam;
}
// Gets the name of a file (including extension) from a given url or file path.
@@ -1151,14 +1152,6 @@ export function getWebsocketPort(protocol) {
return '';
}
-export function getSessionIndex() {
- if (global.window.mm_session_token_index >= 0) {
- return 'session_token_index=' + global.window.mm_session_token_index;
- }
-
- return '';
-}
-
// Generates a RFC-4122 version 4 compliant globally unique identifier.
export function generateId() {
// implementation taken from http://stackoverflow.com/a/2117523
@@ -1405,3 +1398,19 @@ export function isPostEphemeral(post) {
export function getRootId(post) {
return post.root_id === '' ? post.id : post.root_id;
}
+
+export function localizeMessage(id, defaultMessage) {
+ const translations = LocalizationStore.getTranslations();
+ if (translations) {
+ const value = translations[id];
+ if (value) {
+ return value;
+ }
+ }
+
+ if (defaultMessage) {
+ return defaultMessage;
+ }
+
+ return id;
+}