Disable activation on scim profiles
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { ActionDropdown, Avatar, Breadcrumb, Card, DropdownItem, IconTrashCan, useConfirm } from "@probo/ui";
|
||||
import { ActionDropdown, Avatar, Badge, Breadcrumb, Card, DropdownItem, IconTrashCan, useConfirm } from "@probo/ui";
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { useNavigate } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
@@ -18,9 +18,8 @@ export const personPageQuery = graphql`
|
||||
... on Profile {
|
||||
id
|
||||
fullName
|
||||
identity @required(action: THROW) {
|
||||
email
|
||||
}
|
||||
emailAddress
|
||||
source
|
||||
canDelete: permission(action: "iam:membership-profile:delete")
|
||||
...PersonFormFragment
|
||||
}
|
||||
@@ -100,9 +99,11 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
|
||||
<div className="flex items-center gap-6">
|
||||
<Avatar name={person.fullName} size="xl" />
|
||||
<div>
|
||||
|
||||
<div className="text-2xl">{person.fullName}</div>
|
||||
<div className="text-lg text-txt-secondary">{person.identity.email}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl">{person.fullName}</span>
|
||||
<Badge variant="info">{person.source}</Badge>
|
||||
</div>
|
||||
<div className="text-lg text-txt-secondary">{person.emailAddress}</div>
|
||||
</div>
|
||||
</div>
|
||||
{person.canDelete && (
|
||||
|
||||
@@ -29,14 +29,12 @@ const fragment = graphql`
|
||||
source
|
||||
state
|
||||
fullName
|
||||
emailAddress
|
||||
membership @required(action: THROW) {
|
||||
id
|
||||
role
|
||||
canUpdate: permission(action: "iam:membership:update")
|
||||
}
|
||||
identity @required(action: THROW) {
|
||||
email
|
||||
}
|
||||
lastInvitation: pendingInvitations(first: 1, orderBy: { field: CREATED_AT, direction: DESC })
|
||||
@required(action: THROW)
|
||||
@connection(key: "PeopleListItem_lastInvitation") {
|
||||
@@ -44,8 +42,6 @@ const fragment = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
expiresAt
|
||||
acceptedAt
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
@@ -193,87 +189,85 @@ export function PeopleListItem(props: {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr to={`/organizations/${organizationId}/people/${profile.id}`}>
|
||||
<Td className={clsx(
|
||||
<Tr to={`/organizations/${organizationId}/people/${profile.id}`}>
|
||||
<Td className={clsx(
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
<span className="font-semibold">{profile.fullName}</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={profile.state === "INACTIVE" ? "neutral" : "success"}>{profile.state}</Badge>
|
||||
</Td>
|
||||
<Td className={clsx(
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{profile.emailAddress}
|
||||
<Badge variant="info">{profile.source}</Badge>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
noLink
|
||||
className={clsx(
|
||||
"pr-4",
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
disabled={!profile.membership.canUpdate || isUpdatingRole}
|
||||
value={profile.membership.role}
|
||||
onValueChange={role => void handleUpdateRole(role)}
|
||||
>
|
||||
<span className="font-semibold">{profile.fullName}</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={profile.state === "INACTIVE" ? "neutral" : "success"}>{profile.state}</Badge>
|
||||
</Td>
|
||||
<Td className={clsx(
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{profile.identity.email}
|
||||
<Badge variant="info">{profile.source}</Badge>
|
||||
</div>
|
||||
</Td>
|
||||
<Td
|
||||
noLink
|
||||
className={clsx(
|
||||
"pr-4",
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
{availableRoles.includes("OWNER") && (
|
||||
<Option value="OWNER">{__("Owner")}</Option>
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
disabled={!profile.membership.canUpdate || isUpdatingRole}
|
||||
value={profile.membership.role}
|
||||
onValueChange={role => void handleUpdateRole(role)}
|
||||
>
|
||||
{availableRoles.includes("OWNER") && (
|
||||
<Option value="OWNER">{__("Owner")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("ADMIN") && (
|
||||
<Option value="ADMIN">{__("Admin")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("VIEWER") && (
|
||||
<Option value="VIEWER">{__("Viewer")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("AUDITOR") && (
|
||||
<Option value="AUDITOR">{__("Auditor")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("EMPLOYEE") && (
|
||||
<Option value="EMPLOYEE">{__("Employee")}</Option>
|
||||
)}
|
||||
</Select>
|
||||
</Td>
|
||||
<Td className={clsx(
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
{new Date(profile.createdAt).toLocaleDateString()}
|
||||
</Td>
|
||||
<Td noLink width={160} className="text-end">
|
||||
<ActionDropdown>
|
||||
{isInactive && (
|
||||
<DropdownItem
|
||||
onClick={handleInvite}
|
||||
icon={IconMail}
|
||||
>
|
||||
{lastInvitation ? __("Resend activation mail") : __("Send activation mail")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{profile.canDelete && (
|
||||
<DropdownItem
|
||||
onClick={handleRemove}
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Remove person")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
{availableRoles.includes("ADMIN") && (
|
||||
<Option value="ADMIN">{__("Admin")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("VIEWER") && (
|
||||
<Option value="VIEWER">{__("Viewer")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("AUDITOR") && (
|
||||
<Option value="AUDITOR">{__("Auditor")}</Option>
|
||||
)}
|
||||
{availableRoles.includes("EMPLOYEE") && (
|
||||
<Option value="EMPLOYEE">{__("Employee")}</Option>
|
||||
)}
|
||||
</Select>
|
||||
</Td>
|
||||
<Td className={clsx(
|
||||
isRemoving && "opacity-60 pointer-events-none",
|
||||
isInactive && "opacity-50",
|
||||
)}
|
||||
>
|
||||
{new Date(profile.createdAt).toLocaleDateString()}
|
||||
</Td>
|
||||
<Td noLink width={160} className="text-end">
|
||||
<ActionDropdown>
|
||||
{isInactive && profile.source !== "SCIM" && profile.canInvite && (
|
||||
<DropdownItem
|
||||
onClick={handleInvite}
|
||||
icon={IconMail}
|
||||
>
|
||||
{lastInvitation ? __("Resend activation mail") : __("Send activation mail")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{profile.canDelete && profile.source !== "SCIM" && (
|
||||
<DropdownItem
|
||||
onClick={handleRemove}
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Remove person")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const fragment = graphql`
|
||||
id
|
||||
fullName
|
||||
emailAddress
|
||||
source
|
||||
membership @required(action: THROW) {
|
||||
role
|
||||
}
|
||||
@@ -268,7 +269,7 @@ export function PersonFormLoader(props: { fragmentRef: PersonFormFragment$key })
|
||||
return (
|
||||
<PersonForm
|
||||
id={person.id}
|
||||
disabled={!person.canUpdate}
|
||||
disabled={!person.canUpdate || person.source === "SCIM"}
|
||||
defaultValues={
|
||||
{
|
||||
kind: person.kind,
|
||||
|
||||
Reference in New Issue
Block a user