fix: restructure code and show content based on permission (#261)
* fix: restructure code and show content based on permission - Restructure Invite user forms into separate App and Org forms - Show Modal content based on permissions. * update: Permission handling. * fix: Modify permission handling * fix: check manage permission for manage users btn and show copy to clipboard always. * Dummy commit to run the tests * Fix: Test cases * Another dummy commit to run the tests Co-authored-by: Trisha Anand <trisha@appsmith.com>
This commit is contained in:
parent
368ed79f4b
commit
68e048761a
|
|
@ -7,7 +7,7 @@
|
||||||
- `cd internal-tools-client` Change directory to the project directory
|
- `cd internal-tools-client` Change directory to the project directory
|
||||||
- `nvm install` Install the version of `node` and `npm` required by the project using `nvm`
|
- `nvm install` Install the version of `node` and `npm` required by the project using `nvm`
|
||||||
- `yarn` Install packages and run setup scripts
|
- `yarn` Install packages and run setup scripts
|
||||||
- `yarn start` Deploy locally
|
- `yarn start` Start the client locally using this
|
||||||
|
|
||||||
> For more details on how to run this locally, please visit: [Notion Doc](https://www.notion.so/appsmith/How-to-run-the-code-e031545454874419b9f72cd51feb90ff)
|
> For more details on how to run this locally, please visit: [Notion Doc](https://www.notion.so/appsmith/How-to-run-the-code-e031545454874419b9f72cd51feb90ff)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ describe("Create new org and share with a user", function() {
|
||||||
cy.get(homePage.searchInput).type(appid);
|
cy.get(homePage.searchInput).type(appid);
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.contains(orgid);
|
cy.contains(orgid);
|
||||||
cy.xpath(homePage.ShareBtn).should("not.be.visible");
|
cy.xpath(homePage.ShareBtn).should("be.visible");
|
||||||
cy.get(homePage.appEditIcon)
|
cy.get(homePage.appEditIcon)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
|
|
|
||||||
|
|
@ -1371,7 +1371,9 @@ Cypress.Commands.add("startServerAndRoutes", () => {
|
||||||
|
|
||||||
cy.route("POST", "/api/v1/organizations").as("createOrg");
|
cy.route("POST", "/api/v1/organizations").as("createOrg");
|
||||||
cy.route("POST", "/api/v1/users/invite").as("postInvite");
|
cy.route("POST", "/api/v1/users/invite").as("postInvite");
|
||||||
cy.route("GET", "/api/v1/organizations/roles").as("getRoles");
|
cy.route("GET", "/api/v1/organizations/roles?organizationId=*").as(
|
||||||
|
"getRoles",
|
||||||
|
);
|
||||||
cy.route("GET", "/api/v1/users/me").as("getUser");
|
cy.route("GET", "/api/v1/users/me").as("getUser");
|
||||||
cy.route("POST", "/api/v1/pages").as("createPage");
|
cy.route("POST", "/api/v1/pages").as("createPage");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,10 @@ class OrgApi extends Api {
|
||||||
): AxiosPromise<FetchAllUsersResponse> {
|
): AxiosPromise<FetchAllUsersResponse> {
|
||||||
return Api.get(OrgApi.orgsURL + "/" + request.orgId + "/members");
|
return Api.get(OrgApi.orgsURL + "/" + request.orgId + "/members");
|
||||||
}
|
}
|
||||||
static fetchAllRoles(): AxiosPromise<FetchAllRolesResponse> {
|
static fetchAllRoles(
|
||||||
return Api.get(OrgApi.orgsURL + "/roles");
|
request: FetchAllRolesRequest,
|
||||||
|
): AxiosPromise<FetchAllRolesResponse> {
|
||||||
|
return Api.get(OrgApi.orgsURL + `/roles?organizationId=${request.orgId}`);
|
||||||
}
|
}
|
||||||
static changeOrgUserRole(
|
static changeOrgUserRole(
|
||||||
request: ChangeUserRoleRequest,
|
request: ChangeUserRoleRequest,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React, { createRef, useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
|
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
const StyledInput = styled.input`
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #d3dee3;
|
||||||
|
border-right: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #768896;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
width: 90%;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectButton = styled(BaseButton)`
|
||||||
|
&&&& {
|
||||||
|
max-width: 70px;
|
||||||
|
margin: 0 0px;
|
||||||
|
min-height: 32px;
|
||||||
|
border-radius: 0px 4px 4px 0px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f6f7f8;
|
||||||
|
font-size: 14px;
|
||||||
|
&.bp3-button {
|
||||||
|
padding: 0px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CopyToClipboard = (props: any) => {
|
||||||
|
const { copyText } = props;
|
||||||
|
const copyURLInput = createRef<HTMLInputElement>();
|
||||||
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyToClipboard = (url: string) => {
|
||||||
|
copy(url);
|
||||||
|
setIsCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectText = () => {
|
||||||
|
if (copyURLInput.current) {
|
||||||
|
copyURLInput.current.setSelectionRange(0, copyText.length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<StyledInput
|
||||||
|
type="text"
|
||||||
|
ref={copyURLInput}
|
||||||
|
readOnly
|
||||||
|
onClick={() => {
|
||||||
|
selectText();
|
||||||
|
}}
|
||||||
|
value={copyText}
|
||||||
|
/>
|
||||||
|
<SelectButton
|
||||||
|
text={isCopied ? "Copied" : "Copy"}
|
||||||
|
accent="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboard(copyText);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyToClipboard;
|
||||||
|
|
@ -22,7 +22,7 @@ import { AppState } from "reducers";
|
||||||
import { getEditorURL } from "selectors/appViewSelectors";
|
import { getEditorURL } from "selectors/appViewSelectors";
|
||||||
import { getPageList } from "selectors/editorSelectors";
|
import { getPageList } from "selectors/editorSelectors";
|
||||||
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
|
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
|
||||||
import InviteUsersFormv2 from "pages/organization/InviteUsersFromv2";
|
import AppInviteUsersForm from "pages/organization/AppInviteUsersForm";
|
||||||
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
||||||
import { HeaderIcons } from "icons/HeaderIcons";
|
import { HeaderIcons } from "icons/HeaderIcons";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
|
|
@ -119,10 +119,6 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => {
|
||||||
const userPermissions = currentApplicationDetails?.userPermissions ?? [];
|
const userPermissions = currentApplicationDetails?.userPermissions ?? [];
|
||||||
const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION;
|
const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION;
|
||||||
const canEdit = isPermitted(userPermissions, permissionRequired);
|
const canEdit = isPermitted(userPermissions, permissionRequired);
|
||||||
const canShare = isPermitted(
|
|
||||||
userPermissions,
|
|
||||||
PERMISSION_TYPE.MANAGE_APPLICATION,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderWrapper hasPages={pages.length > 1}>
|
<HeaderWrapper hasPages={pages.length > 1}>
|
||||||
|
|
@ -142,7 +138,6 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => {
|
||||||
<HeaderSection justify={"flex-end"}>
|
<HeaderSection justify={"flex-end"}>
|
||||||
{currentApplicationDetails && (
|
{currentApplicationDetails && (
|
||||||
<>
|
<>
|
||||||
{canShare && (
|
|
||||||
<FormDialogComponent
|
<FormDialogComponent
|
||||||
trigger={
|
trigger={
|
||||||
<ShareButton
|
<ShareButton
|
||||||
|
|
@ -160,12 +155,12 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
Form={InviteUsersFormv2}
|
Form={AppInviteUsersForm}
|
||||||
orgId={currentOrgId}
|
orgId={currentOrgId}
|
||||||
applicationId={currentApplicationDetails.id}
|
applicationId={currentApplicationDetails.id}
|
||||||
title={currentApplicationDetails.name}
|
title={currentApplicationDetails.name}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{props.url && canEdit && (
|
{props.url && canEdit && (
|
||||||
<BackToEditorButton
|
<BackToEditorButton
|
||||||
className="t--back-to-editor"
|
className="t--back-to-editor"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import SubHeader from "pages/common/SubHeader";
|
||||||
import PageSectionDivider from "pages/common/PageSectionDivider";
|
import PageSectionDivider from "pages/common/PageSectionDivider";
|
||||||
import ApplicationCard from "./ApplicationCard";
|
import ApplicationCard from "./ApplicationCard";
|
||||||
import CreateApplicationForm from "./CreateApplicationForm";
|
import CreateApplicationForm from "./CreateApplicationForm";
|
||||||
import InviteUsersFormv2 from "pages/organization/InviteUsersFromv2";
|
import OrgInviteUsersForm from "pages/organization/OrgInviteUsersForm";
|
||||||
import { PERMISSION_TYPE, isPermitted } from "./permissionHelpers";
|
import { PERMISSION_TYPE, isPermitted } from "./permissionHelpers";
|
||||||
import { MenuIcons } from "icons/MenuIcons";
|
import { MenuIcons } from "icons/MenuIcons";
|
||||||
import { DELETING_APPLICATION } from "constants/messages";
|
import { DELETING_APPLICATION } from "constants/messages";
|
||||||
|
|
@ -143,7 +143,7 @@ class Applications extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const Form: any = InviteUsersFormv2;
|
const Form: any = OrgInviteUsersForm;
|
||||||
const DropdownProps = (
|
const DropdownProps = (
|
||||||
user: User,
|
user: User,
|
||||||
orgName: string,
|
orgName: string,
|
||||||
|
|
@ -230,6 +230,7 @@ class Applications extends Component<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrgSection className="t--org-section" key={index}>
|
<OrgSection className="t--org-section" key={index}>
|
||||||
|
<OrgDropDown>
|
||||||
{!isPermitted(
|
{!isPermitted(
|
||||||
organization.userPermissions,
|
organization.userPermissions,
|
||||||
PERMISSION_TYPE.MANAGE_ORGANIZATION,
|
PERMISSION_TYPE.MANAGE_ORGANIZATION,
|
||||||
|
|
@ -243,7 +244,7 @@ class Applications extends Component<
|
||||||
{organization.name}
|
{organization.name}
|
||||||
</OrgName>
|
</OrgName>
|
||||||
) : (
|
) : (
|
||||||
<OrgDropDown>
|
<>
|
||||||
{this.props.currentUser && (
|
{this.props.currentUser && (
|
||||||
<CustomizedDropdown
|
<CustomizedDropdown
|
||||||
{...DropdownProps(
|
{...DropdownProps(
|
||||||
|
|
@ -270,6 +271,12 @@ class Applications extends Component<
|
||||||
<Form orgId={organization.id} />
|
<Form orgId={organization.id} />
|
||||||
</div>
|
</div>
|
||||||
</StyledDialog>
|
</StyledDialog>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{isPermitted(
|
||||||
|
organization.userPermissions,
|
||||||
|
PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION,
|
||||||
|
) && (
|
||||||
<FormDialogComponent
|
<FormDialogComponent
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -280,12 +287,12 @@ class Applications extends Component<
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
canOutsideClickClose={true}
|
canOutsideClickClose={true}
|
||||||
Form={InviteUsersFormv2}
|
Form={OrgInviteUsersForm}
|
||||||
orgId={organization.id}
|
orgId={organization.id}
|
||||||
title={`Invite Users to ${organization.name}`}
|
title={`Invite Users to ${organization.name}`}
|
||||||
/>
|
/>
|
||||||
</OrgDropDown>
|
|
||||||
)}
|
)}
|
||||||
|
</OrgDropDown>
|
||||||
<ApplicationCardsWrapper key={organization.id}>
|
<ApplicationCardsWrapper key={organization.id}>
|
||||||
<FormDialogComponent
|
<FormDialogComponent
|
||||||
permissions={organization.userPermissions}
|
permissions={organization.userPermissions}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ export enum PERMISSION_TYPE {
|
||||||
MANAGE_APPLICATION = "manage:applications",
|
MANAGE_APPLICATION = "manage:applications",
|
||||||
READ_APPLICATION = "read:applications",
|
READ_APPLICATION = "read:applications",
|
||||||
READ_ORGANIZATION = "read:organizations",
|
READ_ORGANIZATION = "read:organizations",
|
||||||
|
INVITE_USER_TO_ORGANIZATION = "inviteUsers:organization",
|
||||||
|
MAKE_PUBLIC_APPLICATION = "makePublic:applications",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isPermitted = (permissions: string[], type: string) => {
|
export const isPermitted = (permissions: string[], type: string) => {
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import {
|
||||||
APPLICATIONS_URL,
|
APPLICATIONS_URL,
|
||||||
getApplicationViewerPageURL,
|
getApplicationViewerPageURL,
|
||||||
} from "constants/routes";
|
} from "constants/routes";
|
||||||
import {
|
import AppInviteUsersForm from "pages/organization/AppInviteUsersForm";
|
||||||
PERMISSION_TYPE,
|
|
||||||
isPermitted,
|
|
||||||
} from "pages/Applications/permissionHelpers";
|
|
||||||
import InviteUsersFormv2 from "pages/organization/InviteUsersFromv2";
|
|
||||||
import Button from "components/editorComponents/Button";
|
import Button from "components/editorComponents/Button";
|
||||||
import StyledHeader from "components/designSystems/appsmith/StyledHeader";
|
import StyledHeader from "components/designSystems/appsmith/StyledHeader";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
|
|
@ -183,9 +179,6 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const applicationPermissions = currentApplication?.userPermissions
|
|
||||||
? currentApplication.userPermissions
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderWrapper>
|
<HeaderWrapper>
|
||||||
|
|
@ -218,10 +211,6 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
||||||
<HeaderIcons.FEEDBACK color={Colors.WHITE} width={13} height={13} />
|
<HeaderIcons.FEEDBACK color={Colors.WHITE} width={13} height={13} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{isPermitted(
|
|
||||||
applicationPermissions,
|
|
||||||
PERMISSION_TYPE.MANAGE_APPLICATION,
|
|
||||||
) && (
|
|
||||||
<FormDialogComponent
|
<FormDialogComponent
|
||||||
trigger={
|
trigger={
|
||||||
<ShareButton
|
<ShareButton
|
||||||
|
|
@ -239,14 +228,14 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
Form={InviteUsersFormv2}
|
canOutsideClickClose={true}
|
||||||
|
Form={AppInviteUsersForm}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
applicationId={applicationId}
|
applicationId={applicationId}
|
||||||
title={
|
title={
|
||||||
currentApplication ? currentApplication.name : "Share Application"
|
currentApplication ? currentApplication.name : "Share Application"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<DeploySection>
|
<DeploySection>
|
||||||
<DeployButton
|
<DeployButton
|
||||||
onClick={handlePublish}
|
onClick={handlePublish}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@ import Badge from "./Badge";
|
||||||
import { Directions } from "utils/helpers";
|
import { Directions } from "utils/helpers";
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
import { getOnSelectAction, DropdownOnSelectActions } from "./dropdownHelpers";
|
import { getOnSelectAction, DropdownOnSelectActions } from "./dropdownHelpers";
|
||||||
import DropdownComponent, { CustomizedDropdownProps } from "./index";
|
import { CustomizedDropdownProps } from "./index";
|
||||||
import { Org } from "constants/orgConstants";
|
import { Org } from "constants/orgConstants";
|
||||||
import { User } from "constants/userConstants";
|
import { User } from "constants/userConstants";
|
||||||
import InviteUsersFormv2 from "pages/organization/InviteUsersFromv2";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import FormDialogComponent from "components/editorComponents/form/FormDialogComponent";
|
|
||||||
import { Button } from "@blueprintjs/core";
|
|
||||||
|
|
||||||
const switchdropdown = (
|
const switchdropdown = (
|
||||||
orgs: Org[],
|
orgs: Org[],
|
||||||
|
|
|
||||||
146
app/client/src/pages/organization/AppInviteUsersForm.tsx
Normal file
146
app/client/src/pages/organization/AppInviteUsersForm.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { AppState } from "reducers";
|
||||||
|
import { getCurrentOrg } from "selectors/organizationSelectors";
|
||||||
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
|
import CopyToClipBoard from "components/designSystems/appsmith/CopyToClipBoard";
|
||||||
|
import {
|
||||||
|
isPermitted,
|
||||||
|
PERMISSION_TYPE,
|
||||||
|
} from "../Applications/permissionHelpers";
|
||||||
|
import { getDefaultPageId } from "sagas/SagaUtils";
|
||||||
|
import { getApplicationViewerPageURL } from "constants/routes";
|
||||||
|
import OrgInviteUsersForm from "./OrgInviteUsersForm";
|
||||||
|
import { StyledSwitch } from "components/propertyControls/StyledControls";
|
||||||
|
import Spinner from "components/editorComponents/Spinner";
|
||||||
|
import { getCurrentUser } from "selectors/usersSelectors";
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 10px 0px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShareWithPublicOption = styled.div`
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
padding: 10px 0px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShareToggle = styled.div`
|
||||||
|
{
|
||||||
|
&&& label {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
&&& div {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AppInviteUsersForm = (props: any) => {
|
||||||
|
const {
|
||||||
|
isFetchingApplication,
|
||||||
|
isChangingViewAccess,
|
||||||
|
currentApplicationDetails,
|
||||||
|
changeAppViewAccess,
|
||||||
|
applicationId,
|
||||||
|
fetchCurrentOrg,
|
||||||
|
currentOrg,
|
||||||
|
currentUser,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const userOrgPermissions = currentOrg?.userPermissions ?? [];
|
||||||
|
const userAppPermissions = currentApplicationDetails?.userPermissions ?? [];
|
||||||
|
const canInviteToOrg = isPermitted(
|
||||||
|
userOrgPermissions,
|
||||||
|
PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION,
|
||||||
|
);
|
||||||
|
const canShareWithPublic = isPermitted(
|
||||||
|
userAppPermissions,
|
||||||
|
PERMISSION_TYPE.MAKE_PUBLIC_APPLICATION,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getViewApplicationURL = () => {
|
||||||
|
const defaultPageId = getDefaultPageId(currentApplicationDetails.pages);
|
||||||
|
const appViewEndPoint = getApplicationViewerPageURL(
|
||||||
|
applicationId,
|
||||||
|
defaultPageId,
|
||||||
|
);
|
||||||
|
return window.location.origin.toString() + appViewEndPoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser.name !== "anonymousUser") {
|
||||||
|
fetchCurrentOrg(props.orgId);
|
||||||
|
}
|
||||||
|
}, [props.orgId, fetchCurrentOrg, currentUser.name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{canShareWithPublic && (
|
||||||
|
<>
|
||||||
|
<ShareWithPublicOption>
|
||||||
|
Make the application public
|
||||||
|
<ShareToggle>
|
||||||
|
{(isChangingViewAccess || isFetchingApplication) && (
|
||||||
|
<Spinner size={20} />
|
||||||
|
)}
|
||||||
|
{currentApplicationDetails && (
|
||||||
|
<StyledSwitch
|
||||||
|
onChange={() => {
|
||||||
|
changeAppViewAccess(
|
||||||
|
applicationId,
|
||||||
|
!currentApplicationDetails.isPublic,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
disabled={isChangingViewAccess || isFetchingApplication}
|
||||||
|
checked={currentApplicationDetails.isPublic}
|
||||||
|
large
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ShareToggle>
|
||||||
|
</ShareWithPublicOption>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Title>Get Shareable link for this for this application </Title>
|
||||||
|
<CopyToClipBoard copyText={getViewApplicationURL()} />
|
||||||
|
|
||||||
|
{canInviteToOrg && (
|
||||||
|
<OrgInviteUsersForm orgId={props.orgId} isApplicationInvite={true} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
(state: AppState) => {
|
||||||
|
return {
|
||||||
|
currentOrg: getCurrentOrg(state),
|
||||||
|
currentUser: getCurrentUser(state),
|
||||||
|
currentApplicationDetails: state.ui.applications.currentApplication,
|
||||||
|
isFetchingApplication: state.ui.applications.isFetchingApplication,
|
||||||
|
isChangingViewAccess: state.ui.applications.isChangingViewAccess,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
(dispatch: any) => ({
|
||||||
|
changeAppViewAccess: (applicationId: string, publicAccess: boolean) =>
|
||||||
|
dispatch({
|
||||||
|
type: ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT,
|
||||||
|
payload: {
|
||||||
|
applicationId,
|
||||||
|
publicAccess,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
fetchCurrentOrg: (orgId: string) =>
|
||||||
|
dispatch({
|
||||||
|
type: ReduxActionTypes.FETCH_CURRENT_ORG,
|
||||||
|
payload: {
|
||||||
|
orgId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)(AppInviteUsersForm);
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
FieldArray,
|
|
||||||
reduxForm,
|
|
||||||
InjectedFormProps,
|
|
||||||
WrappedFieldArrayProps,
|
|
||||||
} from "redux-form";
|
|
||||||
import FormMessage from "components/editorComponents/form/FormMessage";
|
|
||||||
import { INVITE_USERS_TO_ORG_FORM } from "constants/forms";
|
|
||||||
import {
|
|
||||||
INVITE_USERS_VALIDATION_EMAIL_LIST,
|
|
||||||
INVITE_USERS_VALIDATION_ROLE_EMPTY,
|
|
||||||
INVITE_USERS_EMAIL_LIST_LABEL,
|
|
||||||
INVITE_USERS_EMAIL_LIST_PLACEHOLDER,
|
|
||||||
INVITE_USERS_ROLE_SELECT_LABEL,
|
|
||||||
INVITE_USERS_ROLE_SELECT_PLACEHOLDER,
|
|
||||||
INVITE_USERS_ADD_EMAIL_LIST_FIELD,
|
|
||||||
INVITE_USERS_SUBMIT_BUTTON_TEXT,
|
|
||||||
INVITE_USERS_SUBMIT_ERROR,
|
|
||||||
INVITE_USERS_SUBMIT_SUCCESS,
|
|
||||||
INVITE_USERS_VALIDATION_EMAILS_EMPTY,
|
|
||||||
} from "constants/messages";
|
|
||||||
import {
|
|
||||||
InviteUsersToOrgFormValues,
|
|
||||||
InviteUsersToOrgByRoleValues,
|
|
||||||
inviteUsersToOrgSubmitHandler,
|
|
||||||
} from "./helpers";
|
|
||||||
import { generateReactKey } from "utils/generators";
|
|
||||||
import TagListField from "components/editorComponents/form/fields/TagListField";
|
|
||||||
import { FormIcons } from "icons/FormIcons";
|
|
||||||
import FormFooter from "components/editorComponents/form/FormFooter";
|
|
||||||
import FormActionButton from "components/editorComponents/form/FormActionButton";
|
|
||||||
import FormGroup from "components/editorComponents/form/FormGroup";
|
|
||||||
import SelectField from "components/editorComponents/form/fields/SelectField";
|
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { getRoles, getDefaultRole } from "selectors/organizationSelectors";
|
|
||||||
import { OrgRole } from "constants/orgConstants";
|
|
||||||
import { isEmail } from "utils/formhelpers";
|
|
||||||
|
|
||||||
const validate = (values: InviteUsersToOrgFormValues) => {
|
|
||||||
const errors: any = { usersByRole: [] };
|
|
||||||
if (values.usersByRole && values.usersByRole.length) {
|
|
||||||
values.usersByRole.forEach((role, index) => {
|
|
||||||
errors.usersByRole[index] = { id: "", users: "", role: "" };
|
|
||||||
// If we have users entered for a role.
|
|
||||||
if (role.users && role.users.length > 0) {
|
|
||||||
// Split the users CSV string to an array.
|
|
||||||
const _users = role.users.split(",").filter(Boolean);
|
|
||||||
// Check if each entry is an email
|
|
||||||
_users.forEach(user => {
|
|
||||||
if (!isEmail(user)) {
|
|
||||||
if (errors.usersByRole[index].users)
|
|
||||||
errors.usersByRole[index].users += `${user}, `;
|
|
||||||
else errors.usersByRole[index].users = `${user}, `;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
errors.usersByRole[index].users &&
|
|
||||||
errors.usersByRole[index].users.length > 0
|
|
||||||
) {
|
|
||||||
errors.usersByRole[
|
|
||||||
index
|
|
||||||
].users = `${INVITE_USERS_VALIDATION_EMAIL_LIST} ${errors.usersByRole[
|
|
||||||
index
|
|
||||||
].users.slice(0, -2)}`;
|
|
||||||
}
|
|
||||||
// Check if role has been specified
|
|
||||||
if (role.role === undefined || role.role?.trim().length === 0) {
|
|
||||||
errors.usersByRole[index].role = INVITE_USERS_VALIDATION_ROLE_EMPTY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.usersByRole[index].users = INVITE_USERS_VALIDATION_EMAILS_EMPTY;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledForm = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
background: white;
|
|
||||||
padding: ${props => props.theme.spaces[11]}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledInviteFieldGroup = styled.div`
|
|
||||||
&& {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: none;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
& > div:first-of-type {
|
|
||||||
}
|
|
||||||
& > div {
|
|
||||||
min-width: 150px;
|
|
||||||
margin: 0em 1em 1em 0em;
|
|
||||||
}
|
|
||||||
& > div:last-of-type {
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const renderInviteUsersByRoleForm = (
|
|
||||||
renderer: WrappedFieldArrayProps<InviteUsersToOrgByRoleValues> & {
|
|
||||||
roles?: OrgRole[];
|
|
||||||
role?: OrgRole;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const { fields, roles, role } = renderer;
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
return (
|
|
||||||
<StyledInviteFieldGroup key={`${field}.id`}>
|
|
||||||
<FormGroup fill label={INVITE_USERS_EMAIL_LIST_LABEL}>
|
|
||||||
<TagListField
|
|
||||||
name={`${field}.users`}
|
|
||||||
placeholder={INVITE_USERS_EMAIL_LIST_PLACEHOLDER}
|
|
||||||
type="email"
|
|
||||||
label="Emails"
|
|
||||||
intent="success"
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
{roles && (
|
|
||||||
<FormGroup label={INVITE_USERS_ROLE_SELECT_LABEL}>
|
|
||||||
<SelectField
|
|
||||||
name={`${field}.role`}
|
|
||||||
placeholder={INVITE_USERS_ROLE_SELECT_PLACEHOLDER}
|
|
||||||
options={roles}
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
<FormIcons.DELETE_ICON
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={() => fields.remove(index)}
|
|
||||||
/>
|
|
||||||
</StyledInviteFieldGroup>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<FormActionButton
|
|
||||||
onClick={() =>
|
|
||||||
fields.push({
|
|
||||||
id: generateReactKey(),
|
|
||||||
role: !!role ? role.id : undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
text={INVITE_USERS_ADD_EMAIL_LIST_FIELD}
|
|
||||||
icon="plus"
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type InviteUsersFormProps = InjectedFormProps<
|
|
||||||
InviteUsersToOrgFormValues,
|
|
||||||
{
|
|
||||||
fetchRoles: () => void;
|
|
||||||
roles?: OrgRole[];
|
|
||||||
defaultRole?: OrgRole;
|
|
||||||
}
|
|
||||||
> & {
|
|
||||||
fetchRoles: () => void;
|
|
||||||
roles?: OrgRole[];
|
|
||||||
defaultRole?: OrgRole;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InviteUsersForm = (props: InviteUsersFormProps) => {
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
submitting,
|
|
||||||
submitFailed,
|
|
||||||
submitSucceeded,
|
|
||||||
error,
|
|
||||||
fetchRoles,
|
|
||||||
roles,
|
|
||||||
initialize,
|
|
||||||
defaultRole,
|
|
||||||
anyTouched,
|
|
||||||
} = props;
|
|
||||||
const history = useHistory();
|
|
||||||
useEffect(() => {
|
|
||||||
if (!roles) {
|
|
||||||
fetchRoles();
|
|
||||||
} else {
|
|
||||||
initialize({
|
|
||||||
usersByRole: [
|
|
||||||
{
|
|
||||||
id: generateReactKey(),
|
|
||||||
role: !!defaultRole ? defaultRole.id : undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [fetchRoles, roles, defaultRole, initialize]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledForm>
|
|
||||||
{submitSucceeded && (
|
|
||||||
<FormMessage intent="primary" message={INVITE_USERS_SUBMIT_SUCCESS} />
|
|
||||||
)}
|
|
||||||
{submitFailed && error && (
|
|
||||||
<FormMessage
|
|
||||||
intent="danger"
|
|
||||||
message={`${INVITE_USERS_SUBMIT_ERROR}: ${error}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FieldArray
|
|
||||||
name="usersByRole"
|
|
||||||
component={renderInviteUsersByRoleForm}
|
|
||||||
props={{ roles: roles }}
|
|
||||||
/>
|
|
||||||
<FormFooter
|
|
||||||
divider
|
|
||||||
onSubmit={handleSubmit(inviteUsersToOrgSubmitHandler)}
|
|
||||||
submitting={submitting && !(submitFailed && !anyTouched)}
|
|
||||||
onCancel={() => history.goBack()}
|
|
||||||
submitOnEnter={false}
|
|
||||||
submitText={INVITE_USERS_SUBMIT_BUTTON_TEXT}
|
|
||||||
></FormFooter>
|
|
||||||
</StyledForm>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
(state: AppState) => {
|
|
||||||
return {
|
|
||||||
roles: getRoles(state),
|
|
||||||
defaultRole: getDefaultRole(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
(dispatch: any) => ({
|
|
||||||
fetchRoles: () => dispatch({ type: ReduxActionTypes.FETCH_ORG_ROLES_INIT }),
|
|
||||||
}),
|
|
||||||
)(
|
|
||||||
reduxForm<
|
|
||||||
InviteUsersToOrgFormValues,
|
|
||||||
{ fetchRoles: () => void; roles?: OrgRole[]; defaultRole?: OrgRole }
|
|
||||||
>({
|
|
||||||
form: INVITE_USERS_TO_ORG_FORM,
|
|
||||||
validate,
|
|
||||||
})(InviteUsersForm),
|
|
||||||
);
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import React, { useEffect, useState, createRef } from "react";
|
import React, { useEffect } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import TagListField from "components/editorComponents/form/fields/TagListField";
|
import TagListField from "components/editorComponents/form/fields/TagListField";
|
||||||
import { reduxForm, SubmissionError } from "redux-form";
|
import { reduxForm, SubmissionError } from "redux-form";
|
||||||
import SelectField from "components/editorComponents/form/fields/SelectField";
|
import SelectField from "components/editorComponents/form/fields/SelectField";
|
||||||
|
import Divider from "components/editorComponents/Divider";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button from "components/editorComponents/Button";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import {
|
import {
|
||||||
getDefaultRole,
|
|
||||||
getRolesForField,
|
getRolesForField,
|
||||||
getAllUsers,
|
getAllUsers,
|
||||||
getCurrentOrg,
|
getCurrentOrg,
|
||||||
|
|
@ -27,8 +27,10 @@ import {
|
||||||
import history from "utils/history";
|
import history from "utils/history";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { isEmail } from "utils/formhelpers";
|
import { isEmail } from "utils/formhelpers";
|
||||||
import ShareWithPublic from "./ShareWithPublic";
|
import {
|
||||||
import Divider from "components/editorComponents/Divider";
|
isPermitted,
|
||||||
|
PERMISSION_TYPE,
|
||||||
|
} from "../Applications/permissionHelpers";
|
||||||
|
|
||||||
const OrgInviteTitle = styled.div`
|
const OrgInviteTitle = styled.div`
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
@ -68,6 +70,7 @@ const StyledForm = styled.form`
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInviteFieldGroup = styled.div`
|
const StyledInviteFieldGroup = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -140,7 +143,7 @@ const validate = (values: any) => {
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InviteUsersForm = (props: any) => {
|
const OrgInviteUsersForm = (props: any) => {
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
allUsers,
|
allUsers,
|
||||||
|
|
@ -152,19 +155,20 @@ const InviteUsersForm = (props: any) => {
|
||||||
fetchUser,
|
fetchUser,
|
||||||
fetchAllRoles,
|
fetchAllRoles,
|
||||||
valid,
|
valid,
|
||||||
onCancel,
|
|
||||||
isFetchingApplication,
|
|
||||||
isChangingViewAccess,
|
|
||||||
currentApplicationDetails,
|
|
||||||
changeAppViewAccess,
|
|
||||||
applicationId,
|
|
||||||
fetchCurrentOrg,
|
fetchCurrentOrg,
|
||||||
currentOrg,
|
currentOrg,
|
||||||
|
isApplicationInvite,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const currentPath = useLocation().pathname;
|
const currentPath = useLocation().pathname;
|
||||||
const pathRegex = /(?:\/org\/)\w+(?:\/settings)/;
|
const pathRegex = /(?:\/org\/)\w+(?:\/settings)/;
|
||||||
|
|
||||||
|
const userOrgPermissions = currentOrg?.userPermissions ?? [];
|
||||||
|
const canManage = isPermitted(
|
||||||
|
userOrgPermissions,
|
||||||
|
PERMISSION_TYPE.MANAGE_ORGANIZATION,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUser(props.orgId);
|
fetchUser(props.orgId);
|
||||||
fetchAllRoles(props.orgId);
|
fetchAllRoles(props.orgId);
|
||||||
|
|
@ -186,20 +190,12 @@ const InviteUsersForm = (props: any) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{applicationId && (
|
{isApplicationInvite && (
|
||||||
<>
|
<>
|
||||||
<ShareWithPublic
|
|
||||||
changeAppViewAccess={changeAppViewAccess}
|
|
||||||
isFetchingApplication={isFetchingApplication}
|
|
||||||
currentApplicationDetails={currentApplicationDetails}
|
|
||||||
applicationId={applicationId}
|
|
||||||
isChangingViewAccess={isChangingViewAccess}
|
|
||||||
/>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<OrgInviteTitle>Invite Users to {currentOrg?.name} </OrgInviteTitle>
|
<OrgInviteTitle>Invite Users to {currentOrg?.name} </OrgInviteTitle>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StyledForm
|
<StyledForm
|
||||||
onSubmit={handleSubmit((values: any, dispatch: any) => {
|
onSubmit={handleSubmit((values: any, dispatch: any) => {
|
||||||
validateFormValues(values);
|
validateFormValues(values);
|
||||||
|
|
@ -251,7 +247,7 @@ const InviteUsersForm = (props: any) => {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</UserList>
|
</UserList>
|
||||||
{!pathRegex.test(currentPath) && (
|
{!pathRegex.test(currentPath) && canManage && (
|
||||||
<Button
|
<Button
|
||||||
className="manageUsers"
|
className="manageUsers"
|
||||||
text="Manage Users"
|
text="Manage Users"
|
||||||
|
|
@ -271,12 +267,8 @@ export default connect(
|
||||||
(state: AppState) => {
|
(state: AppState) => {
|
||||||
return {
|
return {
|
||||||
roles: getRolesForField(state),
|
roles: getRolesForField(state),
|
||||||
defaultRole: getDefaultRole(state),
|
|
||||||
allUsers: getAllUsers(state),
|
allUsers: getAllUsers(state),
|
||||||
currentOrg: getCurrentOrg(state),
|
currentOrg: getCurrentOrg(state),
|
||||||
currentApplicationDetails: state.ui.applications.currentApplication,
|
|
||||||
isFetchingApplication: state.ui.applications.isFetchingApplication,
|
|
||||||
isChangingViewAccess: state.ui.applications.isChangingViewAccess,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(dispatch: any) => ({
|
(dispatch: any) => ({
|
||||||
|
|
@ -301,14 +293,6 @@ export default connect(
|
||||||
orgId,
|
orgId,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
changeAppViewAccess: (applicationId: string, publicAccess: boolean) =>
|
|
||||||
dispatch({
|
|
||||||
type: ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT,
|
|
||||||
payload: {
|
|
||||||
applicationId,
|
|
||||||
publicAccess,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
)(
|
)(
|
||||||
reduxForm<
|
reduxForm<
|
||||||
|
|
@ -317,9 +301,11 @@ export default connect(
|
||||||
fetchAllRoles: (orgId: string) => void;
|
fetchAllRoles: (orgId: string) => void;
|
||||||
roles?: any;
|
roles?: any;
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
|
orgId?: string;
|
||||||
|
isApplicationInvite?: boolean;
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
validate,
|
validate,
|
||||||
form: INVITE_USERS_TO_ORG_FORM,
|
form: INVITE_USERS_TO_ORG_FORM,
|
||||||
})(InviteUsersForm),
|
})(OrgInviteUsersForm),
|
||||||
);
|
);
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
import React, { createRef, useState } from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import copy from "copy-to-clipboard";
|
|
||||||
import { StyledSwitch } from "components/propertyControls/StyledControls";
|
|
||||||
import Spinner from "components/editorComponents/Spinner";
|
|
||||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
|
||||||
import { getDefaultPageId } from "sagas/SagaUtils";
|
|
||||||
import { getApplicationViewerPageURL } from "constants/routes";
|
|
||||||
|
|
||||||
const ShareWithPublicOption = styled.div`
|
|
||||||
{
|
|
||||||
display: flex;
|
|
||||||
padding: 10px 0px;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const CopyToClipboard = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
`;
|
|
||||||
const StyledInput = styled.input`
|
|
||||||
flex: 1;
|
|
||||||
border: 1px solid #d3dee3;
|
|
||||||
border-right: none;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #768896;
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
width: 90%;
|
|
||||||
overflow: hidden;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SelectButton = styled(BaseButton)`
|
|
||||||
&&&& {
|
|
||||||
max-width: 70px;
|
|
||||||
margin: 0 0px;
|
|
||||||
min-height: 32px;
|
|
||||||
border-radius: 0px 4px 4px 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #f6f7f8;
|
|
||||||
font-size: 14px;
|
|
||||||
&.bp3-button {
|
|
||||||
padding: 0px 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ShareToggle = styled.div`
|
|
||||||
{
|
|
||||||
&&& label {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
&&& div {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ShareWithPublic = (props: any) => {
|
|
||||||
const {
|
|
||||||
currentApplicationDetails,
|
|
||||||
applicationId,
|
|
||||||
changeAppViewAccess,
|
|
||||||
isChangingViewAccess,
|
|
||||||
isFetchingApplication,
|
|
||||||
} = props;
|
|
||||||
const copyURLInput = createRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
const defaultPageId = getDefaultPageId(currentApplicationDetails.pages);
|
|
||||||
const appViewEndPoint = getApplicationViewerPageURL(
|
|
||||||
applicationId,
|
|
||||||
defaultPageId,
|
|
||||||
);
|
|
||||||
const viewApplicationURL =
|
|
||||||
window.location.origin.toString() + appViewEndPoint;
|
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
|
||||||
|
|
||||||
const copyToClipboard = (url: string) => {
|
|
||||||
copy(url);
|
|
||||||
setIsCopied(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsCopied(false);
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectText = () => {
|
|
||||||
if (copyURLInput.current) {
|
|
||||||
copyURLInput.current.setSelectionRange(0, viewApplicationURL.length);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ShareWithPublicOption>
|
|
||||||
Make the application public
|
|
||||||
<ShareToggle>
|
|
||||||
{(isChangingViewAccess || isFetchingApplication) && (
|
|
||||||
<Spinner size={20} />
|
|
||||||
)}
|
|
||||||
{currentApplicationDetails && (
|
|
||||||
<StyledSwitch
|
|
||||||
onChange={() => {
|
|
||||||
changeAppViewAccess(
|
|
||||||
applicationId,
|
|
||||||
!currentApplicationDetails.isPublic,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
disabled={isChangingViewAccess || isFetchingApplication}
|
|
||||||
checked={currentApplicationDetails.isPublic}
|
|
||||||
large
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ShareToggle>
|
|
||||||
</ShareWithPublicOption>
|
|
||||||
|
|
||||||
{currentApplicationDetails.isPublic && (
|
|
||||||
<CopyToClipboard>
|
|
||||||
<StyledInput
|
|
||||||
type="text"
|
|
||||||
ref={copyURLInput}
|
|
||||||
readOnly
|
|
||||||
onClick={() => {
|
|
||||||
selectText();
|
|
||||||
}}
|
|
||||||
value={viewApplicationURL}
|
|
||||||
/>
|
|
||||||
<SelectButton
|
|
||||||
text={isCopied ? "Copied" : "Copy"}
|
|
||||||
accent="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
copyToClipboard(viewApplicationURL);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CopyToClipboard>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ShareWithPublic;
|
|
||||||
|
|
@ -2,7 +2,6 @@ import React from "react";
|
||||||
import { Switch, useRouteMatch, useLocation } from "react-router-dom";
|
import { Switch, useRouteMatch, useLocation } from "react-router-dom";
|
||||||
import PageWrapper from "pages/common/PageWrapper";
|
import PageWrapper from "pages/common/PageWrapper";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
import Invite from "./invite";
|
|
||||||
import DefaultOrgPage from "./defaultOrgPage";
|
import DefaultOrgPage from "./defaultOrgPage";
|
||||||
import AppRoute from "pages/common/AppRoute";
|
import AppRoute from "pages/common/AppRoute";
|
||||||
export const Organization = () => {
|
export const Organization = () => {
|
||||||
|
|
@ -17,12 +16,6 @@ export const Organization = () => {
|
||||||
component={Settings}
|
component={Settings}
|
||||||
name={"Settings"}
|
name={"Settings"}
|
||||||
/>
|
/>
|
||||||
<AppRoute
|
|
||||||
exact
|
|
||||||
path={`${path}/invite`}
|
|
||||||
component={Invite}
|
|
||||||
name={"Invite"}
|
|
||||||
/>
|
|
||||||
<AppRoute component={DefaultOrgPage} name={"DefaultOrgPage"} />
|
<AppRoute component={DefaultOrgPage} name={"DefaultOrgPage"} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import InviteUsersForm from "./InviteUsersForm";
|
|
||||||
|
|
||||||
export const Invite = () => {
|
|
||||||
// const handleInviteUsersSubmit = (values: InviteUsersFormValues) => {};
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<h2>Invite Users</h2>
|
|
||||||
<InviteUsersForm />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Invite;
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import PageSectionDivider from "pages/common/PageSectionDivider";
|
import PageSectionDivider from "pages/common/PageSectionDivider";
|
||||||
import PageSectionHeader from "pages/common/PageSectionHeader";
|
import PageSectionHeader from "pages/common/PageSectionHeader";
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
import InviteUsersFormv2 from "pages/organization/InviteUsersFromv2";
|
import OrgInviteUsersForm from "pages/organization/OrgInviteUsersForm";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button from "components/editorComponents/Button";
|
||||||
import { OrgUser, Org } from "constants/orgConstants";
|
import { OrgUser, Org } from "constants/orgConstants";
|
||||||
import { Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
import { Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
||||||
|
|
@ -258,7 +258,7 @@ export const OrgSettings = (props: PageProps) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
canOutsideClickClose={true}
|
canOutsideClickClose={true}
|
||||||
Form={InviteUsersFormv2}
|
Form={OrgInviteUsersForm}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
title={`Invite Users to ${currentOrgName}`}
|
title={`Invite Users to ${currentOrgName}`}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,6 @@ const orgReducer = createReducer(initialState, {
|
||||||
isFetchAllUsers: false,
|
isFetchAllUsers: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.INVITE_USERS_TO_ORG_SUCCESS]: (
|
|
||||||
state: OrgReduxState,
|
|
||||||
action: ReduxAction<OrgUser[]>,
|
|
||||||
) => ({
|
|
||||||
...state,
|
|
||||||
orgUsers: [...state.orgUsers, ...action.payload],
|
|
||||||
}),
|
|
||||||
[ReduxActionTypes.FETCH_ALL_ROLES_SUCCESS]: (
|
[ReduxActionTypes.FETCH_ALL_ROLES_SUCCESS]: (
|
||||||
state: OrgReduxState,
|
state: OrgReduxState,
|
||||||
action: ReduxAction<Org[]>,
|
action: ReduxAction<Org[]>,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import OrgApi, {
|
||||||
FetchAllRolesResponse,
|
FetchAllRolesResponse,
|
||||||
DeleteOrgUserRequest,
|
DeleteOrgUserRequest,
|
||||||
ChangeUserRoleRequest,
|
ChangeUserRoleRequest,
|
||||||
|
FetchAllRolesRequest,
|
||||||
} from "api/OrgApi";
|
} from "api/OrgApi";
|
||||||
import { ApiResponse } from "api/ApiResponses";
|
import { ApiResponse } from "api/ApiResponses";
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
|
|
@ -144,9 +145,13 @@ export function* deleteOrgUserSaga(action: ReduxAction<DeleteOrgUserRequest>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* fetchAllRolesSaga(action: ReduxAction<DeleteOrgUserRequest>) {
|
export function* fetchAllRolesSaga(action: ReduxAction<FetchAllRolesRequest>) {
|
||||||
try {
|
try {
|
||||||
const response: FetchAllRolesResponse = yield call(OrgApi.fetchAllRoles);
|
const request: FetchAllRolesRequest = action.payload;
|
||||||
|
const response: FetchAllRolesResponse = yield call(
|
||||||
|
OrgApi.fetchAllRoles,
|
||||||
|
request,
|
||||||
|
);
|
||||||
const isValidResponse = yield validateResponse(response);
|
const isValidResponse = yield validateResponse(response);
|
||||||
if (isValidResponse) {
|
if (isValidResponse) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
|
||||||
|
|
@ -227,8 +227,10 @@ export function* inviteUsers(
|
||||||
yield call(reject, { _error: errorMessage });
|
yield call(reject, { _error: errorMessage });
|
||||||
}
|
}
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.INVITE_USERS_TO_ORG_SUCCESS,
|
type: ReduxActionTypes.FETCH_ALL_USERS_INIT,
|
||||||
payload: response.data,
|
payload: {
|
||||||
|
orgId: data.orgId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
yield call(resolve);
|
yield call(resolve);
|
||||||
yield put(reset(INVITE_USERS_TO_ORG_FORM));
|
yield put(reset(INVITE_USERS_TO_ORG_FORM));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user