diff --git a/products/ASC.People/Client/src/components/Article/MainButton/index.js b/products/ASC.People/Client/src/components/Article/MainButton/index.js index cc4db40220..f241db21cc 100644 --- a/products/ASC.People/Client/src/components/Article/MainButton/index.js +++ b/products/ASC.People/Client/src/components/Article/MainButton/index.js @@ -7,12 +7,20 @@ import { DropDownItem, toastr } from "asc-web-components"; +import InviteDialog from './../../dialogs/Invite'; import { isAdmin } from '../../../store/auth/selectors'; import { withTranslation, I18nextProvider } from 'react-i18next'; import i18n from '../i18n'; import { typeUser, typeGuest, department } from './../../../helpers/customNames'; class PureArticleMainButtonContent extends React.Component { + constructor(props) { + super(props); + this.state = { + dialogVisible: false, + } + } + onDropDownItemClick = (link) => { this.props.history.push(link); }; @@ -21,48 +29,57 @@ class PureArticleMainButtonContent extends React.Component { toastr.success(text); }; + toggleDialogVisible = () => this.setState({ dialogVisible: !this.state.dialogVisible }); + render() { console.log("People ArticleMainButtonContent render"); const { isAdmin, settings, t } = this.props; return ( isAdmin ? - - + + + + + + + + + + - - - - - - - + > : <>> ); diff --git a/products/ASC.People/Client/src/components/dialogs/Invite/i18n.js b/products/ASC.People/Client/src/components/dialogs/Invite/i18n.js new file mode 100644 index 0000000000..5d4fb211bb --- /dev/null +++ b/products/ASC.People/Client/src/components/dialogs/Invite/i18n.js @@ -0,0 +1,57 @@ +import i18n from "i18next"; +import Backend from "i18next-xhr-backend"; +import config from "../../../../package.json"; + +const newInstance = i18n.createInstance(); + +if (process.env.NODE_ENV === "production") { + newInstance + .use(Backend) + .init({ + lng: 'en', + fallbackLng: "en", + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === 'lowercase') return value.toLowerCase(); + return value; + } + }, + + react: { + useSuspense: true + }, + backend: { + loadPath: `${config.homepage}/locales/Invite/{{lng}}/{{ns}}.json` + } + }); +} else if (process.env.NODE_ENV === "development") { + + const resources = { + en: { + translation: require("./locales/en/translation.json") + } + }; + + newInstance.init({ + resources: resources, + lng: 'en', + fallbackLng: "en", + debug: true, + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + format: function (value, format) { + if (format === 'lowercase') return value.toLowerCase(); + return value; + } + }, + + react: { + useSuspense: true + } + }); +} + +export default newInstance; \ No newline at end of file diff --git a/products/ASC.People/Client/src/components/dialogs/Invite/index.js b/products/ASC.People/Client/src/components/dialogs/Invite/index.js new file mode 100644 index 0000000000..aa519e0f1f --- /dev/null +++ b/products/ASC.People/Client/src/components/dialogs/Invite/index.js @@ -0,0 +1,224 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withRouter } from 'react-router'; +import { + toastr, + ModalDialog, + Link, + Checkbox, + Button, + Textarea, + Text +} from "asc-web-components"; +import { getInvitationLink, getShortenedLink } from '../../../store/profile/actions'; +import { withTranslation, I18nextProvider } from 'react-i18next'; +import i18n from './i18n'; +import { typeGuests } from './../../../helpers/customNames'; +import styled from 'styled-components' + +const ModalDialogContainer = styled.div` + .margin-text { + margin: 12px 0; + } + + .margin-link { + margin-right: 12px; + } + + .margin-textarea { + margin-top: 12px; + } + + .flex{ + display: flex; + justify-content: space-between; + } +`; + +const textAreaName = 'link-textarea'; + +class PureInviteDialog extends React.Component { + constructor(props) { + super(props); + this.state = { + isGuest: false, + peopleInvitationLink: '', + guestInvitationLink: '', + isLoading: true, + isLinkShort: false + } + } + + onCopyLinkToClipboard = () => { + const { t } = this.props; + const link = document.getElementsByName(textAreaName)[0]; + link.select(); + document.execCommand('copy'); + toastr.success(t('LinkCopySuccess')); + window.getSelection().removeAllRanges(); + link.blur(); + }; + + onCheckedGuest = () => this.setState({ isGuest: !this.state.isGuest }); + + onGetShortenedLink = () => { + this.setState({ isLoading: true }); + const { getShortenedLink } = this.props; + + getShortenedLink(this.state.peopleInvitationLink) + .then((res) => { + // console.log("getShortInvitationLinkPeople success", res.data.response); + this.setState({ peopleInvitationLink: res.data.response }); + }) + .catch(e => { + console.error("getShortInvitationLink error", e); + this.setState({ isLoading: false }); + }); + + getShortenedLink(this.state.guestInvitationLink) + .then((res) => { + // console.log("getShortInvitationLinkGuest success", res.data.response); + this.setState({ + guestInvitationLink: res.data.response, + isLoading: false, + isLinkShort: true + }); + }) + .catch(e => { + console.error("getShortInvitationLink error", e); + }); + + }; + + componentDidUpdate(prevProps, prevState) { + if (!prevProps.visible && !prevState.peopleInvitationLink && !prevState.guestInvitationLink) { + // console.log('INVITE DIALOG DidUpdate'); + const { getInvitationLink } = this.props; + const isGuest = true; + + getInvitationLink() + .then((res) => { + // console.log("getInvitationLinkPeople success", res.data.response); + this.setState({ + peopleInvitationLink: res.data.response, + isLoading: false + }); + this.onCopyLinkToClipboard(); + }) + .catch(e => { + console.error("getInvitationLinkPeople error", e); + this.setState({ isLoading: false }); + }); + + getInvitationLink(isGuest) + .then((res) => { + // console.log("getInvitationLinkGuest success", res.data.response); + this.setState({ guestInvitationLink: res.data.response }); + }) + .catch(e => { + console.error("getInvitationLinkGuest error", e); + this.setState({ isLoading: false }); + }); + }; + } + + onClickToCloseButton = () => this.props.onCloseButton && this.props.onCloseButton(); + + render() { + console.log("InviteDialog render"); + const { t, visible, onClose } = this.props; + const fakeSettings = { hasShortenService: false }; + + return ( + + onClose && onClose()} + + headerContent={t('InviteLinkTitle')} + + bodyContent={( + <> + + {t('HelpAnswerLinkInviteSettings')} + + + {t('InviteLinkValidInterval', { count: 7 })} + + + + + {t('CopyToClipboard')} + + { + fakeSettings.hasShortenService && !this.state.isLinkShort && + + {t('GetShortenLink')} + + } + + + + + > + )} + + footerContent={( + <> + + > + )} + /> + + ); + }; +}; + + +const mapStateToProps = (state) => { + return { + settings: state.auth.settings + } +} + +const InviteDialogContainer = withTranslation()(PureInviteDialog); + +const InviteDialog = (props) => ; + +InviteDialog.propTypes = { + visible: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onCloseButton: PropTypes.func.isRequired +}; + +export default connect(mapStateToProps, { getInvitationLink, getShortenedLink })(withRouter(InviteDialog)); \ No newline at end of file diff --git a/products/ASC.People/Client/src/components/dialogs/Invite/locales/en/translation.json b/products/ASC.People/Client/src/components/dialogs/Invite/locales/en/translation.json new file mode 100644 index 0000000000..55265a508b --- /dev/null +++ b/products/ASC.People/Client/src/components/dialogs/Invite/locales/en/translation.json @@ -0,0 +1,13 @@ +{ + "InviteLinkTitle": "Invitation link", + "HelpAnswerLinkInviteSettings": "Share the link to invite your colleagues to your portal.", + "InviteLinkValidInterval": "This link is valid for {{ count }} day only.", + "CopyToClipboard": "Copy the link", + "CloseButton": "Close", + "LinkCopySuccess": "Link has been copied to the clipboard", + "GetShortenLink": "Get shortened link", + "InviteUsersAsCollaborators": "Add users as {{typeGuests, lowercase}}", + "LoadingProcessing": "Loading...", + + "InviteLinkValidInterval_plural": "This link is valid for {{ count }} days only." +} \ No newline at end of file diff --git a/products/ASC.People/Client/src/helpers/customNames.js b/products/ASC.People/Client/src/helpers/customNames.js index 7e913808b2..31116ebb4a 100644 --- a/products/ASC.People/Client/src/helpers/customNames.js +++ b/products/ASC.People/Client/src/helpers/customNames.js @@ -3,5 +3,6 @@ export const department = 'Department'; export const position = 'Position'; export const employedSinceDate = 'Employed since'; export const typeGuest = 'Guest'; +export const typeGuests = 'Guests'; export const typeUser = 'Employee'; export const headOfDepartment = 'Head of Department'; diff --git a/products/ASC.People/Client/src/resourceConfig.json b/products/ASC.People/Client/src/resourceConfig.json index a8fc1c1970..d7bc3984f6 100644 --- a/products/ASC.People/Client/src/resourceConfig.json +++ b/products/ASC.People/Client/src/resourceConfig.json @@ -9,6 +9,22 @@ "ImportPeople" ] }, + "dialogs": { + "Invite": { + "Resource": [ + "HelpAnswerLinkInviteSettings", + "CopyToClipboard", + "CloseButton", + "GetShortenLink", + "LoadingProcessing", + "InviteUsersAsCollaborators", + "InviteLinkTitle" + ], + "ResourceJS": [ + "LinkCopySuccess" + ] + } +}, "pages": { "Profile": { "Resource": [ diff --git a/products/ASC.People/Client/src/store/profile/actions.js b/products/ASC.People/Client/src/store/profile/actions.js index e4a775f042..7edf502f5a 100644 --- a/products/ASC.People/Client/src/store/profile/actions.js +++ b/products/ASC.People/Client/src/store/profile/actions.js @@ -31,7 +31,7 @@ export function employeeWrapperToMemberModel(profile) { const department = profile.groups ? profile.groups.map(group => group.id) : []; const worksFrom = profile.workFrom; - return {...profile, comment, department, worksFrom}; + return { ...profile, comment, department, worksFrom }; } export function fetchProfile(userName) { @@ -56,8 +56,8 @@ export function fetchProfile(userName) { export function createProfile(profile) { return (dispatch, getState) => { - const {people} = getState(); - const {filter} = people; + const { people } = getState(); + const { filter } = people; const member = employeeWrapperToMemberModel(profile); let result; @@ -75,8 +75,8 @@ export function createProfile(profile) { export function updateProfile(profile) { return (dispatch, getState) => { - const {people} = getState(); - const {filter} = people; + const { people } = getState(); + const { filter } = people; const member = employeeWrapperToMemberModel(profile); let result; @@ -112,4 +112,24 @@ export function updateAvatar(profileId, images) { }); } }; -}; \ No newline at end of file +}; + +export function getInvitationLink(isGuest = false) { + return dispatch => { + return api.getInvitationLink(isGuest) + .then(res => { + checkResponseError(res); + return Promise.resolve(res); + }); + } +} + +export function getShortenedLink(link) { + return dispatch => { + return api.getShortenedLink(link) + .then(res => { + checkResponseError(res); + return Promise.resolve(res); + }); + } +} \ No newline at end of file diff --git a/products/ASC.People/Client/src/store/services/api.js b/products/ASC.People/Client/src/store/services/api.js index d74d3b9f41..984bb6bb0e 100644 --- a/products/ASC.People/Client/src/store/services/api.js +++ b/products/ASC.People/Client/src/store/services/api.js @@ -152,6 +152,20 @@ export function getGroup(groupId) { : axios.get(`${API_URL}/group/${groupId}.json`); } +export function getInvitationLink(isGuest) { + return IS_FAKE + ? fakeApi.getInvitationLink(isGuest) + : isGuest + ? axios.get(`${API_URL}/portal/users/invite/2.json`) + : axios.get(`${API_URL}/portal/users/invite/1.json`); +} + +export function getShortenedLink(link) { + return IS_FAKE + ? fakeApi.getShortenedLink(link) + : axios.put(`${API_URL}/portal/getshortenlink.json`, link); +} + function CheckError(res) { if (res.data && res.data.error) { const error = res.data.error.message || "Unknown error has happened"; diff --git a/products/ASC.People/Client/src/store/services/fakeApi.js b/products/ASC.People/Client/src/store/services/fakeApi.js index f3af95f932..a66e6cce4a 100644 --- a/products/ASC.People/Client/src/store/services/fakeApi.js +++ b/products/ASC.People/Client/src/store/services/fakeApi.js @@ -558,3 +558,13 @@ export function getGroup(groupId) { ] }); } + +export function getInvitationLink(isGuest) { + return fakeResponse(isGuest + ? "guest invitation link" + : "user invitation link"); +} + +export function getShortenedLink(link) { + return fakeResponse("SHORT LINK: " + link); +}