From e6dd8ac1c0604d4ba002ecc46263f0b29d946ae3 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Fri, 16 Aug 2024 11:26:42 +0200 Subject: [PATCH] added join org modal Signed-off-by: Erik Jan de Wit --- .../admin/messages/messages_en.properties | 12 +- .../src/organizations/OrganizationModal.tsx | 74 +++++++ .../src/organizations/OrganizationTable.tsx | 18 +- .../organizations/OrganizationsSection.tsx | 1 + js/apps/admin-ui/src/user/Organizations.tsx | 194 +++++++++++++----- 5 files changed, 240 insertions(+), 59 deletions(-) create mode 100644 js/apps/admin-ui/src/organizations/OrganizationModal.tsx diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 4c43e74953..f81428855f 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -3220,4 +3220,14 @@ validatingX509CertsHelp=The public certificates Keycloak uses to validate the si emptyUserOrganizations=No organizations emptyUserOrganizationsInstructions=There is no organization yet. Please join an organization or send an invitation to join an organization. joinOrganization=Join organization -sendInvitation=Send invitation \ No newline at end of file +sendInvitation=Send invitation +removeConfirmOrganizationTitle=Remove organization? +organizationRemoveConfirm=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 +joinOrganization=Join organization +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 diff --git a/js/apps/admin-ui/src/organizations/OrganizationModal.tsx b/js/apps/admin-ui/src/organizations/OrganizationModal.tsx new file mode 100644 index 0000000000..80e8a79c4e --- /dev/null +++ b/js/apps/admin-ui/src/organizations/OrganizationModal.tsx @@ -0,0 +1,74 @@ +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 { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAdminClient } from "../admin-client"; +import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; + +type OrganizationModalProps = { + onAdd: (orgs: OrganizationRepresentation[]) => Promise; + onClose: () => void; +}; + +export const OrganizationModal = ({ + onAdd, + onClose, +}: OrganizationModalProps) => { + const { adminClient } = useAdminClient(); + const { t } = useTranslation(); + + const [selectedRows, setSelectedRows] = useState([]); + + const loader = async (first?: number, max?: number, search?: string) => { + return await adminClient.organizations.find({ first, max, search }); + }; + + return ( + { + await onAdd(selectedRows); + onClose(); + }} + > + {t("join")} + , + , + ]} + > + setSelectedRows([...rows])} + columns={[ + { + name: "name", + displayKey: "organizationName", + }, + { + name: "description", + }, + ]} + /> + + ); +}; diff --git a/js/apps/admin-ui/src/organizations/OrganizationTable.tsx b/js/apps/admin-ui/src/organizations/OrganizationTable.tsx index ab31eacc40..970eb92492 100644 --- a/js/apps/admin-ui/src/organizations/OrganizationTable.tsx +++ b/js/apps/admin-ui/src/organizations/OrganizationTable.tsx @@ -57,17 +57,21 @@ const Domains = (org: OrganizationRepresentation) => { }; type OrganizationTableProps = PropsWithChildren & { - loader: - | LoaderFunction - | OrganizationRepresentation[]; - onDelete?: (org: OrganizationRepresentation) => void; + loader: LoaderFunction; toolbarItem?: ReactNode; + isPaginated?: boolean; + onSelect?: (orgs: OrganizationRepresentation[]) => void; + onDelete?: (org: OrganizationRepresentation) => void; + deleteLabel?: string; }; export const OrganizationTable = ({ loader, toolbarItem, + isPaginated = false, + onSelect, onDelete, + deleteLabel = "delete", children, }: OrganizationTableProps) => { const { t } = useTranslation(); @@ -75,13 +79,15 @@ export const OrganizationTable = ({ return ( - - - - - - } - > - alert("join organization"), - }, - { - text: t("sendInvitation"), - onClick: () => alert("send invitation"), - }, - ]} - /> - + <> + {joinOrganization && ( + setJoinOrganization(false)} + onAdd={async (orgs) => { + try { + await Promise.all( + orgs.map((org) => + adminClient.organizations.addMember({ + orgId: org.id!, + userId: id!, + }), + ), + ); + addAlert(t("userAddedOrganization", { count: orgs.length })); + refresh(); + } catch (error) { + addError("userAddedOrganizationError", error); + } + }} + /> + )} + + + adminClient.organizations.memberOrganizations({ + userId: id!, + }) + } + onSelect={(orgs) => setSelectedOrgs(orgs)} + deleteLabel="remove" + toolbarItem={ + <> + + ( + + {t("joinOrganization")} + + )} + isOpen={joinToggle} + > + + { + setJoinOrganization(true); + }} + > + {t("joinOrganization")} + + + {t("invite")} + + + + + + + + + } + > + alert("join organization"), + }, + { + text: t("sendInvitation"), + onClick: () => alert("send invitation"), + }, + ]} + /> + + ); };