Fix permissions handling with react context
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -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<CustomDomainManagerDeleteMutation>(
|
||||
deleteCustomDomainMutation,
|
||||
@@ -107,11 +108,11 @@ export function CustomDomainManager({
|
||||
)}
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<Authorized entity="Organization" action="createCustomDomain">
|
||||
{isAuthorized("Organization", "createCustomDomain") && (
|
||||
<CreateCustomDomainDialog organizationId={organizationId}>
|
||||
<Button icon={IconPlusLarge}>{__("Add Domain")}</Button>
|
||||
</CreateCustomDomainDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -139,14 +140,14 @@ export function CustomDomainManager({
|
||||
<Button variant="secondary">{__("View Details")}</Button>
|
||||
</DomainDetailsDialog>
|
||||
|
||||
<Authorized entity="CustomDomain" action="deleteCustomDomain">
|
||||
{isAuthorized("CustomDomain", "deleteCustomDomain") && (
|
||||
<DeleteCustomDomainDialog
|
||||
domainName={domain.domain}
|
||||
onConfirm={handleDeleteDomain}
|
||||
>
|
||||
<Button variant="danger">{__("Delete")}</Button>
|
||||
</DeleteCustomDomainDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
</Badge>
|
||||
</div>
|
||||
) : (
|
||||
<Authorized entity="TrustCenter" action="updateTrustCenter">
|
||||
isAuthorized("TrustCenter", "updateTrustCenter") && (
|
||||
<Button variant="secondary" asChild>
|
||||
<a href={getUrl(slackConnection.id)}>{__("Connect")}</a>
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
@@ -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 && (
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Task" action="updateTask">
|
||||
{isAuthorized("Task", "updateTask") && (
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => dialogRef.current?.open()}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
<Authorized entity="Task" action="deleteTask">
|
||||
)}
|
||||
{isAuthorized("Task", "deleteTask") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -248,7 +249,7 @@ function TaskRow(props: TaskRowProps) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<string | null>(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 : '';
|
||||
|
||||
@@ -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<string | null>(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 : '';
|
||||
|
||||
@@ -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<string | null>(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")}
|
||||
/>
|
||||
<Authorized entity="TrustCenterFile" action="updateTrustCenterFile">
|
||||
{isAuthorized("TrustCenterFile", "updateTrustCenterFile") && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconPencil}
|
||||
@@ -215,8 +212,8 @@ function FileRow(props: {
|
||||
disabled={props.disabled}
|
||||
title={__("Edit")}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="TrustCenterFile" action="deleteTrustCenterFile">
|
||||
)}
|
||||
{isAuthorized("TrustCenterFile", "deleteTrustCenterFile") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -224,7 +221,7 @@ function FileRow(props: {
|
||||
disabled={props.disabled}
|
||||
title={__("Delete")}
|
||||
/>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -14,14 +14,14 @@ import {
|
||||
IconPencil,
|
||||
IconArrowLink,
|
||||
} from "@probo/ui";
|
||||
import { type ReactNode, useRef, useState } from "react";
|
||||
import { type ReactNode, useRef, useState, use } from "react";
|
||||
import {
|
||||
useTrustCenterReferences,
|
||||
useUpdateTrustCenterReferenceRankMutation,
|
||||
} from "/hooks/graph/TrustCenterReferenceGraph";
|
||||
import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "./TrustCenterReferenceDialog";
|
||||
import { DeleteTrustCenterReferenceDialog } from "./DeleteTrustCenterReferenceDialog";
|
||||
import { Authorized } from "/permissions";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
type Props = {
|
||||
trustCenterId: string;
|
||||
@@ -41,6 +41,7 @@ type Reference = {
|
||||
|
||||
export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const dialogRef = useRef<TrustCenterReferenceDialogRef>(null);
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
@@ -112,14 +113,14 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||
{__("Showcase your customers and partners on your trust center")}
|
||||
</p>
|
||||
</div>
|
||||
<Authorized entity="TrustCenter" action="createTrustCenterReference">
|
||||
{isAuthorized("TrustCenter", "createTrustCenterReference") && (
|
||||
<Button
|
||||
icon={IconPlusLarge}
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{__("Add Reference")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
@@ -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}
|
||||
/>
|
||||
<Authorized entity="TrustCenterReference" action="updateTrustCenterReference">
|
||||
{isAuthorized("TrustCenterReference", "updateTrustCenterReference") && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconPencil}
|
||||
onClick={onEdit}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="TrustCenterReference" action="deleteTrustCenterReference">
|
||||
)}
|
||||
{isAuthorized("TrustCenterReference", "deleteTrustCenterReference") && (
|
||||
<DeleteTrustCenterReferenceDialog
|
||||
referenceId={reference.id}
|
||||
referenceName={reference.name}
|
||||
@@ -249,7 +251,7 @@ function ReferenceRow({
|
||||
icon={IconTrashCan}
|
||||
/>
|
||||
</DeleteTrustCenterReferenceDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import { useMemo, useState, use } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { isAuthorized } from "/permissions/permissions";
|
||||
import type { TrustCenterVendorsCardFragment$key } from "./__generated__/TrustCenterVendorsCardFragment.graphql";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
const trustCenterVendorFragment = graphql`
|
||||
fragment TrustCenterVendorsCardFragment on Vendor {
|
||||
@@ -49,44 +49,14 @@ type Props<Params> = {
|
||||
|
||||
export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const [limit, setLimit] = useState<number | null>(100);
|
||||
const [canUpdate, setCanUpdate] = useState<boolean>(false);
|
||||
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const canUpdate = isAuthorized("Vendor", "updateVendor");
|
||||
const vendors = useMemo(() => {
|
||||
return limit ? props.vendors.slice(0, limit) : props.vendors;
|
||||
}, [props.vendors, limit]);
|
||||
const showMoreButton = limit !== null && props.vendors.length > limit;
|
||||
|
||||
useEffect(() => {
|
||||
if (!organizationId) {
|
||||
setCanUpdate(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const authorized = isAuthorized(organizationId, "Vendor", "updateVendor");
|
||||
setCanUpdate(authorized);
|
||||
} catch (promise) {
|
||||
if (promise instanceof Promise) {
|
||||
promise
|
||||
.then(() => {
|
||||
try {
|
||||
const authorized = isAuthorized(organizationId, "Vendor", "updateVendor");
|
||||
setCanUpdate(authorized);
|
||||
} catch {
|
||||
setCanUpdate(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setCanUpdate(false);
|
||||
});
|
||||
} else {
|
||||
setCanUpdate(false);
|
||||
}
|
||||
}
|
||||
}, [organizationId]);
|
||||
|
||||
const onToggleVisibility = (vendorId: string, showOnTrustCenter: boolean) => {
|
||||
props.onToggleVisibility({
|
||||
variables: {
|
||||
|
||||
@@ -39,15 +39,14 @@ import {
|
||||
UserDropdown as UserDropdownRoot,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { Suspense, use, useEffect, useState } from "react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import { Link, Navigate, Outlet, useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { MainLayoutQuery as MainLayoutQueryType } from "./__generated__/MainLayoutQuery.graphql";
|
||||
import { PageError } from "/components/PageError";
|
||||
import { Authorized } from "/permissions";
|
||||
import { PermissionsProvider } from "/providers/PermissionsProvider";
|
||||
import { PermissionsContext, PermissionsProvider } from "/providers/PermissionsProvider";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
|
||||
const MainLayoutQuery = graphql`
|
||||
@@ -98,6 +97,7 @@ function MainLayoutContent({
|
||||
prefix: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const data = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
||||
organizationId,
|
||||
});
|
||||
@@ -116,132 +116,132 @@ function MainLayoutContent({
|
||||
}
|
||||
sidebar={
|
||||
<ul className="space-y-[2px]">
|
||||
<Authorized entity="Organization" action="listMeetings">
|
||||
{isAuthorized("Organization", "listMeetings") && (
|
||||
<SidebarItem
|
||||
label={__("Meetings")}
|
||||
icon={IconCalendar1}
|
||||
to={`${prefix}/meetings`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listTasks">
|
||||
)}
|
||||
{isAuthorized("Organization", "listTasks") && (
|
||||
<SidebarItem
|
||||
label={__("Tasks")}
|
||||
icon={IconInboxEmpty}
|
||||
to={`${prefix}/tasks`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listMeasures">
|
||||
)}
|
||||
{isAuthorized("Organization", "listMeasures") && (
|
||||
<SidebarItem
|
||||
label={__("Measures")}
|
||||
icon={IconTodo}
|
||||
to={`${prefix}/measures`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listRisks">
|
||||
)}
|
||||
{isAuthorized("Organization", "listRisks") && (
|
||||
<SidebarItem
|
||||
label={__("Risks")}
|
||||
icon={IconFire3}
|
||||
to={`${prefix}/risks`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listFrameworks">
|
||||
)}
|
||||
{isAuthorized("Organization", "listFrameworks") && (
|
||||
<SidebarItem
|
||||
label={__("Frameworks")}
|
||||
icon={IconBank}
|
||||
to={`${prefix}/frameworks`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listPeople">
|
||||
)}
|
||||
{isAuthorized("Organization", "listPeople") && (
|
||||
<SidebarItem
|
||||
label={__("People")}
|
||||
icon={IconGroup1}
|
||||
to={`${prefix}/people`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listVendors">
|
||||
)}
|
||||
{isAuthorized("Organization", "listVendors") && (
|
||||
<SidebarItem
|
||||
label={__("Vendors")}
|
||||
icon={IconStore}
|
||||
to={`${prefix}/vendors`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listDocuments">
|
||||
)}
|
||||
{isAuthorized("Organization", "listDocuments") && (
|
||||
<SidebarItem
|
||||
label={__("Documents")}
|
||||
icon={IconPageTextLine}
|
||||
to={`${prefix}/documents`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listAssets">
|
||||
)}
|
||||
{isAuthorized("Organization", "listAssets") && (
|
||||
<SidebarItem
|
||||
label={__("Assets")}
|
||||
icon={IconBox}
|
||||
to={`${prefix}/assets`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listData">
|
||||
)}
|
||||
{isAuthorized("Organization", "listData") && (
|
||||
<SidebarItem
|
||||
label={__("Data")}
|
||||
icon={IconListStack}
|
||||
to={`${prefix}/data`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listAudits">
|
||||
)}
|
||||
{isAuthorized("Organization", "listAudits") && (
|
||||
<SidebarItem
|
||||
label={__("Audits")}
|
||||
icon={IconMedal}
|
||||
to={`${prefix}/audits`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listNonconformities">
|
||||
)}
|
||||
{isAuthorized("Organization", "listNonconformities") && (
|
||||
<SidebarItem
|
||||
label={__("Nonconformities")}
|
||||
icon={IconCrossLargeX}
|
||||
to={`${prefix}/nonconformities`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listObligations">
|
||||
)}
|
||||
{isAuthorized("Organization", "listObligations") && (
|
||||
<SidebarItem
|
||||
label={__("Obligations")}
|
||||
icon={IconBook}
|
||||
to={`${prefix}/obligations`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listContinualImprovements">
|
||||
)}
|
||||
{isAuthorized("Organization", "listContinualImprovements") && (
|
||||
<SidebarItem
|
||||
label={__("Continual Improvements")}
|
||||
icon={IconRotateCw}
|
||||
to={`${prefix}/continual-improvements`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listProcessingActivities">
|
||||
)}
|
||||
{isAuthorized("Organization", "listProcessingActivities") && (
|
||||
<SidebarItem
|
||||
label={__("Processing Activities")}
|
||||
icon={IconCircleProgress}
|
||||
to={`${prefix}/processing-activities`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listSnapshots">
|
||||
)}
|
||||
{isAuthorized("Organization", "listSnapshots") && (
|
||||
<SidebarItem
|
||||
label={__("Snapshots")}
|
||||
icon={IconClock}
|
||||
to={`${prefix}/snapshots`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="getTrustCenter">
|
||||
)}
|
||||
{isAuthorized("Organization", "getTrustCenter") && (
|
||||
<SidebarItem
|
||||
label={__("Trust Center")}
|
||||
icon={IconShield}
|
||||
to={`${prefix}/trust-center`}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="listMembers">
|
||||
)}
|
||||
{isAuthorized("Organization", "listMembers") && (
|
||||
<SidebarItem
|
||||
label={__("Settings")}
|
||||
icon={IconSettingsGear2}
|
||||
to={`${prefix}/settings`}
|
||||
/>
|
||||
</Authorized>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
@@ -255,6 +255,7 @@ function MainLayoutContent({
|
||||
function UserDropdown({ organizationId }: { organizationId: string }) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const user = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
||||
organizationId,
|
||||
}).viewer.user;
|
||||
@@ -290,13 +291,13 @@ function UserDropdown({ organizationId }: { organizationId: string }) {
|
||||
|
||||
return (
|
||||
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
||||
<Authorized entity="Organization" action="deleteOrganization">
|
||||
{isAuthorized("Organization", "deleteOrganization") && (
|
||||
<UserDropdownItem
|
||||
to="/api-keys"
|
||||
icon={IconKey}
|
||||
label={__("API Keys")}
|
||||
/>
|
||||
</Authorized>
|
||||
)}
|
||||
<UserDropdownItem
|
||||
to="mailto:support@getprobo.com"
|
||||
icon={IconCircleQuestionmark}
|
||||
|
||||
@@ -29,7 +29,8 @@ import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectFiel
|
||||
import type { AssetGraphNodeQuery } from "/hooks/graph/__generated__/AssetGraphNodeQuery.graphql";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Authorized } from "/permissions";
|
||||
import { use } from "react";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
const updateAssetSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
@@ -51,7 +52,7 @@ export default function AssetDetailsPage(props: Props) {
|
||||
const organizationId = useOrganizationId();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
if (!assetEntry || !assetEntry.id) {
|
||||
return <div>{__("Asset not found")}</div>;
|
||||
}
|
||||
@@ -116,7 +117,7 @@ export default function AssetDetailsPage(props: Props) {
|
||||
</Badge>
|
||||
</div>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="Asset" action="deleteAsset">
|
||||
isAuthorized("Asset", "deleteAsset") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -126,7 +127,7 @@ export default function AssetDetailsPage(props: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -182,11 +183,11 @@ export default function AssetDetailsPage(props: Props) {
|
||||
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && !isSnapshotMode && (
|
||||
<Authorized entity="Asset" action="updateAsset">
|
||||
isAuthorized("Asset", "updateAsset") && (
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <div>{__("Audit not found")}</div>;
|
||||
}
|
||||
@@ -146,7 +147,7 @@ export default function AuditDetailsPage(props: Props) {
|
||||
</Badge>
|
||||
</div>
|
||||
<ActionDropdown variant="secondary">
|
||||
<Authorized entity="Audit" action="deleteAudit">
|
||||
{isAuthorized("Audit", "deleteAudit") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -154,7 +155,7 @@ export default function AuditDetailsPage(props: Props) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
|
||||
@@ -187,11 +188,11 @@ export default function AuditDetailsPage(props: Props) {
|
||||
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && (
|
||||
<Authorized entity="Audit" action="updateAudit">
|
||||
isAuthorized("Audit", "updateAudit") && (
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
@@ -105,14 +106,14 @@ export default function AuditsPage(props: Props) {
|
||||
"Manage your organization's compliance audits and their progress."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createAudit">
|
||||
{isAuthorized("Organization", "createAudit") && (
|
||||
<CreateAuditDialog
|
||||
connection={connectionId}
|
||||
organizationId={organizationId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add audit")}</Button>
|
||||
</CreateAuditDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</PageHeader>
|
||||
<SortableTable {...pagination}>
|
||||
<Thead>
|
||||
@@ -153,6 +154,7 @@ function AuditRow({
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const deleteAudit = useDeleteAudit(entry, connectionId);
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/audits/${entry.id}`}>
|
||||
@@ -177,7 +179,7 @@ function AuditRow({
|
||||
{hasAnyAction && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Audit" action="deleteAudit">
|
||||
{isAuthorized("Audit", "deleteAudit") && (
|
||||
<DropdownItem
|
||||
onClick={deleteAudit}
|
||||
variant="danger"
|
||||
@@ -185,7 +187,7 @@ function AuditRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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 <div>{__("Continual improvement entry not found")}</div>;
|
||||
}
|
||||
@@ -150,13 +151,13 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
||||
]}
|
||||
/>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="ContinualImprovement" action="deleteContinualImprovement">
|
||||
isAuthorized("ContinualImprovement", "deleteContinualImprovement") && (
|
||||
<ActionDropdown>
|
||||
<DropdownItem onClick={deleteImprovement} variant="danger">
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -289,7 +290,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
||||
|
||||
{!isSnapshotMode && (
|
||||
<div className="flex justify-end pt-4">
|
||||
<Authorized entity="ContinualImprovement" action="updateContinualImprovement">
|
||||
{isAuthorized("ContinualImprovement", "updateContinualImprovement") && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
@@ -297,7 +298,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
||||
>
|
||||
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -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<ContinualImprovementsPageQuery>;
|
||||
@@ -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
|
||||
)}
|
||||
<PageHeader title={__("Continual Improvements")} description={__("Manage your continual improvements.")}>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="Organization" action="createContinualImprovement">
|
||||
isAuthorized("Organization", "createContinualImprovement") && (
|
||||
<CreateContinualImprovementDialog
|
||||
organizationId={organizationId}
|
||||
connectionId={connectionId}
|
||||
@@ -150,7 +151,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove
|
||||
{__("Add continual improvement")}
|
||||
</Button>
|
||||
</CreateContinualImprovementDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
@@ -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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="ContinualImprovement" action="deleteContinualImprovement">
|
||||
{isAuthorized("ContinualImprovement", "deleteContinualImprovement") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
@@ -290,7 +292,7 @@ function ImprovementRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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<DatumGraphListQuery>(
|
||||
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 && (
|
||||
<Authorized entity="Organization" action="createDatum">
|
||||
isAuthorized("Organization", "createDatum") && (
|
||||
<CreateDatumDialog
|
||||
connection={connectionId}
|
||||
organizationId={organizationId}
|
||||
@@ -145,7 +147,7 @@ export default function DataPage(props: Props) {
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add data")}</Button>
|
||||
</CreateDatumDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
<SortableTable
|
||||
@@ -186,7 +188,7 @@ function DataRow({
|
||||
const { __ } = useTranslate();
|
||||
const deleteDatum = useDeleteDatum(entry, connectionId);
|
||||
const vendors = entry.vendors?.edges.map((edge) => 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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Datum" action="deleteDatum">
|
||||
{isAuthorized("Datum", "deleteDatum") && (
|
||||
<DropdownItem
|
||||
onClick={deleteDatum}
|
||||
variant="danger"
|
||||
@@ -236,7 +238,7 @@ function DataRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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) {
|
||||
<Badge variant="info">{datumEntry?.dataClassification}</Badge>
|
||||
</div>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="Datum" action="deleteDatum">
|
||||
isAuthorized("Datum", "deleteDatum") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -135,7 +137,7 @@ export default function DatumDetailsPage(props: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -180,11 +182,11 @@ export default function DatumDetailsPage(props: Props) {
|
||||
{!isSnapshotMode && (
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && (
|
||||
<Authorized entity="Datum" action="updateDatum">
|
||||
isAuthorized("Datum", "updateDatum") && (
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<DocumentGraphNodeQuery>;
|
||||
@@ -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) {
|
||||
</Dropdown>
|
||||
|
||||
<ActionDropdown variant="secondary">
|
||||
<Authorized entity="Document" action="updateDocument">
|
||||
{isAuthorized("Document", "updateDocument") && (
|
||||
<DropdownItem
|
||||
onClick={() => updateDialogRef.current?.open()}
|
||||
icon={IconPencil}
|
||||
>
|
||||
{isDraft ? __("Edit draft document") : __("Create new draft")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
{isDraft && versions.length > 1 && (
|
||||
<Authorized entity="Document" action="deleteDocument">
|
||||
isAuthorized("Document", "deleteDocument") && (
|
||||
<DropdownItem
|
||||
onClick={handleDeleteDraft}
|
||||
icon={IconTrashCan}
|
||||
@@ -539,7 +533,7 @@ export default function DocumentDetailPage(props: Props) {
|
||||
>
|
||||
{__("Delete draft document")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
<DropdownItem
|
||||
onClick={() => pdfDownloadDialogRef.current?.open()}
|
||||
@@ -548,7 +542,7 @@ export default function DocumentDetailPage(props: Props) {
|
||||
>
|
||||
{__("Download PDF")}
|
||||
</DropdownItem>
|
||||
<Authorized entity="Document" action="deleteDocument">
|
||||
{isAuthorized("Document", "deleteDocument") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -557,7 +551,7 @@ export default function DocumentDetailPage(props: Props) {
|
||||
>
|
||||
{__("Delete document")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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")}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<Authorized entity="Document" action="sendSigningNotifications">
|
||||
<Button
|
||||
icon={IconBell2}
|
||||
variant="secondary"
|
||||
onClick={handleSendSigningNotifications}
|
||||
>
|
||||
{__("Send signing notifications")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="createDocument">
|
||||
{isAuthorized("Document", "sendSigningNotifications") && (
|
||||
<Button
|
||||
icon={IconBell2}
|
||||
variant="secondary"
|
||||
onClick={handleSendSigningNotifications}
|
||||
>
|
||||
{__("Send signing notifications")}
|
||||
</Button>
|
||||
)}
|
||||
{isAuthorized("Organization", "createDocument") && (
|
||||
<CreateDocumentDialog
|
||||
connection={connectionId}
|
||||
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
|
||||
/>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</PageHeader>
|
||||
{documents.length > 0 ? (
|
||||
@@ -258,7 +258,7 @@ export default function DocumentsPage(props: Props) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Authorized entity="Document" action="updateDocument">
|
||||
{isAuthorized("Document", "updateDocument") && (
|
||||
<PublishDocumentsDialog
|
||||
documentIds={selection}
|
||||
onSave={clear}
|
||||
@@ -270,8 +270,8 @@ export default function DocumentsPage(props: Props) {
|
||||
{__("Publish")}
|
||||
</Button>
|
||||
</PublishDocumentsDialog>
|
||||
</Authorized>
|
||||
<Authorized entity="Document" action="bulkRequestSignatures">
|
||||
)}
|
||||
{isAuthorized("Document", "bulkRequestSignatures") && (
|
||||
<SignatureDocumentsDialog
|
||||
documentIds={selection}
|
||||
onSave={clear}
|
||||
@@ -284,7 +284,7 @@ export default function DocumentsPage(props: Props) {
|
||||
{__("Request signature")}
|
||||
</Button>
|
||||
</SignatureDocumentsDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
<BulkExportDialog
|
||||
ref={bulkExportDialogRef}
|
||||
onExport={handleBulkExport}
|
||||
@@ -300,7 +300,7 @@ export default function DocumentsPage(props: Props) {
|
||||
{__("Export")}
|
||||
</Button>
|
||||
</BulkExportDialog>
|
||||
<Authorized entity="Document" action="deleteDocument">
|
||||
{isAuthorized("Document", "deleteDocument") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -309,7 +309,7 @@ export default function DocumentsPage(props: Props) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Th>
|
||||
@@ -392,6 +392,7 @@ function DocumentRow({
|
||||
onCheck: () => void;
|
||||
hasAnyAction: boolean;
|
||||
}) {
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const document = useFragment<DocumentsPageRowFragment$key>(
|
||||
rowFragment,
|
||||
documentKey
|
||||
@@ -465,7 +466,7 @@ function DocumentRow({
|
||||
{hasAnyAction && (
|
||||
<Td noLink width={50} className="text-end w-18">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Document" action="deleteDocument">
|
||||
{isAuthorized("Document", "deleteDocument") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -473,7 +474,7 @@ function DocumentRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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<DocumentDetailPageDocumentFragment$data["versions"]>;
|
||||
|
||||
@@ -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: {
|
||||
</div>
|
||||
</div>
|
||||
{props.signable && (
|
||||
<Authorized entity="Document" action="requestSignature">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="ml-auto"
|
||||
disabled={isSendingRequest}
|
||||
onClick={() => {
|
||||
requestSignature({
|
||||
variables: {
|
||||
input: {
|
||||
documentVersionId: props.versionId,
|
||||
signatoryId: props.people.id,
|
||||
isAuthorized("Document", "requestSignature") && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="ml-auto"
|
||||
disabled={isSendingRequest}
|
||||
onClick={() => {
|
||||
requestSignature({
|
||||
variables: {
|
||||
input: {
|
||||
documentVersionId: props.versionId,
|
||||
signatoryId: props.people.id,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{__("Request signature")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
});
|
||||
}}
|
||||
>
|
||||
{__("Request signature")}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -331,25 +332,25 @@ function SignatureItem(props: {
|
||||
{__("Signed")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Authorized entity="DocumentVersionSignature" action="cancelSignatureRequest">
|
||||
<Button
|
||||
variant="danger"
|
||||
className="ml-auto"
|
||||
disabled={isCancellingSignature}
|
||||
onClick={() => {
|
||||
cancelSignature({
|
||||
variables: {
|
||||
input: {
|
||||
documentVersionSignatureId: signature.id,
|
||||
isAuthorized("DocumentVersionSignature", "cancelSignatureRequest") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
className="ml-auto"
|
||||
disabled={isCancellingSignature}
|
||||
onClick={() => {
|
||||
cancelSignature({
|
||||
variables: {
|
||||
input: {
|
||||
documentVersionSignatureId: signature.id,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{__("Cancel request")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
});
|
||||
}}
|
||||
>
|
||||
{__("Cancel request")}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Authorized entity="Control" action="updateControl">
|
||||
{isAuthorized("Control", "updateControl") && (
|
||||
<FrameworkControlDialog
|
||||
frameworkId={framework.id}
|
||||
connectionId={connectionId}
|
||||
@@ -246,8 +247,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
||||
{__("Edit control")}
|
||||
</Button>
|
||||
</FrameworkControlDialog>
|
||||
</Authorized>
|
||||
<Authorized entity="Control" action="deleteControl">
|
||||
)}
|
||||
{isAuthorized("Control", "deleteControl") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
@@ -257,7 +258,7 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation>(
|
||||
generateFrameworkStateOfApplicabilityMutation,
|
||||
@@ -150,7 +151,7 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Authorized entity="Framework" action="updateFramework">
|
||||
{isAuthorized("Framework", "updateFramework") && (
|
||||
<FrameworkFormDialog
|
||||
organizationId={organizationId}
|
||||
framework={framework}
|
||||
@@ -159,7 +160,7 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
{__("Edit")}
|
||||
</Button>
|
||||
</FrameworkFormDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="primary"
|
||||
@@ -191,11 +192,11 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
>
|
||||
{__("Export Framework")}
|
||||
</DropdownItem>
|
||||
<Authorized entity="Framework" action="deleteFramework">
|
||||
{isAuthorized("Framework", "deleteFramework") && (
|
||||
<DropdownItem icon={IconTrashCan} variant="danger" onClick={onDelete}>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</PageHeader>
|
||||
<div className="text-lg font-semibold">
|
||||
@@ -216,7 +217,7 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
active={selectedControl?.id === control.id}
|
||||
/>
|
||||
))}
|
||||
<Authorized entity="Organization" action="createControl">
|
||||
{isAuthorized("Organization", "createControl") && (
|
||||
<FrameworkControlDialog
|
||||
frameworkId={framework.id}
|
||||
connectionId={connectionId}
|
||||
@@ -226,7 +227,7 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
{__("Add new control")}
|
||||
</button>
|
||||
</FrameworkControlDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
<Outlet context={{ framework }} />
|
||||
</div>
|
||||
|
||||
@@ -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<FrameworkGraphListQuery>;
|
||||
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
@@ -134,20 +134,22 @@ export default function FrameworksPage(props: Props) {
|
||||
title={__("Frameworks")}
|
||||
description={__("Manage your compliance frameworks")}
|
||||
>
|
||||
<Authorized entity="Organization" action="createFramework">
|
||||
<FileButton
|
||||
variant="secondary"
|
||||
icon={IconFolderUpload}
|
||||
onChange={handleUpload}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{__("Import")}
|
||||
</FileButton>
|
||||
<FrameworkSelector
|
||||
onSelect={importNamedFramework}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Authorized>
|
||||
{isAuthorized("Organization", "createFramework") && (
|
||||
<>
|
||||
<FileButton
|
||||
variant="secondary"
|
||||
icon={IconFolderUpload}
|
||||
onChange={handleUpload}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{__("Import")}
|
||||
</FileButton>
|
||||
<FrameworkSelector
|
||||
onSelect={importNamedFramework}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -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) {
|
||||
<FrameworkLogo {...framework} />
|
||||
{props.hasAnyAction && (
|
||||
<ActionDropdown className="z-10 relative">
|
||||
<Authorized entity="Framework" action="updateFramework">
|
||||
{isAuthorized("Framework", "updateFramework") && (
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => {
|
||||
@@ -209,8 +212,8 @@ function FrameworkCard(props: FrameworkCardProps) {
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
<Authorized entity="Framework" action="deleteFramework">
|
||||
)}
|
||||
{isAuthorized("Framework", "deleteFramework") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={() => deleteFramework()}
|
||||
@@ -218,7 +221,7 @@ function FrameworkCard(props: FrameworkCardProps) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<MeasureGraphNodeQuery>;
|
||||
@@ -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) {
|
||||
/>
|
||||
|
||||
<PageHeader title={measure.name} description={measure.description}>
|
||||
<Authorized entity="Measure" action="updateMeasure">
|
||||
<MeasureFormDialog measure={measure}>
|
||||
<Button variant="secondary" icon={IconPencil}>
|
||||
{__("Edit")}
|
||||
</Button>
|
||||
</MeasureFormDialog>
|
||||
<Select
|
||||
disabled={isUpdating}
|
||||
onValueChange={onStateChange}
|
||||
name="state"
|
||||
placeholder={__("Select state")}
|
||||
className="rounded-full"
|
||||
value={measure.state}
|
||||
>
|
||||
{measureStates.map((state) => (
|
||||
<Option key={state} value={state}>
|
||||
{getMeasureStateLabel(__, state)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Authorized>
|
||||
{isAuthorized("Measure", "updateMeasure") && (
|
||||
<>
|
||||
<MeasureFormDialog measure={measure}>
|
||||
<Button variant="secondary" icon={IconPencil}>
|
||||
{__("Edit")}
|
||||
</Button>
|
||||
</MeasureFormDialog>
|
||||
<Select
|
||||
disabled={isUpdating}
|
||||
onValueChange={onStateChange}
|
||||
name="state"
|
||||
placeholder={__("Select state")}
|
||||
className="rounded-full"
|
||||
value={measure.state}
|
||||
>
|
||||
{measureStates.map((state) => (
|
||||
<Option key={state} value={state}>
|
||||
{getMeasureStateLabel(__, state)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
<ActionDropdown variant="secondary">
|
||||
<Authorized entity="Measure" action="deleteMeasure">
|
||||
{isAuthorized("Measure", "deleteMeasure") && (
|
||||
<DropdownItem variant="danger" icon={IconTrashCan} onClick={onDelete}>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</PageHeader>
|
||||
|
||||
|
||||
@@ -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<MeasureGraphListQuery>;
|
||||
@@ -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<HTMLInputElement>(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<HTMLInputElement> = (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."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createMeasure">
|
||||
<FileButton
|
||||
ref={importFileRef}
|
||||
variant="secondary"
|
||||
icon={IconFolderUpload}
|
||||
onChange={handleImport}
|
||||
>
|
||||
{__("Import")}
|
||||
</FileButton>
|
||||
<MeasureFormDialog connection={connectionId}>
|
||||
<Button variant="primary" icon={IconPlusLarge}>
|
||||
{__("New measure")}
|
||||
</Button>
|
||||
</MeasureFormDialog>
|
||||
</Authorized>
|
||||
{isAuthorized("Organization", "createMeasure") && (
|
||||
<>
|
||||
<FileButton
|
||||
ref={importFileRef}
|
||||
variant="secondary"
|
||||
icon={IconFolderUpload}
|
||||
onChange={handleImport}
|
||||
>
|
||||
{__("Import")}
|
||||
</FileButton>
|
||||
<MeasureFormDialog connection={connectionId}>
|
||||
<Button variant="primary" icon={IconPlusLarge}>
|
||||
{__("New measure")}
|
||||
</Button>
|
||||
</MeasureFormDialog>
|
||||
</>
|
||||
)}
|
||||
</PageHeader>
|
||||
<MeasureImplementation measures={measures} className="my-10" />
|
||||
{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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Measure" action="updateMeasure">
|
||||
{isAuthorized("Measure", "updateMeasure") && (
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => dialogRef.current?.open()}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
<Authorized entity="Measure" action="deleteMeasure">
|
||||
)}
|
||||
{isAuthorized("Measure", "deleteMeasure") && (
|
||||
<DropdownItem
|
||||
onClick={onDelete}
|
||||
disabled={isDeleting}
|
||||
@@ -323,7 +326,7 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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 <div>{__("Meeting not found")}</div>;
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
<h3 className="text-sm font-semibold text-txt-secondary">
|
||||
{__("Summary")}
|
||||
</h3>
|
||||
<Authorized entity="Meeting" action="updateMeeting">
|
||||
{isAuthorized("Meeting", "updateMeeting") && (
|
||||
<Button
|
||||
variant="quaternary"
|
||||
icon={IconPencil}
|
||||
@@ -226,7 +227,7 @@ export default function MeetingsPage(props: Props) {
|
||||
>
|
||||
{__("Edit")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full">
|
||||
{displayedSummary ? (
|
||||
@@ -248,11 +249,11 @@ export default function MeetingsPage(props: Props) {
|
||||
"Track and manage your organization's meetings and their minutes."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createMeeting">
|
||||
{isAuthorized("Organization", "createMeeting") && (
|
||||
<CreateMeetingDialog connectionId={connectionId}>
|
||||
<Button icon={IconPlusLarge}>{__("Add meeting")}</Button>
|
||||
</CreateMeetingDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</PageHeader>
|
||||
{meetingNodes.length > 0 ? (
|
||||
<SortableTable {...pagination}>
|
||||
@@ -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({
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
<Authorized entity="Meeting" action="deleteMeeting">
|
||||
{isAuthorized("Meeting", "deleteMeeting") && (
|
||||
<Td noLink width={50} className="text-end w-18">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
@@ -382,7 +383,7 @@ function MeetingRow({
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
</Authorized>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
<Authorized entity="Organization" action="createNonconformity">
|
||||
isAuthorized("Organization", "createNonconformity") && (
|
||||
<CreateNonconformityDialog organizationId={organizationId} connection={connectionId}>
|
||||
<Button icon={IconPlusLarge}>{__("Add nonconformity")}</Button>
|
||||
</CreateNonconformityDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
@@ -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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Nonconformity" action="deleteNonconformity">
|
||||
{isAuthorized("Nonconformity", "deleteNonconformity") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
@@ -306,7 +307,7 @@ function NonconformityRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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) {
|
||||
</div>
|
||||
{!isSnapshotMode && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<Authorized entity="Nonconformity" action="deleteNonconformity">
|
||||
{isAuthorized("Nonconformity", "deleteNonconformity") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -170,7 +172,7 @@ export default function NonconformityDetailsPage(props: Props) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</div>
|
||||
@@ -279,11 +281,11 @@ export default function NonconformityDetailsPage(props: Props) {
|
||||
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && !isSnapshotMode && (
|
||||
<Authorized entity="Nonconformity" action="updateNonconformity">
|
||||
isAuthorized("Nonconformity", "updateNonconformity") && (
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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 <div>{__("Obligation not found")}</div>;
|
||||
@@ -157,13 +159,13 @@ export default function ObligationDetailsPage(props: Props) {
|
||||
</div>
|
||||
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="Obligation" action="deleteObligation">
|
||||
isAuthorized("Obligation", "deleteObligation") && (
|
||||
<ActionDropdown>
|
||||
<DropdownItem icon={IconTrashCan} onClick={deleteObligation}>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -301,14 +303,14 @@ export default function ObligationDetailsPage(props: Props) {
|
||||
|
||||
{!isSnapshotMode && (
|
||||
<div className="flex justify-end">
|
||||
<Authorized entity="Obligation" action="updateObligation">
|
||||
{isAuthorized("Obligation", "updateObligation") && (
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={formState.isSubmitting}
|
||||
>
|
||||
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -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 && (
|
||||
<Authorized entity="Organization" action="createObligation">
|
||||
isAuthorized("Organization", "createObligation") && (
|
||||
<CreateObligationDialog organizationId={organizationId} connection={connectionId}>
|
||||
<Button icon={IconPlusLarge}>{__("Add obligation")}</Button>
|
||||
</CreateObligationDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
@@ -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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Obligation" action="deleteObligation">
|
||||
{isAuthorized("Obligation", "deleteObligation") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
@@ -269,7 +270,7 @@ function ObligationRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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<PeopleGraphNodeQuery>;
|
||||
@@ -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) {
|
||||
<Avatar name={people.fullName ?? ""} size="xl" />
|
||||
<div className="text-2xl">{people.fullName}</div>
|
||||
</div>
|
||||
<Authorized entity="People" action="deletePeople">
|
||||
{isAuthorized("People", "deletePeople") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -65,7 +67,7 @@ export default function PeopleDetailPage(props: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Tabs>
|
||||
|
||||
@@ -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<PeopleGraphPaginatedFragment$data["peoples"]>;
|
||||
|
||||
@@ -42,14 +42,14 @@ export default function PeopleListPage({
|
||||
queryRef: PreloadedQuery<PeopleGraphPaginatedQuery>;
|
||||
}) {
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
@@ -59,11 +59,11 @@ export default function PeopleListPage({
|
||||
"Keep track of your company's workforce and their progress towards completing tasks assigned to them."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createPeople">
|
||||
{isAuthorized("Organization", "createPeople") && (
|
||||
<CreatePeopleDialog connectionId={connectionId}>
|
||||
<Button icon={IconPlusLarge}>{__("Add member")}</Button>
|
||||
</CreatePeopleDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</PageHeader>
|
||||
<SortableTable
|
||||
refetch={refetch}
|
||||
@@ -107,6 +107,7 @@ function PeopleRow({
|
||||
const { __ } = useTranslate();
|
||||
const deletePeople = useDeletePeople(people, connectionId);
|
||||
const contractEnded = isContractEnded(people);
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
|
||||
return (
|
||||
<Tr
|
||||
@@ -129,7 +130,7 @@ function PeopleRow({
|
||||
{hasAnyAction && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="People" action="deletePeople">
|
||||
{isAuthorized("People", "deletePeople") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
@@ -137,7 +138,7 @@ function PeopleRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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() {
|
||||
</Card>
|
||||
<div className="flex justify-end">
|
||||
{formState.isDirty && (
|
||||
<Authorized entity="People" action="updatePeople">
|
||||
isAuthorized("People", "updatePeople") && (
|
||||
<Button type="submit" disabled={isMutating}>
|
||||
{__("Update")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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<ProcessingActivitiesPageQuery>;
|
||||
@@ -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
|
||||
)}
|
||||
<PageHeader title={__("Processing Activities")} description={__("Manage your processing activities under GDPR")}>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="Organization" action="createProcessingActivity">
|
||||
isAuthorized("Organization", "createProcessingActivity") && (
|
||||
<CreateProcessingActivityDialog
|
||||
organizationId={organizationId}
|
||||
connectionId={connectionId}
|
||||
@@ -149,7 +149,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
||||
{__("Add processing activity")}
|
||||
</Button>
|
||||
</CreateProcessingActivityDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
@@ -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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="ProcessingActivity" action="deleteProcessingActivity">
|
||||
{isAuthorized("ProcessingActivity", "deleteProcessingActivity") && (
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
@@ -278,7 +279,7 @@ function ActivityRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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 <div>{__("Processing activity not found")}</div>;
|
||||
@@ -171,13 +173,13 @@ export default function ProcessingActivityDetailsPage(props: Props) {
|
||||
]}
|
||||
/>
|
||||
{!isSnapshotMode && (
|
||||
<Authorized entity="ProcessingActivity" action="deleteProcessingActivity">
|
||||
isAuthorized("ProcessingActivity", "deleteProcessingActivity") && (
|
||||
<ActionDropdown>
|
||||
<DropdownItem onClick={deleteActivity} variant="danger">
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<RiskGraphNodeQuery>;
|
||||
@@ -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 && (
|
||||
<div className="flex gap-2">
|
||||
<Authorized entity="Risk" action="updateRisk">
|
||||
{isAuthorized("Risk", "updateRisk") && (
|
||||
<FormRiskDialog
|
||||
trigger={
|
||||
<Button icon={IconPencil} variant="secondary">
|
||||
@@ -130,8 +132,8 @@ export default function RiskDetailPage(props: Props) {
|
||||
}
|
||||
risk={{ id: riskId, ...risk }}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="Risk" action="deleteRisk">
|
||||
)}
|
||||
{isAuthorized("Risk", "deleteRisk") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -141,7 +143,7 @@ export default function RiskDetailPage(props: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<RiskGraphListQuery>;
|
||||
@@ -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 && (
|
||||
<Authorized entity="Organization" action="createRisk">
|
||||
isAuthorized("Organization", "createRisk") && (
|
||||
<FormRiskDialog
|
||||
connection={connectionId}
|
||||
onSuccess={() => {
|
||||
@@ -79,7 +80,7 @@ export default function RisksPage(props: Props) {
|
||||
}}
|
||||
trigger={<Button icon={IconPlusLarge}>{__("New Risk")}</Button>}
|
||||
/>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
@@ -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 && (
|
||||
<Td noLink className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Risk" action="updateRisk">
|
||||
{isAuthorized("Risk", "updateRisk") && (
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => formDialogRef.current?.open()}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
|
||||
<Authorized entity="Risk" action="deleteRisk">
|
||||
{isAuthorized("Risk", "deleteRisk") && (
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
@@ -211,7 +213,7 @@ function RiskRow(props: RowProps) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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<OutletContext>();
|
||||
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<string | null>(null);
|
||||
const [horizontalLogoPreview, setHorizontalLogoPreview] = useState<
|
||||
|
||||
@@ -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<OutletContext>();
|
||||
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const membershipsPagination = usePaginationFragment(
|
||||
paginatedMembershipsFragment,
|
||||
organizationKey as MembersSettingsTabMembershipsFragment$key
|
||||
@@ -180,14 +179,14 @@ export default function MembersSettingsTab() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
||||
<Authorized entity="Organization" action="inviteUser">
|
||||
{isAuthorized("Organization", "inviteUser") && (
|
||||
<InviteUserDialog
|
||||
connectionId={invitationsPagination.data.invitations?.__id}
|
||||
onRefetch={refetchInvitations}
|
||||
>
|
||||
<Button variant="secondary">{__("Invite member")}</Button>
|
||||
</InviteUserDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Tabs>
|
||||
@@ -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 ? (
|
||||
<Spinner size={16} />
|
||||
) : (
|
||||
<Authorized entity="Invitation" action="deleteInvitation">
|
||||
isAuthorized("Invitation", "deleteInvitation") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={onDelete}
|
||||
@@ -384,7 +384,7 @@ function InvitationRow(props: {
|
||||
icon={IconTrashCan}
|
||||
aria-label={__("Delete invitation")}
|
||||
/>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
@@ -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()}
|
||||
>
|
||||
<Authorized entity="Organization" action="updateMembership">
|
||||
{canEditThisRole && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleEditClick}
|
||||
disabled={isUpdating}
|
||||
icon={IconPencil}
|
||||
aria-label={__("Edit role")}
|
||||
/>
|
||||
)}
|
||||
</Authorized>
|
||||
{isAuthorized("Organization", "updateMembership") && canEditThisRole && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleEditClick}
|
||||
disabled={isUpdating}
|
||||
icon={IconPencil}
|
||||
aria-label={__("Edit role")}
|
||||
/>
|
||||
)}
|
||||
{isRemoving ? (
|
||||
<Spinner size={16} />
|
||||
) : (
|
||||
<Authorized entity="Organization" action="removeMember">
|
||||
{canEditThisRole && (
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={onRemove}
|
||||
disabled={isRemoving}
|
||||
icon={IconTrashCan}
|
||||
aria-label={__("Remove member")}
|
||||
/>
|
||||
)}
|
||||
</Authorized>
|
||||
isAuthorized("Organization", "removeMember") && canEditThisRole && (
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={onRemove}
|
||||
disabled={isRemoving}
|
||||
icon={IconTrashCan}
|
||||
aria-label={__("Remove member")}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, use } from "react";
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useFragment, graphql } from "react-relay";
|
||||
import { Controller } from "react-hook-form";
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
useVerifyDomainMutation,
|
||||
} from "/hooks/graph/SAMLConfigurationGraph";
|
||||
import type { SAMLSettingsTabFragment$key } from "./__generated__/SAMLSettingsTabFragment.graphql";
|
||||
import { Authorized } from "/permissions";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
const samlSettingsTabFragment = graphql`
|
||||
fragment SAMLSettingsTabFragment on Organization {
|
||||
@@ -97,6 +97,7 @@ type SetupStep = "initiate" | "verify" | "configure";
|
||||
export default function SAMLSettingsTab() {
|
||||
const { __ } = useTranslate();
|
||||
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const organization = useFragment(samlSettingsTabFragment, organizationKey);
|
||||
const configs = organization.samlConfigurations;
|
||||
|
||||
@@ -372,11 +373,11 @@ export default function SAMLSettingsTab() {
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-base font-medium">{__("SAML Single Sign-On")}</h2>
|
||||
<Authorized entity="Organization" action="createSAMLConfiguration">
|
||||
{isAuthorized("Organization", "createSAMLConfiguration") && (
|
||||
<Button onClick={() => handleOpenModal()}>
|
||||
{__("Add Configuration")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{configs.length === 0 ? (
|
||||
@@ -388,11 +389,11 @@ export default function SAMLSettingsTab() {
|
||||
<p className="text-gray-600 mb-6">
|
||||
{__("Set up SAML 2.0 single sign-on for your organization by adding a configuration for each email domain.")}
|
||||
</p>
|
||||
<Authorized entity="Organization" action="createSAMLConfiguration">
|
||||
{isAuthorized("Organization", "createSAMLConfiguration") && (
|
||||
<Button onClick={() => handleOpenModal()}>
|
||||
{__("Add Your First Configuration")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -457,7 +458,7 @@ export default function SAMLSettingsTab() {
|
||||
<div className="flex gap-2 justify-end">
|
||||
{config.domainVerified ? (
|
||||
<>
|
||||
<Authorized entity="SAMLConfiguration" action="updateSAMLConfiguration">
|
||||
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
|
||||
<Button
|
||||
variant={config.enabled ? "danger" : "primary"}
|
||||
onClick={() => handleToggleEnabled(config)}
|
||||
@@ -465,34 +466,34 @@ export default function SAMLSettingsTab() {
|
||||
>
|
||||
{config.enabled ? __("Disable") : __("Enable")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
<Authorized entity="SAMLConfiguration" action="updateSAMLConfiguration">
|
||||
)}
|
||||
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleOpenModal(config)}
|
||||
>
|
||||
{__("Edit")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Authorized entity="Organization" action="verifyDomain">
|
||||
{isAuthorized("Organization", "verifyDomain") && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => handleOpenModal(config)}
|
||||
>
|
||||
{__("Verify Domain")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
<Authorized entity="Organization" action="deleteOrganization">
|
||||
)}
|
||||
{isAuthorized("Organization", "deleteOrganization") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => handleDelete(config)}
|
||||
>
|
||||
{__("Delete")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<SnapshotGraphListQuery>;
|
||||
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
@@ -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."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createSnapshot">
|
||||
{isAuthorized("Organization", "createSnapshot") && (
|
||||
<SnapshotFormDialog connection={connectionId}>
|
||||
<Button variant="primary" icon={IconPlusLarge}>
|
||||
{__("New snapshot")}
|
||||
</Button>
|
||||
</SnapshotFormDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
{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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Snapshot" action="deleteSnapshot">
|
||||
{isAuthorized("Snapshot", "deleteSnapshot") && (
|
||||
<DropdownItem
|
||||
onClick={deleteSnapshot}
|
||||
variant="danger"
|
||||
@@ -167,7 +168,7 @@ function SnapshotRow(props: SnapshotRowProps) {
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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."
|
||||
)}
|
||||
>
|
||||
<Authorized entity="Organization" action="createTask">
|
||||
{isAuthorized("Organization", "createTask") && (
|
||||
<TaskFormDialog connection={connectionId}>
|
||||
<Button icon={IconPlusLarge}>{__("New task")}</Button>
|
||||
</TaskFormDialog>
|
||||
</Authorized>
|
||||
)}
|
||||
</PageHeader>
|
||||
<TasksCard connectionId={connectionId} tasks={tasks ?? []} />
|
||||
</div>
|
||||
|
||||
@@ -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<ContextType>();
|
||||
|
||||
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() {
|
||||
</p>
|
||||
</div>
|
||||
{organization.trustCenter?.id && (
|
||||
<Authorized entity="TrustCenter" action="createTrustCenterAccess">
|
||||
{isAuthorized("TrustCenter", "createTrustCenterAccess") && (
|
||||
<Button icon={IconPlusLarge} onClick={() => {
|
||||
inviteForm.reset();
|
||||
dialogRef.current?.open();
|
||||
}}>
|
||||
{__("Add Access")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -515,22 +515,22 @@ export default function TrustCenterAccessTab() {
|
||||
className="flex gap-2 justify-end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Authorized entity="TrustCenterAccess" action="updateTrustCenterAccess">
|
||||
{isAuthorized("TrustCenterAccess", "updateTrustCenterAccess") && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleEditAccess(access)}
|
||||
disabled={isUpdating}
|
||||
icon={IconPencil}
|
||||
/>
|
||||
</Authorized>
|
||||
<Authorized entity="TrustCenterAccess" action="deleteTrustCenterAccess">
|
||||
)}
|
||||
{isAuthorized("TrustCenterAccess", "deleteTrustCenterAccess") && (
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => handleDelete(access.id)}
|
||||
disabled={isDeleting}
|
||||
icon={IconTrashCan}
|
||||
/>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -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<ContextType>();
|
||||
|
||||
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")}
|
||||
</p>
|
||||
</div>
|
||||
<Authorized entity="Organization" action="createTrustCenterFile">
|
||||
{isAuthorized("Organization", "createTrustCenterFile") && (
|
||||
<Button icon={IconPlusLarge} onClick={() => createDialogRef.current?.open()}>
|
||||
{__("Add File")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
{(isUpdating || isDeleting) && (
|
||||
<div className="flex items-center justify-center">
|
||||
|
||||
@@ -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<ContextType>();
|
||||
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) {
|
||||
|
||||
@@ -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<VendorGraphNodeQuery>;
|
||||
@@ -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 <div>{__("Vendor not found")}</div>;
|
||||
@@ -98,7 +100,7 @@ export default function VendorDetailPage(props: Props) {
|
||||
{__("Assessment From Website")}
|
||||
</Button>
|
||||
</ImportAssessmentDialog>
|
||||
<Authorized entity="Vendor" action="deleteVendor">
|
||||
{isAuthorized("Vendor", "deleteVendor") && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -108,7 +110,7 @@ export default function VendorDetailPage(props: Props) {
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<VendorGraphPaginatedFragment$data["vendors"]>;
|
||||
|
||||
@@ -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 && (
|
||||
<Authorized entity="Organization" action="createVendor">
|
||||
isAuthorized("Organization", "createVendor") && (
|
||||
<CreateVendorDialog
|
||||
connection={connectionId}
|
||||
organizationId={organizationId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||
</CreateVendorDialog>
|
||||
</Authorized>
|
||||
)
|
||||
)}
|
||||
</PageHeader>
|
||||
<SortableTable {...pagination}>
|
||||
@@ -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 && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<Authorized entity="Vendor" action="deleteVendor">
|
||||
{isAuthorized("Vendor", "deleteVendor") && (
|
||||
<DropdownItem
|
||||
onClick={deleteVendor}
|
||||
variant="danger"
|
||||
@@ -167,7 +168,7 @@ function VendorRow({
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</Authorized>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
<div className="flex justify-end">
|
||||
<Authorized entity="Vendor" action="updateVendor">
|
||||
{isAuthorized("Vendor", "updateVendor") && (
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{__("Update vendor")}
|
||||
</Button>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -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
|
||||
* <Authorized entity="Organization" action="createDocument">
|
||||
* <CreateButton />
|
||||
* </Authorized>
|
||||
*
|
||||
* @example
|
||||
* <Authorized entity="Document" action="get">
|
||||
* <DocumentViewer />
|
||||
* </Authorized>
|
||||
*
|
||||
* @example
|
||||
* <Authorized entity="Vendor" action="updateVendor">
|
||||
* <EditButton />
|
||||
* </Authorized>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export { isAuthorized, getUserRole, getAssignableRoles } from "./permissions";
|
||||
export { Authorized } from "./Authorized";
|
||||
export type { EntityPermissions } from "./permissions";
|
||||
@@ -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<string, Record<string, boolean>>;
|
||||
|
||||
type PermissionsResponse = {
|
||||
permissions: EntityPermissions;
|
||||
role: string;
|
||||
};
|
||||
|
||||
type PermissionsCache = {
|
||||
[organizationId: string]: {
|
||||
permissions: EntityPermissions;
|
||||
role: string;
|
||||
};
|
||||
};
|
||||
|
||||
const cache: PermissionsCache = {};
|
||||
const pendingRequests: Map<string, Promise<PermissionsResponse>> = new Map();
|
||||
|
||||
/**
|
||||
* Fetch permissions for the current user's role in the organization
|
||||
*/
|
||||
function fetchPermissions(organizationId: string): Promise<PermissionsResponse> {
|
||||
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 <Spinner />;
|
||||
* if (error) return <ErrorMessage />;
|
||||
* 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 [];
|
||||
}
|
||||
17
apps/console/src/providers/PermissionsContext.tsx
Normal file
17
apps/console/src/providers/PermissionsContext.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createContext } from "react";
|
||||
import { Role } from "@probo/helpers";
|
||||
|
||||
export type PermissionsResponse = {
|
||||
permissions: Record<string, Record<string, boolean>>;
|
||||
role: Role;
|
||||
};
|
||||
|
||||
type PermissionsContextType = {
|
||||
isAuthorized: (entity: string, action: string) => boolean;
|
||||
} & PermissionsResponse;
|
||||
|
||||
export const PermissionsContext = createContext<PermissionsContextType>({
|
||||
permissions: {},
|
||||
role: Role.VIEWER,
|
||||
isAuthorized: () => false,
|
||||
});
|
||||
@@ -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<string, Record<string, boolean>>;
|
||||
role: Role;
|
||||
};
|
||||
|
||||
type PermissionsContextType = {
|
||||
isAuthorized: (entity: string, action: string) => boolean;
|
||||
} & PermissionsResponse;
|
||||
|
||||
export const PermissionsContext = createContext<PermissionsContextType>({
|
||||
permissions: {},
|
||||
role: Role.VIEWER,
|
||||
isAuthorized: () => false,
|
||||
});
|
||||
import { PermissionsContext, type PermissionsResponse } from "./PermissionsContext";
|
||||
|
||||
export function PermissionsProvider(props: PropsWithChildren) {
|
||||
const { children } = props;
|
||||
|
||||
@@ -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";
|
||||
|
||||
19
packages/helpers/src/roles.ts
Normal file
19
packages/helpers/src/roles.ts
Normal file
@@ -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 [];
|
||||
}
|
||||
Reference in New Issue
Block a user