1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-05 00:43:53 +00:00

invite link in popup

This commit is contained in:
sniper-py
2019-04-23 13:36:21 +08:00
parent 86b7a6c861
commit 22bb7b211f
5 changed files with 165 additions and 85 deletions

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import {gettext} from '../../utils/constants'; import {gettext} from '../../utils/constants';
import {seafileAPI} from '../../utils/seafile-api'; import {seafileAPI} from '../../utils/seafile-api';
import {Modal, ModalHeader, ModalBody, ModalFooter, Input, Button} from 'reactstrap'; import {Modal, ModalHeader, ModalBody, ModalFooter, Input, Button} from 'reactstrap';
import InvitationsToolbar from "../toolbar/invitations-toolbar"; import toaster from "../toast";
class InvitePeopleDialog extends React.Component { class InvitePeopleDialog extends React.Component {
@@ -29,50 +29,75 @@ class InvitePeopleDialog extends React.Component {
handleKeyDown = (e) => { handleKeyDown = (e) => {
if (e.keyCode === 13) { if (e.keyCode === 13) {
e.preventDefault();
this.handleSubmitInvite(); this.handleSubmitInvite();
} }
} }
handleSubmitInvite = () => { handleSubmitInvite = () => {
let emails = this.state.emails.trim(); let emails = this.state.emails.trim();
if (emails) { let emailsArray = [];
seafileAPI.invitePeople(emails).then((res) => { emails = emails.split(',');
for (let i = 0; i < emails.length; i++) {
if (res.data.failed.length > 0) { let email = emails[i].trim();
let inviteFailed = ''; if (email) {
for (let i = 0; i < res.data.failed.length; i++) { emailsArray.push(email);
inviteFailed += res.data.failed[i].error_msg }
} }
this.setState({ if (emailsArray.length) {
errorMsg: inviteFailed, seafileAPI.invitePeople(emailsArray).then((res) => {
}); this.setState({
this.props.listInvitations(); emails: '',
} else { });
this.setState({ this.props.toggleInvitePeopleDialog();
emails: '', // success messages
}); let successMsg = '';
this.props.onInvitePeople(); if (res.data.success.length === 1) {
successMsg = gettext('Successfully invited %(email).')
.replace('%(email)', res.data.success[0].accepter);
} else if(res.data.success.length > 1) {
successMsg = gettext('Successfully invited %(email) and %(num) other people.')
.replace('%(email)', res.data.success[0].accepter)
.replace('%(num)', res.data.success.length - 1);
}
if (successMsg) {
toaster.success(successMsg, {duration: 2});
this.props.onInvitePeople(res.data.success);
}
// failed messages
if (res.data.failed.length) {
for (let i = 0; i< res.data.failed.length; i++){
let failedMsg = res.data.failed[i].email + ': ' + res.data.failed[i].error_msg;
toaster.danger(failedMsg, {duration: 3});}
} }
}).catch((error) => { }).catch((error) => {
let errorMsg = gettext(error.response.data.error_msg); this.props.toggleInvitePeopleDialog();
this.setState({ if (error.response){
errorMsg: errorMsg, toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
}); } else {
toaster.danger(gettext('Please check the network.'), {duration: 3});
}
}); });
} else { } else {
this.setState({ if (this.state.emails){
errorMsg: gettext('Email is required') this.setState({
}); errorMsg: gettext('Email is invalid.')
});
} else {
this.setState({
errorMsg: gettext('It is required.')
});
}
} }
} }
render() { render() {
return ( return (
<Modal isOpen={this.props.showInvitationsModal} toggle={this.props.toggleInvitationsModal}> <Modal isOpen={this.props.isInvitePeopleDialogOpen} toggle={this.props.toggleInvitePeopleDialog}>
<ModalHeader toggle={this.props.toggleInvitationsModal}>{gettext('Invite People')}</ModalHeader> <ModalHeader toggle={this.props.toggleInvitePeopleDialog}>{gettext('Invite People')}</ModalHeader>
<ModalBody> <ModalBody>
<label htmlFor="emails">{gettext('Email')}</label> <label htmlFor="emails">{gettext('Emails')}</label>
<Input type="text" id="emails" placeholder="Emails, separated by ','" <Input type="text" id="emails" placeholder={gettext("Emails, separated by ','")}
value={this.state.emails} value={this.state.emails}
onChange={this.handleEmailsChange} onChange={this.handleEmailsChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
@@ -80,7 +105,7 @@ class InvitePeopleDialog extends React.Component {
<span className="error">{this.state.errorMsg}</span> <span className="error">{this.state.errorMsg}</span>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="secondary" onClick={this.props.toggleInvitationsModal}>{gettext('Cancel')}</Button> <Button color="secondary" onClick={this.props.toggleInvitePeopleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmitInvite}>{gettext('Submit')}</Button> <Button color="primary" onClick={this.handleSubmitInvite}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
@@ -89,8 +114,8 @@ class InvitePeopleDialog extends React.Component {
} }
const InvitePeopleDialogPropTypes = { const InvitePeopleDialogPropTypes = {
toggleInvitationsModal: PropTypes.func.isRequired, toggleInvitePeopleDialog: PropTypes.func.isRequired,
showInvitationsModal: PropTypes.bool.isRequired, isInvitePeopleDialogOpen: PropTypes.bool.isRequired,
onInvitePeople: PropTypes.func.isRequired, onInvitePeople: PropTypes.func.isRequired,
}; };

View File

@@ -1,10 +1,11 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { gettext, isPro } from '../../utils/constants'; import {gettext, isPro, canInvitePeople, siteRoot} from '../../utils/constants';
import { Button } from 'reactstrap'; import { Button } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api.js'; import { seafileAPI } from '../../utils/seafile-api.js';
import UserSelect from '../user-select'; import UserSelect from '../user-select';
import SharePermissionEditor from '../select-editor/share-permission-editor'; import SharePermissionEditor from '../select-editor/share-permission-editor';
import "../../css/invitations.css";
class UserItem extends React.Component { class UserItem extends React.Component {
@@ -318,6 +319,12 @@ class ShareToUser extends React.Component {
deleteShareItem={this.deleteShareItem} deleteShareItem={this.deleteShareItem}
onChangeUserPermission={this.onChangeUserPermission} onChangeUserPermission={this.onChangeUserPermission}
/> />
{ canInvitePeople &&
<a href={siteRoot + 'invitations/'} className="invite-link-in-popup">
<i className="sf2-icon-invite invite-link-icon-in-popup"></i>
<span className="invite-link-icon-in-popup">{gettext('Invite People')}</span>
</a>
}
</table> </table>
</div> </div>
</Fragment> </Fragment>

View File

@@ -8,7 +8,7 @@ import { gettext } from '../../utils/constants';
const propTypes = { const propTypes = {
onShowSidePanel: PropTypes.func.isRequired, onShowSidePanel: PropTypes.func.isRequired,
onSearchedClick: PropTypes.func.isRequired, onSearchedClick: PropTypes.func.isRequired,
toggleInvitationsModal: PropTypes.func.isRequired, toggleInvitePeopleDialog: PropTypes.func.isRequired,
}; };
class InvitationsToolbar extends React.Component { class InvitationsToolbar extends React.Component {
@@ -18,17 +18,16 @@ class InvitationsToolbar extends React.Component {
} }
render() { render() {
let { onShowSidePanel, onSearchedClick, toggleInvitationsModal } = this.props; let { onShowSidePanel, onSearchedClick, toggleInvitePeopleDialog } = this.props;
return ( return (
<div className="main-panel-north border-left-show"> <div className="main-panel-north border-left-show">
<div className="cur-view-toolbar"> <div className="cur-view-toolbar">
<span title="Side Nav Menu" onClick={onShowSidePanel} <span title="Side Nav Menu" onClick={onShowSidePanel}
className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none"> className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
</span> </span>
<MediaQuery query="(min-width: 768px)"> <MediaQuery query="(min-width: 768px)">
<div className="operation"> <div className="operation">
<Button color="btn btn-secondary operation-item" onClick={toggleInvitationsModal}> <Button color="btn btn-secondary operation-item" onClick={toggleInvitePeopleDialog}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite People')} <i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite People')}
</Button> </Button>
</div> </div>

View File

@@ -1,8 +1,23 @@
.invite-accept-icon{ .invite-accept-icon {
color:green; color: green;
margin-left: 0.5rem; margin-left: 0.5rem;
font-size: 1.25rem; font-size: 1.25rem;
font-style: normal; font-style: normal;
line-height: 1; line-height: 1;
vertical-align: middle; vertical-align: middle;
} }
.invite-link-in-popup,
.invite-link-in-popup:hover {
text-decoration: none;
}
.invite-link-icon-in-popup {
color: #f89a68;
margin-left: 0.5rem;
font-size: 0.875rem;
font-style: normal;
line-height: 1;
cursor: pointer;
vertical-align: middle;
}

View File

@@ -1,12 +1,14 @@
import React, {Fragment} from 'react'; import React, {Fragment} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {gettext} from '../../utils/constants'; import {gettext, siteRoot} from '../../utils/constants';
import InvitationsToolbar from '../../components/toolbar/invitations-toolbar'; import InvitationsToolbar from '../../components/toolbar/invitations-toolbar';
import InvitePeopleDialog from "../../components/dialog/invite-people-dialog"; import InvitePeopleDialog from "../../components/dialog/invite-people-dialog";
import {seafileAPI} from "../../utils/seafile-api"; import {seafileAPI} from "../../utils/seafile-api";
import {Table} from 'reactstrap'; import {Table} from 'reactstrap';
import Loading from "../../components/loading"; import Loading from "../../components/loading";
import moment from 'moment'; import moment from 'moment';
import toaster from '../../components/toast';
import "../../css/invitations.css"; import "../../css/invitations.css";
class InvitationsListItem extends React.Component { class InvitationsListItem extends React.Component {
@@ -43,7 +45,7 @@ class InvitationsListItem extends React.Component {
const deleteOperation = <i className="action-icon sf2-icon-x3" const deleteOperation = <i className="action-icon sf2-icon-x3"
title={gettext('Delete')} title={gettext('Delete')}
style={!this.state.isOperationShow ? {opacity: 0} : {}} style={!this.state.isOperationShow ? {opacity: 0} : {}}
onClick={this.props.onItemDelete.bind(this, invitationItem.token)}></i>; onClick={this.props.onItemDelete.bind(this, invitationItem.token, this.props.index)}></i>;
return ( return (
<tr onMouseEnter={this.onMouseEnter} <tr onMouseEnter={this.onMouseEnter}
onMouseOver={this.onMouseOver} onMouseOver={this.onMouseOver}
@@ -71,44 +73,52 @@ class InvitationsListView extends React.Component {
super(props); super(props);
} }
onItemDelete = (token) => { onItemDelete = (token, index) => {
seafileAPI.deleteInvitation(token).then((res) => { seafileAPI.deleteInvitation(token).then((res) => {
this.props.listInvitations(); this.props.onDeleteInvitation(index);
toaster.success(gettext('Successfully deleted 1 item.'), {duration: 1});
}).catch((error) => { }).catch((error) => {
}); //todo if (error.response){
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
} else {
toaster.danger(gettext('Please check the network.'), {duration: 3});
}
});
} }
render() { render() {
const invitationsTables = this.props.invitationsList.map((invitation, index) => { const invitationsListItems = this.props.invitationsList.map((invitation, index) => {
return ( return (
<InvitationsListItem <InvitationsListItem
key={index} key={index}
onItemDelete={this.onItemDelete} onItemDelete={this.onItemDelete}
invitation={invitation} invitation={invitation}
index={index}
/>) />)
}); });
return ( return (
<Table hover> <Table hover>
<thead> <thead>
<tr> <tr>
<th width="40%">email</th> <th width="25%">{gettext('Email')}</th>
<th width="20%">invite time</th> <th width="20%">{gettext('Invite Time')}</th>
<th width="20%">expire time</th> <th width="20%">{gettext('Expiration')}</th>
<th width="10%">accept</th> <th width="18%">{gettext('Accepted')}</th>
<th width="10%"></th> <th width="7%"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{invitationsTables} {invitationsListItems}
</tbody> </tbody>
</Table> </Table>
); );
} }
} }
const InvitationsListViewPropTypes = { const InvitationsListViewPropTypes = {
invitationsList: PropTypes.array.isRequired, invitationsList: PropTypes.array.isRequired,
onDeleteInvitation: PropTypes.func.isRequired,
}; };
InvitationsListView.propTypes = InvitationsListViewPropTypes; InvitationsListView.propTypes = InvitationsListViewPropTypes;
@@ -118,10 +128,10 @@ class InvitationsView extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
showInvitationsModal: false, isInvitePeopleDialogOpen: false,
invitationsList: [], invitationsList: [],
isLoading: true, isLoading: true,
errorMsg: '', permissionDeniedMsg: '',
showEmptyTip: false, showEmptyTip: false,
}; };
} }
@@ -134,30 +144,45 @@ class InvitationsView extends React.Component {
isLoading: false, isLoading: false,
}); });
}).catch((error) => { }).catch((error) => {
let errorMsg = gettext(error.response.data.error_msg);
this.setState({ this.setState({
errorMsg: errorMsg,
isLoading: false, isLoading: false,
}); });
if (error.response){
if (error.response.status === 403){
let permissionDeniedMsg = gettext('Permission error');
this.setState({
permissionDeniedMsg: permissionDeniedMsg,
});
} else{
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
}
} else {
toaster.danger(gettext('Please check the network.'), {duration: 3});
}
}); });
} }
onInvitePeople = () => { onInvitePeople = (invitationsArray) => {
invitationsArray.push.apply(invitationsArray,this.state.invitationsList);
this.setState({ this.setState({
showInvitationsModal: false, invitationsList: invitationsArray,
invitationsList: [], });
isLoading: true, }
onDeleteInvitation = (index) => {
this.state.invitationsList.splice(index, 1);
this.setState({
invitationsList: this.state.invitationsList,
}); });
this.listInvitations();
} }
componentDidMount() { componentDidMount() {
this.listInvitations(); this.listInvitations();
} }
toggleInvitationsModal = () => { toggleInvitePeopleDialog = () => {
this.setState({ this.setState({
showInvitationsModal: !this.state.showInvitationsModal isInvitePeopleDialogOpen: !this.state.isInvitePeopleDialogOpen
}); });
} }
@@ -169,36 +194,45 @@ class InvitationsView extends React.Component {
) )
} }
handlePermissionDenied = () => {
window.location = siteRoot;
return(
<div className="error mt-6 text-center">
<span>{this.state.permissionDeniedMsg}</span>
</div>
)
}
render() { render() {
return ( return (
<Fragment> <Fragment>
<InvitationsToolbar <InvitationsToolbar
onShowSidePanel={this.props.onShowSidePanel} onShowSidePanel={this.props.onShowSidePanel}
onSearchedClick={this.props.onSearchedClick} onSearchedClick={this.props.onSearchedClick}
toggleInvitationsModal={this.toggleInvitationsModal} toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
/> />
<div className="main-panel-center flex-row"> <div className="main-panel-center flex-row">
<div className="cur-view-container"> <div className="cur-view-container">
<div className="cur-view-path"> <div className="cur-view-path">
<h3 className="sf-heading">{gettext('Invite People')}</h3> <h3 className="sf-heading">{gettext('Invite People')}</h3>
</div> </div>
{this.state.isLoading && <Loading/>} <div className="cur-view-content">
{(!this.state.isLoading && this.state.errorMsg !== '') && this.state.errorMsg} {this.state.isLoading && <Loading/>}
{(!this.state.isLoading && this.state.invitationsList.length !== 0) && {(!this.state.isLoading && this.state.permissionDeniedMsg !== '') && this.handlePermissionDenied() }
< InvitationsListView {(!this.state.isLoading && this.state.showEmptyTip && this.state.invitationsList.length === 0) && this.emptyTip()}
invitationsList={this.state.invitationsList} {(!this.state.isLoading && this.state.invitationsList.length !== 0) &&
listInvitations={this.listInvitations} < InvitationsListView
/> invitationsList={this.state.invitationsList}
} onDeleteInvitation={this.onDeleteInvitation}
{(!this.state.isLoading && this.state.showEmptyTip && this.state.invitationsList.length === 0) && this.emptyTip()} />}
</div>
</div> </div>
</div> </div>
{this.state.showInvitationsModal && {this.state.isInvitePeopleDialogOpen &&
<InvitePeopleDialog <InvitePeopleDialog
toggleInvitationsModal={this.toggleInvitationsModal} toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
showInvitationsModal={this.state.showInvitationsModal} isInvitePeopleDialogOpen={this.state.isInvitePeopleDialogOpen}
onInvitePeople={this.onInvitePeople} onInvitePeople={this.onInvitePeople}
listInvitations={this.listInvitations}
/> />
} }
</Fragment> </Fragment>