diff --git a/frontend/src/components/dialog/invite-people-dialog.js b/frontend/src/components/dialog/invite-people-dialog.js
new file mode 100644
index 0000000000..fc28b1a0b8
--- /dev/null
+++ b/frontend/src/components/dialog/invite-people-dialog.js
@@ -0,0 +1,99 @@
+import React from 'react';
+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";
+
+class InvitePeopleDialog extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ emails: '',
+ errorMsg: '',
+ };
+ }
+
+ handleEmailsChange = (event) => {
+ let emails = event.target.value;
+ this.setState({
+ emails: emails
+ });
+ if (this.state.errorMsg) {
+ this.setState({
+ errorMsg: ''
+ });
+ }
+ }
+
+ handleKeyDown = (e) => {
+ if (e.keyCode === 13) {
+ 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();
+ }
+ }).catch((error) => {
+ let errorMsg = gettext(error.response.data.error_msg);
+ this.setState({
+ errorMsg: errorMsg,
+ });
+ });
+ } else {
+ this.setState({
+ errorMsg: gettext('Email is required')
+ });
+ }
+ }
+
+ render() {
+ return (
+
+ {gettext('Invite People')}
+
+
+
+ {this.state.errorMsg}
+
+
+
+
+
+
+ );
+ }
+}
+
+const InvitePeopleDialogPropTypes = {
+ toggleInvitationsModal: PropTypes.func.isRequired,
+ showInvitationsModal: PropTypes.bool.isRequired,
+ onInvitePeople: PropTypes.func.isRequired,
+};
+
+InvitePeopleDialog.propTypes = InvitePeopleDialogPropTypes;
+
+export default InvitePeopleDialog;
\ No newline at end of file
diff --git a/frontend/src/components/toolbar/invitations-toolbar.js b/frontend/src/components/toolbar/invitations-toolbar.js
index f1a1111bcb..3bb49ed05b 100644
--- a/frontend/src/components/toolbar/invitations-toolbar.js
+++ b/frontend/src/components/toolbar/invitations-toolbar.js
@@ -8,6 +8,7 @@ import { gettext } from '../../utils/constants';
const propTypes = {
onShowSidePanel: PropTypes.func.isRequired,
onSearchedClick: PropTypes.func.isRequired,
+ toggleInvitationsModal: PropTypes.func.isRequired,
};
class InvitationsToolbar extends React.Component {
@@ -17,17 +18,17 @@ class InvitationsToolbar extends React.Component {
}
render() {
- let { onShowSidePanel, onSearchedClick } = this.props;
+ let { onShowSidePanel, onSearchedClick, toggleInvitationsModal } = this.props;
return (
+ className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
-
@@ -42,5 +43,6 @@ class InvitationsToolbar extends React.Component {
}
}
+InvitationsToolbar.propTypes = propTypes;
export default InvitationsToolbar;
diff --git a/frontend/src/css/invitations.css b/frontend/src/css/invitations.css
new file mode 100644
index 0000000000..95c64d843f
--- /dev/null
+++ b/frontend/src/css/invitations.css
@@ -0,0 +1,8 @@
+.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
diff --git a/frontend/src/pages/invitations/invitations-view.js b/frontend/src/pages/invitations/invitations-view.js
index a6691313b0..5f326b0aaa 100644
--- a/frontend/src/pages/invitations/invitations-view.js
+++ b/frontend/src/pages/invitations/invitations-view.js
@@ -1,27 +1,206 @@
-import React, { Fragment } from 'react';
+import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
-import { gettext } from '../../utils/constants';
+import {gettext} 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 "../../css/invitations.css";
+
+class InvitationsListItem extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isOperationShow: false,
+ };
+ }
+
+ onMouseEnter = (event) => {
+ event.preventDefault();
+ this.setState({
+ isOperationShow: true,
+ });
+ }
+
+ onMouseOver = () => {
+ this.setState({
+ isOperationShow: true,
+ });
+ }
+
+ onMouseLeave = () => {
+ this.setState({
+ isOperationShow: false,
+ });
+ }
+
+ render() {
+ const invitationItem = this.props.invitation;
+ const acceptIcon = ;
+ const deleteOperation = ;
+ return (
+
+ {invitationItem.accepter} |
+ {moment(invitationItem.invite_time).format("YYYY-MM-DD")} |
+ {moment(invitationItem.expire_time).format("YYYY-MM-DD")} |
+ {invitationItem.accept_time && acceptIcon} |
+ {!invitationItem.accept_time && deleteOperation} |
+
+ );
+ }
+}
+
+const InvitationsListItemPropTypes = {
+ invitation: PropTypes.object.isRequired,
+ onItemDelete: PropTypes.func.isRequired,
+};
+
+InvitationsListItem.propTypes = InvitationsListItemPropTypes;
+
+class InvitationsListView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ }
+
+ onItemDelete = (token) => {
+ seafileAPI.deleteInvitation(token).then((res) => {
+ this.props.listInvitations();
+ }).catch((error) => {
+ }); //todo
+ }
+
+ render() {
+ const invitationsTables = this.props.invitationsList.map((invitation, index) => {
+ return (
+ )
+ });
+
+ return (
+
+
+
+ email |
+ invite time |
+ expire time |
+ accept |
+ |
+
+
+
+ {invitationsTables}
+
+
+ );
+ }
+}
+
+const InvitationsListViewPropTypes = {
+ invitationsList: PropTypes.array.isRequired,
+};
+
+InvitationsListView.propTypes = InvitationsListViewPropTypes;
class InvitationsView extends React.Component {
-
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ showInvitationsModal: false,
+ invitationsList: [],
+ isLoading: true,
+ errorMsg: '',
+ showEmptyTip: false,
+ };
+ }
+
+ listInvitations = () => {
+ seafileAPI.listInvitations().then((res) => {
+ this.setState({
+ invitationsList: res.data,
+ showEmptyTip: true,
+ isLoading: false,
+ });
+ }).catch((error) => {
+ let errorMsg = gettext(error.response.data.error_msg);
+ this.setState({
+ errorMsg: errorMsg,
+ isLoading: false,
+ });
+ });
+ }
+
+ onInvitePeople = () => {
+ this.setState({
+ showInvitationsModal: false,
+ invitationsList: [],
+ isLoading: true,
+ });
+ this.listInvitations();
+ }
+
+ componentDidMount() {
+ this.listInvitations();
+ }
+
+ toggleInvitationsModal = () => {
+ this.setState({
+ showInvitationsModal: !this.state.showInvitationsModal
+ });
+ }
+
+ emptyTip = () => {
+ return (
+
+
{gettext('You have not invited any people.')}
+
+ )
+ }
+
render() {
return (
{gettext('Invite People')}
-
-
{gettext('You have not invited any 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.showInvitationsModal &&
+
+ }
);
}