chore: Split share modal component into smaller components (#27681)

## Description

Split share modal component into smaller components

#### PR fixes following issue(s)
Fixes [#27851](https://github.com/appsmithorg/appsmith/issues/27851)
[#27671](https://github.com/appsmithorg/appsmith/issues/27671)

#### Type of change
- Chore (housekeeping or task changes that don't impact user perception)

## Testing

#### How Has This Been Tested?
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [x] Cypress

## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed

---------

Co-authored-by: Dipyaman Biswas <dipyaman@appsmith.com>
This commit is contained in:
Ankita Kinger 2023-10-09 00:18:46 +05:30 committed by GitHub
parent 36aec9863b
commit 21f83023a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 864 additions and 782 deletions

View File

@ -98,7 +98,7 @@
"dayjs": "^1.10.6",
"deep-diff": "^1.0.2",
"design-system": "npm:@appsmithorg/design-system@2.1.21",
"design-system-old": "npm:@appsmithorg/design-system-old@1.1.11",
"design-system-old": "npm:@appsmithorg/design-system-old@1.1.12",
"downloadjs": "^1.4.7",
"echarts": "^5.4.2",
"echarts-gl": "^2.0.9",

View File

@ -32,7 +32,7 @@ import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstant
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import PageWrapper from "pages/common/PageWrapper";
import SubHeader from "pages/common/SubHeader";
import WorkspaceInviteUsersForm from "@appsmith/pages/workspace/WorkspaceInviteUsersForm";
import WorkspaceInviteUsersForm from "pages/workspace/WorkspaceInviteUsersForm";
import type { User } from "constants/userConstants";
import { getCurrentUser } from "selectors/usersSelectors";
import { CREATE_WORKSPACE_FORM_NAME } from "@appsmith/constants/forms";

View File

@ -0,0 +1,529 @@
import React, { useEffect, useState, useMemo, useRef } from "react";
import styled from "styled-components";
import TagListField from "components/editorComponents/form/fields/TagListField";
import { reduxForm, SubmissionError } from "redux-form";
import { connect, useSelector } from "react-redux";
import type { AppState } from "@appsmith/reducers";
import { getRolesForField } from "@appsmith/selectors/workspaceSelectors";
import type {
InviteUsersToWorkspaceFormValues,
InviteUsersProps,
} from "@appsmith/pages/workspace/helpers";
import { 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,
USERS_HAVE_ACCESS_TO_ALL_APPS,
BUSINESS_EDITION_TEXT,
INVITE_USER_RAMP_TEXT,
CUSTOM_ROLES_RAMP_TEXT,
CUSTOM_ROLE_DISABLED_OPTION_TEXT,
CUSTOM_ROLE_TEXT,
} from "@appsmith/constants/messages";
import { isEmail } from "utils/formhelpers";
import AnalyticsUtil from "utils/AnalyticsUtil";
import type { SelectOptionProps } from "design-system";
import { Callout, Checkbox } from "design-system";
import {
Button,
Icon,
Select,
Text,
Option,
Tooltip,
toast,
Link,
} from "design-system";
import {
fetchRolesForWorkspace,
fetchUsersForWorkspace,
fetchWorkspace,
} from "@appsmith/actions/workspaceActions";
import {
getRampLink,
showProductRamps,
} from "@appsmith/selectors/rampSelectors";
import {
RAMP_NAME,
RampFeature,
RampSection,
} from "utils/ProductRamps/RampsControlList";
import BusinessTag from "components/BusinessTag";
import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
import store from "store";
import { isGACEnabled } from "@appsmith/utils/planHelpers";
import type { DefaultOptionType } from "rc-select/lib/Select";
const featureFlags = selectFeatureFlags(store.getState());
const isFeatureEnabled = isGACEnabled(featureFlags);
export const StyledForm = styled.form`
width: 100%;
background: var(--ads-v2-color-bg);
&&& {
.wrapper > div:nth-child(1) {
width: 60%;
}
.wrapper > div:nth-child(2) {
width: 40%;
}
}
`;
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;
gap: 0.8rem;
`;
export const ErrorTextContainer = styled.div`
display: flex;
margin-top: 4px;
gap: 4px;
> p {
color: var(--ads-v2-color-fg-error);
}
svg {
path {
fill: var(--ads-v2-color-fg-error);
}
}
`;
export const WorkspaceText = styled.div`
a {
display: inline;
}
`;
export const CustomRoleRampTooltip = styled(Tooltip)`
pointer-events: auto;
`;
export const RampLink = styled(Link)`
display: inline;
`;
export const StyledCheckbox = styled(Checkbox)`
height: 16px;
.ads-v2-checkbox {
padding: 0;
}
`;
export const OptionLabel = styled(Text)`
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
`;
const validateFormValues = (values: {
users: string;
role?: string;
roles?: Partial<SelectOptionProps>[];
}) => {
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,
!isFeatureEnabled,
),
});
}
});
} else {
throw new SubmissionError({
_error: createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY),
});
}
if (typeof values.role === "undefined" || values.role.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.role === "undefined" || values.role.length === 0) {
errors["role"] = 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,
!isFeatureEnabled,
);
}
});
}
return errors;
};
export function InviteUserText({
isApplicationPage,
}: {
isApplicationPage: boolean;
}) {
const rampLinkSelector = getRampLink({
section: RampSection.AppShare,
feature: RampFeature.Gac,
});
const rampLink = useSelector(rampLinkSelector);
const showRampSelector = showProductRamps(RAMP_NAME.INVITE_USER_TO_APP);
const canShowRamp = useSelector(showRampSelector);
return (
<Text
color="var(--ads-v2-color-fg)"
data-testid="helper-message"
kind="action-m"
>
{canShowRamp && isApplicationPage ? (
<>
{createMessage(INVITE_USER_RAMP_TEXT)}
<Link kind="primary" target="_blank" to={rampLink}>
{createMessage(BUSINESS_EDITION_TEXT)}
</Link>
</>
) : (
createMessage(USERS_HAVE_ACCESS_TO_ALL_APPS)
)}
</Text>
);
}
export function CustomRolesRamp() {
const [dynamicProps, setDynamicProps] = useState<any>({});
const rampLinkSelector = getRampLink({
section: RampSection.WorkspaceShare,
feature: RampFeature.Gac,
});
const rampLink = useSelector(rampLinkSelector);
const rampText = (
<Text color="var(--ads-v2-color-white)" kind="action-m">
{createMessage(CUSTOM_ROLES_RAMP_TEXT)}{" "}
<RampLink
className="inline"
kind="primary"
onClick={() => {
setDynamicProps({ visible: false });
window.open(rampLink, "_blank");
// This reset of prop is required because, else the tooltip will be controlled by the state
setTimeout(() => {
setDynamicProps({});
}, 1);
}}
>
{createMessage(BUSINESS_EDITION_TEXT)}
</RampLink>
</Text>
);
return (
<CustomRoleRampTooltip
content={rampText}
placement="right"
{...dynamicProps}
>
<div className="flex flex-col gap-1">
<div className="flex gap-1">
<Text color="var(--ads-v2-color-fg-emphasis)" kind="heading-xs">
{createMessage(CUSTOM_ROLE_TEXT)}
</Text>
<BusinessTag size="md" />
</div>
<Text kind="body-s">
{createMessage(CUSTOM_ROLE_DISABLED_OPTION_TEXT)}
</Text>
</div>
</CustomRoleRampTooltip>
);
}
function InviteUsersForm(props: any) {
const [emailError, setEmailError] = useState("");
const [selectedOption, setSelectedOption] = useState<any[]>([]);
const selectedId = props?.selected?.id;
const showRampSelector = showProductRamps(RAMP_NAME.CUSTOM_ROLES);
const canShowRamp = useSelector(showRampSelector);
const selected = useMemo(
() =>
selectedId &&
props.selected && {
description: props.selected.rolename,
value: props.selected.rolename,
key: props.selected.id,
},
[selectedId],
);
const {
anyTouched,
customProps = {},
error,
fetchAllRoles,
fetchCurrentWorkspace,
fetchUsers,
handleSubmit,
isAclFlow = false,
isApplicationPage = false,
isMultiSelectDropdown = false,
placeholder = "",
submitFailed,
submitSucceeded,
submitting,
valid,
} = props;
const { disableDropdown = false } = customProps;
// set state for checking number of users invited
const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
const invitedEmails = useRef<undefined | string[]>();
useEffect(() => {
setSelectedOption([]);
}, [submitSucceeded]);
useEffect(() => {
fetchCurrentWorkspace(props.workspaceId);
fetchAllRoles(props.workspaceId);
fetchUsers(props.workspaceId);
}, [props.workspaceId, fetchAllRoles, fetchCurrentWorkspace, fetchUsers]);
useEffect(() => {
if (selected) {
setSelectedOption([selected]);
props.initialize({
role: [selected],
});
}
}, []);
useEffect(() => {
if (submitSucceeded) {
toast.show(
numberOfUsersInvited > 1
? createMessage(INVITE_USERS_SUBMIT_SUCCESS)
: createMessage(INVITE_USER_SUBMIT_SUCCESS),
{ kind: "success" },
);
props?.checkIfInvitedUsersFromDifferentDomain?.(invitedEmails.current);
}
}, [submitSucceeded, invitedEmails.current]);
const styledRoles =
props.options && props.options.length > 0
? props.options
: props.roles.map((role: any) => {
return {
key: role.id,
value: role.name?.split(" - ")[0],
description: role.description,
};
});
const onSelect = (value: string, option: DefaultOptionType) => {
if (isMultiSelectDropdown) {
setSelectedOption((selectedOptions) => [...selectedOptions, option]);
} else {
setSelectedOption([option]);
}
};
const errorHandler = (error: string) => {
setEmailError(error);
};
const onRemoveOptions = (value: string, option: DefaultOptionType) => {
if (isMultiSelectDropdown) {
setSelectedOption((selectedOptions) =>
selectedOptions.filter((opt) => opt.value !== option.value),
);
}
};
return (
<StyledForm
onSubmit={handleSubmit((values: any, dispatch: any) => {
const roles = isMultiSelectDropdown
? selectedOption
.map((option: DefaultOptionType) => option.value)
.join(",")
: selectedOption[0].value;
validateFormValues({ ...values, role: roles });
const usersAsStringsArray = values.users.split(",");
// update state to show success message correctly
updateNumberOfUsersInvited(usersAsStringsArray.length);
const validEmails = usersAsStringsArray.filter((user: string) =>
isEmail(user),
);
const validEmailsString = validEmails.join(",");
invitedEmails.current = validEmails;
AnalyticsUtil.logEvent("INVITE_USER", {
...(!isFeatureEnabled ? { users: usersAsStringsArray } : {}),
role: roles,
numberOfUsersInvited: usersAsStringsArray.length,
orgId: props.workspaceId,
});
return inviteUsersToWorkspace(
{
...(props.workspaceId ? { workspaceId: props.workspaceId } : {}),
users: validEmailsString,
permissionGroupId: roles,
},
dispatch,
);
})}
>
<StyledInviteFieldGroup>
<div style={{ width: "60%" }}>
<TagListField
autofocus
className="ml-0.5"
customError={(err: string) => errorHandler(err)}
data-testid="t--invite-email-input"
intent="success"
label="Emails"
name="users"
placeholder={placeholder || "Enter email address(es)"}
type="email"
/>
{emailError && (
<ErrorTextContainer>
<Icon name="alert-line" size="sm" />
<Text kind="body-s" renderAs="p">
{emailError}
</Text>
</ErrorTextContainer>
)}
</div>
<div style={{ width: "40%" }}>
<Select
data-testid="t--invite-role-input"
getPopupContainer={(triggerNode) =>
triggerNode.parentNode.parentNode
}
isDisabled={disableDropdown}
isMultiSelect={isMultiSelectDropdown}
listHeight={400}
onDeselect={onRemoveOptions}
onSelect={onSelect}
optionLabelProp="label"
placeholder="Select a role"
value={selectedOption}
>
{styledRoles.map((role: DefaultOptionType) => (
<Option key={role.key} label={role.value} value={role.key}>
<div className="flex gap-1 items-center">
{isMultiSelectDropdown && (
<StyledCheckbox
isSelected={selectedOption.find((v) => v.key == role.key)}
/>
)}
<div className="flex flex-col gap-1">
<OptionLabel
color="var(--ads-v2-color-fg-emphasis)"
kind={role.description && "heading-xs"}
>
{role.value}
</OptionLabel>
{role.description && (
<Text kind="body-s">{role.description}</Text>
)}
</div>
</div>
</Option>
))}
{canShowRamp && (
<Option disabled>
<CustomRolesRamp />
</Option>
)}
</Select>
</div>
<div>
<Button
className="t--invite-user-btn"
isDisabled={!valid || selectedOption.length === 0}
isLoading={submitting && !(submitFailed && !anyTouched)}
size="md"
type="submit"
>
Invite
</Button>
</div>
</StyledInviteFieldGroup>
{!isAclFlow && (
<div className="flex gap-2 mt-2 items-start">
<Icon className="mt-1" name="user-3-line" size="md" />
<WorkspaceText>
<InviteUserText isApplicationPage={isApplicationPage} />
</WorkspaceText>
</div>
)}
<ErrorBox message={submitFailed}>
{submitFailed && error && <Callout kind="error">{error}</Callout>}
</ErrorBox>
</StyledForm>
);
}
export const mapStateToProps = (
state: AppState,
{ formName }: { formName?: string },
) => {
return {
roles: getRolesForField(state),
form: formName || INVITE_USERS_TO_WORKSPACE_FORM,
};
};
export const mapDispatchToProps = (dispatch: any) => ({
fetchAllRoles: (workspaceId: string) =>
dispatch(fetchRolesForWorkspace(workspaceId)),
fetchCurrentWorkspace: (workspaceId: string) =>
dispatch(fetchWorkspace(workspaceId)),
fetchUsers: (workspaceId: string) =>
dispatch(fetchUsersForWorkspace(workspaceId)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm<InviteUsersToWorkspaceFormValues, InviteUsersProps>({
validate,
})(InviteUsersForm),
);

View File

@ -36,7 +36,7 @@ import {
PERMISSION_TYPE,
} from "@appsmith/utils/permissionHelpers";
import { getInitials } from "utils/AppsmithUtils";
import { CustomRolesRamp } from "@appsmith/pages/workspace/WorkspaceInviteUsersForm";
import { CustomRolesRamp } from "@appsmith/pages/workspace/InviteUsersForm";
import { showProductRamps } from "@appsmith/selectors/rampSelectors";
import { RAMP_NAME } from "utils/ProductRamps/RampsControlList";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";

View File

@ -1,766 +0,0 @@
import React, { useEffect, useState, useMemo, useRef } from "react";
import styled from "styled-components";
import TagListField from "components/editorComponents/form/fields/TagListField";
import { reduxForm, SubmissionError } from "redux-form";
import { connect, useSelector } from "react-redux";
import type { AppState } from "@appsmith/reducers";
import {
getRolesForField,
getAllUsers,
getCurrentAppWorkspace,
} from "@appsmith/selectors/workspaceSelectors";
import type { InviteUsersToWorkspaceFormValues } from "@appsmith/pages/workspace/helpers";
import { 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,
USERS_HAVE_ACCESS_TO_ALL_APPS,
NO_USERS_INVITED,
BUSINESS_EDITION_TEXT,
INVITE_USER_RAMP_TEXT,
CUSTOM_ROLES_RAMP_TEXT,
CUSTOM_ROLE_DISABLED_OPTION_TEXT,
CUSTOM_ROLE_TEXT,
} from "@appsmith/constants/messages";
import { isEmail } from "utils/formhelpers";
import {
isPermitted,
PERMISSION_TYPE,
} from "@appsmith/utils/permissionHelpers";
import { getAppsmithConfigs } from "@appsmith/configs";
import AnalyticsUtil from "utils/AnalyticsUtil";
import type { SelectOptionProps } from "design-system";
import { Callout, Checkbox } from "design-system";
import {
Avatar,
Button,
Icon,
Select,
Spinner,
Text,
Option,
Tooltip,
toast,
Link,
} from "design-system";
import { getInitialsFromName } from "utils/AppsmithUtils";
import ManageUsers from "pages/workspace/ManageUsers";
import {
fetchRolesForWorkspace,
fetchUsersForWorkspace,
fetchWorkspace,
} from "@appsmith/actions/workspaceActions";
import { USER_PHOTO_ASSET_URL } from "constants/userConstants";
import { importSvg } from "design-system-old";
import type { WorkspaceUserRoles } from "@appsmith/constants/workspaceConstants";
import {
getRampLink,
showProductRamps,
} from "@appsmith/selectors/rampSelectors";
import {
RAMP_NAME,
RampFeature,
RampSection,
} from "utils/ProductRamps/RampsControlList";
import BusinessTag from "components/BusinessTag";
import { getDomainFromEmail } from "utils/helpers";
import { getCurrentUser } from "selectors/usersSelectors";
import PartnerProgramCallout from "./PartnerProgramCallout";
import {
getPartnerProgramCalloutShown,
setPartnerProgramCalloutShown,
} from "utils/storage";
import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
import store from "store";
import { isGACEnabled } from "@appsmith/utils/planHelpers";
const NoEmailConfigImage = importSvg(
() => import("assets/images/email-not-configured.svg"),
);
const { cloudHosting } = getAppsmithConfigs();
const featureFlags = selectFeatureFlags(store.getState());
const isFeatureEnabled = isGACEnabled(featureFlags);
export const WorkspaceInviteWrapper = styled.div`
> div {
margin-top: 0;
}
`;
export const StyledForm = styled.form`
width: 100%;
background: var(--ads-v2-color-bg);
&&& {
.wrapper > div:nth-child(1) {
width: 60%;
}
.wrapper > div:nth-child(2) {
width: 40%;
}
}
`;
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;
gap: 0.8rem;
`;
export const UserList = styled.div`
margin-top: 10px;
max-height: 260px;
overflow-y: auto;
justify-content: space-between;
margin-left: 0.1rem;
`;
export const User = styled.div`
display: flex;
align-items: center;
min-height: 54px;
justify-content: space-between;
border-bottom: 1px solid var(--ads-v2-color-border);
`;
export const UserInfo = styled.div`
display: inline-flex;
align-items: center;
div:first-child {
cursor: default;
}
`;
export const UserRole = styled.div`
span {
word-break: break-word;
margin-right: 8px;
}
`;
export const UserName = styled.div`
display: flex;
flex-direction: column;
margin: 0 10px;
span {
word-break: break-word;
&:nth-child(1) {
margin-bottom: 1px;
}
}
`;
export const MailConfigContainer = styled.div`
display: flex;
flex-direction: column;
padding: 24px 4px;
padding-bottom: 0;
align-items: center;
&& > span {
color: var(--ads-v2-color-fg);
}
`;
export const ManageUsersContainer = styled.div`
padding: 12px 0;
`;
export const ErrorTextContainer = styled.div`
display: flex;
margin-top: 4px;
gap: 4px;
> p {
color: var(--ads-v2-color-fg-error);
}
svg {
path {
fill: var(--ads-v2-color-fg-error);
}
}
`;
export const WorkspaceText = styled.div`
a {
display: inline;
}
`;
export const CustomRoleRampTooltip = styled(Tooltip)`
pointer-events: auto;
`;
export const RampLink = styled(Link)`
display: inline;
`;
export const StyledCheckbox = styled(Checkbox)`
height: 16px;
.ads-v2-checkbox {
padding: 0;
}
`;
export const OptionLabel = styled(Text)`
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
`;
const validateFormValues = (values: {
users: string;
role?: string;
roles?: Partial<SelectOptionProps>[];
}) => {
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,
!isFeatureEnabled,
),
});
}
});
} else {
throw new SubmissionError({
_error: createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY),
});
}
if (typeof values.role === "undefined" || values.role.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.role === "undefined" || values.role.length === 0) {
errors["role"] = 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,
!isFeatureEnabled,
);
}
});
}
return errors;
};
function InviteUserText({
isApplicationInvite,
}: {
isApplicationInvite: boolean;
}) {
const rampLinkSelector = getRampLink({
section: RampSection.AppShare,
feature: RampFeature.Gac,
});
const rampLink = useSelector(rampLinkSelector);
const showRampSelector = showProductRamps(RAMP_NAME.INVITE_USER_TO_APP);
const canShowRamp = useSelector(showRampSelector);
return (
<Text
color="var(--ads-v2-color-fg)"
data-testid="helper-message"
kind="action-m"
>
{canShowRamp && isApplicationInvite ? (
<>
{createMessage(INVITE_USER_RAMP_TEXT)}
<Link kind="primary" target="_blank" to={rampLink}>
{createMessage(BUSINESS_EDITION_TEXT)}
</Link>
</>
) : (
createMessage(USERS_HAVE_ACCESS_TO_ALL_APPS)
)}
</Text>
);
}
export function CustomRolesRamp() {
const [dynamicProps, setDynamicProps] = useState<any>({});
const rampLinkSelector = getRampLink({
section: RampSection.WorkspaceShare,
feature: RampFeature.Gac,
});
const rampLink = useSelector(rampLinkSelector);
const rampText = (
<Text color="var(--ads-v2-color-white)" kind="action-m">
{createMessage(CUSTOM_ROLES_RAMP_TEXT)}{" "}
<RampLink
className="inline"
kind="primary"
onClick={() => {
setDynamicProps({ visible: false });
window.open(rampLink, "_blank");
// This reset of prop is required because, else the tooltip will be controlled by the state
setTimeout(() => {
setDynamicProps({});
}, 1);
}}
>
{createMessage(BUSINESS_EDITION_TEXT)}
</RampLink>
</Text>
);
return (
<CustomRoleRampTooltip
content={rampText}
placement="right"
{...dynamicProps}
>
<div className="flex flex-col gap-1">
<div className="flex gap-1">
<Text color="var(--ads-v2-color-fg-emphasis)" kind="heading-xs">
{createMessage(CUSTOM_ROLE_TEXT)}
</Text>
<BusinessTag size="md" />
</div>
<Text kind="body-s">
{createMessage(CUSTOM_ROLE_DISABLED_OPTION_TEXT)}
</Text>
</div>
</CustomRoleRampTooltip>
);
}
function WorkspaceInviteUsersForm(props: any) {
const [emailError, setEmailError] = useState("");
const [selectedOption, setSelectedOption] = useState<any[]>([]);
const userRef = React.createRef<HTMLDivElement>();
// const history = useHistory();
const selectedId = props?.selected?.id;
const currentUser = useSelector(getCurrentUser);
const showRampSelector = showProductRamps(RAMP_NAME.CUSTOM_ROLES);
const canShowRamp = useSelector(showRampSelector);
const selected = useMemo(
() =>
selectedId &&
props.selected && {
description: props.selected.rolename,
value: props.selected.rolename,
key: props.selected.id,
},
[selectedId],
);
const {
allUsers,
anyTouched,
customProps = {},
error,
fetchAllRoles,
fetchCurrentWorkspace,
fetchUser,
handleSubmit,
isApplicationInvite = false,
isLoading,
isMultiSelectDropdown = false,
placeholder = "",
submitFailed,
submitSucceeded,
submitting,
valid,
} = props;
const {
disableDropdown = false,
disableManageUsers = false,
disableUserList = false,
} = customProps;
// set state for checking number of users invited
const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
const currentWorkspace = useSelector(getCurrentAppWorkspace);
const invitedEmails = useRef<undefined | string[]>();
const emailOutsideCurrentDomain = useRef<undefined | string>();
const [showPartnerProgramCallout, setShowPartnerProgramCallout] =
useState(false);
const userWorkspacePermissions = currentWorkspace?.userPermissions ?? [];
const canManage = isPermitted(
userWorkspacePermissions,
PERMISSION_TYPE.MANAGE_WORKSPACE,
);
useEffect(() => {
setSelectedOption([]);
}, [submitSucceeded]);
useEffect(() => {
fetchUser(props.workspaceId);
fetchAllRoles(props.workspaceId);
fetchCurrentWorkspace(props.workspaceId);
}, [props.workspaceId, fetchUser, fetchAllRoles, fetchCurrentWorkspace]);
useEffect(() => {
if (selected) {
setSelectedOption([selected]);
props.initialize({
role: [selected],
});
}
}, []);
useEffect(() => {
if (submitSucceeded) {
toast.show(
numberOfUsersInvited > 1
? createMessage(INVITE_USERS_SUBMIT_SUCCESS)
: createMessage(INVITE_USER_SUBMIT_SUCCESS),
{ kind: "success" },
);
checkIfInvitedUsersFromDifferentDomain();
}
}, [submitSucceeded]);
const styledRoles =
props.options && props.options.length > 0
? props.options
: props.roles.map((role: any) => {
return {
key: role.id,
value: role.name?.split(" - ")[0],
description: role.description,
};
});
const allUsersProfiles = React.useMemo(
() =>
allUsers.map(
(user: {
userId: string;
username: string;
permissionGroupId: string;
permissionGroupName: string;
name: string;
}) => ({
...user,
initials: getInitialsFromName(user.name || user.username),
}),
),
[allUsers],
);
const onSelect = (value: string, option?: any) => {
if (isMultiSelectDropdown) {
setSelectedOption((selectedOptions) => [...selectedOptions, option]);
} else {
setSelectedOption([option]);
}
};
const errorHandler = (error: string) => {
setEmailError(error);
};
const onRemoveOptions = (value: string, option?: any) => {
if (isMultiSelectDropdown) {
setSelectedOption((selectedOptions) =>
selectedOptions.filter((opt) => opt.value !== option.value),
);
}
};
const checkIfInvitedUsersFromDifferentDomain = async () => {
if (!currentUser?.email) return true;
const currentUserEmail = currentUser?.email;
const partnerProgramCalloutShown = await getPartnerProgramCalloutShown();
const currentUserDomain = getDomainFromEmail(currentUserEmail);
if (invitedEmails.current && !partnerProgramCalloutShown) {
const _emailOutsideCurrentDomain = invitedEmails.current.find(
(email) => getDomainFromEmail(email) !== currentUserDomain,
);
if (_emailOutsideCurrentDomain) {
emailOutsideCurrentDomain.current = _emailOutsideCurrentDomain;
invitedEmails.current = undefined;
setShowPartnerProgramCallout(true);
}
}
};
return (
<WorkspaceInviteWrapper>
<StyledForm
onSubmit={handleSubmit((values: any, dispatch: any) => {
const roles = isMultiSelectDropdown
? selectedOption.map((option: any) => option.value).join(",")
: selectedOption[0].value;
validateFormValues({ ...values, role: roles });
const usersAsStringsArray = values.users.split(",");
// update state to show success message correctly
updateNumberOfUsersInvited(usersAsStringsArray.length);
const validEmails = usersAsStringsArray.filter((user: any) =>
isEmail(user),
);
const validEmailsString = validEmails.join(",");
invitedEmails.current = validEmails;
AnalyticsUtil.logEvent("INVITE_USER", {
...(!isFeatureEnabled ? { users: usersAsStringsArray } : {}),
role: roles,
numberOfUsersInvited: usersAsStringsArray.length,
orgId: props.workspaceId,
});
return inviteUsersToWorkspace(
{
...(props.workspaceId ? { workspaceId: props.workspaceId } : {}),
users: validEmailsString,
permissionGroupId: roles,
},
dispatch,
);
})}
>
<StyledInviteFieldGroup>
<div style={{ width: "60%" }}>
<TagListField
autofocus
className="ml-0.5"
customError={(err: string) => errorHandler(err)}
data-testid="t--invite-email-input"
intent="success"
label="Emails"
name="users"
placeholder={placeholder || "Enter email address(es)"}
type="email"
/>
{emailError && (
<ErrorTextContainer>
<Icon name="alert-line" size="sm" />
<Text kind="body-s" renderAs="p">
{emailError}
</Text>
</ErrorTextContainer>
)}
</div>
<div style={{ width: "40%" }}>
<Select
data-testid="t--invite-role-input"
getPopupContainer={(triggerNode) =>
triggerNode.parentNode.parentNode
}
isDisabled={disableDropdown}
isMultiSelect={isMultiSelectDropdown}
listHeight={400}
onDeselect={onRemoveOptions}
onSelect={onSelect}
optionLabelProp="label"
placeholder="Select a role"
value={selectedOption}
>
{styledRoles.map((role: any) => (
<Option key={role.key} label={role.value} value={role.key}>
<div className="flex items-center gap-1">
{isMultiSelectDropdown && (
<StyledCheckbox
isSelected={selectedOption.find(
(v) => v.key == role.key,
)}
/>
)}
<div className="flex flex-col gap-1">
<OptionLabel
color="var(--ads-v2-color-fg-emphasis)"
kind={role.description && "heading-xs"}
>
{role.value}
</OptionLabel>
{role.description && (
<Text kind="body-s">{role.description}</Text>
)}
</div>
</div>
</Option>
))}
{canShowRamp && (
<Option disabled>
<CustomRolesRamp />
</Option>
)}
</Select>
</div>
<div>
<Button
className="t--invite-user-btn"
isDisabled={!valid || selectedOption.length === 0}
isLoading={submitting && !(submitFailed && !anyTouched)}
size="md"
type="submit"
>
Invite
</Button>
</div>
</StyledInviteFieldGroup>
<div className="flex items-start gap-2 mt-2">
<Icon className="mt-1" name="user-3-line" size="md" />
<WorkspaceText>
<InviteUserText isApplicationInvite={isApplicationInvite} />
</WorkspaceText>
</div>
{!cloudHosting &&
showPartnerProgramCallout &&
emailOutsideCurrentDomain.current && (
<div className="mt-2">
<PartnerProgramCallout
email={emailOutsideCurrentDomain.current}
onClose={() => {
setShowPartnerProgramCallout(false);
setPartnerProgramCalloutShown();
emailOutsideCurrentDomain.current = undefined;
}}
/>
</div>
)}
{isLoading ? (
<div className="pt-4 overflow-hidden">
<Spinner size="lg" />
</div>
) : (
<>
{allUsers.length === 0 && (
<MailConfigContainer data-testid="no-users-content">
<NoEmailConfigImage />
<Text kind="action-s">{createMessage(NO_USERS_INVITED)}</Text>
</MailConfigContainer>
)}
{!disableUserList && (
<UserList ref={userRef}>
{allUsersProfiles.map(
(user: {
username: string;
name: string;
roles: WorkspaceUserRoles[];
initials: string;
photoId?: string;
}) => {
return (
<User key={user.username}>
<UserInfo>
<Avatar
firstLetter={user.initials}
image={
user.photoId
? `/api/${USER_PHOTO_ASSET_URL}/${user.photoId}`
: undefined
}
isTooltipEnabled={false}
label={user.name || user.username}
/>
<UserName>
<Tooltip content={user.username} placement="top">
<Text
color="var(--ads-v2-color-fg)"
kind="heading-xs"
>
{user.name}
</Text>
</Tooltip>
</UserName>
</UserInfo>
<UserRole>
<Text kind="action-m">
{user.roles?.[0]?.name?.split(" - ")[0] || ""}
</Text>
</UserRole>
</User>
);
},
)}
</UserList>
)}
</>
)}
<ErrorBox message={submitFailed}>
{submitFailed && error && <Callout kind="error">{error}</Callout>}
</ErrorBox>
{canManage && !disableManageUsers && (
<ManageUsersContainer>
<ManageUsers workspaceId={props.workspaceId} />
</ManageUsersContainer>
)}
</StyledForm>
</WorkspaceInviteWrapper>
);
}
export const mapStateToProps = (
state: AppState,
{ formName }: { formName?: string },
) => ({
roles: getRolesForField(state),
allUsers: getAllUsers(state),
isLoading: state.ui.workspaces.loadingStates.isFetchAllUsers,
form: formName || INVITE_USERS_TO_WORKSPACE_FORM,
});
export const mapDispatchToProps = (dispatch: any) => ({
fetchAllRoles: (workspaceId: string) =>
dispatch(fetchRolesForWorkspace(workspaceId)),
fetchCurrentWorkspace: (workspaceId: string) =>
dispatch(fetchWorkspace(workspaceId)),
fetchUser: (workspaceId: string) =>
dispatch(fetchUsersForWorkspace(workspaceId)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm<
InviteUsersToWorkspaceFormValues,
{
roles?: any;
applicationId?: string;
workspaceId?: string;
isApplicationInvite?: boolean;
placeholder?: string;
customProps?: any;
selected?: any;
options?: any;
isMultiSelectDropdown?: boolean;
}
>({
validate,
})(WorkspaceInviteUsersForm),
);

View File

@ -1,6 +1,7 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { SubmissionError } from "redux-form";
import type { RouteChildrenProps, RouteComponentProps } from "react-router-dom";
import type { DefaultOptionType } from "rc-select/lib/Select";
export type InviteUsersToWorkspaceByRoleValues = {
id: string;
users?: string;
@ -12,6 +13,19 @@ export type InviteUsersToWorkspaceFormValues = {
usersByRole: InviteUsersToWorkspaceByRoleValues[];
};
export type InviteUsersProps = {
roles?: DefaultOptionType[];
applicationId?: string;
workspaceId?: string;
isApplicationPage?: boolean;
placeholder?: string;
customProps?: any;
selected?: any;
options?: any;
isMultiSelectDropdown?: boolean;
checkIfInvitedUsersFromDifferentDomain?: () => void;
};
export type CreateWorkspaceFormValues = {
name: string;
};

View File

@ -52,6 +52,10 @@ export const initialState: ApplicationsReduxState = {
isUploadingNavigationLogo: false,
isDeletingNavigationLogo: false,
deletingMultipleApps: {},
loadingStates: {
isFetchingAllRoles: false,
isFetchingAllUsers: false,
},
};
export const handlers = {
@ -850,6 +854,10 @@ export interface ApplicationsReduxState {
isUploadingNavigationLogo: boolean;
isDeletingNavigationLogo: boolean;
deletingMultipleApps: DeletingMultipleApps;
loadingStates: {
isFetchingAllRoles: boolean;
isFetchingAllUsers: boolean;
};
}
export interface Application {

View File

@ -278,3 +278,9 @@ export const selectEvaluationVersion = (state: AppState) =>
export const getDeletingMultipleApps = (state: AppState) => {
return state.ui.applications.deletingMultipleApps;
};
export const getApplicationLoadingStates = (state: AppState) => {
return state.ui.applications?.loadingStates;
};
export const getAllAppUsers = () => [];

View File

@ -0,0 +1,3 @@
export * from "ce/pages/workspace/InviteUsersForm";
import { default as CE_Invite_Users_Form } from "ce/pages/workspace/InviteUsersForm";
export default CE_Invite_Users_Form;

View File

@ -1,3 +0,0 @@
export * from "ce/pages/workspace/WorkspaceInviteUsersForm";
import { default as CE_Workspace_Invite_Users_Form } from "ce/pages/workspace/WorkspaceInviteUsersForm";
export default CE_Workspace_Invite_Users_Form;

View File

@ -67,8 +67,6 @@ const Description = styled.span<{ addLeftSpacing?: boolean }>`
padding-left: ${(props) => (props.addLeftSpacing ? `20px` : "0")};
margin-top: var(--ads-v2-spaces-2);
flex: 1;
display: flex;
`;
const UpperContent = styled.div`

View File

@ -8,7 +8,7 @@ import {
isPermitted,
PERMISSION_TYPE,
} from "@appsmith/utils/permissionHelpers";
import WorkspaceInviteUsersForm from "@appsmith/pages/workspace/WorkspaceInviteUsersForm";
import WorkspaceInviteUsersForm from "pages/workspace/WorkspaceInviteUsersForm";
import { getCurrentUser } from "selectors/usersSelectors";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
import { viewerURL } from "RouteBuilder";
@ -112,7 +112,7 @@ function AppInviteUsersForm(props: any) {
{canInviteToApplication && (
<WorkspaceInviteUsersForm
applicationId={applicationId}
isApplicationInvite
isApplicationPage
placeholder={createMessage(INVITE_USERS_PLACEHOLDER, !isGACEnabled)}
workspaceId={props.workspaceId}
/>

View File

@ -0,0 +1,293 @@
import React, { useState, useRef } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import {
getAllUsers,
getCurrentAppWorkspace,
getWorkspaceLoadingStates,
} from "@appsmith/selectors/workspaceSelectors";
import { createMessage, NO_USERS_INVITED } from "@appsmith/constants/messages";
import {
isPermitted,
PERMISSION_TYPE,
} from "@appsmith/utils/permissionHelpers";
import { getAppsmithConfigs } from "@appsmith/configs";
import { Avatar, Icon, Spinner, Text, Tooltip } from "design-system";
import { getInitialsFromName } from "utils/AppsmithUtils";
import ManageUsers from "pages/workspace/ManageUsers";
import { USER_PHOTO_ASSET_URL } from "constants/userConstants";
import { importSvg } from "design-system-old";
import type { WorkspaceUserRoles } from "@appsmith/constants/workspaceConstants";
import { getDomainFromEmail } from "utils/helpers";
import { getCurrentUser } from "selectors/usersSelectors";
import PartnerProgramCallout from "pages/workspace/PartnerProgramCallout";
import {
getPartnerProgramCalloutShown,
setPartnerProgramCalloutShown,
} from "utils/storage";
import InviteUsersForm from "@appsmith/pages/workspace/InviteUsersForm";
import { ENTITY_TYPE } from "@appsmith/constants/workspaceConstants";
import {
getAllAppUsers,
getApplicationLoadingStates,
} from "@appsmith/selectors/applicationSelectors";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
const NoEmailConfigImage = importSvg(
() => import("assets/images/email-not-configured.svg"),
);
const { cloudHosting } = getAppsmithConfigs();
export const WorkspaceInviteWrapper = styled.div``;
export const UserList = styled.div`
margin-top: 10px;
max-height: 260px;
overflow-y: auto;
justify-content: space-between;
margin-left: 0.1rem;
.user-icons {
width: 32px;
justify-content: center;
}
`;
export const User = styled.div`
display: flex;
align-items: center;
min-height: 54px;
justify-content: space-between;
border-bottom: 1px solid var(--ads-v2-color-border);
`;
export const UserInfo = styled.div`
display: inline-flex;
align-items: center;
div:first-child {
cursor: default;
}
`;
export const UserRole = styled.div`
span {
word-break: break-word;
margin-right: 8px;
}
`;
export const UserName = styled.div`
display: flex;
flex-direction: column;
margin: 0 10px;
span {
word-break: break-word;
&:nth-child(1) {
margin-bottom: 1px;
}
}
`;
export const MailConfigContainer = styled.div`
display: flex;
flex-direction: column;
padding: 24px 4px;
padding-bottom: 0;
align-items: center;
&& > span {
color: var(--ads-v2-color-fg);
}
`;
export const ManageUsersContainer = styled.div`
padding: 12px 0;
`;
function WorkspaceInviteUsers(props: any) {
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const userRef = React.createRef<HTMLDivElement>();
const currentUser = useSelector(getCurrentUser);
const currentWorkspace = useSelector(getCurrentAppWorkspace);
const showAppLevelInviteModal =
(isFeatureEnabled && props.isApplicationPage) || false;
const allUsers = useSelector(
showAppLevelInviteModal ? getAllAppUsers : getAllUsers,
);
const isLoading: boolean =
useSelector(
showAppLevelInviteModal
? getApplicationLoadingStates
: getWorkspaceLoadingStates,
)?.isFetchingAllUsers || false;
const emailOutsideCurrentDomain = useRef<undefined | string>();
const [showPartnerProgramCallout, setShowPartnerProgramCallout] =
useState(false);
const userWorkspacePermissions = currentWorkspace?.userPermissions ?? [];
const canManage = isPermitted(
userWorkspacePermissions,
PERMISSION_TYPE.MANAGE_WORKSPACE,
);
const allUsersProfiles = React.useMemo(
() =>
allUsers.map(
(user: {
userId: string;
username: string;
permissionGroupId: string;
permissionGroupName: string;
name: string;
roles: WorkspaceUserRoles[];
userGroupId?: string;
}) => ({
...user,
initials: getInitialsFromName(user.name || user.username),
}),
),
[allUsers],
);
const checkIfInvitedUsersFromDifferentDomain = async (
invitedEmails?: string[],
) => {
if (!currentUser?.email) return true;
const currentUserEmail = currentUser?.email;
const partnerProgramCalloutShown = await getPartnerProgramCalloutShown();
const currentUserDomain = getDomainFromEmail(currentUserEmail);
if (invitedEmails && !partnerProgramCalloutShown) {
const _emailOutsideCurrentDomain = invitedEmails.find(
(email) => getDomainFromEmail(email) !== currentUserDomain,
);
if (_emailOutsideCurrentDomain) {
emailOutsideCurrentDomain.current = _emailOutsideCurrentDomain;
invitedEmails = undefined;
setShowPartnerProgramCallout(true);
}
}
};
return (
<WorkspaceInviteWrapper>
<InviteUsersForm
{...props}
checkIfInvitedUsersFromDifferentDomain={
checkIfInvitedUsersFromDifferentDomain
}
/>
{!cloudHosting &&
showPartnerProgramCallout &&
emailOutsideCurrentDomain.current && (
<div className="mt-2">
<PartnerProgramCallout
email={emailOutsideCurrentDomain.current}
onClose={() => {
setShowPartnerProgramCallout(false);
setPartnerProgramCalloutShown();
emailOutsideCurrentDomain.current = undefined;
}}
/>
</div>
)}
{isLoading ? (
<div className="pt-4 overflow-hidden">
<Spinner size="lg" />
</div>
) : (
<>
{allUsers.length === 0 && (
<MailConfigContainer data-testid="no-users-content">
<NoEmailConfigImage />
<Text kind="action-s">{createMessage(NO_USERS_INVITED)}</Text>
</MailConfigContainer>
)}
<UserList ref={userRef}>
{allUsersProfiles.map(
(user: {
username: string;
name: string;
roles: WorkspaceUserRoles[];
initials: string;
photoId?: string;
userId: string;
userGroupId?: string;
}) => {
const showUser =
(showAppLevelInviteModal
? user.roles?.[0]?.entityType === ENTITY_TYPE.APPLICATION
: user.roles?.[0]?.entityType === ENTITY_TYPE.WORKSPACE) &&
user.roles?.[0]?.id;
return showUser ? (
<User
key={user?.userGroupId ? user.userGroupId : user.username}
>
<UserInfo>
{user?.userGroupId ? (
<>
<Icon
className="user-icons"
name="group-line"
size="lg"
/>
<UserName>
<Text
color="var(--ads-v2-color-fg)"
kind="heading-xs"
>
{user.name}
</Text>
</UserName>
</>
) : (
<>
<Avatar
firstLetter={user.initials}
image={
user.photoId
? `/api/${USER_PHOTO_ASSET_URL}/${user.photoId}`
: undefined
}
isTooltipEnabled={false}
label={user.name || user.username}
/>
<UserName>
<Tooltip content={user.username} placement="top">
<Text
color="var(--ads-v2-color-fg)"
kind="heading-xs"
>
{user.name}
</Text>
</Tooltip>
</UserName>
</>
)}
</UserInfo>
<UserRole>
<Text kind="action-m">
{user.roles?.[0]?.name?.split(" - ")[0] || ""}
</Text>
</UserRole>
</User>
) : null;
},
)}
</UserList>
</>
)}
{canManage && (
<ManageUsersContainer>
<ManageUsers workspaceId={props.workspaceId} />
</ManageUsersContainer>
)}
</WorkspaceInviteWrapper>
);
}
export default WorkspaceInviteUsers;

View File

@ -18,7 +18,7 @@ import { getAllApplications } from "@appsmith/actions/applicationActions";
import { useMediaQuery } from "react-responsive";
import { BackButton, StickyHeader } from "components/utils/helperComponents";
import { debounce } from "lodash";
import WorkspaceInviteUsersForm from "@appsmith/pages/workspace/WorkspaceInviteUsersForm";
import WorkspaceInviteUsersForm from "pages/workspace/WorkspaceInviteUsersForm";
import { SettingsPageHeader } from "./SettingsPageHeader";
import { navigateToTab } from "@appsmith/pages/workspace/helpers";
import {

View File

@ -10332,7 +10332,7 @@ __metadata:
dayjs: ^1.10.6
deep-diff: ^1.0.2
design-system: "npm:@appsmithorg/design-system@2.1.21"
design-system-old: "npm:@appsmithorg/design-system-old@1.1.11"
design-system-old: "npm:@appsmithorg/design-system-old@1.1.12"
diff: ^5.0.0
dotenv: ^8.1.0
downloadjs: ^1.4.7
@ -14329,9 +14329,9 @@ __metadata:
languageName: node
linkType: hard
"design-system-old@npm:@appsmithorg/design-system-old@1.1.11":
version: 1.1.11
resolution: "@appsmithorg/design-system-old@npm:1.1.11"
"design-system-old@npm:@appsmithorg/design-system-old@1.1.12":
version: 1.1.12
resolution: "@appsmithorg/design-system-old@npm:1.1.12"
dependencies:
emoji-mart: 3.0.1
peerDependencies:
@ -14351,7 +14351,7 @@ __metadata:
remixicon-react: ^1.0.0
styled-components: 5.3.6
tinycolor2: ^1.4.2
checksum: 968fc1be2ded862c2cac3bc8ae8eab6642d16ffda7b586aeb87ed69d1bad08ee5e3d1c05098adfb9e80f9c1a6fa6d4bc05d0cd464b700aa39005a5bb6c63b7ca
checksum: 2232dee3caa17e1735de8a3a63290ad6e86b929c2bb7848c09b6ddc71ecea633eb0ea51cd6172d8402d5ecbef931eee53ef8cc6a32e0873cba5ad943a034c845
languageName: node
linkType: hard