diff --git a/apps/console/src/components/customDomains/CustomDomainManager.tsx b/apps/console/src/components/customDomains/CustomDomainManager.tsx index 6a324d598..6a85a348f 100644 --- a/apps/console/src/components/customDomains/CustomDomainManager.tsx +++ b/apps/console/src/components/customDomains/CustomDomainManager.tsx @@ -6,7 +6,8 @@ import type { CustomDomainManagerDeleteMutation } from "./__generated__/CustomDo import { CreateCustomDomainDialog } from "./CreateCustomDomainDialog"; import { DeleteCustomDomainDialog } from "./DeleteCustomDomainDialog"; import { DomainDetailsDialog } from "./DomainDetailsDialog"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; const deleteCustomDomainMutation = graphql` mutation CustomDomainManagerDeleteMutation($input: DeleteCustomDomainInput!) { @@ -45,7 +46,7 @@ export function CustomDomainManager({ customDomain, }: CustomDomainManagerProps) { const { __ } = useTranslate(); - + const { isAuthorized } = use(PermissionsContext); const [deleteCustomDomain] = useMutationWithToasts( deleteCustomDomainMutation, @@ -107,11 +108,11 @@ export function CustomDomainManager({ )}

- + {isAuthorized("Organization", "createCustomDomain") && ( - + )}
@@ -139,14 +140,14 @@ export function CustomDomainManager({ - + {isAuthorized("CustomDomain", "deleteCustomDomain") && ( - + )} diff --git a/apps/console/src/components/organizations/InviteUserDialog.tsx b/apps/console/src/components/organizations/InviteUserDialog.tsx index 7cb5e657e..ff32a5de0 100644 --- a/apps/console/src/components/organizations/InviteUserDialog.tsx +++ b/apps/console/src/components/organizations/InviteUserDialog.tsx @@ -17,8 +17,9 @@ import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { z } from "zod"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { Controller } from "react-hook-form"; -import { Suspense } from "react"; -import { getAssignableRoles } from "/permissions"; +import { Suspense, use } from "react"; +import { getAssignableRoles } from "@probo/helpers"; +import { PermissionsContext } from "/providers/PermissionsContext"; const inviteMutation = graphql` mutation InviteUserDialogMutation( @@ -56,7 +57,8 @@ type Props = PropsWithChildren & { function InviteUserDialogContent({ children, connectionId, onRefetch }: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); - const assignableRoles = getAssignableRoles(organizationId); + const { role: currentUserRole } = use(PermissionsContext); + const assignableRoles = getAssignableRoles(currentUserRole); const [inviteUser, isInviting] = useMutationWithToasts(inviteMutation, { successMessage: __("Invitation sent successfully"), errorMessage: __("Failed to send invitation"), diff --git a/apps/console/src/components/organizations/SlackConnection.tsx b/apps/console/src/components/organizations/SlackConnection.tsx index 12c68651b..912ebf1e3 100644 --- a/apps/console/src/components/organizations/SlackConnection.tsx +++ b/apps/console/src/components/organizations/SlackConnection.tsx @@ -2,7 +2,8 @@ import { Badge, Button, Card } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; import { sprintf } from "@probo/helpers"; import type { TrustCenterGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { organizationId: string; @@ -11,7 +12,7 @@ type Props = { export function SlackConnections({ organizationId, slackConnections: connectedSlackConnections }: Props) { const { __, dateTimeFormat } = useTranslate(); - + const { isAuthorized } = use(PermissionsContext); const slackConnectionDefinitions = [ { id: "SLACK", @@ -77,11 +78,11 @@ export function SlackConnections({ organizationId, slackConnections: connectedSl ) : ( - + isAuthorized("TrustCenter", "updateTrustCenter") && ( - + ) )} ))} diff --git a/apps/console/src/components/tasks/TasksCard.tsx b/apps/console/src/components/tasks/TasksCard.tsx index d176a987c..1f9f512c7 100644 --- a/apps/console/src/components/tasks/TasksCard.tsx +++ b/apps/console/src/components/tasks/TasksCard.tsx @@ -1,3 +1,6 @@ +import { promisifyMutation } from "@probo/helpers"; +import { usePageTitle } from "@probo/hooks"; +import { useTranslate } from "@probo/i18n"; import { ActionDropdown, Avatar, @@ -15,21 +18,17 @@ import { useConfirm, useDialogRef, } from "@probo/ui"; -import { Fragment } from "react"; +import { Fragment, use } from "react"; import { graphql, useMutation, useRelayEnvironment } from "react-relay"; -import { useTranslate } from "@probo/i18n"; -import { usePageTitle } from "@probo/hooks"; -import type { ItemOf } from "/types"; +import { Link, useLocation, useParams } from "react-router"; +import type { TaskFormDialogFragment$key } from "./__generated__/TaskFormDialogFragment.graphql"; import TaskFormDialog, { taskUpdateMutation, } from "/components/tasks/TaskFormDialog"; -import { useOrganizationId } from "/hooks/useOrganizationId"; -import { Link, useLocation, useParams } from "react-router"; -import { promisifyMutation } from "@probo/helpers"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; -import type { TaskFormDialogFragment$key } from "./__generated__/TaskFormDialogFragment.graphql"; import { updateStoreCounter } from "/hooks/useMutationWithIncrement"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import type { ItemOf } from "/types"; type Props = { tasks: ({ @@ -51,7 +50,6 @@ type Props = { export default function TasksCard({ tasks, connectionId }: Props) { const { __ } = useTranslate(); - const organizationId = useOrganizationId(); const hash = useLocation().hash.replace("#", ""); const hashes = [ @@ -70,8 +68,10 @@ export default function TasksCard({ tasks, connectionId }: Props) { usePageTitle(__("Tasks")); - const hasAnyAction = isAuthorized(organizationId, "Task", "updateTask") || - isAuthorized(organizationId, "Task", "deleteTask"); + const { isAuthorized } = use(PermissionsContext); + + const hasAnyAction = isAuthorized("Task", "updateTask") || + isAuthorized("Task", "deleteTask"); return (
@@ -151,6 +151,7 @@ function TaskRow(props: TaskRowProps) { const confirm = useConfirm(); const [deleteTask] = useMutation(deleteMutation); const params = useParams<{ measureId?: string }>(); + const { isAuthorized } = use(PermissionsContext); const relayEnv = useRelayEnvironment(); const [updateTask, isUpdating] = useMutation(taskUpdateMutation); @@ -232,15 +233,15 @@ function TaskRow(props: TaskRowProps) { )} {props.hasAnyAction && ( - + {isAuthorized("Task", "updateTask") && ( dialogRef.current?.open()} > {__("Edit")} - - + )} + {isAuthorized("Task", "deleteTask") && ( {__("Delete")} - + )} )}
diff --git a/apps/console/src/components/trustCenter/TrustCenterAuditsCard.tsx b/apps/console/src/components/trustCenter/TrustCenterAuditsCard.tsx index 9e9a84723..ce9102971 100644 --- a/apps/console/src/components/trustCenter/TrustCenterAuditsCard.tsx +++ b/apps/console/src/components/trustCenter/TrustCenterAuditsCard.tsx @@ -14,11 +14,11 @@ import { } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; import { useFragment } from "react-relay"; -import { useMemo, useState, useCallback, useEffect } from "react"; +import { useMemo, useState, useCallback, useEffect, use } from "react"; import { sprintf, getAuditStateVariant, getAuditStateLabel, formatDate, getTrustCenterVisibilityOptions } from "@probo/helpers"; import { useOrganizationId } from "/hooks/useOrganizationId"; import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; const trustCenterAuditFragment = graphql` fragment TrustCenterAuditsCardFragment on Audit { @@ -124,8 +124,8 @@ function AuditRow(props: { const organizationId = useOrganizationId(); const { __ } = useTranslate(); const [optimisticValue, setOptimisticValue] = useState(null); - - const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false; + const { isAuthorized } = use(PermissionsContext); + const canUpdate = organizationId ? isAuthorized("TrustCenter", "updateTrustCenter") : false; const handleValueChange = useCallback((value: string | {}) => { const stringValue = typeof value === 'string' ? value : ''; diff --git a/apps/console/src/components/trustCenter/TrustCenterDocumentsCard.tsx b/apps/console/src/components/trustCenter/TrustCenterDocumentsCard.tsx index 1deedbbef..422ffbef2 100644 --- a/apps/console/src/components/trustCenter/TrustCenterDocumentsCard.tsx +++ b/apps/console/src/components/trustCenter/TrustCenterDocumentsCard.tsx @@ -17,10 +17,10 @@ import { import { useTranslate } from "@probo/i18n"; import type { TrustCenterDocumentsCardFragment$key } from "./__generated__/TrustCenterDocumentsCardFragment.graphql"; import { useFragment } from "react-relay"; -import { useMemo, useState, useCallback, useEffect } from "react"; +import { useMemo, useState, useCallback, useEffect, use } from "react"; import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers"; import { useOrganizationId } from "/hooks/useOrganizationId"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; const trustCenterDocumentFragment = graphql` fragment TrustCenterDocumentsCardFragment on Document { @@ -129,8 +129,8 @@ function DocumentRow(props: { const organizationId = useOrganizationId(); const { __ } = useTranslate(); const [optimisticValue, setOptimisticValue] = useState(null); - - const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false; + const { isAuthorized } = use(PermissionsContext); + const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter"); const handleValueChange = useCallback((value: string | {}) => { const stringValue = typeof value === 'string' ? value : ''; diff --git a/apps/console/src/components/trustCenter/TrustCenterFilesCard.tsx b/apps/console/src/components/trustCenter/TrustCenterFilesCard.tsx index a6992e0c9..fbc421978 100644 --- a/apps/console/src/components/trustCenter/TrustCenterFilesCard.tsx +++ b/apps/console/src/components/trustCenter/TrustCenterFilesCard.tsx @@ -18,12 +18,10 @@ import { import { useTranslate } from "@probo/i18n"; import type { TrustCenterFilesCardFragment$key, TrustCenterFilesCardFragment$data } from "./__generated__/TrustCenterFilesCardFragment.graphql"; import { useFragment } from "react-relay"; -import { useMemo, useState, useCallback, useEffect } from "react"; +import { useMemo, useState, useCallback, useEffect, use } from "react"; import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers"; import { formatDate } from "@probo/helpers"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; -import { useParams } from "react-router"; +import { PermissionsContext } from "/providers/PermissionsContext"; const trustCenterFileFragment = graphql` fragment TrustCenterFilesCardFragment on TrustCenterFile { @@ -150,9 +148,8 @@ function FileRow(props: { const file = props.file; const { __ } = useTranslate(); const [optimisticValue, setOptimisticValue] = useState(null); - const { organizationId } = useParams(); - - const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false; + const { isAuthorized } = use(PermissionsContext); + const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter"); const handleValueChange = useCallback((value: string | {}) => { const stringValue = typeof value === 'string' ? value : ''; @@ -207,7 +204,7 @@ function FileRow(props: { onClick={() => window.open(file.fileUrl, '_blank', 'noopener,noreferrer')} title={__("Download")} /> - + {isAuthorized("TrustCenterFile", "updateTrustCenterFile") && ( - + )} @@ -190,6 +191,7 @@ function ReferenceRow({ onDrop, }: ReferenceRowProps) { const [isMouseDown, setIsMouseDown] = useState(false); + const { isAuthorized } = use(PermissionsContext); const className = [ isDragging && "opacity-50 cursor-grabbing", @@ -231,14 +233,14 @@ function ReferenceRow({ icon={IconArrowLink} onClick={onVisitWebsite} /> - + {isAuthorized("TrustCenterReference", "updateTrustCenterReference") && ( - + ) )} diff --git a/apps/console/src/pages/organizations/assets/AssetsPage.tsx b/apps/console/src/pages/organizations/assets/AssetsPage.tsx index 8d4ff247d..5b255d722 100644 --- a/apps/console/src/pages/organizations/assets/AssetsPage.tsx +++ b/apps/console/src/pages/organizations/assets/AssetsPage.tsx @@ -23,7 +23,7 @@ import { } from "/hooks/graph/AssetGraph"; import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql"; import { useOrganizationId } from "/hooks/useOrganizationId"; -import { PermissionsContext } from "/providers/PermissionsProvider"; +import { PermissionsContext } from "/providers/PermissionsContext"; const paginatedAssetsFragment = graphql` fragment AssetsPageFragment on Organization diff --git a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx index a0f1db377..7e8a148d7 100644 --- a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx +++ b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx @@ -34,7 +34,8 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema"; import z from "zod"; import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime, formatError, formatDate, type GraphQLError } from "@probo/helpers"; import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const updateAuditSchema = z.object({ name: z.string().nullable().optional(), @@ -52,7 +53,7 @@ export default function AuditDetailsPage(props: Props) { const auditEntry = audit.node; const { __ } = useTranslate(); const organizationId = useOrganizationId(); - + const { isAuthorized } = use(PermissionsContext); if (!auditEntry || !auditEntry.id || !auditEntry.framework) { return
{__("Audit not found")}
; } @@ -146,7 +147,7 @@ export default function AuditDetailsPage(props: Props) { - + {isAuthorized("Audit", "deleteAudit") && ( {__("Delete")} - + )} @@ -187,11 +188,11 @@ export default function AuditDetailsPage(props: Props) {
{formState.isDirty && ( - + isAuthorized("Audit", "updateAudit") && ( - + ) )}
diff --git a/apps/console/src/pages/organizations/audits/AuditsPage.tsx b/apps/console/src/pages/organizations/audits/AuditsPage.tsx index a58bc0582..21f6277d2 100644 --- a/apps/console/src/pages/organizations/audits/AuditsPage.tsx +++ b/apps/console/src/pages/organizations/audits/AuditsPage.tsx @@ -31,8 +31,8 @@ import type { AuditsPageFragment$key, } from "./__generated__/AuditsPageFragment.graphql"; import { SortableTable } from "/components/SortableTable"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; const paginatedAuditsFragment = graphql` fragment AuditsPageFragment on Organization @@ -83,6 +83,7 @@ type Props = { export default function AuditsPage(props: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); + const { isAuthorized } = use(PermissionsContext); const data = usePreloadedQuery(auditsQuery, props.queryRef); const pagination = usePaginationFragment( @@ -94,8 +95,8 @@ export default function AuditsPage(props: Props) { usePageTitle(__("Audits")); - const hasAnyAction = isAuthorized(organizationId, "Audit", "updateAudit") || - isAuthorized(organizationId, "Audit", "deleteAudit"); + const hasAnyAction = isAuthorized("Audit", "updateAudit") || + isAuthorized("Audit", "deleteAudit"); return (
@@ -105,14 +106,14 @@ export default function AuditsPage(props: Props) { "Manage your organization's compliance audits and their progress." )} > - + {isAuthorized("Organization", "createAudit") && ( - + )}
@@ -153,6 +154,7 @@ function AuditRow({ const organizationId = useOrganizationId(); const { __ } = useTranslate(); const deleteAudit = useDeleteAudit(entry, connectionId); + const { isAuthorized } = use(PermissionsContext); return ( @@ -177,7 +179,7 @@ function AuditRow({ {hasAnyAction && ( )} diff --git a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx b/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx index ae0b33cd5..8c37c43d0 100644 --- a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx +++ b/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx @@ -35,7 +35,8 @@ import z from "zod"; import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency } from "@probo/helpers"; import { SnapshotBanner } from "/components/SnapshotBanner"; import type { ContinualImprovementGraphNodeQuery } from "/hooks/graph/__generated__/ContinualImprovementGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const updateImprovementSchema = z.object({ referenceId: z.string().min(1, "Reference ID is required"), @@ -59,7 +60,7 @@ export default function ContinualImprovementDetailsPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); - + const { isAuthorized } = use(PermissionsContext); if (!improvement) { return
{__("Continual improvement entry not found")}
; } @@ -150,13 +151,13 @@ export default function ContinualImprovementDetailsPage(props: Props) { ]} /> {!isSnapshotMode && ( - + isAuthorized("ContinualImprovement", "deleteContinualImprovement") && ( {__("Delete")} - + ) )} @@ -289,7 +290,7 @@ export default function ContinualImprovementDetailsPage(props: Props) { {!isSnapshotMode && (
- + {isAuthorized("ContinualImprovement", "updateContinualImprovement") && ( - + )}
)} diff --git a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementsPage.tsx b/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementsPage.tsx index c40ccec5c..f0d14b9f6 100644 --- a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementsPage.tsx +++ b/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementsPage.tsx @@ -37,8 +37,8 @@ import type { ContinualImprovementsPageFragment$key, ContinualImprovementsPageFragment$data, } from "./__generated__/ContinualImprovementsPageFragment.graphql"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; interface ContinualImprovementsPageProps { queryRef: PreloadedQuery; @@ -93,6 +93,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Continual Improvements")); @@ -130,8 +131,8 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove const improvements = data?.continualImprovements?.edges?.map((edge) => edge.node) ?? []; const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "ContinualImprovement", "updateContinualImprovement") || - isAuthorized(organizationId, "ContinualImprovement", "deleteContinualImprovement") + isAuthorized("ContinualImprovement", "updateContinualImprovement") || + isAuthorized("ContinualImprovement", "deleteContinualImprovement") ); return ( @@ -141,7 +142,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove )} {!isSnapshotMode && ( - + isAuthorized("Organization", "createContinualImprovement") && ( - + ) )} @@ -225,6 +226,7 @@ function ImprovementRow({ const [deleteImprovement] = useMutation(deleteContinualImprovementMutation); const confirm = useConfirm(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); const handleDelete = () => { @@ -282,7 +284,7 @@ function ImprovementRow({ {hasAnyAction && ( )} diff --git a/apps/console/src/pages/organizations/data/DataPage.tsx b/apps/console/src/pages/organizations/data/DataPage.tsx index 3f388890a..e3053b4e8 100644 --- a/apps/console/src/pages/organizations/data/DataPage.tsx +++ b/apps/console/src/pages/organizations/data/DataPage.tsx @@ -35,8 +35,8 @@ import type { import type { DataListQuery } from "./__generated__/DataListQuery.graphql"; import { SortableTable } from "/components/SortableTable"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const paginatedDataFragment = graphql` fragment DataPageFragment on Organization @@ -93,6 +93,7 @@ export default function DataPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); const queryData = usePreloadedQuery( dataQuery, @@ -121,8 +122,9 @@ export default function DataPage(props: Props) { usePageTitle(__("Data")); - const hasAnyAction = !isSnapshotMode && ( isAuthorized(organizationId, "Datum", "updateDatum") || - isAuthorized(organizationId, "Datum", "deleteDatum") + const hasAnyAction = !isSnapshotMode && ( + isAuthorized("Datum", "updateDatum") || + isAuthorized("Datum", "deleteDatum") ); return ( @@ -137,7 +139,7 @@ export default function DataPage(props: Props) { )} > {!snapshotId && ( - + isAuthorized("Organization", "createDatum") && ( - + ) )} edge.node) ?? []; - + const { isAuthorized } = use(PermissionsContext); const detailUrl = snapshotId ? `/organizations/${organizationId}/snapshots/${snapshotId}/data/${entry.id}` : `/organizations/${organizationId}/data/${entry.id}`; @@ -228,7 +230,7 @@ function DataRow({ {hasAnyAction && ( )} diff --git a/apps/console/src/pages/organizations/data/DatumDetailsPage.tsx b/apps/console/src/pages/organizations/data/DatumDetailsPage.tsx index 4f79b902d..83743a177 100644 --- a/apps/console/src/pages/organizations/data/DatumDetailsPage.tsx +++ b/apps/console/src/pages/organizations/data/DatumDetailsPage.tsx @@ -29,7 +29,8 @@ import z from "zod"; import { SnapshotBanner } from "/components/SnapshotBanner"; import { validateSnapshotConsistency } from "@probo/helpers"; import type { DatumGraphNodeQuery } from "/hooks/graph/__generated__/DatumGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const updateDatumSchema = z.object({ name: z.string().min(1, "Name is required"), @@ -57,6 +58,7 @@ export default function DatumDetailsPage(props: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); + const { isAuthorized } = use(PermissionsContext); const deleteDatum = useDeleteDatum( datumEntry, @@ -125,7 +127,7 @@ export default function DatumDetailsPage(props: Props) { {datumEntry?.dataClassification} {!isSnapshotMode && ( - + isAuthorized("Datum", "deleteDatum") && ( - + ) )} @@ -180,11 +182,11 @@ export default function DatumDetailsPage(props: Props) { {!isSnapshotMode && (
{formState.isDirty && ( - + isAuthorized("Datum", "updateDatum") && ( - + ) )}
)} diff --git a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx b/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx index 89ceeb3a0..e93382ccc 100644 --- a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx +++ b/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx @@ -67,7 +67,7 @@ import { PdfDownloadDialog, type PdfDownloadDialogRef, } from "/components/documents/PdfDownloadDialog"; -import { useRef, useState } from "react"; +import { use, useRef, useState } from "react"; import type { NodeOf } from "/types.ts"; import clsx from "clsx"; import { PeopleSelectField } from "/components/form/PeopleSelectField"; @@ -76,7 +76,7 @@ import { DocumentTypeOptions } from "/components/form/DocumentTypeOptions"; import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions"; import { z } from "zod"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -132,37 +132,6 @@ const documentFragment = graphql` } `; -graphql` - fragment DocumentDetailPageRowFragment on Document { - id - title - description - documentType - classification - updatedAt - owner { - id - fullName - } - versions(first: 1) { - edges { - node { - id - status - signatures(first: 100) { - edges { - node { - id - state - } - } - } - } - } - } - } -`; - const publishDocumentVersionMutation = graphql` mutation DocumentDetailPagePublishMutation( $input: PublishDocumentVersionInput! @@ -170,7 +139,31 @@ const publishDocumentVersionMutation = graphql` publishDocumentVersion(input: $input) { document { id - ...DocumentDetailPageRowFragment + title + description + documentType + classification + updatedAt + owner { + id + fullName + } + versions(first: 1) { + edges { + node { + id + status + signatures(first: 100) { + edges { + node { + id + state + } + } + } + } + } + } } } } @@ -230,6 +223,7 @@ export default function DocumentDetailPage(props: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); const navigate = useNavigate(); + const { isAuthorized } = use(PermissionsContext); const [isEditingTitle, setIsEditingTitle] = useState(false); const [isEditingOwner, setIsEditingOwner] = useState(false); @@ -522,16 +516,16 @@ export default function DocumentDetailPage(props: Props) { - + {isAuthorized("Document", "updateDocument") && ( updateDialogRef.current?.open()} icon={IconPencil} > {isDraft ? __("Edit draft document") : __("Create new draft")} - + )} {isDraft && versions.length > 1 && ( - + isAuthorized("Document", "deleteDocument") && ( {__("Delete draft document")} - + ) )} pdfDownloadDialogRef.current?.open()} @@ -548,7 +542,7 @@ export default function DocumentDetailPage(props: Props) { > {__("Download PDF")} - + {isAuthorized("Document", "deleteDocument") && ( {__("Delete document")} - + )} diff --git a/apps/console/src/pages/organizations/documents/DocumentsPage.tsx b/apps/console/src/pages/organizations/documents/DocumentsPage.tsx index 529b90d3e..09e9e8fb5 100644 --- a/apps/console/src/pages/organizations/documents/DocumentsPage.tsx +++ b/apps/console/src/pages/organizations/documents/DocumentsPage.tsx @@ -29,7 +29,7 @@ import { useLazyLoadQuery, type PreloadedQuery, } from "react-relay"; -import { useRef } from "react"; +import { use, useRef } from "react"; import { graphql } from "relay-runtime"; import type { DocumentGraphListQuery } from "/hooks/graph/__generated__/DocumentGraphListQuery.graphql"; import { @@ -57,8 +57,7 @@ import { type BulkExportDialogRef, } from "/components/documents/BulkExportDialog"; import type { DocumentsPageUserEmailQuery } from "./__generated__/DocumentsPageUserEmailQuery.graphql"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsProvider.tsx"; const documentsFragment = graphql` fragment DocumentsPageListFragment on Organization @@ -107,6 +106,7 @@ const UserEmailQuery = graphql` export default function DocumentsPage(props: Props) { const { __ } = useTranslate(); + const { isAuthorized } = use(PermissionsContext); const organization = usePreloadedQuery( documentsQuery, @@ -137,8 +137,8 @@ export default function DocumentsPage(props: Props) { usePageTitle(__("Documents")); - const hasAnyAction = isAuthorized(organization.id, "Document", "updateDocument") || - isAuthorized(organization.id, "Document", "deleteDocument"); + const hasAnyAction = isAuthorized("Document", "updateDocument") || + isAuthorized("Document", "deleteDocument"); const handleSendSigningNotifications = () => { sendSigningNotifications({ @@ -198,21 +198,21 @@ export default function DocumentsPage(props: Props) { description={__("Manage your organization's documents")} >
- - - - + {isAuthorized("Document", "sendSigningNotifications") && ( + + )} + {isAuthorized("Organization", "createDocument") && ( {__("New document")}} /> - + )}
{documents.length > 0 ? ( @@ -258,7 +258,7 @@ export default function DocumentsPage(props: Props) {
- + {isAuthorized("Document", "updateDocument") && ( - - + )} + {isAuthorized("Document", "bulkRequestSignatures") && ( - + )} - + {isAuthorized("Document", "deleteDocument") && ( - + )}
@@ -392,6 +392,7 @@ function DocumentRow({ onCheck: () => void; hasAnyAction: boolean; }) { + const { isAuthorized } = use(PermissionsContext); const document = useFragment( rowFragment, documentKey @@ -465,7 +466,7 @@ function DocumentRow({ {hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx b/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx index 2bcab9a26..f883e836c 100644 --- a/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx +++ b/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx @@ -8,7 +8,7 @@ import { Spinner, } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; -import { Suspense, useState, useEffect, useRef } from "react"; +import { Suspense, useState, useEffect, useRef, use } from "react"; import type { ItemOf, NodeOf } from "/types"; import { graphql, useFragment, useRefetchableFragment } from "react-relay"; import { usePeople } from "/hooks/graph/PeopleGraph.ts"; @@ -20,7 +20,7 @@ import { useOutletContext } from "react-router"; import type { DocumentSignaturesTab_signature$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts"; import type { DocumentSignaturesTab_version$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts"; import type { DocumentSignaturesTabRefetchQuery } from "./__generated__/DocumentSignaturesTabRefetchQuery.graphql"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Version = NodeOf; @@ -246,6 +246,7 @@ function SignatureItem(props: { }) { const signature = useFragment(signatureFragment, props.signature); const { __, dateTimeFormat } = useTranslate(); + const { isAuthorized } = use(PermissionsContext); const [requestSignature, isSendingRequest] = useMutationWithToasts( requestSignatureMutation, { @@ -275,26 +276,26 @@ function SignatureItem(props: { {props.signable && ( - - - + }); + }} + > + {__("Request signature")} + + ) )} ); @@ -331,25 +332,25 @@ function SignatureItem(props: { {__("Signed")} ) : ( - - - + }); + }} + > + {__("Cancel request")} + + ) )} ); diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx index a3bf28fe7..088790f32 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx @@ -27,7 +27,8 @@ import { promisifyMutation } from "@probo/helpers"; import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql"; import { frameworkControlNodeQuery } from "/hooks/graph/FrameworkGraph"; import type { FrameworkDetailPageFragment$data } from "./__generated__/FrameworkDetailPageFragment.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const attachMeasureMutation = graphql` mutation FrameworkControlPageAttachMutation( @@ -166,7 +167,7 @@ export default function FrameworkControlPage({ queryRef }: Props) { const organizationId = useOrganizationId(); const confirm = useConfirm(); const navigate = useNavigate(); - + const { isAuthorized } = use(PermissionsContext); const [detachMeasure, isDetachingMeasure] = useMutation(detachMeasureMutation); const [attachMeasure, isAttachingMeasure] = useMutation(attachMeasureMutation); const [detachDocument, isDetachingDocument] = useMutation(detachDocumentMutation); @@ -236,7 +237,7 @@ export default function FrameworkControlPage({ queryRef }: Props) {
- + {isAuthorized("Control", "updateControl") && ( - - + )} + {isAuthorized("Control", "deleteControl") && ( - + )}
diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkDetailPage.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkDetailPage.tsx index c334b3057..60b8ce097 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworkDetailPage.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworkDetailPage.tsx @@ -31,7 +31,8 @@ import type { FrameworkDetailPageExportFrameworkMutation } from "./__generated__ import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog"; import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const frameworkDetailFragment = graphql` fragment FrameworkDetailPageFragment on Framework { @@ -103,7 +104,7 @@ export default function FrameworkDetailPage(props: Props) { framework, ConnectionHandler.getConnectionID(organizationId, connectionListKey)! ); - + const { isAuthorized } = use(PermissionsContext); const [generateFrameworkStateOfApplicability] = useMutationWithToasts( generateFrameworkStateOfApplicabilityMutation, @@ -150,7 +151,7 @@ export default function FrameworkDetailPage(props: Props) { } > - + {isAuthorized("Framework", "updateFramework") && ( - + )} {__("Export Framework")} - + {isAuthorized("Framework", "deleteFramework") && ( {__("Delete")} - + )}
@@ -216,7 +217,7 @@ export default function FrameworkDetailPage(props: Props) { active={selectedControl?.id === control.id} /> ))} - + {isAuthorized("Organization", "createControl") && ( - + )}
diff --git a/apps/console/src/pages/organizations/frameworks/FrameworksPage.tsx b/apps/console/src/pages/organizations/frameworks/FrameworksPage.tsx index c6e13dc9b..cd2298104 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworksPage.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworksPage.tsx @@ -26,11 +26,10 @@ import { import { Link } from "react-router"; import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { useState, type ChangeEventHandler } from "react"; +import { useState, type ChangeEventHandler, use } from "react"; import { FrameworkLogo } from "/components/FrameworkLogo"; import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -54,6 +53,7 @@ const importFrameworkMutation = graphql` export default function FrameworksPage(props: Props) { const { __ } = useTranslate(); + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Frameworks")); const data = usePreloadedQuery(frameworksQuery, props.queryRef); const connectionId = data.organization.frameworks!.__id; @@ -120,8 +120,8 @@ export default function FrameworksPage(props: Props) { const isLoading = isUploading || isImporting; - const hasAnyAction = isAuthorized(data.organization.id!, "Framework", "updateFramework") || - isAuthorized(data.organization.id!, "Framework", "deleteFramework"); + const hasAnyAction = isAuthorized("Framework", "updateFramework") || + isAuthorized("Framework", "deleteFramework"); return (
@@ -134,20 +134,22 @@ export default function FrameworksPage(props: Props) { title={__("Frameworks")} description={__("Manage your compliance frameworks")} > - - - {__("Import")} - - - + {isAuthorized("Organization", "createFramework") && ( + <> + + {__("Import")} + + + + )}
@@ -182,6 +184,7 @@ type FrameworkCardProps = { function FrameworkCard(props: FrameworkCardProps) { const framework = useFragment(frameworkCardFragment, props.framework); + const { isAuthorized } = use(PermissionsContext); const deleteFramework = useDeleteFrameworkMutation( framework, props.connectionId @@ -200,7 +203,7 @@ function FrameworkCard(props: FrameworkCardProps) { {props.hasAnyAction && ( - + {isAuthorized("Framework", "updateFramework") && ( { @@ -209,8 +212,8 @@ function FrameworkCard(props: FrameworkCardProps) { > {__("Edit")} - - + )} + {isAuthorized("Framework", "deleteFramework") && ( deleteFramework()} @@ -218,7 +221,7 @@ function FrameworkCard(props: FrameworkCardProps) { > {__("Delete")} - + )} )}
diff --git a/apps/console/src/pages/organizations/measures/MeasureDetailPage.tsx b/apps/console/src/pages/organizations/measures/MeasureDetailPage.tsx index 00b94e6bc..43ca233df 100644 --- a/apps/console/src/pages/organizations/measures/MeasureDetailPage.tsx +++ b/apps/console/src/pages/organizations/measures/MeasureDetailPage.tsx @@ -43,7 +43,8 @@ import { sprintf, } from "@probo/helpers"; import MeasureFormDialog from "./dialog/MeasureFormDialog"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -59,7 +60,7 @@ export default function MeasureDetailPage(props: Props) { const navigate = useNavigate(); const confirm = useConfirm(); const [updateMeasure, isUpdating] = useUpdateMeasure(); - + const { isAuthorized } = use(PermissionsContext); if (!measureId) { throw new Error( "Cannot load measure detail page without measureId parameter", @@ -136,33 +137,35 @@ export default function MeasureDetailPage(props: Props) { /> - - - - - - + {isAuthorized("Measure", "updateMeasure") && ( + <> + + + + + + )} - + {isAuthorized("Measure", "deleteMeasure") && ( {__("Delete")} - + )} diff --git a/apps/console/src/pages/organizations/measures/MeasuresPage.tsx b/apps/console/src/pages/organizations/measures/MeasuresPage.tsx index 601742c32..1932f76e9 100644 --- a/apps/console/src/pages/organizations/measures/MeasuresPage.tsx +++ b/apps/console/src/pages/organizations/measures/MeasuresPage.tsx @@ -40,7 +40,7 @@ import type { MeasuresPageFragment$key, } from "./__generated__/MeasuresPageFragment.graphql"; import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers"; -import { useMemo, useRef, useState, type ChangeEventHandler } from "react"; +import { useMemo, useRef, useState, type ChangeEventHandler, use } from "react"; import type { NodeOf } from "/types"; import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; @@ -48,8 +48,7 @@ import { useOrganizationId } from "/hooks/useOrganizationId"; import { Link, useParams } from "react-router"; import MeasureFormDialog from "./dialog/MeasureFormDialog"; import { usePageTitle } from "@probo/hooks"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -92,6 +91,7 @@ const importMeasuresMutation = graphql` export default function MeasuresPage(props: Props) { const { __ } = useTranslate(); + const { isAuthorized } = use(PermissionsContext); const organization = usePreloadedQuery( measuresQuery, props.queryRef @@ -115,8 +115,8 @@ export default function MeasuresPage(props: Props) { const importFileRef = useRef(null); usePageTitle(__("Measures")); - const hasAnyAction = isAuthorized(organization.id, "Measure", "updateMeasure") || - isAuthorized(organization.id, "Measure", "deleteMeasure"); + const hasAnyAction = isAuthorized("Measure", "updateMeasure") || + isAuthorized("Measure", "deleteMeasure"); const handleImport: ChangeEventHandler = (event) => { const file = event.target.files?.[0]; @@ -148,21 +148,23 @@ export default function MeasuresPage(props: Props) { "Measures are actions taken to reduce the risk. Add them to track their implementation status." )} > - - - {__("Import")} - - - - - + {isAuthorized("Organization", "createMeasure") && ( + <> + + {__("Import")} + + + + + + )} {objectKeys(measuresPerCategory) @@ -269,6 +271,7 @@ function MeasureRow(props: MeasureRowProps) { const [deleteMeasure, isDeleting] = useDeleteMeasureMutation(); const confirm = useConfirm(); const organizationId = useOrganizationId(); + const { isAuthorized } = use(PermissionsContext); const onDelete = () => { confirm( @@ -306,15 +309,15 @@ function MeasureRow(props: MeasureRowProps) { {props.hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx b/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx index 7256bda80..db1e51131 100644 --- a/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx +++ b/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx @@ -21,12 +21,12 @@ import { UpdateMeetingMinutesDialog, type UpdateMeetingMinutesDialogRef, } from "./dialogs/UpdateMeetingMinutesDialog"; -import { useRef, useState, useEffect } from "react"; +import { useRef, useState, useEffect, use } from "react"; import { meetingNodeQuery, useDeleteMeetingMutation, } from "/hooks/graph/MeetingGraph"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; const meetingFragment = graphql` fragment MeetingDetailPageMeetingFragment on Meeting { @@ -54,6 +54,7 @@ export default function MeetingDetailPage(props: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); const navigate = useNavigate(); + const { isAuthorized } = use(PermissionsContext); if (!meeting) { return
{__("Meeting not found")}
; @@ -74,14 +75,14 @@ export default function MeetingDetailPage(props: Props) { } try { - const updateAuth = isAuthorized(organizationId, "Meeting", "updateMeeting"); + const updateAuth = isAuthorized("Meeting", "updateMeeting"); setCanUpdate(updateAuth); } catch (promise) { if (promise instanceof Promise) { promise .then(() => { try { - const updateAuth = isAuthorized(organizationId, "Meeting", "updateMeeting"); + const updateAuth = isAuthorized("Meeting", "updateMeeting"); setCanUpdate(updateAuth); } catch { setCanUpdate(false); @@ -96,14 +97,14 @@ export default function MeetingDetailPage(props: Props) { } try { - const deleteAuth = isAuthorized(organizationId, "Meeting", "deleteMeeting"); + const deleteAuth = isAuthorized("Meeting", "deleteMeeting"); setCanDelete(deleteAuth); } catch (promise) { if (promise instanceof Promise) { promise .then(() => { try { - const deleteAuth = isAuthorized(organizationId, "Meeting", "deleteMeeting"); + const deleteAuth = isAuthorized("Meeting", "deleteMeeting"); setCanDelete(deleteAuth); } catch { setCanDelete(false); diff --git a/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx b/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx index c50456f97..abfef2c4e 100644 --- a/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx +++ b/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx @@ -42,7 +42,8 @@ import { Link } from "react-router"; import { useState, useEffect, useRef } from "react"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import type { MeetingsPage_UpdateSummaryMutation } from "./__generated__/MeetingsPage_UpdateSummaryMutation.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const meetingsFragment = graphql` fragment MeetingsPageListFragment on Organization @@ -85,7 +86,7 @@ type Props = { export default function MeetingsPage(props: Props) { const { __ } = useTranslate(); - + const { isAuthorized } = use(PermissionsContext); const organization = usePreloadedQuery( meetingsQuery, props.queryRef @@ -218,7 +219,7 @@ export default function MeetingsPage(props: Props) {

{__("Summary")}

- + {isAuthorized("Meeting", "updateMeeting") && ( - + )}
{displayedSummary ? ( @@ -248,11 +249,11 @@ export default function MeetingsPage(props: Props) { "Track and manage your organization's meetings and their minutes." )} > - + {isAuthorized("Organization", "createMeeting") && ( - + )} {meetingNodes.length > 0 ? ( @@ -320,7 +321,7 @@ function MeetingRow({ const { __ } = useTranslate(); const [deleteMeeting] = useDeleteMeetingMutation(); const confirm = useConfirm(); - + const { isAuthorized } = use(PermissionsContext); const handleDelete = () => { confirm( () => @@ -370,7 +371,7 @@ function MeetingRow({ )} - + {isAuthorized("Meeting", "deleteMeeting") && (
- + )} ); } diff --git a/apps/console/src/pages/organizations/nonconformities/NonconformitiesPage.tsx b/apps/console/src/pages/organizations/nonconformities/NonconformitiesPage.tsx index 8dfd216cc..911eb5e34 100644 --- a/apps/console/src/pages/organizations/nonconformities/NonconformitiesPage.tsx +++ b/apps/console/src/pages/organizations/nonconformities/NonconformitiesPage.tsx @@ -31,13 +31,13 @@ import { deleteNonconformityMutation, NonconformitiesConnectionKey } from "../.. import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers"; import { SnapshotBanner } from "/components/SnapshotBanner"; import { useParams } from "react-router"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; import type { NonconformitiesPageQuery } from "./__generated__/NonconformitiesPageQuery.graphql"; import type { NonconformitiesPageFragment$key, NonconformitiesPageFragment$data, } from "./__generated__/NonconformitiesPageFragment.graphql"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Nonconformity = NonconformitiesPageFragment$data['nonconformities']['edges'][number]['node']; @@ -103,6 +103,7 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Nonconformities")); @@ -132,8 +133,8 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro const nonconformities: Nonconformity[] = nonconformitiesData?.nonconformities?.edges?.map((edge) => edge.node) ?? []; const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "Nonconformity", "updateNonconformity") || - isAuthorized(organizationId, "Nonconformity", "deleteNonconformity") + isAuthorized("Nonconformity", "updateNonconformity") || + isAuthorized("Nonconformity", "deleteNonconformity") ); return ( @@ -148,11 +149,11 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro )} > {!isSnapshotMode && ( - + isAuthorized("Organization", "createNonconformity") && ( - + ) )} @@ -231,7 +232,7 @@ function NonconformityRow({ const { __ } = useTranslate(); const confirm = useConfirm(); const [deleteNonconformity] = useMutation(deleteNonconformityMutation); - + const { isAuthorized } = use(PermissionsContext); const nonconformityDetailUrl = isSnapshotMode ? `/organizations/${organizationId}/snapshots/${snapshotId}/nonconformities/${nonconformity.id}` @@ -298,7 +299,7 @@ function NonconformityRow({ {hasAnyAction && ( )} diff --git a/apps/console/src/pages/organizations/nonconformities/NonconformityDetailsPage.tsx b/apps/console/src/pages/organizations/nonconformities/NonconformityDetailsPage.tsx index bf10a4935..99996778d 100644 --- a/apps/console/src/pages/organizations/nonconformities/NonconformityDetailsPage.tsx +++ b/apps/console/src/pages/organizations/nonconformities/NonconformityDetailsPage.tsx @@ -34,7 +34,8 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema"; import z from "zod"; import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency, getStatusOptions, formatError, type GraphQLError } from "@probo/helpers"; import type { NonconformityGraphNodeQuery } from "/hooks/graph/__generated__/NonconformityGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; const updateNonconformitySchema = z.object({ referenceId: z.string().min(1, "Reference ID is required"), @@ -63,6 +64,7 @@ export default function NonconformityDetailsPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); validateSnapshotConsistency(nonconformity, snapshotId); @@ -162,7 +164,7 @@ export default function NonconformityDetailsPage(props: Props) { {!isSnapshotMode && ( - + {isAuthorized("Nonconformity", "deleteNonconformity") && ( {__("Delete")} - + )} )} @@ -279,11 +281,11 @@ export default function NonconformityDetailsPage(props: Props) {
{formState.isDirty && !isSnapshotMode && ( - + isAuthorized("Nonconformity", "updateNonconformity") && ( - + ) )}
diff --git a/apps/console/src/pages/organizations/obligations/ObligationDetailsPage.tsx b/apps/console/src/pages/organizations/obligations/ObligationDetailsPage.tsx index 5b272e29c..7e9521ef6 100644 --- a/apps/console/src/pages/organizations/obligations/ObligationDetailsPage.tsx +++ b/apps/console/src/pages/organizations/obligations/ObligationDetailsPage.tsx @@ -35,7 +35,8 @@ import z from "zod"; import { getObligationStatusVariant, getObligationStatusLabel, formatDatetime, getObligationStatusOptions, validateSnapshotConsistency } from "@probo/helpers"; import { SnapshotBanner } from "/components/SnapshotBanner"; import type { ObligationGraphNodeQuery } from "/hooks/graph/__generated__/ObligationGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const updateObligationSchema = z.object({ area: z.string().optional(), @@ -61,6 +62,7 @@ export default function ObligationDetailsPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); if (!obligation) { return
{__("Obligation not found")}
; @@ -157,13 +159,13 @@ export default function ObligationDetailsPage(props: Props) { {!isSnapshotMode && ( - + isAuthorized("Obligation", "deleteObligation") && ( {__("Delete")} - + ) )} @@ -301,14 +303,14 @@ export default function ObligationDetailsPage(props: Props) { {!isSnapshotMode && (
- + {isAuthorized("Obligation", "updateObligation") && ( - + )}
)} diff --git a/apps/console/src/pages/organizations/obligations/ObligationsPage.tsx b/apps/console/src/pages/organizations/obligations/ObligationsPage.tsx index c423f20db..8c875f41b 100644 --- a/apps/console/src/pages/organizations/obligations/ObligationsPage.tsx +++ b/apps/console/src/pages/organizations/obligations/ObligationsPage.tsx @@ -31,12 +31,12 @@ import { deleteObligationMutation } from "../../../hooks/graph/ObligationGraph"; import { promisifyMutation, getObligationStatusVariant, getObligationStatusLabel, formatDate } from "@probo/helpers"; import { SnapshotBanner } from "/components/SnapshotBanner"; import type { ObligationsPageQuery } from "./__generated__/ObligationsPageQuery.graphql"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; import type { ObligationsPageFragment$key, ObligationsPageFragment$data, } from "./__generated__/ObligationsPageFragment.graphql"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Obligation = ObligationsPageFragment$data['obligations']['edges'][number]['node']; @@ -95,6 +95,7 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Obligations")); @@ -120,8 +121,8 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) { const obligations: Obligation[] = obligationsData?.obligations?.edges?.map((edge) => edge.node) ?? []; const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "Obligation", "updateObligation") || - isAuthorized(organizationId, "Obligation", "deleteObligation") + isAuthorized("Obligation", "updateObligation") || + isAuthorized("Obligation", "deleteObligation") ); return ( @@ -136,11 +137,11 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) { )} > {!snapshotId && ( - + isAuthorized("Organization", "createObligation") && ( - + ) )} @@ -214,7 +215,7 @@ function ObligationRow({ const [deleteObligation] = useMutation(deleteObligationMutation); const confirm = useConfirm(); const isSnapshotMode = Boolean(snapshotId); - + const { isAuthorized } = use(PermissionsContext); const handleDelete = () => { confirm( @@ -261,7 +262,7 @@ function ObligationRow({ {hasAnyAction && ( )} diff --git a/apps/console/src/pages/organizations/people/PeopleDetailPage.tsx b/apps/console/src/pages/organizations/people/PeopleDetailPage.tsx index 1ac7b8c72..6f8a9a601 100644 --- a/apps/console/src/pages/organizations/people/PeopleDetailPage.tsx +++ b/apps/console/src/pages/organizations/people/PeopleDetailPage.tsx @@ -21,7 +21,8 @@ import { import { useTranslate } from "@probo/i18n"; import { useOrganizationId } from "/hooks/useOrganizationId"; import { Outlet } from "react-router"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -32,6 +33,7 @@ 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) @@ -55,7 +57,7 @@ export default function PeopleDetailPage(props: Props) {
{people.fullName}
- + {isAuthorized("People", "deletePeople") && ( - + )} diff --git a/apps/console/src/pages/organizations/people/PeopleListPage.tsx b/apps/console/src/pages/organizations/people/PeopleListPage.tsx index 0bb59199d..ba7f9b509 100644 --- a/apps/console/src/pages/organizations/people/PeopleListPage.tsx +++ b/apps/console/src/pages/organizations/people/PeopleListPage.tsx @@ -23,8 +23,8 @@ import { usePageTitle } from "@probo/hooks"; import { getRole } from "@probo/helpers"; import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog"; import { useOrganizationId } from "/hooks/useOrganizationId"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; type People = NodeOf; @@ -42,14 +42,14 @@ export default function PeopleListPage({ queryRef: PreloadedQuery; }) { const { __ } = useTranslate(); - const organizationId = useOrganizationId(); + const { isAuthorized } = use(PermissionsContext); const { people, refetch, connectionId, hasNext, loadNext, isLoadingNext } = usePeopleQuery(queryRef); usePageTitle(__("Members")); - const hasAnyAction = isAuthorized(organizationId, "People", "updatePeople") || - isAuthorized(organizationId, "People", "deletePeople"); + const hasAnyAction = isAuthorized("People", "updatePeople") || + isAuthorized("People", "deletePeople"); return (
@@ -59,11 +59,11 @@ export default function PeopleListPage({ "Keep track of your company's workforce and their progress towards completing tasks assigned to them." )} > - + {isAuthorized("Organization", "createPeople") && ( - + )} - + {isAuthorized("People", "deletePeople") && ( {__("Delete")} - + )} )} diff --git a/apps/console/src/pages/organizations/people/tabs/PeopleProfileTab.tsx b/apps/console/src/pages/organizations/people/tabs/PeopleProfileTab.tsx index 61b9b3cd1..bff86930f 100644 --- a/apps/console/src/pages/organizations/people/tabs/PeopleProfileTab.tsx +++ b/apps/console/src/pages/organizations/people/tabs/PeopleProfileTab.tsx @@ -9,7 +9,8 @@ import type { PeopleGraphUpdateMutation } from "/hooks/graph/__generated__/Peopl import { updatePeopleMutation } from "/hooks/graph/PeopleGraph"; import { Button, Card, Field, Input } from "@probo/ui"; import { EmailsField } from "/components/form/EmailsField"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; const schema = z.object({ fullName: z.string().min(1), @@ -49,7 +50,7 @@ export default function PeopleProfileTab() { errorMessage: __("Failed to update member"), } ); - + const { isAuthorized } = use(PermissionsContext); const onSubmit = handleSubmit((data) => { const input = { id: people.id!, @@ -95,11 +96,11 @@ export default function PeopleProfileTab() {
{formState.isDirty && ( - + isAuthorized("People", "updatePeople") && ( - + ) )}
diff --git a/apps/console/src/pages/organizations/processingActivities/ProcessingActivitiesPage.tsx b/apps/console/src/pages/organizations/processingActivities/ProcessingActivitiesPage.tsx index 7b3388aa1..a17a9cea1 100644 --- a/apps/console/src/pages/organizations/processingActivities/ProcessingActivitiesPage.tsx +++ b/apps/console/src/pages/organizations/processingActivities/ProcessingActivitiesPage.tsx @@ -32,14 +32,14 @@ import { CreateProcessingActivityDialog } from "./dialogs/CreateProcessingActivi import { deleteProcessingActivityMutation, ProcessingActivitiesConnectionKey } from "../../../hooks/graph/ProcessingActivityGraph"; import { sprintf, promisifyMutation } from "@probo/helpers"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; import type { NodeOf } from "/types"; import type { ProcessingActivitiesPageQuery } from "./__generated__/ProcessingActivitiesPageQuery.graphql"; import type { ProcessingActivitiesPageFragment$key, ProcessingActivitiesPageFragment$data, } from "./__generated__/ProcessingActivitiesPageFragment.graphql"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; interface ProcessingActivitiesPageProps { queryRef: PreloadedQuery; @@ -91,7 +91,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); - + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Processing Activities")); @@ -129,8 +129,8 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit const activities = data?.processingActivities?.edges?.map((edge) => edge.node) ?? []; const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "ProcessingActivity", "updateProcessingActivity") || - isAuthorized(organizationId, "ProcessingActivity", "deleteProcessingActivity") + isAuthorized("ProcessingActivity", "updateProcessingActivity") || + isAuthorized("ProcessingActivity", "deleteProcessingActivity") ); return ( @@ -140,7 +140,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit )} {!isSnapshotMode && ( - + isAuthorized("Organization", "createProcessingActivity") && ( - + ) )} @@ -222,6 +222,7 @@ function ActivityRow({ const isSnapshotMode = Boolean(snapshotId); const [deleteActivity] = useMutation(deleteProcessingActivityMutation); const confirm = useConfirm(); + const { isAuthorized } = use(PermissionsContext); const handleDelete = () => { confirm( @@ -270,7 +271,7 @@ function ActivityRow({ {hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/processingActivities/ProcessingActivityDetailsPage.tsx b/apps/console/src/pages/organizations/processingActivities/ProcessingActivityDetailsPage.tsx index bbee80724..97758c0f7 100644 --- a/apps/console/src/pages/organizations/processingActivities/ProcessingActivityDetailsPage.tsx +++ b/apps/console/src/pages/organizations/processingActivities/ProcessingActivityDetailsPage.tsx @@ -41,7 +41,8 @@ import { } from "../../../components/form/ProcessingActivityEnumOptions"; import type { ProcessingActivityGraphNodeQuery } from "/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; const updateProcessingActivitySchema = z.object({ name: z.string().min(1, "Name is required"), @@ -74,6 +75,7 @@ export default function ProcessingActivityDetailsPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); if (!activity) { return
{__("Processing activity not found")}
; @@ -171,13 +173,13 @@ export default function ProcessingActivityDetailsPage(props: Props) { ]} /> {!isSnapshotMode && ( - + isAuthorized("ProcessingActivity", "deleteProcessingActivity") && ( {__("Delete")} - + ) )} diff --git a/apps/console/src/pages/organizations/risks/RiskDetailPage.tsx b/apps/console/src/pages/organizations/risks/RiskDetailPage.tsx index 4f08c4d05..7b01e24d8 100644 --- a/apps/console/src/pages/organizations/risks/RiskDetailPage.tsx +++ b/apps/console/src/pages/organizations/risks/RiskDetailPage.tsx @@ -30,7 +30,8 @@ import { } from "/hooks/graph/RiskGraph"; import type { RiskGraphNodeQuery } from "/hooks/graph/__generated__/RiskGraphNodeQuery.graphql"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -41,6 +42,7 @@ export default function RiskDetailPage(props: Props) { const organizationId = useOrganizationId(); const navigate = useNavigate(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); if (!riskId) { throw new Error("Cannot load risk detail page without riskId parameter"); @@ -121,7 +123,7 @@ export default function RiskDetailPage(props: Props) { /> {!isSnapshotMode && (
- + {isAuthorized("Risk", "updateRisk") && ( @@ -130,8 +132,8 @@ export default function RiskDetailPage(props: Props) { } risk={{ id: riskId, ...risk }} /> - - + )} + {isAuthorized("Risk", "deleteRisk") && ( - + )}
)} diff --git a/apps/console/src/pages/organizations/risks/RisksPage.tsx b/apps/console/src/pages/organizations/risks/RisksPage.tsx index 3675cbfed..f28fa9287 100644 --- a/apps/console/src/pages/organizations/risks/RisksPage.tsx +++ b/apps/console/src/pages/organizations/risks/RisksPage.tsx @@ -29,8 +29,8 @@ import type { RiskGraphListQuery } from "/hooks/graph/__generated__/RiskGraphLis import type { RiskGraphFragment$data } from "/hooks/graph/__generated__/RiskGraphFragment.graphql"; import { useParams } from "react-router"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -41,6 +41,7 @@ export default function RisksPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); const { connectionId, risks, ...pagination } = useRisksQuery(props.queryRef); @@ -57,8 +58,8 @@ export default function RisksPage(props: Props) { usePageTitle(__("Risks")); const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "Risk", "updateRisk") || - isAuthorized(organizationId, "Risk", "deleteRisk") + isAuthorized("Risk", "updateRisk") || + isAuthorized("Risk", "deleteRisk") ); return ( @@ -71,7 +72,7 @@ export default function RisksPage(props: Props) { )} > {!isSnapshotMode && ( - + isAuthorized("Organization", "createRisk") && ( { @@ -79,7 +80,7 @@ export default function RisksPage(props: Props) { }} trigger={} /> - + ) )} @@ -143,6 +144,7 @@ function RiskRow(props: RowProps) { const isSnapshotMode = Boolean(snapshotId); const [deleteRisk] = useDeleteRiskMutation(); const confirm = useConfirm(); + const { isAuthorized } = use(PermissionsContext); const onDelete = () => { confirm( () => @@ -194,16 +196,16 @@ function RiskRow(props: RowProps) { {props.hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/settings/GeneralSettingsTab.tsx b/apps/console/src/pages/organizations/settings/GeneralSettingsTab.tsx index cf0c53414..ae23ebb7d 100644 --- a/apps/console/src/pages/organizations/settings/GeneralSettingsTab.tsx +++ b/apps/console/src/pages/organizations/settings/GeneralSettingsTab.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect, type ChangeEventHandler } from "react"; +import { useState, useRef, useEffect, type ChangeEventHandler, use } from "react"; import { useOutletContext, useNavigate } from "react-router"; import { useFragment, graphql } from "react-relay"; import { @@ -23,7 +23,7 @@ import { z } from "zod"; import type { GeneralSettingsTabFragment$key } from "./__generated__/GeneralSettingsTabFragment.graphql"; import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog"; import { useDeleteOrganizationMutation } from "/hooks/graph/OrganizationGraph"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; const generalSettingsTabFragment = graphql` fragment GeneralSettingsTabFragment on Organization { @@ -90,9 +90,10 @@ export default function GeneralSettingsTab() { const { organization: organizationKey } = useOutletContext(); const organization = useFragment(generalSettingsTabFragment, organizationKey); const deleteDialogRef = useDialogRef(); + const { isAuthorized } = use(PermissionsContext); - const canUpdate = isAuthorized(organization.id, "Organization", "updateOrganization"); - const canDelete = isAuthorized(organization.id, "Organization", "deleteOrganization"); + const canUpdate = isAuthorized("Organization", "updateOrganization"); + const canDelete = isAuthorized("Organization", "deleteOrganization"); const [logoPreview, setLogoPreview] = useState(null); const [horizontalLogoPreview, setHorizontalLogoPreview] = useState< diff --git a/apps/console/src/pages/organizations/settings/MembersSettingsTab.tsx b/apps/console/src/pages/organizations/settings/MembersSettingsTab.tsx index 589d64f11..a3b099fdd 100644 --- a/apps/console/src/pages/organizations/settings/MembersSettingsTab.tsx +++ b/apps/console/src/pages/organizations/settings/MembersSettingsTab.tsx @@ -1,4 +1,4 @@ -import { useState, Suspense } from "react"; +import { useState, Suspense, use } from "react"; import { useOutletContext } from "react-router"; import { usePaginationFragment, graphql } from "react-relay"; import { @@ -29,11 +29,9 @@ import { useTranslate } from "@probo/i18n"; import { SortableTable, SortableTh } from "/components/SortableTable"; import { InviteUserDialog } from "/components/organizations/InviteUserDialog"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { sprintf } from "@probo/helpers"; +import { getAssignableRoles, sprintf } from "@probo/helpers"; import clsx from "clsx"; import type { NodeOf } from "/types"; -import { Authorized } from "/permissions"; -import { getAssignableRoles, getUserRole } from "/permissions"; import type { MembersSettingsTabMembershipsFragment$data, MembersSettingsTabMembershipsFragment$key @@ -42,6 +40,7 @@ import type { MembersSettingsTabInvitationsFragment$data, MembersSettingsTabInvitationsFragment$key } from "./__generated__/MembersSettingsTabInvitationsFragment.graphql"; +import { PermissionsContext } from "/providers/PermissionsContext"; const paginatedMembershipsFragment = graphql` fragment MembersSettingsTabMembershipsFragment on Organization @@ -153,7 +152,7 @@ type OutletContext = { export default function MembersSettingsTab() { const { __ } = useTranslate(); const { organization: organizationKey } = useOutletContext(); - + const { isAuthorized } = use(PermissionsContext); const membershipsPagination = usePaginationFragment( paginatedMembershipsFragment, organizationKey as MembersSettingsTabMembershipsFragment$key @@ -180,14 +179,14 @@ export default function MembersSettingsTab() {

{__("Workspace members")}

- + {isAuthorized("Organization", "inviteUser") && ( - + )}
@@ -314,6 +313,7 @@ function InvitationRow(props: { }) { const { __ } = useTranslate(); const confirm = useConfirm(); + const { isAuthorized } = use(PermissionsContext); const [deleteInvitation, isDeleting] = useMutationWithToasts( deleteInvitationMutation, { @@ -376,7 +376,7 @@ function InvitationRow(props: { {isDeleting ? ( ) : ( - + isAuthorized("Invitation", "deleteInvitation") && (
@@ -399,9 +399,9 @@ function MembershipRowContent(props: { onRefetch: () => void; }) { const { __ } = useTranslate(); - const availableRoles = getAssignableRoles(props.organizationId); - const currentUserRole = getUserRole(props.organizationId); - + const { role: currentUserRole } = use(PermissionsContext); + const availableRoles = getAssignableRoles(currentUserRole); + const { isAuthorized } = use(PermissionsContext); const [removeMember, isRemoving] = useMutationWithToasts(removeMemberMutation, { successMessage: __("Member removed successfully"), errorMessage: __("Failed to remove member"), @@ -494,31 +494,27 @@ function MembershipRowContent(props: { className="flex gap-2 justify-end" onClick={(e) => e.stopPropagation()} > - - {canEditThisRole && ( - - + )} {configs.length === 0 ? ( @@ -388,11 +389,11 @@ export default function SAMLSettingsTab() {

{__("Set up SAML 2.0 single sign-on for your organization by adding a configuration for each email domain.")}

- + {isAuthorized("Organization", "createSAMLConfiguration") && ( - + )} ) : ( @@ -457,7 +458,7 @@ export default function SAMLSettingsTab() {
{config.domainVerified ? ( <> - + {isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && ( - - + )} + {isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && ( - + )} ) : ( <> - + {isAuthorized("Organization", "verifyDomain") && ( - - + )} + {isAuthorized("Organization", "deleteOrganization") && ( - + )} )}
diff --git a/apps/console/src/pages/organizations/snapshots/SnapshotsPage.tsx b/apps/console/src/pages/organizations/snapshots/SnapshotsPage.tsx index 31dc993b4..f16f2ffe1 100644 --- a/apps/console/src/pages/organizations/snapshots/SnapshotsPage.tsx +++ b/apps/console/src/pages/organizations/snapshots/SnapshotsPage.tsx @@ -34,8 +34,8 @@ import type { NodeOf } from "/types"; import SnapshotFormDialog from "./dialog/SnapshotFormDialog"; import { usePageTitle } from "@probo/hooks"; import { useOrganizationId } from "/hooks/useOrganizationId"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; type Props = { queryRef: PreloadedQuery; @@ -71,9 +71,10 @@ export default function SnapshotsPage(props: Props) { ); const connectionId = data.snapshots.__id; const snapshots = data.snapshots.edges.map((edge) => edge.node); + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Snapshots")); - const hasAnyAction = isAuthorized(organizationId, "Snapshot", "deleteSnapshot"); + const hasAnyAction = isAuthorized("Snapshot", "deleteSnapshot"); return (
@@ -83,13 +84,13 @@ export default function SnapshotsPage(props: Props) { "Snapshots capture point-in-time views of your organization's compliance state. Create snapshots to track progress over time." )} > - + {isAuthorized("Organization", "createSnapshot") && ( - + )} {snapshots.length > 0 ? ( @@ -139,7 +140,7 @@ type SnapshotRowProps = { function SnapshotRow(props: SnapshotRowProps) { const { __ } = useTranslate(); const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId); - + const { isAuthorized } = use(PermissionsContext); const typePath = getSnapshotTypeUrlPath(props.snapshot.type); return ( @@ -159,7 +160,7 @@ function SnapshotRow(props: SnapshotRowProps) { {props.hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/tasks/TasksPage.tsx b/apps/console/src/pages/organizations/tasks/TasksPage.tsx index 8565139e4..fd66555dd 100644 --- a/apps/console/src/pages/organizations/tasks/TasksPage.tsx +++ b/apps/console/src/pages/organizations/tasks/TasksPage.tsx @@ -12,7 +12,8 @@ import { tasksQuery } from "/hooks/graph/TaskGraph"; import { usePageTitle } from "@probo/hooks"; import TasksCard from "/components/tasks/TasksCard"; import TaskFormDialog from "/components/tasks/TaskFormDialog"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { use } from "react"; const tasksFragment = graphql` fragment TasksPageFragment on Organization @@ -66,7 +67,7 @@ export default function TasksPage({ queryRef }: Props) { ); const tasks = data.tasks?.edges.map((edge) => edge.node); const connectionId = data.tasks.__id; - + const { isAuthorized } = use(PermissionsContext); usePageTitle(__("Tasks")); return ( @@ -77,11 +78,11 @@ export default function TasksPage({ queryRef }: Props) { "Track your assigned compliance tasks and keep progress on track." )} > - + {isAuthorized("Organization", "createTask") && ( - + )} diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx index ff52368ec..da2443571 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx @@ -24,7 +24,7 @@ import { import { useTranslate } from "@probo/i18n"; import { formatDate } from "@probo/helpers"; import { useOutletContext } from "react-router"; -import { useState, useCallback, useEffect, useRef } from "react"; +import { useState, useCallback, useEffect, useRef, use } from "react"; import { useQueryLoader, usePreloadedQuery } from 'react-relay'; import z from "zod"; import { @@ -36,7 +36,7 @@ import { } from "/hooks/graph/TrustCenterAccessGraph"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; type ContextType = { organization: { @@ -130,7 +130,7 @@ function DocumentAccessesLoader({ export default function TrustCenterAccessTab() { const { __ } = useTranslate(); const { organization } = useOutletContext(); - + const { isAuthorized } = use(PermissionsContext); const inviteSchema = z.object({ name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")), email: z.string().min(1, __("Email is required")).email(__("Please enter a valid email address")), @@ -423,14 +423,14 @@ export default function TrustCenterAccessTab() {

{organization.trustCenter?.id && ( - + {isAuthorized("TrustCenter", "createTrustCenterAccess") && ( - + )} )} @@ -515,22 +515,22 @@ export default function TrustCenterAccessTab() { className="flex gap-2 justify-end" onClick={(e) => e.stopPropagation()} > - + {isAuthorized("TrustCenterAccess", "updateTrustCenterAccess") && (
diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterFilesTab.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterFilesTab.tsx index a2be0edc1..acb783e04 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterFilesTab.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterFilesTab.tsx @@ -13,7 +13,7 @@ import { } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; import { useOutletContext } from "react-router"; -import { useState, useCallback } from "react"; +import { useState, useCallback, use } from "react"; import z from "zod"; import { getTrustCenterVisibilityOptions } from "@probo/helpers"; import { @@ -23,8 +23,8 @@ import { } from "/hooks/graph/TrustCenterFileGraph"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { TrustCenterFilesCard } from "/components/trustCenter/TrustCenterFilesCard"; -import { Authorized } from "/permissions"; import type { TrustCenterFilesCardFragment$key } from "/components/trustCenter/__generated__/TrustCenterFilesCardFragment.graphql"; +import { PermissionsContext } from "/providers/PermissionsContext"; type ContextType = { organization: { @@ -41,7 +41,7 @@ type ContextType = { export default function TrustCenterFilesTab() { const { __ } = useTranslate(); const { organization } = useOutletContext(); - + const { isAuthorized } = use(PermissionsContext); const createSchema = z.object({ name: z.string().min(1, __("Name is required")), category: z.string().min(1, __("Category is required")), @@ -201,11 +201,11 @@ export default function TrustCenterFilesTab() { {__("Upload and manage files for your trust center")}

- + {isAuthorized("Organization", "createTrustCenterFile") && ( - + )} {(isUpdating || isDeleting) && (
diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterOverviewTab.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterOverviewTab.tsx index aa4e4b58e..e452aa334 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterOverviewTab.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterOverviewTab.tsx @@ -11,10 +11,9 @@ import { import { useOutletContext } from "react-router"; import { useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph"; import type { TrustCenterGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql"; -import { useState } from "react"; +import { use, useState } from "react"; import { SlackConnections } from "../../../components/organizations/SlackConnection"; -import { isAuthorized } from "/permissions"; -import { useParams } from "react-router"; +import { PermissionsContext } from "/providers/PermissionsContext"; type ContextType = { organization: TrustCenterGraphQuery$data["organization"]; @@ -24,14 +23,14 @@ export default function TrustCenterOverviewTab() { const { __ } = useTranslate(); const { toast } = useToast(); const { organization } = useOutletContext(); - const { organizationId } = useParams(); + const { isAuthorized } = use(PermissionsContext); const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation(); const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation(); const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation(); const [isActive, setIsActive] = useState(organization.trustCenter?.active || false); - const canUpdateTrustCenter = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false; + const canUpdateTrustCenter = isAuthorized("TrustCenter", "updateTrustCenter"); const handleToggleActive = async (active: boolean) => { if (!organization.trustCenter?.id) { diff --git a/apps/console/src/pages/organizations/vendors/VendorDetailPage.tsx b/apps/console/src/pages/organizations/vendors/VendorDetailPage.tsx index da12e34bc..71323721c 100644 --- a/apps/console/src/pages/organizations/vendors/VendorDetailPage.tsx +++ b/apps/console/src/pages/organizations/vendors/VendorDetailPage.tsx @@ -29,7 +29,8 @@ import { ImportAssessmentDialog } from "./dialogs/ImportAssessmentDialog"; import { complianceReportsFragment } from "./tabs/VendorComplianceTab"; import type { VendorComplianceTabFragment$key } from "./tabs/__generated__/VendorComplianceTabFragment.graphql"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Props = { queryRef: PreloadedQuery; @@ -42,6 +43,7 @@ export default function VendorDetailPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); if (!vendor) { return
{__("Vendor not found")}
; @@ -98,7 +100,7 @@ export default function VendorDetailPage(props: Props) { {__("Assessment From Website")} - + {isAuthorized("Vendor", "deleteVendor") && ( - + )}
)} diff --git a/apps/console/src/pages/organizations/vendors/VendorsPage.tsx b/apps/console/src/pages/organizations/vendors/VendorsPage.tsx index bc7bd8045..ece1dec7c 100644 --- a/apps/console/src/pages/organizations/vendors/VendorsPage.tsx +++ b/apps/console/src/pages/organizations/vendors/VendorsPage.tsx @@ -37,8 +37,8 @@ import type { } from "/hooks/graph/__generated__/VendorGraphPaginatedFragment.graphql"; import { SortableTable, SortableTh } from "/components/SortableTable"; import { SnapshotBanner } from "/components/SnapshotBanner"; -import { Authorized } from "/permissions"; -import { isAuthorized } from "/permissions"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; type Vendor = NodeOf; @@ -51,6 +51,7 @@ export default function VendorsPage(props: Props) { const organizationId = useOrganizationId(); const { snapshotId } = useParams<{ snapshotId?: string }>(); const isSnapshotMode = Boolean(snapshotId); + const { isAuthorized } = use(PermissionsContext); const data = usePreloadedQuery(vendorsQuery, props.queryRef); const pagination = usePaginationFragment( @@ -64,8 +65,8 @@ export default function VendorsPage(props: Props) { usePageTitle(__("Vendors")); const hasAnyAction = !isSnapshotMode && ( - isAuthorized(organizationId, "Vendor", "updateVendor") || - isAuthorized(organizationId, "Vendor", "deleteVendor") + isAuthorized("Vendor", "updateVendor") || + isAuthorized("Vendor", "deleteVendor") ); return ( @@ -78,14 +79,14 @@ export default function VendorsPage(props: Props) { )} > {!isSnapshotMode && ( - + isAuthorized("Organization", "createVendor") && ( - + ) )} @@ -129,7 +130,7 @@ function VendorRow({ const isSnapshotMode = Boolean(snapshotId); const { __ } = useTranslate(); const latestAssessment = vendor.riskAssessments?.edges[0]?.node; - + const { isAuthorized } = use(PermissionsContext); const deleteVendor = useDeleteVendor(vendor, connectionId); const vendorUrl = isSnapshotMode && snapshotId @@ -159,7 +160,7 @@ function VendorRow({ {hasAnyAction && (
)} diff --git a/apps/console/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx b/apps/console/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx index 389ef3f49..e58e8ec27 100644 --- a/apps/console/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx +++ b/apps/console/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx @@ -6,7 +6,7 @@ import { PeopleSelectField } from "/components/form/PeopleSelectField"; import { ControlledField } from "/components/form/ControlledField"; import { CountriesField } from "/components/form/CountriesField"; import { useOrganizationId } from "/hooks/useOrganizationId"; -import { useMemo } from "react"; +import { use, useMemo } from "react"; import { usePageTitle } from "@probo/hooks"; import { downloadFile, formatDate } from "@probo/helpers"; import { useFragment, graphql } from "react-relay"; @@ -20,7 +20,7 @@ import type { useVendorFormFragment$key } from "/hooks/forms/__generated__/useVe import type { VendorOverviewTabBusinessAssociateAgreementFragment$key } from "./__generated__/VendorOverviewTabBusinessAssociateAgreementFragment.graphql"; import type { VendorOverviewTabDataPrivacyAgreementFragment$key } from "./__generated__/VendorOverviewTabDataPrivacyAgreementFragment.graphql"; import type { VendorCategory } from "@probo/vendors"; -import { Authorized } from "/permissions"; +import { PermissionsContext } from "/providers/PermissionsContext"; const vendorBusinessAssociateAgreementFragment = graphql` fragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor { @@ -58,7 +58,7 @@ export default function VendorOverviewTab() { }>(); const { __ } = useTranslate(); - + const { isAuthorized } = use(PermissionsContext); const vendorCategories: { value: VendorCategory; label: string }[] = [ { value: "ANALYTICS", label: __("Analytics") }, { value: "CLOUD_MONITORING", label: __("Cloud Monitoring") }, @@ -396,11 +396,11 @@ export default function VendorOverviewTab() { {/* Submit */} {!isSnapshotMode && (
- + {isAuthorized("Vendor", "updateVendor") && ( - + )}
)} diff --git a/apps/console/src/permissions/Authorized.tsx b/apps/console/src/permissions/Authorized.tsx deleted file mode 100644 index e5e81bfc1..000000000 --- a/apps/console/src/permissions/Authorized.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { type ReactNode } from "react"; -import { useParams } from "react-router"; -import { usePermissions } from "./permissions"; - -type Props = { - entity: string; - action: string; - children: ReactNode; - fallback?: ReactNode; -}; - -/** - * Conditionally render children based on authorization check - * Automatically fetches permissions if not cached - * - * @param entity - The entity name (e.g., "Document", "Organization", "Vendor") - * @param action - The action/field name (e.g., "get", "createDocument", "updateVendor") - * - * @example - * - * - * - * - * @example - * - * - * - * - * @example - * - * - * - */ -export function Authorized({ - entity, - action, - children, - fallback = null, -}: Props) { - const { organizationId } = useParams(); - const { loading, error, isAuthorized } = usePermissions(organizationId || ""); - - if (!organizationId || loading || error || !isAuthorized(entity, action)) { - return fallback; - } - - return children; -} diff --git a/apps/console/src/permissions/index.ts b/apps/console/src/permissions/index.ts deleted file mode 100644 index 783d67c97..000000000 --- a/apps/console/src/permissions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { isAuthorized, getUserRole, getAssignableRoles } from "./permissions"; -export { Authorized } from "./Authorized"; -export type { EntityPermissions } from "./permissions"; diff --git a/apps/console/src/permissions/permissions.ts b/apps/console/src/permissions/permissions.ts deleted file mode 100644 index 4b78b273a..000000000 --- a/apps/console/src/permissions/permissions.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { useEffect, useState } from 'react'; - -// Authorization system that checks permissions from the backend -// Permissions are fetched per organization from /authz/:organizationId/permissions -// Format: { permissions: { "Document": { "node": true, "updateDocument": true }, "Organization": { "createDocument": true } }, role: "ADMIN" } - -export type EntityPermissions = Record>; - -type PermissionsResponse = { - permissions: EntityPermissions; - role: string; -}; - -type PermissionsCache = { - [organizationId: string]: { - permissions: EntityPermissions; - role: string; - }; -}; - -const cache: PermissionsCache = {}; -const pendingRequests: Map> = new Map(); - -/** - * Fetch permissions for the current user's role in the organization - */ -function fetchPermissions(organizationId: string): Promise { - if (cache[organizationId]) { - return Promise.resolve(cache[organizationId]); - } - - const pending = pendingRequests.get(organizationId); - if (pending) { - return pending; - } - - const promise = fetch(`/authz/${encodeURIComponent(organizationId)}/permissions`, { - credentials: 'include', - }) - .then((response) => { - if (!response.ok) { - throw new Error(`Failed to fetch permissions: ${response.statusText}`); - } - return response.json(); - }) - .then((data: PermissionsResponse) => { - if (!data || typeof data.permissions !== 'object' || !data.role) { - throw new Error('Invalid permissions response structure'); - } - - cache[organizationId] = { - permissions: data.permissions, - role: data.role, - }; - pendingRequests.delete(organizationId); - return data; - }) - .catch((error) => { - pendingRequests.delete(organizationId); - throw error; - }); - - pendingRequests.set(organizationId, promise); - return promise; -} - -/** - * React hook to fetch and manage permissions for an organization - * - * @param organizationId - The organization ID - * @returns Object with loading, error, permissions, role, and helper functions - * - * @example - * const { loading, error, permissions, role, isAuthorized, getAssignableRoles } = usePermissions(orgId); - * - * if (loading) return ; - * if (error) return ; - * if (isAuthorized("Document", "updateDocument")) { ... } - */ -export function usePermissions(organizationId: string) { - const [state, setState] = useState<{ - loading: boolean; - error: Error | null; - permissions: EntityPermissions | null; - role: string | null; - }>({ - loading: true, - error: null, - permissions: null, - role: null, - }); - - useEffect(() => { - setState({ loading: true, error: null, permissions: null, role: null }); - - fetchPermissions(organizationId) - .then((data) => { - setState({ - loading: false, - error: null, - permissions: data.permissions, - role: data.role, - }); - }) - .catch((error) => { - setState({ - loading: false, - error, - permissions: null, - role: null, - }); - }); - }, [organizationId]); - - const checkAuthorized = (entity: string, action: string): boolean => { - if (!state.permissions) return false; - - const entityPermissions = state.permissions[entity]; - if (!entityPermissions) return false; - - return entityPermissions[action] === true; - }; - - const getAssignableRolesList = (): string[] => { - if (!state.role) return []; - - if (state.role === "OWNER" || state.role === "FULL") { - return ["OWNER", "ADMIN", "VIEWER"]; - } - - if (state.role === "ADMIN") { - return ["ADMIN", "VIEWER"]; - } - - return []; - }; - - return { - loading: state.loading, - error: state.error, - permissions: state.permissions, - role: state.role, - isAuthorized: checkAuthorized, - getAssignableRoles: getAssignableRolesList, - }; -} - -/** - * Check if the user has permission for an entity and action (synchronous) - * Returns false if permissions are not loaded yet - * - * @param organizationId - The organization ID - * @param entity - The entity name (e.g., "Document", "Organization", "Vendor") - * @param action - The action/field name (e.g., "node", "updateDocument", "createDocument") - * @returns true if the user has permission, false otherwise - * - * @example - * isAuthorized(orgId, "Document", "get") - * isAuthorized(orgId, "Document", "updateDocument") - * isAuthorized(orgId, "Organization", "createDocument") - */ -export function isAuthorized( - organizationId: string, - entity: string, - action: string -): boolean { - const cached = cache[organizationId]; - if (!cached) return false; - - const entityPermissions = cached.permissions[entity]; - if (!entityPermissions) return false; - - return entityPermissions[action] === true; -} - -/** - * Get the current user's role in the organization - * Returns empty string if not loaded yet - * - * @param organizationId - The organization ID - * @returns The user's role (e.g., "OWNER", "ADMIN", "VIEWER", "FULL") - * - * @example - * getUserRole(orgId) // Returns "ADMIN" - */ -export function getUserRole(organizationId: string): string { - const cached = cache[organizationId]; - return cached?.role || ""; -} - -/** - * Get available roles that the current user can assign - * Based on the rule: OWNER and FULL can assign any role, ADMIN can assign ADMIN and VIEWER but not OWNER - * - * @param organizationId - The organization ID - * @returns Array of roles that can be assigned - */ -export function getAssignableRoles(organizationId: string): string[] { - const currentRole = getUserRole(organizationId); - if (!currentRole) return []; - - if (currentRole === "OWNER" || currentRole === "FULL") { - return ["OWNER", "ADMIN", "VIEWER"]; - } - - if (currentRole === "ADMIN") { - return ["ADMIN", "VIEWER"]; - } - - return []; -} diff --git a/apps/console/src/providers/PermissionsContext.tsx b/apps/console/src/providers/PermissionsContext.tsx new file mode 100644 index 000000000..99a61cff0 --- /dev/null +++ b/apps/console/src/providers/PermissionsContext.tsx @@ -0,0 +1,17 @@ +import { createContext } from "react"; +import { Role } from "@probo/helpers"; + +export type PermissionsResponse = { + permissions: Record>; + role: Role; +}; + +type PermissionsContextType = { + isAuthorized: (entity: string, action: string) => boolean; +} & PermissionsResponse; + +export const PermissionsContext = createContext({ + permissions: {}, + role: Role.VIEWER, + isAuthorized: () => false, +}); diff --git a/apps/console/src/providers/PermissionsProvider.tsx b/apps/console/src/providers/PermissionsProvider.tsx index 13efb7247..957d19ed9 100644 --- a/apps/console/src/providers/PermissionsProvider.tsx +++ b/apps/console/src/providers/PermissionsProvider.tsx @@ -1,27 +1,7 @@ import { useSuspenseQuery } from "@tanstack/react-query"; -import { createContext, type PropsWithChildren } from "react"; +import { type PropsWithChildren } from "react"; import { useOrganizationId } from "/hooks/useOrganizationId"; - -enum Role { - OWNER = "OWNER", - ADMIN = "ADMIN", - VIEWER = "VIEWER", -} - -type PermissionsResponse = { - permissions: Record>; - role: Role; -}; - -type PermissionsContextType = { - isAuthorized: (entity: string, action: string) => boolean; -} & PermissionsResponse; - -export const PermissionsContext = createContext({ - permissions: {}, - role: Role.VIEWER, - isAuthorized: () => false, -}); +import { PermissionsContext, type PermissionsResponse } from "./PermissionsContext"; export function PermissionsProvider(props: PropsWithChildren) { const { children } = props; diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index 65fd387aa..b1398827e 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -65,3 +65,4 @@ export { fileType, fileSize } from "./file"; export { formatDatetime, formatDate } from "./date"; export { getLogoUrl, getTrustCenterUrl } from "./trustCenter"; export { formatError, type GraphQLError } from "./error"; +export { Role, getAssignableRoles } from "./roles"; diff --git a/packages/helpers/src/roles.ts b/packages/helpers/src/roles.ts new file mode 100644 index 000000000..72a0d7abb --- /dev/null +++ b/packages/helpers/src/roles.ts @@ -0,0 +1,19 @@ +export enum Role { + OWNER = "OWNER", + ADMIN = "ADMIN", + VIEWER = "VIEWER", +} + +export function getAssignableRoles(currentRole: Role): string[] { + if (!currentRole) return []; + + if (currentRole === "OWNER") { + return ["OWNER", "ADMIN", "VIEWER"]; + } + + if (currentRole === "ADMIN") { + return ["ADMIN", "VIEWER"]; + } + + return []; +} \ No newline at end of file
- + {isAuthorized("Audit", "deleteAudit") && ( {__("Delete")} - + )} - + {isAuthorized("ContinualImprovement", "deleteContinualImprovement") && ( {__("Delete")} - + )} - + {isAuthorized("Datum", "deleteDatum") && ( {__("Delete")} - + )} - + {isAuthorized("Document", "deleteDocument") && ( {__("Delete")} - + )} - + {isAuthorized("Measure", "updateMeasure") && ( dialogRef.current?.open()} > {__("Edit")} - - + )} + {isAuthorized("Measure", "deleteMeasure") && ( {__("Delete")} - + )}
- + {isAuthorized("Nonconformity", "deleteNonconformity") && ( {__("Delete")} - + )} - + {isAuthorized("Obligation", "deleteObligation") && ( {__("Delete")} - + )} - + {isAuthorized("ProcessingActivity", "deleteProcessingActivity") && ( {__("Delete")} - + )} - + {isAuthorized("Risk", "updateRisk") && ( formDialogRef.current?.open()} > {__("Edit")} - + )} - + {isAuthorized("Risk", "deleteRisk") && ( {__("Delete")} - + )} - + {isAuthorized("Snapshot", "deleteSnapshot") && ( {__("Delete")} - + )}
- + {isAuthorized("Vendor", "deleteVendor") && ( {__("Delete")} - + )}