mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 07:55:36 +00:00
invite link in popup
This commit is contained in:
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import {gettext} from '../../utils/constants';
|
||||
import {seafileAPI} from '../../utils/seafile-api';
|
||||
import {Modal, ModalHeader, ModalBody, ModalFooter, Input, Button} from 'reactstrap';
|
||||
import InvitationsToolbar from "../toolbar/invitations-toolbar";
|
||||
import toaster from "../toast";
|
||||
|
||||
class InvitePeopleDialog extends React.Component {
|
||||
|
||||
@@ -29,50 +29,75 @@ class InvitePeopleDialog extends React.Component {
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
this.handleSubmitInvite();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmitInvite = () => {
|
||||
let emails = this.state.emails.trim();
|
||||
if (emails) {
|
||||
seafileAPI.invitePeople(emails).then((res) => {
|
||||
|
||||
if (res.data.failed.length > 0) {
|
||||
let inviteFailed = '';
|
||||
for (let i = 0; i < res.data.failed.length; i++) {
|
||||
inviteFailed += res.data.failed[i].error_msg
|
||||
let emailsArray = [];
|
||||
emails = emails.split(',');
|
||||
for (let i = 0; i < emails.length; i++) {
|
||||
let email = emails[i].trim();
|
||||
if (email) {
|
||||
emailsArray.push(email);
|
||||
}
|
||||
this.setState({
|
||||
errorMsg: inviteFailed,
|
||||
});
|
||||
this.props.listInvitations();
|
||||
} else {
|
||||
}
|
||||
if (emailsArray.length) {
|
||||
seafileAPI.invitePeople(emailsArray).then((res) => {
|
||||
this.setState({
|
||||
emails: '',
|
||||
});
|
||||
this.props.onInvitePeople();
|
||||
this.props.toggleInvitePeopleDialog();
|
||||
// success messages
|
||||
let successMsg = '';
|
||||
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) => {
|
||||
let errorMsg = gettext(error.response.data.error_msg);
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
this.props.toggleInvitePeopleDialog();
|
||||
if (error.response){
|
||||
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
|
||||
} else {
|
||||
toaster.danger(gettext('Please check the network.'), {duration: 3});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.state.emails){
|
||||
this.setState({
|
||||
errorMsg: gettext('Email is invalid.')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errorMsg: gettext('Email is required')
|
||||
errorMsg: gettext('It is required.')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isOpen={this.props.showInvitationsModal} toggle={this.props.toggleInvitationsModal}>
|
||||
<ModalHeader toggle={this.props.toggleInvitationsModal}>{gettext('Invite People')}</ModalHeader>
|
||||
<Modal isOpen={this.props.isInvitePeopleDialogOpen} toggle={this.props.toggleInvitePeopleDialog}>
|
||||
<ModalHeader toggle={this.props.toggleInvitePeopleDialog}>{gettext('Invite People')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<label htmlFor="emails">{gettext('Email')}</label>
|
||||
<Input type="text" id="emails" placeholder="Emails, separated by ','"
|
||||
<label htmlFor="emails">{gettext('Emails')}</label>
|
||||
<Input type="text" id="emails" placeholder={gettext("Emails, separated by ','")}
|
||||
value={this.state.emails}
|
||||
onChange={this.handleEmailsChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
@@ -80,7 +105,7 @@ class InvitePeopleDialog extends React.Component {
|
||||
<span className="error">{this.state.errorMsg}</span>
|
||||
</ModalBody>
|
||||
<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>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
@@ -89,8 +114,8 @@ class InvitePeopleDialog extends React.Component {
|
||||
}
|
||||
|
||||
const InvitePeopleDialogPropTypes = {
|
||||
toggleInvitationsModal: PropTypes.func.isRequired,
|
||||
showInvitationsModal: PropTypes.bool.isRequired,
|
||||
toggleInvitePeopleDialog: PropTypes.func.isRequired,
|
||||
isInvitePeopleDialogOpen: PropTypes.bool.isRequired,
|
||||
onInvitePeople: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, isPro } from '../../utils/constants';
|
||||
import {gettext, isPro, canInvitePeople, siteRoot} from '../../utils/constants';
|
||||
import { Button } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import UserSelect from '../user-select';
|
||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||
import "../../css/invitations.css";
|
||||
|
||||
class UserItem extends React.Component {
|
||||
|
||||
@@ -318,6 +319,12 @@ class ShareToUser extends React.Component {
|
||||
deleteShareItem={this.deleteShareItem}
|
||||
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>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
@@ -8,7 +8,7 @@ import { gettext } from '../../utils/constants';
|
||||
const propTypes = {
|
||||
onShowSidePanel: PropTypes.func.isRequired,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
toggleInvitationsModal: PropTypes.func.isRequired,
|
||||
toggleInvitePeopleDialog: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class InvitationsToolbar extends React.Component {
|
||||
@@ -18,17 +18,16 @@ class InvitationsToolbar extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { onShowSidePanel, onSearchedClick, toggleInvitationsModal } = this.props;
|
||||
let { onShowSidePanel, onSearchedClick, toggleInvitePeopleDialog } = this.props;
|
||||
return (
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span title="Side Nav Menu" onClick={onShowSidePanel}
|
||||
className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
|
||||
</span>
|
||||
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
<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')}
|
||||
</Button>
|
||||
</div>
|
||||
|
@@ -1,8 +1,23 @@
|
||||
.invite-accept-icon{
|
||||
color:green;
|
||||
.invite-accept-icon {
|
||||
color: green;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
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;
|
||||
}
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {gettext} from '../../utils/constants';
|
||||
import {gettext, siteRoot} from '../../utils/constants';
|
||||
import InvitationsToolbar from '../../components/toolbar/invitations-toolbar';
|
||||
import InvitePeopleDialog from "../../components/dialog/invite-people-dialog";
|
||||
import {seafileAPI} from "../../utils/seafile-api";
|
||||
import {Table} from 'reactstrap';
|
||||
import Loading from "../../components/loading";
|
||||
import moment from 'moment';
|
||||
import toaster from '../../components/toast';
|
||||
|
||||
import "../../css/invitations.css";
|
||||
|
||||
class InvitationsListItem extends React.Component {
|
||||
@@ -43,7 +45,7 @@ class InvitationsListItem extends React.Component {
|
||||
const deleteOperation = <i className="action-icon sf2-icon-x3"
|
||||
title={gettext('Delete')}
|
||||
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 (
|
||||
<tr onMouseEnter={this.onMouseEnter}
|
||||
onMouseOver={this.onMouseOver}
|
||||
@@ -71,20 +73,27 @@ class InvitationsListView extends React.Component {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onItemDelete = (token) => {
|
||||
onItemDelete = (token, index) => {
|
||||
seafileAPI.deleteInvitation(token).then((res) => {
|
||||
this.props.listInvitations();
|
||||
this.props.onDeleteInvitation(index);
|
||||
toaster.success(gettext('Successfully deleted 1 item.'), {duration: 1});
|
||||
}).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() {
|
||||
const invitationsTables = this.props.invitationsList.map((invitation, index) => {
|
||||
const invitationsListItems = this.props.invitationsList.map((invitation, index) => {
|
||||
return (
|
||||
<InvitationsListItem
|
||||
key={index}
|
||||
onItemDelete={this.onItemDelete}
|
||||
invitation={invitation}
|
||||
index={index}
|
||||
/>)
|
||||
});
|
||||
|
||||
@@ -92,15 +101,15 @@ class InvitationsListView extends React.Component {
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">email</th>
|
||||
<th width="20%">invite time</th>
|
||||
<th width="20%">expire time</th>
|
||||
<th width="10%">accept</th>
|
||||
<th width="10%"></th>
|
||||
<th width="25%">{gettext('Email')}</th>
|
||||
<th width="20%">{gettext('Invite Time')}</th>
|
||||
<th width="20%">{gettext('Expiration')}</th>
|
||||
<th width="18%">{gettext('Accepted')}</th>
|
||||
<th width="7%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{invitationsTables}
|
||||
{invitationsListItems}
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
@@ -109,6 +118,7 @@ class InvitationsListView extends React.Component {
|
||||
|
||||
const InvitationsListViewPropTypes = {
|
||||
invitationsList: PropTypes.array.isRequired,
|
||||
onDeleteInvitation: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
InvitationsListView.propTypes = InvitationsListViewPropTypes;
|
||||
@@ -118,10 +128,10 @@ class InvitationsView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showInvitationsModal: false,
|
||||
isInvitePeopleDialogOpen: false,
|
||||
invitationsList: [],
|
||||
isLoading: true,
|
||||
errorMsg: '',
|
||||
permissionDeniedMsg: '',
|
||||
showEmptyTip: false,
|
||||
};
|
||||
}
|
||||
@@ -134,30 +144,45 @@ class InvitationsView extends React.Component {
|
||||
isLoading: false,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errorMsg = gettext(error.response.data.error_msg);
|
||||
this.setState({
|
||||
errorMsg: errorMsg,
|
||||
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({
|
||||
showInvitationsModal: false,
|
||||
invitationsList: [],
|
||||
isLoading: true,
|
||||
invitationsList: invitationsArray,
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteInvitation = (index) => {
|
||||
this.state.invitationsList.splice(index, 1);
|
||||
this.setState({
|
||||
invitationsList: this.state.invitationsList,
|
||||
});
|
||||
this.listInvitations();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listInvitations();
|
||||
}
|
||||
|
||||
toggleInvitationsModal = () => {
|
||||
toggleInvitePeopleDialog = () => {
|
||||
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() {
|
||||
return (
|
||||
<Fragment>
|
||||
<InvitationsToolbar
|
||||
onShowSidePanel={this.props.onShowSidePanel}
|
||||
onSearchedClick={this.props.onSearchedClick}
|
||||
toggleInvitationsModal={this.toggleInvitationsModal}
|
||||
toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
|
||||
/>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{gettext('Invite People')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
{this.state.isLoading && <Loading/>}
|
||||
{(!this.state.isLoading && this.state.errorMsg !== '') && this.state.errorMsg}
|
||||
{(!this.state.isLoading && this.state.permissionDeniedMsg !== '') && this.handlePermissionDenied() }
|
||||
{(!this.state.isLoading && this.state.showEmptyTip && this.state.invitationsList.length === 0) && this.emptyTip()}
|
||||
{(!this.state.isLoading && this.state.invitationsList.length !== 0) &&
|
||||
< InvitationsListView
|
||||
invitationsList={this.state.invitationsList}
|
||||
listInvitations={this.listInvitations}
|
||||
/>
|
||||
}
|
||||
{(!this.state.isLoading && this.state.showEmptyTip && this.state.invitationsList.length === 0) && this.emptyTip()}
|
||||
onDeleteInvitation={this.onDeleteInvitation}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showInvitationsModal &&
|
||||
</div>
|
||||
{this.state.isInvitePeopleDialogOpen &&
|
||||
<InvitePeopleDialog
|
||||
toggleInvitationsModal={this.toggleInvitationsModal}
|
||||
showInvitationsModal={this.state.showInvitationsModal}
|
||||
toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
|
||||
isInvitePeopleDialogOpen={this.state.isInvitePeopleDialogOpen}
|
||||
onInvitePeople={this.onInvitePeople}
|
||||
listInvitations={this.listInvitations}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
|
Reference in New Issue
Block a user