Fix people pages permissions handling

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2026-01-02 10:28:49 +01:00
committed by Bryan Frimin
parent 2dd7d7800b
commit cbf338fd14
15 changed files with 300 additions and 69 deletions

View File

@@ -10,6 +10,7 @@ import {
IconGroup1,
IconInboxEmpty,
IconListStack,
IconLock,
IconMedal,
IconPageCheck,
IconPageTextLine,
@@ -47,12 +48,13 @@ const fragment = graphql`
canListProcessingActivities: permission(
action: "core:processing-activity:list"
)
canListStatesOfApplicability: permission(
action: "core:state-of-applicability:list"
)
canListRightsRequests: permission(action: "core:rights-request:list")
canListSnapshots: permission(action: "core:snapshot:list")
canGetTrustCenter: permission(action: "core:trust-center:get")
canUpdateOrganization: permission(action: "iam:organization:update")
canListStatesOfApplicability: permission(
action: "core:state-of-applicability:list"
)
}
`;
@@ -180,6 +182,13 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) {
to={`${prefix}/states-of-applicability`}
/>
)}
{organization.canListRightsRequests && (
<SidebarItem
label={__("Rights Requests")}
icon={IconLock}
to={`${prefix}/rights-requests`}
/>
)}
{organization.canListSnapshots && (
<SidebarItem
label={__("Snapshots")}

View File

@@ -21,8 +21,6 @@ import {
import { useTranslate } from "@probo/i18n";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { Outlet } from "react-router";
import { use } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
type Props = {
queryRef: PreloadedQuery<PeopleGraphNodeQuery>;
@@ -33,7 +31,6 @@ export default function PeopleDetailPage(props: Props) {
const people = data.node;
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const { isAuthorized } = use(PermissionsContext);
const deletePeople = useDeletePeople(
people,
ConnectionHandler.getConnectionID(organizationId, PeopleConnectionKey),
@@ -57,7 +54,7 @@ export default function PeopleDetailPage(props: Props) {
<Avatar name={people.fullName ?? ""} size="xl" />
<div className="text-2xl">{people.fullName}</div>
</div>
{isAuthorized("People", "deletePeople") && (
{data.node.canDelete && (
<ActionDropdown variant="secondary">
<DropdownItem
variant="danger"

View File

@@ -23,10 +23,12 @@ import type { NodeOf } from "/types";
import { usePageTitle } from "@probo/hooks";
import { getRole } from "@probo/helpers";
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
import { SetEndOfContractDialog, type SetEndOfContractDialogRef } from "./dialogs/SetEndOfContractDialog";
import {
SetEndOfContractDialog,
type SetEndOfContractDialogRef,
} from "./dialogs/SetEndOfContractDialog";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { PermissionsContext } from "/providers/PermissionsContext";
import { use, useRef } from "react";
import { useRef } from "react";
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
@@ -44,25 +46,31 @@ export default function PeopleListPage({
queryRef: PreloadedQuery<PeopleGraphPaginatedQuery>;
}) {
const { __ } = useTranslate();
const { isAuthorized } = use(PermissionsContext);
const { people, refetch, connectionId, hasNext, loadNext, isLoadingNext } =
usePeopleQuery(queryRef);
const {
data,
people,
refetch,
connectionId,
hasNext,
loadNext,
isLoadingNext,
} = usePeopleQuery(queryRef);
usePageTitle(__("Members"));
const hasAnyAction =
isAuthorized("People", "updatePeople") ||
isAuthorized("People", "deletePeople");
const hasAnyAction = people.some(
({ canDelete, canUpdate }) => canDelete || canUpdate
);
return (
<div className="space-y-6">
<PageHeader
title={__("Members")}
description={__(
"Keep track of your company's workforce and their progress towards completing tasks assigned to them.",
"Keep track of your company's workforce and their progress towards completing tasks assigned to them."
)}
>
{isAuthorized("Organization", "createPeople") && (
{data.canCreatePeople && (
<CreatePeopleDialog connectionId={connectionId}>
<Button icon={IconPlusLarge}>{__("Add member")}</Button>
</CreatePeopleDialog>
@@ -110,7 +118,6 @@ function PeopleRow({
const { __ } = useTranslate();
const deletePeople = useDeletePeople(people, connectionId);
const contractEnded = isContractEnded(people);
const { isAuthorized } = use(PermissionsContext);
const dialogRef = useRef<SetEndOfContractDialogRef>(null);
return (
@@ -140,7 +147,7 @@ function PeopleRow({
{hasAnyAction && (
<Td noLink width={50} className="text-end">
<ActionDropdown>
{isAuthorized("People", "updatePeople") && (
{people.canUpdate && (
<DropdownItem
icon={IconCalendar2}
onClick={() => dialogRef.current?.open()}
@@ -148,7 +155,7 @@ function PeopleRow({
{__("Set end of contract")}
</DropdownItem>
)}
{isAuthorized("People", "deletePeople") && (
{people.canDelete && (
<DropdownItem
icon={IconTrashCan}
variant="danger"

View File

@@ -31,7 +31,7 @@ const schema = z.object({
additionalEmailAddresses: z.preprocess(
// Empty additional emails are skipped
(v) => (v as string[]).filter((v) => !!v),
z.array(z.string().email())
z.array(z.string().email()),
),
kind: z.enum(peopleRoles),
});
@@ -50,6 +50,8 @@ export const createPeopleMutation = graphql`
position
kind
additionalEmailAddresses
canDelete: permission(action: "core:people:delete")
canUpdate: permission(action: "core:people:update")
}
}
}

View File

@@ -9,8 +9,6 @@ import type { PeopleGraphUpdateMutation } from "/__generated__/core/PeopleGraphU
import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
import { Button, Card, Field, Input } from "@probo/ui";
import { EmailsField } from "/components/form/EmailsField";
import { PermissionsContext } from "/providers/PermissionsContext";
import { use } from "react";
const schema = z.object({
fullName: z.string().min(1),
@@ -50,7 +48,6 @@ export default function PeopleProfileTab() {
errorMessage: __("Failed to update member"),
},
);
const { isAuthorized } = use(PermissionsContext);
const onSubmit = handleSubmit((data) => {
const input = {
id: people.id!,
@@ -80,22 +77,32 @@ export default function PeopleProfileTab() {
{...register("position")}
type="text"
placeholder={__("e.g. CEO, CFO, etc.")}
disabled={!people.canUpdate}
/>
<Field
label={__("Primary email")}
{...register("primaryEmailAddress")}
type="email"
disabled={!people.canUpdate}
/>
<EmailsField control={control} register={register} />
<Field label={__("Contract start date")}>
<Input {...register("contractStartDate")} type="date" />
<Input
{...register("contractStartDate")}
type="date"
disabled={!people.canUpdate}
/>
</Field>
<Field label={__("Contract end date")}>
<Input {...register("contractEndDate")} type="date" />
<Input
{...register("contractEndDate")}
type="date"
disabled={!people.canUpdate}
/>
</Field>
</Card>
<div className="flex justify-end">
{formState.isDirty && isAuthorized("People", "updatePeople") && (
{formState.isDirty && people.canUpdate && (
<Button type="submit" disabled={isMutating}>
{__("Update")}
</Button>

View File

@@ -1,6 +1,6 @@
import { useTranslate } from "@probo/i18n";
import { Button, Card, IconCheckmark1 } from "@probo/ui";
import { use, type PropsWithChildren } from "react";
import { type PropsWithChildren } from "react";
import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { ControlledField } from "/components/form/ControlledField";
@@ -11,7 +11,6 @@ import { useOutletContext } from "react-router";
import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import type { PeopleGraphUpdateMutation } from "/__generated__/core/PeopleGraphUpdateMutation.graphql";
import { PermissionsContext } from "/providers/PermissionsContext";
const schema = z.object({
kind: z.enum(peopleRoles),
@@ -22,8 +21,6 @@ export default function PeopleRoleTab() {
people: PeopleGraphNodeQuery$data["node"];
}>();
const { __ } = useTranslate();
const { isAuthorized } = use(PermissionsContext);
const canUpdatePeople = isAuthorized("People", "updatePeople");
const { control, formState, handleSubmit, reset } = useFormWithSchema(
schema,
{
@@ -68,7 +65,7 @@ export default function PeopleRoleTab() {
name="kind"
type="select"
label={__("Role")}
disabled={!canUpdatePeople}
disabled={!people.canUpdate}
>
{getRoles(__).map((role) => (
<Option key={role.value} value={role.value}>
@@ -97,7 +94,7 @@ export default function PeopleRoleTab() {
</ul>
</div>
</Card>
{canUpdatePeople && (
{people.canUpdate && (
<div className="flex justify-end">
{formState.isDirty && (
<Button type="submit" disabled={isMutating}>