diff --git a/frontend/src/components/dialog/invite-people-dialog.js b/frontend/src/components/dialog/invite-people-dialog.js index fc28b1a0b8..75eae2be3f 100644 --- a/frontend/src/components/dialog/invite-people-dialog.js +++ b/frontend/src/components/dialog/invite-people-dialog.js @@ -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 - } - this.setState({ - errorMsg: inviteFailed, - }); - this.props.listInvitations(); - } else { - this.setState({ - emails: '', - }); - this.props.onInvitePeople(); + let emailsArray = []; + emails = emails.split(','); + for (let i = 0; i < emails.length; i++) { + let email = emails[i].trim(); + if (email) { + emailsArray.push(email); + } + } + if (emailsArray.length) { + seafileAPI.invitePeople(emailsArray).then((res) => { + this.setState({ + emails: '', + }); + 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 { - this.setState({ - errorMsg: gettext('Email is required') - }); + if (this.state.emails){ + this.setState({ + errorMsg: gettext('Email is invalid.') + }); + } else { + this.setState({ + errorMsg: gettext('It is required.') + }); + } } } render() { return ( - - {gettext('Invite People')} + + {gettext('Invite People')} - - {gettext('Emails')} + {this.state.errorMsg} - + @@ -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, }; diff --git a/frontend/src/components/dialog/share-to-user.js b/frontend/src/components/dialog/share-to-user.js index 3341972cc2..ab68022e8e 100644 --- a/frontend/src/components/dialog/share-to-user.js +++ b/frontend/src/components/dialog/share-to-user.js @@ -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 && + + + {gettext('Invite People')} + + } diff --git a/frontend/src/components/toolbar/invitations-toolbar.js b/frontend/src/components/toolbar/invitations-toolbar.js index 3bb49ed05b..6b73049a1d 100644 --- a/frontend/src/components/toolbar/invitations-toolbar.js +++ b/frontend/src/components/toolbar/invitations-toolbar.js @@ -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 (
-
-
diff --git a/frontend/src/css/invitations.css b/frontend/src/css/invitations.css index 95c64d843f..c79a33abe8 100644 --- a/frontend/src/css/invitations.css +++ b/frontend/src/css/invitations.css @@ -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; -} \ No newline at end of file +} + +.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; +} diff --git a/frontend/src/pages/invitations/invitations-view.js b/frontend/src/pages/invitations/invitations-view.js index 5f326b0aaa..65ea3882b5 100644 --- a/frontend/src/pages/invitations/invitations-view.js +++ b/frontend/src/pages/invitations/invitations-view.js @@ -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 = ; + onClick={this.props.onItemDelete.bind(this, invitationItem.token, this.props.index)}>; return ( { + 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 ( ) }); return ( - - - - - - - - - - - - {invitationsTables} - -
emailinvite timeexpire timeaccept
+ + + + + + + + + + + + {invitationsListItems} + +
{gettext('Email')}{gettext('Invite Time')}{gettext('Expiration')}{gettext('Accepted')}
); } } 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( +
+ {this.state.permissionDeniedMsg} +
+ ) + } + render() { return (

{gettext('Invite People')}

- {this.state.isLoading && } - {(!this.state.isLoading && this.state.errorMsg !== '') && this.state.errorMsg} - {(!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()} +
+ {this.state.isLoading && } + {(!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} + onDeleteInvitation={this.onDeleteInvitation} + />} +
- {this.state.showInvitationsModal && + {this.state.isInvitePeopleDialogOpen && }