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 ( + + + + + + + + + + + + {invitationsTables} + +
emailinvite timeexpire timeaccept
+ ); + } +} + +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 && + + }
); }