mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-06 05:04:35 +08:00
Merge pull request #778 from ZmagoD/zd_SCI_1497
Rewrite all teams page into React.js
This commit is contained in:
commit
6159b18533
30 changed files with 626 additions and 32 deletions
|
@ -1,5 +1,6 @@
|
|||
module ClientApi
|
||||
class TeamsController < ApplicationController
|
||||
include ClientApi::Users::UserTeamsHelper
|
||||
MissingTeamError = Class.new(StandardError)
|
||||
|
||||
def index
|
||||
|
@ -34,11 +35,11 @@ module ClientApi
|
|||
end
|
||||
|
||||
def teams
|
||||
{ teams: current_user.teams }
|
||||
{ teams: current_user.teams_data }
|
||||
end
|
||||
|
||||
def change_current_team
|
||||
team_id = params.fetch(:team_id) { raise MissingTeamError }
|
||||
team_id = params.fetch(:team_id) { raise MissingTeamError }
|
||||
unless current_user.teams.pluck(:id).include? team_id
|
||||
raise MissingTeamError
|
||||
end
|
||||
|
|
86
app/controllers/client_api/users/user_teams_controller.rb
Normal file
86
app/controllers/client_api/users/user_teams_controller.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
module ClientApi
|
||||
module Users
|
||||
class UserTeamsController < ApplicationController
|
||||
include NotificationsHelper
|
||||
include InputSanitizeHelper
|
||||
include ClientApi::Users::UserTeamsHelper
|
||||
|
||||
before_action :find_user_team, only: :leave_team
|
||||
|
||||
def leave_team
|
||||
if user_cant_leave?
|
||||
unsuccess_response
|
||||
else
|
||||
begin
|
||||
assign_new_team_owner
|
||||
generate_new_notification
|
||||
success_response
|
||||
rescue
|
||||
unsuccess_response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user_team
|
||||
@team = Team.find_by_id(params[:team])
|
||||
@user_team = UserTeam.where(team: @team, user: current_user).first
|
||||
end
|
||||
|
||||
def user_cant_leave?
|
||||
return unless @user_team && @team
|
||||
@user_team.admin? &&
|
||||
@team.user_teams.where(role: 2).count <= 1
|
||||
end
|
||||
|
||||
def success_response
|
||||
respond_to do |format|
|
||||
# return a list of teams
|
||||
format.json do
|
||||
render template: '/client_api/teams/index',
|
||||
status: :ok,
|
||||
locals: {
|
||||
teams: current_user.teams_data,
|
||||
flash_message: t('client_api.user_teams.leave_flash',
|
||||
team: @team.name)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsuccess_response
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: t(
|
||||
'client_api.user_teams.leave_team_error'
|
||||
) },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def assign_new_team_owner
|
||||
new_owner = @team.user_teams
|
||||
.where(role: 2)
|
||||
.where.not(id: @user_team.id)
|
||||
.first.user
|
||||
new_owner ||= current_user
|
||||
reset_user_current_team(@user_team)
|
||||
@user_team.destroy(new_owner)
|
||||
end
|
||||
|
||||
def reset_user_current_team(user_team)
|
||||
ids = user_team.user.teams_ids
|
||||
ids -= [user_team.team.id]
|
||||
user_team.user.current_team_id = ids.first
|
||||
user_team.user.save
|
||||
end
|
||||
|
||||
def generate_new_notification
|
||||
generate_notification(@user_team.user, @user_team.user, @user_team.team,
|
||||
false, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
app/helpers/client_api/users/user_teams_helper.rb
Normal file
10
app/helpers/client_api/users/user_teams_helper.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module ClientApi
|
||||
module Users
|
||||
module UserTeamsHelper
|
||||
def retrive_role_name(index)
|
||||
return unless index
|
||||
['Guest', 'Normal user', 'Administrator'].at(index)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,3 +23,7 @@ export const CHANGE_RECENT_NOTIFICATION_EMAIL =
|
|||
"CHANGE_RECENT_NOTIFICATION_EMAIL";
|
||||
export const CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL =
|
||||
"CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL";
|
||||
|
||||
// user teams
|
||||
export const LEAVE_TEAM = "LEAVE_TEAM"
|
||||
export const SHOW_LEAVE_TEAM_MODAL = "SHOW_LEAVE_TEAM_MODAL"
|
||||
|
|
|
@ -5,10 +5,12 @@ import {
|
|||
} from "../shared/reducers/TeamReducers";
|
||||
import { globalActivities } from "../shared/reducers/ActivitiesReducers";
|
||||
import { currentUser } from "../shared/reducers/UsersReducer";
|
||||
import { showLeaveTeamModal } from "../shared/reducers/LeaveTeamReducer";
|
||||
|
||||
export default combineReducers({
|
||||
current_team: setCurrentTeam,
|
||||
all_teams: getListOfTeams,
|
||||
global_activities: globalActivities,
|
||||
current_user: currentUser
|
||||
current_user: currentUser,
|
||||
showLeaveTeamModal
|
||||
});
|
||||
|
|
|
@ -22,6 +22,9 @@ export const PREMIUM_LINK = "http://scinote.net/premium/";
|
|||
export const CONTACT_US_LINK =
|
||||
"http://scinote.net/story-of-scinote/#contact-scinote";
|
||||
|
||||
// user teams
|
||||
export const LEAVE_TEAM_PATH = "/client_api/users/leave_team"
|
||||
|
||||
// settings
|
||||
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
|
||||
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";
|
||||
|
|
|
@ -18,6 +18,19 @@ export default {
|
|||
info_label: "Info"
|
||||
},
|
||||
settings_page: {
|
||||
all_teams: "All teams",
|
||||
in_team: "You are member of {num} team",
|
||||
in_teams: "You are member of {num} team",
|
||||
leave_team: "Leave team",
|
||||
leave_team_modal: {
|
||||
title: "Leave team {teamName}",
|
||||
subtitle: "Are you sure you wish to leave team My projects? This action is irreversible.",
|
||||
warnings: "Leaving team has following consequences:",
|
||||
warning_message_one: "you will lose access to all content belonging to the team (including projects, tasks, protocols and activities);",
|
||||
warning_message_two: "all projects in the team where you were the sole <b>Owner</b> will receive a new owner from the team administrators;",
|
||||
warning_message_three: "all repository protocols in the team belonging to you will be reassigned onto a new owner from team administrators.",
|
||||
leave_team: "Leave"
|
||||
},
|
||||
account: "Account",
|
||||
team: "Team",
|
||||
avatar: "Avatar",
|
||||
|
|
8
app/javascript/packs/shared/actions/LeaveTeamActions.js
Normal file
8
app/javascript/packs/shared/actions/LeaveTeamActions.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { SHOW_LEAVE_TEAM_MODAL } from "../../app/action_types";
|
||||
|
||||
export function leaveTeamModalShow(show = false, id = 0, teamName = "") {
|
||||
return {
|
||||
payload: { show, id, teamName },
|
||||
type: SHOW_LEAVE_TEAM_MODAL
|
||||
};
|
||||
}
|
|
@ -3,16 +3,16 @@ import _ from "lodash";
|
|||
import { TEAMS_PATH, CHANGE_TEAM_PATH } from "../../app/routes";
|
||||
import { GET_LIST_OF_TEAMS, SET_CURRENT_TEAM } from "../../app/action_types";
|
||||
|
||||
function addTeamsData(data) {
|
||||
export function addTeamsData(data) {
|
||||
return {
|
||||
type: GET_LIST_OF_TEAMS,
|
||||
payload: data
|
||||
};
|
||||
}
|
||||
|
||||
export function setCurrentUser(user) {
|
||||
export function setCurrentTeam(team) {
|
||||
return {
|
||||
user,
|
||||
team,
|
||||
type: SET_CURRENT_TEAM
|
||||
};
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ export function getTeamsList() {
|
|||
axios
|
||||
.get(TEAMS_PATH, { withCredentials: true })
|
||||
.then(response => {
|
||||
let teams = _.values(response.data);
|
||||
const teams = response.data.teams.collection;
|
||||
dispatch(addTeamsData(teams));
|
||||
let current_team = _.find(teams, team => team.current_team);
|
||||
dispatch(setCurrentUser(current_team));
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
dispatch(setCurrentTeam(currentTeam));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("get Teams Error: ", error);
|
||||
|
@ -33,15 +33,15 @@ export function getTeamsList() {
|
|||
};
|
||||
}
|
||||
|
||||
export function changeTeam(team_id) {
|
||||
export function changeTeam(teamId) {
|
||||
return dispatch => {
|
||||
axios
|
||||
.post(CHANGE_TEAM_PATH, { team_id }, { withCredentials: true })
|
||||
.post(CHANGE_TEAM_PATH, { teamId }, { withCredentials: true })
|
||||
.then(response => {
|
||||
let teams = _.values(response.data);
|
||||
const teams = response.data.teams.collection;
|
||||
dispatch(addTeamsData(teams));
|
||||
let current_team = _.find(teams, team => team.current_team);
|
||||
dispatch(setCurrentUser(current_team));
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
dispatch(setCurrentTeam(currentTeam));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("get Teams Error: ", error);
|
||||
|
|
|
@ -108,4 +108,4 @@ DataTable.propTypes = {
|
|||
data: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
export default DataTable;
|
||||
|
|
7
app/javascript/packs/shared/modals_container/index.jsx
Normal file
7
app/javascript/packs/shared/modals_container/index.jsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import React from "react";
|
||||
import LeaveTeamModal from "./modals/LeaveTeamModal";
|
||||
|
||||
export default () =>
|
||||
<div>
|
||||
<LeaveTeamModal />
|
||||
</div>;
|
|
@ -0,0 +1,103 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes, { bool, number, string, func } from "prop-types";
|
||||
import { Modal, Button, Alert, Glyphicon } from "react-bootstrap";
|
||||
import { FormattedMessage, FormattedHTMLMessage } from "react-intl";
|
||||
import { connect } from "react-redux";
|
||||
import axios from "../../../app/axios";
|
||||
|
||||
import { LEAVE_TEAM_PATH } from "../../../app/routes";
|
||||
import { leaveTeamModalShow } from "../../actions/LeaveTeamActions";
|
||||
import { addTeamsData, setCurrentTeam } from "../../actions/TeamsActions";
|
||||
|
||||
class LeaveTeamModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onCloseModal = this.onCloseModal.bind(this);
|
||||
this.leaveTeam = this.leaveTeam.bind(this);
|
||||
}
|
||||
|
||||
onCloseModal() {
|
||||
this.props.leaveTeamModalShow(false);
|
||||
}
|
||||
|
||||
leaveTeam() {
|
||||
const teamUrl = `${LEAVE_TEAM_PATH}?team=${this.props.teamId}`;
|
||||
axios
|
||||
.delete(teamUrl, {
|
||||
withCredentials: true
|
||||
})
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
const teams = response.data.teams.collection;
|
||||
this.props.addTeamsData(teams);
|
||||
const currentTeam = _.find(teams, team => team.current_team);
|
||||
this.props.setCurrentTeam(currentTeam);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error: ", error.response.data.message);
|
||||
});
|
||||
this.props.leaveTeamModalShow(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal show={this.props.showModal} onHide={this.onCloseModal}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<FormattedMessage
|
||||
id="settings_page.leave_team_modal.title"
|
||||
values={{ teamName: this.props.teamName }}
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>
|
||||
<FormattedMessage id="settings_page.leave_team_modal.subtitle" />
|
||||
</p>
|
||||
<Alert bsStyle="danger">
|
||||
<Glyphicon glyph="exclamation-sign" />
|
||||
<FormattedMessage id="settings_page.leave_team_modal.warnings" />
|
||||
<ul>
|
||||
<li>
|
||||
<FormattedMessage id="settings_page.leave_team_modal.warning_message_one" />
|
||||
</li>
|
||||
<li>
|
||||
<FormattedHTMLMessage id="settings_page.leave_team_modal.warning_message_two" />
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage id="settings_page.leave_team_modal.warning_message_three" />
|
||||
</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.onCloseModal}>
|
||||
<FormattedMessage id="general.close" />
|
||||
</Button>
|
||||
<Button bsStyle="success" onClick={this.leaveTeam}>
|
||||
<FormattedMessage id="settings_page.leave_team_modal.leave_team" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LeaveTeamModal.propTypes = {
|
||||
showModal: bool.isRequired,
|
||||
teamId: number.isRequired,
|
||||
teamName: string.isRequired,
|
||||
addTeamsData: func.isRequired,
|
||||
leaveTeamModalShow: func.isRequired
|
||||
};
|
||||
const mapStateToProps = ({ showLeaveTeamModal }) => ({
|
||||
showModal: showLeaveTeamModal.show,
|
||||
teamId: showLeaveTeamModal.id,
|
||||
teamName: showLeaveTeamModal.teamName
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
leaveTeamModalShow,
|
||||
addTeamsData,
|
||||
setCurrentTeam
|
||||
})(LeaveTeamModal);
|
|
@ -7,7 +7,7 @@ import styled from "styled-components";
|
|||
import _ from "lodash";
|
||||
|
||||
import { BORDER_GRAY_COLOR } from "../../../app/constants/colors";
|
||||
import { setCurrentUser, changeTeam } from "../../actions/TeamsActions";
|
||||
import { changeTeam } from "../../actions/TeamsActions";
|
||||
import { getTeamsList } from "../../actions/TeamsActions";
|
||||
|
||||
const StyledNavDropdown = styled(NavDropdown)`
|
||||
|
@ -89,14 +89,11 @@ TeamSwitch.propTypes = {
|
|||
// Map the states from store to component
|
||||
const mapStateToProps = ({ all_teams, current_team }) => ({
|
||||
current_team,
|
||||
all_teams: _.values(all_teams)
|
||||
all_teams: all_teams.collection
|
||||
});
|
||||
|
||||
// Map the fetch activity action to component
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setCurrentUser() {
|
||||
dispatch(setCurrentUser());
|
||||
},
|
||||
changeTeam(teamId) {
|
||||
dispatch(changeTeam(teamId));
|
||||
},
|
||||
|
|
11
app/javascript/packs/shared/reducers/LeaveTeamReducer.js
Normal file
11
app/javascript/packs/shared/reducers/LeaveTeamReducer.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { SHOW_LEAVE_TEAM_MODAL } from "../../app/action_types";
|
||||
|
||||
export function showLeaveTeamModal(
|
||||
state = { show: false, id: 0, teamName: "" },
|
||||
action
|
||||
) {
|
||||
if (action.type === SHOW_LEAVE_TEAM_MODAL) {
|
||||
return { ...state, ...action.payload };
|
||||
}
|
||||
return state;
|
||||
}
|
|
@ -3,14 +3,17 @@ import { SET_CURRENT_TEAM, GET_LIST_OF_TEAMS } from "../../app/action_types";
|
|||
const initialState = { name: "", id: 0, current_team: true };
|
||||
export const setCurrentTeam = (state = initialState, action) => {
|
||||
if (action.type === SET_CURRENT_TEAM) {
|
||||
return Object.assign({}, state, action.user);
|
||||
return Object.assign({}, state, action.team);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export const getListOfTeams = (state = [], action) => {
|
||||
export const getListOfTeams = (state = { collection: [] }, action) => {
|
||||
if (action.type === GET_LIST_OF_TEAMS) {
|
||||
return Object.assign({}, state, action.payload);
|
||||
return {
|
||||
...state,
|
||||
collection: action.payload
|
||||
};
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import store from "../../app/store";
|
|||
import messages from "../../locales/messages";
|
||||
|
||||
import MainNav from "./components/MainNav";
|
||||
import ModalsContainer from "../../shared/modals_container";
|
||||
|
||||
addLocaleData([...enLocaleData]);
|
||||
const locale = "en-US";
|
||||
|
@ -17,6 +18,7 @@ const locale = "en-US";
|
|||
const SettingsPage = () =>
|
||||
<div>
|
||||
<MainNav />
|
||||
<ModalsContainer />
|
||||
</div>;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import React from "react";
|
||||
import PropTypes, { number, string, bool } from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { connect } from "react-redux";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { BORDER_LIGHT_COLOR } from "../../../../app/constants/colors";
|
||||
import {
|
||||
BORDER_LIGHT_COLOR,
|
||||
COLOR_CONCRETE
|
||||
} from "../../../../app/constants/colors";
|
||||
|
||||
import TeamsPageDetails from "./components/TeamsPageDetails";
|
||||
import TeamsDataTable from "./components/TeamsDataTable";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: white;
|
||||
|
@ -9,12 +18,42 @@ const Wrapper = styled.div`
|
|||
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||
border-top: none;
|
||||
margin: 0;
|
||||
padding: 16px 0 50px 0;
|
||||
padding: 16px 15px 50px 15px;
|
||||
`;
|
||||
|
||||
const SettingsTeams = () =>
|
||||
const TabTitle = styled.div`
|
||||
background-color: ${COLOR_CONCRETE};
|
||||
padding: 15px;
|
||||
`;
|
||||
|
||||
const SettingsTeams = ({ teams }) =>
|
||||
<Wrapper>
|
||||
<h1 className="text-center">Settings Teams</h1>
|
||||
<TabTitle>
|
||||
<FormattedMessage id="settings_page.all_teams" />
|
||||
</TabTitle>
|
||||
<TeamsPageDetails teams={teams} />
|
||||
<TeamsDataTable teams={teams} />
|
||||
</Wrapper>;
|
||||
|
||||
export default SettingsTeams;
|
||||
SettingsTeams.propTypes = {
|
||||
teams: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
current_team: bool.isRequired,
|
||||
role: string.isRequired,
|
||||
members: number.isRequired,
|
||||
can_be_leaved: bool.isRequired
|
||||
}).isRequired
|
||||
)
|
||||
};
|
||||
|
||||
SettingsTeams.defaultProps = {
|
||||
teams: [{id: 0, name: "", current_team: "", role: "", members: 0}]
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ all_teams }) => ({
|
||||
teams: all_teams.collection
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(SettingsTeams);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
import { LEAVE_TEAM_PATH } from '../../../../../app/routes'
|
|
@ -0,0 +1,106 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes, { func, number, string, bool } from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { Button } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { leaveTeamModalShow } from "../../../../../shared/actions/LeaveTeamActions";
|
||||
import DataTable from "../../../../../shared/data_table";
|
||||
|
||||
class TeamsDataTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.leaveTeamModal = this.leaveTeamModal.bind(this);
|
||||
this.leaveTeamButton = this.leaveTeamButton.bind(this);
|
||||
}
|
||||
|
||||
leaveTeamModal(e, id) {
|
||||
const team = _.find(this.props.teams, el => el.id === id);
|
||||
this.props.leaveTeamModalShow(true, id, team.name);
|
||||
}
|
||||
|
||||
leaveTeamButton(id) {
|
||||
const team = _.find(this.props.teams, el => el.id === id);
|
||||
if (team.can_be_leaved) {
|
||||
return (
|
||||
<Button onClick={e => this.leaveTeamModal(e, id)}>
|
||||
<FormattedMessage id="settings_page.leave_team" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button disabled>
|
||||
<FormattedMessage id="settings_page.leave_team" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const options = {
|
||||
defaultSortName: "name",
|
||||
defaultSortOrder: "desc",
|
||||
sizePerPageList: [10, 25, 50, 100],
|
||||
paginationPosition: "top",
|
||||
alwaysShowAllBtns: false
|
||||
};
|
||||
const columns = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Name",
|
||||
isKey: false,
|
||||
textId: "name",
|
||||
position: 0,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Role",
|
||||
isKey: false,
|
||||
textId: "role",
|
||||
position: 1,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Members",
|
||||
isKey: false,
|
||||
textId: "members",
|
||||
position: 2,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "",
|
||||
isKey: true,
|
||||
textId: "id",
|
||||
dataFormat: this.leaveTeamButton,
|
||||
position: 3
|
||||
}
|
||||
];
|
||||
return (
|
||||
<DataTable
|
||||
data={this.props.teams}
|
||||
columns={columns}
|
||||
pagination={true}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TeamsDataTable.propTypes = {
|
||||
leaveTeamModalShow: func.isRequired,
|
||||
teams: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
current_team: bool.isRequired,
|
||||
role: string.isRequired,
|
||||
members: number.isRequired,
|
||||
can_be_leaved: bool.isRequired
|
||||
}).isRequired
|
||||
)
|
||||
};
|
||||
|
||||
export default connect(null, { leaveTeamModalShow })(TeamsDataTable);
|
|
@ -0,0 +1,59 @@
|
|||
import React from "react";
|
||||
import PropTypes, { number, string, bool } from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage, FormattedPlural } from "react-intl";
|
||||
import { Button, Glyphicon } from "react-bootstrap";
|
||||
|
||||
const Wrapper = styled.div`margin: 15px 0;`;
|
||||
const TeamsPageDetails = ({ teams }) => {
|
||||
const teamsNumber = teams.length;
|
||||
return (
|
||||
<Wrapper>
|
||||
<FormattedPlural
|
||||
value={teamsNumber}
|
||||
one={
|
||||
<FormattedMessage
|
||||
id="settings_page.in_team"
|
||||
values={{
|
||||
num: teamsNumber
|
||||
}}
|
||||
/>
|
||||
}
|
||||
other={
|
||||
<FormattedMessage
|
||||
id="settings_page.in_teams"
|
||||
values={{
|
||||
num: teamsNumber
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.location = "/users/settings/teams/new";
|
||||
}}
|
||||
>
|
||||
<Glyphicon glyph="plus" /> <FormattedMessage id="global_team_switch.new_team" />
|
||||
</Button>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
TeamsPageDetails.propTypes = {
|
||||
teams: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: number.isRequired,
|
||||
name: string.isRequired,
|
||||
current_team: bool.isRequired,
|
||||
role: string.isRequired,
|
||||
members: number.isRequired,
|
||||
can_be_leaved: bool.isRequired
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
TeamsPageDetails.defaultProps = {
|
||||
teams: []
|
||||
};
|
||||
|
||||
export default TeamsPageDetails;
|
|
@ -204,6 +204,24 @@ class User < ApplicationRecord
|
|||
Team.find_by_id(self.current_team_id)
|
||||
end
|
||||
|
||||
# Retrieves the data needed in all teams page
|
||||
def teams_data
|
||||
ActiveRecord::Base.connection.execute(
|
||||
ActiveRecord::Base.send(
|
||||
:sanitize_sql_array,
|
||||
['SELECT teams.id AS id, teams.name AS name, user_teams.role ' \
|
||||
'AS role, (SELECT COUNT(*) FROM user_teams WHERE ' \
|
||||
'user_teams.team_id = teams.id) AS members, ' \
|
||||
'CASE WHEN teams.id=? THEN true ELSE false END AS current_team, ' \
|
||||
'CASE WHEN (SELECT COUNT(*) FROM user_teams WHERE ' \
|
||||
'user_teams.team_id=teams.id AND role=2) >= 2 THEN true ELSE false ' \
|
||||
'END AS can_be_leaved FROM teams INNER JOIN user_teams ON ' \
|
||||
'teams.id=user_teams.team_id WHERE user_teams.user_id=?',
|
||||
self.current_team_id, self.id]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Search all active users for username & email. Can
|
||||
# also specify which team to ignore.
|
||||
def self.search(
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
json.array! teams do |team|
|
||||
json.id team.id
|
||||
json.name team.name
|
||||
json.current_team team == current_user.current_team
|
||||
json.teams do
|
||||
json.collection teams do |team|
|
||||
json.id team.fetch('id')
|
||||
json.name team.fetch('name')
|
||||
json.members team.fetch('members')
|
||||
json.role retrive_role_name(team.fetch('role') { nil })
|
||||
json.current_team team.fetch('current_team')
|
||||
json.can_be_leaved team.fetch('can_be_leaved')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
json.teams do
|
||||
json.flash_message flash_message
|
||||
json.collection teams do |team|
|
||||
json.id team.fetch('id')
|
||||
json.name team.fetch('name')
|
||||
json.members team.fetch('members')
|
||||
json.role json.role retrive_role_name(team.fetch('role') { nil })
|
||||
json.current_team team.fetch('current_team')
|
||||
json.can_be_leaved team.fetch('can_be_leaved')
|
||||
end
|
||||
end
|
|
@ -1819,3 +1819,8 @@ en:
|
|||
More: "More"
|
||||
Added: 'Added'
|
||||
by: 'by'
|
||||
|
||||
client_api:
|
||||
user_teams:
|
||||
leave_team_error: "An error occured."
|
||||
leave_flash: "Successfuly left team %{team}."
|
||||
|
|
|
@ -23,6 +23,10 @@ Rails.application.routes.draw do
|
|||
get '/recent_notifications', to: 'notifications#recent_notifications'
|
||||
# users
|
||||
get '/current_user_info', to: 'users#current_user_info'
|
||||
|
||||
namespace :users do
|
||||
delete '/leave_team', to: 'user_teams#leave_team'
|
||||
end
|
||||
end
|
||||
|
||||
# Save sample table state
|
||||
|
|
32
spec/controllers/client_api/users/user_teams_controller.rb
Normal file
32
spec/controllers/client_api/users/user_teams_controller.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ClientApi::Users::UserTeamsController, type: :controller do
|
||||
describe 'DELETE #leave_team' do
|
||||
login_user
|
||||
before do
|
||||
@user_one = User.first
|
||||
@user_two = FactoryGirl.create(:user, email: 'sec_user@asdf.com')
|
||||
@team = FactoryGirl.create :team
|
||||
FactoryGirl.create :user_team, team: @team, user: @user_one, role: 2
|
||||
end
|
||||
|
||||
it 'Returns HTTP success if user can leave the team' do
|
||||
FactoryGirl.create :user_team, team: @team, user: @user_two, role: 2
|
||||
delete :leave_team, params: { team: @team.id }, format: :json
|
||||
expect(response).to be_success
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'Returns HTTP unprocessable_entity if user can\'t leave the team' do
|
||||
delete :leave_team, params: { team: @team.id }, format: :json
|
||||
expect(response).to_not be_success
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it 'Returns HTTP unprocessable_entity if no params given' do
|
||||
delete :leave_team, format: :json
|
||||
expect(response).to_not be_success
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
5
spec/factories/user_teams.rb
Normal file
5
spec/factories/user_teams.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
FactoryGirl.define do
|
||||
factory :user_team do
|
||||
role 'admin'
|
||||
end
|
||||
end
|
|
@ -149,4 +149,47 @@ describe User, type: :model do
|
|||
expect(user.name).to eq 'Axe'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'teams_data should return a list of teams' do
|
||||
# needs persistence because is testing a sql query
|
||||
let(:team) { create :team }
|
||||
let(:user_one) do
|
||||
create :user, email: 'user1@asdf.com', current_team_id: team.id
|
||||
end
|
||||
let(:user_two) { create :user, email: 'user2@asdf.com' }
|
||||
|
||||
it 'in a specific format: {id: .., name: .., members: .., role: ' \
|
||||
'.., current_team: .., can_be_leaved: ..}' do
|
||||
create :user_team, team: team, user: user_one
|
||||
expected_result = {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
members: 1,
|
||||
role: 2,
|
||||
current_team: true,
|
||||
can_be_leaved: false
|
||||
}
|
||||
|
||||
user_one.teams_data.first.each do |k, v|
|
||||
expect(v).to eq(expected_result.fetch(k.to_sym))
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return correct number of team members' do
|
||||
create :user_team, team: team, user: user_one
|
||||
create :user_team, team: team, user: user_two
|
||||
expected_result = {
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
members: 2,
|
||||
role: 2,
|
||||
current_team: true,
|
||||
can_be_leaved: true
|
||||
}
|
||||
|
||||
user_one.teams_data.first.each do |k, v|
|
||||
expect(v).to eq(expected_result.fetch(k.to_sym))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
require 'spec_helper'
|
||||
require 'shoulda-matchers'
|
||||
require 'database_cleaner'
|
||||
require 'devise'
|
||||
require_relative 'support/controller_macros'
|
||||
ENV['RAILS_ENV'] = 'test'
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
# Prevent database truncation if the environment is production
|
||||
|
@ -78,6 +80,9 @@ RSpec.configure do |config|
|
|||
|
||||
# includes FactoryGirl in rspec
|
||||
config.include FactoryGirl::Syntax::Methods
|
||||
# Devise
|
||||
config.include Devise::Test::ControllerHelpers, type: :controller
|
||||
config.extend ControllerMacros, type: :controller
|
||||
end
|
||||
|
||||
# config shoulda matchers to work with rspec
|
||||
|
|
10
spec/support/controller_macros.rb
Normal file
10
spec/support/controller_macros.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module ControllerMacros
|
||||
def login_user
|
||||
before(:each) do
|
||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
||||
user = FactoryGirl.create(:user)
|
||||
user.confirm
|
||||
sign_in user
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue