diff options
Diffstat (limited to 'webapp')
152 files changed, 6045 insertions, 5416 deletions
diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json index 13c9ee97f..3eb02cc40 100644 --- a/webapp/.eslintrc.json +++ b/webapp/.eslintrc.json @@ -19,6 +19,13 @@ "jquery": true, "es6": true }, + "globals": { + "jest": true, + "describe": true, + "it": true, + "expect": true, + "before": true + }, "rules": { "comma-dangle": [2, "never"], "array-callback-return": 2, diff --git a/webapp/Makefile b/webapp/Makefile index 6ec75d1df..b0c2c831a 100644 --- a/webapp/Makefile +++ b/webapp/Makefile @@ -1,6 +1,6 @@ .PHONY: build test run clean stop -test: +test: .npminstall @echo Checking for style guide compliance npm run check diff --git a/webapp/action_creators/global_actions.jsx b/webapp/action_creators/global_actions.jsx index fd447ec93..5641246f9 100644 --- a/webapp/action_creators/global_actions.jsx +++ b/webapp/action_creators/global_actions.jsx @@ -4,11 +4,16 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import PostStore from 'stores/post_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import BrowserStore from 'stores/browser_store.jsx'; +import ErrorStore from 'stores/error_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import PreferenceStore from 'stores/preference_store.jsx'; import SearchStore from 'stores/search_store.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import * as Websockets from './websocket_actions.jsx'; import * as I18n from 'i18n/i18n.jsx'; @@ -21,7 +26,6 @@ export function emitChannelClickEvent(channel) { function userVisitedFakeChannel(chan, success, fail) { const otherUserId = Utils.getUserIdFromChannelName(chan); Client.createDirectChannel( - chan, otherUserId, (data) => { success(data); @@ -61,6 +65,69 @@ export function emitChannelClickEvent(channel) { } } +export function emitInitialLoad(callback) { + Client.getInitialLoad( + (data) => { + global.window.mm_config = data.client_cfg; + global.window.mm_license = data.license_cfg; + + UserStore.setNoAccounts(data.no_accounts); + + if (data.user && data.user.id) { + global.window.mm_user = data.user; + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_ME, + me: data.user + }); + } + + if (data.preferences) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_PREFERENCES, + preferences: data.preferences + }); + } + + if (data.teams) { + var teams = {}; + data.teams.forEach((team) => { + teams[team.id] = team; + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_ALL_TEAMS, + teams + }); + } + + if (data.team_members) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_TEAM_MEMBERS, + team_members: data.team_members + }); + } + + if (data.direct_profiles) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_DIRECT_PROFILES, + profiles: data.direct_profiles + }); + } + + if (callback) { + callback(); + } + }, + (err) => { + AsyncClient.dispatchError(err, 'getInitialLoad'); + + if (callback) { + callback(); + } + } + ); +} + export function emitPostFocusEvent(postId) { AsyncClient.getChannels(true); Client.getPostById( @@ -80,6 +147,18 @@ export function emitPostFocusEvent(postId) { ); } +export function emitCloseRightHandSide() { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_SEARCH, + results: null + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POST_SELECTED, + postId: null + }); +} + export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) { Client.getPost( post.channel_id, @@ -133,21 +212,21 @@ export function emitLoadMorePostsFocusedBottomEvent() { AsyncClient.getPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE); } -export function emitPostRecievedEvent(post, websocketMessageProps) { +export function emitPostRecievedEvent(post, msg) { if (ChannelStore.getCurrentId() === post.channel_id) { if (window.isActive) { AsyncClient.updateLastViewedAt(); } else { AsyncClient.getChannel(post.channel_id); } - } else { + } else if (msg && TeamStore.getCurrentId() === msg.team_id) { AsyncClient.getChannel(post.channel_id); } AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POST, post, - websocketMessageProps + websocketMessageProps: msg.props }); } @@ -271,10 +350,6 @@ export function sendEphemeralPost(message, channelId) { emitPostRecievedEvent(post); } -export function loadTeamRequiredPage() { - AsyncClient.getAllTeams(); -} - export function newLocalizationSelected(locale) { if (locale === 'en') { AppDispatcher.handleServerAction({ @@ -311,8 +386,6 @@ export function loadBrowserLocale() { 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(); @@ -335,3 +408,21 @@ export function emitRemoteUserTypingEvent(channelId, userId, postParentId) { postParentId }); } + +export function emitUserLoggedOutEvent(redirectTo) { + const rURL = (redirectTo && typeof redirectTo === 'string') ? redirectTo : '/'; + Client.logout( + () => { + BrowserStore.signalLogout(); + BrowserStore.clear(); + ErrorStore.clearLastError(); + PreferenceStore.clear(); + UserStore.clear(); + TeamStore.clear(); + browserHistory.push(rURL); + }, + () => { + browserHistory.push(rURL); + } + ); +} diff --git a/webapp/action_creators/websocket_actions.jsx b/webapp/action_creators/websocket_actions.jsx index a66d79d18..c4e9c63c2 100644 --- a/webapp/action_creators/websocket_actions.jsx +++ b/webapp/action_creators/websocket_actions.jsx @@ -3,12 +3,14 @@ import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; import PostStore from 'stores/post_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import ErrorStore from 'stores/error_store.jsx'; import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; @@ -31,7 +33,7 @@ export function initialize() { protocol = 'wss://'; } - const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + '/api/v1/websocket'; + const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + Client.getUsersRoute() + '/websocket'; if (connectFailCount === 0) { console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console @@ -145,6 +147,11 @@ function handleMessage(msg) { export function sendMessage(msg) { if (conn && conn.readyState === WebSocket.OPEN) { + var teamId = TeamStore.getCurrentId(); + if (teamId && teamId.length > 0) { + msg.team_id = teamId; + } + conn.send(JSON.stringify(msg)); } else if (!conn || conn.readyState === WebSocket.Closed) { conn = null; @@ -161,7 +168,7 @@ export function close() { function handleNewPostEvent(msg) { const post = JSON.parse(msg.props.post); - GlobalActions.emitPostRecievedEvent(post, msg.props); + GlobalActions.emitPostRecievedEvent(post, msg); } function handlePostEditEvent(msg) { @@ -193,7 +200,7 @@ function handleUserAddedEvent(msg) { AsyncClient.getChannelExtraInfo(); } - if (UserStore.getCurrentId() === msg.user_id) { + if (TeamStore.getCurrentId() === msg.team_id && UserStore.getCurrentId() === msg.user_id) { AsyncClient.getChannel(msg.channel_id); } } @@ -219,7 +226,7 @@ function handleUserRemovedEvent(msg) { function handleChannelViewedEvent(msg) { // Useful for when multiple devices have the app open to different channels - if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) { + if (TeamStore.getCurrentId() === msg.team_id && ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) { AsyncClient.getChannel(msg.channel_id); } } @@ -230,5 +237,7 @@ function handlePreferenceChangedEvent(msg) { } function handleUserTypingEvent(msg) { - GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id); + if (TeamStore.getCurrentId() === msg.team_id) { + GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id); + } } diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx new file mode 100644 index 000000000..98e660227 --- /dev/null +++ b/webapp/client/client.jsx @@ -0,0 +1,1463 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import request from 'superagent'; + +const HEADER_X_VERSION_ID = 'x-version-id'; +const HEADER_TOKEN = 'token'; +const HEADER_BEARER = 'BEARER'; +const HEADER_AUTH = 'Authorization'; + +export default class Client { + constructor() { + this.teamId = ''; + this.serverVersion = ''; + this.logToConsole = false; + this.useToken = false; + this.token = ''; + this.url = ''; + this.urlVersion = '/api/v3'; + this.defaultHeaders = { + 'X-Requested-With': 'XMLHttpRequest' + }; + + this.translations = { + connectionError: 'There appears to be a problem with your internet connection.', + unknownError: 'We received an unexpected status code from the server.' + }; + } + + setUrl = (url) => { + this.url = url; + } + + setTeamId = (id) => { + this.teamId = id; + } + + getTeamId = () => { + if (this.teamId === '') { + console.error('You are trying to use a route that requires a team_id, but you have not called setTeamId() in client.jsx'); // eslint-disable-line no-console + } + + return this.teamId; + } + + getServerVersion = () => { + return this.serverVersion; + } + + getBaseRoute() { + return `${this.url}${this.urlVersion}`; + } + + getAdminRoute() { + return `${this.url}${this.urlVersion}/admin`; + } + + getLicenseRoute() { + return `${this.url}${this.urlVersion}/license`; + } + + getTeamsRoute() { + return `${this.url}${this.urlVersion}/teams`; + } + + getTeamNeededRoute() { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}`; + } + + getChannelsRoute() { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels`; + } + + getChannelNeededRoute(channelId) { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels/${channelId}`; + } + + getCommandsRoute() { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/commands`; + } + + getHooksRoute() { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/hooks`; + } + + getPostsRoute(channelId) { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/channels/${channelId}/posts`; + } + + getUsersRoute() { + return `${this.url}${this.urlVersion}/users`; + } + + getFilesRoute() { + return `${this.url}${this.urlVersion}/teams/${this.getTeamId()}/files`; + } + + getOAuthRoute() { + return `${this.url}${this.urlVersion}/oauth`; + } + + getUserNeededRoute(userId) { + return `${this.url}${this.urlVersion}/users/${userId}`; + } + + setTranslations = (messages) => { + this.translations = messages; + } + + enableLogErrorsToConsole = (enabled) => { + this.logToConsole = enabled; + } + + useHeaderToken = () => { + this.useToken = true; + if (this.token !== '') { + this.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${this.token}`; + } + } + + track = (category, action, label, property, value) => { // eslint-disable-line no-unused-vars + // NO-OP for inherited classes to override + } + + trackPage = () => { + // NO-OP for inherited classes to override + } + + handleError = (err, res) => { // eslint-disable-line no-unused-vars + // NO-OP for inherited classes to override + } + + handleResponse = (methodName, successCallback, errorCallback, err, res) => { + if (res && res.header) { + this.serverVersion = res.header[HEADER_X_VERSION_ID]; + if (res.header[HEADER_X_VERSION_ID]) { + this.serverVersion = res.header[HEADER_X_VERSION_ID]; + } + } + + if (err) { + // test to make sure it looks like a server JSON error response + var e = null; + if (res && res.body && res.body.id) { + e = res.body; + } + + var msg = ''; + + if (e) { + msg = 'method=' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id; + } else { + msg = 'method=' + methodName + ' status=' + err.status + ' statusCode=' + err.statusCode + ' err=' + err; + + if (err.status === 0 || !err.status) { + e = {message: this.translations.connectionError}; + } else { + e = {message: this.translations.unknownError + ' (' + err.status + ')'}; + } + } + + if (this.logToConsole) { + console.error(msg); // eslint-disable-line no-console + console.error(e); // eslint-disable-line no-console + } + + this.track('api', 'api_weberror', methodName, 'message', msg); + + this.handleError(err, res); + + if (errorCallback) { + errorCallback(e, err, res); + return; + } + } + + if (successCallback) { + successCallback(res.body, res); + } + } + + // General / Admin / Licensing Routes Section + + getTranslations = (url, success, error) => { + return request. + get(url). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getTranslations', success, error)); + } + + getClientConfig = (success, error) => { + return request. + get(`${this.getAdminRoute()}/client_props`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getClientConfig', success, error)); + } + + getComplianceReports = (success, error) => { + return request. + get(`${this.getAdminRoute()}/compliance_reports`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getComplianceReports', success, error)); + } + + uploadBrandImage = (image, success, error) => { + request. + post(`${this.getAdminRoute()}/upload_brand_image`). + set(this.defaultHeaders). + accept('application/json'). + attach('image', image, image.name). + end(this.handleResponse.bind(this, 'uploadBrandImage', success, error)); + } + + saveComplianceReports = (job, success, error) => { + return request. + post(`${this.getAdminRoute()}/save_compliance_report`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(job). + end(this.handleResponse.bind(this, 'saveComplianceReports', success, error)); + } + + getLogs = (success, error) => { + return request. + get(`${this.getAdminRoute()}/logs`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getLogs', success, error)); + } + + getServerAudits = (success, error) => { + return request. + get(`${this.getAdminRoute()}/audits`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getServerAudits', success, error)); + } + + getConfig = (success, error) => { + return request. + get(`${this.getAdminRoute()}/config`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getConfig', success, error)); + } + + getAnalytics = (name, teamId, success, error) => { + let url = `${this.getAdminRoute()}/analytics/`; + if (teamId == null) { + url += name; + } else { + url += teamId + '/' + name; + } + + return request. + get(url). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getAnalytics', success, error)); + } + + getTeamAnalytics = (teamId, name, success, error) => { + return request. + get(`${this.getAdminRoute()}/analytics/${teamId}/${name}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getTeamAnalytics', success, error)); + } + + saveConfig = (config, success, error) => { + request. + post(`${this.getAdminRoute()}/save_config`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(config). + end(this.handleResponse.bind(this, 'saveConfig', success, error)); + } + + testEmail = (config, success, error) => { + request. + post(`${this.getAdminRoute()}/test_email`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(config). + end(this.handleResponse.bind(this, 'testEmail', success, error)); + } + + logClientError = (msg) => { + var l = {}; + l.level = 'ERROR'; + l.message = msg; + + request. + post(`${this.getAdminRoute()}/log_client`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(l). + end(this.handleResponse.bind(this, 'logClientError', null, null)); + } + + getClientLicenceConfig = (success, error) => { + request. + get(`${this.getLicenseRoute()}/client_config`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getClientLicenceConfig', success, error)); + } + + removeLicenseFile = (success, error) => { + request. + post(`${this.getLicenseRoute()}/remove`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'removeLicenseFile', success, error)); + } + + uploadLicenseFile = (license, success, error) => { + request. + post(`${this.getLicenseRoute()}/add`). + set(this.defaultHeaders). + accept('application/json'). + attach('license', license, license.name). + end(this.handleResponse.bind(this, 'uploadLicenseFile', success, error)); + + this.track('api', 'api_license_upload'); + } + + importSlack = (fileData, success, error) => { + request. + post(`${this.getTeamsRoute()}/import_team`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(fileData). + end(this.handleResponse.bind(this, 'importSlack', success, error)); + } + + exportTeam = (success, error) => { + request. + get(`${this.getTeamsRoute()}/export_team`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'exportTeam', success, error)); + } + + signupTeam = (email, success, error) => { + request. + post(`${this.getTeamsRoute()}/signup`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({email}). + end(this.handleResponse.bind(this, 'signupTeam', success, error)); + + this.track('api', 'api_teams_signup'); + } + + adminResetMfa = (userId, success, error) => { + const data = {}; + data.user_id = userId; + + request. + post(`${this.getAdminRoute()}/reset_mfa`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'adminResetMfa', success, error)); + } + + adminResetPassword = (userId, newPassword, success, error) => { + var data = {}; + data.new_password = newPassword; + data.user_id = userId; + + request. + post(`${this.getAdminRoute()}/reset_password`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'adminResetPassword', success, error)); + + this.track('api', 'api_admin_reset_password'); + } + + // Team Routes Section + + createTeamFromSignup = (teamSignup, success, error) => { + request. + post(`${this.getTeamsRoute()}/create_from_signup`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(teamSignup). + end(this.handleResponse.bind(this, 'createTeamFromSignup', success, error)); + } + + findTeamByName = (teamName, success, error) => { + request. + post(`${this.getTeamsRoute()}/find_team_by_name`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({name: teamName}). + end(this.handleResponse.bind(this, 'findTeamByName', success, error)); + } + + createTeam = (team, success, error) => { + request. + post(`${this.getTeamsRoute()}/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(team). + end(this.handleResponse.bind(this, 'createTeam', success, error)); + + this.track('api', 'api_users_create', '', 'email', team.name); + } + + updateTeam = (team, success, error) => { + request. + post(`${this.getTeamNeededRoute()}/update`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(team). + end(this.handleResponse.bind(this, 'updateTeam', success, error)); + + this.track('api', 'api_teams_update_name'); + } + + getAllTeams = (success, error) => { + request. + get(`${this.getTeamsRoute()}/all`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getAllTeams', success, error)); + } + + getAllTeamListings = (success, error) => { + request. + get(`${this.getTeamsRoute()}/all_team_listings`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getAllTeamListings', success, error)); + } + + getMyTeam = (success, error) => { + request. + get(`${this.getTeamNeededRoute()}/me`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getMyTeam', success, error)); + } + + getTeamMembers = (teamId, success, error) => { + request. + get(`${this.getTeamsRoute()}/members/${teamId}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getTeamMembers', success, error)); + } + + inviteMembers = (data, success, error) => { + request. + post(`${this.getTeamNeededRoute()}/invite_members`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'inviteMembers', success, error)); + + this.track('api', 'api_teams_invite_members'); + } + + addUserToTeam = (userId, success, error) => { + request. + post(`${this.getTeamNeededRoute()}/add_user_to_team`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({user_id: userId}). + end(this.handleResponse.bind(this, 'addUserToTeam', success, error)); + + this.track('api', 'api_teams_invite_members'); + } + + addUserToTeamFromInvite = (data, hash, inviteId, success, error) => { + request. + post(`${this.getTeamsRoute()}/add_user_to_team_from_invite`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({hash, data, invite_id: inviteId}). + end(this.handleResponse.bind(this, 'addUserToTeam', success, error)); + + this.track('api', 'api_teams_invite_members'); + } + + getInviteInfo = (inviteId, success, error) => { + request. + post(`${this.getTeamsRoute()}/get_invite_info`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({invite_id: inviteId}). + end(this.handleResponse.bind(this, 'getInviteInfo', success, error)); + } + + // User Routes Setions + + createUser = (user, success, error) => { + this.createUserWithInvite(user, null, null, null, success, error); + } + + createUserWithInvite = (user, data, emailHash, inviteId, success, error) => { + var url = `${this.getUsersRoute()}/create`; + + if (data || emailHash || inviteId) { + url += '?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash) + '&iid=' + encodeURIComponent(inviteId); + } + + request. + post(url). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(user). + end(this.handleResponse.bind(this, 'createUser', success, error)); + + this.track('api', 'api_users_create', '', 'email', user.email); + } + + updateUser = (user, success, error) => { + request. + post(`${this.getUsersRoute()}/update`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(user). + end(this.handleResponse.bind(this, 'updateUser', success, error)); + + this.track('api', 'api_users_update'); + } + + updatePassword = (userId, currentPassword, newPassword, success, error) => { + var data = {}; + data.user_id = userId; + data.current_password = currentPassword; + data.new_password = newPassword; + + request. + post(`${this.getUsersRoute()}/newpassword`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updatePassword', success, error)); + + this.track('api', 'api_users_newpassword'); + } + + updateUserNotifyProps = (notifyProps, success, error) => { + request. + post(`${this.getUsersRoute()}/update_notify`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(notifyProps). + end(this.handleResponse.bind(this, 'updateUserNotifyProps', success, error)); + } + + updateRoles = (userId, newRoles, success, error) => { + var data = { + user_id: userId, + new_roles: newRoles + }; + + request. + post(`${this.getUsersRoute()}/update_roles`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateRoles', success, error)); + + this.track('api', 'api_users_update_roles'); + } + + updateActive = (userId, active, success, error) => { + var data = {}; + data.user_id = userId; + data.active = '' + active; + + request. + post(`${this.getUsersRoute()}/update_active`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateActive', success, error)); + + this.track('api', 'api_users_update_roles'); + } + + sendPasswordReset = (email, success, error) => { + var data = {}; + data.email = email; + + request. + post(`${this.getUsersRoute()}/send_password_reset`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'sendPasswordReset', success, error)); + + this.track('api', 'api_users_send_password_reset'); + } + + resetPassword = (code, newPassword, success, error) => { + var data = {}; + data.new_password = newPassword; + data.code = code; + + request. + post(`${this.getUsersRoute()}/reset_password`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'resetPassword', success, error)); + + this.track('api', 'api_users_reset_password'); + } + + emailToOAuth = (email, password, service, success, error) => { + var data = {}; + data.password = password; + data.email = email; + data.service = service; + + request. + post(`${this.getUsersRoute()}/claim/email_to_oauth`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'emailToOAuth', success, error)); + + this.track('api', 'api_users_email_to_oauth'); + } + + oauthToEmail = (email, password, success, error) => { + var data = {}; + data.password = password; + data.email = email; + + request. + post(`${this.getUsersRoute()}/claim/oauth_to_email`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'oauthToEmail', success, error)); + + this.track('api', 'api_users_oauth_to_email'); + } + + emailToLdap = (email, password, ldapId, ldapPassword, success, error) => { + var data = {}; + data.email_password = password; + data.email = email; + data.ldap_id = ldapId; + data.ldap_password = ldapPassword; + + request. + post(`${this.getUsersRoute()}/claim/email_to_ldap`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'emailToLdap', success, error)); + + this.track('api', 'api_users_email_to_ldap'); + } + + ldapToEmail = (email, emailPassword, ldapPassword, success, error) => { + var data = {}; + data.email = email; + data.ldap_password = ldapPassword; + data.email_password = emailPassword; + + request. + post(`${this.getUsersRoute()}/claim/ldap_to_email`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'ldapToEmail', success, error)); + + this.track('api', 'api_users_oauth_to_email'); + } + + getInitialLoad = (success, error) => { + request. + get(`${this.getUsersRoute()}/initial_load`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getInitialLoad', success, error)); + } + + getMe = (success, error) => { + request. + get(`${this.getUsersRoute()}/me`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getMe', success, error)); + } + + login = (email, username, password, mfaToken, success, error) => { + var outer = this; // eslint-disable-line consistent-this + + request. + post(`${this.getUsersRoute()}/login`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({email, password, username, token: mfaToken}). + end(this.handleResponse.bind( + this, + 'login', + (data, res) => { + if (res && res.header) { + outer.token = res.header[HEADER_TOKEN]; + + if (outer.useToken) { + outer.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${outer.token}`; + } + } + + if (success) { + success(data, res); + } + }, + error + )); + + this.track('api', 'api_users_login', '', 'email', email); + } + + loginByLdap = (ldapId, password, mfaToken, success, error) => { + var outer = this; // eslint-disable-line consistent-this + + request. + post(`${this.getUsersRoute()}/login_ldap`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: ldapId, password, token: mfaToken}). + end(this.handleResponse.bind( + this, + 'loginByLdap', + (data, res) => { + if (res && res.header) { + outer.token = res.header[HEADER_TOKEN]; + + if (outer.useToken) { + outer.defaultHeaders[HEADER_AUTH] = `${HEADER_BEARER} ${outer.token}`; + } + } + + if (success) { + success(data, res); + } + }, + error + )); + + this.track('api', 'api_users_loginLdap', '', 'email', ldapId); + } + + logout = (success, error) => { + request. + post(`${this.getUsersRoute()}/logout`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'logout', success, error)); + + this.track('api', 'api_users_logout'); + } + + checkMfa = (method, loginId, success, error) => { + var data = {}; + data.method = method; + data.login_id = loginId; + + request. + post(`${this.getUsersRoute()}/mfa`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'checkMfa', success, error)); + + this.track('api', 'api_users_oauth_to_email'); + } + + revokeSession = (altId, success, error) => { + request. + post(`${this.getUsersRoute()}/revoke_session`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: altId}). + end(this.handleResponse.bind(this, 'revokeSession', success, error)); + } + + getSessions = (userId, success, error) => { + request. + get(`${this.getUserNeededRoute(userId)}/sessions`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getSessions', success, error)); + } + + getAudits = (userId, success, error) => { + request. + get(`${this.getUserNeededRoute(userId)}/audits`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getAudits', success, error)); + } + + getDirectProfiles = (success, error) => { + request. + get(`${this.getUsersRoute()}/direct_profiles`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getDirectProfiles', success, error)); + } + + getProfiles = (success, error) => { + request. + get(`${this.getUsersRoute()}/profiles/${this.getTeamId()}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getProfiles', success, error)); + } + + getProfilesForTeam = (teamId, success, error) => { + request. + get(`${this.getUsersRoute()}/profiles/${teamId}?skip_direct=true`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getProfilesForTeam', success, error)); + } + + getStatuses = (ids, success, error) => { + request. + post(`${this.getUsersRoute()}/status`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(ids). + end(this.handleResponse.bind(this, 'getStatuses', success, error)); + } + + verifyEmail = (uid, hid, success, error) => { + request. + post(`${this.getUsersRoute()}/verify_email`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({uid, hid}). + end(this.handleResponse.bind(this, 'verifyEmail', success, error)); + } + + resendVerification = (email, success, error) => { + request. + post(`${this.getUsersRoute()}/resend_verification`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({email}). + end(this.handleResponse.bind(this, 'resendVerification', success, error)); + } + + updateMfa = (token, activate, success, error) => { + const data = {}; + data.activate = activate; + data.token = token; + + request. + post(`${this.getUsersRoute()}/update_mfa`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateMfa', success, error)); + } + + uploadProfileImage = (image, success, error) => { + request. + post(`${this.getUsersRoute()}/newimage`). + set(this.defaultHeaders). + attach('image', image, image.name). + accept('application/json'). + end(this.handleResponse.bind(this, 'uploadProfileImage', success, error)); + } + + // Channel Routes Section + + createChannel = (channel, success, error) => { + request. + post(`${this.getChannelsRoute()}/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(channel). + end(this.handleResponse.bind(this, 'createChannel', success, error)); + + this.track('api', 'api_channels_create', channel.type, 'name', channel.name); + } + + createDirectChannel = (userId, success, error) => { + request. + post(`${this.getChannelsRoute()}/create_direct`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({user_id: userId}). + end(this.handleResponse.bind(this, 'createDirectChannel', success, error)); + } + + updateChannel = (channel, success, error) => { + request. + post(`${this.getChannelsRoute()}/update`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(channel). + end(this.handleResponse.bind(this, 'updateChannel', success, error)); + + this.track('api', 'api_channels_update'); + } + + updateChannelHeader = (channelId, header, success, error) => { + const data = { + channel_id: channelId, + channel_header: header + }; + + request. + post(`${this.getChannelsRoute()}/update_header`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateChannel', success, error)); + + this.track('api', 'api_channels_header'); + } + + updateChannelPurpose = (channelId, purpose, success, error) => { + const data = { + channel_id: channelId, + channel_purpose: purpose + }; + + request. + post(`${this.getChannelsRoute()}/update_purpose`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateChannelPurpose', success, error)); + + this.track('api', 'api_channels_purpose'); + } + + updateChannelNotifyProps = (data, success, error) => { + request. + post(`${this.getChannelsRoute()}/update_notify_props`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'updateChannelNotifyProps', success, error)); + } + + leaveChannel = (channelId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/leave`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'leaveChannel', success, error)); + + this.track('api', 'api_channels_leave'); + } + + joinChannel = (channelId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/join`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'joinChannel', success, error)); + + this.track('api', 'api_channels_join'); + } + + deleteChannel = (channelId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/delete`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'deleteChannel', success, error)); + + this.track('api', 'api_channels_delete'); + } + + updateLastViewedAt = (channelId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/update_last_viewed_at`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'updateLastViewedAt', success, error)); + } + + getChannels = (success, error) => { + request. + get(`${this.getChannelsRoute()}/`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getChannels', success, error)); + } + + getChannel = (channelId, success, error) => { + request. + get(`${this.getChannelNeededRoute(channelId)}/`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getChannel', success, error)); + + this.track('api', 'api_channel_get'); + } + + getMoreChannels = (success, error) => { + request. + get(`${this.getChannelsRoute()}/more`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getMoreChannels', success, error)); + } + + getChannelCounts = (success, error) => { + request. + get(`${this.getChannelsRoute()}/counts`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getChannelCounts', success, error)); + } + + getChannelExtraInfo = (channelId, memberLimit, success, error) => { + var url = `${this.getChannelNeededRoute(channelId)}/extra_info`; + if (memberLimit) { + url += '/' + memberLimit; + } + + request. + get(url). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getChannelExtraInfo', success, error)); + } + + addChannelMember = (channelId, userId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/add`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({user_id: userId}). + end(this.handleResponse.bind(this, 'addChannelMember', success, error)); + + this.track('api', 'api_channels_add_member'); + } + + removeChannelMember = (channelId, userId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/remove`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({user_id: userId}). + end(this.handleResponse.bind(this, 'removeChannelMember', success, error)); + + this.track('api', 'api_channels_remove_member'); + } + + // Routes for Commands + + listCommands = (success, error) => { + request. + get(`${this.getCommandsRoute()}/list`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'listCommands', success, error)); + } + + executeCommand = (channelId, command, suggest, success, error) => { + request. + post(`${this.getCommandsRoute()}/execute`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({channelId, command, suggest: '' + suggest}). + end(this.handleResponse.bind(this, 'executeCommand', success, error)); + } + + addCommand = (command, success, error) => { + request. + post(`${this.getCommandsRoute()}/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(command). + end(this.handleResponse.bind(this, 'addCommand', success, error)); + } + + deleteCommand = (commandId, success, error) => { + request. + post(`${this.getCommandsRoute()}/delete`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: commandId}). + end(this.handleResponse.bind(this, 'deleteCommand', success, error)); + } + + listTeamCommands = (success, error) => { + request. + get(`${this.getCommandsRoute()}/list_team_commands`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'listTeamCommands', success, error)); + } + + regenCommandToken = (commandId, suggest, success, error) => { + request. + post(`${this.getCommandsRoute()}/regen_token`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: commandId}). + end(this.handleResponse.bind(this, 'regenCommandToken', success, error)); + } + + // Routes for Posts + + createPost = (post, success, error) => { + request. + post(`${this.getPostsRoute(post.channel_id)}/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(post). + end(this.handleResponse.bind(this, 'createPost', success, error)); + + this.track('api', 'api_posts_create', post.channel_id, 'length', post.message.length); + } + + getPostById = (postId, success, error) => { + request. + get(`${this.getTeamNeededRoute()}/posts/${postId}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPostById', success, error)); + } + + getPost = (channelId, postId, success, error) => { + request. + get(`${this.getChannelNeededRoute(channelId)}/posts/${postId}/get`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPost', success, error)); + } + + updatePost = (post, success, error) => { + request. + post(`${this.getPostsRoute(post.channel_id)}/update`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(post). + end(this.handleResponse.bind(this, 'updatePost', success, error)); + + this.track('api', 'api_posts_update'); + } + + deletePost = (channelId, postId, success, error) => { + request. + post(`${this.getChannelNeededRoute(channelId)}/posts/${postId}/delete`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'deletePost', success, error)); + + this.track('api', 'api_posts_delete'); + } + + search = (terms, success, error) => { + request. + get(`${this.getTeamNeededRoute()}/posts/search`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + query({terms}). + end(this.handleResponse.bind(this, 'search', success, error)); + + this.track('api', 'api_posts_search'); + } + + getPostsPage = (channelId, offset, limit, success, error) => { + request. + get(`${this.getPostsRoute(channelId)}/page/${offset}/${limit}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPostsPage', success, error)); + } + + getPosts = (channelId, since, success, error) => { + request. + get(`${this.getPostsRoute(channelId)}/since/${since}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPosts', success, error)); + } + + getPostsBefore = (channelId, postId, offset, numPost, success, error) => { + request. + get(`${this.getPostsRoute(channelId)}/${postId}/before/${offset}/${numPost}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPostsBefore', success, error)); + } + + getPostsAfter = (channelId, postId, offset, numPost, success, error) => { + request. + get(`${this.getPostsRoute(channelId)}/${postId}/after/${offset}/${numPost}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPostsAfter', success, error)); + } + + // Routes for Files + + getFileInfo = (filename, success, error) => { + request. + get(`${this.getFilesRoute()}/get_info${filename}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getFileInfo', success, error)); + } + + getPublicLink = (data, success, error) => { + request. + post(`${this.getFilesRoute()}/get_public_link`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(data). + end(this.handleResponse.bind(this, 'getPublicLink', success, error)); + } + + uploadFile = (file, filename, channelId, clientId, success, error) => { + return request. + post(`${this.getFilesRoute()}/upload`). + set(this.defaultHeaders). + attach('files', file, filename). + field('channel_id', channelId). + field('client_ids', clientId). + accept('application/json'). + end(this.handleResponse.bind(this, 'uploadFile', success, error)); + } + + // Routes for OAuth + + registerOAuthApp = (app, success, error) => { + request. + post(`${this.getOAuthRoute()}/register`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(app). + end(this.handleResponse.bind(this, 'registerOAuthApp', success, error)); + + this.track('api', 'api_apps_register'); + } + + allowOAuth2 = (responseType, clientId, redirectUri, state, scope, success, error) => { + request. + get(`${this.getOAuthRoute()}/allow`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + query({response_type: responseType}). + query({client_id: clientId}). + query({redirect_uri: redirectUri}). + query({scope}). + query({state}). + end(this.handleResponse.bind(this, 'allowOAuth2', success, error)); + } + + // Routes for Hooks + + addIncomingHook = (hook, success, error) => { + request. + post(`${this.getHooksRoute()}/incoming/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(hook). + end(this.handleResponse.bind(this, 'addIncomingHook', success, error)); + } + + deleteIncomingHook = (hookId, success, error) => { + request. + post(`${this.getHooksRoute()}/incoming/delete`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: hookId}). + end(this.handleResponse.bind(this, 'deleteIncomingHook', success, error)); + } + + listIncomingHooks = (success, error) => { + request. + get(`${this.getHooksRoute()}/incoming/list`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'listIncomingHooks', success, error)); + } + + addOutgoingHook = (hook, success, error) => { + request. + post(`${this.getHooksRoute()}/outgoing/create`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(hook). + end(this.handleResponse.bind(this, 'addOutgoingHook', success, error)); + } + + deleteOutgoingHook = (hookId, success, error) => { + request. + post(`${this.getHooksRoute()}/outgoing/delete`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: hookId}). + end(this.handleResponse.bind(this, 'deleteOutgoingHook', success, error)); + } + + listOutgoingHooks = (success, error) => { + request. + get(`${this.getHooksRoute()}/outgoing/list`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'listOutgoingHooks', success, error)); + } + + regenOutgoingHookToken = (hookId, success, error) => { + request. + post(`${this.getHooksRoute()}/outgoing/regen_token`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send({id: hookId}). + end(this.handleResponse.bind(this, 'regenOutgoingHookToken', success, error)); + } + + //Routes for Prefrecnes + + getAllPreferences = (success, error) => { + request. + get(`${this.getBaseRoute()}/preferences/`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getAllPreferences', success, error)); + } + + savePreferences = (preferences, success, error) => { + request. + post(`${this.getBaseRoute()}/preferences/save`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(preferences). + end(this.handleResponse.bind(this, 'savePreferences', success, error)); + } + + getPreferenceCategory = (category, success, error) => { + request. + get(`${this.getBaseRoute()}/preferences/${category}`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + end(this.handleResponse.bind(this, 'getPreferenceCategory', success, error)); + } +} diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx index f1dd4a26a..d3e5ce66d 100644 --- a/webapp/components/activity_log_modal.jsx +++ b/webapp/components/activity_log_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {Modal} from 'react-bootstrap'; import LoadingScreen from './loading_screen.jsx'; diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx index 729d4b14d..dd56411f4 100644 --- a/webapp/components/admin_console/admin_navbar_dropdown.jsx +++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx @@ -3,27 +3,20 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router'; -function getStateFromStores() { - return {currentTeam: TeamStore.getCurrent()}; -} - import React from 'react'; export default class AdminNavbarDropdown extends React.Component { constructor(props) { super(props); this.blockToggle = false; - - this.state = getStateFromStores(); } componentDidMount() { @@ -64,24 +57,27 @@ export default class AdminNavbarDropdown extends React.Component { > <li> <Link - to={'/' + this.state.currentTeam.name + '/channels/town-square'} + to={'/select_team'} > <FormattedMessage id='admin.nav.switch' defaultMessage='Switch to {display_name}' values={{ - display_name: this.state.currentTeam.display_name + display_name: global.window.mm_config.SiteName }} /> </Link> </li> <li> - <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <FormattedMessage id='admin.nav.logout' defaultMessage='Logout' /> - </Link> + </a> </li> </ul> </li> diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx index 2e6252075..400730030 100644 --- a/webapp/components/admin_console/admin_sidebar_header.jsx +++ b/webapp/components/admin_console/admin_sidebar_header.jsx @@ -4,6 +4,7 @@ import $ from 'jquery'; import AdminNavbarDropdown from './admin_navbar_dropdown.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -41,7 +42,7 @@ export default class SidebarHeader extends React.Component { profilePicture = ( <img className='user__picture' - src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at} /> ); } diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx index 84def2bce..702c1a969 100644 --- a/webapp/components/admin_console/compliance_reports.jsx +++ b/webapp/components/admin_console/compliance_reports.jsx @@ -7,7 +7,7 @@ import * as Utils from '../../utils/utils.jsx'; import AdminStore from '../../stores/admin_store.jsx'; import UserStore from '../../stores/user_store.jsx'; -import * as Client from '../../utils/client.jsx'; +import * as Client from '../../utils/web_client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl'; @@ -153,7 +153,7 @@ export default class ComplianceReports extends React.Component { var download = ''; if (report.status === 'finished') { download = ( - <a href={'/api/v1/admin/download_compliance_report/' + report.id}> + <a href={Client.getAdminRoute() + '/download_compliance_report/' + report.id}> <FormattedMessage id='admin.compliance_table.download' defaultMessage='Download' diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx index 206bb0faa..b127634e8 100644 --- a/webapp/components/admin_console/compliance_settings.jsx +++ b/webapp/components/admin_console/compliance_settings.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; -import * as Client from '../../utils/client.jsx'; +import * as Client from '../../utils/web_client.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import * as Utils from '../../utils/utils.jsx'; diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx index 8df48b206..1fa75ead9 100644 --- a/webapp/components/admin_console/email_settings.jsx +++ b/webapp/components/admin_console/email_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; import ConnectionSecurityDropdownSetting from './connection_security_dropdown_setting.jsx'; diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx index 7fdedde13..747905ac6 100644 --- a/webapp/components/admin_console/gitlab_settings.jsx +++ b/webapp/components/admin_console/gitlab_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/image_settings.jsx b/webapp/components/admin_console/image_settings.jsx index 576ff18fd..023e9af3b 100644 --- a/webapp/components/admin_console/image_settings.jsx +++ b/webapp/components/admin_console/image_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx index dd6e4338f..01402a588 100644 --- a/webapp/components/admin_console/ldap_settings.jsx +++ b/webapp/components/admin_console/ldap_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; @@ -61,6 +61,7 @@ class LdapSettings extends React.Component { config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim(); config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim(); config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim(); + config.LdapSettings.NicknameAttribute = this.refs.NicknameAttribute.value.trim(); config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim(); config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim(); config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim(); @@ -441,6 +442,35 @@ class LdapSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='NicknameAttribute' + > + <FormattedMessage + id='admin.ldap.nicknameAttrTitle' + defaultMessage='Nickname Attribute:' + /> + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='NicknameAttribute' + ref='NicknameAttribute' + placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')} + defaultValue={this.props.config.LdapSettings.NicknameAttribute} + onChange={this.handleChange} + disabled={!this.state.enable} + /> + <p className='help-text'> + <FormattedMessage + id='admin.ldap.nicknameAttrDesc' + defaultMessage='(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.' + /> + </p> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='EmailAttribute' > <FormattedMessage diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx index bbbb3713c..9f72f5fdf 100644 --- a/webapp/components/admin_console/legal_and_support_settings.jsx +++ b/webapp/components/admin_console/legal_and_support_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx index 20e2fc83a..f2c511e44 100644 --- a/webapp/components/admin_console/license_settings.jsx +++ b/webapp/components/admin_console/license_settings.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -54,10 +54,7 @@ class LicenseSettings extends React.Component { $('#upload-button').button('loading'); - const formData = new FormData(); - formData.append('license', file, file.name); - - Client.uploadLicenseFile(formData, + Client.uploadLicenseFile(file, () => { Utils.clearFileInput(element[0]); $('#upload-button').button('reset'); diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx index 5aa3ca1e0..061c2b6e3 100644 --- a/webapp/components/admin_console/log_settings.jsx +++ b/webapp/components/admin_console/log_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx index a312dddca..5045a6d31 100644 --- a/webapp/components/admin_console/privacy_settings.jsx +++ b/webapp/components/admin_console/privacy_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx index f3fb1742c..de7a40e6b 100644 --- a/webapp/components/admin_console/rate_settings.jsx +++ b/webapp/components/admin_console/rate_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx index f80c740e3..5133f3f28 100644 --- a/webapp/components/admin_console/reset_password_modal.jsx +++ b/webapp/components/admin_console/reset_password_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {Modal} from 'react-bootstrap'; @@ -40,12 +40,9 @@ class ResetPasswordModal extends React.Component { this.setState({serverError: null}); - var data = {}; - data.new_password = password; - data.name = this.props.team.name; - data.user_id = this.props.user.id; - - Client.resetPassword(data, + Client.adminResetPassword( + this.props.user.id, + password, () => { this.props.onModalSubmit(ReactDOM.findDOMNode(this.refs.password).value); }, @@ -159,4 +156,4 @@ ResetPasswordModal.propTypes = { onModalDismissed: React.PropTypes.func }; -export default injectIntl(ResetPasswordModal);
\ No newline at end of file +export default injectIntl(ResetPasswordModal); diff --git a/webapp/components/admin_console/service_settings.jsx b/webapp/components/admin_console/service_settings.jsx index 2c3f4081c..90b6a39b4 100644 --- a/webapp/components/admin_console/service_settings.jsx +++ b/webapp/components/admin_console/service_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/admin_console/sql_settings.jsx b/webapp/components/admin_console/sql_settings.jsx index 33bb2cece..f2e005b83 100644 --- a/webapp/components/admin_console/sql_settings.jsx +++ b/webapp/components/admin_console/sql_settings.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import crypto from 'crypto'; diff --git a/webapp/components/admin_console/team_settings.jsx b/webapp/components/admin_console/team_settings.jsx index 6c9828351..bbb7ec3c4 100644 --- a/webapp/components/admin_console/team_settings.jsx +++ b/webapp/components/admin_console/team_settings.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -42,6 +42,7 @@ class TeamSettings extends React.Component { this.handleImageSubmit = this.handleImageSubmit.bind(this); this.uploading = false; + this.timestamp = 0; this.state = { saveNeeded: false, @@ -53,7 +54,7 @@ class TeamSettings extends React.Component { componentWillMount() { if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') { - $.get('/api/v1/admin/get_brand_image').done(() => this.setState({brandImageExists: true})); + $.get(Client.getAdminRoute() + '/get_brand_image').done(() => this.setState({brandImageExists: true})); } } @@ -89,6 +90,7 @@ class TeamSettings extends React.Component { if (element.prop('files').length > 0) { this.setState({fileSelected: true, brandImage: element.prop('files')[0]}); } + $('#upload-button').button('reset'); } handleSubmit(e) { @@ -100,8 +102,8 @@ class TeamSettings extends React.Component { config.TeamSettings.RestrictCreationToDomains = this.refs.RestrictCreationToDomains.value.trim(); config.TeamSettings.EnableTeamCreation = this.refs.EnableTeamCreation.checked; config.TeamSettings.EnableUserCreation = this.refs.EnableUserCreation.checked; + config.TeamSettings.EnableOpenServer = this.refs.EnableOpenServer.checked; config.TeamSettings.RestrictTeamNames = this.refs.RestrictTeamNames.checked; - config.TeamSettings.EnableTeamListing = this.refs.EnableTeamListing.checked; if (this.refs.EnableCustomBrand) { config.TeamSettings.EnableCustomBrand = this.refs.EnableCustomBrand.checked; @@ -155,6 +157,7 @@ class TeamSettings extends React.Component { Client.uploadBrandImage(this.state.brandImage, () => { $('#upload-button').button('complete'); + this.timestamp = Utils.getTimestamp(); this.setState({brandImageExists: true, brandImage: null}); this.uploading = false; }, @@ -193,7 +196,7 @@ class TeamSettings extends React.Component { img = ( <img className='brand-img' - src='/api/v1/admin/get_brand_image' + src={Client.getAdminRoute() + '/get_brand_image?t=' + this.timestamp} /> ); } else { @@ -537,6 +540,53 @@ class TeamSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='EnableOpenServer' + > + <FormattedMessage + id='admin.team.openServerTitle' + defaultMessage='Enable Open Server: ' + /> + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableOpenServer' + value='true' + ref='EnableOpenServer' + defaultChecked={this.props.config.TeamSettings.EnableOpenServer} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.team.true' + defaultMessage='true' + /> + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableOpenServer' + value='false' + defaultChecked={!this.props.config.TeamSettings.EnableOpenServer} + onChange={this.handleChange} + /> + <FormattedMessage + id='admin.team.false' + defaultMessage='false' + /> + </label> + <p className='help-text'> + <FormattedMessage + id='admin.team.openServerDescription' + defaultMessage='When true, anyone can signup for a user account on this server without the need to be invited.' + /> + </p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='RestrictCreationToDomains' > <FormattedMessage @@ -610,53 +660,6 @@ class TeamSettings extends React.Component { </div> </div> - <div className='form-group'> - <label - className='control-label col-sm-4' - htmlFor='EnableTeamListing' - > - <FormattedMessage - id='admin.team.dirTitle' - defaultMessage='Enable Team Directory: ' - /> - </label> - <div className='col-sm-8'> - <label className='radio-inline'> - <input - type='radio' - name='EnableTeamListing' - value='true' - ref='EnableTeamListing' - defaultChecked={this.props.config.TeamSettings.EnableTeamListing} - onChange={this.handleChange} - /> - <FormattedMessage - id='admin.team.true' - defaultMessage='true' - /> - </label> - <label className='radio-inline'> - <input - type='radio' - name='EnableTeamListing' - value='false' - defaultChecked={!this.props.config.TeamSettings.EnableTeamListing} - onChange={this.handleChange} - /> - <FormattedMessage - id='admin.team.false' - defaultMessage='false' - /> - </label> - <p className='help-text'> - <FormattedMessage - id='admin.team.dirDesc' - defaultMessage='When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.' - /> - </p> - </div> - </div> - {brand} <div className='form-group'> diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx index 8b37bd237..2b0e6af0a 100644 --- a/webapp/components/admin_console/team_users.jsx +++ b/webapp/components/admin_console/team_users.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import LoadingScreen from '../loading_screen.jsx'; import UserItem from './user_item.jsx'; import ResetPasswordModal from './reset_password_modal.jsx'; diff --git a/webapp/components/admin_console/user_item.jsx b/webapp/components/admin_console/user_item.jsx index 5bd05d063..b8f21d77e 100644 --- a/webapp/components/admin_console/user_item.jsx +++ b/webapp/components/admin_console/user_item.jsx @@ -1,13 +1,13 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import UserStore from 'stores/user_store.jsx'; import ConfirmModal from '../confirm_modal.jsx'; import TeamStore from 'stores/team_store.jsx'; -import {FormattedMessage} from 'react-intl'; +import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import React from 'react'; import {browserHistory} from 'react-router'; @@ -22,6 +22,7 @@ export default class UserItem extends React.Component { this.handleMakeAdmin = this.handleMakeAdmin.bind(this); this.handleMakeSystemAdmin = this.handleMakeSystemAdmin.bind(this); this.handleResetPassword = this.handleResetPassword.bind(this); + this.handleResetMfa = this.handleResetMfa.bind(this); this.handleDemote = this.handleDemote.bind(this); this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this); this.handleDemoteCancel = this.handleDemoteCancel.bind(this); @@ -40,12 +41,9 @@ export default class UserItem extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { - const data = { - user_id: this.props.user.id, - new_roles: '' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + '', () => { this.props.refreshProfiles(); }, @@ -86,12 +84,9 @@ export default class UserItem extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { - const data = { - user_id: this.props.user.id, - new_roles: 'admin' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'admin', () => { this.props.refreshProfiles(); }, @@ -104,12 +99,10 @@ export default class UserItem extends React.Component { handleMakeSystemAdmin(e) { e.preventDefault(); - const data = { - user_id: this.props.user.id, - new_roles: 'system_admin' - }; - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'system_admin', () => { this.props.refreshProfiles(); }, @@ -124,6 +117,19 @@ export default class UserItem extends React.Component { this.props.doPasswordReset(this.props.user); } + handleResetMfa(e) { + e.preventDefault(); + + Client.adminResetMfa(this.props.user.id, + () => { + this.props.refreshProfiles(); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + handleDemote(user, role) { this.setState({ serverError: this.state.serverError, @@ -143,12 +149,9 @@ export default class UserItem extends React.Component { } handleDemoteSubmit() { - const data = { - user_id: this.props.user.id, - new_roles: this.state.role - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + this.state.role, () => { this.setState({ serverError: null, @@ -211,10 +214,15 @@ export default class UserItem extends React.Component { const email = user.email; let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; - let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + + //let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + let showMakeAdmin = false; + let showMakeSystemAdmin = user.roles === '' || user.roles === 'admin'; let showMakeActive = false; let showMakeNotActive = user.roles !== 'system_admin'; + let mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true'; + let showMfaReset = mfaEnabled && user.mfa_active; if (user.delete_at > 0) { currentRoles = ( @@ -319,6 +327,64 @@ export default class UserItem extends React.Component { </li> ); } + + let mfaReset = null; + if (showMfaReset) { + mfaReset = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleResetMfa} + > + <FormattedMessage + id='admin.user_item.resetMfa' + defaultMessage='Remove MFA' + /> + </a> + </li> + ); + } + + let mfaActiveText; + if (mfaEnabled) { + if (user.mfa_active) { + mfaActiveText = ( + <FormattedHTMLMessage + id='admin.user_item.mfaYes' + defaultMessage=', <strong>MFA</strong>: Yes' + /> + ); + } else { + mfaActiveText = ( + <FormattedHTMLMessage + id='admin.user_item.mfaNo' + defaultMessage=', <strong>MFA</strong>: No' + /> + ); + } + } + + let authServiceText; + if (user.auth_service) { + authServiceText = ( + <FormattedHTMLMessage + id='admin.user_item.authServiceNotEmail' + defaultMessage=', <strong>Sign-in Method:</strong> {service}' + values={{ + service: Utils.toTitleCase(user.auth_service) + }} + /> + ); + } else { + authServiceText = ( + <FormattedHTMLMessage + id='admin.user_item.authServiceEmail' + defaultMessage=', <strong>Sign-in Method:</strong> Email' + /> + ); + } + const me = UserStore.getCurrentUser(); let makeDemoteModal = null; if (this.props.user.id === me.id) { @@ -368,13 +434,23 @@ export default class UserItem extends React.Component { <div className='more-modal__row'> <img className='more-modal__image pull-left' - src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`} height='36' width='36' /> <div className='more-modal__details'> <div className='more-modal__name'>{Utils.getDisplayName(user)}</div> - <div className='more-modal__description'>{email}</div> + <div className='more-modal__description'> + <FormattedHTMLMessage + id='admin.user_item.emailTitle' + defaultMessage='<strong>Email:</strong> {email}' + values={{ + email + }} + /> + {authServiceText} + {mfaActiveText} + </div> </div> <div className='more-modal__actions'> <div className='dropdown member-drop'> @@ -397,6 +473,7 @@ export default class UserItem extends React.Component { {makeActive} {makeNotActive} {makeSystemAdmin} + {mfaReset} <li role='presentation'> <a role='menuitem' diff --git a/webapp/components/authorize.jsx b/webapp/components/authorize.jsx index 01b37f439..b18469568 100644 --- a/webapp/components/authorize.jsx +++ b/webapp/components/authorize.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx index 2eb7bdb21..ba9ac4e79 100644 --- a/webapp/components/backstage/add_command.jsx +++ b/webapp/components/backstage/add_command.jsx @@ -89,6 +89,8 @@ export default class AddCommand extends React.Component { /> ) }); + + return; } if (!command.url) { @@ -101,12 +103,14 @@ export default class AddCommand extends React.Component { /> ) }); + + return; } AsyncClient.addCommand( command, () => { - browserHistory.push('/settings/integrations/commands'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'); }, (err) => { this.setState({ @@ -251,7 +255,7 @@ export default class AddCommand extends React.Component { return ( <div className='backstage-content row'> <BackstageHeader> - <Link to={'/settings/integrations/commands'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}> <FormattedMessage id='installed_command.header' defaultMessage='Slash Commands' @@ -482,7 +486,7 @@ export default class AddCommand extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/settings/integrations/commands'} + to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} > <FormattedMessage id='add_command.cancel' diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx index f68a263be..0f0d49ea7 100644 --- a/webapp/components/backstage/add_incoming_webhook.jsx +++ b/webapp/components/backstage/add_incoming_webhook.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; import BackstageHeader from './backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; @@ -69,7 +70,7 @@ export default class AddIncomingWebhook extends React.Component { AsyncClient.addIncomingHook( hook, () => { - browserHistory.push('/settings/integrations/incoming_webhooks'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'); }, (err) => { this.setState({ @@ -102,7 +103,7 @@ export default class AddIncomingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/settings/integrations/incoming_webhooks'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}> <FormattedMessage id='installed_incoming_webhooks.header' defaultMessage='Incoming Webhooks' diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx index ff5e90e07..245df1604 100644 --- a/webapp/components/backstage/add_outgoing_webhook.jsx +++ b/webapp/components/backstage/add_outgoing_webhook.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; import BackstageHeader from './backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; @@ -109,7 +110,7 @@ export default class AddOutgoingWebhook extends React.Component { AsyncClient.addOutgoingHook( hook, () => { - browserHistory.push('/settings/integrations/outgoing_webhooks'); + browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'); }, (err) => { this.setState({ @@ -154,7 +155,7 @@ export default class AddOutgoingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/settings/integrations/outgoing_webhooks'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}> <FormattedMessage id='installed_outgoing_webhooks.header' defaultMessage='Outgoing Webhooks' @@ -273,7 +274,7 @@ export default class AddOutgoingWebhook extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/settings/integrations/outgoing_webhooks'} + to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} > <FormattedMessage id='add_outgoing_webhook.cancel' diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/backstage_navbar.jsx index d2d2da1ed..8352296b7 100644 --- a/webapp/components/backstage/backstage_navbar.jsx +++ b/webapp/components/backstage/backstage_navbar.jsx @@ -46,7 +46,7 @@ export default class BackstageNavbar extends React.Component { <div className='backstage-navbar row'> <Link className='backstage-navbar__back' - to={`/${this.state.team.display_name}/channels/town-square`} + to={`/${this.state.team.name}/channels/town-square`} > <i className='fa fa-angle-left'/> <span> diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx index eb84709a3..6f8e0b86a 100644 --- a/webapp/components/backstage/backstage_sidebar.jsx +++ b/webapp/components/backstage/backstage_sidebar.jsx @@ -3,6 +3,7 @@ import React from 'react'; +import * as Utils from 'utils/utils.jsx'; import BackstageCategory from './backstage_category.jsx'; import BackstageSection from './backstage_section.jsx'; import {FormattedMessage} from 'react-intl'; @@ -14,7 +15,7 @@ export default class BackstageSidebar extends React.Component { <ul> <BackstageCategory name='integrations' - parentLink={'/settings'} + parentLink={'/' + Utils.getTeamNameFromUrl() + '/settings'} icon='fa-link' title={ <FormattedMessage diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/backstage/installed_commands.jsx index 3527a574b..8b0cd59c8 100644 --- a/webapp/components/backstage/installed_commands.jsx +++ b/webapp/components/backstage/installed_commands.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledCommand from './installed_command.jsx'; @@ -84,7 +85,7 @@ export default class InstalledCommands extends React.Component { defaultMessage='Add Slash Command' /> } - addLink='/settings/integrations/commands/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands/add'} > {commands} </InstalledIntegrations> diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx index 58d318310..afa6e9958 100644 --- a/webapp/components/backstage/installed_incoming_webhook.jsx +++ b/webapp/components/backstage/installed_incoming_webhook.jsx @@ -90,6 +90,17 @@ export default class InstalledIncomingWebhook extends React.Component { </span> </div> {description} + <div className='item-details__row'> + <span className='item-details__url'> + <FormattedMessage + id='installed_integrations.url' + defaultMessage='URL: {url}' + values={{ + url: Utils.getWindowLocationOrigin() + '/hooks/' + incomingWebhook.id + }} + /> + </span> + </div> <div className='tem-details__row'> <span className='item-details__creation'> <FormattedMessage diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/backstage/installed_incoming_webhooks.jsx index de7154afe..0d6f900d1 100644 --- a/webapp/components/backstage/installed_incoming_webhooks.jsx +++ b/webapp/components/backstage/installed_incoming_webhooks.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledIncomingWebhook from './installed_incoming_webhook.jsx'; @@ -76,7 +77,7 @@ export default class InstalledIncomingWebhooks extends React.Component { defaultMessage='Add Incoming Webhook' /> } - addLink='/settings/integrations/incoming_webhooks/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks/add'} > {incomingWebhooks} </InstalledIntegrations> diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/backstage/installed_outgoing_webhooks.jsx index 15d927a41..98992b081 100644 --- a/webapp/components/backstage/installed_outgoing_webhooks.jsx +++ b/webapp/components/backstage/installed_outgoing_webhooks.jsx @@ -5,6 +5,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import IntegrationStore from 'stores/integration_store.jsx'; +import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx'; @@ -82,7 +83,7 @@ export default class InstalledOutgoingWebhooks extends React.Component { defaultMessage='Add Outgoing Webhook' /> } - addLink='/settings/integrations/outgoing_webhooks/add' + addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks/add'} > {outgoingWebhooks} </InstalledIntegrations> diff --git a/webapp/components/backstage/integrations.jsx b/webapp/components/backstage/integrations.jsx index 71232ea45..fdd75026a 100644 --- a/webapp/components/backstage/integrations.jsx +++ b/webapp/components/backstage/integrations.jsx @@ -5,6 +5,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import IntegrationOption from './integration_option.jsx'; +import * as Utils from 'utils/utils.jsx'; import WebhookIcon from 'images/webhook_icon.jpg'; @@ -29,7 +30,7 @@ export default class Integrations extends React.Component { defaultMessage='Incoming webhooks allow external integrations to send messages' /> } - link={'/settings/integrations/incoming_webhooks'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'} /> ); } @@ -51,7 +52,7 @@ export default class Integrations extends React.Component { defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages' /> } - link={'/settings/integrations/outgoing_webhooks'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} /> ); } @@ -73,7 +74,7 @@ export default class Integrations extends React.Component { defaultMessage='Slash commands send events to an external integration' /> } - link={'/settings/integrations/commands'} + link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} /> ); } diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 16d9ea536..c82f59299 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -25,7 +25,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as Utils from 'utils/utils.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; diff --git a/webapp/components/channel_info_modal.jsx b/webapp/components/channel_info_modal.jsx index c7f9f9f79..4c16dda90 100644 --- a/webapp/components/channel_info_modal.jsx +++ b/webapp/components/channel_info_modal.jsx @@ -3,33 +3,26 @@ import * as Utils from 'utils/utils.jsx'; -import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; - +import {FormattedMessage} from 'react-intl'; import {Modal} from 'react-bootstrap'; -const holders = defineMessages({ - notFound: { - id: 'channel_info.notFound', - defaultMessage: 'No Channel Found' - } -}); - import React from 'react'; -class ChannelInfoModal extends React.Component { +export default class ChannelInfoModal extends React.Component { render() { - const {formatMessage} = this.props.intl; let channel = this.props.channel; if (!channel) { + const notFound = Utils.localizeMessage('channel_info.notFound', 'No Channel Found'); + channel = { - display_name: formatMessage(holders.notFound), - name: formatMessage(holders.notFound), - purpose: formatMessage(holders.notFound), - id: formatMessage(holders.notFound) + display_name: notFound, + name: notFound, + purpose: notFound, + id: notFound }; } - const channelURL = Utils.getShortenedTeamURL() + channel.name; + const channelURL = Utils.getTeamURLFromAddressBar() + '/channels/' + channel.name; return ( <Modal @@ -97,10 +90,7 @@ class ChannelInfoModal extends React.Component { } ChannelInfoModal.propTypes = { - intl: intlShape.isRequired, show: React.PropTypes.bool.isRequired, onHide: React.PropTypes.func.isRequired, channel: React.PropTypes.object.isRequired -}; - -export default injectIntl(ChannelInfoModal);
\ No newline at end of file +};
\ No newline at end of file diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx index 1fcd461ea..ed013bb26 100644 --- a/webapp/components/channel_invite_button.jsx +++ b/webapp/components/channel_invite_button.jsx @@ -4,7 +4,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; import SpinnerButton from 'components/spinner_button.jsx'; @@ -37,13 +37,9 @@ export default class ChannelInviteButton extends React.Component { addingUser: true }); - const data = { - user_id: this.props.user.id - }; - Client.addChannelMember( this.props.channel.id, - data, + this.props.user.id, () => { this.setState({ addingUser: false diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx index 67be2ef50..ecea891bb 100644 --- a/webapp/components/channel_members_modal.jsx +++ b/webapp/components/channel_members_modal.jsx @@ -9,7 +9,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import {FormattedMessage} from 'react-intl'; @@ -97,12 +97,9 @@ export default class ChannelMembersModal extends React.Component { handleRemove(user) { const userId = user.id; - const data = {}; - data.user_id = userId; - Client.removeChannelMember( ChannelStore.getCurrentId(), - data, + userId, () => { const memberList = this.state.memberList.slice(); for (let i = 0; i < memberList.length; i++) { diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx index 564776876..112c07ad0 100644 --- a/webapp/components/channel_notifications_modal.jsx +++ b/webapp/components/channel_notifications_modal.jsx @@ -6,7 +6,7 @@ import {Modal} from 'react-bootstrap'; import SettingItemMin from './setting_item_min.jsx'; import SettingItemMax from './setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import {FormattedMessage} from 'react-intl'; @@ -60,7 +60,7 @@ export default class ChannelNotificationsModal extends React.Component { data.desktop = notifyLevel; //TODO: This should be moved to event_helpers - Client.updateNotifyProps(data, + Client.updateChannelNotifyProps(data, () => { // YUCK var member = ChannelStore.getMember(channelId); @@ -252,7 +252,7 @@ export default class ChannelNotificationsModal extends React.Component { }; //TODO: This should be fixed, moved to event_helpers - Client.updateNotifyProps(data, + Client.updateChannelNotifyProps(data, () => { // Yuck... var member = ChannelStore.getMember(channelId); diff --git a/webapp/components/channel_select.jsx b/webapp/components/channel_select.jsx index 8622d1f57..238cfa1ae 100644 --- a/webapp/components/channel_select.jsx +++ b/webapp/components/channel_select.jsx @@ -54,7 +54,7 @@ export default class ChannelSelect extends React.Component { ]; this.state.channels.forEach((channel) => { - if (channel.type !== Constants.DM_CHANNEL) { + if (channel.type === Constants.OPEN_CHANNEL) { options.push( <option key={channel.id} diff --git a/webapp/components/claim/claim.jsx b/webapp/components/claim/claim.jsx index 5cfb04af3..0197e1677 100644 --- a/webapp/components/claim/claim.jsx +++ b/webapp/components/claim/claim.jsx @@ -1,8 +1,6 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import TeamStore from 'stores/team_store.jsx'; - import React from 'react'; import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router'; @@ -13,40 +11,15 @@ export default class Claim 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.display_name; - } - this.setState({ - teamDisplayName: displayName + oldType: this.props.location.query.old_type }); } - onTeamChange() { - this.updateStateFromStores(); - } render() { return ( <div> @@ -66,8 +39,6 @@ export default class Claim extends React.Component { /> <div id='claim'> {React.cloneElement(this.props.children, { - teamName: this.state.teamName, - teamDisplayName: this.state.teamDisplayName, currentType: this.state.oldType, newType: this.state.newType, email: this.state.email @@ -83,7 +54,6 @@ export default class Claim extends React.Component { Claim.defaultProps = { }; Claim.propTypes = { - params: React.PropTypes.object.isRequired, location: React.PropTypes.object.isRequired, children: React.PropTypes.node }; diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx index 1ceb42a27..fbf26cade 100644 --- a/webapp/components/claim/components/email_to_ldap.jsx +++ b/webapp/components/claim/components/email_to_ldap.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class EmailToLDAP extends React.Component { constructor(props) { @@ -45,17 +44,14 @@ export default class EmailToLDAP extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.email_password = password; - postData.ldap_id = ldapId; - postData.ldap_password = ldapPassword; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.emailToLDAP(postData, + Client.emailToLdap( + this.props.email, + password, + ldapId, + ldapPassword, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -74,6 +70,20 @@ export default class EmailToLDAP extends React.Component { formClass += ' has-error'; } + let loginPlaceholder; + if (global.window.mm_config.LdapLoginFieldName) { + loginPlaceholder = global.window.mm_config.LdapLoginFieldName; + } else { + loginPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID'); + } + + let passwordPlaceholder; + if (global.window.mm_config.LdapPasswordFieldName) { + passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName; + } else { + passwordPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password'); + } + return ( <div> <h3> @@ -98,9 +108,8 @@ export default class EmailToLDAP extends React.Component { <p> <FormattedMessage id='claim.email_to_ldap.enterPwd' - defaultMessage='Enter the password for your {team} {site} email account' + defaultMessage='Enter the password for your {site} email account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -125,10 +134,6 @@ export default class EmailToLDAP extends React.Component { <FormattedMessage id='claim.email_to_ldap.enterLdapPwd' defaultMessage='Enter the ID and password for your LDAP account' - values={{ - team: this.props.teamDisplayName, - site: global.window.mm_config.SiteName - }} /> </p> <div className={formClass}> @@ -138,7 +143,7 @@ export default class EmailToLDAP extends React.Component { name='ldapId' ref='ldapid' autoComplete='off' - placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapId', 'LDAP ID')} + placeholder={loginPlaceholder} spellCheck='false' /> </div> @@ -149,7 +154,7 @@ export default class EmailToLDAP extends React.Component { name='ldapPassword' ref='ldappassword' autoComplete='off' - placeholder={Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'LDAP Password')} + placeholder={passwordPlaceholder} spellCheck='false' /> </div> @@ -172,7 +177,5 @@ export default class EmailToLDAP extends React.Component { EmailToLDAP.defaultProps = { }; EmailToLDAP.propTypes = { - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/email_to_oauth.jsx b/webapp/components/claim/components/email_to_oauth.jsx index f4376067a..1fd284bed 100644 --- a/webapp/components/claim/components/email_to_oauth.jsx +++ b/webapp/components/claim/components/email_to_oauth.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class EmailToOAuth extends React.Component { constructor(props) { @@ -31,16 +30,13 @@ export default class EmailToOAuth extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.password = password; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - postData.service = this.props.newType; - - Client.emailToOAuth(postData, + Client.emailToOAuth( + this.props.email, + password, + this.props.newType, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -94,9 +90,8 @@ export default class EmailToOAuth extends React.Component { <p> <FormattedMessage id='claim.email_to_oauth.enterPwd' - defaultMessage='Enter the password for your {team} {site} account' + defaultMessage='Enter the password for your {site} account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -134,7 +129,5 @@ EmailToOAuth.defaultProps = { }; EmailToOAuth.propTypes = { newType: React.PropTypes.string, - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx index ed8a314bd..a10cefd6f 100644 --- a/webapp/components/claim/components/ldap_to_email.jsx +++ b/webapp/components/claim/components/ldap_to_email.jsx @@ -2,12 +2,11 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; export default class LDAPToEmail extends React.Component { constructor(props) { @@ -45,16 +44,13 @@ export default class LDAPToEmail extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.email_password = password; - postData.ldap_password = ldapPassword; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.ldapToEmail(postData, + Client.ldapToEmail( + this.props.email, + password, + ldapPassword, (data) => { if (data.follow_link) { - browserHistory.push(data.follow_link); + window.location.href = data.follow_link; } }, (error) => { @@ -73,6 +69,13 @@ export default class LDAPToEmail extends React.Component { formClass += ' has-error'; } + let passwordPlaceholder; + if (global.window.mm_config.LdapPasswordFieldName) { + passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName; + } else { + passwordPlaceholder = Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password'); + } + return ( <div> <h3> @@ -100,9 +103,9 @@ export default class LDAPToEmail extends React.Component { <p> <FormattedMessage id='claim.ldap_to_email.enterLdapPwd' - defaultMessage='Enter your LDAP password for your {team} {site} email account' + defaultMessage='Enter your {ldapPassword} for your {site} email account' values={{ - team: this.props.teamDisplayName, + ldapPassword: passwordPlaceholder, site: global.window.mm_config.SiteName }} /> @@ -113,7 +116,7 @@ export default class LDAPToEmail extends React.Component { className='form-control' name='ldapPassword' ref='ldappassword' - placeholder={Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'LDAP Password')} + placeholder={passwordPlaceholder} spellCheck='false' /> </div> @@ -162,7 +165,5 @@ export default class LDAPToEmail extends React.Component { LDAPToEmail.defaultProps = { }; LDAPToEmail.propTypes = { - email: React.PropTypes.string, - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string + email: React.PropTypes.string }; diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx index 72e0500a9..7fd18aaa6 100644 --- a/webapp/components/claim/components/oauth_to_email.jsx +++ b/webapp/components/claim/components/oauth_to_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -38,12 +38,9 @@ export default class OAuthToEmail extends React.Component { state.error = null; this.setState(state); - var postData = {}; - postData.password = password; - postData.email = this.props.email; - postData.team_name = this.props.teamName; - - Client.oauthToEmail(postData, + Client.oauthToEmail( + this.props.email, + password, (data) => { if (data.follow_link) { browserHistory.push(data.follow_link); @@ -87,10 +84,9 @@ export default class OAuthToEmail extends React.Component { </p> <p> <FormattedMessage - id='claim.oauth_to_email_newPwd' - defaultMessage='Enter a new password for your {team} {site} account' + id='claim.oauth_to_email.enterNewPwd' + defaultMessage='Enter a new password for your {site} account' values={{ - team: this.props.teamDisplayName, site: global.window.mm_config.SiteName }} /> @@ -137,8 +133,6 @@ export default class OAuthToEmail extends React.Component { OAuthToEmail.defaultProps = { }; OAuthToEmail.propTypes = { - teamName: React.PropTypes.string, - teamDisplayName: React.PropTypes.string, currentType: React.PropTypes.string, email: React.PropTypes.string }; diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index a91c03d58..e8fa59165 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; @@ -148,7 +148,6 @@ class CreateComment extends React.Component { Client.createPost( post, - ChannelStore.getCurrent(), (data) => { AsyncClient.getPosts(this.props.channelId); diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index 232537d8b..9bbc44f38 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -11,7 +11,7 @@ import TutorialTip from './tutorial/tutorial_tip.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -172,7 +172,7 @@ class CreatePost extends React.Component { GlobalActions.emitUserPostedEvent(post); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - Client.createPost(post, channel, + Client.createPost(post, (data) => { AsyncClient.getPosts(); diff --git a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx b/webapp/components/create_team/components/display_name.jsx index 111fc6835..e33eee1bc 100644 --- a/webapp/components/signup_team_complete/components/team_signup_display_name_page.jsx +++ b/webapp/components/create_team/components/display_name.jsx @@ -3,7 +3,8 @@ import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; +import {Link} from 'react-router'; import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; @@ -11,11 +12,11 @@ import logoImage from 'images/logo.png'; const holders = defineMessages({ required: { - id: 'team_signup_display_name.required', + id: 'create_team_display_name.required', defaultMessage: 'This field is required' }, charLength: { - id: 'team_signup_display_name.charLength', + id: 'create_team_display_name.charLength', defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' } }); @@ -26,16 +27,11 @@ class TeamSignupDisplayNamePage extends React.Component { constructor(props) { super(props); - this.submitBack = this.submitBack.bind(this); this.submitNext = this.submitNext.bind(this); this.state = {}; } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'welcome'; - this.props.updateParent(this.props.state); - } + submitNext(e) { e.preventDefault(); @@ -54,12 +50,14 @@ class TeamSignupDisplayNamePage extends React.Component { this.props.state.team.name = utils.cleanUpUrlable(displayName); this.props.updateParent(this.props.state); } + handleFocus(e) { e.preventDefault(); e.currentTarget.select(); } + render() { - client.track('signup', 'signup_team_02_name'); + Client.track('signup', 'signup_team_02_name'); var nameError = null; var nameDivClass = 'form-group'; @@ -77,7 +75,7 @@ class TeamSignupDisplayNamePage extends React.Component { /> <h2> <FormattedMessage - id='team_signup_display_name.teamName' + id='create_team_display_name.teamName' defaultMessage='Team Name' /> </h2> @@ -101,7 +99,7 @@ class TeamSignupDisplayNamePage extends React.Component { </div> <div> <FormattedMessage - id='team_signup_display_name.nameHelp' + id='create_team_display_name.nameHelp' defaultMessage='Name your team in any language. Your team name shows in menus and headings.' /> </div> @@ -111,20 +109,17 @@ class TeamSignupDisplayNamePage extends React.Component { onClick={this.submitNext} > <FormattedMessage - id='team_signup_display_name.next' + id='create_team_display_name.next' defaultMessage='Next' /><i className='glyphicon glyphicon-chevron-right'></i> </button> <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > + <Link to='/select_team'> <FormattedMessage - id='team_signup_display_name.back' + id='create_team_display_name.back' defaultMessage='Back to previous step' /> - </a> + </Link> </div> </form> </div> diff --git a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx b/webapp/components/create_team/components/team_url.jsx index b2ab57285..025103141 100644 --- a/webapp/components/signup_team_complete/components/team_signup_url_page.jsx +++ b/webapp/components/create_team/components/team_url.jsx @@ -4,8 +4,11 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; import Constants from 'utils/constants.jsx'; +import {browserHistory} from 'react-router'; import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -13,30 +16,30 @@ import logoImage from 'images/logo.png'; const holders = defineMessages({ required: { - id: 'team_signup_url.required', + id: 'create_team_url.required', defaultMessage: 'This field is required' }, regex: { - id: 'team_signup_url.regex', + id: 'create_team_url.regex', defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash." }, charLength: { - id: 'team_signup_url.charLength', + id: 'create_team_url.charLength', defaultMessage: 'Name must be 4 or more characters up to a maximum of 15' }, taken: { - id: 'team_signup_url.taken', + id: 'create_team_url.taken', defaultMessage: 'URL is taken or contains a reserved word' }, unavailable: { - id: 'team_signup_url.unavailable', + id: 'create_team_url.unavailable', defaultMessage: 'This URL is unavailable. Please try another.' } }); import React from 'react'; -class TeamSignupUrlPage extends React.Component { +class TeamUrl extends React.Component { constructor(props) { super(props); @@ -48,7 +51,7 @@ class TeamSignupUrlPage extends React.Component { } submitBack(e) { e.preventDefault(); - this.props.state.wizard = 'team_display_name'; + this.props.state.wizard = 'display_name'; this.props.updateParent(this.props.state); } submitNext(e) { @@ -81,25 +84,39 @@ class TeamSignupUrlPage extends React.Component { } } + $('#finish-button').button('loading'); + var teamSignup = JSON.parse(JSON.stringify(this.props.state)); + teamSignup.team.type = 'O'; + teamSignup.team.name = name; + Client.findTeamByName(name, - (data) => { - if (data) { - this.setState({nameError: formatMessage(holders.unavailable)}); - } else { - if (global.window.mm_config.SendEmailNotifications === 'true') { - this.props.state.wizard = 'send_invites'; - } else { - this.props.state.wizard = 'username'; - } - this.props.state.team.type = 'O'; - - this.props.state.team.name = name; - this.props.updateParent(this.props.state); - } - }, - (err) => { - this.setState({nameError: err.message}); - } + (findTeam) => { + if (findTeam) { + this.setState({nameError: formatMessage(holders.unavailable)}); + $('#finish-button').button('reset'); + } else { + Client.createTeam(teamSignup.team, + (team) => { + Client.track('signup', 'signup_team_08_complete'); + $('#sign-up-button').button('reset'); + TeamStore.saveTeam(team); + TeamStore.appendTeamMember({team_id: team.id, user_id: UserStore.getCurrentId(), roles: 'admin'}); + TeamStore.emitChange(); + browserHistory.push('/' + team.name + '/channels/town-square'); + }, + (err) => { + this.setState({nameError: err.message}); + $('#finish-button').button('reset'); + } + ); + + $('#finish-button').button('reset'); + } + }, + (err) => { + this.setState({nameError: err.message}); + $('#finish-button').button('reset'); + } ); } handleFocus(e) { @@ -130,7 +147,7 @@ class TeamSignupUrlPage extends React.Component { /> <h2> <FormattedMessage - id='team_signup_url.teamUrl' + id='create_team_url.teamUrl' defaultMessage='Team URL' /> </h2> @@ -163,13 +180,13 @@ class TeamSignupUrlPage extends React.Component { </div> <p> <FormattedMessage - id='team_signup_url.webAddress' + id='create_team_url.webAddress' defaultMessage='Choose the web address of your new team:' /> </p> <ul className='color--light'> <FormattedHTMLMessage - id='team_signup_url.hint' + id='create_team_url.hint' defaultMessage="<li>Short and memorable is best</li> <li>Use lowercase letters, numbers and dashes</li> <li>Must start with a letter and can't end in a dash</li>" @@ -177,13 +194,14 @@ class TeamSignupUrlPage extends React.Component { </ul> <button type='submit' + id='finish-button' className='btn btn-primary margin--extra' onClick={this.submitNext} > <FormattedMessage - id='team_signup_url.next' - defaultMessage='Next' - /><i className='glyphicon glyphicon-chevron-right'></i> + id='create_team_password.finish' + defaultMessage='Finish' + /> </button> <div className='margin--extra'> <a @@ -191,7 +209,7 @@ class TeamSignupUrlPage extends React.Component { onClick={this.submitBack} > <FormattedMessage - id='team_signup_url.back' + id='create_team_url.back' defaultMessage='Back to previous step' /> </a> @@ -202,10 +220,10 @@ class TeamSignupUrlPage extends React.Component { } } -TeamSignupUrlPage.propTypes = { +TeamUrl.propTypes = { intl: intlShape.isRequired, state: React.PropTypes.object, updateParent: React.PropTypes.func }; -export default injectIntl(TeamSignupUrlPage); +export default injectIntl(TeamUrl); diff --git a/webapp/components/create_team/create_team.jsx b/webapp/components/create_team/create_team.jsx new file mode 100644 index 000000000..8a119a122 --- /dev/null +++ b/webapp/components/create_team/create_team.jsx @@ -0,0 +1,72 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ErrorBar from 'components/error_bar.jsx'; + +import {FormattedMessage} from 'react-intl'; +import {browserHistory, Link} from 'react-router'; + +import React from 'react'; + +export default class CreateTeam extends React.Component { + constructor(props) { + super(props); + + this.submit = this.submit.bind(this); + this.updateParent = this.updateParent.bind(this); + + const state = {}; + state.team = {}; + state.wizard = 'display_name'; + this.state = state; + } + + submit() { + // todo fill in + } + + componentDidMount() { + browserHistory.push('/create_team/display_name'); + } + + updateParent(state) { + this.setState(state); + browserHistory.push('/create_team/' + state.wizard); + } + + render() { + return ( + <div> + <ErrorBar/> + <div className='signup-header'> + <Link to='/select_team'> + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='web.header.back' + /> + </Link> + </div> + <div className='col-sm-12'> + <div className='signup-team__container'> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> + <div className='signup__content'> + {React.cloneElement(this.props.children, { + state: this.state, + updateParent: this.updateParent + })} + </div> + </div> + </div> + </div> + ); + } +} + +CreateTeam.propTypes = { + children: React.PropTypes.node +}; diff --git a/webapp/components/delete_channel_modal.jsx b/webapp/components/delete_channel_modal.jsx index 244472a56..222b72c41 100644 --- a/webapp/components/delete_channel_modal.jsx +++ b/webapp/components/delete_channel_modal.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {Modal} from 'react-bootstrap'; import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/delete_post_modal.jsx b/webapp/components/delete_post_modal.jsx index 0dbdc2b43..b2aea6590 100644 --- a/webapp/components/delete_post_modal.jsx +++ b/webapp/components/delete_post_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import PostStore from 'stores/post_store.jsx'; import ModalStore from 'stores/modal_store.jsx'; import {Modal} from 'react-bootstrap'; diff --git a/webapp/components/do_verify_email.jsx b/webapp/components/do_verify_email.jsx index c3be111ed..fe4d61a72 100644 --- a/webapp/components/do_verify_email.jsx +++ b/webapp/components/do_verify_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import {FormattedMessage} from 'react-intl'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import LoadingScreen from './loading_screen.jsx'; import {browserHistory, Link} from 'react-router'; @@ -25,14 +25,14 @@ export default class DoVerifyEmail extends React.Component { const email = this.props.location.query.email; Client.verifyEmail( + uid, + hid, () => { browserHistory.push('/' + teamName + '/login?extra=verified&email=' + email); }, (err) => { this.setState({verifyStatus: 'failure', serverError: err.message}); - }, - uid, - hid + } ); } render() { diff --git a/webapp/components/edit_channel_header_modal.jsx b/webapp/components/edit_channel_header_modal.jsx index 35a5fb9dc..6a7cccebb 100644 --- a/webapp/components/edit_channel_header_modal.jsx +++ b/webapp/components/edit_channel_header_modal.jsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/edit_channel_purpose_modal.jsx b/webapp/components/edit_channel_purpose_modal.jsx index 31cbdd240..a4779d022 100644 --- a/webapp/components/edit_channel_purpose_modal.jsx +++ b/webapp/components/edit_channel_purpose_modal.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; @@ -49,12 +49,9 @@ export default class EditChannelPurposeModal extends React.Component { return; } - const data = { - channel_id: this.props.channel.id, - channel_purpose: ReactDOM.findDOMNode(this.refs.purpose).value.trim() - }; - - Client.updateChannelPurpose(data, + Client.updateChannelPurpose( + this.props.channel.id, + ReactDOM.findDOMNode(this.refs.purpose).value.trim(), () => { AsyncClient.getChannel(this.props.channel.id); diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx index caf9a0ee5..bc67a34f9 100644 --- a/webapp/components/edit_post_modal.jsx +++ b/webapp/components/edit_post_modal.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import Textbox from './textbox.jsx'; @@ -33,20 +33,25 @@ class EditPostModal extends React.Component { this.handleEdit = this.handleEdit.bind(this); this.handleEditInput = this.handleEditInput.bind(this); this.handleEditKeyPress = this.handleEditKeyPress.bind(this); - this.handleUserInput = this.handleUserInput.bind(this); this.handleEditPostEvent = this.handleEditPostEvent.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.onPreferenceChange = this.onPreferenceChange.bind(this); - this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; + this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; } handleEdit() { var updatedPost = {}; updatedPost.message = this.state.editText.trim(); + if (updatedPost.message === this.state.originalText.trim()) { + // no changes so just close the modal + $('#edit_post').modal('hide'); + return; + } + if (updatedPost.message.length === 0) { var tempState = this.state; - delete tempState.editText; + Reflect.deleteProperty(tempState, 'editText'); BrowserStore.setItem('edit_state_transfer', tempState); $('#edit_post').modal('hide'); GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments); @@ -79,12 +84,10 @@ class EditPostModal extends React.Component { this.handleEdit(e); } } - handleUserInput(e) { - this.setState({editText: e.target.value}); - } handleEditPostEvent(options) { this.setState({ editText: options.message || '', + originalText: options.message || '', title: options.title || '', post_id: options.postId || '', channel_id: options.channelId || '', @@ -108,7 +111,7 @@ class EditPostModal extends React.Component { var self = this; $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => { - self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); + self.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); }); $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => { @@ -116,7 +119,15 @@ class EditPostModal extends React.Component { if (!button) { return; } - self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refocusid')}); + self.setState({ + editText: $(button).attr('data-message'), + originalText: $(button).attr('data-message'), + title: $(button).attr('data-title'), + channel_id: $(button).attr('data-channelid'), + post_id: $(button).attr('data-postid'), + comments: $(button).attr('data-comments'), + refocusId: $(button).attr('data-refocusid') + }); }); $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => { @@ -163,17 +174,17 @@ class EditPostModal extends React.Component { aria-label='Close' onClick={this.handleEditClose} > - <span aria-hidden='true'>×</span> + <span aria-hidden='true'>{'×'}</span> </button> - <h4 className='modal-title'> - <FormattedMessage - id='edit_post.edit' - defaultMessage='Edit {title}' - values={{ - title: this.state.title - }} - /> - </h4> + <h4 className='modal-title'> + <FormattedMessage + id='edit_post.edit' + defaultMessage='Edit {title}' + values={{ + title: this.state.title + }} + /> + </h4> </div> <div className='edit-modal-body modal-body'> <Textbox diff --git a/webapp/components/error_bar.jsx b/webapp/components/error_bar.jsx index 572f96e02..7257ffe94 100644 --- a/webapp/components/error_bar.jsx +++ b/webapp/components/error_bar.jsx @@ -29,8 +29,8 @@ export default class ErrorBar extends React.Component { } componentWillMount() { - if (global.window.mm_config.SendEmailNotifications === 'false') { - ErrorStore.storeLastError({message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')}); + if (!ErrorStore.getIgnoreEmailPreview() && global.window.mm_config.SendEmailNotifications === 'false') { + ErrorStore.storeLastError({email_preview: true, message: Utils.localizeMessage('error_bar.preview_mode', 'Preview Mode: Email notifications have not been configured')}); this.onErrorChange(); } } diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx index ccd4070aa..4a040a35b 100644 --- a/webapp/components/file_attachment.jsx +++ b/webapp/components/file_attachment.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages} from 'react-intl'; @@ -100,7 +100,7 @@ class FileAttachment extends React.Component { getFileInfoFromName(name) { var fileInfo = utils.splitFileLocation(name); - fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; + fileInfo.path = utils.getWindowLocationOrigin() + Client.getFilesRoute() + '/get' + fileInfo.path; return fileInfo; } diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx index 8d2ec5ac8..03a8a8128 100644 --- a/webapp/components/file_upload.jsx +++ b/webapp/components/file_upload.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import 'jquery-dragster/jquery.dragster.js'; import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -49,9 +49,9 @@ class FileUpload extends React.Component { fileUploadSuccess(channelId, data) { this.props.onFileUpload(data.filenames, data.client_ids, channelId); - var requests = this.state.requests; + const requests = JSON.parse(JSON.stringify(this.state.requests)); for (var j = 0; j < data.client_ids.length; j++) { - delete requests[data.client_ids[j]]; + Reflect.deleteProperty(requests, data.client_ids[j]); } this.setState({requests}); } @@ -64,13 +64,13 @@ class FileUpload extends React.Component { // clear any existing errors this.props.onUploadError(null); - var channelId = this.props.channelId || ChannelStore.getCurrentId(); + const channelId = this.props.channelId || ChannelStore.getCurrentId(); - var uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId); - var numUploads = 0; + const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId); + let numUploads = 0; // keep track of how many files have been too large - var tooLargeFiles = []; + const tooLargeFiles = []; for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) { if (files[i].size > Constants.MAX_FILE_SIZE) { @@ -81,18 +81,15 @@ class FileUpload extends React.Component { // generate a unique id that can be used by other components to refer back to this upload const clientId = Utils.generateId(); - // prepare data to be uploaded - var formData = new FormData(); - formData.append('channel_id', channelId); - formData.append('files', files[i], files[i].name); - formData.append('client_ids', clientId); - - var request = Client.uploadFile(formData, + const request = Client.uploadFile(files[i], + files[i].name, + channelId, + clientId, this.fileUploadSuccess.bind(this, channelId), this.fileUploadFail.bind(this, clientId) ); - var requests = this.state.requests; + const requests = this.state.requests; requests[clientId] = request; this.setState({requests}); @@ -231,8 +228,6 @@ class FileUpload extends React.Component { // generate a unique id that can be used by other components to refer back to this file upload var clientId = Utils.generateId(); - var formData = new FormData(); - formData.append('channel_id', channelId); var d = new Date(); var hour; if (d.getHours() < 10) { @@ -247,16 +242,17 @@ class FileUpload extends React.Component { min = String(d.getMinutes()); } - var name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext; - formData.append('files', file, name); - formData.append('client_ids', clientId); + const name = formatMessage(holders.pasted) + d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext; - var request = Client.uploadFile(formData, + const request = Client.uploadFile(file, + name, + channelId, + clientId, self.fileUploadSuccess.bind(self, channelId), self.fileUploadFail.bind(self, clientId) ); - var requests = self.state.requests; + const requests = self.state.requests; requests[clientId] = request; self.setState({requests}); @@ -280,13 +276,13 @@ class FileUpload extends React.Component { } cancelUpload(clientId) { - var requests = this.state.requests; - var request = requests[clientId]; + const requests = JSON.parse(JSON.stringify(this.state.requests)); + const request = requests[clientId]; if (request) { request.abort(); - delete requests[clientId]; + Reflect.deleteProperty(requests, clientId); this.setState({requests}); } } diff --git a/webapp/components/filtered_user_list.jsx b/webapp/components/filtered_user_list.jsx index bd6c49714..5aa0bd96c 100644 --- a/webapp/components/filtered_user_list.jsx +++ b/webapp/components/filtered_user_list.jsx @@ -113,6 +113,7 @@ class FilteredUserList extends React.Component { > <UserList users={users} + teamMembers={this.props.teamMembers} actions={this.props.actions} actionProps={this.props.actionProps} /> @@ -124,6 +125,7 @@ class FilteredUserList extends React.Component { FilteredUserList.defaultProps = { users: [], + teamMembers: [], actions: [], actionProps: {} }; @@ -131,6 +133,7 @@ FilteredUserList.defaultProps = { FilteredUserList.propTypes = { intl: intlShape.isRequired, users: React.PropTypes.arrayOf(React.PropTypes.object), + teamMembers: React.PropTypes.arrayOf(React.PropTypes.object), actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object, style: React.PropTypes.object diff --git a/webapp/components/not_logged_in.jsx b/webapp/components/header_footer_template.jsx index 4beee6259..ce2f59541 100644 --- a/webapp/components/not_logged_in.jsx +++ b/webapp/components/header_footer_template.jsx @@ -9,12 +9,12 @@ import {Link} from 'react-router'; export default class NotLoggedIn extends React.Component { componentDidMount() { - $('body').attr('class', 'sticky'); - $('#root').attr('class', 'container-fluid'); + $('body').addClass('sticky'); + $('#root').addClass('container-fluid'); } componentWillUnmount() { - $('body').attr('class', ''); - $('#root').attr('class', ''); + $('body').removeClass('sticky'); + $('#root').removeClass('container-fluid'); } render() { return ( diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx index 17cec7aad..4ac620f08 100644 --- a/webapp/components/invite_member_modal.jsx +++ b/webapp/components/invite_member_modal.jsx @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom'; 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 Client from 'utils/web_client.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import ModalStore from 'stores/modal_store.jsx'; import UserStore from 'stores/user_store.jsx'; diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index 1dcb6b0aa..3941dd12c 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -2,39 +2,17 @@ // See License.txt for license information. import $ from 'jquery'; +import LoadingScreen from 'components/loading_screen.jsx'; 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 ChannelStore from 'stores/channel_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import Constants from 'utils/constants.jsx'; -const TutorialSteps = Constants.TutorialSteps; -const Preferences = Constants.Preferences; -import ErrorBar from 'components/error_bar.jsx'; import * as Websockets from 'action_creators/websocket_actions.jsx'; -import LoadingScreen from 'components/loading_screen.jsx'; +import Constants from 'utils/constants.jsx'; import {browserHistory} from 'react-router'; -import SidebarRight from 'components/sidebar_right.jsx'; -import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; -import Navbar from 'components/navbar.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; @@ -45,19 +23,22 @@ export default class LoggedIn extends React.Component { super(params); this.onUserChanged = this.onUserChanged.bind(this); + this.setupUser = this.setupUser.bind(this); this.state = { - user: null, - profiles: null + user: UserStore.getCurrentUser() }; + + if (this.state.user) { + this.setupUser(this.state.user); + } } + isValidState() { - return this.state.user != null && this.state.profiles != null; + return this.state.user != null; } - onUserChanged() { - // Grab the current user - const user = UserStore.getCurrentUser(); + setupUser(user) { // Update segment indentify if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') { global.window.analytics.identify(user.id, { @@ -65,7 +46,6 @@ export default class LoggedIn extends React.Component { email: user.email, createdAt: user.create_at, username: user.username, - team_id: user.team_id, id: user.id }); } @@ -78,25 +58,19 @@ export default class LoggedIn extends React.Component { Utils.applyTheme(Constants.THEMES.default); } } + } - // Go to tutorial if we are first arrivign - const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); - if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { - browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); - } - - // Get profiles - const profiles = UserStore.getProfiles(); + onUserChanged() { + // Grab the current user + const user = UserStore.getCurrentUser(); + this.setupUser(user); this.setState({ - user, - profiles + user }); } - componentWillMount() { - // Emit view action - GlobalActions.viewLoggedIn(); + componentWillMount() { // Listen for user UserStore.addChangeListener(this.onUserChanged); @@ -116,7 +90,7 @@ export default class LoggedIn extends React.Component { } console.log('detected logout from a different tab'); //eslint-disable-line no-console - browserHistory.push('/' + this.props.params.team); + browserHistory.push('/'); } if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) { @@ -170,18 +144,6 @@ export default class LoggedIn extends React.Component { $('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); @@ -193,13 +155,11 @@ export default class LoggedIn extends React.Component { } }); } + componentWillUnmount() { $('#root').attr('class', ''); clearInterval(this.intervalId); - $(window).off('focus'); - $(window).off('blur'); - Websockets.close(); UserStore.removeChangeListener(this.onUserChanged); @@ -211,75 +171,18 @@ export default class LoggedIn extends React.Component { $(window).off('keydown.preventBackspace'); } + render() { if (!this.isValidState()) { return <LoadingScreen/>; } - let content = []; - if (this.props.children) { - content = this.props.children; - } else { - content.push( - this.props.navbar - ); - content.push( - this.props.sidebar - ); - content.push( - <div - key='inner-wrap' - className='inner-wrap channel__wrap' - > - <div className='row header'> - <div id='navbar'> - <Navbar/> - </div> - </div> - <div className='row main'> - {React.cloneElement(this.props.center, { - user: this.state.user, - profiles: this.state.profiles - })} - </div> - </div> - ); - } - return ( - <div className='channel-view'> - <ErrorBar/> - <div className='container-fluid'> - <SidebarRight/> - <SidebarRightMenu/> - {content} - - <GetPostLinkModal/> - <GetTeamInviteLinkModal/> - <InviteMemberModal/> - <ImportThemeModal/> - <TeamSettingsModal/> - <MoreChannelsModal/> - <EditPostModal/> - <DeletePostModal/> - <RemovedFromChannelModal/> - <RegisterAppModal/> - <SelectTeamModal/> - </div> - </div> - ); + return React.cloneElement(this.props.children, { + user: this.state.user + }); } } -LoggedIn.defaultProps = { -}; - LoggedIn.propTypes = { - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]), - navbar: React.PropTypes.element, - sidebar: React.PropTypes.element, - center: React.PropTypes.element, - params: React.PropTypes.object + children: React.PropTypes.object }; diff --git a/webapp/components/login/login.jsx b/webapp/components/login/login.jsx index a3dadbf36..c5955a1f4 100644 --- a/webapp/components/login/login.jsx +++ b/webapp/components/login/login.jsx @@ -5,12 +5,14 @@ import LoginEmail from './components/login_email.jsx'; import LoginUsername from './components/login_username.jsx'; import LoginLdap from './components/login_ldap.jsx'; import LoginMfa from './components/login_mfa.jsx'; +import ErrorBar from 'components/error_bar.jsx'; -import TeamStore from 'stores/team_store.jsx'; +import * as GlobalActions from '../../action_creators/global_actions.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; -import * as Client from 'utils/client.jsx'; + import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -18,40 +20,24 @@ import {FormattedMessage} from 'react-intl'; import {browserHistory, Link} from 'react-router'; import React from 'react'; +import logoImage from 'images/logo.png'; export default class Login extends React.Component { constructor(props) { super(props); - this.getStateFromStores = this.getStateFromStores.bind(this); - this.onTeamChange = this.onTeamChange.bind(this); this.preSubmit = this.preSubmit.bind(this); this.submit = this.submit.bind(this); + this.finishSignin = this.finishSignin.bind(this); - const state = this.getStateFromStores(); - state.doneCheckLogin = false; + const state = {}; + state.showMfa = false; this.state = state; } componentDidMount() { - TeamStore.addChangeListener(this.onTeamChange); - Client.getMeLoggedIn((data) => { - if (data && data.logged_in !== 'false') { - browserHistory.push('/' + this.props.params.team + '/channels/town-square'); - } else { - this.setState({doneCheckLogin: true}); //eslint-disable-line react/no-did-mount-set-state - } - }); - } - componentWillUnmount() { - TeamStore.removeChangeListener(this.onTeamChange); - } - getStateFromStores() { - return { - currentTeam: TeamStore.getByName(this.props.params.team) - }; - } - onTeamChange() { - this.setState(this.getStateFromStores()); + if (UserStore.getCurrentUser()) { + browserHistory.push('/select_team'); + } } preSubmit(method, loginId, password) { if (global.window.mm_config.EnableMultifactorAuthentication !== 'true') { @@ -59,7 +45,7 @@ export default class Login extends React.Component { return; } - Client.checkMfa(method, this.state.currentTeam.name, loginId, + Client.checkMfa(method, loginId, (data) => { if (data.mfa_required === 'true') { this.setState({showMfa: true, method, loginId, password}); @@ -78,27 +64,41 @@ export default class Login extends React.Component { } ); } + finishSignin() { + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); + } + submit(method, loginId, password, token) { this.setState({showMfa: false, serverEmailError: null, serverUsernameError: null, serverLdapError: null}); - const team = this.state.currentTeam.name; - if (method === Constants.EMAIL_SERVICE) { - Client.loginByEmail(team, loginId, password, token, + Client.webLogin( + loginId, + null, + password, + token, () => { UserStore.setLastEmail(loginId); - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); }, (err) => { if (err.id === 'api.user.login.not_verified.app_error') { - browserHistory.push('/should_verify_email?teamname=' + encodeURIComponent(team) + '&email=' + encodeURIComponent(loginId)); + browserHistory.push('/should_verify_email?&email=' + encodeURIComponent(loginId)); return; } this.setState({serverEmailError: err.message}); } ); } else if (method === Constants.USERNAME_SERVICE) { - Client.loginByUsername(team, loginId, password, token, + Client.webLogin( + null, + loginId, + password, + token, () => { UserStore.setLastUsername(loginId); @@ -106,7 +106,7 @@ export default class Login extends React.Component { if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); } }, (err) => { @@ -120,13 +120,16 @@ export default class Login extends React.Component { } ); } else if (method === Constants.LDAP_SERVICE) { - Client.loginByLdap(team, loginId, password, token, + Client.loginByLdap( + loginId, + password, + token, () => { const redirect = Utils.getUrlParameter('redirect'); if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + team + '/channels/town-square'); + this.finishSignin(); } }, (err) => { @@ -144,7 +147,7 @@ export default class Login extends React.Component { return ( <div> <img - src='/api/v1/admin/get_brand_image' + src={Client.getAdminRoute() + '/get_brand_image'} /> <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/> </div> @@ -153,7 +156,7 @@ export default class Login extends React.Component { return null; } - createLoginOptions(currentTeam) { + createLoginOptions() { const extraParam = Utils.getUrlParameter('extra'); let extraBox = ''; if (extraParam) { @@ -187,10 +190,19 @@ export default class Login extends React.Component { /> </div> ); + } else if (extraParam === Constants.PASSWORD_CHANGE) { + extraBox = ( + <div className='alert alert-success'> + <i className='fa fa-check'/> + <FormattedMessage + id='login.passwordChanged' + defaultMessage=' Password updated successfully' + /> + </div> + ); } } - const teamName = currentTeam.name; const ldapEnabled = global.window.mm_config.EnableLdap === 'true'; const gitlabSigninEnabled = global.window.mm_config.EnableSignUpWithGitLab === 'true'; const googleSigninEnabled = global.window.mm_config.EnableSignUpWithGoogle === 'true'; @@ -200,10 +212,10 @@ export default class Login extends React.Component { const oauthLogins = []; if (gitlabSigninEnabled) { oauthLogins.push( - <Link + <a className='btn btn-custom-login gitlab' key='gitlab' - to={'/api/v1/oauth/gitlab/login?team=' + encodeURIComponent(teamName)} + href={Client.getOAuthRoute() + '/gitlab/login'} > <span className='icon'/> <span> @@ -212,7 +224,7 @@ export default class Login extends React.Component { defaultMessage='with GitLab' /> </span> - </Link> + </a> ); } @@ -221,7 +233,7 @@ export default class Login extends React.Component { <Link className='btn btn-custom-login google' key='google' - to={'/api/v1/oauth/google/login?team=' + encodeURIComponent(teamName)} + to={Client.getOAuthRoute() + '/google/login'} > <span className='icon'/> <span> @@ -238,7 +250,6 @@ export default class Login extends React.Component { if (emailSigninEnabled) { emailLogin = ( <LoginEmail - teamName={teamName} serverError={this.state.serverEmailError} submit={this.preSubmit} /> @@ -263,7 +274,6 @@ export default class Login extends React.Component { if (usernameSigninEnabled) { usernameLogin = ( <LoginUsername - teamName={teamName} serverError={this.state.serverUsernameError} submit={this.preSubmit} /> @@ -288,7 +298,6 @@ export default class Login extends React.Component { if (ldapEnabled) { ldapLogin = ( <LoginLdap - teamName={teamName} serverError={this.state.serverLdapError} submit={this.preSubmit} /> @@ -309,34 +318,31 @@ export default class Login extends React.Component { } } - let userSignUp; - if (currentTeam.allow_open_invite) { - userSignUp = ( - <div> - <span> + const userSignUp = ( + <div> + <span> + <FormattedMessage + id='login.noAccount' + defaultMessage="Don't have an account? " + /> + <Link + to={'/signup_user_complete'} + className='signup-team-login' + > <FormattedMessage - id='login.noAccount' - defaultMessage="Don't have an account? " + id='login.create' + defaultMessage='Create one now' /> - <Link - to={'/signup_user_complete/?id=' + currentTeam.invite_id} - className='signup-team-login' - > - <FormattedMessage - id='login.create' - defaultMessage='Create one now' - /> - </Link> - </span> - </div> - ); - } + </Link> + </span> + </div> + ); let forgotPassword; if (usernameSigninEnabled || emailSigninEnabled) { forgotPassword = ( <div className='form-group'> - <Link to={'/' + teamName + '/reset_password'}> + <Link to={'/reset_password'}> <FormattedMessage id='login.forgot' defaultMessage='I forgot my password' @@ -346,23 +352,6 @@ export default class Login extends React.Component { ); } - let teamSignUp; - if (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp()) { - teamSignUp = ( - <div className='margin--extra'> - <Link - to='/' - className='signup-team-login' - > - <FormattedMessage - id='login.createTeam' - defaultMessage='Create a new team' - /> - </Link> - </div> - ); - } - return ( <div> {extraBox} @@ -372,16 +361,10 @@ export default class Login extends React.Component { {ldapLogin} {userSignUp} {forgotPassword} - {teamSignUp} </div> ); } render() { - const currentTeam = this.state.currentTeam; - if (currentTeam == null || !this.state.doneCheckLogin) { - return <div/>; - } - let content; let customContent; let customClass; @@ -395,7 +378,7 @@ export default class Login extends React.Component { /> ); } else { - content = this.createLoginOptions(currentTeam); + content = this.createLoginOptions(); customContent = this.createCustomLogin(); if (customContent) { customClass = 'branded'; @@ -404,36 +387,23 @@ export default class Login extends React.Component { return ( <div> - <div className='signup-header'> - <Link to='/'> - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </Link> - </div> + <ErrorBar/> <div className='col-sm-12'> <div className={'signup-team__container ' + customClass}> <div className='signup__markdown'> {customContent} </div> + <img + className='signup-team-logo' + src={logoImage} + /> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> <div className='signup__content'> - <h5 className='margin--less'> - <FormattedMessage - id='login.signTo' - defaultMessage='Sign in to:' - /> - </h5> - <h2 className='signup-team__name'>{currentTeam.display_name}</h2> - <h2 className='signup-team__subdomain'> - <FormattedMessage - id='login.on' - defaultMessage='on {siteName}' - values={{ - siteName: global.window.mm_config.SiteName - }} - /> - </h2> {content} </div> </div> diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx index bb5eee496..d0714e942 100644 --- a/webapp/components/member_list_team.jsx +++ b/webapp/components/member_list_team.jsx @@ -4,6 +4,8 @@ import FilteredUserList from './filtered_user_list.jsx'; import TeamMembersDropdown from './team_members_dropdown.jsx'; import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; import React from 'react'; @@ -13,18 +15,23 @@ export default class MemberListTeam extends React.Component { this.getUsers = this.getUsers.bind(this); this.onChange = this.onChange.bind(this); + this.onTeamChange = this.onTeamChange.bind(this); this.state = { - users: this.getUsers() + users: this.getUsers(), + teamMembers: TeamStore.getMembersForTeam() }; } componentDidMount() { UserStore.addChangeListener(this.onChange); + TeamStore.addChangeListener(this.onTeamChange); + AsyncClient.getTeamMembers(TeamStore.getCurrentId()); } componentWillUnmount() { UserStore.removeChangeListener(this.onChange); + TeamStore.removeChangeListener(this.onTeamChange); } getUsers() { @@ -46,11 +53,18 @@ export default class MemberListTeam extends React.Component { }); } + onTeamChange() { + this.setState({ + teamMembers: TeamStore.getMembersForTeam() + }); + } + render() { return ( <FilteredUserList style={this.props.style} users={this.state.users} + teamMembers={this.state.teamMembers} actions={[TeamMembersDropdown]} /> ); diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx index 5ccf9c11e..3ab05341b 100644 --- a/webapp/components/more_channels.jsx +++ b/webapp/components/more_channels.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import LoadingScreen from './loading_screen.jsx'; diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index e4e64c12e..919a72d0a 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -18,7 +18,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import TeamStore from 'stores/team_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx index da1ae237e..19b99a14d 100644 --- a/webapp/components/navbar_dropdown.jsx +++ b/webapp/components/navbar_dropdown.jsx @@ -6,6 +6,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; +import TeamStore from 'stores/team_store.jsx'; import AboutBuildModal from './about_build_modal.jsx'; import TeamMembersModal from './team_members_modal.jsx'; import ToggleModalButton from './toggle_modal_button.jsx'; @@ -25,10 +26,13 @@ export default class NavbarDropdown extends React.Component { this.handleAboutModal = this.handleAboutModal.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this); + this.onTeamChange = this.onTeamChange.bind(this); this.state = { showUserSettingsModal: false, - showAboutModal: false + showAboutModal: false, + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers() }; } handleAboutModal() { @@ -37,6 +41,7 @@ export default class NavbarDropdown extends React.Component { aboutModalDismissed() { this.setState({showAboutModal: false}); } + componentDidMount() { $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => { $('.sidebar--left .dropdown-menu').scrollTop(0); @@ -45,10 +50,22 @@ export default class NavbarDropdown extends React.Component { this.blockToggle = false; }, 100); }); + + TeamStore.addChangeListener(this.onTeamChange); + } + + onTeamChange() { + this.setState({ + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers() + }); } + componentWillUnmount() { $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + TeamStore.removeChangeListener(this.onTeamChange); } + render() { var teamLink = ''; var inviteLink = ''; @@ -130,7 +147,7 @@ export default class NavbarDropdown extends React.Component { if (isAdmin || window.EnableAdminOnlyIntegrations !== 'true') { integrationsLink = ( <li> - <Link to={'/settings/integrations'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations'}> <FormattedMessage id='navbar_dropdown.integrations' defaultMessage='Integrations' @@ -163,8 +180,7 @@ export default class NavbarDropdown extends React.Component { <li key='newTeam_li'> <Link key='newTeam_a' - target='_blank' - to={Utils.getWindowLocationOrigin() + '/signup_team'} + to='/create_team' > <FormattedMessage id='navbar_dropdown.create' @@ -175,6 +191,34 @@ export default class NavbarDropdown extends React.Component { ); } + if (this.state.teamMembers && this.state.teamMembers.length > 0) { + teams.push( + <li + key='teamDiv' + className='divider' + ></li> + ); + + for (var index in this.state.teamMembers) { + if (this.state.teamMembers.hasOwnProperty(index)) { + var teamMember = this.state.teamMembers[index]; + var team = this.state.teams[teamMember.team_id]; + + if (team.name !== this.props.teamName) { + teams.push( + <li key={'team_' + team.name}> + <Link + to={'/' + team.name + '/channels/town-square'} + > + {team.display_name} + </Link> + </li> + ); + } + } + } + } + let helpLink = null; if (global.window.mm_config.HelpLink) { helpLink = ( @@ -245,12 +289,15 @@ export default class NavbarDropdown extends React.Component { {inviteLink} {teamLink} <li> - <Link to={'/' + this.props.teamName + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <FormattedMessage id='navbar_dropdown.logout' defaultMessage='Logout' /> - </Link> + </a> </li> {adminDivider} {teamSettings} diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx index f624b1ebc..59797f086 100644 --- a/webapp/components/needs_team.jsx +++ b/webapp/components/needs_team.jsx @@ -1,22 +1,156 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import React from 'react'; + +import $ from 'jquery'; + +import {browserHistory} from 'react-router'; +import * as Utils from 'utils/utils.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import PreferenceStore from 'stores/preference_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; +import Constants from 'utils/constants.jsx'; +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; -import React from 'react'; +import ErrorBar from 'components/error_bar.jsx'; +import SidebarRight from 'components/sidebar_right.jsx'; +import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; +import Navbar from 'components/navbar.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'; export default class NeedsTeam extends React.Component { + constructor(params) { + super(params); + + this.onChanged = this.onChanged.bind(this); + + this.state = { + profiles: UserStore.getProfiles(), + team: TeamStore.getCurrent() + }; + } + + onChanged() { + this.setState({ + profiles: UserStore.getProfiles(), + team: TeamStore.getCurrent() + }); + } + componentWillMount() { - GlobalActions.loadTeamRequiredPage(); + UserStore.addChangeListener(this.onChanged); + TeamStore.addChangeListener(this.onChanged); + + // Emit view action + GlobalActions.viewLoggedIn(); + + // Go to tutorial if we are first arrivign + const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); + if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { + browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); + } + + // 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; + }); + } + + componentWillUnmount() { + UserStore.removeChangeListener(this.onChanged); + TeamStore.addChangeListener(this.onChanged); + $(window).off('focus'); + $(window).off('blur'); } + render() { - return this.props.children; + let content = []; + if (this.props.children) { + content = this.props.children; + } else { + content.push( + this.props.navbar + ); + content.push( + this.props.sidebar + ); + content.push( + <div + key='inner-wrap' + className='inner-wrap channel__wrap' + > + <div className='row header'> + <div id='navbar'> + <Navbar/> + </div> + </div> + <div className='row main'> + {React.cloneElement(this.props.center, { + user: this.props.user, + profiles: this.state.profiles, + team: this.state.team + })} + </div> + </div> + ); + } + return ( + <div className='channel-view'> + <ErrorBar/> + <div className='container-fluid'> + <SidebarRight/> + <SidebarRightMenu/> + {content} + + <GetPostLinkModal/> + <GetTeamInviteLinkModal/> + <InviteMemberModal/> + <ImportThemeModal/> + <TeamSettingsModal/> + <MoreChannelsModal/> + <EditPostModal/> + <DeletePostModal/> + <RemovedFromChannelModal/> + <RegisterAppModal/> + <SelectTeamModal/> + </div> + </div> + ); } } -NeedsTeam.defaultProps = { -}; - NeedsTeam.propTypes = { - children: React.PropTypes.object + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]), + navbar: React.PropTypes.element, + sidebar: React.PropTypes.element, + center: React.PropTypes.element, + params: React.PropTypes.object, + user: React.PropTypes.object }; diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx index 82494dac0..7019da4aa 100644 --- a/webapp/components/new_channel_flow.jsx +++ b/webapp/components/new_channel_flow.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import UserStore from 'stores/user_store.jsx'; import NewChannelModal from './new_channel_modal.jsx'; @@ -106,11 +106,7 @@ class NewChannelFlow extends React.Component { (data) => { Client.getChannel( data.id, - (data2, textStatus, xhr) => { - if (xhr.status === 304 || !data2) { - return; - } - + (data2) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CHANNEL, channel: data2.channel, diff --git a/webapp/components/notify_counts.jsx b/webapp/components/notify_counts.jsx index acc64dfb0..9238c8736 100644 --- a/webapp/components/notify_counts.jsx +++ b/webapp/components/notify_counts.jsx @@ -32,17 +32,22 @@ export default class NotifyCounts extends React.Component { this.onListenerChange = this.onListenerChange.bind(this); this.state = getCountsStateFromStores(); + this.mounted = false; } componentDidMount() { + this.mounted = true; ChannelStore.addChangeListener(this.onListenerChange); } componentWillUnmount() { + this.mounted = false; ChannelStore.removeChangeListener(this.onListenerChange); } onListenerChange() { - var newState = getCountsStateFromStores(); - if (!utils.areObjectsEqual(newState, this.state)) { - this.setState(newState); + if (this.mounted) { + var newState = getCountsStateFromStores(); + if (!utils.areObjectsEqual(newState, this.state)) { + this.setState(newState); + } } } render() { diff --git a/webapp/components/password_reset_form.jsx b/webapp/components/password_reset_form.jsx index 863420902..23b8952cc 100644 --- a/webapp/components/password_reset_form.jsx +++ b/webapp/components/password_reset_form.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import ReactDOM from 'react-dom'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -40,16 +40,12 @@ class PasswordResetForm extends React.Component { error: null }); - const data = {}; - data.new_password = password; - data.hash = this.props.location.query.h; - data.data = this.props.location.query.d; - data.name = this.props.params.team; - - Client.resetPassword(data, + Client.resetPassword( + this.props.location.query.code, + password, () => { this.setState({error: null}); - browserHistory.push('/' + this.props.params.team + '/login'); + browserHistory.push('/login?extra=' + Constants.PASSWORD_CHANGE); }, (err) => { this.setState({error: err.message}); diff --git a/webapp/components/password_reset_send_link.jsx b/webapp/components/password_reset_send_link.jsx index e3ab8949e..65d9439bd 100644 --- a/webapp/components/password_reset_send_link.jsx +++ b/webapp/components/password_reset_send_link.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -43,10 +43,8 @@ class PasswordResetSendLink extends React.Component { error: '' }); - var data = {}; - data.email = email; - data.name = this.props.params.team; - client.sendPasswordReset(data, + client.sendPasswordReset( + email, () => { this.setState({ error: null, diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx index 8c3c381af..387c37ab5 100644 --- a/webapp/components/popover_list_members.jsx +++ b/webapp/components/popover_list_members.jsx @@ -7,6 +7,7 @@ import UserStore from 'stores/user_store.jsx'; import {Popover, Overlay} from 'react-bootstrap'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; import {browserHistory} from 'react-router'; @@ -97,7 +98,7 @@ export default class PopoverListMembers extends React.Component { className='more-modal__image' width='26px' height='26px' - src={`/api/v1/users/${m.id}/image?time=${m.update_at}`} + src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`} /> <div className='more-modal__details'> <div diff --git a/webapp/components/post.jsx b/webapp/components/post.jsx index 7fabec741..ae3fa9c98 100644 --- a/webapp/components/post.jsx +++ b/webapp/components/post.jsx @@ -10,7 +10,7 @@ import ChannelStore from 'stores/channel_store.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; @@ -48,7 +48,7 @@ export default class Post extends React.Component { e.preventDefault(); var post = this.props.post; - Client.createPost(post, post.channel_id, + Client.createPost(post, (data) => { AsyncClient.getPosts(); diff --git a/webapp/components/register_app_modal.jsx b/webapp/components/register_app_modal.jsx index c632233cf..82a095c75 100644 --- a/webapp/components/register_app_modal.jsx +++ b/webapp/components/register_app_modal.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import ModalStore from 'stores/modal_store.jsx'; import {Modal} from 'react-bootstrap'; diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx index 3e47847e7..ed045da91 100644 --- a/webapp/components/rename_channel_modal.jsx +++ b/webapp/components/rename_channel_modal.jsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index 53170ee15..709865dc1 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -9,7 +9,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import FileAttachmentList from './file_attachment_list.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; var ActionTypes = Constants.ActionTypes; import * as TextFormatting from 'utils/text_formatting.jsx'; @@ -43,7 +43,7 @@ class RhsComment extends React.Component { e.preventDefault(); var post = this.props.post; - Client.createPost(post, post.channel_id, + Client.createPost(post, (data) => { AsyncClient.getPosts(post.channel_id); @@ -261,7 +261,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} + src={Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' /> diff --git a/webapp/components/rhs_header_post.jsx b/webapp/components/rhs_header_post.jsx index 189ee0acb..493040800 100644 --- a/webapp/components/rhs_header_post.jsx +++ b/webapp/components/rhs_header_post.jsx @@ -3,6 +3,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; import Constants from 'utils/constants.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -21,16 +22,7 @@ export default class RhsHeaderPost extends React.Component { } handleClose(e) { e.preventDefault(); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_SEARCH, - results: null - }); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_POST_SELECTED, - postId: null - }); + GlobalActions.emitCloseRightHandSide(); } handleBack(e) { e.preventDefault(); diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx index 59364d085..0adbc7f04 100644 --- a/webapp/components/root.jsx +++ b/webapp/components/root.jsx @@ -1,10 +1,10 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; +//import $ from 'jquery'; +//import Client from 'utils/web_client.jsx'; + import * as GlobalActions from 'action_creators/global_actions.jsx'; -import * as Client from 'utils/client.jsx'; -import BrowserStore from 'stores/browser_store.jsx'; import LocalizationStore from 'stores/localization_store.jsx'; import {IntlProvider} from 'react-intl'; @@ -14,6 +14,7 @@ import React from 'react'; import FastClick from 'fastclick'; import {browserHistory} from 'react-router'; +import UserStore from 'stores/user_store.jsx'; export default class Root extends React.Component { constructor(props) { @@ -29,15 +30,16 @@ export default class Root extends React.Component { localizationChanged() { this.setState({locale: LocalizationStore.getLocale(), translations: LocalizationStore.getTranslations()}); } + redirectIfNecessary(props) { if (props.location.pathname === '/') { - Client.getMeLoggedIn((data) => { - if (!data || data.logged_in === 'false') { - browserHistory.push('/signup_team'); - } else { - browserHistory.push('/' + data.team_name + '/channels/town-square'); - } - }); + if (UserStore.getNoAccounts()) { + browserHistory.push('/signup_user_complete'); + } else if (UserStore.getCurrentUser()) { + browserHistory.push('/select_team'); + } else { + browserHistory.push('/login'); + } } } componentWillReceiveProps(newProps) { @@ -47,28 +49,6 @@ export default class Root extends React.Component { // 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 !== "") { @@ -76,11 +56,7 @@ export default class Root extends React.Component { analytics.load(window.mm_config.SegmentDeveloperKey); analytics.page(); }}(); - } else { - global.window.analytics = {}; - global.window.analytics.page = function(){}; - global.window.analytics.track = function(){}; - } + } /*eslint-enable */ // Fastclick diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx index caaf0f844..1156ac0f1 100644 --- a/webapp/components/search_bar.jsx +++ b/webapp/components/search_bar.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; -import * as client from 'utils/client.jsx'; +import client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import SearchStore from 'stores/search_store.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx new file mode 100644 index 000000000..5804a641e --- /dev/null +++ b/webapp/components/select_team/select_team.jsx @@ -0,0 +1,257 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as Utils from 'utils/utils.jsx'; +import ErrorBar from 'components/error_bar.jsx'; +import LoadingScreen from 'components/loading_screen.jsx'; +import Client from 'utils/web_client.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; + +import * as TextFormatting from 'utils/text_formatting.jsx'; + +import {Link} from 'react-router'; + +import {FormattedMessage} from 'react-intl'; + +//import {browserHistory, Link} from 'react-router'; + +import React from 'react'; +import logoImage from 'images/logo.png'; + +export default class Login extends React.Component { + + constructor(props) { + super(props); + this.onTeamChange = this.onTeamChange.bind(this); + + const state = this.getStateFromStores(false); + this.state = state; + } + + componentDidMount() { + TeamStore.addChangeListener(this.onTeamChange); + AsyncClient.getAllTeamListings(); + } + + componentWillUnmount() { + TeamStore.removeChangeListener(this.onTeamChange); + } + + onTeamChange() { + this.setState(this.getStateFromStores(true)); + } + + getStateFromStores(loaded) { + return { + teams: TeamStore.getAll(), + teamMembers: TeamStore.getTeamMembers(), + teamListings: TeamStore.getTeamListings(), + loaded + }; + } + + createCustomLogin() { + if (global.window.mm_license.IsLicensed === 'true' && + global.window.mm_license.CustomBrand === 'true' && + global.window.mm_config.EnableCustomBrand === 'true') { + const text = global.window.mm_config.CustomBrandText || ''; + + return ( + <div> + <img + src={Client.getAdminRoute() + '/get_brand_image'} + /> + <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/> + </div> + ); + } + + return null; + } + + render() { + var content; + + let customClass; + const customContent = this.createCustomLogin(); + if (customContent) { + customClass = 'branded'; + } + + var teamContents = []; + var isAlreadyMember = new Map(); + + for (var index in this.state.teamMembers) { + if (this.state.teamMembers.hasOwnProperty(index)) { + var teamMember = this.state.teamMembers[index]; + var team = this.state.teams[teamMember.team_id]; + isAlreadyMember[teamMember.team_id] = true; + teamContents.push( + <div + key={'team_' + team.name} + className='signup-team-dir' + > + <Link + to={'/' + team.name + '/channels/town-square'} + > + <span className='signup-team-dir__name'>{team.display_name}</span> + <span + className='glyphicon glyphicon-menu-right right signup-team-dir__arrow' + aria-hidden='true' + /> + </Link> + </div> + ); + } + } + + if (!teamContents || teamContents.length === 0) { + teamContents = ( + <div className='signup-team-dir-err'> + <div> + <FormattedMessage + id='signup_team.no_teams' + defaultMessage='You do not appear to be a member of any team. Please ask your administrator for an invite, join an open team if one exists or possibly create a new team.' + /> + </div> + </div> + ); + } + + content = ( + <div className='signup__content'> + <h4> + <FormattedMessage + id='signup_team.choose' + defaultMessage='Teams you are a member of:' + /> + </h4> + <div className='signup-team-all'> + {teamContents} + </div> + </div> + ); + + var openTeamContents = []; + + for (var id in this.state.teamListings) { + if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) { + var openTeam = this.state.teamListings[id]; + openTeamContents.push( + <div + key={'team_' + openTeam.name} + className='signup-team-dir' + > + <Link + to={`/signup_user_complete/?id=${openTeam.invite_id}`} + > + <span className='signup-team-dir__name'>{openTeam.display_name}</span> + <span + className='glyphicon glyphicon-menu-right right signup-team-dir__arrow' + aria-hidden='true' + /> + </Link> + </div> + ); + } + } + + var openContent; + if (openTeamContents.length > 0) { + openContent = ( + <div className='signup__content'> + <h4> + <FormattedMessage + id='signup_team.join_open' + defaultMessage='Open teams you can join: ' + /> + </h4> + <div className='signup-team-all'> + {openTeamContents} + </div> + </div> + ); + } + + if (!this.state.loaded) { + openContent = <LoadingScreen/>; + } + + var isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles); + + let teamSignUp; + if (isSystemAdmin || (global.window.mm_config.EnableTeamCreation === 'true' && !Utils.isMobileApp())) { + teamSignUp = ( + <div className='margin--extra'> + <Link + to='/create_team' + className='signup-team-login' + > + <FormattedMessage + id='login.createTeam' + defaultMessage='Create a new team' + /> + </Link> + </div> + ); + } + + let adminConsoleLink; + if (isSystemAdmin) { + adminConsoleLink = ( + <div className='margin--extra'> + <Link + to='/admin_console' + className='signup-team-login' + > + <FormattedMessage + id='navbar_dropdown.console' + defaultMessage='System Console' + /> + </Link> + </div> + ); + } + + return ( + <div> + <ErrorBar/> + <div className='signup-header'> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='navbar_dropdown.logout' + /> + </a> + </div> + <div className='col-sm-12'> + <div className={'signup-team__container ' + customClass}> + <div className='signup__markdown'> + {customContent} + </div> + <img + className='signup-team-logo' + src={logoImage} + /> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> + <FormattedMessage + id='web.root.singup_info' + /> + </h4> + {content} + {openContent} + {teamSignUp} + {adminConsoleLink} + </div> + </div> + </div> + ); + } +} diff --git a/webapp/components/should_verify_email.jsx b/webapp/components/should_verify_email.jsx index 5103452b0..a95101ba1 100644 --- a/webapp/components/should_verify_email.jsx +++ b/webapp/components/should_verify_email.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import {FormattedMessage} from 'react-intl'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import {Link} from 'react-router'; @@ -18,19 +18,19 @@ export default class ShouldVerifyEmail extends React.Component { }; } 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); + Client.resendVerification( + email, + () => { + this.setState({resendStatus: 'success'}); + }, + () => { + this.setState({resendStatus: 'failure'}); + } + ); } render() { let resendConfirm = ''; diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx index ec3a03d17..143a3458a 100644 --- a/webapp/components/sidebar_header.jsx +++ b/webapp/components/sidebar_header.jsx @@ -7,6 +7,7 @@ import NavbarDropdown from './navbar_dropdown.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; const Preferences = Constants.Preferences; @@ -61,7 +62,7 @@ export default class SidebarHeader extends React.Component { profilePicture = ( <img className='user__picture' - src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at} /> ); } diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx index c7e6577fc..42bc7ce53 100644 --- a/webapp/components/sidebar_right_menu.jsx +++ b/webapp/components/sidebar_right_menu.jsx @@ -228,13 +228,16 @@ export default class SidebarRightMenu extends React.Component { {manageLink} {consoleLink} <li> - <Link to={Utils.getTeamURLFromAddressBar() + '/logout'}> + <a + href='#' + onClick={GlobalActions.emitUserLoggedOutEvent} + > <i className='fa fa-sign-out'></i> <FormattedMessage id='sidebar_right_menu.logout' defaultMessage='Logout' /> - </Link> + </a> </li> <li className='divider'></li> {helpLink} diff --git a/webapp/components/signup_team.jsx b/webapp/components/signup_team.jsx deleted file mode 100644 index 634aa6e68..000000000 --- a/webapp/components/signup_team.jsx +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ChoosePage from './team_signup_choose_auth.jsx'; -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 'react-intl'; - -import React from 'react'; -import {Link} from 'react-router'; - -import logoImage from 'images/logo.png'; - -export default class TeamSignUp extends React.Component { - constructor(props) { - super(props); - - this.updatePage = this.updatePage.bind(this); - this.onTeamUpdate = this.onTeamUpdate.bind(this); - - var count = 0; - - if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - count = count + 1; - } - - if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - count = count + 1; - } - - if (global.window.mm_config.EnableLdap === 'true') { - count = count + 1; - } - - if (count > 1) { - this.state = {page: 'choose'}; - } else if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - this.state = {page: 'email'}; - } else if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - this.state = {page: 'gitlab'}; - } else if (global.window.mm_config.EnableLdap === 'true') { - this.state = {page: 'ldap'}; - } else { - this.state = {page: 'none'}; - } - } - - updatePage(page) { - 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() { - let teamListing = null; - - if (global.window.mm_config.EnableTeamListing === 'true') { - if (this.state.teams == null) { - teamListing = (<div/>); - } else if (this.state.teams.length === 0) { - if (global.window.mm_config.EnableTeamCreation !== 'true') { - teamListing = ( - <div> - <FormattedMessage - id='signup_team.noTeams' - defaultMessage='There are no teams included in the Team Directory and team creation has been disabled.' - /> - </div> - ); - } - } else { - teamListing = ( - <div> - <h4> - <FormattedMessage - id='signup_team.choose' - defaultMessage='Choose a Team' - /> - </h4> - <div className='signup-team-all'> - { - Object.values(this.state.teams).map((team) => { - if (team.allow_team_listing) { - return ( - <div - key={'team_' + team.name} - className='signup-team-dir' - > - <Link - to={'/' + 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' - /> - </Link> - </div> - ); - } - return null; - }) - } - </div> - <h4> - <FormattedMessage - id='signup_team.createTeam' - defaultMessage='Or Create a Team' - /> - </h4> - </div> - ); - } - } - - let signupMethod = null; - let goBack = ( - <div className='signup-header'> - <a - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.updatePage('choose'); - } - } - > - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </a> - </div> - ); - - if (global.window.mm_config.EnableTeamCreation !== 'true') { - if (teamListing == null) { - signupMethod = ( - <FormattedMessage - id='signup_team.disabled' - defaultMessage='Team creation has been disabled. Please contact an administrator for access.' - /> - ); - } - } else if (this.state.page === 'choose') { - signupMethod = ( - <ChoosePage - updatePage={this.updatePage} - /> - ); - goBack = null; - } else if (this.state.page === 'email') { - signupMethod = ( - <div> - <EmailSignUpPage/> - </div> - ); - } else if (this.state.page === 'ldap') { - return ( - <div> - {teamListing} - <LdapSignUpPage/> - </div> - ); - } else if (this.state.page === 'gitlab') { - signupMethod = ( - <SSOSignupPage service={Constants.GITLAB_SERVICE}/> - ); - } else if (this.state.page === 'google') { - signupMethod = ( - <SSOSignupPage service={Constants.GOOGLE_SERVICE}/> - ); - } else if (this.state.page === 'none') { - signupMethod = ( - <FormattedMessage - id='signup_team.none' - defaultMessage='No team creation method has been enabled. Please contact an administrator for access.' - /> - ); - goBack = null; - } - - return ( - <div> - {goBack} - <div className='col-sm-12'> - <div className='signup-team__container'> - <img - className='signup-team-logo' - src={logoImage} - /> - <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> - </div> - ); - } -} - -TeamSignUp.propTypes = { -}; - diff --git a/webapp/components/signup_team_complete/components/signup_team_complete.jsx b/webapp/components/signup_team_complete/components/signup_team_complete.jsx index 95b41dbde..00fdafe5f 100644 --- a/webapp/components/signup_team_complete/components/signup_team_complete.jsx +++ b/webapp/components/signup_team_complete/components/signup_team_complete.jsx @@ -74,6 +74,7 @@ export default class SignupTeamComplete extends React.Component { SignupTeamComplete.defaultProps = { }; + SignupTeamComplete.propTypes = { location: React.PropTypes.object, children: React.PropTypes.node diff --git a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx b/webapp/components/signup_team_complete/components/team_signup_email_item.jsx deleted file mode 100644 index c3903ca85..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_email_item.jsx +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; - -import {intlShape, injectIntl, defineMessages} from 'react-intl'; - -const holders = defineMessages({ - validEmail: { - id: 'team_signup_email.validEmail', - defaultMessage: 'Please enter a valid email address' - }, - different: { - id: 'team_signup_email.different', - defaultMessage: 'Please use a different email than the one used at signup' - }, - address: { - id: 'team_signup_email.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class TeamSignupEmailItem extends React.Component { - constructor(props) { - super(props); - - this.getValue = this.getValue.bind(this); - this.validate = this.validate.bind(this); - - this.state = {}; - } - getValue() { - return ReactDOM.findDOMNode(this.refs.email).value.trim(); - } - validate(teamEmail) { - const {formatMessage} = this.props.intl; - const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - - if (!email) { - return true; - } - - if (!Utils.isEmail(email)) { - this.setState({emailError: formatMessage(holders.validEmail)}); - return false; - } else if (email === teamEmail) { - this.setState({emailError: formatMessage(holders.different)}); - return false; - } - - this.setState({emailError: ''}); - return true; - } - render() { - let emailError = null; - let emailDivClass = 'form-group'; - if (this.state.emailError) { - emailError = <label className='control-label'>{this.state.emailError}</label>; - emailDivClass += ' has-error'; - } - - return ( - <div className={emailDivClass}> - <input - autoFocus={this.props.focus} - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - defaultValue={this.props.email} - maxLength='128' - spellCheck='false' - /> - {emailError} - </div> - ); - } -} - -TeamSignupEmailItem.propTypes = { - intl: intlShape.isRequired, - focus: React.PropTypes.bool, - email: React.PropTypes.string -}; - -export default injectIntl(TeamSignupEmailItem, {withRef: true}); diff --git a/webapp/components/signup_team_complete/components/team_signup_finished.jsx b/webapp/components/signup_team_complete/components/team_signup_finished.jsx deleted file mode 100644 index 9fbb8473a..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_finished.jsx +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; - -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/webapp/components/signup_team_complete/components/team_signup_password_page.jsx b/webapp/components/signup_team_complete/components/team_signup_password_page.jsx deleted file mode 100644 index 7b8b49e0c..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_password_page.jsx +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -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 'react-intl'; -import {browserHistory} from 'react-router'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - passwordError: { - id: 'team_signup_password.passwordError', - defaultMessage: 'Please enter at least {chars} characters' - }, - creating: { - id: 'team_signup_password.creating', - defaultMessage: 'Creating team...' - } -}); - -import React from 'react'; - -class TeamSignupPasswordPage extends React.Component { - constructor(props) { - super(props); - - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - - this.state = {}; - } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - var password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) { - this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})}); - return; - } - - this.setState({passwordError: null, serverError: null}); - $('#finish-button').button('loading'); - var teamSignup = JSON.parse(JSON.stringify(this.props.state)); - teamSignup.user.password = password; - teamSignup.user.allow_marketing = true; - delete teamSignup.wizard; - - Client.createTeamFromSignup(teamSignup, - () => { - Client.track('signup', 'signup_team_08_complete'); - - var props = this.props; - - Client.loginByEmail( - teamSignup.team.name, - teamSignup.team.email, - teamSignup.user.password, - '', // No MFA Token - () => { - UserStore.setLastEmail(teamSignup.team.email); - if (this.props.hash > 0) { - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); - } - - $('#sign-up-button').button('reset'); - props.state.wizard = 'finished'; - props.updateParent(props.state, true); - - browserHistory.push('/' + teamSignup.team.name + '/channels/town-square'); - }, - (err) => { - if (err.id === 'api.user.login.not_verified.app_error') { - browserHistory.push('/should_verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name)); - } else { - this.setState({serverError: err.message}); - $('#finish-button').button('reset'); - } - } - ); - }, - (err) => { - this.setState({serverError: err.message}); - $('#finish-button').button('reset'); - } - ); - } - render() { - Client.track('signup', 'signup_team_07_password'); - - var passwordError = null; - var passwordDivStyle = 'form-group'; - if (this.state.passwordError) { - passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>; - passwordDivStyle = ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2 className='margin--less'> - <FormattedMessage - id='team_signup_password.yourPassword' - defaultMessage='Your password' - /> - </h2> - <h5 className='color--light'> - <FormattedMessage - id='team_signup_password.selectPassword' - defaultMessage="Select a password that you'll use to login with your email address:" - /> - </h5> - <div className='inner__content margin--extra'> - <h5><strong> - <FormattedMessage - id='team_signup_password.email' - defaultMessage='Email' - /> - </strong></h5> - <div className='block--gray form-group'>{this.props.state.team.email}</div> - <div className={passwordDivStyle}> - <div className='row'> - <div className='col-sm-11'> - <h5><strong> - <FormattedMessage - id='team_signup_password.choosePwd' - defaultMessage='Choose your password' - /> - </strong></h5> - <input - autoFocus={true} - type='password' - ref='password' - className='form-control' - placeholder='' - maxLength='128' - spellCheck='false' - /> - <span className='color--light help-block'> - <FormattedMessage - id='team_signup_password.hint' - defaultMessage='Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.' - values={{ - min: Constants.MIN_PASSWORD_LENGTH, - max: Constants.MAX_PASSWORD_LENGTH - }} - /> - </span> - </div> - </div> - {passwordError} - {serverError} - </div> - </div> - <div className='form-group'> - <button - type='submit' - className='btn btn-primary margin--extra' - id='finish-button' - data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.creating)} - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_password.finish' - defaultMessage='Finish' - /> - </button> - </div> - <p> - <FormattedHTMLMessage - id='team_signup_password.agreement' - defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." - values={{ - siteName: global.window.mm_config.SiteName - }} - /> - </p> - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_password.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </form> - </div> - ); - } -} - -TeamSignupPasswordPage.defaultProps = { - state: {}, - hash: '' -}; -TeamSignupPasswordPage.propTypes = { - intl: intlShape.isRequired, - state: React.PropTypes.object, - hash: React.PropTypes.string, - updateParent: React.PropTypes.func -}; - -export default injectIntl(TeamSignupPasswordPage); diff --git a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx b/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx deleted file mode 100644 index db060b6b9..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_send_invites_page.jsx +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import EmailItem from './team_signup_email_item.jsx'; -import * as Client from 'utils/client.jsx'; - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; - -import logoImage from 'images/logo.png'; - -import React from 'react'; - -export default class TeamSignupSendInvitesPage extends React.Component { - constructor(props) { - super(props); - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - this.submitAddInvite = this.submitAddInvite.bind(this); - this.submitSkip = this.submitSkip.bind(this); - this.keySubmit = this.keySubmit.bind(this); - this.state = { - emailEnabled: global.window.mm_config.SendEmailNotifications === 'true' - }; - } - submitBack(e) { - e.preventDefault(); - this.props.state.wizard = 'team_url'; - - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - var valid = true; - - if (this.state.emailEnabled) { - var emails = []; - - for (var i = 0; i < this.props.state.invites.length; i++) { - if (this.refs['email_' + i].getWrappedInstance().validate(this.props.state.team.email)) { - emails.push(this.refs['email_' + i].getWrappedInstance().getValue()); - } else { - valid = false; - } - } - - if (valid) { - this.props.state.invites = emails; - } - } - - if (valid) { - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - } - submitAddInvite(e) { - e.preventDefault(); - this.props.state.wizard = 'send_invites'; - if (!this.props.state.invites) { - this.props.state.invites = []; - } - this.props.state.invites.push(''); - this.props.updateParent(this.props.state); - } - submitSkip(e) { - e.preventDefault(); - this.props.state.wizard = 'username'; - this.props.updateParent(this.props.state); - } - keySubmit(e) { - if (e && e.keyCode === 13) { - this.submitNext(e); - } - } - componentDidMount() { - if (!this.state.emailEnabled) { - // Must use keypress not keyup due to event chain of pressing enter - $('body').keypress(this.keySubmit); - } - } - componentWillUnmount() { - if (!this.state.emailEnabled) { - $('body').off('keypress', this.keySubmit); - } - } - render() { - Client.track('signup', 'signup_team_05_send_invites'); - - var content = null; - var bottomContent = null; - - if (this.state.emailEnabled) { - var emails = []; - - for (var i = 0; i < this.props.state.invites.length; i++) { - if (i === 0) { - emails.push( - <EmailItem - focus={true} - key={i} - ref={'email_' + i} - email={this.props.state.invites[i]} - /> - ); - } else { - emails.push( - <EmailItem - focus={false} - key={i} - ref={'email_' + i} - email={this.props.state.invites[i]} - /> - ); - } - } - - content = ( - <div> - {emails} - <div className='form-group text-right'> - <a - href='#' - onClick={this.submitAddInvite} - > - <FormattedMessage - id='team_signup_send_invites.addInvitation' - defaultMessage='Add Invitation' - /> - </a> - </div> - </div> - ); - - bottomContent = ( - <p className='color--light'> - <FormattedHTMLMessage - id='team_signup_send_invites.prefer' - defaultMessage='if you prefer, you can invite team members later<br /> and ' - /> - <a - href='#' - onClick={this.submitSkip} - > - <FormattedMessage - id='team_signup_send_invites.skip' - defaultMessage='skip this step ' - /> - </a> - <FormattedMessage - id='team_signup_send_invites.forNow' - defaultMessage='for now.' - /> - </p> - ); - } else { - content = ( - <div className='form-group color--light'> - <FormattedMessage - id='team_signup_send_invites.disabled' - defaultMessage='Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.' - /> - </div> - ); - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2> - <FormattedMessage - id='team_signup_send_invites.title' - defaultMessage='Invite Team Members' - /> - </h2> - {content} - <div className='form-group'> - <button - type='submit' - className='btn-primary btn' - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_send_invites.next' - defaultMessage='Next' - /><i className='glyphicon glyphicon-chevron-right'/> - </button> - </div> - </form> - {bottomContent} - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_send_invites.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </div> - ); - } -} - -TeamSignupSendInvitesPage.propTypes = { - state: React.PropTypes.object.isRequired, - updateParent: React.PropTypes.func.isRequired -}; diff --git a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx b/webapp/components/signup_team_complete/components/team_signup_username_page.jsx deleted file mode 100644 index b79c1179f..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_username_page.jsx +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -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 'react-intl'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - reserved: { - id: 'team_signup_username.reserved', - defaultMessage: 'This username is reserved, please choose a new one.' - }, - invalid: { - id: 'team_signup_username.invalid', - defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\'' - } -}); - -import React from 'react'; - -class TeamSignupUsernamePage extends React.Component { - constructor(props) { - super(props); - - this.submitBack = this.submitBack.bind(this); - this.submitNext = this.submitNext.bind(this); - - this.state = {}; - } - submitBack(e) { - e.preventDefault(); - if (global.window.mm_config.SendEmailNotifications === 'true') { - this.props.state.wizard = 'send_invites'; - } else { - this.props.state.wizard = 'team_url'; - } - - this.props.updateParent(this.props.state); - } - submitNext(e) { - e.preventDefault(); - - const {formatMessage} = this.props.intl; - var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase(); - - var usernameError = Utils.isValidUsername(name); - if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID - this.setState({nameError: formatMessage(holders.reserved)}); - return; - } else if (usernameError) { - this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})}); - return; - } - - this.props.state.wizard = 'password'; - this.props.state.user.username = name; - this.props.updateParent(this.props.state); - } - render() { - Client.track('signup', 'signup_team_06_username'); - - var nameError = null; - var nameHelpText = ( - <span className='color--light help-block'> - <FormattedMessage - id='team_signup_username.hint' - defaultMessage="Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'" - values={{ - min: Constants.MIN_USERNAME_LENGTH, - max: Constants.MAX_USERNAME_LENGTH - }} - /> - </span> - ); - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameHelpText = ''; - nameDivClass += ' has-error'; - } - - return ( - <div> - <form> - <img - className='signup-team-logo' - src={logoImage} - /> - <h2 className='margin--less'> - <FormattedMessage - id='team_signup_username.username' - defaultMessage='Your username' - /> - </h2> - <h5 className='color--light'> - <FormattedMessage - id='team_signup_username.memorable' - defaultMessage='Select a memorable username that makes it easy for teammates to identify you:' - /> - </h5> - <div className='inner__content margin--extra'> - <div className={nameDivClass}> - <div className='row'> - <div className='col-sm-11'> - <h5><strong> - <FormattedMessage - id='team_signup_username.chooseUsername' - defaultMessage='Choose your username' - /> - </strong></h5> - <input - autoFocus={true} - type='text' - ref='name' - className='form-control' - placeholder='' - defaultValue={this.props.state.user.username} - maxLength={Constants.MAX_USERNAME_LENGTH} - spellCheck='false' - /> - {nameHelpText} - </div> - </div> - {nameError} - </div> - </div> - <button - type='submit' - className='btn btn-primary margin--extra' - onClick={this.submitNext} - > - <FormattedMessage - id='team_signup_username.next' - defaultMessage='Next' - /> - <i className='glyphicon glyphicon-chevron-right'></i> - </button> - <div className='margin--extra'> - <a - href='#' - onClick={this.submitBack} - > - <FormattedMessage - id='team_signup_username.back' - defaultMessage='Back to previous step' - /> - </a> - </div> - </form> - </div> - ); - } -} - -TeamSignupUsernamePage.defaultProps = { - state: null -}; -TeamSignupUsernamePage.propTypes = { - intl: intlShape.isRequired, - state: React.PropTypes.object, - updateParent: React.PropTypes.func -}; - -export default injectIntl(TeamSignupUsernamePage); diff --git a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx b/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx deleted file mode 100644 index 15b708128..000000000 --- a/webapp/components/signup_team_complete/components/team_signup_welcome_page.jsx +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -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 'react-intl'; - -import {browserHistory} from 'react-router'; - -import logoImage from 'images/logo.png'; - -const holders = defineMessages({ - storageError: { - id: 'team_signup_welcome.storageError', - defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.' - }, - validEmailError: { - id: 'team_signup_welcome.validEmailError', - defaultMessage: 'Please enter a valid email address' - }, - address: { - id: 'team_signup_welcome.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class TeamSignupWelcomePage extends React.Component { - constructor(props) { - super(props); - - this.submitNext = this.submitNext.bind(this); - this.handleDiffEmail = this.handleDiffEmail.bind(this); - this.handleDiffSubmit = this.handleDiffSubmit.bind(this); - this.handleKeyPress = this.handleKeyPress.bind(this); - - this.state = {useDiff: false}; - - document.addEventListener('keyup', this.handleKeyPress, false); - } - submitNext(e) { - if (!BrowserStore.isLocalStorageSupported()) { - this.setState({storageError: this.props.intl.formatMessage(holders.storageError)}); - return; - } - e.preventDefault(); - this.props.state.wizard = 'team_display_name'; - this.props.updateParent(this.props.state); - } - handleDiffEmail(e) { - e.preventDefault(); - this.setState({useDiff: true}); - } - handleDiffSubmit(e) { - e.preventDefault(); - - const {formatMessage} = this.props.intl; - var state = {useDiff: true, serverError: ''}; - - var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - if (!email || !Utils.isEmail(email)) { - state.emailError = formatMessage(holders.validEmailError); - this.setState(state); - return; - } else if (!BrowserStore.isLocalStorageSupported()) { - state.emailError = formatMessage(holders.storageError); - this.setState(state); - return; - } - state.emailError = ''; - - Client.signupTeam(email, - function success(data) { - if (data.follow_link) { - browserHistory.push(data.follow_link); - } else { - this.props.state.wizard = 'finished'; - this.props.updateParent(this.props.state); - browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email)); - } - }.bind(this), - function error(err) { - let errorMsg = err.message; - - if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) { - errorMsg = formatMessage(holders.validEmailError); - } - - this.setState({emailError: '', serverError: errorMsg}); - }.bind(this) - ); - } - handleKeyPress(event) { - if (event.keyCode === 13) { - if (this.state.useDiff) { - this.handleDiffSubmit(event); - } else { - this.submitNext(event); - } - } - } - componentWillUnmount() { - document.removeEventListener('keyup', this.handleKeyPress, false); - } - render() { - Client.track('signup', 'signup_team_01_welcome'); - - var storageError = null; - if (this.state.storageError) { - storageError = <label className='control-label'>{this.state.storageError}</label>; - } - - var emailError = null; - var emailDivClass = 'form-group'; - if (this.state.emailError) { - emailError = <label className='control-label'>{this.state.emailError}</label>; - emailDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = ( - <div className='form-group has-error'> - <label className='control-label'>{this.state.serverError}</label> - </div> - ); - } - - var differentEmailLinkClass = ''; - var emailDivContainerClass = 'hidden'; - if (this.state.useDiff) { - differentEmailLinkClass = 'hidden'; - emailDivContainerClass = ''; - } - - return ( - <div> - <img - className='signup-team-logo' - src={logoImage} - /> - <h3 className='sub-heading'> - <FormattedMessage - id='team_signup_welcome.welcome' - defaultMessage='Welcome to:' - /> - </h3> - <h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1> - <p className='margin--less'> - <FormattedMessage - id='team_signup_welcome.lets' - defaultMessage="Let's set up your new team" - /> - </p> - <div> - <FormattedMessage - id='team_signup_welcome.confirm' - defaultMessage='Please confirm your email address:' - /> - <br/> - <div className='inner__content'> - <div className='block--gray'>{this.props.state.team.email}</div> - </div> - </div> - <p className='margin--extra color--light'> - <FormattedHTMLMessage - id='team_signup_welcome.admin' - defaultMessage='Your account will administer the new team site. <br /> - You can add other administrators later.' - /> - </p> - <div className='form-group'> - <button - className='btn-primary btn form-group' - type='submit' - onClick={this.submitNext} - > - <i className='glyphicon glyphicon-ok'></i> - <FormattedMessage - id='team_signup_welcome.yes' - defaultMessage='Yes, this address is correct' - /> - </button> - {storageError} - </div> - <hr/> - <div className={emailDivContainerClass}> - <div className={emailDivClass}> - <div className='row'> - <div className='col-sm-9'> - <input - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - maxLength='128' - spellCheck='false' - /> - </div> - </div> - {emailError} - </div> - {serverError} - <button - className='btn btn-md btn-primary' - type='button' - onClick={this.handleDiffSubmit} - > - <FormattedMessage - id='team_signup_welcome.instead' - defaultMessage='Use this instead' - /> - </button> - </div> - <a - href='#' - onClick={this.handleDiffEmail} - className={differentEmailLinkClass} - > - <FormattedMessage - id='team_signup_welcome.different' - defaultMessage='Use a different email' - /> - </a> - </div> - ); - } -} - -TeamSignupWelcomePage.defaultProps = { - state: {} -}; -TeamSignupWelcomePage.propTypes = { - intl: intlShape.isRequired, - updateParent: React.PropTypes.func.isRequired, - state: React.PropTypes.object -}; - -export default injectIntl(TeamSignupWelcomePage); diff --git a/webapp/components/signup_team_confirm.jsx b/webapp/components/signup_team_confirm.jsx deleted file mode 100644 index 117a0b068..000000000 --- a/webapp/components/signup_team_confirm.jsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; -import {Link} from 'react-router'; - -import React from 'react'; - -export default class SignupTeamConfirm extends React.Component { - render() { - return ( - <div> - <div className='signup-header'> - <Link to='/'> - <span className='fa fa-chevron-left'/> - <FormattedMessage - id='web.header.back' - /> - </Link> - </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 = { -}; -SignupTeamConfirm.propTypes = { - location: React.PropTypes.object -}; diff --git a/webapp/components/signup_user_complete.jsx b/webapp/components/signup_user_complete.jsx index 9e821289b..666e72e13 100644 --- a/webapp/components/signup_user_complete.jsx +++ b/webapp/components/signup_user_complete.jsx @@ -3,12 +3,13 @@ import LoadingScreen from 'components/loading_screen.jsx'; import LoginLdap from 'components/login/components/login_ldap.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; @@ -24,7 +25,6 @@ class SignupUserComplete extends React.Component { super(props); this.handleSubmit = this.handleSubmit.bind(this); - this.inviteInfoRecieved = this.inviteInfoRecieved.bind(this); this.handleLdapSignup = this.handleLdapSignup.bind(this); this.state = { @@ -34,7 +34,10 @@ class SignupUserComplete extends React.Component { email: '', teamDisplayName: '', teamName: '', - teamId: '' + teamId: '', + openServer: false, + loading: true, + inviteId: '' }; } componentWillMount() { @@ -46,19 +49,91 @@ class SignupUserComplete extends React.Component { let teamDisplayName = ''; let teamName = ''; let teamId = ''; + let openServer = false; + let loading = true; + + if ((inviteId && inviteId.length > 0) || (hash && hash.length > 0)) { + // if we are already logged in then attempt to just join the team + if (UserStore.getCurrentUser()) { + loading = true; + Client.addUserToTeamFromInvite( + data, + hash, + inviteId, + () => { + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); + }, + (err) => { + this.setState({ + noOpenServerError: true, + serverError: err.message, + loading: false + }); + } + ); + } else if (hash) { + // If we have a hash in the url then we are attempting to access a private team + const parsedData = JSON.parse(data); + usedBefore = BrowserStore.getGlobalItem(hash); + email = parsedData.email; + teamDisplayName = parsedData.display_name; + teamName = parsedData.name; + teamId = parsedData.id; + loading = false; + } else { + loading = true; + Client.getInviteInfo( + inviteId, + (inviteData) => { + if (!inviteData) { + return; + } - // 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; + this.setState({ + serverError: null, + teamDisplayName: inviteData.display_name, + teamName: inviteData.name, + teamId: inviteData.id, + loading: false + }); + }, + () => { + this.setState({ + noOpenServerError: true, + loading: false, + serverError: + <FormattedMessage + id='signup_user_completed.invalid_invite' + defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.' + /> + }); + } + ); + + data = ''; + hash = ''; + } + } else if (global.window.mm_config.EnableOpenServer === 'true' || UserStore.getNoAccounts()) { + // If this is the first account then let them create an account anyway. + // The server will verify it's the first account before allowing creation. + // Of if the server is open then we don't care. + openServer = true; + loading = false; } else { - Client.getInviteInfo(this.inviteInfoRecieved, null, inviteId); - data = ''; - hash = ''; + loading = false; + this.setState({ + noOpenServerError: true, + serverError: + <FormattedMessage + id='signup_user_completed.no_open_server' + defaultMessage='This server does not allow open signups. Please speak with your Administrator to receive an invitation.' + />, + loading: false + }); } this.setState({ @@ -68,29 +143,25 @@ class SignupUserComplete extends React.Component { email, teamDisplayName, teamName, - teamId - }); - } - inviteInfoRecieved(data) { - if (!data) { - return; - } - - this.setState({ - teamDisplayName: data.display_name, - teamName: data.name, - teamId: data.id + teamId, + openServer, + inviteId, + loading }); } handleLdapSignup(method, loginId, password, token) { - Client.loginByLdap(this.state.teamName, loginId, password, token, + Client.loginByLdap(loginId, password, token, () => { const redirect = Utils.getUrlParameter('redirect'); if (redirect) { browserHistory.push(decodeURIComponent(redirect)); } else { - browserHistory.push('/' + this.state.teamName + '/channels/town-square'); + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); } }, (err) => { @@ -187,28 +258,34 @@ class SignupUserComplete extends React.Component { }); 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.createUserWithInvite(user, + this.state.data, + this.state.hash, + this.state.inviteId, () => { Client.track('signup', 'signup_user_02_complete'); - - Client.loginByEmail( - this.state.teamName, + Client.login( user.email, + null, user.password, - '', // No MFA Token + '', () => { UserStore.setLastEmail(user.email); if (this.state.hash > 0) { BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true})); } - browserHistory.push('/' + this.state.teamName + '/channels/town-square'); + + GlobalActions.emitInitialLoad( + () => { + browserHistory.push('/select_team'); + } + ); }, (err) => { if (err.id === 'api.user.login.not_verified.app_error') { @@ -239,9 +316,7 @@ 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 === '') { + if (this.state.loading) { return (<LoadingScreen/>); } @@ -349,7 +424,7 @@ class SignupUserComplete extends React.Component { <a className='btn btn-custom-login gitlab' key='gitlab' - href={'/api/v1/oauth/gitlab/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} + href={Client.getOAuthRoute() + '/gitlab/signup' + window.location.search} > <span className='icon'/> <span> @@ -367,7 +442,7 @@ class SignupUserComplete extends React.Component { <a className='btn btn-custom-login google' key='google' - href={'/api/v1/oauth/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} + href={Client.getOAuthRoute() + '/google/signup' + window.location.search + '&team=' + encodeURIComponent(this.state.teamName)} > <span className='icon'/> <span> @@ -497,12 +572,33 @@ class SignupUserComplete extends React.Component { ); } + let terms = ( + <p> + <FormattedHTMLMessage + id='create_team.agreement' + defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}." + values={{ + siteName: global.window.mm_config.SiteName + }} + /> + </p> + ); + + if (this.state.noOpenServerError) { + signupMessage = null; + ldapSignup = null; + emailSignup = null; + terms = null; + } + return ( <div> <div className='signup-header'> <Link to='/'> - <span classNameNameName='fa fa-chevron-left'/> - <FormattedMessage id='web.header.back'/> + <span className='fa fa-chevron-left'/> + <FormattedMessage + id='web.header.back' + /> </Link> </div> <div className='col-sm-12'> @@ -511,22 +607,12 @@ class SignupUserComplete extends React.Component { className='signup-team-logo' src={logoImage} /> - <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'> + <h1>{global.window.mm_config.SiteName}</h1> + <h4 className='color--light'> <FormattedMessage - id='signup_user_completed.onSite' - defaultMessage='on {siteName}' - values={{ - siteName: global.window.mm_config.SiteName - }} + id='web.root.singup_info' /> - </h2> + </h4> <h4 className='color--light'> <FormattedMessage id='signup_user_completed.lets' @@ -537,6 +623,7 @@ class SignupUserComplete extends React.Component { {ldapSignup} {emailSignup} {serverError} + {terms} </div> </div> </div> diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx index 90ec6e660..79ac8aaf1 100644 --- a/webapp/components/suggestion/at_mention_provider.jsx +++ b/webapp/components/suggestion/at_mention_provider.jsx @@ -4,6 +4,7 @@ import SuggestionStore from 'stores/suggestion_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -42,7 +43,7 @@ class AtMentionSuggestion extends React.Component { icon = ( <img className='mention__image' - src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at} + src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at} /> ); } diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx index eeaee68a7..b7234469a 100644 --- a/webapp/components/suggestion/search_user_provider.jsx +++ b/webapp/components/suggestion/search_user_provider.jsx @@ -3,6 +3,7 @@ import SuggestionStore from 'stores/suggestion_store.jsx'; import UserStore from 'stores/user_store.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; @@ -22,7 +23,7 @@ class SearchUserSuggestion extends React.Component { > <img className='profile-img rounded' - src={'/api/v1/users/' + item.id + '/image?time=' + item.update_at} + src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at} /> <i className='fa fa fa-plus-square'></i>{item.username} </div> diff --git a/webapp/components/team_export_tab.jsx b/webapp/components/team_export_tab.jsx index 9bd5785a0..37f886aab 100644 --- a/webapp/components/team_export_tab.jsx +++ b/webapp/components/team_export_tab.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx index c27e8ca59..1f783fe9f 100644 --- a/webapp/components/team_general_tab.jsx +++ b/webapp/components/team_general_tab.jsx @@ -5,7 +5,7 @@ import $ from 'jquery'; import SettingItemMin from './setting_item_min.jsx'; import SettingItemMax from './setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import TeamStore from 'stores/team_store.jsx'; @@ -42,7 +42,7 @@ const holders = defineMessages({ }, openInviteTitle: { id: 'general_tab.openInviteTitle', - defaultMessage: 'Allow anyone to sign-up from login page' + defaultMessage: 'Allow anyone to join this team' }, codeTitle: { id: 'general_tab.codeTitle', @@ -68,7 +68,6 @@ class GeneralTab extends React.Component { this.handleNameSubmit = this.handleNameSubmit.bind(this); this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this); this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this); - this.handleTeamListingSubmit = this.handleTeamListingSubmit.bind(this); this.handleClose = this.handleClose.bind(this); this.onUpdateNameSection = this.onUpdateNameSection.bind(this); this.updateName = this.updateName.bind(this); @@ -76,8 +75,6 @@ class GeneralTab extends React.Component { this.updateInviteId = this.updateInviteId.bind(this); this.onUpdateOpenInviteSection = this.onUpdateOpenInviteSection.bind(this); this.handleOpenInviteRadio = this.handleOpenInviteRadio.bind(this); - this.onUpdateTeamListingSection = this.onUpdateTeamListingSection.bind(this); - this.handleTeamListingRadio = this.handleTeamListingRadio.bind(this); this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this); this.state = this.setupInitialState(props); @@ -96,12 +93,19 @@ class GeneralTab extends React.Component { name: team.display_name, invite_id: team.invite_id, allow_open_invite: team.allow_open_invite, - allow_team_listing: team.allow_team_listing, serverError: '', clientError: '' }; } + componentWillReceiveProps(nextProps) { + this.setState({ + name: nextProps.team.display_name, + invite_id: nextProps.team.invite_id, + allow_open_invite: nextProps.team.allow_open_invite + }); + } + handleGenerateInviteId(e) { e.preventDefault(); @@ -117,14 +121,6 @@ class GeneralTab extends React.Component { this.setState({allow_open_invite: openInvite}); } - handleTeamListingRadio(listing) { - if (global.window.mm_config.EnableTeamListing !== 'true' && listing) { - this.setState({clientError: this.props.intl.formatMessage(holders.dirDisabled)}); - } else { - this.setState({allow_team_listing: listing}); - } - } - handleOpenInviteSubmit(e) { e.preventDefault(); @@ -145,26 +141,6 @@ class GeneralTab extends React.Component { ); } - handleTeamListingSubmit(e) { - e.preventDefault(); - - var state = {serverError: '', clientError: ''}; - - var data = this.props.team; - data.allow_team_listing = this.state.allow_team_listing; - Client.updateTeam(data, - (team) => { - TeamStore.saveTeam(team); - TeamStore.emitChange(); - this.updateSection(''); - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - handleNameSubmit(e) { e.preventDefault(); @@ -239,12 +215,6 @@ class GeneralTab extends React.Component { ); } - componentWillReceiveProps(newProps) { - if (newProps.team && newProps.teamDisplayName) { - this.setState({name: newProps.teamDisplayName}); - } - } - handleClose() { this.updateSection(''); } @@ -284,15 +254,6 @@ class GeneralTab extends React.Component { } } - onUpdateTeamListingSection(e) { - e.preventDefault(); - if (this.props.activeSection === 'team_listing') { - this.updateSection(''); - } else { - this.updateSection('team_listing'); - } - } - updateName(e) { e.preventDefault(); this.setState({name: e.target.value}); @@ -313,105 +274,8 @@ class GeneralTab extends React.Component { serverError = this.state.serverError; } - const enableTeamListing = global.window.mm_config.EnableTeamListing === 'true'; const {formatMessage} = this.props.intl; - let teamListingSection; - if (this.props.activeSection === 'team_listing') { - const inputs = []; - let submitHandle = null; - - if (enableTeamListing) { - submitHandle = this.handleTeamListingSubmit; - - inputs.push( - <div key='userTeamListingOptions'> - <div className='radio'> - <label> - <input - name='userTeamListingOptions' - type='radio' - defaultChecked={this.state.allow_team_listing} - onChange={this.handleTeamListingRadio.bind(this, true)} - /> - <FormattedMessage - id='general_tab.yes' - defaultMessage='Yes' - /> - </label> - <br/> - </div> - <div className='radio'> - <label> - <input - ref='teamListingRadioNo' - name='userTeamListingOptions' - type='radio' - defaultChecked={!this.state.allow_team_listing} - onChange={this.handleTeamListingRadio.bind(this, false)} - /> - <FormattedMessage - id='general_tab.no' - defaultMessage='No' - /> - </label> - <br/> - </div> - <div> - <br/> - <FormattedMessage - id='general_tab.includeDirDesc' - defaultMessage='Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.' - /> - </div> - </div> - ); - } else { - inputs.push( - <div key='userTeamListingOptions'> - <div> - <br/> - <FormattedMessage - id='general_tab.dirContact' - defaultMessage='Contact your system administrator to turn on the team directory on the system home page.' - /> - </div> - </div> - ); - } - - teamListingSection = ( - <SettingItemMax - title={formatMessage(holders.includeDirTitle)} - inputs={inputs} - submit={submitHandle} - server_error={serverError} - client_error={clientError} - updateSection={this.onUpdateTeamListingSection} - /> - ); - } else { - let describe = ''; - - if (enableTeamListing) { - if (this.state.allow_team_listing === true) { - describe = formatMessage(holders.yes); - } else { - describe = formatMessage(holders.no); - } - } else { - describe = formatMessage(holders.dirOff); - } - - teamListingSection = ( - <SettingItemMin - title={formatMessage(holders.includeDirTitle)} - describe={describe} - updateSection={this.onUpdateTeamListingSection} - /> - ); - } - let openInviteSection; if (this.props.activeSection === 'open_invite') { const inputs = [ @@ -450,7 +314,7 @@ class GeneralTab extends React.Component { <br/> <FormattedMessage id='general_tab.openInviteDesc' - defaultMessage='When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.' + defaultMessage='When allowed, a link to this team will be including on the landing page allowing anyone with an account to join this team.' /> </div> </div> @@ -639,8 +503,6 @@ class GeneralTab extends React.Component { <div className='divider-light'/> {openInviteSection} <div className='divider-light'/> - {teamListingSection} - <div className='divider-light'/> {inviteSection} <div className='divider-dark'/> </div> diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown.jsx index ad82a2280..251c2ce3b 100644 --- a/webapp/components/team_members_dropdown.jsx +++ b/webapp/components/team_members_dropdown.jsx @@ -3,7 +3,7 @@ import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import ConfirmModal from './confirm_modal.jsx'; @@ -38,11 +38,9 @@ export default class TeamMembersDropdown extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, ''); } else { - const data = { - user_id: this.props.user.id, - new_roles: '' - }; - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + '', () => { AsyncClient.getProfiles(); }, @@ -79,12 +77,9 @@ export default class TeamMembersDropdown extends React.Component { if (this.props.user.id === me.id) { this.handleDemote(this.props.user, 'admin'); } else { - const data = { - user_id: this.props.user.id, - new_roles: 'admin' - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + 'admin', () => { AsyncClient.getProfiles(); }, @@ -94,12 +89,13 @@ export default class TeamMembersDropdown extends React.Component { ); } } - handleDemote(user, role) { + handleDemote(user, role, newRole) { this.setState({ serverError: this.state.serverError, showDemoteModal: true, user, - role + role, + newRole }); } handleDemoteCancel() { @@ -107,16 +103,14 @@ export default class TeamMembersDropdown extends React.Component { serverError: null, showDemoteModal: false, user: null, - role: null + role: null, + newRole: null }); } handleDemoteSubmit() { - const data = { - user_id: this.props.user.id, - new_roles: this.state.role - }; - - Client.updateRoles(data, + Client.updateRoles( + this.props.user.id, + this.state.newRole, () => { const teamUrl = TeamStore.getCurrentTeamUrl(); if (teamUrl) { @@ -140,6 +134,7 @@ export default class TeamMembersDropdown extends React.Component { ); } + const teamMember = this.props.teamMember; const user = this.props.user; let currentRoles = ( <FormattedMessage @@ -168,8 +163,10 @@ export default class TeamMembersDropdown extends React.Component { } } - let showMakeMember = user.roles === 'admin' || user.roles === 'system_admin'; - let showMakeAdmin = user.roles === '' || user.roles === 'system_admin'; + let showMakeMember = teamMember.roles === 'admin' || user.roles === 'system_admin'; + + //let showMakeAdmin = teamMember.roles === '' && user.roles !== 'system_admin'; + let showMakeAdmin = false; let showMakeActive = false; let showMakeNotActive = user.roles !== 'system_admin'; @@ -331,5 +328,6 @@ export default class TeamMembersDropdown extends React.Component { } TeamMembersDropdown.propTypes = { - user: React.PropTypes.object.isRequired + user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired }; diff --git a/webapp/components/team_settings.jsx b/webapp/components/team_settings.jsx index dc303059d..210d1f541 100644 --- a/webapp/components/team_settings.jsx +++ b/webapp/components/team_settings.jsx @@ -25,6 +25,7 @@ export default class TeamSettings extends React.Component { } onChange() { var team = TeamStore.getCurrent(); + if (!Utils.areObjectsEqual(this.state.team, team)) { this.setState({team}); } diff --git a/webapp/components/team_signup_choose_auth.jsx b/webapp/components/team_signup_choose_auth.jsx deleted file mode 100644 index 7a4ce972a..000000000 --- a/webapp/components/team_signup_choose_auth.jsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {FormattedMessage} from 'react-intl'; - -import React from 'react'; - -export default class ChooseAuthPage extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - render() { - var buttons = []; - if (global.window.mm_config.EnableSignUpWithGitLab === 'true') { - buttons.push( - <a - className='btn btn-custom-login gitlab btn-full' - key='gitlab' - href='#' - onClick={ - function clickGit(e) { - e.preventDefault(); - this.props.updatePage('gitlab'); - }.bind(this) - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.gitlabCreate' - defaultMessage='Create new team with GitLab Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { - buttons.push( - <a - className='btn btn-custom-login google btn-full' - key='google' - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.props.updatePage('google'); - } - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.googleCreate' - defaultMessage='Create new team with Google Apps Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableLdap === 'true') { - buttons.push( - <a - className='btn btn-custom-login ldap btn-full' - key='ldap' - href='#' - onClick={ - (e) => { - e.preventDefault(); - this.props.updatePage('ldap'); - } - } - > - <span className='icon'/> - <span> - <FormattedMessage - id='choose_auth_page.ldapCreate' - defaultMessage='Create new team with LDAP Account' - /> - </span> - </a> - ); - } - - if (global.window.mm_config.EnableSignUpWithEmail === 'true') { - buttons.push( - <a - className='btn btn-custom-login email btn-full' - key='email' - href='#' - onClick={ - function clickEmail(e) { - e.preventDefault(); - this.props.updatePage('email'); - }.bind(this) - } - > - <span className='fa fa-envelope'/> - <span> - <FormattedMessage - id='choose_auth_page.emailCreate' - defaultMessage='Create new team with email address' - /> - </span> - </a> - ); - } - - if (buttons.length === 0) { - buttons = ( - <span> - <FormattedMessage - id='choose_auth_page.noSignup' - defaultMessage='No sign-up methods configured, please contact your system administrator.' - /> - </span> - ); - } - - return ( - <div> - {buttons} - </div> - ); - } -} - -ChooseAuthPage.propTypes = { - updatePage: React.PropTypes.func -}; diff --git a/webapp/components/team_signup_with_email.jsx b/webapp/components/team_signup_with_email.jsx deleted file mode 100644 index 90a6e9773..000000000 --- a/webapp/components/team_signup_with_email.jsx +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; - -const holders = defineMessages({ - emailError: { - id: 'email_signup.emailError', - defaultMessage: 'Please enter a valid email address' - }, - address: { - id: 'email_signup.address', - defaultMessage: 'Email Address' - } -}); - -import React from 'react'; - -class EmailSignUpPage extends React.Component { - constructor() { - super(); - - this.handleSubmit = this.handleSubmit.bind(this); - - this.state = {}; - } - handleSubmit(e) { - e.preventDefault(); - const team = {}; - const state = {serverError: null}; - let isValid = true; - - team.email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase(); - if (!team.email || !Utils.isEmail(team.email)) { - state.emailError = this.props.intl.formatMessage(holders.emailError); - isValid = false; - } else { - state.emailError = null; - } - - if (!isValid) { - this.setState(state); - return; - } - - Client.signupTeam(team.email, - (data) => { - if (data.follow_link) { - browserHistory.push(data.follow_link); - } else { - browserHistory.push(`/signup_team_confirm/?email=${encodeURIComponent(team.email)}`); - } - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - render() { - let serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - let emailError = null; - if (this.state.emailError) { - emailError = <div className='form-group has-error'><label className='control-label'>{this.state.emailError}</label></div>; - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className='form-group'> - <input - autoFocus={true} - type='email' - ref='email' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.address)} - maxLength='128' - spellCheck='false' - /> - {emailError} - </div> - <div className='form-group'> - <button - className='btn btn-md btn-primary' - type='submit' - > - <FormattedMessage - id='email_signup.createTeam' - defaultMessage='Create Team' - /> - </button> - {serverError} - </div> - </form> - ); - } -} - -EmailSignUpPage.defaultProps = { -}; -EmailSignUpPage.propTypes = { - intl: intlShape.isRequired -}; - -export default injectIntl(EmailSignUpPage); diff --git a/webapp/components/team_signup_with_ldap.jsx b/webapp/components/team_signup_with_ldap.jsx deleted file mode 100644 index 9d812e8ee..000000000 --- a/webapp/components/team_signup_with_ldap.jsx +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import $ from 'jquery'; -import ReactDOM from 'react-dom'; -import * as utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; - -import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router'; - -const holders = defineMessages({ - team_error: { - id: 'ldap_signup.team_error', - defaultMessage: 'Please enter a team name' - }, - length_error: { - id: 'ldap_signup.length_error', - defaultMessage: 'Name must be 3 or more characters up to a maximum of 15' - }, - teamName: { - id: 'ldap_signup.teamName', - defaultMessage: 'Enter name of new team' - }, - badTeam: { - id: 'login_ldap.badTeam', - defaultMessage: 'Bad team name' - }, - idReq: { - id: 'login_ldap.idlReq', - defaultMessage: 'An LDAP ID is required' - }, - pwdReq: { - id: 'login_ldap.pwdReq', - defaultMessage: 'An LDAP password is required' - }, - username: { - id: 'login_ldap.username', - defaultMessage: 'LDAP Username' - }, - pwd: { - id: 'login_ldap.pwd', - defaultMessage: 'LDAP Password' - } -}); - -import React from 'react'; - -class LdapSignUpPage extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - this.valueChange = this.valueChange.bind(this); - - this.state = {name: '', id: '', password: ''}; - } - - handleSubmit(e) { - e.preventDefault(); - const {formatMessage} = this.props.intl; - var teamSignup = {}; - teamSignup.user = {}; - teamSignup.team = {}; - var state = this.state; - state.serverError = null; - - teamSignup.team.display_name = this.state.name; - - if (!teamSignup.team.display_name) { - state.serverError = formatMessage(holders.team_error); - this.setState(state); - return; - } - - if (teamSignup.team.display_name.length <= 2) { - state.serverError = formatMessage(holders.length_error); - this.setState(state); - return; - } - - const id = this.refs.id.value.trim(); - if (!id) { - state.serverError = formatMessage(holders.idReq); - this.setState(state); - return; - } - - const password = this.refs.password.value.trim(); - if (!password) { - state.serverError = formatMessage(holders.pwdReq); - this.setState(state); - return; - } - - state.serverError = ''; - this.setState(state); - - teamSignup.team.name = utils.cleanUpUrlable(teamSignup.team.display_name); - teamSignup.team.type = 'O'; - - teamSignup.user.username = ReactDOM.findDOMNode(this.refs.id).value.trim(); - teamSignup.user.password = ReactDOM.findDOMNode(this.refs.password).value.trim(); - teamSignup.user.allow_marketing = true; - teamSignup.user.ldap = true; - teamSignup.user.auth_service = 'ldap'; - - $('#ldap-button').button('loading'); - - Client.createTeamWithLdap(teamSignup, - () => { - Client.track('signup', 'signup_team_ldap_complete'); - - Client.loginByLdap(teamSignup.team.name, id, password, - () => { - browserHistory.push('/' + teamSignup.team.name + '/channels/town-square'); - }, - (err) => { - $('#ldap-button').button('reset'); - this.setState({serverError: err.message}); - } - ); - }, - (err) => { - $('#ldap-button').button('reset'); - this.setState({serverError: err.message}); - } - ); - } - - valueChange() { - this.setState({ - name: ReactDOM.findDOMNode(this.refs.teamname).value.trim(), - id: ReactDOM.findDOMNode(this.refs.id).value.trim(), - password: ReactDOM.findDOMNode(this.refs.password).value.trim() - }); - } - - render() { - const {formatMessage} = this.props.intl; - var nameError = null; - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var disabled = false; - if (this.state.name.length <= 2) { - disabled = true; - } - - if (this.state.id.length <= 1) { - disabled = true; - } - - if (this.state.password.length <= 1) { - disabled = true; - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className={nameDivClass}> - <input - autoFocus={true} - type='text' - ref='teamname' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.teamName)} - maxLength='128' - onChange={this.valueChange} - spellCheck='false' - /> - {nameError} - </div> - <div className={nameDivClass}> - <input - className='form-control' - ref='id' - placeholder={formatMessage(holders.username)} - spellCheck='false' - onChange={this.valueChange} - /> - </div> - <div className={nameDivClass}> - <input - type='password' - className='form-control' - ref='password' - placeholder={formatMessage(holders.pwd)} - spellCheck='false' - onChange={this.valueChange} - /> - </div> - <div className='form-group'> - <a - className='btn btn-custom-login ldap btn-full' - key='ldap' - id='ldap-button' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='ldap_signup.ldap' - defaultMessage='Create team with LDAP Account' - /> - </span> - </a> - {serverError} - </div> - </form> - ); - } -} - -LdapSignUpPage.propTypes = { - intl: intlShape.isRequired -}; - -export default injectIntl(LdapSignUpPage); diff --git a/webapp/components/team_signup_with_sso.jsx b/webapp/components/team_signup_with_sso.jsx deleted file mode 100644 index 78396eea8..000000000 --- a/webapp/components/team_signup_with_sso.jsx +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ReactDOM from 'react-dom'; -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} from 'react-intl'; - -const holders = defineMessages({ - team_error: { - id: 'sso_signup.team_error', - defaultMessage: 'Please enter a team name' - }, - length_error: { - id: 'sso_signup.length_error', - defaultMessage: 'Name must be 3 or more characters up to a maximum of 15' - }, - teamName: { - id: 'sso_signup.teamName', - defaultMessage: 'Enter name of new team' - } -}); - -import React from 'react'; -import {browserHistory} from 'react-router'; - -class SSOSignUpPage extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - this.nameChange = this.nameChange.bind(this); - - this.state = {name: ''}; - } - handleSubmit(e) { - e.preventDefault(); - const {formatMessage} = this.props.intl; - var team = {}; - var state = this.state; - state.nameError = null; - state.serverError = null; - - team.display_name = this.state.name; - - if (!team.display_name) { - state.nameError = formatMessage(holders.team_error); - this.setState(state); - return; - } - - if (team.display_name.length <= 2) { - state.nameError = formatMessage(holders.length_error); - this.setState(state); - return; - } - - team.name = utils.cleanUpUrlable(team.display_name); - team.type = 'O'; - - client.createTeamWithSSO(team, - this.props.service, - (data) => { - if (data.follow_link) { - window.location.href = data.follow_link; - } else { - browserHistory.push('/' + team.name + '/channels/town-square'); - } - }, - (err) => { - state.serverError = err.message; - this.setState(state); - } - ); - } - nameChange() { - this.setState({name: ReactDOM.findDOMNode(this.refs.teamname).value.trim()}); - } - render() { - var nameError = null; - var nameDivClass = 'form-group'; - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameDivClass += ' has-error'; - } - - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var disabled = false; - if (this.state.name.length <= 2) { - disabled = true; - } - - var button = null; - - if (this.props.service === Constants.GITLAB_SERVICE) { - button = ( - <a - className='btn btn-custom-login gitlab btn-full' - key='gitlab' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='sso_signup.gitlab' - defaultMessage='Create team with GitLab Account' - /> - </span> - </a> - ); - } else if (this.props.service === Constants.GOOGLE_SERVICE) { - button = ( - <a - className='btn btn-custom-login google btn-full' - key='google' - href='#' - onClick={this.handleSubmit} - disabled={disabled} - > - <span className='icon'/> - <span> - <FormattedMessage - id='sso_signup.google' - defaultMessage='Create team with Google Apps Account' - /> - </span> - </a> - ); - } - - return ( - <form - role='form' - onSubmit={this.handleSubmit} - > - <div className={nameDivClass}> - <input - autoFocus={true} - type='text' - ref='teamname' - className='form-control' - placeholder={this.props.intl.formatMessage(holders.teamName)} - maxLength='128' - onChange={this.nameChange} - spellCheck='false' - /> - {nameError} - </div> - <div className='form-group'> - {button} - {serverError} - </div> - </form> - ); - } -} - -SSOSignUpPage.defaultProps = { - service: '' -}; -SSOSignUpPage.propTypes = { - intl: intlShape.isRequired, - service: React.PropTypes.string -}; - -export default injectIntl(SSOSignUpPage); diff --git a/webapp/components/toggle_modal_button.jsx b/webapp/components/toggle_modal_button.jsx index de225493c..69bdbbda0 100644 --- a/webapp/components/toggle_modal_button.jsx +++ b/webapp/components/toggle_modal_button.jsx @@ -15,7 +15,8 @@ export default class ModalToggleButton extends React.Component { }; } - show() { + show(e) { + e.preventDefault(); this.setState({show: true}); } @@ -29,10 +30,10 @@ export default class ModalToggleButton extends React.Component { // allow callers to provide an onClick which will be called before the modal is shown let clickHandler = this.show; if (onClick) { - clickHandler = () => { + clickHandler = (e) => { onClick(); - this.show(); + this.show(e); }; } diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx index 3652723be..626cb3cf5 100644 --- a/webapp/components/user_list.jsx +++ b/webapp/components/user_list.jsx @@ -13,10 +13,18 @@ export default class UserList extends React.Component { let content; if (users.length > 0) { content = users.map((user) => { + var teamMember; + for (var index in this.props.teamMembers) { + if (this.props.teamMembers[index].user_id === user.id) { + teamMember = this.props.teamMembers[index]; + } + } + return ( <UserListRow key={user.id} user={user} + teamMember={teamMember} actions={this.props.actions} actionProps={this.props.actionProps} /> @@ -48,12 +56,14 @@ export default class UserList extends React.Component { UserList.defaultProps = { users: [], + teamMembers: [], actions: [], actionProps: {} }; UserList.propTypes = { users: React.PropTypes.arrayOf(React.PropTypes.object), + teamMembers: React.PropTypes.arrayOf(React.PropTypes.object), actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object }; diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx index f6fd91688..a7838ec32 100644 --- a/webapp/components/user_list_row.jsx +++ b/webapp/components/user_list_row.jsx @@ -4,9 +4,10 @@ import Constants from 'utils/constants.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; -export default function UserListRow({user, actions, actionProps}) { +export default function UserListRow({user, teamMember, actions, actionProps}) { const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', ''); let name = user.username; @@ -21,6 +22,7 @@ export default function UserListRow({user, actions, actionProps}) { <Action key={index.toString()} user={user} + teamMember={teamMember} {...actionProps} /> ); @@ -35,7 +37,7 @@ export default function UserListRow({user, actions, actionProps}) { className='more-modal__image' width='38' height='38' - src={`/api/v1/users/${user.id}/image?time=${user.update_at}`} + src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`} /> <div className='more-modal__details' @@ -57,12 +59,17 @@ export default function UserListRow({user, actions, actionProps}) { } UserListRow.defaultProps = { + teamMember: { + team_id: '', + roles: '' + }, actions: [], actionProps: {} }; UserListRow.propTypes = { user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired, actions: React.PropTypes.arrayOf(React.PropTypes.func), actionProps: React.PropTypes.object }; diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx index d83ab7454..04b4f99a4 100644 --- a/webapp/components/user_profile.jsx +++ b/webapp/components/user_profile.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; +import Client from 'utils/web_client.jsx'; import {FormattedMessage} from 'react-intl'; @@ -28,7 +29,7 @@ export default class UserProfile extends React.Component { 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; + profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.update_at; } if (this.props.overwriteName) { diff --git a/webapp/components/user_settings/import_theme_modal.jsx b/webapp/components/user_settings/import_theme_modal.jsx index 2fc75ca13..32da296bf 100644 --- a/webapp/components/user_settings/import_theme_modal.jsx +++ b/webapp/components/user_settings/import_theme_modal.jsx @@ -5,7 +5,7 @@ import ReactDOM from 'react-dom'; import ModalStore from 'stores/modal_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import {Modal} from 'react-bootstrap'; import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; diff --git a/webapp/components/user_settings/manage_languages.jsx b/webapp/components/user_settings/manage_languages.jsx index 094eaa127..bbf3a2e40 100644 --- a/webapp/components/user_settings/manage_languages.jsx +++ b/webapp/components/user_settings/manage_languages.jsx @@ -3,7 +3,7 @@ import SettingItemMax from '../setting_item_max.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as I18n from 'i18n/i18n.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx index eddbc1efe..c6a05d1ee 100644 --- a/webapp/components/user_settings/user_settings_general.jsx +++ b/webapp/components/user_settings/user_settings_general.jsx @@ -9,7 +9,7 @@ import SettingPicture from '../setting_picture.jsx'; import UserStore from 'stores/user_store.jsx'; import ErrorStore from 'stores/error_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import Constants from 'utils/constants.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -225,11 +225,9 @@ class UserSettingsGeneralTab extends React.Component { return; } - var formData = new FormData(); - formData.append('image', picture, picture.name); this.setState({loadingPicture: true}); - Client.uploadProfileImage(formData, + Client.uploadProfileImage(picture, () => { this.submitActive = false; AsyncClient.getMe(); @@ -781,7 +779,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} + src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.last_picture_update} server_error={serverError} client_error={clientError} updateSection={(e) => { diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx index b119c42f9..fa84ce2d6 100644 --- a/webapp/components/user_settings/user_settings_notifications.jsx +++ b/webapp/components/user_settings/user_settings_notifications.jsx @@ -8,7 +8,7 @@ import SettingItemMax from '../setting_item_max.jsx'; import UserStore from 'stores/user_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index ff5a898a9..f28e34197 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -10,7 +10,7 @@ import ToggleModalButton from '../toggle_modal_button.jsx'; import TeamStore from 'stores/team_store.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -96,12 +96,10 @@ class SecurityTab extends React.Component { return; } - var data = {}; - data.user_id = user.id; - data.current_password = currentPassword; - data.new_password = newPassword; - - Client.updatePassword(data, + Client.updatePassword( + user.id, + currentPassword, + newPassword, () => { this.props.updateSection(''); AsyncClient.getMe(); @@ -120,11 +118,9 @@ class SecurityTab extends React.Component { ); } activateMfa() { - const data = {}; - data.activate = true; - data.token = this.state.mfaToken; - - Client.updateMfa(data, + Client.updateMfa( + this.state.mfaToken, + true, () => { this.props.updateSection(''); AsyncClient.getMe(); @@ -224,7 +220,7 @@ class SecurityTab extends React.Component { <div className='col-sm-7'> <img className='qr-code-img' - src={'/api/v1/users/generate_mfa_qr?time=' + this.props.user.update_at} + src={Client.getUsersRoute() + '/generate_mfa_qr?time=' + this.props.user.update_at} /> </div> <br/> @@ -531,9 +527,9 @@ class SecurityTab extends React.Component { if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') { let link; if (user.auth_service === Constants.LDAP_SERVICE) { - link = '/' + teamName + '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); + link = '/claim/ldap_to_email?email=' + encodeURIComponent(user.email); } else { - link = '/' + teamName + '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; + link = '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service; } emailOption = ( @@ -558,7 +554,7 @@ class SecurityTab extends React.Component { <div> <Link className='btn btn-primary' - to={'/' + teamName + '/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} + to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE} > <FormattedMessage id='user.settings.security.switchGitlab' @@ -594,7 +590,7 @@ class SecurityTab extends React.Component { <div> <Link className='btn btn-primary' - to={'/' + teamName + '/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} + to={'/claim/email_to_ldap?email=' + encodeURIComponent(user.email)} > <FormattedMessage id='user.settings.security.switchLdap' @@ -660,6 +656,13 @@ class SecurityTab extends React.Component { defaultMessage='GitLab SSO' /> ); + } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) { + describe = ( + <FormattedMessage + id='user.settings.security.ldap' + defaultMessage='LDAP' + /> + ); } return ( diff --git a/webapp/components/user_settings/user_settings_theme.jsx b/webapp/components/user_settings/user_settings_theme.jsx index 14991037d..f19538f71 100644 --- a/webapp/components/user_settings/user_settings_theme.jsx +++ b/webapp/components/user_settings/user_settings_theme.jsx @@ -11,7 +11,7 @@ import SettingItemMax from '../setting_item_max.jsx'; import UserStore from 'stores/user_store.jsx'; import AppDispatcher from '../../dispatcher/app_dispatcher.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx index 3d3107d92..bd4aeaa41 100644 --- a/webapp/components/view_image.jsx +++ b/webapp/components/view_image.jsx @@ -3,7 +3,7 @@ import $ from 'jquery'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; +import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import AudioVideoPreview from './audio_video_preview.jsx'; import Constants from 'utils/constants.jsx'; diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index da69aba74..6d4f4c287 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -271,6 +271,9 @@ "admin.ldap.lastnameAttrDesc": "The attribute in the LDAP server that will be used to populate the last name of users in Mattermost.", "admin.ldap.lastnameAttrEx": "Ex \"sn\"", "admin.ldap.lastnameAttrTitle": "Last Name Attribute:", + "admin.ldap.nicknameAttrDesc": "(Optional) The attribute in the LDAP server that will be used to populate the nickname of users in Mattermost.", + "admin.ldap.nicknameAttrEx": "Ex \"nickname\"", + "admin.ldap.nicknameAttrTitle": "Nickname Attribute:", "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note:</h4><p>LDAP is an enterprise feature. Your current license does not support LDAP. Click <a href=\"http://mattermost.com\"target=\"_blank\">here</a> for information and pricing on enterprise licenses.</p>", "admin.ldap.portDesc": "The port Mattermost will use to connect to the LDAP server. Default is 389.", "admin.ldap.portEx": "Ex \"389\"", @@ -526,10 +529,18 @@ "admin.team.uploading": "Uploading..", "admin.team.userCreationDescription": "When false, the ability to create accounts is disabled. The create account button displays error when pressed.", "admin.team.userCreationTitle": "Enable User Creation: ", + "admin.team.openServerDescription": "When true, anyone can signup for a user account on this server without the need to be invited.", + "admin.team.openServerTitle": "Enable Open Server: ", "admin.team_analytics.activeUsers": "Active Users With Posts", "admin.team_analytics.totalPosts": "Total Posts", "admin.userList.title": "Users for {team}", "admin.userList.title2": "Users for {team} ({count})", + "admin.user_item.resetMfa": "Remove MFA", + "admin.user_item.mfaYes": ", <strong>MFA</strong>: Yes", + "admin.user_item.mfaNo": ", <strong>MFA</strong>: No", + "admin.user_item.authServiceNotEmail": ", <strong>Sign-in Method:</strong> {service}", + "admin.user_item.authServiceEmail": ", <strong>Sign-in Method:</strong> Email", + "admin.user_item.emailTitle": "<strong>Email:</strong> {email}", "admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.", "admin.user_item.confirmDemoteRoleTitle": "Confirm demotion from System Admin role", "admin.user_item.confirmDemotion": "Confirm Demotion", @@ -721,7 +732,7 @@ "choose_auth_page.noSignup": "No sign-up methods configured, please contact your system administrator.", "claim.account.noEmail": "No email specified", "claim.email_to_ldap.enterLdapPwd": "Enter the ID and password for your LDAP account", - "claim.email_to_ldap.enterPwd": "Enter the password for your {team} {site} email account", + "claim.email_to_ldap.enterPwd": "Enter the password for your {site} email account", "claim.email_to_ldap.ldapId": "LDAP ID", "claim.email_to_ldap.ldapIdError": "Please enter your LDAP ID.", "claim.email_to_ldap.ldapPasswordError": "Please enter your LDAP password.", @@ -732,7 +743,7 @@ "claim.email_to_ldap.ssoType": "Upon claiming your account, you will only be able to login with LDAP", "claim.email_to_ldap.switchTo": "Switch account to LDAP", "claim.email_to_ldap.title": "Switch Email/Password Account to LDAP", - "claim.email_to_oauth.enterPwd": "Enter the password for your {team} {site} account", + "claim.email_to_oauth.enterPwd": "Enter the password for your {site} account", "claim.email_to_oauth.pwd": "Password", "claim.email_to_oauth.pwdError": "Please enter your password.", "claim.email_to_oauth.ssoNote": "You must already have a valid {type} account", @@ -741,7 +752,7 @@ "claim.email_to_oauth.title": "Switch Email/Password Account to {uiType}", "claim.ldap_to_email.confirm": "Confirm Password", "claim.ldap_to_email.email": "You will use the email {email} to login", - "claim.ldap_to_email.enterLdapPwd": "Enter your LDAP password for your {team} {site} email account", + "claim.ldap_to_email.enterLdapPwd": "Enter your {ldapPassword} for your {site} email account", "claim.ldap_to_email.enterPwd": "Enter a new password for your email account", "claim.ldap_to_email.ldapPasswordError": "Please enter your LDAP password.", "claim.ldap_to_email.ldapPwd": "LDAP Password", @@ -758,7 +769,7 @@ "claim.oauth_to_email.pwdNotMatch": "Password do not match.", "claim.oauth_to_email.switchTo": "Switch {type} to email and password", "claim.oauth_to_email.title": "Switch {type} Account to Email", - "claim.oauth_to_email_newPwd": "Enter a new password for your {team} {site} account", + "claim.oauth_to_email.enterNewPwd": "Enter a new password for your {site} account", "confirm_modal.cancel": "Cancel", "create_comment.addComment": "Add a comment...", "create_comment.comment": "Add Comment", @@ -841,8 +852,8 @@ "general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.", "general_tab.includeDirTitle": "Include this team in the Team Directory", "general_tab.no": "No", - "general_tab.openInviteDesc": "When allowed, a link to account creation will be included on the sign-in page of this team and allow any visitor to sign-up.", - "general_tab.openInviteTitle": "Allow anyone to sign-up from login page", + "general_tab.openInviteDesc": "When allowed, a link to this team will be including on the landing page allowing anyone with an account to join this team.", + "general_tab.openInviteTitle": "Allow anyone to join this team", "general_tab.regenerate": "Re-Generate", "general_tab.required": "This field is required", "general_tab.teamName": "Team Name", @@ -866,6 +877,7 @@ "installed_integrations.regenToken": "Regen Token", "installed_integrations.search": "Search Integrations", "installed_integrations.token": "Token: {token}", + "installed_integrations.url": "URL: {url}", "installed_outgoing_webhooks.add": "Add Outgoing Webhook", "installed_outgoing_webhooks.header": "Outgoing Webhooks", "integrations.command.description": "Slash commands send events to external integrations", @@ -913,6 +925,7 @@ "ldap_signup.team_error": "Please enter a team name", "loading_screen.loading": "Loading", "login.changed": " Sign-in method changed successfully", + "login.passwordChanged": " Password updated successfully", "login.create": "Create one now", "login.createTeam": "Create a new team", "login.find": "Find your other teams", @@ -1111,7 +1124,9 @@ "sidebar_right_menu.report": "Report a Problem", "sidebar_right_menu.teamLink": "Get Team Invite Link", "sidebar_right_menu.teamSettings": "Team Settings", - "signup_team.choose": "Choose a Team", + "signup_team.no_teams": "You do not appear to be a member of any team. Please ask your administrator for an invite, join an open team if one exists or possibly create a new team.", + "signup_team.choose": "Teams you are a member of: ", + "signup_team.join_open": "Open teams you can join: ", "signup_team.createTeam": "Or Create a Team", "signup_team.disabled": "Team creation has been disabled. Please contact an administrator for access.", "signup_team.noTeams": "There are no teams included in the Team Directory and team creation has been disabled.", @@ -1119,6 +1134,8 @@ "signup_team_complete.completed": "You've already completed the signup process for this invitation or this invitation has expired.", "signup_team_confirm.checkEmail": "Please check your email: <strong>{email}</strong><br />Your email contains a link to set up your team", "signup_team_confirm.title": "Sign up Complete", + "signup_user_completed.no_open_server": "This server does not allow open signups. Please speak with your Administrator to receive an invitation.", + "signup_user_completed.invalid_invite": "The invite link was invalid. Please speak with your Administrator to receive an invitation.", "signup_user_completed.choosePwd": "Choose your password", "signup_user_completed.chooseUser": "Choose your username", "signup_user_completed.create": "Create Account", @@ -1181,61 +1198,23 @@ "team_settings_modal.generalTab": "General", "team_settings_modal.importTab": "Import", "team_settings_modal.title": "Team Settings", - "team_signup_display_name.back": "Back to previous step", - "team_signup_display_name.charLength": "Name must be 4 or more characters up to a maximum of 15", - "team_signup_display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.", - "team_signup_display_name.next": "Next", - "team_signup_display_name.required": "This field is required", - "team_signup_display_name.teamName": "Team Name", - "team_signup_email.address": "Email Address", - "team_signup_email.different": "Please use a different email than the one used at signup", - "team_signup_email.validEmail": "Please enter a valid email address", - "team_signup_password.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.", - "team_signup_password.back": "Back to previous step", - "team_signup_password.choosePwd": "Choose your password", - "team_signup_password.creating": "Creating team...", - "team_signup_password.email": "Email", - "team_signup_password.finish": "Finish", - "team_signup_password.hint": "Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.", - "team_signup_password.passwordError": "Please enter at least {chars} characters", - "team_signup_password.selectPassword": "Select a password that you'll use to login with your email address:", - "team_signup_password.yourPassword": "Your password", - "team_signup_send_invites.addInvitation": "Add Invitation", - "team_signup_send_invites.back": "Back to previous step", - "team_signup_send_invites.disabled": "Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.", - "team_signup_send_invites.forNow": "for now.", - "team_signup_send_invites.next": "Next", - "team_signup_send_invites.prefer": "if you prefer, you can invite team members later<br /> and ", - "team_signup_send_invites.skip": "skip this step ", - "team_signup_send_invites.title": "Invite Team Members", - "team_signup_url.back": "Back to previous step", - "team_signup_url.charLength": "Name must be 4 or more characters up to a maximum of 15", - "team_signup_url.hint": "<li>Short and memorable is best</li><li>Use lowercase letters, numbers and dashes</li><li>Must start with a letter and can't end in a dash</li>", - "team_signup_url.next": "Next", - "team_signup_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.", - "team_signup_url.required": "This field is required", - "team_signup_url.taken": "URL is taken or contains a reserved word", - "team_signup_url.teamUrl": "Team URL", - "team_signup_url.unavailable": "This URL is unavailable. Please try another.", - "team_signup_url.webAddress": "Choose the web address of your new team:", - "team_signup_username.back": "Back to previous step", - "team_signup_username.chooseUsername": "Choose your username", - "team_signup_username.hint": "Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'", - "team_signup_username.invalid": "Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'", - "team_signup_username.memorable": "Select a memorable username that makes it easy for teammates to identify you:", - "team_signup_username.next": "Next", - "team_signup_username.reserved": "This username is reserved, please choose a new one.", - "team_signup_username.username": "Your username", - "team_signup_welcome.address": "Email Address", - "team_signup_welcome.admin": "Your account will administer the new team site. <br />You can add other administrators later.", - "team_signup_welcome.confirm": "Please confirm your email address:", - "team_signup_welcome.different": "Use a different email", - "team_signup_welcome.instead": "Use this instead", - "team_signup_welcome.lets": "Let's set up your new team", - "team_signup_welcome.storageError": "This service requires local storage to be enabled. Please enable it or exit private browsing.", - "team_signup_welcome.validEmailError": "Please enter a valid email address", - "team_signup_welcome.welcome": "Welcome to:", - "team_signup_welcome.yes": "Yes, this address is correct", + "create_team.agreement": "By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}.", + "create_team.display_name.teamName": "Team Name", + "create_team.display_name.nameHelp": "Name your team in any language. Your team name shows in menus and headings.", + "create_team.display_name.next": "Next", + "create_team.display_name.back": "Back to previous step", + "create_team.display_name.required": "This field is required", + "create_team.display_name.charLength": "Name must be 4 or more characters up to a maximum of 15", + "create_team.team_url.required": "This field is required", + "create_team.team_url.regex": "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash.", + "create_team.team_url.charLength": "Name must be 4 or more characters up to a maximum of 15", + "create_team.team_url.taken": "URL is taken or contains a reserved word", + "create_team.team_url.unavailable": "This URL is unavailable. Please try another.", + "create_team.team_url.teamUrl": "Team URL", + "create_team.team_url.webAddress": "Choose the web address of your new team:", + "create_team.team_url.hint": "<li>Short and memorable is best</li><li>Use lowercase letters, numbers and dashes</li><li>Must start with a letter and can't end in a dash</li>", + "create_team.team_url.finish": "Finish", + "create_team.team_url.back": "Back to previous step", "textbox.bold": "**bold**", "textbox.edit": "Edit message", "textbox.help": "Help", @@ -1406,6 +1385,7 @@ "user.settings.security.currentPasswordError": "Please enter your current password", "user.settings.security.emailPwd": "Email and Password", "user.settings.security.gitlab": "GitLab SSO", + "user.settings.security.ldap": "LDAP", "user.settings.security.lastUpdated": "Last updated {date} at {time}", "user.settings.security.loginGitlab": "Login done through Gitlab", "user.settings.security.loginLdap": "Login done through LDAP", diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json index 23f1d5a45..7d429b629 100644 --- a/webapp/i18n/es.json +++ b/webapp/i18n/es.json @@ -721,7 +721,7 @@ "choose_auth_page.noSignup": "No hay métodos de inicio de sesión configurad, por favor contacte al administrador de sistemasos", "claim.account.noEmail": "No se especifico un correo electrónico.", "claim.email_to_ldap.enterLdapPwd": "Ingresa el ID y la contraseña de tu cuenta LDAP", - "claim.email_to_ldap.enterPwd": "Ingresa la contraseña para tu cuenta de correo en {team} {site}", + "claim.email_to_ldap.enterPwd": "Ingresa la contraseña para tu cuenta de correo en {site}", "claim.email_to_ldap.ldapId": "LDAP ID", "claim.email_to_ldap.ldapIdError": "Por favor ingresa tu ID de LDAP.", "claim.email_to_ldap.ldapPasswordError": "Por favor ingresa tu contraseña de LDAP.", @@ -732,7 +732,7 @@ "claim.email_to_ldap.ssoType": "Al reclamar tu cuenta, sólo podrás iniciar sesión con LDAP", "claim.email_to_ldap.switchTo": "Cambiar cuenta a LDAP", "claim.email_to_ldap.title": "Cambiar Cuenta de Correo/Contraseña a LDAP", - "claim.email_to_oauth.enterPwd": "Ingresa la contraseña para tu cuenta para {team} {site}", + "claim.email_to_oauth.enterPwd": "Ingresa la contraseña para tu cuenta para {site}", "claim.email_to_oauth.pwd": "Contraseña", "claim.email_to_oauth.pwdError": "Por favor introduce tu contraseña.", "claim.email_to_oauth.ssoNote": "Debes tener una cuenta válida con {type}", @@ -741,7 +741,6 @@ "claim.email_to_oauth.title": "Cambiar Cuenta de Correo/Contraseña a {uiType}", "claim.ldap_to_email.confirm": "Confirmar Contraseña", "claim.ldap_to_email.email": "Para iniciar sesión debes utilizar el correo electrónico {email}", - "claim.ldap_to_email.enterLdapPwd": "Ingresa tu contraseña de LDAP para tu cuenta de correo en {team} {site}", "claim.ldap_to_email.enterPwd": "Ingresa una nueva contraseña para tu cuenta de correo", "claim.ldap_to_email.ldapPasswordError": "Por favor ingresa tu contraseña LDAP.", "claim.ldap_to_email.ldapPwd": "Contraseña LDAP", @@ -758,7 +757,6 @@ "claim.oauth_to_email.pwdNotMatch": "Las contraseñas no coinciden.", "claim.oauth_to_email.switchTo": "Cambiar {type} a correo electrónico y contraseña", "claim.oauth_to_email.title": "Cambiar la cuenta de {type} a Correo Electrónico", - "claim.oauth_to_email_newPwd": "Ingresa una nueva contraseña para tu cuenta de {team} en {site}", "confirm_modal.cancel": "Cancelar", "create_comment.addComment": "Agregar un comentario...", "create_comment.comment": "Agregar Comentario", @@ -841,8 +839,6 @@ "general_tab.includeDirDesc": "Incluir este equipo mostrará el nombre del equipo en la sección de Directorio de Equipos en la página de inicio, y proveerá un enlace para la página de inicio de sesión.", "general_tab.includeDirTitle": "Incluir este Equipo en el Directorio de Equipos", "general_tab.no": "No", - "general_tab.openInviteDesc": "Cuando está permitido, un enlace para la creación de cuentas será incluido en la página de registro de este equipo y permitirá a cualquier visitante registrarse.", - "general_tab.openInviteTitle": "Permitir a cualquiera a inscribirse desde la página de inicio de sesión", "general_tab.regenerate": "Regenerar", "general_tab.required": "Este campo es obligatorio", "general_tab.teamName": "Nombre del Equipo", @@ -1111,7 +1107,6 @@ "sidebar_right_menu.report": "Reporta un Problema", "sidebar_right_menu.teamLink": "Enlace Invitación al Equipo", "sidebar_right_menu.teamSettings": "Configurar Equipo", - "signup_team.choose": "Selecciona un Equipo", "signup_team.createTeam": "O Crea un Equipo", "signup_team.disabled": "La creación de Equipos ha sido deshabilitada.", "signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido deshabilitada.", @@ -1181,61 +1176,6 @@ "team_settings_modal.generalTab": "General", "team_settings_modal.importTab": "Importar", "team_settings_modal.title": "Configuración del Equipo", - "team_signup_display_name.back": "Volver al paso previo", - "team_signup_display_name.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15", - "team_signup_display_name.nameHelp": "Nombre tu equipo en cualquier idioma. El nombre de tu equipo aparecerá en menús y en inicios", - "team_signup_display_name.next": "Siguiente", - "team_signup_display_name.required": "Este campo es obligatorio", - "team_signup_display_name.teamName": "Nombre del Equipo", - "team_signup_email.address": "Dirección de correo electrónico", - "team_signup_email.different": "Please use a different email than the one used at signup", - "team_signup_email.validEmail": "Por favor ingresa una dirección de correo electrónico válida", - "team_signup_password.agreement": "Procediendo a crear tu cuenta y el uso de {siteName}, indicas que estás de acuerdo con nuestros <a href='/static/help/terms.html'>Términos de Servicio</a> y <a href='/static/help/privacy.html'>Políticas de Privacidad</a>. Si no estás de acuerdo, no debes utilizar {siteName}.", - "team_signup_password.back": "Volver al paso previo", - "team_signup_password.choosePwd": "Escoge tu contraseña", - "team_signup_password.creating": "Creando equipo...", - "team_signup_password.email": "Correo electrónico", - "team_signup_password.finish": "Finalizar", - "team_signup_password.hint": "Las contraseñas deben contener de {min} a {max} caracteres. Su contraseña será más fuerte si contiene una mezcla de símbolos, números y caracteres en mayúsculas y minúsculas.", - "team_signup_password.passwordError": "Por favor ingrese al menos {chars} caracteres", - "team_signup_password.selectPassword": "Selecciona la contraseña que estás usando con tu dirección de correos:", - "team_signup_password.yourPassword": "Tu contraseña", - "team_signup_send_invites.addInvitation": "Agrega una Invitación", - "team_signup_send_invites.back": "Volver al paso previo", - "team_signup_send_invites.disabled": "Este correo electrónico está actualmente deshabilitado para tu equipo, y los correos no podrán ser enviados. Contacta a tu administrador de sistemas", - "team_signup_send_invites.forNow": "por ahora.", - "team_signup_send_invites.next": "Siguiente", - "team_signup_send_invites.prefer": "Si prefieres, puedes invitar a miembros de equipo más tarde <br /> y ", - "team_signup_send_invites.skip": "saltarte este paso ", - "team_signup_send_invites.title": "Invita Miembros al Equipo", - "team_signup_url.back": "Volver al paso previo", - "team_signup_url.charLength": "El nombre debe tener 4 o más caracteres hasta un máximo de 15", - "team_signup_url.hint": "<li>Corto y memorizable es mejor</li><li>Use letras en minúsculas, números y guiones</li><li>Debe empezar con una letra y no puede finalizar con un guión</li>", - "team_signup_url.next": "Siguiente", - "team_signup_url.regex": "Sólo utiliza letras en minúsculas, numeros y guiones. Debe comenzar con una letra y no puede terminar en un guión.", - "team_signup_url.required": "Este campo es obligatorio", - "team_signup_url.taken": "Este URL ya fue asignado o contiene una palabra reservada", - "team_signup_url.teamUrl": "URL de Equipo", - "team_signup_url.unavailable": "Este URL no está disponible. Por favor intenta con otro.", - "team_signup_url.webAddress": "Escoge la dirección web de tu nuevo equipo:", - "team_signup_username.back": "Volver al paso previo", - "team_signup_username.chooseUsername": "Escoge un nombre de usuario", - "team_signup_username.hint": "El nombre de usuario debe empezar con una letra, y contener entre {min} a {max} caracteres en minúscula con números, letras, y los símbolos '.', '-' y '_'.", - "team_signup_username.invalid": "El nombre de usuario debe comenzar con una letra, y tener entre {min} y {max} de caracteres en total, los cuales pueden ser numeros, letras en minúsculas, o cualquiera de los simbolos '.', '-', o '_'", - "team_signup_username.memorable": "Selecciona un nombre de usuario sencillo de recordar y que sea fácil para a tus compañeros de equipo identificarte:", - "team_signup_username.next": "Siguiente", - "team_signup_username.reserved": "Este nombre de usuario está reservado. Por favor escoge otro.", - "team_signup_username.username": "Tu nombre de usuario", - "team_signup_welcome.address": "Dirección de correo", - "team_signup_welcome.admin": "Tu cuenta administrará un nuevo sitio del equipo. <br />Puedes agregar otros administradores más adelante.", - "team_signup_welcome.confirm": "Por favor confirma tu dirección de correos:", - "team_signup_welcome.different": "Usa un correo diferente", - "team_signup_welcome.instead": "Usa este en vez de", - "team_signup_welcome.lets": "permítenos setear tu nuevo equipo", - "team_signup_welcome.storageError": "Este servicio requiere de almacenamiento local para ser habilitado. Por favor habilítalo o sale de la navegación privad.", - "team_signup_welcome.validEmailError": "Por favor ingresa una dirección de correo electrónico válida", - "team_signup_welcome.welcome": "Bienvenido a:", - "team_signup_welcome.yes": "Sí, esta dirección es correcta", "textbox.bold": "**negritas**", "textbox.edit": "Editar mensaje", "textbox.help": "Ayuda", diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json index 44bf36ef6..1e2ea0e9b 100644 --- a/webapp/i18n/fr.json +++ b/webapp/i18n/fr.json @@ -647,7 +647,7 @@ "choose_auth_page.noSignup": "Aucune méthode configurée pour s'inscrive, veuillez contacter votre administrateur système.", "claim.account.noEmail": "Aucune adresse électronique indiquée", "claim.email_to_ldap.enterLdapPwd": "Saisissez l'identifiant et le mode de passe de votre compte LDAP", - "claim.email_to_ldap.enterPwd": "Saisissez le mot de passe pour votre compte {team} {site}", + "claim.email_to_ldap.enterPwd": "Saisissez le mot de passe pour votre compte {site}", "claim.email_to_ldap.ldapId": "Identifiant LDAP", "claim.email_to_ldap.ldapIdError": "Veuillez saisir votre identifiant LDAP.", "claim.email_to_ldap.ldapPasswordError": "Veuillez saisir votre mot de passe LDAP.", @@ -658,7 +658,7 @@ "claim.email_to_ldap.ssoType": "Une fois votre compte configuré, vous ne pourrez vous connectez qu'avec LDAP", "claim.email_to_ldap.switchTo": "Basculer le compte vers LDAP", "claim.email_to_ldap.title": "Transférer le login par courriel/mot de passe en LDAP", - "claim.email_to_oauth.enterPwd": "Saisissez le mot de passe pour votre compte {team} {site}", + "claim.email_to_oauth.enterPwd": "Saisissez le mot de passe pour votre compte {site}", "claim.email_to_oauth.pwd": "Mot de passe", "claim.email_to_oauth.pwdError": "Veuillez saisir votre mot de passe.", "claim.email_to_oauth.ssoNote": "Vous devez déjà avoir un compte {type} valide", @@ -667,7 +667,6 @@ "claim.email_to_oauth.title": "Changer l'adresse électronique/mot de passe pour {uiType}", "claim.ldap_to_email.confirm": "Confirmer le mot de passe", "claim.ldap_to_email.email": "Vous devrez utiliser l'adresse électronique {email} pour vous connecter.", - "claim.ldap_to_email.enterLdapPwd": "Saisissez votre mot de passe LDAP pour votre compte {team} {site}", "claim.ldap_to_email.enterPwd": "Saisissez un nouveau mot de passe pour votre compte", "claim.ldap_to_email.ldapPasswordError": "Veuillez saisir votre mot de passe LDAP.", "claim.ldap_to_email.ldapPwd": "Mot de passe LDAP", @@ -680,7 +679,7 @@ "claim.oauth_to_email.confirm": "Confirmez le mot de passe", "claim.oauth_to_email.description": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'à l'aide de votre adresse électronique et votre mot de passe.", "claim.oauth_to_email.enterPwd": "Veuillez saisir un mot de passe.", - "claim.oauth_to_email.newPwd": "Saisissez un nouveau mot de passe pour votre compte {team} {site}", + "claim.oauth_to_email.newPwd": "Saisissez un nouveau mot de passe pour votre compte {site}", "claim.oauth_to_email.pwdNotMatch": "Le mot de passe ne correspond pas.", "claim.oauth_to_email.switchTo": "Basculer de {type} vers adresse électronique et mot de passe", "claim.oauth_to_email.title": "Basculer du compte {type} vers l'adresse électronique", @@ -765,8 +764,6 @@ "general_tab.includeDirDesc": "Inclure cette équipe affichera le nom de l'équipe dans l'annuaire sur la page d'accueil, ainsi qu'un lien pour rejoindre cette équipe.", "general_tab.includeDirTitle": "Afficher cette équipe dans l'annuaire", "general_tab.no": "Non", - "general_tab.openInviteDesc": "Si activé, un lien pour créer un compte est affiché sur la page de connexion de l'équipe, et permet à n'importe qui de rejoindre l'équipe.", - "general_tab.openInviteTitle": "Permettre à tout le monde de s'inscrire", "general_tab.regenerate": "Générer de nouveau", "general_tab.required": "Ce champ est obligatoire", "general_tab.teamName": "Nom de l'équipe", @@ -1012,7 +1009,6 @@ "sidebar_right_menu.report": "Signaler un problème", "sidebar_right_menu.teamLink": "Obtenir un lien d'invitation d'équipe", "sidebar_right_menu.teamSettings": "Configuration de l'équipe", - "signup_team.choose": "Choisir une équipe", "signup_team.createTeam": "Ou créez une équipe", "signup_team.disabled": "Aucune méthode de création d'utilisateur n'est disponible. Veuillez contacter votre administrateur système pour obtenir un accès.", "signup_team.noTeams": "Il n'y a aucune équipe dans l'annuaire, et la création d'équipe n'est pas autorisée.", @@ -1081,61 +1077,6 @@ "team_settings_modal.generalTab": "Général", "team_settings_modal.importTab": "Importer", "team_settings_modal.title": "Configuration de l'équipe", - "team_signup_display_name.back": "Revenir à l'étape précédente", - "team_signup_display_name.charLength": "Le nom doit être composé de 4 à 15 caractères", - "team_signup_display_name.nameHelp": "Donnez un nom à votre équipe. Le nom de votre équipe apparait dans les menus et les en-têtes.", - "team_signup_display_name.next": "Suivant", - "team_signup_display_name.required": "Ce champ est obligatoire", - "team_signup_display_name.teamName": "Nom de l'équipe", - "team_signup_email.address": "Adresse électronique", - "team_signup_email.different": "Veuillez utiliser une autre adresse électronique que celle utilisée pour votre inscription", - "team_signup_email.validEmail": "Veuillez saisir une adresse électronique valide", - "team_signup_password.agreement": "En créant ce compte et en utilisant {siteName}, vous consentez à nos <a href='/static/help/terms.html'>Conditions d'utilisation</a> et <a href='/static/help/privacy.html'>Politique de Confidentialité</a>. Si vous n'y consentez pas, vous ne pouvez pas utiliser {siteName}.", - "team_signup_password.back": "Retour à l'étape précédente", - "team_signup_password.choosePwd": "Choisissez votre mot de passe", - "team_signup_password.creating": "Création de l'équipe...", - "team_signup_password.email": "Adresse électronique", - "team_signup_password.finish": "Terminé", - "team_signup_password.hint": "Les mots de passe doivent contenir entre {min} et {max} caractère. Votre mot de passe sera plus sécurisé s'il contient un mélange de symboles, de chiffres, et de lettres majuscules et minuscules", - "team_signup_password.passwordError": "Veuillez saisir au moins {chars} caractères", - "team_signup_password.selectPassword": "Choisissez le mot de passe que vous utiliserez pour vous connecter avec votre adresse électronique :", - "team_signup_password.yourPassword": "Votre mot de passe", - "team_signup_send_invites.addInvitation": "Ajouter une invitation", - "team_signup_send_invites.back": "Retour à l'étape précédente", - "team_signup_send_invites.disabled": "Les courriels sont désactivés pour votre équipe et ne peuvent pas être envoyés. Contactez votre administrateur système pour activer les courriels et les invitations par courriel.", - "team_signup_send_invites.forNow": "pour l'instant.", - "team_signup_send_invites.next": "Suivant", - "team_signup_send_invites.prefer": "Vous pouvez aussi inviter des membres plus tard<br /> et", - "team_signup_send_invites.skip": "passer cette étape", - "team_signup_send_invites.title": "Inviter des membres", - "team_signup_url.back": "Retour à l'étape précédente", - "team_signup_url.charLength": "Le nom doit contenir entre 4 et 15 caractères", - "team_signup_url.hint": "<li>Court et facile à retenir, c'est mieux !</li><li>Utilisez des lettres minuscules, des chiffres et des tirets</li><li>Doit commencer par une lettre et ne peut pas finir par un tiret.</li>", - "team_signup_url.next": "Suivant", - "team_signup_url.regex": "Utilisez seulement des lettres minuscules, des chiffres et des tirets. Doit commencer par une lettre et ne doit pas finir par un tiret.", - "team_signup_url.required": "Champ obligatoire", - "team_signup_url.taken": "Cette URL est indisponible ou contient un mot réservé", - "team_signup_url.teamUrl": "URL de l'équipe", - "team_signup_url.unavailable": "Cette URL est indisponible. Veuillez essayer une autre URL.", - "team_signup_url.webAddress": "Choisissez l'adresse internet de votre nouvelle équipe :", - "team_signup_username.back": "Retour à l'étape précédente", - "team_signup_username.chooseUsername": "Choisissez votre nom d'utilisateur", - "team_signup_username.hint": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de lettres minuscules, de chiffres et des symboles '.', '-' et '_'.", - "team_signup_username.invalid": "Les nomes d'utilisateur doivent commencer par une lettre et contenir entre {min} et {max} caractères, qui peuvent être des chiffres, des lettres minuscules ou les symboles '.', '-' ou '_'.", - "team_signup_username.memorable": "Choisissez un nom d'utilisateur facile à retenir qui permettra aux autres membres de l'équipe de vous identifier facilement :", - "team_signup_username.next": "Suivant", - "team_signup_username.reserved": "Ce nom d'utilisateur est réservé, veuilles en choisir un autre.", - "team_signup_username.username": "Votre nom d'utilisateur", - "team_signup_welcome.address": "Adresse électronique", - "team_signup_welcome.admin": "Votre compte sera administrateur de votre nouvelle équipe. <br />Vous pourrez ajouter d'autres administrateurs par la suite.", - "team_signup_welcome.confirm": "Veuillez confirmer votre adresse électronique :", - "team_signup_welcome.different": "Utiliser une autre adresse électronique", - "team_signup_welcome.instead": "Utiliser plutôt ceci", - "team_signup_welcome.lets": "Configurons ensemble votre nouvelle équipe", - "team_signup_welcome.storageError": "Ce service nécessite l'utilisation des cookies. Veuillez permettre le stockage des cookies ou quitter la navigation privée.", - "team_signup_welcome.validEmailError": "Veuillez entrer une adresse électronique valide", - "team_signup_welcome.welcome": "Bienvenue sur :", - "team_signup_welcome.yes": "Oui, cette adresse est correcte", "textbox.bold": "**gras**", "textbox.edit": "Modifier le message", "textbox.help": "Aide", diff --git a/webapp/i18n/ja.json b/webapp/i18n/ja.json index ea951235e..eab97673e 100644 --- a/webapp/i18n/ja.json +++ b/webapp/i18n/ja.json @@ -1181,61 +1181,6 @@ "team_settings_modal.generalTab": "全般", "team_settings_modal.importTab": "インポート", "team_settings_modal.title": "チームの設定", - "team_signup_display_name.back": "前のステップに戻る", - "team_signup_display_name.charLength": "名前は4文字以上の15文字以下にしてください", - "team_signup_display_name.nameHelp": "チーム名はどんな言語でも使うことができます。チーム名はメニューと画面上部に表示されます。", - "team_signup_display_name.next": "次へ", - "team_signup_display_name.required": "この項目は必須です", - "team_signup_display_name.teamName": "チーム名", - "team_signup_email.address": "電子メールアドレス", - "team_signup_email.different": "利用登録で使用した電子メールアドレスとは別の電子メールアドレスを使ってください", - "team_signup_email.validEmail": "有効な電子メールアドレスを入力してください", - "team_signup_password.agreement": "{siteName}にアカウントを作成し利用する前に<a href='/static/help/terms.html'>使用条件</a>と<a href='/static/help/privacy.html'>プライバシーポリシー</a>に同意してください。同意できない場合は{siteName}は使用できません。", - "team_signup_password.back": "前のステップに戻る", - "team_signup_password.choosePwd": "パスワードを入力してください", - "team_signup_password.creating": "チームを作成しています…", - "team_signup_password.email": "電子メールアドレス", - "team_signup_password.finish": "完了する", - "team_signup_password.hint": "パスワードは{min}から{max}文字にしてください。パスワードを強固にするには、記号、数字、大文字と小文字の英字が混在するようにしてください。", - "team_signup_password.passwordError": "少なくとも{chars}文字を入力してください。", - "team_signup_password.selectPassword": "電子メールアドレスでログインする場合のパスワードを入力してください:", - "team_signup_password.yourPassword": "あなたのパスワード", - "team_signup_send_invites.addInvitation": "招待する", - "team_signup_send_invites.back": "前のステップに戻る", - "team_signup_send_invites.disabled": "あなたのチームでは電子メールは有効になっていません。電子メールによる招待状は送信できません。システム管理者に電子メールと電子メールによる招待を有効にするように連絡してください。", - "team_signup_send_invites.forNow": "いますぐ。", - "team_signup_send_invites.next": "次へ", - "team_signup_send_invites.prefer": "後ほどチームメンバーを招待することもできます<br />また ", - "team_signup_send_invites.skip": "このステップはスキップできます ", - "team_signup_send_invites.title": "チームメンバーを招待する", - "team_signup_url.back": "前のステップに戻る", - "team_signup_url.charLength": "名前は4文字以上の15文字以下にしてください", - "team_signup_url.hint": "<li>短く覚えやすいものがベストです</li><li>英小文字、数字、ダッシュを使ってください</li><li>英小文字で始めてください。ダッシュで終わることはできません</li>", - "team_signup_url.next": "次へ", - "team_signup_url.regex": "英小文字、数字、ダッシュのみ使用できます。英小文字で始めてください。ダッシュで終わることはできません。", - "team_signup_url.required": "この項目は必須です", - "team_signup_url.taken": "URLが取得済みか、予約された単語を含んでいます", - "team_signup_url.teamUrl": "チームURL", - "team_signup_url.unavailable": "このURLは使用できません。他のものを試してください。", - "team_signup_url.webAddress": "あなたの新しいチームのウェブアドレスを選択してください。", - "team_signup_username.back": "前のステップに戻る", - "team_signup_username.chooseUsername": "ユーザー名を入力してください", - "team_signup_username.hint": "ユーザー名は英小文字で始めてください。また{min}から{max} 文字の英数字と'.'、'-'、'_'の記号だけで構成してください。", - "team_signup_username.invalid": "ユーザー名は英小文字で始めてください。また{min}から{max} 文字の英数字と'.'、'-'、'_'の記号だけで構成してください。", - "team_signup_username.memorable": "チームメイトが認識しやすいように覚えやすいユーザー名を選択してください:", - "team_signup_username.next": "次へ", - "team_signup_username.reserved": "このユーザー名は予約されています。他のユーザー名を使ってください。", - "team_signup_username.username": "あなたのユーザー名", - "team_signup_welcome.address": "電子メールアドレス", - "team_signup_welcome.admin": "あなたは新しいチームサイトの管理者になります。<br />後ほど他の人を管理者として追加することができます。", - "team_signup_welcome.confirm": "あなたの電子メールアドレスを確認してください:", - "team_signup_welcome.different": "違う電子メールアドレスを使う", - "team_signup_welcome.instead": "これを代わりに使用する", - "team_signup_welcome.lets": "新しいチームを作りましょう", - "team_signup_welcome.storageError": "このサービスを使用するにはローカルストレージを有効にしてください。有効にするかプライベートブラウジングを止めてください。", - "team_signup_welcome.validEmailError": "有効な電子メールアドレスを入力してください", - "team_signup_welcome.welcome": "ようこそ:", - "team_signup_welcome.yes": "この電子メールアドレスは正しいです", "textbox.bold": "**太字**", "textbox.edit": "メッセージを編集する", "textbox.help": "ヘルプ", diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt.json index 97fea8396..0fcb958c2 100644 --- a/webapp/i18n/pt.json +++ b/webapp/i18n/pt.json @@ -721,7 +721,7 @@ "choose_auth_page.noSignup": "Nenhum método de inscrição configurado, por favor contate seu administrador do sistema.", "claim.account.noEmail": "Nenhum email específicado", "claim.email_to_ldap.enterLdapPwd": "Entre o ID e a senha para sua conta LDAP", - "claim.email_to_ldap.enterPwd": "Entre a senha para o sua conta com email {team} {site}", + "claim.email_to_ldap.enterPwd": "Entre a senha para o sua conta com email {site}", "claim.email_to_ldap.ldapId": "LDAP ID", "claim.email_to_ldap.ldapIdError": "Por favor digite seu ID LDAP.", "claim.email_to_ldap.ldapPasswordError": "Por favor digite a sua senha LDAP.", @@ -732,7 +732,7 @@ "claim.email_to_ldap.ssoType": "Ao retirar a sua conta, você só vai ser capaz de logar com LDAP", "claim.email_to_ldap.switchTo": "Trocar a conta para LDAP", "claim.email_to_ldap.title": "Trocar E-mail/Senha da Conta para LDAP", - "claim.email_to_oauth.enterPwd": "Entre a senha para o sua conta {team} {site}", + "claim.email_to_oauth.enterPwd": "Entre a senha para o sua conta {site}", "claim.email_to_oauth.pwd": "Senha", "claim.email_to_oauth.pwdError": "Por favor digite a sua senha.", "claim.email_to_oauth.ssoNote": "Você precisa já ter uma conta {type} válida", @@ -741,7 +741,6 @@ "claim.email_to_oauth.title": "Trocar E-mail/Senha da Conta para {uiType}", "claim.ldap_to_email.confirm": "Confirmar senha", "claim.ldap_to_email.email": "Você vai usar o email {email} para logar", - "claim.ldap_to_email.enterLdapPwd": "Entre a sua senha LDAP para o sua conta {team} {site}", "claim.ldap_to_email.enterPwd": "Entre a nova senha para o sua conta com email.", "claim.ldap_to_email.ldapPasswordError": "Por favor digite a sua senha LDAP.", "claim.ldap_to_email.ldapPwd": "Senha LDAP", @@ -758,7 +757,6 @@ "claim.oauth_to_email.pwdNotMatch": "As senha não correspondem.", "claim.oauth_to_email.switchTo": "Trocar {type} para email e senha", "claim.oauth_to_email.title": "Trocar Conta {type} para E-mail", - "claim.oauth_to_email_newPwd": "Entre a nova senha para o sua conta {team} {site}", "confirm_modal.cancel": "Cancelar", "create_comment.addComment": "Adicionar um comentário...", "create_comment.comment": "Adicionar Comentário", @@ -841,8 +839,6 @@ "general_tab.includeDirDesc": "Incluindo esta equipe irá exibir o nome da equipe da seção Diretório Equipe da página inicial, e fornecer um link para a página de login.", "general_tab.includeDirTitle": "Incluir esta equipe no Diretório de Equipe", "general_tab.no": "Não", - "general_tab.openInviteDesc": "Quando permitido, um link para a criação da conta será incluído na página de login da equipe e permitir que qualquer visitante inscreva-se.", - "general_tab.openInviteTitle": "Permitir que qualquer pessoa se inscreva a partir da página de login", "general_tab.regenerate": "Re-Gerar", "general_tab.required": "Este campo é obrigatório", "general_tab.teamName": "Nome da Equipe", @@ -1111,7 +1107,6 @@ "sidebar_right_menu.report": "Relatar um Problema", "sidebar_right_menu.teamLink": "Obter Link para Convite de Equipe", "sidebar_right_menu.teamSettings": "Configurações da Equipe", - "signup_team.choose": "Escolha uma Equipe", "signup_team.createTeam": "Ou Criar uma Equipe", "signup_team.disabled": "A criação de equipe foi desativada. Por favor, entre em contato com um administrador para o acesso.", "signup_team.noTeams": "Não existe equipes incluidas no Diretório de Equipe e a criação de equipes foi desativada.", @@ -1181,61 +1176,6 @@ "team_settings_modal.generalTab": "Geral", "team_settings_modal.importTab": "Importar", "team_settings_modal.title": "Configurações da Equipe", - "team_signup_display_name.back": "Voltar para o passo anterior", - "team_signup_display_name.charLength": "O nome deve ser de 4 ou mais caracteres até um máximo de 15", - "team_signup_display_name.nameHelp": "Nome da sua equipe em qualquer idioma. Seu nome de equipe é mostrado em menus e títulos.", - "team_signup_display_name.next": "Próximo", - "team_signup_display_name.required": "Este campo é obrigatório", - "team_signup_display_name.teamName": "Nome Da Equipe", - "team_signup_email.address": "Endereço de E-mail", - "team_signup_email.different": "Por favor, use um e-mail diferente do que o usado na inscrição", - "team_signup_email.validEmail": "Por favor entre um endereço de e-mail válido", - "team_signup_password.agreement": "Ao prosseguir para criar sua conta e usar {siteName}, você concorda com nosso <a href='/static/help/terms.html'>Termo de Serviço</a> e <a href='/static/help/privacy.html'>Politica de Privacidade</a>. Se você não concorda, você não pode usar {siteName}.", - "team_signup_password.back": "Voltar para o passo anterior", - "team_signup_password.choosePwd": "Escolha sua senha", - "team_signup_password.creating": "Criando um equipe...", - "team_signup_password.email": "E-mail", - "team_signup_password.finish": "Terminar", - "team_signup_password.hint": "Senhas precisam conter {min} a {max} caracteres. Sua senha será segura se conter uma mistura de símbolos, números, e caracteres maiúsculos e minúsculos.", - "team_signup_password.passwordError": "Por favor, insira pelo menos {chars} caracteres", - "team_signup_password.selectPassword": "Selecione uma senha que você irá usar no login com seu endereço de email:", - "team_signup_password.yourPassword": "Sua senha", - "team_signup_send_invites.addInvitation": "Adicionar Convite", - "team_signup_send_invites.back": "Voltar para o passo anterior", - "team_signup_send_invites.disabled": "Email está desativado para a sua equipe, e emails e não podem ser enviados. Contate o seu administrador do sistema para ativar e-mail e convites por e-mail.", - "team_signup_send_invites.forNow": "agora.", - "team_signup_send_invites.next": "Próximo", - "team_signup_send_invites.prefer": "se você preferir, você pode convidar membros da equipe depois<br /> e ", - "team_signup_send_invites.skip": "pular este passo ", - "team_signup_send_invites.title": "Convidar Membros da Equipe", - "team_signup_url.back": "Voltar para o passo anterior", - "team_signup_url.charLength": "O nome deve ser de 4 ou mais caracteres até um máximo de 15", - "team_signup_url.hint": "<li>Curto e memorizável é o melhor</li><li>Use letras minúsculas, números e traços</li><li>Deve começar com uma letra e não pode terminar em um traço</li>", - "team_signup_url.next": "Próximo", - "team_signup_url.regex": "Utilize apenas letras minúsculas, números e traços. Deve começar com uma letra e não pode terminar em um traço.", - "team_signup_url.required": "Este campo é obrigatório", - "team_signup_url.taken": "URL é usada ou contém uma palavra reservada", - "team_signup_url.teamUrl": "Equipe URL", - "team_signup_url.unavailable": "Está URL está indisponível. Por favor tente outra.", - "team_signup_url.webAddress": "Escolha o endereço web para sua nova equipe:", - "team_signup_username.back": "Voltar para o passo anterior", - "team_signup_username.chooseUsername": "Escolha o seu nome de usuário", - "team_signup_username.hint": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres minúsculos contendo números, letras, e os símbolos '.', '-' e '_'", - "team_signup_username.invalid": "O nome de usuário precisa começar com uma letra, e conter entre {min} e {max} caracteres no total, podendo ser números, letras minúsculas, ou qualquer dos símbolos '.', '-' ou '_'", - "team_signup_username.memorable": "Escolha um nome de usuário memorizável que torna fácil para sua equipe de trabalho identificá-lo:", - "team_signup_username.next": "Próximo", - "team_signup_username.reserved": "Este nome de usuário é reservado, por favor, escolha uma nova.", - "team_signup_username.username": "Seu usuário", - "team_signup_welcome.address": "Endereço de E-mail", - "team_signup_welcome.admin": "Sua conta irá administrar o novo site da equipe. <br />Você pode adicionar outros administradores depois.", - "team_signup_welcome.confirm": "Por favor confirme seu endereço de e-mail:", - "team_signup_welcome.different": "Utilize um e-mail diferente", - "team_signup_welcome.instead": "Use este ao invez", - "team_signup_welcome.lets": "Vamos configurar sua nova equipe", - "team_signup_welcome.storageError": "Este serviço requer um armazenamento local para ser ativado. Por favor, habilite ou saia da navegação privada.", - "team_signup_welcome.validEmailError": "Por favor entre um endereço de e-mail válido", - "team_signup_welcome.welcome": "Bem-vindo:", - "team_signup_welcome.yes": "Sim, este endereço de email está correto", "textbox.bold": "**negrito**", "textbox.edit": "Editar mensagem", "textbox.help": "Ajuda", diff --git a/webapp/package.json b/webapp/package.json index 2e9efadb3..3ab971c09 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -28,7 +28,8 @@ "react-router": "2.0.1", "react-textarea-autosize": "3.3.0", "twemoji": "1.4.1", - "velocity-animate": "1.2.3" + "velocity-animate": "1.2.3", + "superagent": "1.8.3" }, "devDependencies": { "babel-eslint": "5.0.0", @@ -53,12 +54,22 @@ "sass-loader": "3.2.0", "style-loader": "0.13.0", "url-loader": "0.5.7", - "webpack": "2.1.0-beta.5" + "webpack": "2.1.0-beta.5", + + "mocha": "*", + "mocha-webpack": "*", + "webpack-node-externals": "*", + "mocha-jsdom": "*", + "jsdom": "*", + "jsdom-global": "*", + "react-addons-test-utils": "*", + "jquery-deferred": "*" }, "scripts": { "check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .", "build": "webpack", "run": "webpack --progress --watch", - "run-fullmap": "webpack --progress --watch" + "run-fullmap": "webpack --progress --watch", + "test": "mocha-webpack --webpack-config webpack.config-test.js \"**/*.test.jsx\"" } } diff --git a/webapp/root.html b/webapp/root.html index 1612bdce4..cc2b7cd61 100644 --- a/webapp/root.html +++ b/webapp/root.html @@ -46,7 +46,18 @@ </script> </head> <body> - <div id='root'/> + <div id='root'> + <div + class='loading-screen' + style='relative' + > + <div class='loading__content'> + <div class='round round-1'></div> + <div class='round round-2'></div> + <div class='round round-3'></div> + </div> + </div> + </div> <script> window.setup_root(); </script> diff --git a/webapp/root.jsx b/webapp/root.jsx index 9268643f3..b0625438f 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -10,10 +10,10 @@ import 'sass/styles.scss'; import React from 'react'; import ReactDOM from 'react-dom'; -import {Router, Route, IndexRoute, IndexRedirect, Redirect, browserHistory} from 'react-router'; +import {Router, Route, IndexRoute, Redirect, browserHistory} from 'react-router'; import Root from 'components/root.jsx'; import LoggedIn from 'components/logged_in.jsx'; -import NotLoggedIn from 'components/not_logged_in.jsx'; +import HeaderFooterTemplate from 'components/header_footer_template.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'; @@ -21,16 +21,15 @@ import ChannelView from 'components/channel_view.jsx'; import PermalinkView from 'components/permalink_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 Websockets from 'action_creators/websocket_actions.jsx'; +import TeamStore from 'stores/team_store.jsx'; import * as Utils from 'utils/utils.jsx'; + +import Client from 'utils/web_client.jsx'; + +import * as Websockets from 'action_creators/websocket_actions.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'; @@ -47,14 +46,9 @@ import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx'; import AddCommand from 'components/backstage/add_command.jsx'; import ErrorPage from 'components/error_page.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'; +import AppDispatcher from './dispatcher/app_dispatcher.jsx'; +import Constants from './utils/constants.jsx'; +const ActionTypes = Constants.ActionTypes; import Claim from 'components/claim/claim.jsx'; import EmailToOAuth from 'components/claim/components/email_to_oauth.jsx'; @@ -63,6 +57,10 @@ import LDAPToEmail from 'components/claim/components/ldap_to_email.jsx'; import EmailToLDAP from 'components/claim/components/email_to_ldap.jsx'; import Login from 'components/login/login.jsx'; +import SelectTeam from 'components/select_team/select_team.jsx'; +import CreateTeam from 'components/create_team/create_team.jsx'; +import CreateTeamDisplayName from 'components/create_team/components/display_name.jsx'; +import CreateTeamTeamUrl from 'components/create_team/components/team_url.jsx'; import * as I18n from 'i18n/i18n.jsx'; @@ -76,53 +74,33 @@ const notFoundParams = { // 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'); + 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/v3/admin/log_client', + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(l) + }); + + if (window.mm_config && 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(); } - ); + }; - const d2 = Client.getClientLicenceConfig( - (data) => { - if (!data) { - return; - } + var d1 = $.Deferred(); //eslint-disable-line new-cap - global.window.mm_license = data; - }, - (err) => { - AsyncClient.dispatchError(err, 'getClientLicenceConfig'); + GlobalActions.emitInitialLoad( + () => { + d1.resolve(); } ); - // 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 - }; - // Make sure the websockets close $(window).on('beforeunload', () => { @@ -132,7 +110,9 @@ function preRenderSetup(callwhendone) { function afterIntl() { I18n.doAddLocaleData(); - $.when(d1, d2).done(callwhendone); + $.when(d1).done(() => { + callwhendone(); + }); } if (global.Intl) { @@ -143,18 +123,59 @@ function preRenderSetup(callwhendone) { } function preLoggedIn(nextState, replace, callback) { - const d1 = Client.getAllPreferences( + ErrorStore.clearLastError(); + callback(); +} + +function preNeedsTeam(nextState, replace, callback) { + // First check to make sure you're in the current team + // for the current url. + var teamName = Utils.getTeamNameFromUrl(); + var team = TeamStore.getByName(teamName); + + if (!team) { + browserHistory.push('/error'); + return; + } + + GlobalActions.emitCloseRightHandSide(); + + TeamStore.saveMyTeam(team); + TeamStore.emitChange(); + + var d1 = $.Deferred(); //eslint-disable-line new-cap + var d2 = $.Deferred(); //eslint-disable-line new-cap + + Client.getChannels( (data) => { - PreferenceStore.setPreferencesFromServer(data); + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_CHANNELS, + channels: data.channels, + members: data.members + }); + + d1.resolve(); }, (err) => { - AsyncClient.dispatchError(err, 'getAllPreferences'); + AsyncClient.dispatchError(err, 'getChannels'); + d1.resolve(); } ); - const d2 = AsyncClient.getChannels(); + Client.getProfiles( + (data) => { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_PROFILES, + profiles: data + }); - ErrorStore.clearLastError(); + d2.resolve(); + }, + (err) => { + AsyncClient.dispatchError(err, 'getProfiles'); + d2.resolve(); + } + ); $.when(d1, d2).done(() => { callback(); @@ -163,21 +184,20 @@ function preLoggedIn(nextState, replace, callback) { function onPermalinkEnter(nextState) { const postId = nextState.params.postid; - GlobalActions.emitPostFocusEvent(postId); } -function onChannelEnter(nextState) { - doChannelChange(nextState); +function onChannelEnter(nextState, replace) { + doChannelChange(nextState, replace); } -function onChannelChange(prevState, nextState) { +function onChannelChange(prevState, nextState, replace) { if (prevState.params.channel !== nextState.params.channel) { - doChannelChange(nextState); + doChannelChange(nextState, replace); } } -function doChannelChange(state) { +function doChannelChange(state, replace) { let channel; if (state.location.query.fakechannel) { channel = JSON.parse(state.location.query.fakechannel); @@ -187,28 +207,13 @@ function doChannelChange(state) { channel = ChannelStore.getMoreByName(state.params.channel); } if (!channel) { - console.error('Unable to get channel to change to.'); //eslint-disable-line no-console + replace('/'); + return; } } GlobalActions.emitChannelClickEvent(channel); } -function onLoggedOut(nextState) { - const teamName = nextState.params.team; - Client.logout( - () => { - browserHistory.push('/' + teamName + '/login'); - BrowserStore.signalLogout(); - BrowserStore.clear(); - ErrorStore.clearLastError(); - PreferenceStore.clear(); - }, - () => { - browserHistory.push('/' + teamName + '/login'); - } - ); -} - function renderRootComponent() { ReactDOM.render(( <Router @@ -222,141 +227,38 @@ function renderRootComponent() { path='error' component={ErrorPage} /> - <Route - component={LoggedIn} - onEnter={preLoggedIn} - > + <Route component={HeaderFooterTemplate}> <Route - path=':team/channels/:channel' - onEnter={onChannelEnter} - onChange={onChannelChange} - components={{ - sidebar: Sidebar, - center: ChannelView - }} + path='login' + component={Login} /> <Route - path=':team/pl/:postid' - onEnter={onPermalinkEnter} - components={{ - sidebar: Sidebar, - center: PermalinkView - }} - /> - <Route - path=':team/tutorial' - components={{ - sidebar: Sidebar, - center: TutorialView - }} - /> - <Route - path=':team/logout' - onEnter={onLoggedOut} - /> - <Route path='settings/integrations'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: Integrations - }} - /> - <Route path='incoming_webhooks'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledIncomingWebhooks - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddIncomingWebhook - }} - /> - </Route> - <Route path='outgoing_webhooks'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledOutgoingWebhooks - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddOutgoingWebhook - }} - /> - </Route> - <Route path='commands'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledCommands - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddCommand - }} - /> - </Route> - <Redirect - from='*' - to='/error' - query={notFoundParams} - /> - </Route> - <Route - path='admin_console' - component={AdminConsole} + path='reset_password' + component={PasswordResetSendLink} /> - </Route> - <Route component={NotLoggedIn}> <Route - path='signup_team' - component={SignupTeam} + path='reset_password_complete' + component={PasswordResetForm} /> <Route - path='signup_team_complete' - component={SignupTeamComplete} + path='claim' + component={Claim} > - <IndexRoute component={FinishedPage}/> - <Route - path='welcome' - component={WelcomePage} - /> - <Route - path='team_display_name' - component={TeamDisplayNamePage} - /> <Route - path='team_url' - component={TeamURLPage} + path='oauth_to_email' + component={OAuthToEmail} /> <Route - path='send_invites' - component={SendInivtesPage} + path='email_to_oauth' + component={EmailToOAuth} /> <Route - path='username' - component={UsernamePage} + path='email_to_ldap' + component={EmailToLDAP} /> <Route - path='password' - component={PasswordPage} + path='ldap_to_email' + component={LDAPToEmail} /> </Route> <Route @@ -364,10 +266,6 @@ function renderRootComponent() { component={SignupUserComplete} /> <Route - path='signup_team_confirm' - component={SignupTeamConfirm} - /> - <Route path='should_verify_email' component={ShouldVerifyEmail} /> @@ -375,51 +273,136 @@ function renderRootComponent() { path='do_verify_email' component={DoVerifyEmail} /> + </Route> + <Route + component={LoggedIn} + onEnter={preLoggedIn} + > + <Route component={HeaderFooterTemplate}> + <Route + path='select_team' + component={SelectTeam} + /> + <Route + path='create_team' + component={CreateTeam} + > + <IndexRoute component={CreateTeamDisplayName}/> + <Route + path='display_name' + component={CreateTeamDisplayName} + /> + <Route + path='team_url' + component={CreateTeamTeamUrl} + /> + </Route> + </Route> + <Route + path='admin_console' + component={AdminConsole} + /> <Route path=':team' component={NeedsTeam} + onEnter={preNeedsTeam} > - <IndexRedirect to='login'/> <Route - path='login' - component={Login} + path='channels/:channel' + onEnter={onChannelEnter} + onChange={onChannelChange} + components={{ + sidebar: Sidebar, + center: ChannelView + }} /> <Route - path='reset_password' - component={PasswordResetSendLink} + path='pl/:postid' + onEnter={onPermalinkEnter} + components={{ + sidebar: Sidebar, + center: PermalinkView + }} /> <Route - path='reset_password_complete' - component={PasswordResetForm} + path='tutorial' + components={{ + sidebar: Sidebar, + center: TutorialView + }} /> - <Route - path='claim' - component={Claim} - > - <Route - path='oauth_to_email' - component={OAuthToEmail} - /> - <Route - path='email_to_oauth' - component={EmailToOAuth} - /> - <Route - path='email_to_ldap' - component={EmailToLDAP} + <Route path='settings/integrations'> + <IndexRoute + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: Integrations + }} /> - <Route - path='ldap_to_email' - component={LDAPToEmail} + <Route path='incoming_webhooks'> + <IndexRoute + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: InstalledIncomingWebhooks + }} + /> + <Route + path='add' + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: AddIncomingWebhook + }} + /> + </Route> + <Route path='outgoing_webhooks'> + <IndexRoute + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: InstalledOutgoingWebhooks + }} + /> + <Route + path='add' + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: AddOutgoingWebhook + }} + /> + </Route> + <Route path='commands'> + <IndexRoute + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: InstalledCommands + }} + /> + <Route + path='add' + components={{ + navbar: BackstageNavbar, + sidebar: BackstageSidebar, + center: AddCommand + }} + /> + </Route> + <Redirect + from='*' + to='/error' + query={notFoundParams} /> </Route> - <Redirect - from='*' - to='/error' - query={notFoundParams} - /> </Route> </Route> + <Redirect + from='*' + to='/error' + query={notFoundParams} + /> </Route> </Router> ), diff --git a/webapp/sass/routes/_backstage.scss b/webapp/sass/routes/_backstage.scss index 3257f6582..ebfe97ee4 100644 --- a/webapp/sass/routes/_backstage.scss +++ b/webapp/sass/routes/_backstage.scss @@ -216,6 +216,7 @@ body { .item-details__description, .item-details__token, .item-details__trigger-words, + .item-details__url, .item-details__creation { display: inline-block; margin-top: 10px; diff --git a/webapp/sass/routes/_signup.scss b/webapp/sass/routes/_signup.scss index 77ccdf4ed..08bd0d12d 100644 --- a/webapp/sass/routes/_signup.scss +++ b/webapp/sass/routes/_signup.scss @@ -419,6 +419,17 @@ } } + .signup-team-dir-err { + background: #fafafa; + border-top: 1px solid #d5d5d5; + color: inherit; + padding: 5px 15px; + + &:first-child { + border: none; + } + } + .signup-team-dir__name { float: left; overflow: hidden; diff --git a/webapp/stores/browser_store.jsx b/webapp/stores/browser_store.jsx index d605aac80..2dae78f46 100644 --- a/webapp/stores/browser_store.jsx +++ b/webapp/stores/browser_store.jsx @@ -35,18 +35,6 @@ class BrowserStoreClass { this.isSignallingLogin = this.isSignallingLogin.bind(this); } - checkVersion() { - var currentVersion = this.getGlobalItem('storage_version'); - if (currentVersion !== global.window.mm_config.Version) { - this.clearAll(); - try { - this.setGlobalItem('storage_version', global.window.mm_config.Version); - } catch (e) { - // Do nothing - } - } - } - setItem(name, value) { this.setGlobalItem(getPrefix() + name, value); } diff --git a/webapp/stores/error_store.jsx b/webapp/stores/error_store.jsx index 715029185..3e043dd78 100644 --- a/webapp/stores/error_store.jsx +++ b/webapp/stores/error_store.jsx @@ -20,6 +20,12 @@ class ErrorStoreClass extends EventEmitter { this.removeChangeListener = this.removeChangeListener.bind(this); this.getLastError = this.getLastError.bind(this); this.storeLastError = this.storeLastError.bind(this); + this.getIgnoreEmailPreview = this.getIgnoreEmailPreview.bind(this); + this.ignore_email_preview = false; + } + + getIgnoreEmailPreview() { + return this.ignore_email_preview; } emitChange() { @@ -57,6 +63,11 @@ class ErrorStoreClass extends EventEmitter { } clearLastError() { + var lastError = this.getLastError(); + if (lastError && lastError.email_preview) { + this.ignore_email_preview = true; + } + BrowserStore.removeGlobalItem('last_error'); BrowserStore.removeGlobalItem('last_error_conn'); this.emitChange(); diff --git a/webapp/stores/preference_store.jsx b/webapp/stores/preference_store.jsx index fcfd1c426..1a461f39f 100644 --- a/webapp/stores/preference_store.jsx +++ b/webapp/stores/preference_store.jsx @@ -112,4 +112,4 @@ class PreferenceStoreClass extends EventEmitter { const PreferenceStore = new PreferenceStoreClass(); export default PreferenceStore; -window.PreferenceStore = PreferenceStore; +global.window.PreferenceStore = PreferenceStore; diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx index e1fc9167d..356df7b07 100644 --- a/webapp/stores/team_store.jsx +++ b/webapp/stores/team_store.jsx @@ -33,7 +33,14 @@ class TeamStoreClass extends EventEmitter { this.getCurrentInviteLink = this.getCurrentInviteLink.bind(this); this.saveTeam = this.saveTeam.bind(this); + this.clear(); + } + + clear() { this.teams = {}; + this.team_members = []; + this.members_for_team = []; + this.teamListings = {}; this.currentTeamId = ''; } @@ -119,6 +126,34 @@ class TeamStoreClass extends EventEmitter { this.saveTeam(team); this.currentTeamId = team.id; } + + saveTeamMembers(members) { + this.team_members = members; + } + + appendTeamMember(member) { + this.team_members.push(member); + } + + getTeamMembers() { + return this.team_members; + } + + saveMembersForTeam(members) { + this.members_for_team = members; + } + + getMembersForTeam() { + return this.members_for_team; + } + + saveTeamListings(teams) { + this.teamListings = teams; + } + + getTeamListings() { + return this.teamListings; + } } var TeamStore = new TeamStoreClass(); @@ -135,6 +170,18 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => { TeamStore.saveTeams(action.teams); TeamStore.emitChange(); break; + case ActionTypes.RECEIVED_TEAM_MEMBERS: + TeamStore.saveTeamMembers(action.team_members); + TeamStore.emitChange(); + break; + case ActionTypes.RECEIVED_ALL_TEAM_LISTINGS: + TeamStore.saveTeamListings(action.teams); + TeamStore.emitChange(); + break; + case ActionTypes.RECEIVED_MEMBERS_FOR_TEAM: + TeamStore.saveMembersForTeam(action.team_members); + TeamStore.emitChange(); + break; default: } }); diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx index 4213e6e8c..2c6bfade3 100644 --- a/webapp/stores/user_store.jsx +++ b/webapp/stores/user_store.jsx @@ -16,11 +16,17 @@ const CHANGE_EVENT_STATUSES = 'change_statuses'; class UserStoreClass extends EventEmitter { constructor() { super(); + this.clear(); + } + + clear() { this.profiles = {}; + this.direct_profiles = {}; this.statuses = {}; this.sessions = {}; this.audits = {}; this.currentUserId = ''; + this.noAccounts = false; } emitChange(userId) { @@ -116,7 +122,12 @@ class UserStoreClass extends EventEmitter { return this.getCurrentUser(); } - return this.getProfiles()[userId]; + const user = this.getProfiles()[userId]; + if (user) { + return user; + } + + return this.getDirectProfiles()[userId]; } getProfileByUsername(username) { @@ -137,6 +148,14 @@ class UserStoreClass extends EventEmitter { return profileUsernameMap; } + getDirectProfiles() { + return this.direct_profiles; + } + + saveDirectProfiles(profiles) { + this.direct_profiles = profiles; + } + getProfiles() { return this.profiles; } @@ -259,6 +278,14 @@ class UserStoreClass extends EventEmitter { getStatus(id) { return this.getStatuses()[id]; } + + getNoAccounts() { + return this.noAccounts; + } + + setNoAccounts(noAccounts) { + this.noAccounts = noAccounts; + } } var UserStore = new UserStoreClass(); @@ -272,6 +299,10 @@ UserStore.dispatchToken = AppDispatcher.register((payload) => { UserStore.saveProfiles(action.profiles); UserStore.emitChange(); break; + case ActionTypes.RECEIVED_DIRECT_PROFILES: + UserStore.saveDirectProfiles(action.profiles); + UserStore.emitChange(); + break; case ActionTypes.RECEIVED_ME: UserStore.setCurrentUser(action.me); UserStore.emitChange(action.me.id); diff --git a/webapp/tests/client_channel.test.jsx b/webapp/tests/client_channel.test.jsx new file mode 100644 index 000000000..b8374123c --- /dev/null +++ b/webapp/tests/client_channel.test.jsx @@ -0,0 +1,334 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Channels', function() { + this.timeout(100000); + + it('createChannel', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.fakeChannel(); + channel.team_id = TestHelper.basicTeam().id; + TestHelper.basicClient().createChannel( + channel, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.name, channel.name); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('createDirectChannel', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().createUser( + TestHelper.fakeUser(), + function(user2) { + TestHelper.basicClient().addUserToTeam( + user2.id, + function() { + TestHelper.basicClient().createDirectChannel( + user2.id, + function(data) { + assert.equal(data.id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateChannel', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + channel.display_name = 'changed'; + TestHelper.basicClient().updateChannel( + channel, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.display_name, 'changed'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateChannelHeader', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + channel.display_name = 'changed'; + TestHelper.basicClient().updateChannelHeader( + channel.id, + 'new header', + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.header, 'new header'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateChannelPurpose', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + channel.display_name = 'changed'; + TestHelper.basicClient().updateChannelPurpose( + channel.id, + 'new purpose', + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.purpose, 'new purpose'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateChannelNotifyProps', function(done) { + TestHelper.initBasic(() => { + var props = {}; + props.channel_id = TestHelper.basicChannel().id; + props.user_id = TestHelper.basicUser().id; + props.desktop = 'all'; + TestHelper.basicClient().updateChannelNotifyProps( + props, + function(data) { + assert.equal(data.desktop, 'all'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('leaveChannel', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + TestHelper.basicClient().leaveChannel( + channel.id, + function(data) { + assert.equal(data.id, channel.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('joinChannel', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + TestHelper.basicClient().leaveChannel( + channel.id, + function() { + TestHelper.basicClient().joinChannel( + channel.id, + function(data) { + assert.equal(data.id, channel.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('deleteChannel', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + TestHelper.basicClient().deleteChannel( + channel.id, + function(data) { + assert.equal(data.id, channel.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateLastViewedAt', function(done) { + TestHelper.initBasic(() => { + var channel = TestHelper.basicChannel(); + TestHelper.basicClient().updateLastViewedAt( + channel.id, + function(data) { + assert.equal(data.id, channel.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getChannels', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getChannels( + function(data) { + assert.equal(data.channels.length, 3); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getChannel', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getChannel( + TestHelper.basicChannel().id, + function(data) { + assert.equal(TestHelper.basicChannel().id, data.channel.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getMoreChannels', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getMoreChannels( + function(data) { + assert.equal(data.channels.length, 0); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getChannelCounts', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getChannelCounts( + function(data) { + assert.equal(data.counts[TestHelper.basicChannel().id], 1); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getChannelExtraInfo', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getChannelExtraInfo( + TestHelper.basicChannel().id, + 5, + function(data) { + assert.equal(data.member_count, 1); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('addChannelMember', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().createUser( + TestHelper.fakeUser(), + function(user2) { + TestHelper.basicClient().addUserToTeam( + user2.id, + function() { + TestHelper.basicClient().addChannelMember( + TestHelper.basicChannel().id, + user2.id, + function(data) { + assert.equal(data.channel_id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('removeChannelMember', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().removeChannelMember( + TestHelper.basicChannel().id, + TestHelper.basicUser().id, + function(data) { + assert.equal(data.channel_id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_command.test.jsx b/webapp/tests/client_command.test.jsx new file mode 100644 index 000000000..f7f0d2b25 --- /dev/null +++ b/webapp/tests/client_command.test.jsx @@ -0,0 +1,123 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Commands', function() { + this.timeout(100000); + + it('listCommands', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().listCommands( + function(data) { + assert.equal(data.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('listTeamCommands', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().listTeamCommands( + function() { + done(new Error('cmds not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.command.disabled.app_error'); + done(); + } + ); + }); + }); + + it('executeCommand', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().executeCommand( + TestHelper.basicChannel().id, + '/shrug', + null, + function(data) { + assert.equal(data.response_type, 'in_channel'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('addCommand', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var cmd = {}; + cmd.url = 'http://www.gonowhere.com'; + cmd.trigger = '/hello'; + cmd.method = 'P'; + cmd.username = ''; + cmd.icon_url = ''; + cmd.auto_complete = false; + cmd.auto_complete_desc = ''; + cmd.auto_complete_hint = ''; + cmd.display_name = 'Unit Test'; + + TestHelper.basicClient().addCommand( + cmd, + function() { + done(new Error('cmds not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.command.disabled.app_error'); + done(); + } + ); + }); + }); + + it('deleteCommand', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().deleteCommand( + TestHelper.generateId(), + function() { + done(new Error('cmds not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.command.disabled.app_error'); + done(); + } + ); + }); + }); + + it('regenCommandToken', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().regenCommandToken( + TestHelper.generateId(), + function() { + done(new Error('cmds not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.command.disabled.app_error'); + done(); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_general.test.jsx b/webapp/tests/client_general.test.jsx new file mode 100644 index 000000000..870c11257 --- /dev/null +++ b/webapp/tests/client_general.test.jsx @@ -0,0 +1,333 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +var assert = require('assert'); +import TestHelper from './test_helper.jsx'; + +describe('Client.General', function() { + this.timeout(10000); + + it('Admin.getClientConfig', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getClientConfig( + function(data) { + assert.equal(data.SiteName, 'Mattermost'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('Admin.getComplianceReports', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getComplianceReports( + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.saveComplianceReports', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var job = {}; + job.desc = 'desc'; + job.emails = ''; + job.keywords = 'test'; + job.start_at = new Date(); + job.end_at = new Date(); + + TestHelper.basicClient().saveComplianceReports( + job, + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.getLogs', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getLogs( + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.getServerAudits', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getServerAudits( + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.getConfig', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getConfig( + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.getAnalytics', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getAnalytics( + 'standard', + null, + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.getTeamAnalytics', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().getTeamAnalytics( + TestHelper.basicTeam().id, + 'standard', + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.saveConfig', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var config = {}; + config.site_name = 'test'; + + TestHelper.basicClient().saveConfig( + config, + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.testEmail', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var config = {}; + config.site_name = 'test'; + + TestHelper.basicClient().testEmail( + config, + function() { + done(new Error('should need system admin permissions')); + }, + function(err) { + assert.equal(err.id, 'api.context.system_permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.logClientError', function(done) { + TestHelper.initBasic(() => { + var config = {}; + config.site_name = 'test'; + TestHelper.basicClient().logClientError('this is a test'); + done(); + }); + }); + + it('Admin.adminResetMfa', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + TestHelper.basicClient().adminResetMfa( + TestHelper.basicUser().id, + function() { + done(new Error('should need a license')); + }, + function(err) { + assert.equal(err.id, 'api.context.permissions.app_error'); + done(); + } + ); + }); + }); + + it('Admin.adminResetPassword', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var user = TestHelper.basicUser(); + + TestHelper.basicClient().resetPassword( + user.id, + 'new_password', + function() { + throw Error('shouldnt work'); + }, + function(err) { + // this should fail since you're not a system admin + assert.equal(err.id, 'api.context.invalid_param.app_error'); + done(); + } + ); + }); + }); + + it('License.getClientLicenceConfig', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getClientLicenceConfig( + function(data) { + assert.equal(data.IsLicensed, 'false'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('License.removeLicenseFile', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().removeLicenseFile( + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.context.permissions.app_error'); + done(); + } + ); + }); + }); + + /*it('License.uploadLicenseFile', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().uploadLicenseFile( + 'form data', + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.context.permissions.app_error'); + done(); + } + ); + }); + });*/ + + // TODO XXX FIX ME - this test depends on make dist + + // it('General.getTranslations', function(done) { + // TestHelper.initBasic(() => { + // TestHelper.basicClient().getTranslations( + // 'http://localhost:8065/static/i18n/es.json', + // function(data) { + // assert.equal(data['login.or'], 'o'); + // done(); + // }, + // function(err) { + // done(new Error(err.message)); + // } + // ); + // }); + // }); + + it('File.getFileInfo', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + TestHelper.basicClient().getFileInfo( + `/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`, + function(data) { + assert.equal(data.filename, 'filename.txt'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('File.getPublicLink', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var data = {}; + data.channel_id = TestHelper.basicChannel().id; + data.user_id = TestHelper.basicUser().id; + data.filename = `/${TestHelper.basicChannel().id}/${TestHelper.basicUser().id}/filename.txt`; + + TestHelper.basicClient().getPublicLink( + data, + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.file.get_public_link.disabled.app_error'); + done(); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_hooks.test.jsx b/webapp/tests/client_hooks.test.jsx new file mode 100644 index 000000000..0cad22153 --- /dev/null +++ b/webapp/tests/client_hooks.test.jsx @@ -0,0 +1,139 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Hooks', function() { + this.timeout(100000); + + it('addIncomingHook', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var hook = {}; + hook.channel_id = TestHelper.basicChannel().id; + hook.description = 'desc'; + hook.display_name = 'Unit Test'; + + TestHelper.basicClient().addIncomingHook( + hook, + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.create_incoming.disabled.app_errror'); + done(); + } + ); + }); + }); + + it('deleteIncomingHook', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().deleteIncomingHook( + TestHelper.generateId(), + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.delete_incoming.disabled.app_errror'); + done(); + } + ); + }); + }); + + it('listIncomingHooks', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().listIncomingHooks( + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.get_incoming.disabled.app_error'); + done(); + } + ); + }); + }); + + it('addOutgoingHook', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var hook = {}; + hook.channel_id = TestHelper.basicChannel().id; + hook.description = 'desc'; + hook.display_name = 'Unit Test'; + + TestHelper.basicClient().addOutgoingHook( + hook, + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.create_outgoing.disabled.app_error'); + done(); + } + ); + }); + }); + + it('deleteOutgoingHook', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().deleteOutgoingHook( + TestHelper.generateId(), + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.delete_outgoing.disabled.app_error'); + done(); + } + ); + }); + }); + + it('listOutgoingHooks', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().listOutgoingHooks( + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.get_outgoing.disabled.app_error'); + done(); + } + ); + }); + }); + + it('regenOutgoingHookToken', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().regenOutgoingHookToken( + TestHelper.generateId(), + function() { + done(new Error('hooks not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.webhook.regen_outgoing_token.disabled.app_error'); + done(); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_oauth.test.jsx b/webapp/tests/client_oauth.test.jsx new file mode 100644 index 000000000..df2fc665b --- /dev/null +++ b/webapp/tests/client_oauth.test.jsx @@ -0,0 +1,60 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.OAuth', function() { + this.timeout(100000); + + it('registerOAuthApp', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var app = {}; + app.name = 'test'; + app.homepage = 'homepage'; + app.description = 'desc'; + app.callback_urls = ''; + + TestHelper.basicClient().registerOAuthApp( + app, + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.oauth.register_oauth_app.turn_off.app_error'); + done(); + } + ); + }); + }); + + it('allowOAuth2', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + TestHelper.basicClient().allowOAuth2( + 'GET', + '123456', + 'http://nowhere.com', + 'state', + 'scope', + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.oauth.allow_oauth.turn_off.app_error'); + done(); + } + ); + }); + }); +}); diff --git a/webapp/tests/client_post.test.jsx b/webapp/tests/client_post.test.jsx new file mode 100644 index 000000000..db48e4000 --- /dev/null +++ b/webapp/tests/client_post.test.jsx @@ -0,0 +1,207 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Posts', function() { + this.timeout(100000); + + it('createPost', function(done) { + TestHelper.initBasic(() => { + var post = TestHelper.fakePost(); + post.channel_id = TestHelper.basicChannel().id; + + TestHelper.basicClient().createPost( + post, + function(data) { + assert.equal(data.id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPostById', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getPostById( + TestHelper.basicPost().id, + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPost', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getPost( + TestHelper.basicChannel().id, + TestHelper.basicPost().id, + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updatePost', function(done) { + TestHelper.initBasic(() => { + var post = TestHelper.basicPost(); + post.message = 'new message'; + post.channel_id = TestHelper.basicChannel().id; + + TestHelper.basicClient().updatePost( + post, + function(data) { + assert.equal(data.id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('deletePost', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().deletePost( + TestHelper.basicChannel().id, + TestHelper.basicPost().id, + function(data) { + assert.equal(data.id, TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('searchPost', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().search( + 'unit test', + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPostsPage', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getPostsPage( + TestHelper.basicChannel().id, + 0, + 10, + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPosts', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getPosts( + TestHelper.basicChannel().id, + 0, + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPostsBefore', function(done) { + TestHelper.initBasic(() => { + var post = TestHelper.fakePost(); + post.channel_id = TestHelper.basicChannel().id; + + TestHelper.basicClient().createPost( + post, + function(rpost) { + TestHelper.basicClient().getPostsBefore( + TestHelper.basicChannel().id, + rpost.id, + 0, + 10, + function(data) { + assert.equal(data.order[0], TestHelper.basicPost().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPostsAfter', function(done) { + TestHelper.initBasic(() => { + var post = TestHelper.fakePost(); + post.channel_id = TestHelper.basicChannel().id; + + TestHelper.basicClient().createPost( + post, + function(rpost) { + TestHelper.basicClient().getPostsAfter( + TestHelper.basicChannel().id, + TestHelper.basicPost().id, + 0, + 10, + function(data) { + assert.equal(data.order[0], rpost.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_preferences.test.jsx b/webapp/tests/client_preferences.test.jsx new file mode 100644 index 000000000..987728704 --- /dev/null +++ b/webapp/tests/client_preferences.test.jsx @@ -0,0 +1,72 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Preferences', function() { + this.timeout(100000); + + it('getAllPreferences', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getAllPreferences( + function(data) { + assert.equal(data[0].category, 'tutorial_step'); + assert.equal(data[0].user_id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('savePreferences', function(done) { + TestHelper.initBasic(() => { + var perf = {}; + perf.user_id = TestHelper.basicUser().id; + perf.category = 'test'; + perf.name = 'name'; + perf.value = 'value'; + + var perfs = []; + perfs.push(perf); + + TestHelper.basicClient().savePreferences( + perfs, + function(data) { + assert.equal(data, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getPreferenceCategory', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getPreferenceCategory( + 'tutorial_step', + function(data) { + assert.equal(data[0].category, 'tutorial_step'); + assert.equal(data[0].user_id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_team.test.jsx b/webapp/tests/client_team.test.jsx new file mode 100644 index 000000000..e8b71d2f8 --- /dev/null +++ b/webapp/tests/client_team.test.jsx @@ -0,0 +1,235 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.Team', function() { + this.timeout(100000); + + it('findTeamByName', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().findTeamByName( + TestHelper.basicTeam().name, + function(data) { + assert.equal(data, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('signupTeam', function(done) { + var client = TestHelper.createClient(); + var email = TestHelper.fakeEmail(); + + client.signupTeam( + email, + function(data) { + assert.equal(data.email, email); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('createTeamFromSignup', function(done) { + var client = TestHelper.createClient(); + var email = TestHelper.fakeEmail(); + + client.signupTeam( + email, + function(data) { + var teamSignup = {}; + teamSignup.invites = []; + teamSignup.data = decodeURIComponent(data.follow_link.split('&h=')[0].replace('/signup_team_complete/?d=', '')); + teamSignup.hash = decodeURIComponent(data.follow_link.split('&h=')[1]); + + teamSignup.user = TestHelper.fakeUser(); + teamSignup.team = TestHelper.fakeTeam(); + teamSignup.team.email = teamSignup.user.email; + + client.createTeamFromSignup( + teamSignup, + function(data2) { + assert.equal(data2.team.id.length > 0, true); + assert.equal(data2.user.id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('createTeam', function(done) { + var client = TestHelper.createClient(); + var team = TestHelper.fakeTeam(); + client.createTeam( + team, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.name, team.name); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('getAllTeams', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getAllTeams( + function(data) { + assert.equal(data[TestHelper.basicTeam().id].name, TestHelper.basicTeam().name); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getAllTeamListings', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getAllTeamListings( + function(data) { + console.log(data); + assert.equal(data != null, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getMyTeam', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getMyTeam( + function(data) { + assert.equal(data.name, TestHelper.basicTeam().name); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('GetTeamMembers', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getTeamMembers( + TestHelper.basicTeam().id, + function(data) { + assert.equal(data.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('inviteMembers', function(done) { + TestHelper.initBasic(() => { + var data = {}; + data.invites = []; + var invite = {}; + invite.email = TestHelper.fakeEmail(); + invite.firstName = 'first'; + invite.lastName = 'last'; + data.invites.push(invite); + + TestHelper.basicClient().inviteMembers( + data, + function(dataBack) { + assert.equal(dataBack.invites.length, 1); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateTeam', function(done) { + TestHelper.initBasic(() => { + var team = TestHelper.basicTeam(); + team.display_name = 'test_updated'; + + TestHelper.basicClient().updateTeam( + team, + function(data) { + assert.equal(data.display_name, 'test_updated'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('addUserToTeam', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().createUser( + TestHelper.fakeUser(), + function(user2) { + TestHelper.basicClient().addUserToTeam( + user2.id, + function(data) { + assert.equal(data.user_id, user2.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getInviteInfo', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getInviteInfo( + TestHelper.basicTeam().invite_id, + function(data) { + assert.equal(data.display_name.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); +}); + diff --git a/webapp/tests/client_user.test.jsx b/webapp/tests/client_user.test.jsx new file mode 100644 index 000000000..e0ead2de9 --- /dev/null +++ b/webapp/tests/client_user.test.jsx @@ -0,0 +1,550 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +import assert from 'assert'; +import TestHelper from './test_helper.jsx'; + +describe('Client.User', function() { + this.timeout(100000); + + it('getMe', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getMe( + function(data) { + assert.equal(data.id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getInitialLoad', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getInitialLoad( + function(data) { + assert.equal(data.user.id.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('createUser', function(done) { + var client = TestHelper.createClient(); + var user = TestHelper.fakeUser(); + client.createUser( + user, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.email, user.email); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('loginByEmail', function(done) { + var client = TestHelper.createClient(); + var user = TestHelper.fakeUser(); + client.createUser( + user, + function() { + client.login( + user.email, + null, + user.password, + null, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.email, user.email); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('loginByUsername', function(done) { + var client = TestHelper.createClient(); + var user = TestHelper.fakeUser(); + client.createUser( + user, + function() { + client.login( + null, + user.username, + user.password, + null, + function(data) { + assert.equal(data.id.length > 0, true); + assert.equal(data.email, user.email); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('loginByLdap', function(done) { + var client = TestHelper.createClient(); + client.enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var user = TestHelper.fakeUser(); + client.createUser( + user, + function() { + client.loginByLdap( + user.username, + user.password, + null, + function() { + done(new Error()); + }, + function(err) { + assert.equal(err.id, 'api.user.login_ldap.disabled.app_error'); + done(); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + + it('updateUser', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + user.nickname = 'updated'; + + TestHelper.basicClient().updateUser( + user, + function(data) { + assert.equal(data.nickname, 'updated'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updatePassword', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + TestHelper.basicClient().updatePassword( + user.id, + user.password, + 'update_password', + function(data) { + assert.equal(data.user_id, user.id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateUserNotifyProps', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + var notifyProps = { + all: 'true', + channel: 'true', + desktop: 'all', + desktop_sound: 'true', + email: 'false', + first_name: 'false', + mention_keys: '', + user_id: user.id + }; + + TestHelper.basicClient().updateUserNotifyProps( + notifyProps, + function(data) { + assert.equal(data.notify_props.email, 'false'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateRoles', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + TestHelper.basicClient().updateRoles( + user.id, + '', + function(data) { + assert.equal(data.roles, ''); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateActive', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + TestHelper.basicClient().updateActive( + user.id, + false, + function(data) { + assert.equal(data.last_activity_at > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('sendPasswordReset', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + TestHelper.basicClient().sendPasswordReset( + user.email, + function(data) { + assert.equal(data.email, user.email); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('resetPassword', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + TestHelper.basicClient().resetPassword( + '', + 'new_password', + function() { + throw Error('shouldnt work'); + }, + function(err) { + // this should fail since you're not a system admin + assert.equal(err.id, 'api.context.invalid_param.app_error'); + done(); + } + ); + }); + }); + + it('emailToOAuth', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var user = TestHelper.basicUser(); + + TestHelper.basicClient().emailToOAuth( + user.email, + 'new_password', + 'gitlab', + function() { + throw Error('shouldnt work'); + }, + function(err) { + // this should fail since you're not a system admin + assert.equal(err.id, 'api.user.check_user_password.invalid.app_error'); + done(); + } + ); + }); + }); + + it('oauthToEmail', function(done) { + TestHelper.initBasic(() => { + var user = TestHelper.basicUser(); + + TestHelper.basicClient().oauthToEmail( + user.email, + 'new_password', + function(data) { + assert.equal(data.follow_link.length > 0, true); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('emailToLdap', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var user = TestHelper.basicUser(); + + TestHelper.basicClient().emailToLdap( + user.email, + user.password, + 'unknown_id', + 'unknown_pwd', + function() { + throw Error('shouldnt work'); + }, + function(err) { + assert.equal(err.id, 'ent.ldap.do_login.licence_disable.app_error'); + done(); + } + ); + }); + }); + + it('ldapToEmail', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + var user = TestHelper.basicUser(); + + TestHelper.basicClient().ldapToEmail( + user.email, + 'new_password', + 'new_password', + function() { + throw Error('shouldnt work'); + }, + function(err) { + assert.equal(err.id, 'api.user.ldap_to_email.not_ldap_account.app_error'); + done(); + } + ); + }); + }); + + it('logout', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().logout( + function(data) { + assert.equal(data.user_id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('checkMfa', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().checkMfa( + 'email', + TestHelper.generateId(), + function(data) { + assert.equal(data.mfa_required, 'false'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getSessions', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getSessions( + TestHelper.basicUser().id, + function(data) { + assert.equal(data[0].user_id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('revokeSession', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getSessions( + TestHelper.basicUser().id, + function(sessions) { + TestHelper.basicClient().revokeSession( + sessions[0].id, + function(data) { + assert.equal(data.id, sessions[0].id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getAudits', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getAudits( + TestHelper.basicUser().id, + function(data) { + assert.equal(data[0].user_id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getDirectProfiles', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getDirectProfiles( + function(data) { + assert.equal(Object.keys(data).length === 0, true); + done(); + }, + function(err) { + done(new Error(err.getDirectProfiles)); + } + ); + }); + }); + + it('getProfiles', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getProfiles( + function(data) { + assert.equal(data[TestHelper.basicUser().id].id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getProfilesForTeam', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().getProfilesForTeam( + TestHelper.basicTeam().id, + function(data) { + assert.equal(data[TestHelper.basicUser().id].id, TestHelper.basicUser().id); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('getStatuses', function(done) { + TestHelper.initBasic(() => { + var ids = []; + ids.push(TestHelper.basicUser().id); + + TestHelper.basicClient().getStatuses( + ids, + function(data) { + assert.equal(data[TestHelper.basicUser().id], 'online'); + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('verifyEmail', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().verifyEmail( + 'junk', + 'junk', + function() { + done(new Error('should be invalid')); + }, + function(err) { + assert.equal(err.id, 'api.context.invalid_param.app_error'); + done(); + } + ); + }); + }); + + it('resendVerification', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().resendVerification( + TestHelper.basicUser().email, + function() { + done(); + }, + function(err) { + done(new Error(err.message)); + } + ); + }); + }); + + it('updateMfa', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + TestHelper.basicClient().updateMfa( + 'junk', + true, + function() { + done(new Error('not enabled')); + }, + function(err) { + assert.equal(err.id, 'ent.mfa.license_disable.app_error'); + done(); + } + ); + }); + }); +}); diff --git a/webapp/tests/spinner_button.test.jsx b/webapp/tests/spinner_button.test.jsx new file mode 100644 index 000000000..0e282e0ee --- /dev/null +++ b/webapp/tests/spinner_button.test.jsx @@ -0,0 +1,24 @@ +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ + +var jsdom = require('mocha-jsdom'); +var assert = require('assert'); +import TestUtils from 'react-addons-test-utils'; +import SpinnerButton from '../components/spinner_button.jsx'; +import React from 'react'; + +describe('SpinnerButton', function() { + jsdom(); + + it('check props', function() { + const spinner = TestUtils.renderIntoDocument( + <SpinnerButton spinning={false}/> + ); + + assert.equal(spinner.props.spinning, false, 'should start in the default false state'); + }); +});
\ No newline at end of file diff --git a/webapp/tests/test_helper.jsx b/webapp/tests/test_helper.jsx new file mode 100644 index 000000000..385279360 --- /dev/null +++ b/webapp/tests/test_helper.jsx @@ -0,0 +1,183 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +/* eslint-disable no-console */ +/* eslint-disable global-require */ +/* eslint-disable func-names */ +/* eslint-disable prefer-arrow-callback */ +/* eslint-disable no-magic-numbers */ +/* eslint-disable no-unreachable */ +/* eslint-disable new-cap */ + +import Client from '../client/client.jsx'; +import jqd from 'jquery-deferred'; + +class TestHelperClass { + basicClient = () => { + return this.basicc; + } + + basicTeam = () => { + return this.basict; + } + + basicUser = () => { + return this.basicu; + } + + basicChannel = () => { + return this.basicch; + } + + basicPost = () => { + return this.basicp; + } + + generateId = () => { + // implementation taken from http://stackoverflow.com/a/2117523 + var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; + + id = id.replace(/[xy]/g, function replaceRandom(c) { + var r = Math.floor(Math.random() * 16); + + var v; + if (c === 'x') { + v = r; + } else { + v = r & 0x3 | 0x8; + } + + return v.toString(16); + }); + + return 'uid' + id; + } + + createClient() { + var c = new Client(); + c.setUrl('http://localhost:8065'); + c.useHeaderToken(); + c.enableLogErrorsToConsole(true); + return c; + } + + fakeEmail = () => { + return 'success' + this.generateId() + '@simulator.amazonses.com'; + } + + fakeUser = () => { + var user = {}; + user.email = this.fakeEmail(); + user.allow_marketing = true; + user.password = 'password1'; + user.username = this.generateId(); + return user; + } + + fakeTeam = () => { + var team = {}; + team.name = this.generateId(); + team.display_name = `Unit Test ${team.name}`; + team.type = 'O'; + team.email = this.fakeEmail(); + team.allowed_domains = ''; + return team; + } + + fakeChannel = () => { + var channel = {}; + channel.name = this.generateId(); + channel.display_name = `Unit Test ${channel.name}`; + channel.type = 'O'; // open channel + return channel; + } + + fakePost = () => { + var post = {}; + post.message = `Unit Test ${this.generateId()}`; + return post; + } + + initBasic = (callback) => { + this.basicc = this.createClient(); + + var d1 = jqd.Deferred(); + var email = this.fakeEmail(); + var outer = this; // eslint-disable-line consistent-this + + this.basicClient().signupTeam( + email, + function(rsignUp) { + var teamSignup = {}; + teamSignup.invites = []; + teamSignup.data = decodeURIComponent(rsignUp.follow_link.split('&h=')[0].replace('/signup_team_complete/?d=', '')); + teamSignup.hash = decodeURIComponent(rsignUp.follow_link.split('&h=')[1]); + + teamSignup.user = outer.fakeUser(); + teamSignup.team = outer.fakeTeam(); + teamSignup.team.email = email; + teamSignup.user.email = email; + var password = teamSignup.user.password; + + outer.basicClient().createTeamFromSignup( + teamSignup, + function(rteamSignup) { + outer.basict = rteamSignup.team; + outer.basicu = rteamSignup.user; + outer.basicu.password = password; + outer.basicClient().setTeamId(outer.basict.id); + outer.basicClient().login( + rteamSignup.user.email, + null, + password, + null, + function() { + outer.basicClient().useHeaderToken(); + var channel = outer.fakeChannel(); + channel.team_id = outer.basicTeam().id; + outer.basicClient().createChannel( + channel, + function(rchannel) { + outer.basicch = rchannel; + var post = outer.fakePost(); + post.channel_id = rchannel.id; + + outer.basicClient().createPost( + post, + function(rpost) { + outer.basicp = rpost; + d1.resolve(); + }, + function(err) { + throw err; + } + ); + }, + function(err) { + throw err; + } + ); + }, + function(err) { + throw err; + } + ); + }, + function(err) { + throw err; + } + ); + }, + function(err) { + throw err; + } + ); + + jqd.when(d1).done(() => { + callback(); + }); + } +} + +var TestHelper = new TestHelperClass(); +export default TestHelper; diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 80a08dc21..189b159e8 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; -import * as client from './client.jsx'; +import Client from './web_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'; @@ -11,6 +11,7 @@ import PreferenceStore from 'stores/preference_store.jsx'; import PostStore from 'stores/post_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as utils from './utils.jsx'; +import ErrorStore from 'stores/error_store.jsx'; import Constants from './constants.jsx'; const ActionTypes = Constants.ActionTypes; @@ -19,6 +20,8 @@ const StatTypes = Constants.StatTypes; // Used to track in progress async calls const callTracker = {}; +const ASYNC_CLIENT_TIMEOUT = 5000; + export function dispatchError(err, method) { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_ERROR, @@ -36,7 +39,7 @@ function isCallInProgress(callName) { return false; } - if (utils.getTimestamp() - callTracker[callName] > 5000) { + if (utils.getTimestamp() - callTracker[callName] > ASYNC_CLIENT_TIMEOUT) { //console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds'); return false; } @@ -51,16 +54,12 @@ export function getChannels(checkVersion) { callTracker.getChannels = utils.getTimestamp(); - return client.getChannels( - (data, textStatus, xhr) => { + return Client.getChannels( + (data) => { callTracker.getChannels = 0; - if (xhr.status === 304 || !data) { - return; - } - if (checkVersion) { - var serverVersion = xhr.getResponseHeader('X-Version-ID'); + var serverVersion = Client.getServerVersion(); if (serverVersion !== BrowserStore.getLastServerVersion()) { if (!BrowserStore.getLastServerVersion() || BrowserStore.getLastServerVersion() === '') { @@ -93,14 +92,10 @@ export function getChannel(id) { callTracker['getChannel' + id] = utils.getTimestamp(); - client.getChannel(id, - (data, textStatus, xhr) => { + Client.getChannel(id, + (data) => { callTracker['getChannel' + id] = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CHANNEL, channel: data.channel, @@ -131,13 +126,17 @@ export function updateLastViewedAt(id) { } callTracker[`updateLastViewed${channelId}`] = utils.getTimestamp(); - client.updateLastViewedAt( + Client.updateLastViewedAt( channelId, () => { callTracker.updateLastViewed = 0; + ErrorStore.clearLastError(); + ErrorStore.emitChange(); }, (err) => { callTracker.updateLastViewed = 0; + var count = ErrorStore.getConnectionErrorCount(); + ErrorStore.setConnectionErrorCount(count + 1); dispatchError(err, 'updateLastViewedAt'); } ); @@ -150,21 +149,17 @@ export function getMoreChannels(force) { if (ChannelStore.getMoreAll().loading || force) { callTracker.getMoreChannels = utils.getTimestamp(); - client.getMoreChannels( - function getMoreChannelsSuccess(data, textStatus, xhr) { + Client.getMoreChannels( + (data) => { callTracker.getMoreChannels = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_MORE_CHANNELS, channels: data.channels, members: data.members }); }, - function getMoreChannelsFailure(err) { + (err) => { callTracker.getMoreChannels = 0; dispatchError(err, 'getMoreChannels'); } @@ -187,16 +182,12 @@ export function getChannelExtraInfo(id, memberLimit) { callTracker['getChannelExtraInfo_' + channelId] = utils.getTimestamp(); - client.getChannelExtraInfo( + Client.getChannelExtraInfo( channelId, memberLimit, - (data, textStatus, xhr) => { + (data) => { callTracker['getChannelExtraInfo_' + channelId] = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CHANNEL_EXTRA_INFO, extra_info: data @@ -210,53 +201,90 @@ export function getChannelExtraInfo(id, memberLimit) { } } +export function getTeamMembers(teamId) { + if (isCallInProgress('getTeamMembers')) { + return; + } + + callTracker.getTeamMembers = utils.getTimestamp(); + Client.getTeamMembers( + teamId, + (data) => { + callTracker.getTeamMembers = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_MEMBERS_FOR_TEAM, + team_members: data + }); + }, + (err) => { + callTracker.getTeamMembers = 0; + dispatchError(err, 'getTeamMembers'); + } + ); +} + export function getProfiles() { if (isCallInProgress('getProfiles')) { return; } callTracker.getProfiles = utils.getTimestamp(); - client.getProfiles( - function getProfilesSuccess(data, textStatus, xhr) { + Client.getProfiles( + (data) => { callTracker.getProfiles = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_PROFILES, profiles: data }); }, - function getProfilesFailure(err) { + (err) => { callTracker.getProfiles = 0; dispatchError(err, 'getProfiles'); } ); } +export function getDirectProfiles() { + if (isCallInProgress('getDirectProfiles')) { + return; + } + + callTracker.getDirectProfiles = utils.getTimestamp(); + Client.getDirectProfiles( + (data) => { + callTracker.getDirectProfiles = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_DIRECT_PROFILES, + profiles: data + }); + }, + (err) => { + callTracker.getDirectProfiles = 0; + dispatchError(err, 'getDirectProfiles'); + } + ); +} + export function getSessions() { if (isCallInProgress('getSessions')) { return; } callTracker.getSessions = utils.getTimestamp(); - client.getSessions( + Client.getSessions( UserStore.getCurrentId(), - function getSessionsSuccess(data, textStatus, xhr) { + (data) => { callTracker.getSessions = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_SESSIONS, sessions: data }); }, - function getSessionsFailure(err) { + (err) => { callTracker.getSessions = 0; dispatchError(err, 'getSessions'); } @@ -269,21 +297,17 @@ export function getAudits() { } callTracker.getAudits = utils.getTimestamp(); - client.getAudits( + Client.getAudits( UserStore.getCurrentId(), - function getAuditsSuccess(data, textStatus, xhr) { + (data) => { callTracker.getAudits = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_AUDITS, audits: data }); }, - function getAuditsFailure(err) { + (err) => { callTracker.getAudits = 0; dispatchError(err, 'getAudits'); } @@ -296,14 +320,10 @@ export function getLogs() { } callTracker.getLogs = utils.getTimestamp(); - client.getLogs( - (data, textStatus, xhr) => { + Client.getLogs( + (data) => { callTracker.getLogs = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_LOGS, logs: data @@ -322,14 +342,10 @@ export function getServerAudits() { } callTracker.getServerAudits = utils.getTimestamp(); - client.getServerAudits( - (data, textStatus, xhr) => { + Client.getServerAudits( + (data) => { callTracker.getServerAudits = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_SERVER_AUDITS, audits: data @@ -348,14 +364,10 @@ export function getComplianceReports() { } callTracker.getComplianceReports = utils.getTimestamp(); - client.getComplianceReports( - (data, textStatus, xhr) => { + Client.getComplianceReports( + (data) => { callTracker.getComplianceReports = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_SERVER_COMPLIANCE_REPORTS, complianceReports: data @@ -374,14 +386,10 @@ export function getConfig() { } callTracker.getConfig = utils.getTimestamp(); - client.getConfig( - (data, textStatus, xhr) => { + Client.getConfig( + (data) => { callTracker.getConfig = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CONFIG, config: data @@ -400,14 +408,10 @@ export function getAllTeams() { } callTracker.getAllTeams = utils.getTimestamp(); - client.getAllTeams( - (data, textStatus, xhr) => { + Client.getAllTeams( + (data) => { callTracker.getAllTeams = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_ALL_TEAMS, teams: data @@ -420,27 +424,45 @@ export function getAllTeams() { ); } +export function getAllTeamListings() { + if (isCallInProgress('getAllTeamListings')) { + return; + } + + callTracker.getAllTeamListings = utils.getTimestamp(); + Client.getAllTeamListings( + (data) => { + callTracker.getAllTeamListings = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_ALL_TEAM_LISTINGS, + teams: data + }); + }, + (err) => { + callTracker.getAllTeams = 0; + dispatchError(err, 'getAllTeamListings'); + } + ); +} + export function search(terms) { if (isCallInProgress('search_' + String(terms))) { return; } callTracker['search_' + String(terms)] = utils.getTimestamp(); - client.search( + Client.search( terms, - function searchSuccess(data, textStatus, xhr) { + (data) => { callTracker['search_' + String(terms)] = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_SEARCH, results: data }); }, - function searchFailure(err) { + (err) => { callTracker['search_' + String(terms)] = 0; dispatchError(err, 'search'); } @@ -478,15 +500,11 @@ export function getPostsPage(id, maxPosts) { if (channelId != null) { callTracker['getPostsPage_' + channelId] = utils.getTimestamp(); - client.getPostsPage( + Client.getPostsPage( channelId, 0, numPosts, - (data, textStatus, xhr) => { - if (xhr.status === 304 || !data) { - return; - } - + (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POSTS, id: channelId, @@ -536,14 +554,10 @@ export function getPosts(id) { callTracker['getPosts_' + channelId] = utils.getTimestamp(); - client.getPosts( + Client.getPosts( channelId, latestPostTime, - (data, textStatus, xhr) => { - if (xhr.status === 304 || !data) { - return; - } - + (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POSTS, id: channelId, @@ -573,16 +587,12 @@ export function getPostsBefore(postId, offset, numPost) { return; } - client.getPostsBefore( + Client.getPostsBefore( channelId, postId, offset, numPost, - (data, textStatus, xhr) => { - if (xhr.status === 304 || !data) { - return; - } - + (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POSTS, id: channelId, @@ -612,16 +622,12 @@ export function getPostsAfter(postId, offset, numPost) { return; } - client.getPostsAfter( + Client.getPostsAfter( channelId, postId, offset, numPost, - (data, textStatus, xhr) => { - if (xhr.status === 304 || !data) { - return; - } - + (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_POSTS, id: channelId, @@ -647,14 +653,10 @@ export function getMe() { } callTracker.getMe = utils.getTimestamp(); - return client.getMe( - (data, textStatus, xhr) => { + return Client.getMe( + (data) => { callTracker.getMe = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_ME, me: data @@ -684,14 +686,10 @@ export function getStatuses() { } callTracker.getStatuses = utils.getTimestamp(); - client.getStatuses(teammateIds, - (data, textStatus, xhr) => { + Client.getStatuses(teammateIds, + (data) => { callTracker.getStatuses = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_STATUSES, statuses: data @@ -710,20 +708,16 @@ export function getMyTeam() { } callTracker.getMyTeam = utils.getTimestamp(); - return client.getMyTeam( - function getMyTeamSuccess(data, textStatus, xhr) { + return Client.getMyTeam( + (data) => { callTracker.getMyTeam = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_MY_TEAM, team: data }); }, - function getMyTeamFailure(err) { + (err) => { callTracker.getMyTeam = 0; dispatchError(err, 'getMyTeam'); } @@ -736,14 +730,10 @@ export function getAllPreferences() { } callTracker.getAllPreferences = utils.getTimestamp(); - client.getAllPreferences( - (data, textStatus, xhr) => { + Client.getAllPreferences( + (data) => { callTracker.getAllPreferences = 0; - if (xhr.status === 304 || !data) { - return; - } - AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_PREFERENCES, preferences: data @@ -768,15 +758,13 @@ export function savePreference(category, name, value, success, error) { } export function savePreferences(preferences, success, error) { - client.savePreferences( + Client.savePreferences( preferences, - (data, textStatus, xhr) => { - if (xhr.status !== 304) { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_PREFERENCES, - preferences - }); - } + (data) => { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_PREFERENCES, + preferences + }); if (success) { success(data); @@ -793,12 +781,12 @@ export function savePreferences(preferences, success, error) { } export function getSuggestedCommands(command, suggestionId, component) { - client.listCommands( + Client.listCommands( (data) => { var matches = []; data.forEach((cmd) => { if (('/' + cmd.trigger).indexOf(command) === 0) { - let s = '/' + cmd.trigger; + const s = '/' + cmd.trigger; let hint = ''; if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) { hint = cmd.auto_complete_hint; @@ -842,7 +830,7 @@ export function getFileInfo(filename) { callTracker[callName] = utils.getTimestamp(); - client.getFileInfo( + Client.getFileInfo( filename, (data) => { callTracker[callName] = 0; @@ -870,7 +858,7 @@ export function getStandardAnalytics(teamId) { callTracker[callName] = utils.getTimestamp(); - client.getAnalytics( + Client.getAnalytics( 'standard', teamId, (data) => { @@ -923,7 +911,7 @@ export function getAdvancedAnalytics(teamId) { callTracker[callName] = utils.getTimestamp(); - client.getAnalytics( + Client.getAnalytics( 'extra_counts', teamId, (data) => { @@ -980,7 +968,7 @@ export function getPostsPerDayAnalytics(teamId) { callTracker[callName] = utils.getTimestamp(); - client.getAnalytics( + Client.getAnalytics( 'post_counts_day', teamId, (data) => { @@ -1014,7 +1002,7 @@ export function getUsersPerDayAnalytics(teamId) { callTracker[callName] = utils.getTimestamp(); - client.getAnalytics( + Client.getAnalytics( 'user_counts_with_posts_day', teamId, (data) => { @@ -1048,7 +1036,7 @@ export function getRecentAndNewUsersAnalytics(teamId) { callTracker[callName] = utils.getTimestamp(); - client.getProfilesForTeam( + Client.getProfilesForTeam( teamId, (users) => { const stats = {}; @@ -1129,7 +1117,7 @@ export function listIncomingHooks() { callTracker.listIncomingHooks = utils.getTimestamp(); - client.listIncomingHooks( + Client.listIncomingHooks( (data) => { callTracker.listIncomingHooks = 0; @@ -1152,7 +1140,7 @@ export function listOutgoingHooks() { callTracker.listOutgoingHooks = utils.getTimestamp(); - client.listOutgoingHooks( + Client.listOutgoingHooks( (data) => { callTracker.listOutgoingHooks = 0; @@ -1169,7 +1157,7 @@ export function listOutgoingHooks() { } export function addIncomingHook(hook, success, error) { - client.addIncomingHook( + Client.addIncomingHook( hook, (data) => { AppDispatcher.handleServerAction({ @@ -1192,7 +1180,7 @@ export function addIncomingHook(hook, success, error) { } export function addOutgoingHook(hook, success, error) { - client.addOutgoingHook( + Client.addOutgoingHook( hook, (data) => { AppDispatcher.handleServerAction({ @@ -1215,8 +1203,8 @@ export function addOutgoingHook(hook, success, error) { } export function deleteIncomingHook(id) { - client.deleteIncomingHook( - {id}, + Client.deleteIncomingHook( + id, () => { AppDispatcher.handleServerAction({ type: ActionTypes.REMOVED_INCOMING_WEBHOOK, @@ -1230,8 +1218,8 @@ export function deleteIncomingHook(id) { } export function deleteOutgoingHook(id) { - client.deleteOutgoingHook( - {id}, + Client.deleteOutgoingHook( + id, () => { AppDispatcher.handleServerAction({ type: ActionTypes.REMOVED_OUTGOING_WEBHOOK, @@ -1245,8 +1233,8 @@ export function deleteOutgoingHook(id) { } export function regenOutgoingHookToken(id) { - client.regenOutgoingHookToken( - {id}, + Client.regenOutgoingHookToken( + id, (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.UPDATED_OUTGOING_WEBHOOK, @@ -1266,7 +1254,7 @@ export function listTeamCommands() { callTracker.listTeamCommands = utils.getTimestamp(); - client.listTeamCommands( + Client.listTeamCommands( (data) => { callTracker.listTeamCommands = 0; @@ -1283,7 +1271,7 @@ export function listTeamCommands() { } export function addCommand(command, success, error) { - client.addCommand( + Client.addCommand( command, (data) => { AppDispatcher.handleServerAction({ @@ -1306,8 +1294,8 @@ export function addCommand(command, success, error) { } export function deleteCommand(id) { - client.deleteCommand( - {id}, + Client.deleteCommand( + id, () => { AppDispatcher.handleServerAction({ type: ActionTypes.REMOVED_COMMAND, @@ -1321,8 +1309,8 @@ export function deleteCommand(id) { } export function regenCommandToken(id) { - client.regenCommandToken( - {id}, + Client.regenCommandToken( + id, (data) => { AppDispatcher.handleServerAction({ type: ActionTypes.UPDATED_COMMAND, diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx index ddd615581..1d18e26ba 100644 --- a/webapp/utils/channel_intro_messages.jsx +++ b/webapp/utils/channel_intro_messages.jsx @@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import Constants from 'utils/constants.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; +import Client from 'utils/web_client.jsx'; import React from 'react'; import {FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl'; @@ -40,7 +41,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} + src={Client.getUsersRoute() + '/' + teammate.id + '/image?time=' + teammate.update_at} height='50' width='50' /> diff --git a/webapp/utils/client.jsx b/webapp/utils/client.jsx deleted file mode 100644 index 687d47da4..000000000 --- a/webapp/utils/client.jsx +++ /dev/null @@ -1,1759 +0,0 @@ -// See License.txt for license information. - -import BrowserStore from 'stores/browser_store.jsx'; -import $ from 'jquery'; - -import {browserHistory} from 'react-router'; - -let translations = { - connectionError: 'There appears to be a problem with your internet connection.', - unknownError: 'We received an unexpected status code from the server.' -}; - -export function setTranslations(messages) { - translations = messages; -} - -export function track(category, action, label, property, value) { - global.window.analytics.track(action, {category, label, property, value}); -} - -export function trackPage() { - global.window.analytics.page(); -} - -function handleError(methodName, xhr, status, err) { - var e = null; - try { - e = JSON.parse(xhr.responseText); - } catch (parseError) { - e = null; - } - - var msg = ''; - - if (e) { - msg = 'method=' + methodName + ' msg=' + e.message + ' detail=' + e.detailed_error + ' rid=' + e.request_id; - } else { - msg = 'method=' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; - - if (xhr.status === 0) { - e = {message: translations.connectionError}; - } else { - e = {message: translations.unknownError + ' (' + xhr.status + ')'}; - } - } - - console.error(msg); //eslint-disable-line no-console - console.error(e); //eslint-disable-line no-console - - track('api', 'api_weberror', methodName, 'message', msg); - - if (xhr.status === 401) { - const team = window.location.pathname.split('/')[1]; - browserHistory.push('/' + team + '/login?extra=expired&redirect=' + encodeURIComponent(window.location.pathname + window.location.search)); - } - - return e; -} - -export function getTranslations(url, success, error) { - $.ajax({ - url: url, - dataType: 'json', - success, - error: function onError(xhr, status, err) { - var e = handleError('getTranslations', xhr, status, err); - error(e); - } - }); -} - -export function createTeamFromSignup(teamSignup, success, error) { - $.ajax({ - url: '/api/v1/teams/create_from_signup', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(teamSignup), - success, - error: function onError(xhr, status, err) { - var e = handleError('createTeamFromSignup', xhr, status, err); - error(e); - } - }); -} - -export function createTeamWithLdap(teamSignup, success, error) { - $.ajax({ - url: '/api/v1/teams/create_with_ldap', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(teamSignup), - success, - error: function onError(xhr, status, err) { - var e = handleError('createTeamFromSignup', xhr, status, err); - error(e); - } - }); -} - -export function createTeamWithSSO(team, service, success, error) { - $.ajax({ - url: '/api/v1/teams/create_with_sso/' + service, - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(team), - success, - error: function onError(xhr, status, err) { - var e = handleError('createTeamWithSSO', xhr, status, err); - error(e); - } - }); -} - -export function createUser(user, data, emailHash, success, error) { - $.ajax({ - url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash), - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(user), - success, - error: function onError(xhr, status, err) { - var e = handleError('createUser', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_create', user.team_id, 'email', user.email); -} - -export function updateUser(user, success, error) { - $.ajax({ - url: '/api/v1/users/update', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(user), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateUser', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_update'); -} - -export function updatePassword(data, success, error) { - $.ajax({ - url: '/api/v1/users/newpassword', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('newPassword', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_newpassword'); -} - -export function updateUserNotifyProps(data, success, error) { - $.ajax({ - url: '/api/v1/users/update_notify', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateUserNotifyProps', xhr, status, err); - error(e); - } - }); -} - -export function updateRoles(data, success, error) { - $.ajax({ - url: '/api/v1/users/update_roles', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateRoles', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_update_roles'); -} - -export function updateActive(userId, active, success, error) { - var data = {}; - data.user_id = userId; - data.active = '' + active; - - $.ajax({ - url: '/api/v1/users/update_active', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateActive', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_update_roles'); -} - -export function sendPasswordReset(data, success, error) { - $.ajax({ - url: '/api/v1/users/send_password_reset', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('sendPasswordReset', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_send_password_reset'); -} - -export function resetPassword(data, success, error) { - $.ajax({ - url: '/api/v1/users/reset_password', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('resetPassword', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_reset_password'); -} - -export function emailToOAuth(data, success, error) { - $.ajax({ - url: '/api/v1/users/claim/email_to_oauth', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('emailToOAuth', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_email_to_oauth'); -} - -export function oauthToEmail(data, success, error) { - $.ajax({ - url: '/api/v1/users/claim/oauth_to_email', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('oauthToEmail', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_oauth_to_email'); -} - -export function emailToLDAP(data, success, error) { - $.ajax({ - url: '/api/v1/users/claim/email_to_ldap', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('emailToLDAP', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_email_to_ldap'); -} - -export function ldapToEmail(data, success, error) { - $.ajax({ - url: '/api/v1/users/claim/ldap_to_email', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('ldapToEmail', xhr, status, err); - error(e); - } - }); - - track('api', 'api_users_ldap_to_email'); -} - -export function logout(success, error) { - track('api', 'api_users_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 checkMfa(method, team, loginId, success, error) { - $.ajax({ - url: '/api/v1/users/mfa', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({method, team_name: team, login_id: loginId}), - success, - error: function onError(xhr, status, err) { - var e = handleError('checkMfa', xhr, status, err); - error(e); - } - }); -} - -export function loginByEmail(name, email, password, token, success, error) { - $.ajax({ - url: '/api/v1/users/login', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({name, email, password, token}), - success: function onSuccess(data, textStatus, xhr) { - track('api', 'api_users_login_success', data.team_id, 'email', data.email); - sessionStorage.removeItem(data.id + '_last_error'); - BrowserStore.signalLogin(); - success(data, textStatus, xhr); - }, - error: function onError(xhr, status, err) { - track('api', 'api_users_login_fail', name, 'email', email); - - var e = handleError('loginByEmail', xhr, status, err); - error(e); - } - }); -} - -export function loginByUsername(name, username, password, success, error) { - $.ajax({ - url: '/api/v1/users/login', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({name, username, password}), - success: function onSuccess(data, textStatus, xhr) { - track('api', 'api_users_login_success', data.team_id, 'username', data.username); - sessionStorage.removeItem(data.id + '_last_error'); - BrowserStore.signalLogin(); - success(data, textStatus, xhr); - }, - error: function onError(xhr, status, err) { - track('api', 'api_users_login_fail', name, 'username', username); - - var e = handleError('loginByUsername', xhr, status, err); - error(e); - } - }); -} - -export function loginByLdap(teamName, id, password, token, success, error) { - $.ajax({ - url: '/api/v1/users/login_ldap', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({teamName, id, password, token}), - success: function onSuccess(data, textStatus, xhr) { - track('api', 'api_users_loginLdap_success', data.team_id, 'id', id); - sessionStorage.removeItem(data.id + '_last_error'); - BrowserStore.signalLogin(); - success(data, textStatus, xhr); - }, - error: function onError(xhr, status, err) { - track('api', 'api_users_loginLdap_fail', teamName, 'id', id); - - var e = handleError('loginByLdap', xhr, status, err); - error(e); - } - }); -} - -export function revokeSession(altId, success, error) { - $.ajax({ - url: '/api/v1/users/revoke_session', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({id: altId}), - success, - error: function onError(xhr, status, err) { - var e = handleError('revokeSession', xhr, status, err); - error(e); - } - }); -} - -export function getSessions(userId, success, error) { - $.ajax({ - cache: false, - url: '/api/v1/users/' + userId + '/sessions', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getSessions', xhr, status, err); - error(e); - } - }); -} - -export function getAudits(userId, success, error) { - $.ajax({ - url: '/api/v1/users/' + userId + '/audits', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getAudits', xhr, status, err); - error(e); - } - }); -} - -export function getComplianceReports(success, error) { - $.ajax({ - url: '/api/v1/admin/compliance_reports', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getComplianceReports', xhr, status, err); - error(e); - } - }); -} - -export function saveComplianceReports(job, success, error) { - $.ajax({ - url: '/api/v1/admin/save_compliance_report', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(job), - success, - error: (xhr, status, err) => { - var e = handleError('saveComplianceReports', xhr, status, err); - error(e); - } - }); -} - -export function getLogs(success, error) { - $.ajax({ - url: '/api/v1/admin/logs', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getLogs', xhr, status, err); - error(e); - } - }); -} - -export function getServerAudits(success, error) { - $.ajax({ - url: '/api/v1/admin/audits', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getServerAudits', xhr, status, err); - error(e); - } - }); -} - -export function getConfig(success, error) { - return $.ajax({ - url: '/api/v1/admin/config', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getConfig', xhr, status, err); - error(e); - } - }); -} - -export function getAnalytics(name, teamId, success, error) { - let url = '/api/v1/admin/analytics/'; - if (teamId == null) { - url += name; - } else { - url += teamId + '/' + name; - } - $.ajax({ - url, - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('getSystemAnalytics', xhr, status, err); - error(e); - } - }); -} - -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', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(config), - success, - error: function onError(xhr, status, err) { - var e = handleError('saveConfig', xhr, status, err); - error(e); - } - }); -} - -export function logClientError(msg) { - var l = {}; - l.level = 'ERROR'; - l.message = msg; - - $.ajax({ - url: '/api/v1/admin/log_client', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(l) - }); -} - -export function testEmail(config, success, error) { - $.ajax({ - url: '/api/v1/admin/test_email', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(config), - success, - error: function onError(xhr, status, err) { - var e = handleError('testEmail', xhr, status, err); - error(e); - } - }); -} - -export function getAllTeams(success, error) { - $.ajax({ - url: '/api/v1/teams/all', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getAllTeams', xhr, status, err); - error(e); - } - }); -} - -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({ - cache: false, - url: '/api/v1/users/me', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success: function gotUser(data, textStatus, xhr) { - currentUser = data; - if (success) { - success(data, textStatus, xhr); - } - }, - error: function onError(xhr, status, err) { - if (error) { - var e = handleError('getMe', xhr, status, err); - error(e); - } - } - }); - - return currentUser; -} - -export function inviteMembers(data, success, error) { - $.ajax({ - url: '/api/v1/teams/invite_members', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('inviteMembers', xhr, status, err); - error(e); - } - }); - - track('api', 'api_teams_invite_members'); -} - -export function updateTeam(team, success, error) { - $.ajax({ - url: '/api/v1/teams/update', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(team), - success, - error: (xhr, status, err) => { - var e = handleError('updateTeam', xhr, status, err); - error(e); - } - }); - - track('api', 'api_teams_update_name'); -} - -export function signupTeam(email, success, error) { - $.ajax({ - url: '/api/v1/teams/signup', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({email: email}), - success, - error: function onError(xhr, status, err) { - var e = handleError('singupTeam', xhr, status, err); - error(e); - } - }); - - track('api', 'api_teams_signup'); -} - -export function createTeam(team, success, error) { - $.ajax({ - url: '/api/v1/teams/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(team), - success, - error: function onError(xhr, status, err) { - var e = handleError('createTeam', xhr, status, err); - error(e); - } - }); -} - -export function findTeamByName(teamName, success, error) { - $.ajax({ - url: '/api/v1/teams/find_team_by_name', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({name: teamName}), - success, - error: function onError(xhr, status, err) { - var e = handleError('findTeamByName', xhr, status, err); - error(e); - } - }); -} - -export function createChannel(channel, success, error) { - $.ajax({ - url: '/api/v1/channels/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(channel), - success, - error: function onError(xhr, status, err) { - var e = handleError('createChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_create'); -} - -export function createDirectChannel(channel, userId, success, error) { - $.ajax({ - url: '/api/v1/channels/create_direct', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({user_id: userId}), - success, - error: function onError(xhr, status, err) { - var e = handleError('createDirectChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_create_direct'); -} - -export function updateChannel(channel, success, error) { - $.ajax({ - url: '/api/v1/channels/update', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(channel), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_update'); -} - -export function updateChannelHeader(channelId, header, success, error) { - const data = { - channel_id: channelId, - channel_header: header - }; - - $.ajax({ - url: '/api/v1/channels/update_header', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateChannelHeader', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_header'); -} - -export function updateChannelPurpose(data, success, error) { - $.ajax({ - url: '/api/v1/channels/update_purpose', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateChannelPurpose', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_purpose'); -} - -export function updateNotifyProps(data, success, error) { - $.ajax({ - url: '/api/v1/channels/update_notify_props', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('updateNotifyProps', xhr, status, err); - error(e); - } - }); -} - -export function joinChannel(id, success, error) { - $.ajax({ - url: '/api/v1/channels/' + id + '/join', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - success, - error: function onError(xhr, status, err) { - var e = handleError('joinChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_join'); -} - -export function leaveChannel(id, success, error) { - $.ajax({ - url: '/api/v1/channels/' + id + '/leave', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - success, - error: function onError(xhr, status, err) { - var e = handleError('leaveChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_leave'); -} - -export function deleteChannel(id, success, error) { - $.ajax({ - url: '/api/v1/channels/' + id + '/delete', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - success, - error: function onError(xhr, status, err) { - var e = handleError('deleteChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_delete'); -} - -export function updateLastViewedAt(channelId, success, error) { - $.ajax({ - url: '/api/v1/channels/' + channelId + '/update_last_viewed_at', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - success, - error: function onError(xhr, status, err) { - var e = handleError('updateLastViewedAt', xhr, status, err); - error(e); - } - }); -} - -export function getChannels(success, error) { - return $.ajax({ - cache: false, - url: '/api/v1/channels/', - dataType: 'json', - type: 'GET', - success, - ifModified: true, - error: function onError(xhr, status, err) { - var e = handleError('getChannels', xhr, status, err); - error(e); - } - }); -} - -export function getChannel(id, success, error) { - $.ajax({ - cache: false, - url: '/api/v1/channels/' + id + '/', - dataType: 'json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getChannel', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channel_get'); -} - -export function getMoreChannels(success, error) { - $.ajax({ - url: '/api/v1/channels/more', - dataType: 'json', - type: 'GET', - success, - ifModified: true, - error: function onError(xhr, status, err) { - var e = handleError('getMoreChannels', xhr, status, err); - error(e); - } - }); -} - -export function getChannelCounts(success, error) { - $.ajax({ - cache: false, - url: '/api/v1/channels/counts', - dataType: 'json', - type: 'GET', - success, - ifModified: true, - error: function onError(xhr, status, err) { - var e = handleError('getChannelCounts', xhr, status, err); - error(e); - } - }); -} - -export function getChannelExtraInfo(id, memberLimit, success, error) { - let url = '/api/v1/channels/' + id + '/extra_info'; - - if (memberLimit) { - url += '/' + memberLimit; - } - - return $.ajax({ - url, - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getChannelExtraInfo', xhr, status, err); - error(e); - } - }); -} - -export function executeCommand(channelId, command, suggest, success, error) { - $.ajax({ - url: '/api/v1/commands/execute', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify({channelId, command, suggest: '' + suggest}), - success, - error: function onError(xhr, status, err) { - var e = handleError('executeCommand', xhr, status, err); - error(e); - } - }); -} - -export function addCommand(cmd, success, error) { - $.ajax({ - url: '/api/v1/commands/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(cmd), - success, - error: (xhr, status, err) => { - var e = handleError('addCommand', xhr, status, err); - error(e); - } - }); -} - -export function deleteCommand(data, success, error) { - $.ajax({ - url: '/api/v1/commands/delete', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('deleteCommand', xhr, status, err); - error(e); - } - }); -} - -export function listTeamCommands(success, error) { - $.ajax({ - url: '/api/v1/commands/list_team_commands', - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('listTeamCommands', xhr, status, err); - error(e); - } - }); -} - -export function regenCommandToken(data, success, error) { - $.ajax({ - url: '/api/v1/commands/regen_token', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('regenCommandToken', xhr, status, err); - error(e); - } - }); -} - -export function listCommands(success, error) { - $.ajax({ - url: '/api/v1/commands/list', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('listCommands', xhr, status, err); - error(e); - } - }); -} - -export function getPostsPage(channelId, offset, limit, success, error, complete) { - $.ajax({ - cache: false, - url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit, - dataType: 'json', - type: 'GET', - ifModified: true, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPosts', xhr, status, err); - error(e); - }, - complete: complete - }); -} - -export function getPosts(channelId, since, success, error, complete) { - return $.ajax({ - url: '/api/v1/channels/' + channelId + '/posts/' + since, - dataType: 'json', - type: 'GET', - ifModified: true, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPosts', xhr, status, err); - error(e); - }, - complete: complete - }); -} - -export function getPostsBefore(channelId, post, offset, numPost, success, error, complete) { - $.ajax({ - url: '/api/v1/channels/' + channelId + '/post/' + post + '/before/' + offset + '/' + numPost, - dataType: 'json', - type: 'GET', - ifModified: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPostsBefore', xhr, status, err); - error(e); - }, - complete: complete - }); -} - -export function getPostsAfter(channelId, post, offset, numPost, success, error, complete) { - $.ajax({ - url: '/api/v1/channels/' + channelId + '/post/' + post + '/after/' + offset + '/' + numPost, - dataType: 'json', - type: 'GET', - ifModified: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPostsAfter', xhr, status, err); - error(e); - }, - complete: complete - }); -} - -export function getPost(channelId, postId, success, error, complete) { - $.ajax({ - cache: false, - url: '/api/v1/channels/' + channelId + '/post/' + postId, - dataType: 'json', - type: 'GET', - ifModified: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPost', xhr, status, err); - error(e); - }, - complete - }); -} - -export function getPostById(postId, success, error, complete) { - $.ajax({ - cache: false, - url: '/api/v1/posts/' + postId, - dataType: 'json', - type: 'GET', - ifModified: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('getPostById', xhr, status, err); - error(e); - }, - complete - }); -} - -export function search(terms, success, error) { - $.ajax({ - url: '/api/v1/posts/search', - dataType: 'json', - type: 'GET', - data: {terms: terms}, - success, - error: function onError(xhr, status, err) { - var e = handleError('search', xhr, status, err); - error(e); - } - }); - - track('api', 'api_posts_search'); -} - -export function deletePost(channelId, id, success, error) { - $.ajax({ - url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - success, - error: function onError(xhr, status, err) { - var e = handleError('deletePost', xhr, status, err); - error(e); - } - }); - - track('api', 'api_posts_delete'); -} - -export function createPost(post, channel, success, error) { - $.ajax({ - url: '/api/v1/channels/' + post.channel_id + '/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(post), - success, - error: function onError(xhr, status, err) { - var e = handleError('createPost', xhr, status, err); - error(e); - } - }); - - track('api', 'api_posts_create', channel.name, 'length', post.message.length); - - // global.window.analytics.track('api_posts_create', { - // category: 'api', - // channel_name: channel.name, - // channel_type: channel.type, - // length: post.message.length, - // files: (post.filenames || []).length, - // mentions: (post.message.match('/<mention>/g') || []).length - // }); -} - -export function updatePost(post, success, error) { - $.ajax({ - url: '/api/v1/channels/' + post.channel_id + '/update', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(post), - success, - error: function onError(xhr, status, err) { - var e = handleError('updatePost', xhr, status, err); - error(e); - } - }); - - track('api', 'api_posts_update'); -} - -export function addChannelMember(id, data, success, error) { - $.ajax({ - url: '/api/v1/channels/' + id + '/add', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('addChannelMember', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_add_member'); -} - -export function removeChannelMember(id, data, success, error) { - $.ajax({ - url: '/api/v1/channels/' + id + '/remove', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('removeChannelMember', xhr, status, err); - error(e); - } - }); - - track('api', 'api_channels_remove_member'); -} - -export function getProfiles(success, error) { - $.ajax({ - cache: false, - url: '/api/v1/users/profiles', - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - ifModified: true, - error: function onError(xhr, status, err) { - var e = handleError('getProfiles', xhr, status, err); - error(e); - } - }); -} - -export function getProfilesForTeam(teamId, success, error) { - $.ajax({ - cache: false, - url: '/api/v1/users/profiles/' + teamId, - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: function onError(xhr, status, err) { - var e = handleError('getProfilesForTeam', xhr, status, err); - error(e); - } - }); -} - -export function uploadFile(formData, success, error) { - var request = $.ajax({ - url: '/api/v1/files/upload', - type: 'POST', - data: formData, - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - if (err !== 'abort') { - var e = handleError('uploadFile', xhr, status, err); - error(e); - } - } - }); - - track('api', 'api_files_upload'); - - return request; -} - -export function getFileInfo(filename, success, error) { - $.ajax({ - url: '/api/v1/files/get_info' + filename, - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success: (data) => { - success(data); - }, - error: function onError(xhr, status, err) { - var e = handleError('getFileInfo', xhr, status, err); - error(e); - } - }); -} - -export function getPublicLink(data, success, error) { - $.ajax({ - url: '/api/v1/files/get_public_link', - dataType: 'json', - type: 'POST', - data: JSON.stringify(data), - success, - error: function onError(xhr, status, err) { - var e = handleError('getPublicLink', xhr, status, err); - error(e); - } - }); -} - -export function uploadProfileImage(imageData, success, error) { - $.ajax({ - url: '/api/v1/users/newimage', - type: 'POST', - data: imageData, - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('uploadProfileImage', xhr, status, err); - error(e); - } - }); -} - -export function importSlack(fileData, success, error) { - $.ajax({ - url: '/api/v1/teams/import_team', - type: 'POST', - data: fileData, - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('importTeam', xhr, status, err); - error(e); - } - }); -} - -export function exportTeam(success, error) { - $.ajax({ - url: '/api/v1/teams/export_team', - type: 'GET', - dataType: 'json', - success, - error: function onError(xhr, status, err) { - var e = handleError('exportTeam', xhr, status, err); - error(e); - } - }); -} - -export function getStatuses(ids, success, error) { - $.ajax({ - url: '/api/v1/users/status', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(ids), - success, - error: function onError(xhr, status, err) { - var e = handleError('getStatuses', xhr, status, err); - error(e); - } - }); -} - -export function getMyTeam(success, error) { - return $.ajax({ - url: '/api/v1/teams/me', - dataType: 'json', - type: 'GET', - success, - ifModified: true, - error: function onError(xhr, status, err) { - var e = handleError('getMyTeam', xhr, status, err); - error(e); - } - }); -} - -export function registerOAuthApp(app, success, error) { - $.ajax({ - url: '/api/v1/oauth/register', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(app), - success: success, - error: (xhr, status, err) => { - const e = handleError('registerApp', xhr, status, err); - error(e); - } - }); - - module.exports.track('api', 'api_apps_register'); -} - -export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) { - $.ajax({ - url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state, - dataType: 'json', - contentType: 'application/json', - type: 'GET', - success, - error: (xhr, status, err) => { - const e = handleError('allowOAuth2', xhr, status, err); - error(e); - } - }); - - module.exports.track('api', 'api_users_allow_oauth2'); -} - -export function addIncomingHook(hook, success, error) { - $.ajax({ - url: '/api/v1/hooks/incoming/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(hook), - success, - error: (xhr, status, err) => { - var e = handleError('addIncomingHook', xhr, status, err); - error(e); - } - }); -} - -export function deleteIncomingHook(data, success, error) { - $.ajax({ - url: '/api/v1/hooks/incoming/delete', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('deleteIncomingHook', xhr, status, err); - error(e); - } - }); -} - -export function listIncomingHooks(success, error) { - $.ajax({ - url: '/api/v1/hooks/incoming/list', - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('listIncomingHooks', xhr, status, err); - error(e); - } - }); -} - -export function getAllPreferences(success, error) { - return $.ajax({ - url: '/api/v1/preferences/', - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('getAllPreferences', xhr, status, err); - error(e); - } - }); -} - -export function getPreferenceCategory(category, success, error) { - $.ajax({ - url: `/api/v1/preferences/${category}`, - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('getPreferenceCategory', xhr, status, err); - error(e); - } - }); -} - -export function savePreferences(preferences, success, error) { - $.ajax({ - url: '/api/v1/preferences/save', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(preferences), - success, - error: (xhr, status, err) => { - var e = handleError('savePreferences', xhr, status, err); - error(e); - } - }); -} - -export function addOutgoingHook(hook, success, error) { - $.ajax({ - url: '/api/v1/hooks/outgoing/create', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(hook), - success, - error: (xhr, status, err) => { - var e = handleError('addOutgoingHook', xhr, status, err); - error(e); - } - }); -} - -export function deleteOutgoingHook(data, success, error) { - $.ajax({ - url: '/api/v1/hooks/outgoing/delete', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('deleteOutgoingHook', xhr, status, err); - error(e); - } - }); -} - -export function listOutgoingHooks(success, error) { - $.ajax({ - url: '/api/v1/hooks/outgoing/list', - dataType: 'json', - type: 'GET', - success, - error: (xhr, status, err) => { - var e = handleError('listOutgoingHooks', xhr, status, err); - error(e); - } - }); -} - -export function regenOutgoingHookToken(data, success, error) { - $.ajax({ - url: '/api/v1/hooks/outgoing/regen_token', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('regenOutgoingHookToken', xhr, status, err); - error(e); - } - }); -} - -export function uploadLicenseFile(formData, success, error) { - $.ajax({ - url: '/api/v1/license/add', - type: 'POST', - data: formData, - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('uploadLicenseFile', xhr, status, err); - error(e); - } - }); - - track('api', 'api_license_upload'); -} - -export function removeLicenseFile(success, error) { - $.ajax({ - url: '/api/v1/license/remove', - type: 'POST', - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('removeLicenseFile', xhr, status, err); - error(e); - } - }); - - 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); - } - } - }); -} - -export function updateMfa(data, success, error) { - $.ajax({ - url: '/api/v1/users/update_mfa', - dataType: 'json', - contentType: 'application/json', - type: 'POST', - data: JSON.stringify(data), - success, - error: (xhr, status, err) => { - var e = handleError('updateMfa', xhr, status, err); - error(e); - } - }); -} - -export function uploadBrandImage(image, success, error) { - const formData = new FormData(); - formData.append('image', image, image.name); - - $.ajax({ - url: '/api/v1/admin/upload_brand_image', - type: 'POST', - data: formData, - cache: false, - contentType: false, - processData: false, - success, - error: function onError(xhr, status, err) { - var e = handleError('uploadBrandImage', xhr, status, err); - error(e); - } - }); -} diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 87f4153fb..9bdf348cd 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -61,6 +61,7 @@ export default { RECEIVED_ADD_MENTION: null, RECEIVED_PROFILES: null, + RECEIVED_DIRECT_PROFILES: null, RECEIVED_ME: null, RECEIVED_SESSIONS: null, RECEIVED_AUDITS: null, @@ -92,6 +93,9 @@ export default { RECEIVED_SERVER_AUDITS: null, RECEIVED_SERVER_COMPLIANCE_REPORTS: null, RECEIVED_ALL_TEAMS: null, + RECEIVED_ALL_TEAM_LISTINGS: null, + RECEIVED_TEAM_MEMBERS: null, + RECEIVED_MEMBERS_FOR_TEAM: null, RECEIVED_LOCALE: null, @@ -205,6 +209,7 @@ export default { LDAP_SERVICE: 'ldap', USERNAME_SERVICE: 'username', SIGNIN_CHANGE: 'signin_change', + PASSWORD_CHANGE: 'password_change', SIGNIN_VERIFIED: 'verified', SESSION_EXPIRED: 'expired', POST_CHUNK_SIZE: 60, diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index c4f4a025e..a1e16928b 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -10,9 +10,8 @@ import PreferenceStore from 'stores/preference_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import Constants from 'utils/constants.jsx'; var ActionTypes = Constants.ActionTypes; -import * as Client from './client.jsx'; import * as AsyncClient from './async_client.jsx'; -import * as client from './client.jsx'; +import Client from './web_client.jsx'; import React from 'react'; import {browserHistory} from 'react-router'; @@ -1114,7 +1113,7 @@ 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) { - return getWindowLocationOrigin() + '/api/v1/files/get' + filename; + return getWindowLocationOrigin() + Client.getFilesRoute() + '/get' + filename; } // Gets the name of a file (including extension) from a given url or file path. @@ -1210,13 +1209,17 @@ export function importSlack(file, success, error) { formData.append('filesize', file.size); formData.append('importFrom', 'slack'); - client.importSlack(formData, success, error); + Client.importSlack(formData, success, error); } export function getTeamURLFromAddressBar() { return window.location.origin + '/' + window.location.pathname.split('/')[1]; } +export function getTeamNameFromUrl() { + return window.location.pathname.split('/')[1]; +} + export function getTeamURLNoOriginFromAddressBar() { return '/' + window.location.pathname.split('/')[1]; } @@ -1263,16 +1266,11 @@ export function openDirectChannelToUser(user, successCb, errorCb) { }; Client.createDirectChannel( - channel, user.id, (data) => { Client.getChannel( data.id, - (data2, textStatus, xhr) => { - if (xhr.status === 304 || !data2) { - return; - } - + (data2) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_CHANNEL, channel: data2.channel, @@ -1400,7 +1398,7 @@ export function localizeMessage(id, defaultMessage) { } export function getProfilePicSrcForPost(post, timestamp) { - let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp; + let src = Client.getUsersRoute() + '/' + 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/webapp/utils/web_client.jsx b/webapp/utils/web_client.jsx new file mode 100644 index 000000000..6071b4bb4 --- /dev/null +++ b/webapp/utils/web_client.jsx @@ -0,0 +1,67 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Client from '../client/client.jsx'; +import TeamStore from '../stores/team_store.jsx'; +import BrowserStore from '../stores/browser_store.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; + +const HTTP_UNAUTHORIZED = 401; + +class WebClientClass extends Client { + constructor() { + super(); + this.enableLogErrorsToConsole(true); + TeamStore.addChangeListener(this.onTeamStoreChanged); + } + + onTeamStoreChanged = () => { + this.setTeamId(TeamStore.getCurrentId()); + } + + track = (category, action, label, property, value) => { + if (global.window && global.window.analytics) { + global.window.analytics.track(action, {category, label, property, value}); + } + } + + trackPage = () => { + if (global.window && global.window.analytics) { + global.window.analytics.page(); + } + } + + handleError = (err, res) => { // eslint-disable-line no-unused-vars + if (err.status === HTTP_UNAUTHORIZED) { + GlobalActions.emitUserLoggedOutEvent('/login'); + } + } + + // not sure why but super.login doesn't work if using an () => arrow functions. + // I think this might be a webpack issue. + webLogin(email, username, password, token, success, error) { + this.login( + email, + username, + password, + token, + (data) => { + this.track('api', 'api_users_login_success', '', 'email', data.email); + BrowserStore.signalLogin(); + + if (success) { + success(data); + } + }, + (err) => { + this.track('api', 'api_users_login_fail', name, 'email', email); + if (error) { + error(err); + } + } + ); + } +} + +var WebClient = new WebClientClass(); +export default WebClient; diff --git a/webapp/webpack.config-test.js b/webapp/webpack.config-test.js new file mode 100644 index 000000000..aaeefeb8c --- /dev/null +++ b/webapp/webpack.config-test.js @@ -0,0 +1,131 @@ +const webpack = require('webpack'); +const path = require('path'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const nodeExternals = require('webpack-node-externals'); + +const htmlExtract = new ExtractTextPlugin('html', 'root.html'); + +const NPM_TARGET = process.env.npm_lifecycle_event; //eslint-disable-line no-process-env + +var DEV = true; +var FULLMAP = false; +if (NPM_TARGET === 'run' || NPM_TARGET === 'run-fullmap') { + DEV = true; + if (NPM_TARGET === 'run-fullmap') { + FULLMAP = true; + } +} + +var config = { + target: 'node', + externals: [nodeExternals()], + module: { + loaders: [ + { + test: /\.jsx?$/, + loader: 'babel', + exclude: /(node_modules|non_npm_dependencies)/, + query: { + presets: ['react', 'es2015-webpack', 'stage-0'], + plugins: ['transform-runtime'], + cacheDirectory: DEV + } + }, + { + test: /\.json$/, + loader: 'json' + }, + { + test: /(node_modules|non_npm_dependencies)\/.+\.(js|jsx)$/, + loader: 'imports', + query: { + $: 'jquery', + jQuery: 'jquery' + } + }, + { + test: /\.scss$/, + loaders: ['style', 'css', 'sass'] + }, + { + test: /\.css$/, + loaders: ['style', 'css'] + }, + { + test: /\.(png|eot|tiff|svg|woff2|woff|ttf|gif|mp3|jpg)$/, + loader: 'file', + query: { + name: 'files/[hash].[ext]' + } + }, + { + test: /\.html$/, + loader: htmlExtract.extract('html?attrs=link:href') + } + ] + }, + sassLoader: { + includePaths: ['node_modules/compass-mixins/lib'] + }, + plugins: [ + new webpack.ProvidePlugin({ + 'window.jQuery': 'jquery' + }), + htmlExtract, + new CopyWebpackPlugin([ + {from: 'images/emoji', to: 'emoji'} + ]), + new webpack.LoaderOptionsPlugin({ + minimize: !DEV, + debug: false + }) + ], + resolve: { + alias: { + jquery: 'jquery/dist/jquery' + }, + modules: [ + 'node_modules', + 'non_npm_dependencies', + path.resolve(__dirname) + ] + } +}; + +// Development mode configuration +if (DEV) { + if (FULLMAP) { + config.devtool = 'source-map'; + } else { + config.devtool = 'eval-cheap-module-source-map'; + } +} + +// Production mode configuration +if (!DEV) { + config.devtool = 'source-map'; + config.plugins.push( + new webpack.optimize.UglifyJsPlugin({ + 'screw-ie8': true, + mangle: { + toplevel: false + }, + compress: { + warnings: false + }, + comments: false + }) + ); + config.plugins.push( + new webpack.optimize.AggressiveMergingPlugin() + ); + config.plugins.push( + new webpack.optimize.OccurrenceOrderPlugin(true) + ); + config.plugins.push( + new webpack.optimize.DedupePlugin() + ); +} + +module.exports = config; diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js index 4e2d6b70d..dac074c0b 100644 --- a/webapp/webpack.config.js +++ b/webapp/webpack.config.js @@ -88,7 +88,8 @@ var config = { ], resolve: { alias: { - jquery: 'jquery/dist/jquery' + jquery: 'jquery/dist/jquery', + superagent: 'node_modules/superagent/lib/client' }, modules: [ 'node_modules', |