refactor: Invite modal component to support single & multi-select dropdown options (#17073)
* refactored code for invite modal * updated invite users modal component * minor change * optimized addition of links in invite modal dropdown * fixed a cypress test * removed msw * fixed a cypress test * fixed a cypress test * fixed a cypress test * fixed a test
This commit is contained in:
parent
e67ab8eea5
commit
a1b24ab7dc
|
|
@ -48,9 +48,8 @@ describe("Create new workspace and invite user & validate all roles", () => {
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.xpath(HomePage.selectRole).click();
|
cy.xpath(HomePage.selectRole).click();
|
||||||
cy.get(".t--dropdown-option")
|
cy.get(".t--dropdown-option")
|
||||||
.should("have.length", 2)
|
.should("have.length", 1)
|
||||||
.and("contain.text", `App Viewer - ${workspaceId}`);
|
.and("contain.text", `App Viewer - ${workspaceId}`);
|
||||||
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
|
|
||||||
cy.get(HomePage.closeBtn).click();
|
cy.get(HomePage.closeBtn).click();
|
||||||
|
|
||||||
homePage.LaunchAppFromAppHover();
|
homePage.LaunchAppFromAppHover();
|
||||||
|
|
@ -88,13 +87,12 @@ describe("Create new workspace and invite user & validate all roles", () => {
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.xpath(HomePage.selectRole).click();
|
cy.xpath(HomePage.selectRole).click();
|
||||||
cy.get(".t--dropdown-option")
|
cy.get(".t--dropdown-option")
|
||||||
.should("have.length", 3)
|
.should("have.length", 2)
|
||||||
.and(
|
.and(
|
||||||
"contain.text",
|
"contain.text",
|
||||||
`App Viewer - ${workspaceId}`,
|
`App Viewer - ${workspaceId}`,
|
||||||
`Developer - ${workspaceId}`,
|
`Developer - ${workspaceId}`,
|
||||||
);
|
);
|
||||||
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
|
|
||||||
cy.get(HomePage.closeBtn).click();
|
cy.get(HomePage.closeBtn).click();
|
||||||
homePage.LogOutviaAPI();
|
homePage.LogOutviaAPI();
|
||||||
});
|
});
|
||||||
|
|
@ -117,19 +115,27 @@ describe("Create new workspace and invite user & validate all roles", () => {
|
||||||
Cypress.env("TESTPASSWORD1"),
|
Cypress.env("TESTPASSWORD1"),
|
||||||
"Administrator",
|
"Administrator",
|
||||||
);
|
);
|
||||||
homePage.FilterApplication(appid, workspaceId);
|
|
||||||
cy.get(homePage._applicationCard)
|
|
||||||
.first()
|
|
||||||
.trigger("mouseover");
|
|
||||||
homePage.InviteUserToWorkspace(
|
homePage.InviteUserToWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
Cypress.env("TESTUSERNAME2"),
|
Cypress.env("TESTUSERNAME2"),
|
||||||
"App Viewer",
|
"App Viewer",
|
||||||
);
|
);
|
||||||
|
cy.get(HomePage.closeBtn).click();
|
||||||
|
cy.wait(2000);
|
||||||
|
homePage.FilterApplication(appid, workspaceId);
|
||||||
|
cy.get(homePage._applicationCard)
|
||||||
|
.first()
|
||||||
|
.trigger("mouseover");
|
||||||
|
cy.get(homePage._appHoverIcon("edit"))
|
||||||
|
.first()
|
||||||
|
.click({ force: true });
|
||||||
|
// cy.xpath(homePage._editPageLanding).should("exist");
|
||||||
|
cy.wait(4000);
|
||||||
|
cy.xpath("//span[text()='SHARE']").click();
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.xpath(HomePage.selectRole).click();
|
cy.xpath(HomePage.selectRole).click();
|
||||||
cy.get(".t--dropdown-option")
|
cy.get(".t--dropdown-option")
|
||||||
.should("have.length", 4)
|
.should("have.length", 3)
|
||||||
.should(
|
.should(
|
||||||
"contain.text",
|
"contain.text",
|
||||||
`App Viewer - ${workspaceId}`,
|
`App Viewer - ${workspaceId}`,
|
||||||
|
|
@ -139,7 +145,6 @@ describe("Create new workspace and invite user & validate all roles", () => {
|
||||||
"contain.text",
|
"contain.text",
|
||||||
`Administrator - ${workspaceId}`,
|
`Administrator - ${workspaceId}`,
|
||||||
);
|
);
|
||||||
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
|
|
||||||
cy.get(HomePage.closeBtn).click();
|
cy.get(HomePage.closeBtn).click();
|
||||||
homePage.LogOutviaAPI();
|
homePage.LogOutviaAPI();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,6 @@
|
||||||
"test:unit": "$(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
|
"test:unit": "$(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
|
||||||
"test:jest": "$(npm bin)/jest --watch",
|
"test:jest": "$(npm bin)/jest --watch",
|
||||||
"generate:widget": "plop --plopfile generators/index.js",
|
"generate:widget": "plop --plopfile generators/index.js",
|
||||||
"postbuild": "rm build/mockServiceWorker.js",
|
|
||||||
"postinstall": "patch-package && CURRENT_SCOPE=client node ../shared/install-dependencies.js",
|
"postinstall": "patch-package && CURRENT_SCOPE=client node ../shared/install-dependencies.js",
|
||||||
"preinstall": "CURRENT_SCOPE=client node ../shared/build-shared-dep.js",
|
"preinstall": "CURRENT_SCOPE=client node ../shared/build-shared-dep.js",
|
||||||
"install": "node cypress/apply-patches.js"
|
"install": "node cypress/apply-patches.js"
|
||||||
|
|
@ -307,8 +306,5 @@
|
||||||
"json-schema": "0.4.0",
|
"json-schema": "0.4.0",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"babel-plugin-styled-components": "2.0.7"
|
"babel-plugin-styled-components": "2.0.7"
|
||||||
},
|
|
||||||
"msw": {
|
|
||||||
"workerDirectory": "public"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,322 +0,0 @@
|
||||||
/**
|
|
||||||
* Mock Service Worker.
|
|
||||||
* @see https://github.com/mswjs/msw
|
|
||||||
* - Please do NOT modify this file.
|
|
||||||
* - Please do NOT serve this file on production.
|
|
||||||
*/
|
|
||||||
/* eslint-disable */
|
|
||||||
/* tslint:disable */
|
|
||||||
|
|
||||||
const INTEGRITY_CHECKSUM = '795882c72c7304f6fa1d4a65a2418900'
|
|
||||||
const bypassHeaderName = 'x-msw-bypass'
|
|
||||||
const activeClientIds = new Set()
|
|
||||||
|
|
||||||
self.addEventListener('install', function () {
|
|
||||||
return self.skipWaiting()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener('activate', async function (event) {
|
|
||||||
return self.clients.claim()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.addEventListener('message', async function (event) {
|
|
||||||
const clientId = event.source.id
|
|
||||||
|
|
||||||
if (!clientId || !self.clients) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await self.clients.get(clientId)
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const allClients = await self.clients.matchAll()
|
|
||||||
|
|
||||||
switch (event.data) {
|
|
||||||
case 'KEEPALIVE_REQUEST': {
|
|
||||||
sendToClient(client, {
|
|
||||||
type: 'KEEPALIVE_RESPONSE',
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'INTEGRITY_CHECK_REQUEST': {
|
|
||||||
sendToClient(client, {
|
|
||||||
type: 'INTEGRITY_CHECK_RESPONSE',
|
|
||||||
payload: INTEGRITY_CHECKSUM,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'MOCK_ACTIVATE': {
|
|
||||||
activeClientIds.add(clientId)
|
|
||||||
|
|
||||||
sendToClient(client, {
|
|
||||||
type: 'MOCKING_ENABLED',
|
|
||||||
payload: true,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'MOCK_DEACTIVATE': {
|
|
||||||
activeClientIds.delete(clientId)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'CLIENT_CLOSED': {
|
|
||||||
activeClientIds.delete(clientId)
|
|
||||||
|
|
||||||
const remainingClients = allClients.filter((client) => {
|
|
||||||
return client.id !== clientId
|
|
||||||
})
|
|
||||||
|
|
||||||
// Unregister itself when there are no more clients
|
|
||||||
if (remainingClients.length === 0) {
|
|
||||||
self.registration.unregister()
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Resolve the "master" client for the given event.
|
|
||||||
// Client that issues a request doesn't necessarily equal the client
|
|
||||||
// that registered the worker. It's with the latter the worker should
|
|
||||||
// communicate with during the response resolving phase.
|
|
||||||
async function resolveMasterClient(event) {
|
|
||||||
const client = await self.clients.get(event.clientId)
|
|
||||||
|
|
||||||
if (client.frameType === 'top-level') {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
const allClients = await self.clients.matchAll()
|
|
||||||
|
|
||||||
return allClients
|
|
||||||
.filter((client) => {
|
|
||||||
// Get only those clients that are currently visible.
|
|
||||||
return client.visibilityState === 'visible'
|
|
||||||
})
|
|
||||||
.find((client) => {
|
|
||||||
// Find the client ID that's recorded in the
|
|
||||||
// set of clients that have registered the worker.
|
|
||||||
return activeClientIds.has(client.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRequest(event, requestId) {
|
|
||||||
const client = await resolveMasterClient(event)
|
|
||||||
const response = await getResponse(event, client, requestId)
|
|
||||||
|
|
||||||
// Send back the response clone for the "response:*" life-cycle events.
|
|
||||||
// Ensure MSW is active and ready to handle the message, otherwise
|
|
||||||
// this message will pend indefinitely.
|
|
||||||
if (activeClientIds.has(client.id)) {
|
|
||||||
;(async function () {
|
|
||||||
const clonedResponse = response.clone()
|
|
||||||
sendToClient(client, {
|
|
||||||
type: 'RESPONSE',
|
|
||||||
payload: {
|
|
||||||
requestId,
|
|
||||||
type: clonedResponse.type,
|
|
||||||
ok: clonedResponse.ok,
|
|
||||||
status: clonedResponse.status,
|
|
||||||
statusText: clonedResponse.statusText,
|
|
||||||
body:
|
|
||||||
clonedResponse.body === null ? null : await clonedResponse.text(),
|
|
||||||
headers: serializeHeaders(clonedResponse.headers),
|
|
||||||
redirected: clonedResponse.redirected,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getResponse(event, client, requestId) {
|
|
||||||
const { request } = event
|
|
||||||
const requestClone = request.clone()
|
|
||||||
const getOriginalResponse = () => fetch(requestClone)
|
|
||||||
|
|
||||||
// Bypass mocking when the request client is not active.
|
|
||||||
if (!client) {
|
|
||||||
return getOriginalResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bypass initial page load requests (i.e. static assets).
|
|
||||||
// The absence of the immediate/parent client in the map of the active clients
|
|
||||||
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
|
||||||
// and is not ready to handle requests.
|
|
||||||
if (!activeClientIds.has(client.id)) {
|
|
||||||
return await getOriginalResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bypass requests with the explicit bypass header
|
|
||||||
if (requestClone.headers.get(bypassHeaderName) === 'true') {
|
|
||||||
const cleanRequestHeaders = serializeHeaders(requestClone.headers)
|
|
||||||
|
|
||||||
// Remove the bypass header to comply with the CORS preflight check.
|
|
||||||
delete cleanRequestHeaders[bypassHeaderName]
|
|
||||||
|
|
||||||
const originalRequest = new Request(requestClone, {
|
|
||||||
headers: new Headers(cleanRequestHeaders),
|
|
||||||
})
|
|
||||||
|
|
||||||
return fetch(originalRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the request to the client-side MSW.
|
|
||||||
const reqHeaders = serializeHeaders(request.headers)
|
|
||||||
const body = await request.text()
|
|
||||||
|
|
||||||
const clientMessage = await sendToClient(client, {
|
|
||||||
type: 'REQUEST',
|
|
||||||
payload: {
|
|
||||||
id: requestId,
|
|
||||||
url: request.url,
|
|
||||||
method: request.method,
|
|
||||||
headers: reqHeaders,
|
|
||||||
cache: request.cache,
|
|
||||||
mode: request.mode,
|
|
||||||
credentials: request.credentials,
|
|
||||||
destination: request.destination,
|
|
||||||
integrity: request.integrity,
|
|
||||||
redirect: request.redirect,
|
|
||||||
referrer: request.referrer,
|
|
||||||
referrerPolicy: request.referrerPolicy,
|
|
||||||
body,
|
|
||||||
bodyUsed: request.bodyUsed,
|
|
||||||
keepalive: request.keepalive,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
switch (clientMessage.type) {
|
|
||||||
case 'MOCK_SUCCESS': {
|
|
||||||
return delayPromise(
|
|
||||||
() => respondWithMock(clientMessage),
|
|
||||||
clientMessage.payload.delay,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'MOCK_NOT_FOUND': {
|
|
||||||
return getOriginalResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'NETWORK_ERROR': {
|
|
||||||
const { name, message } = clientMessage.payload
|
|
||||||
const networkError = new Error(message)
|
|
||||||
networkError.name = name
|
|
||||||
|
|
||||||
// Rejecting a request Promise emulates a network error.
|
|
||||||
throw networkError
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'INTERNAL_ERROR': {
|
|
||||||
const parsedBody = JSON.parse(clientMessage.payload.body)
|
|
||||||
|
|
||||||
console.error(
|
|
||||||
`\
|
|
||||||
[MSW] Request handler function for "%s %s" has thrown the following exception:
|
|
||||||
|
|
||||||
${parsedBody.errorType}: ${parsedBody.message}
|
|
||||||
(see more detailed error stack trace in the mocked response body)
|
|
||||||
|
|
||||||
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
|
|
||||||
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
|
|
||||||
`,
|
|
||||||
request.method,
|
|
||||||
request.url,
|
|
||||||
)
|
|
||||||
|
|
||||||
return respondWithMock(clientMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getOriginalResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addEventListener('fetch', function (event) {
|
|
||||||
const { request } = event
|
|
||||||
|
|
||||||
// Bypass navigation requests.
|
|
||||||
if (request.mode === 'navigate') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opening the DevTools triggers the "only-if-cached" request
|
|
||||||
// that cannot be handled by the worker. Bypass such requests.
|
|
||||||
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bypass all requests when there are no active clients.
|
|
||||||
// Prevents the self-unregistered worked from handling requests
|
|
||||||
// after it's been deleted (still remains active until the next reload).
|
|
||||||
if (activeClientIds.size === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestId = uuidv4()
|
|
||||||
|
|
||||||
return event.respondWith(
|
|
||||||
handleRequest(event, requestId).catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'[MSW] Failed to mock a "%s" request to "%s": %s',
|
|
||||||
request.method,
|
|
||||||
request.url,
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function serializeHeaders(headers) {
|
|
||||||
const reqHeaders = {}
|
|
||||||
headers.forEach((value, name) => {
|
|
||||||
reqHeaders[name] = reqHeaders[name]
|
|
||||||
? [].concat(reqHeaders[name]).concat(value)
|
|
||||||
: value
|
|
||||||
})
|
|
||||||
return reqHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendToClient(client, message) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const channel = new MessageChannel()
|
|
||||||
|
|
||||||
channel.port1.onmessage = (event) => {
|
|
||||||
if (event.data && event.data.error) {
|
|
||||||
return reject(event.data.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(event.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.postMessage(JSON.stringify(message), [channel.port2])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayPromise(cb, duration) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => resolve(cb()), duration)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function respondWithMock(clientMessage) {
|
|
||||||
return new Response(clientMessage.payload.body, {
|
|
||||||
...clientMessage.payload,
|
|
||||||
headers: clientMessage.payload.headers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function uuidv4() {
|
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
||||||
const r = (Math.random() * 16) | 0
|
|
||||||
const v = c == 'x' ? r : (r & 0x3) | 0x8
|
|
||||||
return v.toString(16)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -39,5 +39,3 @@ export const WELCOME_NON_SUPER_FORM_NAME = "WelcomeNonSuperSetupForm";
|
||||||
export const SAVE_THEME_FORM_NAME = "SaveThemeForm";
|
export const SAVE_THEME_FORM_NAME = "SaveThemeForm";
|
||||||
export const REDIRECT_URL_FORM = "RedirectURLForm";
|
export const REDIRECT_URL_FORM = "RedirectURLForm";
|
||||||
export const ENTITYID_URL_FORM = "EntityIdURLForm";
|
export const ENTITYID_URL_FORM = "EntityIdURLForm";
|
||||||
|
|
||||||
export const inviteModalLinks: any[] = [];
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
import { rest } from "msw";
|
/* import { rest } from "msw"; */
|
||||||
import testMockApi from "./mockJsons/testMockApi.json";
|
|
||||||
|
|
||||||
export const handlers = [
|
export const handlers = [];
|
||||||
rest.get("/api/testMockApi", (req, res, ctx) => {
|
|
||||||
return res(ctx.status(200), ctx.json(testMockApi));
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"environment": "CE"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -49,11 +49,7 @@ import {
|
||||||
Size,
|
Size,
|
||||||
Text,
|
Text,
|
||||||
TextType,
|
TextType,
|
||||||
Icon,
|
|
||||||
IconSize,
|
|
||||||
SegmentHeader,
|
|
||||||
TextProps,
|
TextProps,
|
||||||
TooltipComponent,
|
|
||||||
DropdownOption,
|
DropdownOption,
|
||||||
} from "design-system";
|
} from "design-system";
|
||||||
import { Classes, Variant } from "components/ads/common";
|
import { Classes, Variant } from "components/ads/common";
|
||||||
|
|
@ -65,8 +61,7 @@ import { ScrollIndicator } from "design-system";
|
||||||
import UserApi from "@appsmith/api/UserApi";
|
import UserApi from "@appsmith/api/UserApi";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { fetchWorkspace } from "actions/workspaceActions";
|
import { fetchWorkspace } from "actions/workspaceActions";
|
||||||
import { SubTextPosition } from "components/constants";
|
import { useHistory } from "react-router-dom";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Tooltip } from "@blueprintjs/core";
|
import { Tooltip } from "@blueprintjs/core";
|
||||||
import { isEllipsisActive } from "utils/helpers";
|
import { isEllipsisActive } from "utils/helpers";
|
||||||
|
|
||||||
|
|
@ -228,16 +223,6 @@ export const LabelText = styled(Text)`
|
||||||
letter-spacing: -0.24px;
|
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`
|
export const LeftIconWrapper = styled.span`
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
|
|
@ -247,145 +232,12 @@ export const LeftIconWrapper = styled.span`
|
||||||
top: 1px;
|
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,
|
|
||||||
&.highlighted {
|
|
||||||
background-color: ${(props) =>
|
|
||||||
props.selectedHighlightBg || `var(--appsmith-color-black-200)`};
|
|
||||||
&&& 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)`
|
export const StyledText = styled(Text)`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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(
|
export function TooltipWrappedText(
|
||||||
props: TextProps & {
|
props: TextProps & {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -428,19 +280,7 @@ const validateFormValues = (values: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (typeof values.role === "undefined" || values.role.length === 0) {
|
||||||
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({
|
throw new SubmissionError({
|
||||||
_error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY),
|
_error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY),
|
||||||
});
|
});
|
||||||
|
|
@ -453,20 +293,10 @@ const validate = (values: any) => {
|
||||||
errors["users"] = createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY);
|
errors["users"] = createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (typeof values.role === "undefined" || values.role.length === 0) {
|
||||||
typeof values.roles === "undefined" &&
|
|
||||||
(typeof values.role === "undefined" || values.role?.trim().length === 0)
|
|
||||||
) {
|
|
||||||
errors["role"] = createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY);
|
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) {
|
if (values.users && values.users.length > 0) {
|
||||||
const _users = values.users.split(",").filter(Boolean);
|
const _users = values.users.split(",").filter(Boolean);
|
||||||
|
|
||||||
|
|
@ -485,13 +315,10 @@ export const InviteButtonWidth = "88px";
|
||||||
|
|
||||||
function WorkspaceInviteUsersForm(props: any) {
|
function WorkspaceInviteUsersForm(props: any) {
|
||||||
const [emailError, setEmailError] = useState("");
|
const [emailError, setEmailError] = useState("");
|
||||||
const [selectedOption, setSelectedOption] = useState<any>({});
|
const [selectedOption, setSelectedOption] = useState<any[]>([]);
|
||||||
const userRef = React.createRef<HTMLDivElement>();
|
const userRef = React.createRef<HTMLDivElement>();
|
||||||
|
const history = useHistory();
|
||||||
const selectedId = props?.selected?.id;
|
const selectedId = props?.selected?.id;
|
||||||
const multiSelectDropdownOptions: Partial<DropdownOption>[] =
|
|
||||||
props.options && props.options.length > 0 && props.isMultiSelectDropdown
|
|
||||||
? props.options
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const selected = useMemo(
|
const selected = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -515,11 +342,9 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
fetchCurrentWorkspace,
|
fetchCurrentWorkspace,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
isAclFlow = false,
|
|
||||||
isApplicationInvite,
|
isApplicationInvite,
|
||||||
isLoading,
|
isLoading,
|
||||||
isMultiSelectDropdown = false,
|
isMultiSelectDropdown = false,
|
||||||
links = [],
|
|
||||||
message = "",
|
message = "",
|
||||||
placeholder = "",
|
placeholder = "",
|
||||||
submitFailed,
|
submitFailed,
|
||||||
|
|
@ -528,8 +353,6 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
valid,
|
valid,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [selectedItems, setSelectedItems] = useState<any[]>([]);
|
|
||||||
|
|
||||||
// set state for checking number of users invited
|
// set state for checking number of users invited
|
||||||
const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
|
const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
|
||||||
const currentWorkspace = useSelector(getCurrentAppWorkspace);
|
const currentWorkspace = useSelector(getCurrentAppWorkspace);
|
||||||
|
|
@ -541,31 +364,30 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAclFlow) {
|
fetchUser(props.workspaceId);
|
||||||
fetchUser(props.workspaceId);
|
fetchAllRoles(props.workspaceId);
|
||||||
fetchAllRoles(props.workspaceId);
|
fetchCurrentWorkspace(props.workspaceId);
|
||||||
fetchCurrentWorkspace(props.workspaceId);
|
|
||||||
}
|
|
||||||
}, [props.workspaceId, fetchUser, fetchAllRoles, fetchCurrentWorkspace]);
|
}, [props.workspaceId, fetchUser, fetchAllRoles, fetchCurrentWorkspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
setSelectedItems([selected]);
|
setSelectedOption([selected]);
|
||||||
props.initialize({
|
props.initialize({
|
||||||
roles: [selected],
|
role: [selected],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const styledRoles = props.roles.map((role: any) => {
|
const styledRoles =
|
||||||
return {
|
props.options && props.options.length > 0
|
||||||
id: role.id,
|
? props.options
|
||||||
value: role.name,
|
: props.roles.map((role: any) => {
|
||||||
label: role.description,
|
return {
|
||||||
};
|
id: role.id,
|
||||||
});
|
value: role.name,
|
||||||
|
label: role.description,
|
||||||
styledRoles.push(...links);
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useContext(ThemeContext);
|
||||||
|
|
||||||
|
|
@ -592,12 +414,15 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
[allUsers, theme],
|
[allUsers, theme],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSelect = (_value?: string, options?: any) => {
|
const onSelect = (_value?: string, option?: any) => {
|
||||||
setSelectedItems(options);
|
if (option.link) {
|
||||||
|
history.push(option.link);
|
||||||
|
}
|
||||||
|
setSelectedOption(isMultiSelectDropdown ? option : [option]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveOptions = (updatedItems: any) => {
|
const onRemoveOptions = (updatedItems: any) => {
|
||||||
setSelectedItems(updatedItems);
|
setSelectedOption(updatedItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLabel = (selectedOption: Partial<DropdownOption>[]) => {
|
const getLabel = (selectedOption: Partial<DropdownOption>[]) => {
|
||||||
|
|
@ -626,155 +451,6 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderOption = ({
|
|
||||||
index,
|
|
||||||
isHighlighted,
|
|
||||||
option,
|
|
||||||
optionClickHandler,
|
|
||||||
}: {
|
|
||||||
index?: number;
|
|
||||||
option: DropdownOption | DropdownOption[];
|
|
||||||
optionClickHandler?: (dropdownOption: DropdownOption) => void;
|
|
||||||
isHighlighted?: boolean;
|
|
||||||
}) => {
|
|
||||||
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 ? (
|
|
||||||
<TooltipComponent
|
|
||||||
content={
|
|
||||||
!!option.disabledTooltipText
|
|
||||||
? option.disabledTooltipText
|
|
||||||
: "Action not supported"
|
|
||||||
}
|
|
||||||
disabled={!option.disabled}
|
|
||||||
key={`tootltip-${index}`}
|
|
||||||
styles={{
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<OptionWrapper
|
|
||||||
aria-selected={isSelected}
|
|
||||||
className={`t--dropdown-option ${isSelected ? "selected" : ""} ${
|
|
||||||
isHighlighted ? "highlighted" : ""
|
|
||||||
}`}
|
|
||||||
data-cy={`t--dropdown-option-${option?.label}`}
|
|
||||||
disabled={option.disabled}
|
|
||||||
key={index}
|
|
||||||
onClick={
|
|
||||||
// users should be able to unselect a selected option by clicking the option again.
|
|
||||||
isSelected && props.allowDeselection
|
|
||||||
? () => props.removeSelectedOptionClickHandler(option)
|
|
||||||
: () => optionClickHandler?.(option)
|
|
||||||
}
|
|
||||||
role="option"
|
|
||||||
selected={
|
|
||||||
props.isMultiSelect ? props.highlightIndex === index : isSelected
|
|
||||||
}
|
|
||||||
selectedHighlightBg={props.selectedHighlightBg}
|
|
||||||
subTextPosition={option.subTextPosition ?? SubTextPosition.LEFT}
|
|
||||||
>
|
|
||||||
{option.leftElement && (
|
|
||||||
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
|
|
||||||
)}
|
|
||||||
{option.icon ? (
|
|
||||||
<SelectedIcon
|
|
||||||
fillColor={option?.iconColor}
|
|
||||||
hoverFillColor={option?.iconColor}
|
|
||||||
name={option.icon}
|
|
||||||
size={option.iconSize || IconSize.XL}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{props.showLabelOnly ? (
|
|
||||||
props.truncateOption ? (
|
|
||||||
<>
|
|
||||||
<TooltipWrappedText
|
|
||||||
label={option.label || ""}
|
|
||||||
type={TextType.P1}
|
|
||||||
/>
|
|
||||||
{option.hasCustomBadge && props.customBadge}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text type={TextType.P1}>{option.label}</Text>
|
|
||||||
{option.hasCustomBadge && props.customBadge}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
) : option.label && option.value ? (
|
|
||||||
<LabelWrapper className="label-container">
|
|
||||||
<Text type={TextType.H5}>{option.value}</Text>
|
|
||||||
<Text type={TextType.P1}>{option.label}</Text>
|
|
||||||
</LabelWrapper>
|
|
||||||
) : props.truncateOption ? (
|
|
||||||
<TooltipWrappedText
|
|
||||||
label={option.value || ""}
|
|
||||||
type={TextType.P1}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text type={TextType.P1}>{option.value}</Text>
|
|
||||||
)}
|
|
||||||
{option.subText ? (
|
|
||||||
<StyledSubText
|
|
||||||
subTextPosition={option.subTextPosition}
|
|
||||||
type={TextType.P3}
|
|
||||||
>
|
|
||||||
{option.subText}
|
|
||||||
</StyledSubText>
|
|
||||||
) : null}
|
|
||||||
</OptionWrapper>
|
|
||||||
</TooltipComponent>
|
|
||||||
) : (
|
|
||||||
<Link key={index} to={option.link || "/"}>
|
|
||||||
<OptionWrapper
|
|
||||||
className={`t--dropdown-link`}
|
|
||||||
data-cy={`t--dropdown-option-${option?.value}`}
|
|
||||||
disabled={option.disabled}
|
|
||||||
role="option"
|
|
||||||
selected={false}
|
|
||||||
selectedHighlightBg={props.selectedHighlightBg}
|
|
||||||
subTextPosition={option.subTextPosition ?? SubTextPosition.LEFT}
|
|
||||||
>
|
|
||||||
{option.leftElement && (
|
|
||||||
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
|
|
||||||
)}
|
|
||||||
{option.icon ? (
|
|
||||||
<SelectedIcon
|
|
||||||
fillColor={option?.iconColor}
|
|
||||||
hoverFillColor={option?.iconColor}
|
|
||||||
name={option.icon}
|
|
||||||
size={option.iconSize || IconSize.XL}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Text type={TextType.P1}>{option.value}</Text>
|
|
||||||
{option.subText ? (
|
|
||||||
<StyledSubText
|
|
||||||
subTextPosition={option.subTextPosition}
|
|
||||||
type={TextType.P3}
|
|
||||||
>
|
|
||||||
{option.subText}
|
|
||||||
</StyledSubText>
|
|
||||||
) : null}
|
|
||||||
</OptionWrapper>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<SegmentHeader
|
|
||||||
key={index}
|
|
||||||
style={{ paddingRight: theme.spaces[5] }}
|
|
||||||
title={!Array.isArray(option) && option.label ? option.label : ""}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceInviteWrapper>
|
<WorkspaceInviteWrapper>
|
||||||
<InviteModalStyles />
|
<InviteModalStyles />
|
||||||
|
|
@ -797,10 +473,11 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
.join(",");
|
.join(",");
|
||||||
return inviteUsersToWorkspace(
|
return inviteUsersToWorkspace(
|
||||||
{
|
{
|
||||||
...values,
|
...(props.workspaceId ? { workspaceId: props.workspaceId } : {}),
|
||||||
users,
|
users,
|
||||||
permissionGroupId: selectedOption.id,
|
permissionGroupId: isMultiSelectDropdown
|
||||||
workspaceId: props.workspaceId,
|
? selectedOption.map((group: any) => group.id).join(",")
|
||||||
|
: selectedOption[0].id,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
|
@ -821,36 +498,24 @@ function WorkspaceInviteUsersForm(props: any) {
|
||||||
placeholder={placeholder || "Enter email address"}
|
placeholder={placeholder || "Enter email address"}
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
{isMultiSelectDropdown ? (
|
<SelectField
|
||||||
<SelectField
|
allowDeselection={isMultiSelectDropdown}
|
||||||
allowDeselection
|
data-cy="t--invite-role-input"
|
||||||
disabled={!!selected}
|
disabled={props.disableDropdown}
|
||||||
isMultiSelect
|
isMultiSelect={isMultiSelectDropdown}
|
||||||
labelRenderer={(selected: Partial<DropdownOption>[]) =>
|
labelRenderer={(selected: Partial<DropdownOption>[]) =>
|
||||||
getLabel(selected)
|
getLabel(selected)
|
||||||
}
|
}
|
||||||
name="roles"
|
name={"role"}
|
||||||
onSelect={onSelect}
|
onSelect={(value, option) => onSelect(value, option)}
|
||||||
options={multiSelectDropdownOptions}
|
options={styledRoles}
|
||||||
outline={false}
|
outline={false}
|
||||||
placeholder="Select a role"
|
placeholder="Select a role"
|
||||||
removeSelectedOption={onRemoveOptions}
|
removeSelectedOption={onRemoveOptions}
|
||||||
selected={selectedItems}
|
selected={selectedOption}
|
||||||
showLabelOnly
|
showLabelOnly={isMultiSelectDropdown}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<SelectField
|
|
||||||
data-cy="t--invite-role-input"
|
|
||||||
name="role"
|
|
||||||
onSelect={(value, option) => setSelectedOption(option)}
|
|
||||||
options={styledRoles}
|
|
||||||
outline={false}
|
|
||||||
placeholder="Select a role"
|
|
||||||
renderOption={renderOption}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="t--invite-user-btn"
|
className="t--invite-user-btn"
|
||||||
|
|
@ -983,7 +648,6 @@ export default connect(
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
isApplicationInvite?: boolean;
|
isApplicationInvite?: boolean;
|
||||||
links?: any[];
|
|
||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
validate,
|
validate,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ type FormDialogComponentProps = {
|
||||||
selected?: any;
|
selected?: any;
|
||||||
tabs?: any[];
|
tabs?: any[];
|
||||||
options?: any[];
|
options?: any[];
|
||||||
links?: any[];
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -137,7 +136,6 @@ export function FormDialogComponent(props: FormDialogComponentProps) {
|
||||||
<Form
|
<Form
|
||||||
{...props.customProps}
|
{...props.customProps}
|
||||||
applicationId={props.applicationId}
|
applicationId={props.applicationId}
|
||||||
links={props.links}
|
|
||||||
message={props.message}
|
message={props.message}
|
||||||
onCancel={() => setIsOpen(false)}
|
onCancel={() => setIsOpen(false)}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,15 @@ type DropdownWrapperProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function DropdownWrapper(props: DropdownWrapperProps) {
|
function DropdownWrapper(props: DropdownWrapperProps) {
|
||||||
const [selectedOption, setSelectedOption] = useState({
|
const [selectedOption, setSelectedOption] = useState<any>([
|
||||||
value: props.placeholder,
|
{
|
||||||
});
|
value: props.placeholder,
|
||||||
const [selected, setSelected] = useState<any>([]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const onSelectHandler = (value?: string, option?: DropdownOption) => {
|
const onSelectHandler = (value?: string, option?: DropdownOption) => {
|
||||||
if (props?.isMultiSelect) {
|
if (props?.isMultiSelect) {
|
||||||
const updatedItems: DropdownOption[] = [...selected, option];
|
const updatedItems: DropdownOption[] = [...selectedOption, option];
|
||||||
props.input && props.input.onChange && props.input.onChange(updatedItems);
|
props.input && props.input.onChange && props.input.onChange(updatedItems);
|
||||||
props.onOptionSelect && props.onOptionSelect(value, updatedItems);
|
props.onOptionSelect && props.onOptionSelect(value, updatedItems);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -42,7 +43,7 @@ function DropdownWrapper(props: DropdownWrapperProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveOptions = (value: any) => {
|
const onRemoveOptions = (value: any) => {
|
||||||
const updatedItems = selected.filter(
|
const updatedItems = selectedOption.filter(
|
||||||
(option: any) => option.value !== value,
|
(option: any) => option.value !== value,
|
||||||
);
|
);
|
||||||
props.input && props.input.onChange && props.input.onChange(updatedItems);
|
props.input && props.input.onChange && props.input.onChange(updatedItems);
|
||||||
|
|
@ -50,13 +51,13 @@ function DropdownWrapper(props: DropdownWrapperProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props?.isMultiSelect) {
|
if (props.selected) {
|
||||||
setSelected(props.selected);
|
setSelectedOption(props.selected);
|
||||||
} else {
|
} else {
|
||||||
if (props.input && props.input.value) {
|
if (props.input && props.input.value) {
|
||||||
setSelectedOption({ value: props.input.value });
|
setSelectedOption([{ value: props.input.value }]);
|
||||||
} else if (props.placeholder) {
|
} else if (props.placeholder) {
|
||||||
setSelectedOption({ value: props.placeholder });
|
setSelectedOption([{ value: props.placeholder }]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [props.input, props.placeholder, props.selected]);
|
}, [props.input, props.placeholder, props.selected]);
|
||||||
|
|
@ -73,11 +74,7 @@ function DropdownWrapper(props: DropdownWrapperProps) {
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
removeSelectedOption={onRemoveOptions}
|
removeSelectedOption={onRemoveOptions}
|
||||||
renderOption={props?.renderOption}
|
renderOption={props?.renderOption}
|
||||||
selected={
|
selected={props.isMultiSelect ? selectedOption : selectedOption[0]}
|
||||||
props.isMultiSelect
|
|
||||||
? (props.selected as DropdownOption[])
|
|
||||||
: selectedOption
|
|
||||||
}
|
|
||||||
showLabelOnly={props.showLabelOnly}
|
showLabelOnly={props.showLabelOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,6 @@ import AppErrorBoundary from "./AppErrorBoundry";
|
||||||
import GlobalStyles from "globalStyles";
|
import GlobalStyles from "globalStyles";
|
||||||
appInitializer();
|
appInitializer();
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
import("./mocks/browser").then(({ worker }) => {
|
|
||||||
worker.start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Sentry.ErrorBoundary fallback={"An error has occured"}>
|
<Sentry.ErrorBoundary fallback={"An error has occured"}>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import BackToHomeButton from "./BackToHomeButton";
|
||||||
import TourCompletionMessage from "pages/Editor/GuidedTour/TourCompletionMessage";
|
import TourCompletionMessage from "pages/Editor/GuidedTour/TourCompletionMessage";
|
||||||
import { useHref } from "pages/Editor/utils";
|
import { useHref } from "pages/Editor/utils";
|
||||||
import { builderURL } from "RouteBuilder";
|
import { builderURL } from "RouteBuilder";
|
||||||
import { inviteModalLinks } from "@appsmith/constants/forms";
|
|
||||||
import {
|
import {
|
||||||
createMessage,
|
createMessage,
|
||||||
INVITE_USERS_MESSAGE,
|
INVITE_USERS_MESSAGE,
|
||||||
|
|
@ -135,7 +134,6 @@ export function AppViewerHeader(props: AppViewerHeaderProps) {
|
||||||
bgColor: "transparent",
|
bgColor: "transparent",
|
||||||
}}
|
}}
|
||||||
isOpen={showAppInviteUsersDialog}
|
isOpen={showAppInviteUsersDialog}
|
||||||
links={inviteModalLinks}
|
|
||||||
message={createMessage(INVITE_USERS_MESSAGE)}
|
message={createMessage(INVITE_USERS_MESSAGE)}
|
||||||
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
||||||
title={currentApplicationDetails.name}
|
title={currentApplicationDetails.name}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { useHref } from "pages/Editor/utils";
|
||||||
import { APP_MODE } from "entities/App";
|
import { APP_MODE } from "entities/App";
|
||||||
import { builderURL, viewerURL } from "RouteBuilder";
|
import { builderURL, viewerURL } from "RouteBuilder";
|
||||||
import { trimQueryString } from "utils/helpers";
|
import { trimQueryString } from "utils/helpers";
|
||||||
import { inviteModalLinks } from "@appsmith/constants/forms";
|
|
||||||
import {
|
import {
|
||||||
createMessage,
|
createMessage,
|
||||||
INVITE_USERS_MESSAGE,
|
INVITE_USERS_MESSAGE,
|
||||||
|
|
@ -118,7 +117,6 @@ export function PageMenu(props: AppViewerHeaderProps) {
|
||||||
bgColor: "transparent",
|
bgColor: "transparent",
|
||||||
}}
|
}}
|
||||||
isOpen={showAppInviteUsersDialog}
|
isOpen={showAppInviteUsersDialog}
|
||||||
links={inviteModalLinks}
|
|
||||||
message={createMessage(INVITE_USERS_MESSAGE)}
|
message={createMessage(INVITE_USERS_MESSAGE)}
|
||||||
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
||||||
title={application.name}
|
title={application.name}
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,7 @@ import FormDialogComponent from "components/editorComponents/form/FormDialogComp
|
||||||
import Dialog from "components/ads/DialogComponent";
|
import Dialog from "components/ads/DialogComponent";
|
||||||
import { User } from "constants/userConstants";
|
import { User } from "constants/userConstants";
|
||||||
import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
|
import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
|
||||||
import {
|
import { CREATE_WORKSPACE_FORM_NAME } from "@appsmith/constants/forms";
|
||||||
CREATE_WORKSPACE_FORM_NAME,
|
|
||||||
inviteModalLinks,
|
|
||||||
} from "@appsmith/constants/forms";
|
|
||||||
import {
|
import {
|
||||||
DropdownOnSelectActions,
|
DropdownOnSelectActions,
|
||||||
getOnSelectAction,
|
getOnSelectAction,
|
||||||
|
|
@ -708,7 +705,6 @@ function ApplicationsSection(props: any) {
|
||||||
title={`Invite Users to ${workspace.name}`}
|
title={`Invite Users to ${workspace.name}`}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
links={inviteModalLinks}
|
|
||||||
message={createMessage(INVITE_USERS_MESSAGE)}
|
message={createMessage(INVITE_USERS_MESSAGE)}
|
||||||
workspaceId={workspace.id}
|
workspaceId={workspace.id}
|
||||||
/>
|
/>
|
||||||
|
|
@ -734,7 +730,6 @@ function ApplicationsSection(props: any) {
|
||||||
<FormDialogComponent
|
<FormDialogComponent
|
||||||
Form={WorkspaceInviteUsersForm}
|
Form={WorkspaceInviteUsersForm}
|
||||||
canOutsideClickClose
|
canOutsideClickClose
|
||||||
links={inviteModalLinks}
|
|
||||||
message={createMessage(INVITE_USERS_MESSAGE)}
|
message={createMessage(INVITE_USERS_MESSAGE)}
|
||||||
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
||||||
title={`Invite Users to ${workspace.name}`}
|
title={`Invite Users to ${workspace.name}`}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@ import EndTour from "./GuidedTour/EndTour";
|
||||||
import { GUIDED_TOUR_STEPS } from "./GuidedTour/constants";
|
import { GUIDED_TOUR_STEPS } from "./GuidedTour/constants";
|
||||||
import { viewerURL } from "RouteBuilder";
|
import { viewerURL } from "RouteBuilder";
|
||||||
import { useHref } from "./utils";
|
import { useHref } from "./utils";
|
||||||
import { inviteModalLinks } from "@appsmith/constants/forms";
|
|
||||||
|
|
||||||
const HeaderWrapper = styled.div`
|
const HeaderWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -462,7 +461,6 @@ export function EditorHeader(props: EditorHeaderProps) {
|
||||||
bgColor: Colors.GEYSER_LIGHT,
|
bgColor: Colors.GEYSER_LIGHT,
|
||||||
}}
|
}}
|
||||||
isOpen={showAppInviteUsersDialog}
|
isOpen={showAppInviteUsersDialog}
|
||||||
links={inviteModalLinks}
|
|
||||||
message={createMessage(INVITE_USERS_MESSAGE)}
|
message={createMessage(INVITE_USERS_MESSAGE)}
|
||||||
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
|
||||||
title={
|
title={
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ function AppInviteUsersForm(props: any) {
|
||||||
fetchCurrentWorkspace,
|
fetchCurrentWorkspace,
|
||||||
isChangingViewAccess,
|
isChangingViewAccess,
|
||||||
isFetchingApplication,
|
isFetchingApplication,
|
||||||
links,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const currentWorkspaceId = useSelector(getCurrentWorkspaceId);
|
const currentWorkspaceId = useSelector(getCurrentWorkspaceId);
|
||||||
|
|
@ -125,7 +124,6 @@ function AppInviteUsersForm(props: any) {
|
||||||
{canInviteToWorkspace && (
|
{canInviteToWorkspace && (
|
||||||
<WorkspaceInviteUsersForm
|
<WorkspaceInviteUsersForm
|
||||||
isApplicationInvite
|
isApplicationInvite
|
||||||
links={links}
|
|
||||||
workspaceId={props.workspaceId}
|
workspaceId={props.workspaceId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import { createSelector } from "reselect";
|
|
||||||
import { AppState } from "@appsmith/reducers";
|
|
||||||
import { WorkspaceRole } from "constants/workspaceConstants";
|
|
||||||
|
|
||||||
export const getRolesFromState = (state: AppState) => {
|
|
||||||
return state.ui.workspaces.roles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWorkspaceLoadingStates = (state: AppState) => {
|
|
||||||
return {
|
|
||||||
isFetchingWorkspace: state.ui.workspaces.loadingStates.isFetchingWorkspace,
|
|
||||||
isFetchingAllUsers: state.ui.workspaces.loadingStates.isFetchAllUsers,
|
|
||||||
isFetchingAllRoles: state.ui.workspaces.loadingStates.isFetchAllRoles,
|
|
||||||
deletingUserInfo: state.ui.workspaces.workspaceUsers.filter(
|
|
||||||
(el) => el.isDeleting,
|
|
||||||
)[0],
|
|
||||||
roleChangingUserInfo: state.ui.workspaces.workspaceUsers.filter(
|
|
||||||
(el) => el.isChangingRole,
|
|
||||||
)[0],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCurrentWorkspaceId = (state: AppState) =>
|
|
||||||
state.ui.workspaces.currentWorkspace.id;
|
|
||||||
export const getWorkspaces = (state: AppState) => {
|
|
||||||
return state.ui.applications.userWorkspaces;
|
|
||||||
};
|
|
||||||
export const getCurrentWorkspace = (state: AppState) => {
|
|
||||||
return state.ui.applications.userWorkspaces.map((el) => el.workspace);
|
|
||||||
};
|
|
||||||
export const getCurrentAppWorkspace = (state: AppState) => {
|
|
||||||
return state.ui.workspaces.currentWorkspace;
|
|
||||||
};
|
|
||||||
export const getAllUsers = (state: AppState) =>
|
|
||||||
state.ui.workspaces.workspaceUsers;
|
|
||||||
export const getAllRoles = (state: AppState) =>
|
|
||||||
state.ui.workspaces.workspaceRoles;
|
|
||||||
|
|
||||||
export const getRoles = createSelector(
|
|
||||||
getRolesFromState,
|
|
||||||
(roles?: WorkspaceRole[]): WorkspaceRole[] | undefined => {
|
|
||||||
return roles?.map((role) => ({
|
|
||||||
id: role.id,
|
|
||||||
name: role.displayName || role.name,
|
|
||||||
isDefault: role.isDefault,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getRolesForField = createSelector(getAllRoles, (roles?: any) => {
|
|
||||||
return Object.entries(roles).map((role) => {
|
|
||||||
return {
|
|
||||||
id: role[0],
|
|
||||||
name: role[0],
|
|
||||||
description: role[1],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getDefaultRole = createSelector(
|
|
||||||
getRoles,
|
|
||||||
(roles?: WorkspaceRole[]) => {
|
|
||||||
return roles?.find((role) => role.isDefault);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
export const getCurrentError = (state: AppState) => {
|
|
||||||
return state.ui.errors.currentError;
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue
Block a user