added invite user dialog
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
e6dd8ac1c0
commit
2b0392a3e8
5 changed files with 87 additions and 25 deletions
|
@ -3222,7 +3222,8 @@ emptyUserOrganizationsInstructions=There is no organization yet. Please join an
|
||||||
joinOrganization=Join organization
|
joinOrganization=Join organization
|
||||||
sendInvitation=Send invitation
|
sendInvitation=Send invitation
|
||||||
removeConfirmOrganizationTitle=Remove organization?
|
removeConfirmOrganizationTitle=Remove organization?
|
||||||
organizationRemoveConfirm=Are you sure you want to remove user from the {{count}} selected organizations?
|
organizationRemoveConfirm_one=Are you sure you want to remove user from the selected organization?
|
||||||
|
organizationRemoveConfirm_other=Are you sure you want to remove user from the {{count}} selected organizations?
|
||||||
organizationRemovedSuccess=User removed from organizations
|
organizationRemovedSuccess=User removed from organizations
|
||||||
organizationRemoveError=Could not remove user from organizations\: {{error}}
|
organizationRemoveError=Could not remove user from organizations\: {{error}}
|
||||||
organizationName=Organization name
|
organizationName=Organization name
|
||||||
|
@ -3231,3 +3232,4 @@ join=Join
|
||||||
userAddedOrganization_one=Organization added to the user
|
userAddedOrganization_one=Organization added to the user
|
||||||
userAddedOrganizationError=Could not add organizations to the user\: {{error}}
|
userAddedOrganizationError=Could not add organizations to the user\: {{error}}
|
||||||
userAddedOrganization_other={{count}} organizations added to the user
|
userAddedOrganization_other={{count}} organizations added to the user
|
||||||
|
sentInvitation=Sent invitation
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation";
|
import OrganizationRepresentation from "@keycloak/keycloak-admin-client/lib/defs/organizationRepresentation";
|
||||||
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import { Button, Modal, ModalVariant } from "@patternfly/react-core";
|
import { Button, Modal, ModalVariant } from "@patternfly/react-core";
|
||||||
|
import { differenceBy } from "lodash-es";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAdminClient } from "../admin-client";
|
import { useAdminClient } from "../admin-client";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { TableText } from "@patternfly/react-table";
|
||||||
|
|
||||||
type OrganizationModalProps = {
|
type OrganizationModalProps = {
|
||||||
|
isJoin?: boolean;
|
||||||
|
existingOrgs: OrganizationRepresentation[];
|
||||||
onAdd: (orgs: OrganizationRepresentation[]) => Promise<void>;
|
onAdd: (orgs: OrganizationRepresentation[]) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrganizationModal = ({
|
export const OrganizationModal = ({
|
||||||
|
isJoin = true,
|
||||||
|
existingOrgs,
|
||||||
onAdd,
|
onAdd,
|
||||||
onClose,
|
onClose,
|
||||||
}: OrganizationModalProps) => {
|
}: OrganizationModalProps) => {
|
||||||
|
@ -21,13 +27,20 @@ export const OrganizationModal = ({
|
||||||
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
||||||
|
|
||||||
const loader = async (first?: number, max?: number, search?: string) => {
|
const loader = async (first?: number, max?: number, search?: string) => {
|
||||||
return await adminClient.organizations.find({ first, max, search });
|
const params = {
|
||||||
|
first,
|
||||||
|
search,
|
||||||
|
max: max! + existingOrgs.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const orgs = await adminClient.organizations.find(params);
|
||||||
|
return differenceBy(orgs, existingOrgs, "id");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
variant={ModalVariant.small}
|
variant={ModalVariant.small}
|
||||||
title={t("joinOrganization")}
|
title={isJoin ? t("joinOrganization") : t("sendInvitation")}
|
||||||
isOpen
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
actions={[
|
actions={[
|
||||||
|
@ -40,7 +53,7 @@ export const OrganizationModal = ({
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("join")}
|
{isJoin ? t("join") : t("send")}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
data-testid="cancel"
|
data-testid="cancel"
|
||||||
|
@ -66,6 +79,9 @@ export const OrganizationModal = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
|
cellRenderer: (row) => (
|
||||||
|
<TableText wrapModifier="truncate">{row.description}</TableText>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -57,7 +57,9 @@ const Domains = (org: OrganizationRepresentation) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
type OrganizationTableProps = PropsWithChildren & {
|
type OrganizationTableProps = PropsWithChildren & {
|
||||||
loader: LoaderFunction<OrganizationRepresentation>;
|
loader:
|
||||||
|
| LoaderFunction<OrganizationRepresentation>
|
||||||
|
| OrganizationRepresentation[];
|
||||||
toolbarItem?: ReactNode;
|
toolbarItem?: ReactNode;
|
||||||
isPaginated?: boolean;
|
isPaginated?: boolean;
|
||||||
onSelect?: (orgs: OrganizationRepresentation[]) => void;
|
onSelect?: (orgs: OrganizationRepresentation[]) => void;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||||
import { OrganizationModal } from "../organizations/OrganizationModal";
|
import { OrganizationModal } from "../organizations/OrganizationModal";
|
||||||
import { OrganizationTable } from "../organizations/OrganizationTable";
|
import { OrganizationTable } from "../organizations/OrganizationTable";
|
||||||
|
import { useFetch } from "../utils/useFetch";
|
||||||
import useToggle from "../utils/useToggle";
|
import useToggle from "../utils/useToggle";
|
||||||
import { UserParams } from "./routes/User";
|
import { UserParams } from "./routes/User";
|
||||||
|
|
||||||
|
@ -30,11 +31,19 @@ export const Organizations = () => {
|
||||||
const refresh = () => setKey(key + 1);
|
const refresh = () => setKey(key + 1);
|
||||||
|
|
||||||
const [joinToggle, toggle, setJoinToggle] = useToggle();
|
const [joinToggle, toggle, setJoinToggle] = useToggle();
|
||||||
const [joinOrganization, setJoinOrganization] = useState(false);
|
const [shouldJoin, setShouldJoin] = useState(true);
|
||||||
|
const [openOrganizationPicker, setOpenOrganizationPicker] = useState(false);
|
||||||
|
const [userOrgs, setUserOrgs] = useState<OrganizationRepresentation[]>([]);
|
||||||
const [selectedOrgs, setSelectedOrgs] = useState<
|
const [selectedOrgs, setSelectedOrgs] = useState<
|
||||||
OrganizationRepresentation[]
|
OrganizationRepresentation[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.organizations.memberOrganizations({ userId: id! }),
|
||||||
|
setUserOrgs,
|
||||||
|
[key],
|
||||||
|
);
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
titleKey: "removeConfirmOrganizationTitle",
|
titleKey: "removeConfirmOrganizationTitle",
|
||||||
messageKey: t("organizationRemoveConfirm", { count: selectedOrgs.length }),
|
messageKey: t("organizationRemoveConfirm", { count: selectedOrgs.length }),
|
||||||
|
@ -51,6 +60,7 @@ export const Organizations = () => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
addAlert(t("organizationRemovedSuccess"));
|
addAlert(t("organizationRemovedSuccess"));
|
||||||
|
setSelectedOrgs([]);
|
||||||
refresh();
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("organizationRemoveError", error);
|
addError("organizationRemoveError", error);
|
||||||
|
@ -60,18 +70,29 @@ export const Organizations = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{joinOrganization && (
|
{openOrganizationPicker && (
|
||||||
<OrganizationModal
|
<OrganizationModal
|
||||||
onClose={() => setJoinOrganization(false)}
|
isJoin={shouldJoin}
|
||||||
|
existingOrgs={userOrgs}
|
||||||
|
onClose={() => setOpenOrganizationPicker(false)}
|
||||||
onAdd={async (orgs) => {
|
onAdd={async (orgs) => {
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
orgs.map((org) =>
|
orgs.map((org) => {
|
||||||
adminClient.organizations.addMember({
|
const form = new FormData();
|
||||||
orgId: org.id!,
|
form.append("id", id!);
|
||||||
userId: id!,
|
return shouldJoin
|
||||||
}),
|
? adminClient.organizations.addMember({
|
||||||
),
|
orgId: org.id!,
|
||||||
|
userId: id!,
|
||||||
|
})
|
||||||
|
: adminClient.organizations.inviteExistingUser(
|
||||||
|
{
|
||||||
|
orgId: org.id!,
|
||||||
|
},
|
||||||
|
form,
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
addAlert(t("userAddedOrganization", { count: orgs.length }));
|
addAlert(t("userAddedOrganization", { count: orgs.length }));
|
||||||
refresh();
|
refresh();
|
||||||
|
@ -83,14 +104,13 @@ export const Organizations = () => {
|
||||||
)}
|
)}
|
||||||
<DeleteConfirm />
|
<DeleteConfirm />
|
||||||
<OrganizationTable
|
<OrganizationTable
|
||||||
key={key}
|
loader={userOrgs}
|
||||||
loader={() =>
|
|
||||||
adminClient.organizations.memberOrganizations({
|
|
||||||
userId: id!,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSelect={(orgs) => setSelectedOrgs(orgs)}
|
onSelect={(orgs) => setSelectedOrgs(orgs)}
|
||||||
deleteLabel="remove"
|
deleteLabel="remove"
|
||||||
|
onDelete={(org) => {
|
||||||
|
setSelectedOrgs([org]);
|
||||||
|
toggleDeleteDialog();
|
||||||
|
}}
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<>
|
<>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
|
@ -112,13 +132,20 @@ export const Organizations = () => {
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="join"
|
key="join"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setJoinOrganization(true);
|
setShouldJoin(true);
|
||||||
|
setOpenOrganizationPicker(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("joinOrganization")}
|
{t("joinOrganization")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem key="invite" component="button">
|
<DropdownItem
|
||||||
{t("invite")}
|
key="invite"
|
||||||
|
onClick={() => {
|
||||||
|
setShouldJoin(false);
|
||||||
|
setOpenOrganizationPicker(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("sentInvite")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownList>
|
</DropdownList>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
@ -142,11 +169,17 @@ export const Organizations = () => {
|
||||||
secondaryActions={[
|
secondaryActions={[
|
||||||
{
|
{
|
||||||
text: t("joinOrganization"),
|
text: t("joinOrganization"),
|
||||||
onClick: () => alert("join organization"),
|
onClick: () => {
|
||||||
|
setShouldJoin(true);
|
||||||
|
setOpenOrganizationPicker(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t("sendInvitation"),
|
text: t("sendInvitation"),
|
||||||
onClick: () => alert("send invitation"),
|
onClick: () => {
|
||||||
|
setShouldJoin(false);
|
||||||
|
setOpenOrganizationPicker(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -111,6 +111,15 @@ export class Organizations extends Resource<{ realm?: string }> {
|
||||||
urlParamKeys: ["orgId"],
|
urlParamKeys: ["orgId"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public inviteExistingUser = this.makeUpdateRequest<
|
||||||
|
{ orgId: string },
|
||||||
|
FormData
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
path: "/{orgId}/members/invite-existing-user",
|
||||||
|
urlParamKeys: ["orgId"],
|
||||||
|
});
|
||||||
|
|
||||||
public listIdentityProviders = this.makeRequest<
|
public listIdentityProviders = this.makeRequest<
|
||||||
{ orgId: string },
|
{ orgId: string },
|
||||||
IdentityProviderRepresentation[]
|
IdentityProviderRepresentation[]
|
||||||
|
|
Loading…
Reference in a new issue