mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
adds team details view
This commit is contained in:
parent
1325732b0c
commit
91d2d8a935
|
@ -1,50 +1,31 @@
|
|||
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
|
||||
user_team_service = ClientApi::UserTeamService.new(user: current_user)
|
||||
user_team_service.destroy_user_team_and_assign_new_team_owner!
|
||||
success_response(user_team_service.teams_data)
|
||||
rescue
|
||||
unsuccess_response
|
||||
end
|
||||
|
||||
def update_role
|
||||
|
||||
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
|
||||
def success_response(locals)
|
||||
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)
|
||||
}
|
||||
locals: locals
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -60,27 +41,7 @@ module ClientApi
|
|||
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
|
||||
|
|
|
@ -22,15 +22,6 @@ export default {
|
|||
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",
|
||||
|
@ -66,7 +57,31 @@ export default {
|
|||
show_in_scinote: "Show in sciNote",
|
||||
notify_me_via_email: "Notify me via email",
|
||||
no: "No",
|
||||
yes: "Yes"
|
||||
yes: "Yes",
|
||||
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"
|
||||
},
|
||||
single_team: {
|
||||
created_on: "Created on: <strong>{created_at}</strong>",
|
||||
created_by: "Created by: <strong>{created_by}</strong>",
|
||||
space_usage: "Space usage: <strong>{space_usage}</strong>",
|
||||
no_description: "<i>No description</i>",
|
||||
members_panel_title: "Team members",
|
||||
add_members: "Add team members",
|
||||
actions: {
|
||||
user_role: "User role",
|
||||
guest: "Guest",
|
||||
normal_user: "Normal user",
|
||||
administrator: "Administrator",
|
||||
remove_user: "Remove"
|
||||
}
|
||||
}
|
||||
},
|
||||
activities: {
|
||||
modal_title: "Activities",
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes, { number, string, bool } from "prop-types";
|
||||
import ReactRouterPropTypes from "react-router-prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Row, Col, Glyphicon, Well } from "react-bootstrap";
|
||||
import { FormattedHTMLMessage, FormattedMessage } from "react-intl";
|
||||
import moment from "moment";
|
||||
import prettysize from "prettysize";
|
||||
import axios from "../../../../app/axios";
|
||||
|
||||
import { TEAM_DETAILS_PATH } from "../../../../app/routes";
|
||||
|
@ -10,6 +13,8 @@ import {
|
|||
COLOR_CONCRETE
|
||||
} from "../../../../app/constants/colors";
|
||||
|
||||
import TeamsMembers from "./components/TeamsMembers";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
|
@ -24,6 +29,21 @@ const TabTitle = styled.div`
|
|||
padding: 15px;
|
||||
`;
|
||||
|
||||
const BadgeWrapper = styled.div`
|
||||
font-size: 1.4em;
|
||||
float: left;
|
||||
padding: 6px 10px;
|
||||
background-color: #37a0d9;
|
||||
color: #fff;
|
||||
`;
|
||||
|
||||
const StyledWell = styled.div`
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
class SettingsTeamPageContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -31,24 +51,102 @@ class SettingsTeamPageContainer extends Component {
|
|||
team: {},
|
||||
users: []
|
||||
};
|
||||
this.updateDescription = this.updateDescription.bind(this);
|
||||
this.updateRole = this.updateRole.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const path = TEAM_DETAILS_PATH.replace(":team_id", this.props.params.id);
|
||||
const { id } = this.props.match.params;
|
||||
const path = TEAM_DETAILS_PATH.replace(":team_id", id);
|
||||
axios.get(path).then(response => {
|
||||
|
||||
const { team, users } = response.data.team_details;
|
||||
this.setState({ team, users });
|
||||
});
|
||||
}
|
||||
|
||||
updateDescription() {
|
||||
console.log("banana");
|
||||
}
|
||||
|
||||
updateRole(userId) {
|
||||
|
||||
}
|
||||
|
||||
renderDescription() {
|
||||
if (this.state.team.description) {
|
||||
return this.state.team.description;
|
||||
}
|
||||
return (
|
||||
<FormattedHTMLMessage id="settings_page.single_team.no_description" />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<TabTitle>
|
||||
<FormattedMessage id="settings_page.all_teams" />
|
||||
{` / ${this.state.team.name}`}
|
||||
</TabTitle>
|
||||
<Row>
|
||||
<Col xs={6} sm={3}>
|
||||
<BadgeWrapper>
|
||||
<Glyphicon glyph="calendar" />
|
||||
</BadgeWrapper>
|
||||
<Well>
|
||||
<FormattedHTMLMessage
|
||||
id="settings_page.single_team.created_on"
|
||||
values={{
|
||||
created_at: moment(this.state.team.created_at).format(
|
||||
"DD.MM.YYYY"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Well>
|
||||
</Col>
|
||||
<Col xs={10} sm={5}>
|
||||
<BadgeWrapper>
|
||||
<Glyphicon glyph="user" />
|
||||
</BadgeWrapper>
|
||||
<Well>
|
||||
<FormattedHTMLMessage
|
||||
id="settings_page.single_team.created_by"
|
||||
values={{ created_by: this.state.team.created_by }}
|
||||
/>
|
||||
</Well>
|
||||
</Col>
|
||||
<Col xs={8} sm={4}>
|
||||
<BadgeWrapper>
|
||||
<Glyphicon glyph="hdd" />
|
||||
</BadgeWrapper>
|
||||
<Well>
|
||||
<FormattedHTMLMessage
|
||||
id="settings_page.single_team.space_usage"
|
||||
values={{
|
||||
space_usage: prettysize(this.state.team.space_taken)
|
||||
}}
|
||||
/>
|
||||
</Well>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col sm={12} onClick={this.updateDescription}>
|
||||
<BadgeWrapper>
|
||||
<Glyphicon glyph="info-sign" />
|
||||
</BadgeWrapper>
|
||||
<StyledWell>
|
||||
{this.renderDescription()}
|
||||
</StyledWell>
|
||||
</Col>
|
||||
</Row>
|
||||
<TeamsMembers members={this.state.users} updateRole={this.updateRole} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsTeamPageContainer.PropTypes = {
|
||||
match: ReactRouterPropTypes.match.isRequired
|
||||
};
|
||||
|
||||
export default SettingsTeamPageContainer;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Panel,
|
||||
Button,
|
||||
Glyphicon,
|
||||
DropdownButton,
|
||||
MenuItem
|
||||
} from "react-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import DataTable from "../../../../../shared/data_table";
|
||||
|
||||
class TeamsMembers extends Component {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this.memberAction = this.memberAction.bind(this);
|
||||
}
|
||||
|
||||
currentRole(memberRole, role) {
|
||||
return memberRole === role ? <Glyphicon glyph="ok" /> : " ";
|
||||
}
|
||||
|
||||
|
||||
memberAction(data, row) {
|
||||
return (
|
||||
<DropdownButton bsStyle="default" title={"banana"} id="actions-dropdown">
|
||||
<MenuItem className="dropdown-header" disabled>
|
||||
<FormattedMessage id="settings_page.single_team.actions.user_role" />
|
||||
</MenuItem>
|
||||
<MenuItem >
|
||||
{this.currentRole(data.current_role, "Guest")}
|
||||
<FormattedMessage id="settings_page.single_team.actions.guest" />
|
||||
</MenuItem>
|
||||
<MenuItem >
|
||||
{this.currentRole(data.current_role, "Normal user")}
|
||||
<FormattedMessage id="settings_page.single_team.actions.normal_user" />
|
||||
</MenuItem>
|
||||
<MenuItem >
|
||||
{this.currentRole(data.current_role, "Administrator")}
|
||||
<FormattedMessage id="settings_page.single_team.actions.administrator" />
|
||||
</MenuItem>
|
||||
<MenuItem divider />
|
||||
<MenuItem>
|
||||
<FormattedMessage id="settings_page.single_team.actions.remove_user" />
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const columns = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Name",
|
||||
isKey: false,
|
||||
textId: "name",
|
||||
position: 0,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Email",
|
||||
isKey: true,
|
||||
textId: "email",
|
||||
position: 1,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Role",
|
||||
isKey: false,
|
||||
textId: "role",
|
||||
position: 2,
|
||||
dataSort: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Joined on",
|
||||
isKey: false,
|
||||
textId: "created_at",
|
||||
position: 3
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Status",
|
||||
isKey: false,
|
||||
textId: "status",
|
||||
position: 3
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Actions",
|
||||
isKey: false,
|
||||
textId: "actions",
|
||||
columnClassName: "react-bootstrap-table-dropdown-fix",
|
||||
dataFormat: this.memberAction,
|
||||
position: 3
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<FormattedMessage id="settings_page.single_team.members_panel_title" />
|
||||
}
|
||||
>
|
||||
<Button>
|
||||
<Glyphicon glyph="plus" />
|
||||
<FormattedMessage id="settings_page.single_team.add_members" />
|
||||
</Button>
|
||||
|
||||
<DataTable data={this.props.members} columns={columns} />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TeamsMembers;
|
|
@ -39,9 +39,9 @@ class TeamsDataTable extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
linkToTeam(name, col) {
|
||||
linkToTeam(name, row) {
|
||||
return (
|
||||
<Link to={`${SETTINGS_TEAMS_ROUTE}/${col.id}`}>
|
||||
<Link to={`${SETTINGS_TEAMS_ROUTE}/${row.id}`}>
|
||||
{name}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -21,3 +21,11 @@ body {
|
|||
background-color: $primary-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
// // fixes issue with dropdown in datatable
|
||||
.react-bootstrap-table-dropdown-fix {
|
||||
overflow: inherit !important;
|
||||
& .open > .dropdown-menu {
|
||||
position: relative !important;
|
||||
}
|
||||
}
|
||||
|
|
62
app/services/client_api/uset_team_service.rb
Normal file
62
app/services/client_api/uset_team_service.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module ClientApi
|
||||
class UserTeamService
|
||||
include NotificationsHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
def initialize(arg)
|
||||
parsed_args = validate_params(args)
|
||||
@team = Team.find_by_id(parsed_args.fetch(:team_id))
|
||||
@user = parsed_args.fetch(:user)
|
||||
@user_team = UserTeam.find_by_id(parsed_args.fetch(:user_team_id))
|
||||
raise ClientApi::CustomUserTeamError unless @user_team.team == @team
|
||||
end
|
||||
|
||||
def destroy_user_team_and_assign_new_team_owner!
|
||||
raise ClientApi::CustomUserTeamError unless user_cant_leave?
|
||||
new_owner = @team.user_teams
|
||||
.where(role: 2)
|
||||
.where.not(id: @user_team.id)
|
||||
.first.user
|
||||
new_owner ||= @user
|
||||
reset_user_current_team(@user_team)
|
||||
@user_team.destroy(new_owner)
|
||||
generate_new_notification
|
||||
end
|
||||
|
||||
def teams_data
|
||||
{
|
||||
teams: @user.teams_data,
|
||||
flash_message: t('client_api.user_teams.leave_flash', team: @team.name)
|
||||
}
|
||||
end
|
||||
private
|
||||
|
||||
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 user_cant_leave?
|
||||
@user_team.admin? && @team.user_teams.where(role: 2).count <= 1
|
||||
end
|
||||
|
||||
def generate_new_notification
|
||||
user = @user_team.user
|
||||
generate_notification(user,
|
||||
user,
|
||||
@user_team.team,
|
||||
false,
|
||||
false)
|
||||
end
|
||||
|
||||
def validate_params(args)
|
||||
team_id = args.fetch(:team_id) { raise ClientApi::CustomUserTeamError }
|
||||
user_team_id = args.fetch(:user_team_id) { raise ClientApi::CustomUserTeamError }
|
||||
user = args.fetch(:user) { raise ClientApi::CustomUserTeamError }
|
||||
{user: user, user_team_id: user_team_id, team_id: team_id }
|
||||
end
|
||||
end
|
||||
CustomUserTeamError = Class.new(StandardError)
|
||||
end
|
|
@ -1,12 +1,16 @@
|
|||
json.team_details do
|
||||
json.id team.id
|
||||
json.created_at team.created_at
|
||||
json.created_by "#{team.created_by.full_name} (#{team.created_by.email})"
|
||||
json.space_taken team.space_taken
|
||||
json.description team.description
|
||||
json.team do
|
||||
json.id team.id
|
||||
json.name team.name
|
||||
json.created_at team.created_at
|
||||
json.created_by "#{team.created_by.full_name} (#{team.created_by.email})"
|
||||
json.space_taken team.space_taken
|
||||
json.description team.description
|
||||
end
|
||||
json.users team_users do |team_user|
|
||||
json.id team_user.user.id
|
||||
json.email team_user.user.full_name
|
||||
json.name team_user.user.full_name
|
||||
json.email team_user.user.email
|
||||
json.role team_user.role_str
|
||||
json.created_at I18n.l(team_user.created_at, format: :full_date)
|
||||
json.status team_user.user.active_status_str
|
||||
|
|
|
@ -29,6 +29,7 @@ Rails.application.routes.draw do
|
|||
|
||||
namespace :users do
|
||||
delete '/leave_team', to: 'user_teams#leave_team'
|
||||
put '/update_role', to: 'user_teams#update_role'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -59,11 +59,14 @@
|
|||
"postcss-loader": "^2.0.6",
|
||||
"postcss-smart-import": "^0.7.5",
|
||||
"precss": "^2.0.0",
|
||||
"prettysize": "^0.1.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"rails-erb-loader": "^5.0.2",
|
||||
"react": "^15.6.1",
|
||||
"react-bootstrap": "^0.31.1",
|
||||
"react-bootstrap-table": "^4.0.0",
|
||||
"react-bootstrap-timezone-picker": "^1.0.11",
|
||||
"react-data-grid": "^2.0.2",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-intl": "^2.3.0",
|
||||
"react-intl-redux": "^0.6.0",
|
||||
|
@ -71,6 +74,7 @@
|
|||
"react-redux": "^5.0.5",
|
||||
"react-router-bootstrap": "^0.24.2",
|
||||
"react-router-dom": "^4.1.2",
|
||||
"react-router-prop-types": "^0.0.1",
|
||||
"react-timezone": "^0.2.0",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
|
@ -80,8 +84,6 @@
|
|||
"styled-components": "^2.1.1",
|
||||
"webpack": "^3.2.0",
|
||||
"webpack-manifest-plugin": "^1.1.2",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"react-bootstrap-table": "^4.0.0",
|
||||
"react-data-grid": "^2.0.2"
|
||||
"webpack-merge": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -4516,6 +4516,10 @@ prettier@^1.5.3:
|
|||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.5.3.tgz#59dadc683345ec6b88f88b94ed4ae7e1da394bfe"
|
||||
|
||||
prettysize@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/prettysize/-/prettysize-0.1.0.tgz#38ee534e2d298bc945fb7243203dd873cefc9679"
|
||||
|
||||
private@^0.1.6, private@^0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
|
||||
|
@ -4550,7 +4554,7 @@ prop-types-extra@^1.0.1:
|
|||
dependencies:
|
||||
warning "^3.0.0"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8:
|
||||
prop-types@15.5.10, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8:
|
||||
version "15.5.10"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
|
||||
dependencies:
|
||||
|
@ -4723,10 +4727,6 @@ react-intl@^2.3.0:
|
|||
intl-relativeformat "^1.3.0"
|
||||
invariant "^2.1.1"
|
||||
|
||||
react-moment@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.6.4.tgz#5e531d47ad7b0bff6f6b7175093e98659f5e667b"
|
||||
|
||||
react-modal@^1.4.0:
|
||||
version "1.9.7"
|
||||
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d"
|
||||
|
@ -4738,6 +4738,10 @@ react-modal@^1.4.0:
|
|||
prop-types "^15.5.7"
|
||||
react-dom-factories "^1.0.0"
|
||||
|
||||
react-moment@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.6.4.tgz#5e531d47ad7b0bff6f6b7175093e98659f5e667b"
|
||||
|
||||
react-overlays@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.7.0.tgz#531898ff566c7e5c7226ead2863b8cf9fbb5a981"
|
||||
|
@ -4781,6 +4785,12 @@ react-router-dom@^4.1.2:
|
|||
prop-types "^15.5.4"
|
||||
react-router "^4.2.0"
|
||||
|
||||
react-router-prop-types@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-prop-types/-/react-router-prop-types-0.0.1.tgz#fd7fe4431f8ee104cdb250c738f410eb85847377"
|
||||
dependencies:
|
||||
prop-types "15.5.10"
|
||||
|
||||
react-router@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986"
|
||||
|
|
Loading…
Reference in a new issue