diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Workspace/MemberRoles_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Workspace/MemberRoles_Spec.ts index 94f933ee85..2b1f97c477 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Workspace/MemberRoles_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Workspace/MemberRoles_Spec.ts @@ -45,12 +45,13 @@ describe("Create new workspace and invite user & validate all roles", () => { .first() .click(); // click on selet a role - cy.wait(2000) - cy.xpath(HomePage.selectRole).click() - cy.get('.t--dropdown-option').should('have.length', 1).and('contain.text', `App Viewer - ${workspaceId}`) - cy.get(HomePage.closeBtn).click() - - + cy.wait(2000); + cy.xpath(HomePage.selectRole).click(); + cy.get(".t--dropdown-option") + .should("have.length", 2) + .and("contain.text", `App Viewer - ${workspaceId}`); + cy.get(".t--dropdown-option").should("contain.text", `Select a role`); + cy.get(HomePage.closeBtn).click(); homePage.LaunchAppFromAppHover(); homePage.LogOutviaAPI(); @@ -82,13 +83,19 @@ describe("Create new workspace and invite user & validate all roles", () => { .first() .click({ force: true }); // cy.xpath(homePage._editPageLanding).should("exist"); - cy.wait(4000) - cy.xpath("//span[text()='SHARE']") - .click(); - cy.wait(2000) - cy.xpath(HomePage.selectRole).click() - cy.get('.t--dropdown-option').should('have.length', 2).and('contain.text', `App Viewer - ${workspaceId}`, `Developer - ${workspaceId}`) - cy.get(HomePage.closeBtn).click() + cy.wait(4000); + cy.xpath("//span[text()='SHARE']").click(); + cy.wait(2000); + cy.xpath(HomePage.selectRole).click(); + cy.get(".t--dropdown-option") + .should("have.length", 3) + .and( + "contain.text", + `App Viewer - ${workspaceId}`, + `Developer - ${workspaceId}`, + ); + cy.get(".t--dropdown-option").should("contain.text", `Select a role`); + cy.get(HomePage.closeBtn).click(); homePage.LogOutviaAPI(); }); @@ -119,11 +126,21 @@ describe("Create new workspace and invite user & validate all roles", () => { Cypress.env("TESTUSERNAME2"), "App Viewer", ); - cy.wait(2000) - cy.xpath(HomePage.selectRole).click() - cy.get('.t--dropdown-option').should('have.length', 3).should("contain.text", `App Viewer - ${workspaceId}`, `Developer - ${workspaceId}`) - cy.get('.t--dropdown-option').should("contain.text", `Administrator - ${workspaceId}`) - cy.get(HomePage.closeBtn).click() + cy.wait(2000); + cy.xpath(HomePage.selectRole).click(); + cy.get(".t--dropdown-option") + .should("have.length", 4) + .should( + "contain.text", + `App Viewer - ${workspaceId}`, + `Developer - ${workspaceId}`, + ); + cy.get(".t--dropdown-option").should( + "contain.text", + `Administrator - ${workspaceId}`, + ); + cy.get(".t--dropdown-option").should("contain.text", `Select a role`); + cy.get(HomePage.closeBtn).click(); homePage.LogOutviaAPI(); }); @@ -139,4 +156,4 @@ describe("Create new workspace and invite user & validate all roles", () => { }); homePage.NavigateToHome(); }); -}); \ No newline at end of file +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/GoogleSheetsQuery_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/GoogleSheetsQuery_spec.js index a2106b49d9..c2c5fc2d5c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/GoogleSheetsQuery_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/GoogleSheetsQuery_spec.js @@ -6,39 +6,44 @@ let dataSources = ObjectsRegistry.DataSources; let queryName; let datasourceName; let pluginName = "Google Sheets"; -let placeholderText = "{\n \"name\": {{nameInput.text}},\n \"dob\": {{dobPicker.formattedDate}},\n \"gender\": {{genderSelect.selectedOptionValue}} \n}" +let placeholderText = + '{\n "name": {{nameInput.text}},\n "dob": {{dobPicker.formattedDate}},\n "gender": {{genderSelect.selectedOptionValue}} \n}'; describe("Google Sheets datasource row objects placeholder", function() { - it("Bug: 16391 - Google Sheets DS, placeholder objects keys should have quotes", function() { - // create new Google Sheets datasource - dataSources.NavigateToDSCreateNew(); - dataSources.CreatePlugIn(pluginName); - - // navigate to create query tab and create a new query - cy.get("@createDatasource").then((httpResponse) => { - datasourceName = httpResponse.response.body.data.name; - // clicking on new query to write a query - cy.NavigateToQueryEditor(); - cy.get(explorer.createNew).click(); - cy.get("div:contains('" + datasourceName + " Query')").last().click(); - - // fill the create new api google sheets form - // and check for rowobject placeholder text - cy.get(datasource.gSheetsOperationDropdown).click(); - cy.get(datasource.gSheetsInsertOneOption).click(); - - cy.get(datasource.gSheetsEntityDropdown).click(); - cy.get(datasource.gSheetsSheetRowsOption).click(); - - cy.get(datasource.gSheetsCodeMirrorPlaceholder).should('have.text', placeholderText); + it("Bug: 16391 - Google Sheets DS, placeholder objects keys should have quotes", function() { + // create new Google Sheets datasource + dataSources.NavigateToDSCreateNew(); + dataSources.CreatePlugIn(pluginName); - // delete query and datasource after test is done - cy.get("@createNewApi").then((httpResponse) => { - queryName = httpResponse.response.body.data.name; - cy.deleteQueryUsingContext(); - cy.deleteDatasource(datasourceName); - }) + // navigate to create query tab and create a new query + cy.get("@createDatasource").then((httpResponse) => { + datasourceName = httpResponse.response.body.data.name; + // clicking on new query to write a query + cy.NavigateToQueryEditor(); + cy.get(explorer.createNew).click(); + cy.get("div:contains('" + datasourceName + " Query')") + .last() + .click(); + + // fill the create new api google sheets form + // and check for rowobject placeholder text + cy.get(datasource.gSheetsOperationDropdown).click(); + cy.get(datasource.gSheetsInsertOneOption).click(); + + cy.get(datasource.gSheetsEntityDropdown).click(); + cy.get(datasource.gSheetsSheetRowsOption).click(); + + cy.get(datasource.gSheetsCodeMirrorPlaceholder).should( + "have.text", + placeholderText, + ); + + // delete query and datasource after test is done + cy.get("@createNewApi").then((httpResponse) => { + queryName = httpResponse.response.body.data.name; + cy.deleteQueryUsingContext(); + cy.deleteDatasource(datasourceName); }); }); - } -); + }); +}); diff --git a/app/client/cypress/support/Pages/HomePage.ts b/app/client/cypress/support/Pages/HomePage.ts index 4c31a0678f..f329579119 100644 --- a/app/client/cypress/support/Pages/HomePage.ts +++ b/app/client/cypress/support/Pages/HomePage.ts @@ -24,11 +24,15 @@ export class HomePage { ".t--workspace-section:contains(" + workspaceName + ") button:contains('Share')"; - private _email = "//input[@type='email']"; + private _email = + "//input[@type='text' and contains(@class,'bp3-input-ghost')]"; _visibleTextSpan = (spanText: string) => "//span[text()='" + spanText + "']"; private _userRole = (role: string, workspaceName: string) => "//div[contains(@class, 'label-container')]//span[1][text()='" + - role + ' - ' + workspaceName + "']"; + role + + " - " + + workspaceName + + "']"; private _manageUsers = ".manageUsers"; private _appHome = "//a[@href='/applications']"; @@ -50,8 +54,8 @@ export class HomePage { "//td[text()='" + email + "']/following-sibling::td//span[contains(@class, 't--deleteUser')]"; - private _userRoleDropDown = (role: string, WorkspaceName:string)=> "//span[text()='" + - role + " - "+ WorkspaceName + "']"; + private _userRoleDropDown = (role: string, WorkspaceName: string) => + "//span[text()='" + role + " - " + WorkspaceName + "']"; //private _userRoleDropDown = (email: string) => "//td[text()='" + email + "']/following-sibling::td" private _leaveWorkspaceConfirmModal = ".t--member-delete-confirmation-modal"; private _workspaceImportAppModal = ".t--import-application-modal"; @@ -160,8 +164,9 @@ export class HomePage { cy.get(this._homeIcon).click({ force: true }); this.agHelper.Sleep(2000); //cy.wait("@applications"); this randomly fails & introduces flakyness hence commenting! - this.agHelper.AssertElementVisible(this._homePageAppCreateBtn).then($ele=> - expect($ele).be.enabled); + this.agHelper + .AssertElementVisible(this._homePageAppCreateBtn) + .then(($ele) => expect($ele).be.enabled); } public CreateNewApplication() { @@ -181,7 +186,7 @@ export class HomePage { this.agHelper.ValidateNetworkStatus("@createNewApplication", 201); cy.get(this.locator._loading).should("not.exist"); this.agHelper.Sleep(2000); - if(appname) this.RenameApplication(appname); + if (appname) this.RenameApplication(appname); cy.get(this._buildFromScratchActionCard).click(); //this.agHelper.ValidateNetworkStatus("@updateApplication", 200); } @@ -310,7 +315,7 @@ export class HomePage { cy.log(workspaceName, email, currentRole); cy.xpath(this._userRoleDropDown(currentRole, workspaceName)) .first() - .click({force:true}) + .click({ force: true }); //cy.xpath(this._userRoleDropDown(email)).first().click({force: true}); cy.xpath(this._visibleTextSpan(`${newRole} - ${workspaceName}`)) @@ -353,7 +358,6 @@ export class HomePage { cy.contains(successMessage); } - public DeleteWorkspace(workspaceNameToDelete: string) { cy.get(this._homeIcon).click(); this.agHelper.GetNClick( diff --git a/app/client/src/ce/constants/forms.ts b/app/client/src/ce/constants/forms.ts index b62651ffd4..3e97445965 100644 --- a/app/client/src/ce/constants/forms.ts +++ b/app/client/src/ce/constants/forms.ts @@ -1 +1,43 @@ -export * from "constants/forms"; +export const API_EDITOR_FORM_NAME = "ApiEditorForm"; +export const CREATE_APPLICATION_FORM_NAME = "CreateApplicationForm"; +export const INVITE_USERS_TO_WORKSPACE_FORM = "InviteUsersToWorkspaceForm"; +export const LOGIN_FORM_NAME = "LoginForm"; + +export const LOGIN_FORM_EMAIL_FIELD_NAME = "username"; +export const LOGIN_FORM_PASSWORD_FIELD_NAME = "password"; + +export const SIGNUP_FORM_NAME = "SignupForm"; +export const SIGNUP_FORM_EMAIL_FIELD_NAME = "email"; +export const FORGOT_PASSWORD_FORM_NAME = "ForgotPasswordForm"; +export const RESET_PASSWORD_FORM_NAME = "ResetPasswordForm"; +export const CREATE_PASSWORD_FORM_NAME = "CreatePasswordForm"; + +export const CREATE_WORKSPACE_FORM_NAME = "New Workspace"; +export const CURL_IMPORT_FORM = "CurlImportForm"; + +export const QUERY_EDITOR_FORM_NAME = "QueryEditorForm"; +export const API_HOME_SCREEN_FORM = "APIHomeScreenForm"; +export const SAAS_EDITOR_FORM = "SaaSEditorForm"; + +export const DATASOURCE_DB_FORM = "DatasourceDBForm"; +export const DATASOURCE_REST_API_FORM = "DatasourceRestAPIForm"; +export const DATASOURCE_SAAS_FORM = "DatasourceSaaSForm"; + +export const WELCOME_FORM_NAME = "WelcomeSetupForm"; +export const WELCOME_FORM_NAME_FIELD_NAME = "name"; +export const WELCOME_FORM_EMAIL_FIELD_NAME = "email"; +export const WELCOME_FORM_PASSWORD_FIELD_NAME = "password"; +export const WELCOME_FORM_VERIFY_PASSWORD_FIELD_NAME = "verify_password"; +export const WELCOME_FORM_ROLE_FIELD_NAME = "role"; +export const WELCOME_FORM_ROLE_NAME_FIELD_NAME = "role_name"; +export const WELCOME_FORM_USECASE_FIELD_NAME = "useCase"; +export const WELCOME_FORM_CUSTOM_USECASE_FIELD_NAME = "custom_useCase"; + +export const SETTINGS_FORM_NAME = "SettingsForm"; +export const WELCOME_NON_SUPER_FORM_NAME = "WelcomeNonSuperSetupForm"; + +export const SAVE_THEME_FORM_NAME = "SaveThemeForm"; +export const REDIRECT_URL_FORM = "RedirectURLForm"; +export const ENTITYID_URL_FORM = "EntityIdURLForm"; + +export const inviteModalLinks: any[] = []; diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 2980afc230..621a6430ed 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -139,6 +139,8 @@ export const INVITE_USERS_ROLE_SELECT_PLACEHOLDER = () => `Select role`; export const INVITE_USERS_ROLE_SELECT_LABEL = () => `Role`; export const INVITE_USERS_EMAIL_LIST_LABEL = () => `User emails`; export const INVITE_USERS_ADD_EMAIL_LIST_FIELD = () => `Add more`; +export const INVITE_USERS_MESSAGE = () => `Invite users`; +export const INVITE_USERS_PLACEHOLDER = () => `Enter email address`; export const INVITE_USERS_SUBMIT_BUTTON_TEXT = () => `Invite users`; export const INVITE_USERS_SUBMIT_SUCCESS = () => `The users have been invited successfully`; diff --git a/app/client/src/ce/pages/AdminSettings/config/types.ts b/app/client/src/ce/pages/AdminSettings/config/types.ts index cfef7e2b4c..2f2a436c2c 100644 --- a/app/client/src/ce/pages/AdminSettings/config/types.ts +++ b/app/client/src/ce/pages/AdminSettings/config/types.ts @@ -51,7 +51,7 @@ export interface Setting { isVisible?: (values: Record) => boolean; isHidden?: boolean; isDisabled?: (values: Record) => boolean; - calloutType?: "Info" | "Warning"; + calloutType?: "Info" | "Warning" | "Notify"; advanced?: Setting[]; isRequired?: boolean; formName?: string; diff --git a/app/client/src/ce/pages/UserAuth/Login.tsx b/app/client/src/ce/pages/UserAuth/Login.tsx index c077108450..6fff0568f9 100644 --- a/app/client/src/ce/pages/UserAuth/Login.tsx +++ b/app/client/src/ce/pages/UserAuth/Login.tsx @@ -12,7 +12,7 @@ import { LOGIN_FORM_NAME, LOGIN_FORM_EMAIL_FIELD_NAME, LOGIN_FORM_PASSWORD_FIELD_NAME, -} from "constants/forms"; +} from "@appsmith/constants/forms"; import { FORGOT_PASSWORD_URL, SETUP, SIGN_UP_URL } from "constants/routes"; import { LOGIN_PAGE_TITLE, diff --git a/app/client/src/ce/pages/UserAuth/SignUp.tsx b/app/client/src/ce/pages/UserAuth/SignUp.tsx index 5b2f637c39..54d10a0d46 100644 --- a/app/client/src/ce/pages/UserAuth/SignUp.tsx +++ b/app/client/src/ce/pages/UserAuth/SignUp.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form"; import { AUTH_LOGIN_URL } from "constants/routes"; -import { SIGNUP_FORM_NAME } from "constants/forms"; +import { SIGNUP_FORM_NAME } from "@appsmith/constants/forms"; import { RouteComponentProps, useHistory, @@ -48,7 +48,7 @@ import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; -import { SIGNUP_FORM_EMAIL_FIELD_NAME } from "constants/forms"; +import { SIGNUP_FORM_EMAIL_FIELD_NAME } from "@appsmith/constants/forms"; import { getAppsmithConfigs } from "@appsmith/configs"; import { useScript, ScriptStatus, AddScriptTo } from "utils/hooks/useScript"; diff --git a/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx b/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx new file mode 100644 index 0000000000..c64ff84e36 --- /dev/null +++ b/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx @@ -0,0 +1,985 @@ +import React, { + Fragment, + useContext, + useEffect, + useState, + useMemo, + useRef, +} from "react"; +import styled, { + createGlobalStyle, + css, + ThemeContext, +} from "styled-components"; +import TagListField from "components/editorComponents/form/fields/TagListField"; +import { reduxForm, SubmissionError } from "redux-form"; +import SelectField from "components/editorComponents/form/fields/SelectField"; +import { connect, useSelector } from "react-redux"; +import { AppState } from "@appsmith/reducers"; +import { + getRolesForField, + getAllUsers, + getCurrentAppWorkspace, +} from "@appsmith/selectors/workspaceSelectors"; +import Spinner from "components/editorComponents/Spinner"; +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { + InviteUsersToWorkspaceFormValues, + inviteUsersToWorkspace, +} from "@appsmith/pages/workspace/helpers"; +import { INVITE_USERS_TO_WORKSPACE_FORM } from "@appsmith/constants/forms"; +import { + createMessage, + INVITE_USERS_SUBMIT_SUCCESS, + INVITE_USER_SUBMIT_SUCCESS, + INVITE_USERS_VALIDATION_EMAILS_EMPTY, + INVITE_USERS_VALIDATION_EMAIL_LIST, + INVITE_USERS_VALIDATION_ROLE_EMPTY, +} from "@appsmith/constants/messages"; +import { isEmail } from "utils/formhelpers"; +import { + isPermitted, + PERMISSION_TYPE, +} from "pages/Applications/permissionHelpers"; +import { getAppsmithConfigs } from "@appsmith/configs"; +import { ReactComponent as NoEmailConfigImage } from "assets/images/email-not-configured.svg"; +import AnalyticsUtil from "utils/AnalyticsUtil"; +import { + Button, + Size, + Text, + TextType, + Icon, + IconSize, + SegmentHeader, + TextProps, + TooltipComponent, + DropdownOption, +} from "design-system"; +import { Classes, Variant } from "components/ads/common"; +import Callout from "components/ads/Callout"; +import { getInitialsAndColorCode } from "utils/AppsmithUtils"; +import ProfileImage from "pages/common/ProfileImage"; +import ManageUsers from "pages/workspace/ManageUsers"; +import { ScrollIndicator } from "design-system"; +import UserApi from "@appsmith/api/UserApi"; +import { Colors } from "constants/Colors"; +import { fetchWorkspace } from "actions/workspaceActions"; +import { SubTextPosition } from "components/constants"; +import { Link } from "react-router-dom"; +import { Tooltip } from "@blueprintjs/core"; +import { isEllipsisActive } from "utils/helpers"; + +export const CommonTitleTextStyle = css` + color: ${Colors.CHARCOAL}; + font-weight: normal; +`; + +export const WorkspaceInviteWrapper = styled.div` + > div { + margin-top: 0; + } +`; + +export const WorkspaceInviteTitle = styled.div` + padding: 0 0 10px 0; + & > span[type="h5"] { + ${CommonTitleTextStyle} + } +`; + +export const StyledForm = styled.form` + width: 100%; + background: ${(props) => props.theme.colors.modal.bg}; + &&& { + .wrapper > div:nth-child(1) { + width: 60%; + } + .wrapper > div:nth-child(2) { + width: 40%; + } + .bp3-input { + box-shadow: none; + } + .bp3-button { + padding-top: 5px; + } + } +`; + +export const ErrorBox = styled.div<{ message?: boolean }>` + ${(props) => + props.message ? `margin: ${props.theme.spaces[9]}px 0px;` : null}; +`; + +export const StyledInviteFieldGroup = styled.div` + display: flex; + align-items: baseline; + justify-content: space-between; + + .wrapper { + display: flex; + width: 85%; + flex-direction: row; + align-items: baseline; + justify-content: space-between; + margin-right: ${(props) => props.theme.spaces[3]}px; + border-right: 0px; + } +`; + +export const InviteModalStyles = createGlobalStyle` + .label-container > * { + word-break: break-word; + } +`; + +export const UserList = styled.div` + margin-top: 24px; + max-height: 260px; + overflow-y: auto; + &&::-webkit-scrollbar-thumb { + background-color: ${(props) => props.theme.colors.modal.scrollbar}; + } +`; + +export const User = styled.div` + display: flex; + align-items: center; + min-height: 54px; + padding: 5px 0 5px 15px; + justify-content: space-between; + color: ${(props) => props.theme.colors.modal.user.textColor}; +`; + +export const UserInfo = styled.div` + display: inline-flex; + align-items: center; + div:first-child { + cursor: default; + } +`; + +export const UserRole = styled.div` + flex-basis: 40%; + flex-shrink: 0; + .${Classes.TEXT} { + color: ${Colors.COD_GRAY}; + display: inline-block; + width: 100%; + word-break: break-word; + } +`; + +export const UserName = styled.div` + display: flex; + flex-direction: column; + margin: 0 10px; + span { + word-break: break-word; + + &:nth-child(1) { + margin-bottom: 1px; + } + + &[type="h5"] { + color: ${Colors.COD_GRAY}; + } + + &[type="p2"] { + color: ${Colors.GRAY}; + } + } +`; + +export const RoleDivider = styled.div` + border-top: 1px solid ${(props) => props.theme.colors.menuBorder}; +`; + +export const Loading = styled(Spinner)` + padding-top: 10px; + margin: auto; + width: 100%; +`; + +export const MailConfigContainer = styled.div` + display: flex; + flex-direction: column; + padding: 24px 4px; + padding-bottom: 0; + align-items: center; + && > span { + color: ${(props) => props.theme.colors.modal.email.message}; + font-weight: 500; + font-size: 14px; + } + && > a { + color: ${(props) => props.theme.colors.modal.email.desc}; + font-size: 12px; + text-decoration: underline; + } +`; + +export const LabelText = styled(Text)` + font-size: 14px; + color: ${Colors.GREY_8}; + margin-bottom: 8px; + line-height: 1.57; + letter-spacing: -0.24px; +`; + +/*const LinksWrapper = styled.div` + &:before { + border-top: 1px solid var(--appsmith-color-black-200); + content: ""; + position: absolute; + left: 12px; + right: 12px; + } +`;*/ + +export const LeftIconWrapper = styled.span` + font-size: 20px; + line-height: 19px; + margin-right: 10px; + height: 100%; + position: relative; + top: 1px; +`; + +export const SelectedIcon = styled(Icon)<{ name: string }>` + margin-right: 6px; + & > div:first-child { + height: 18px; + width: 18px; + svg { + height: 18px; + width: 18px; + rect { + fill: ${(props) => props.theme.colors.dropdownIconBg}; + rx: 0; + } + path { + fill: ${(props) => props.theme.colors.propertyPane.label}; + } + } + } + svg { + ${(props) => + props.name === "right-arrow" ? `transform: rotate(-45deg);` : ``} + path { + fill: ${(props) => + props.fillColor + ? props.fillColor + : props.theme.colors.dropdown.selected.icon}; + } + } +`; + +export const StyledSubText = styled(Text)<{ + showDropIcon?: boolean; + subTextPosition?: SubTextPosition; +}>` + ${(props) => + props.subTextPosition === SubTextPosition.BOTTOM + ? "margin-top: 3px" + : "margin-left: auto"}; + &&& { + color: ${(props) => props.theme.colors.dropdown.menu.subText}; + } + &.sub-text { + color: ${(props) => props.theme.colors.dropdown.selected.subtext}; + text-align: end; + margin-right: ${(props) => `${props.theme.spaces[4]}px`}; + } +`; + +export const OptionWrapper = styled.div<{ + disabled?: boolean; + selected: boolean; + subTextPosition?: SubTextPosition; + selectedHighlightBg?: string; +}>` + padding: ${(props) => props.theme.spaces[3] + 1}px + ${(props) => props.theme.spaces[5]}px; + ${(props) => (!props.disabled ? "cursor: pointer" : "")}; + display: flex; + width: 100%; + min-height: 36px; + flex-direction: ${(props) => + props.subTextPosition === SubTextPosition.BOTTOM ? "column" : "row"}; + align-items: ${(props) => + props.subTextPosition === SubTextPosition.BOTTOM ? "flex-start" : "center"}; + background-color: ${(props) => + props.selected + ? props.selectedHighlightBg || `var(--appsmith-color-black-200)` + : `initial`}; + &&& svg { + rect { + fill: ${(props) => props.theme.colors.dropdownIconBg}; + } + } + .bp3-popover-wrapper { + width: 100%; + } + .${Classes.TEXT} { + color: ${(props) => + props.disabled + ? Colors.GRAY2 + : props.selected + ? props.theme.colors.dropdown.menu.hoverText + : props.theme.colors.dropdown.menu.text}; + } + .${Classes.ICON} { + margin-right: ${(props) => props.theme.spaces[5]}px; + svg { + path { + ${(props) => + props.selected + ? `fill: ${props.theme.colors.dropdown.selected.icon}` + : `fill: ${props.theme.colors.dropdown.icon}`}; + } + } + } + &:hover { + background-color: ${(props) => props.selectedHighlightBg || `initial`}; + &&& svg { + rect { + fill: ${(props) => props.theme.colors.textOnDarkBG}; + } + } + .${Classes.TEXT} { + color: ${(props) => props.theme.colors.dropdown.menu.hoverText}; + } + ${StyledSubText} { + color: ${(props) => props.theme.colors.dropdown.menu.subText}; + } + .${Classes.ICON} { + svg { + path { + fill: ${(props) => props.theme.colors.dropdown.hovered.icon}; + } + } + } + } +`; + +export const StyledText = styled(Text)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +export const LabelWrapper = styled.div<{ label?: string }>` + display: flex; + flex-direction: column; + align-items: flex-start; + span:last-child { + margin-top: ${(props) => props.theme.spaces[2] - 1}px; + } + &:hover { + .${Classes.TEXT} { + color: ${(props) => props.theme.colors.dropdown.selected.text}; + } + } +`; + +export function TooltipWrappedText( + props: TextProps & { + label: string; + }, +) { + const { label, ...textProps } = props; + const targetRef = useRef(null); + return ( + + + {label} + + + ); +} + +const validateFormValues = (values: { + users: string; + role?: string; + roles?: Partial[]; +}) => { + if (values.users && values.users.length > 0) { + const _users = values.users.split(",").filter(Boolean); + + _users.forEach((user) => { + if (!isEmail(user)) { + throw new SubmissionError({ + _error: createMessage(INVITE_USERS_VALIDATION_EMAIL_LIST), + }); + } + }); + } else { + throw new SubmissionError({ + _error: createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY), + }); + } + + if ( + typeof values.roles === "undefined" && + (typeof values.role === "undefined" || values.role?.trim().length === 0) + ) { + throw new SubmissionError({ + _error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY), + }); + } + + if ( + typeof values.role === "undefined" && + (typeof values.roles === "undefined" || values.roles.length === 0) + ) { + throw new SubmissionError({ + _error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY), + }); + } +}; + +const validate = (values: any) => { + const errors: any = {}; + if (!(values.users && values.users.length > 0)) { + errors["users"] = createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY); + } + + if ( + typeof values.roles === "undefined" && + (typeof values.role === "undefined" || values.role?.trim().length === 0) + ) { + errors["role"] = createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY); + } + + if ( + typeof values.role === "undefined" && + (typeof values.roles === "undefined" || values.roles.length === 0) + ) { + errors["roles"] = createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY); + } + + if (values.users && values.users.length > 0) { + const _users = values.users.split(",").filter(Boolean); + + _users.forEach((user: string) => { + if (!isEmail(user)) { + errors["users"] = createMessage(INVITE_USERS_VALIDATION_EMAIL_LIST); + } + }); + } + return errors; +}; + +export const { mailEnabled } = getAppsmithConfigs(); + +export const InviteButtonWidth = "88px"; + +function WorkspaceInviteUsersForm(props: any) { + const [emailError, setEmailError] = useState(""); + const [selectedOption, setSelectedOption] = useState({}); + const userRef = React.createRef(); + const selectedId = props?.selected?.id; + const multiSelectDropdownOptions: Partial[] = + props.options && props.options.length > 0 && props.isMultiSelectDropdown + ? props.options + : []; + + const selected = useMemo( + () => + selectedId && + props.selected && { + label: props.selected.rolename, + value: props.selected.rolename, + id: props.selected.id, + }, + [selectedId], + ); + + const { + allUsers, + anyTouched, + disableEmailSetup = false, + disableManageUsers = false, + disableUserList = false, + error, + fetchAllRoles, + fetchCurrentWorkspace, + fetchUser, + handleSubmit, + isAclFlow = false, + isApplicationInvite, + isLoading, + isMultiSelectDropdown = false, + links = [], + message = "", + placeholder = "", + submitFailed, + submitSucceeded, + submitting, + valid, + } = props; + + const [selectedItems, setSelectedItems] = useState([]); + + // set state for checking number of users invited + const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0); + const currentWorkspace = useSelector(getCurrentAppWorkspace); + + const userWorkspacePermissions = currentWorkspace?.userPermissions ?? []; + const canManage = isPermitted( + userWorkspacePermissions, + PERMISSION_TYPE.MANAGE_WORKSPACE, + ); + + useEffect(() => { + if (!isAclFlow) { + fetchUser(props.workspaceId); + fetchAllRoles(props.workspaceId); + fetchCurrentWorkspace(props.workspaceId); + } + }, [props.workspaceId, fetchUser, fetchAllRoles, fetchCurrentWorkspace]); + + useEffect(() => { + if (selected) { + setSelectedItems([selected]); + props.initialize({ + roles: [selected], + }); + } + }, []); + + const styledRoles = props.roles.map((role: any) => { + return { + id: role.id, + value: role.name, + label: role.description, + }; + }); + + styledRoles.push(...links); + + const theme = useContext(ThemeContext); + + const allUsersProfiles = React.useMemo( + () => + allUsers.map( + (user: { + userId: string; + username: string; + permissionGroupId: string; + permissionGroupName: string; + name: string; + }) => { + const details = getInitialsAndColorCode( + user.name || user.username, + theme.colors.appCardColors, + ); + return { + ...user, + initials: details[0], + }; + }, + ), + [allUsers, theme], + ); + + const onSelect = (_value?: string, options?: any) => { + setSelectedItems(options); + }; + + const onRemoveOptions = (updatedItems: any) => { + setSelectedItems(updatedItems); + }; + + const getLabel = (selectedOption: Partial[]) => { + return ( + + {`${ + selected + ? selectedOption[0].label + : `${selectedOption?.length} Selected` + }`} + + ); + }; + + const errorHandler = (error: string, values: string[]) => { + if (values && values.length > 0) { + let error = ""; + values.forEach((user: any) => { + if (!isEmail(user)) { + error = createMessage(INVITE_USERS_VALIDATION_EMAIL_LIST); + } + }); + setEmailError(error); + } else { + props.customError?.(""); + } + }; + + const renderOption = ({ + index, + option, + optionClickHandler, + }: { + index?: number; + option: DropdownOption | DropdownOption[]; + optionClickHandler?: (dropdownOption: DropdownOption) => void; + }) => { + let isSelected = false; + if (props.isMultiSelect && Array.isArray(selected) && selected.length) { + isSelected = !!selected.find((selectedOption: any) => + !Array.isArray(option) ? selectedOption.value === option.value : false, + ); + } else { + isSelected = + !Array.isArray(option) && selected + ? selected.value === option.value + : false; + } + return !Array.isArray(option) && !option.isSectionHeader ? ( + !option.link ? ( + + props.removeSelectedOptionClickHandler(option) + : () => optionClickHandler?.(option) + } + role="option" + selected={ + props.isMultiSelect ? props.highlightIndex === index : isSelected + } + selectedHighlightBg={props.selectedHighlightBg} + subTextPosition={option.subTextPosition ?? SubTextPosition.LEFT} + > + {option.leftElement && ( + {option.leftElement} + )} + {option.icon ? ( + + ) : null} + {props.showLabelOnly ? ( + props.truncateOption ? ( + <> + + {option.hasCustomBadge && props.customBadge} + + ) : ( + <> + {option.label} + {option.hasCustomBadge && props.customBadge} + + ) + ) : option.label && option.value ? ( + + {option.value} + {option.label} + + ) : props.truncateOption ? ( + + ) : ( + {option.value} + )} + {option.subText ? ( + + {option.subText} + + ) : null} + + + ) : ( + + + {option.leftElement && ( + {option.leftElement} + )} + {option.icon ? ( + + ) : null} + {option.value} + {option.subText ? ( + + {option.subText} + + ) : null} + + + ) + ) : ( + + ); + }; + + return ( + + + {isApplicationInvite && ( + + + Invite users to {currentWorkspace?.name}{" "} + + + )} + { + validateFormValues(values); + AnalyticsUtil.logEvent("INVITE_USER", values); + const usersAsStringsArray = values.users.split(","); + // update state to show success message correctly + updateNumberOfUsersInvited(usersAsStringsArray.length); + const users = usersAsStringsArray + .filter((user: any) => isEmail(user)) + .join(","); + return inviteUsersToWorkspace( + { + ...values, + users, + permissionGroupId: selectedOption.id, + workspaceId: props.workspaceId, + }, + dispatch, + ); + })} + > + {message} + +
+ + errorHandler(err, values || []) + } + data-cy="t--invite-email-input" + intent="success" + label="Emails" + name="users" + placeholder={placeholder || "Enter email address"} + type="text" + /> + {isMultiSelectDropdown ? ( + []) => + getLabel(selected) + } + name="roles" + onSelect={onSelect} + options={multiSelectDropdownOptions} + outline={false} + placeholder="Select a role" + removeSelectedOption={onRemoveOptions} + selected={selectedItems} + showLabelOnly + size="small" + /> + ) : ( + setSelectedOption(option)} + options={styledRoles} + outline={false} + placeholder="Select a role" + renderOption={renderOption} + size="small" + /> + )} +
+