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
|
||||
sendInvitation=Send invitation
|
||||
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
|
||||
organizationRemoveError=Could not remove user from organizations\: {{error}}
|
||||
organizationName=Organization name
|
||||
|
@ -3231,3 +3232,4 @@ join=Join
|
|||
userAddedOrganization_one=Organization added to the user
|
||||
userAddedOrganizationError=Could not add organizations to the user\: {{error}}
|
||||
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 UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { Button, Modal, ModalVariant } from "@patternfly/react-core";
|
||||
import { differenceBy } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { TableText } from "@patternfly/react-table";
|
||||
|
||||
type OrganizationModalProps = {
|
||||
isJoin?: boolean;
|
||||
existingOrgs: OrganizationRepresentation[];
|
||||
onAdd: (orgs: OrganizationRepresentation[]) => Promise<void>;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const OrganizationModal = ({
|
||||
isJoin = true,
|
||||
existingOrgs,
|
||||
onAdd,
|
||||
onClose,
|
||||
}: OrganizationModalProps) => {
|
||||
|
@ -21,13 +27,20 @@ export const OrganizationModal = ({
|
|||
const [selectedRows, setSelectedRows] = useState<UserRepresentation[]>([]);
|
||||
|
||||
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 (
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t("joinOrganization")}
|
||||
title={isJoin ? t("joinOrganization") : t("sendInvitation")}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
actions={[
|
||||
|
@ -40,7 +53,7 @@ export const OrganizationModal = ({
|
|||
onClose();
|
||||
}}
|
||||
>
|
||||
{t("join")}
|
||||
{isJoin ? t("join") : t("send")}
|
||||
</Button>,
|
||||
<Button
|
||||
data-testid="cancel"
|
||||
|
@ -66,6 +79,9 @@ export const OrganizationModal = ({
|
|||
},
|
||||
{
|
||||
name: "description",
|
||||
cellRenderer: (row) => (
|
||||
<TableText wrapModifier="truncate">{row.description}</TableText>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -57,7 +57,9 @@ const Domains = (org: OrganizationRepresentation) => {
|
|||
};
|
||||
|
||||
type OrganizationTableProps = PropsWithChildren & {
|
||||
loader: LoaderFunction<OrganizationRepresentation>;
|
||||
loader:
|
||||
| LoaderFunction<OrganizationRepresentation>
|
||||
| OrganizationRepresentation[];
|
||||
toolbarItem?: ReactNode;
|
||||
isPaginated?: boolean;
|
||||
onSelect?: (orgs: OrganizationRepresentation[]) => void;
|
||||
|
|
|
@ -17,6 +17,7 @@ import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
|||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { OrganizationModal } from "../organizations/OrganizationModal";
|
||||
import { OrganizationTable } from "../organizations/OrganizationTable";
|
||||
import { useFetch } from "../utils/useFetch";
|
||||
import useToggle from "../utils/useToggle";
|
||||
import { UserParams } from "./routes/User";
|
||||
|
||||
|
@ -30,11 +31,19 @@ export const Organizations = () => {
|
|||
const refresh = () => setKey(key + 1);
|
||||
|
||||
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<
|
||||
OrganizationRepresentation[]
|
||||
>([]);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.organizations.memberOrganizations({ userId: id! }),
|
||||
setUserOrgs,
|
||||
[key],
|
||||
);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "removeConfirmOrganizationTitle",
|
||||
messageKey: t("organizationRemoveConfirm", { count: selectedOrgs.length }),
|
||||
|
@ -51,6 +60,7 @@ export const Organizations = () => {
|
|||
),
|
||||
);
|
||||
addAlert(t("organizationRemovedSuccess"));
|
||||
setSelectedOrgs([]);
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addError("organizationRemoveError", error);
|
||||
|
@ -60,18 +70,29 @@ export const Organizations = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{joinOrganization && (
|
||||
{openOrganizationPicker && (
|
||||
<OrganizationModal
|
||||
onClose={() => setJoinOrganization(false)}
|
||||
isJoin={shouldJoin}
|
||||
existingOrgs={userOrgs}
|
||||
onClose={() => setOpenOrganizationPicker(false)}
|
||||
onAdd={async (orgs) => {
|
||||
try {
|
||||
await Promise.all(
|
||||
orgs.map((org) =>
|
||||
adminClient.organizations.addMember({
|
||||
orgs.map((org) => {
|
||||
const form = new FormData();
|
||||
form.append("id", id!);
|
||||
return shouldJoin
|
||||
? adminClient.organizations.addMember({
|
||||
orgId: org.id!,
|
||||
userId: id!,
|
||||
})
|
||||
: adminClient.organizations.inviteExistingUser(
|
||||
{
|
||||
orgId: org.id!,
|
||||
},
|
||||
form,
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
addAlert(t("userAddedOrganization", { count: orgs.length }));
|
||||
refresh();
|
||||
|
@ -83,14 +104,13 @@ export const Organizations = () => {
|
|||
)}
|
||||
<DeleteConfirm />
|
||||
<OrganizationTable
|
||||
key={key}
|
||||
loader={() =>
|
||||
adminClient.organizations.memberOrganizations({
|
||||
userId: id!,
|
||||
})
|
||||
}
|
||||
loader={userOrgs}
|
||||
onSelect={(orgs) => setSelectedOrgs(orgs)}
|
||||
deleteLabel="remove"
|
||||
onDelete={(org) => {
|
||||
setSelectedOrgs([org]);
|
||||
toggleDeleteDialog();
|
||||
}}
|
||||
toolbarItem={
|
||||
<>
|
||||
<ToolbarItem>
|
||||
|
@ -112,13 +132,20 @@ export const Organizations = () => {
|
|||
<DropdownItem
|
||||
key="join"
|
||||
onClick={() => {
|
||||
setJoinOrganization(true);
|
||||
setShouldJoin(true);
|
||||
setOpenOrganizationPicker(true);
|
||||
}}
|
||||
>
|
||||
{t("joinOrganization")}
|
||||
</DropdownItem>
|
||||
<DropdownItem key="invite" component="button">
|
||||
{t("invite")}
|
||||
<DropdownItem
|
||||
key="invite"
|
||||
onClick={() => {
|
||||
setShouldJoin(false);
|
||||
setOpenOrganizationPicker(true);
|
||||
}}
|
||||
>
|
||||
{t("sentInvite")}
|
||||
</DropdownItem>
|
||||
</DropdownList>
|
||||
</Dropdown>
|
||||
|
@ -142,11 +169,17 @@ export const Organizations = () => {
|
|||
secondaryActions={[
|
||||
{
|
||||
text: t("joinOrganization"),
|
||||
onClick: () => alert("join organization"),
|
||||
onClick: () => {
|
||||
setShouldJoin(true);
|
||||
setOpenOrganizationPicker(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
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"],
|
||||
});
|
||||
|
||||
public inviteExistingUser = this.makeUpdateRequest<
|
||||
{ orgId: string },
|
||||
FormData
|
||||
>({
|
||||
method: "POST",
|
||||
path: "/{orgId}/members/invite-existing-user",
|
||||
urlParamKeys: ["orgId"],
|
||||
});
|
||||
|
||||
public listIdentityProviders = this.makeRequest<
|
||||
{ orgId: string },
|
||||
IdentityProviderRepresentation[]
|
||||
|
|
Loading…
Reference in a new issue