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 { CreateCustomDomainDialog } from "./CreateCustomDomainDialog";
|
||||||
import { DeleteCustomDomainDialog } from "./DeleteCustomDomainDialog";
|
import { DeleteCustomDomainDialog } from "./DeleteCustomDomainDialog";
|
||||||
import { DomainDetailsDialog } from "./DomainDetailsDialog";
|
import { DomainDetailsDialog } from "./DomainDetailsDialog";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import { use } from "react";
|
||||||
|
|
||||||
const deleteCustomDomainMutation = graphql`
|
const deleteCustomDomainMutation = graphql`
|
||||||
mutation CustomDomainManagerDeleteMutation($input: DeleteCustomDomainInput!) {
|
mutation CustomDomainManagerDeleteMutation($input: DeleteCustomDomainInput!) {
|
||||||
@@ -45,7 +46,7 @@ export function CustomDomainManager({
|
|||||||
customDomain,
|
customDomain,
|
||||||
}: CustomDomainManagerProps) {
|
}: CustomDomainManagerProps) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [deleteCustomDomain] =
|
const [deleteCustomDomain] =
|
||||||
useMutationWithToasts<CustomDomainManagerDeleteMutation>(
|
useMutationWithToasts<CustomDomainManagerDeleteMutation>(
|
||||||
deleteCustomDomainMutation,
|
deleteCustomDomainMutation,
|
||||||
@@ -107,11 +108,11 @@ export function CustomDomainManager({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<Authorized entity="Organization" action="createCustomDomain">
|
{isAuthorized("Organization", "createCustomDomain") && (
|
||||||
<CreateCustomDomainDialog organizationId={organizationId}>
|
<CreateCustomDomainDialog organizationId={organizationId}>
|
||||||
<Button icon={IconPlusLarge}>{__("Add Domain")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add Domain")}</Button>
|
||||||
</CreateCustomDomainDialog>
|
</CreateCustomDomainDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -139,14 +140,14 @@ export function CustomDomainManager({
|
|||||||
<Button variant="secondary">{__("View Details")}</Button>
|
<Button variant="secondary">{__("View Details")}</Button>
|
||||||
</DomainDetailsDialog>
|
</DomainDetailsDialog>
|
||||||
|
|
||||||
<Authorized entity="CustomDomain" action="deleteCustomDomain">
|
{isAuthorized("CustomDomain", "deleteCustomDomain") && (
|
||||||
<DeleteCustomDomainDialog
|
<DeleteCustomDomainDialog
|
||||||
domainName={domain.domain}
|
domainName={domain.domain}
|
||||||
onConfirm={handleDeleteDomain}
|
onConfirm={handleDeleteDomain}
|
||||||
>
|
>
|
||||||
<Button variant="danger">{__("Delete")}</Button>
|
<Button variant="danger">{__("Delete")}</Button>
|
||||||
</DeleteCustomDomainDialog>
|
</DeleteCustomDomainDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
import { Suspense } from "react";
|
import { Suspense, use } from "react";
|
||||||
import { getAssignableRoles } from "/permissions";
|
import { getAssignableRoles } from "@probo/helpers";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const inviteMutation = graphql`
|
const inviteMutation = graphql`
|
||||||
mutation InviteUserDialogMutation(
|
mutation InviteUserDialogMutation(
|
||||||
@@ -56,7 +57,8 @@ type Props = PropsWithChildren & {
|
|||||||
function InviteUserDialogContent({ children, connectionId, onRefetch }: Props) {
|
function InviteUserDialogContent({ children, connectionId, onRefetch }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const assignableRoles = getAssignableRoles(organizationId);
|
const { role: currentUserRole } = use(PermissionsContext);
|
||||||
|
const assignableRoles = getAssignableRoles(currentUserRole);
|
||||||
const [inviteUser, isInviting] = useMutationWithToasts(inviteMutation, {
|
const [inviteUser, isInviting] = useMutationWithToasts(inviteMutation, {
|
||||||
successMessage: __("Invitation sent successfully"),
|
successMessage: __("Invitation sent successfully"),
|
||||||
errorMessage: __("Failed to send invitation"),
|
errorMessage: __("Failed to send invitation"),
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Badge, Button, Card } from "@probo/ui";
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { sprintf } from "@probo/helpers";
|
import { sprintf } from "@probo/helpers";
|
||||||
import type { TrustCenterGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
|
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 = {
|
type Props = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
@@ -11,7 +12,7 @@ type Props = {
|
|||||||
|
|
||||||
export function SlackConnections({ organizationId, slackConnections: connectedSlackConnections }: Props) {
|
export function SlackConnections({ organizationId, slackConnections: connectedSlackConnections }: Props) {
|
||||||
const { __, dateTimeFormat } = useTranslate();
|
const { __, dateTimeFormat } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const slackConnectionDefinitions = [
|
const slackConnectionDefinitions = [
|
||||||
{
|
{
|
||||||
id: "SLACK",
|
id: "SLACK",
|
||||||
@@ -77,11 +78,11 @@ export function SlackConnections({ organizationId, slackConnections: connectedSl
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Authorized entity="TrustCenter" action="updateTrustCenter">
|
isAuthorized("TrustCenter", "updateTrustCenter") && (
|
||||||
<Button variant="secondary" asChild>
|
<Button variant="secondary" asChild>
|
||||||
<a href={getUrl(slackConnection.id)}>{__("Connect")}</a>
|
<a href={getUrl(slackConnection.id)}>{__("Connect")}</a>
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { promisifyMutation } from "@probo/helpers";
|
||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -15,21 +18,17 @@ import {
|
|||||||
useConfirm,
|
useConfirm,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { Fragment } from "react";
|
import { Fragment, use } from "react";
|
||||||
import { graphql, useMutation, useRelayEnvironment } from "react-relay";
|
import { graphql, useMutation, useRelayEnvironment } from "react-relay";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { Link, useLocation, useParams } from "react-router";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import type { TaskFormDialogFragment$key } from "./__generated__/TaskFormDialogFragment.graphql";
|
||||||
import type { ItemOf } from "/types";
|
|
||||||
import TaskFormDialog, {
|
import TaskFormDialog, {
|
||||||
taskUpdateMutation,
|
taskUpdateMutation,
|
||||||
} from "/components/tasks/TaskFormDialog";
|
} 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 { updateStoreCounter } from "/hooks/useMutationWithIncrement";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import type { ItemOf } from "/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
tasks: ({
|
tasks: ({
|
||||||
@@ -51,7 +50,6 @@ type Props = {
|
|||||||
|
|
||||||
export default function TasksCard({ tasks, connectionId }: Props) {
|
export default function TasksCard({ tasks, connectionId }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
|
||||||
const hash = useLocation().hash.replace("#", "");
|
const hash = useLocation().hash.replace("#", "");
|
||||||
|
|
||||||
const hashes = [
|
const hashes = [
|
||||||
@@ -70,8 +68,10 @@ export default function TasksCard({ tasks, connectionId }: Props) {
|
|||||||
|
|
||||||
usePageTitle(__("Tasks"));
|
usePageTitle(__("Tasks"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organizationId, "Task", "updateTask") ||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
isAuthorized(organizationId, "Task", "deleteTask");
|
|
||||||
|
const hasAnyAction = isAuthorized("Task", "updateTask") ||
|
||||||
|
isAuthorized("Task", "deleteTask");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -151,6 +151,7 @@ function TaskRow(props: TaskRowProps) {
|
|||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const [deleteTask] = useMutation(deleteMutation);
|
const [deleteTask] = useMutation(deleteMutation);
|
||||||
const params = useParams<{ measureId?: string }>();
|
const params = useParams<{ measureId?: string }>();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const relayEnv = useRelayEnvironment();
|
const relayEnv = useRelayEnvironment();
|
||||||
const [updateTask, isUpdating] = useMutation(taskUpdateMutation);
|
const [updateTask, isUpdating] = useMutation(taskUpdateMutation);
|
||||||
@@ -232,15 +233,15 @@ function TaskRow(props: TaskRowProps) {
|
|||||||
)}
|
)}
|
||||||
{props.hasAnyAction && (
|
{props.hasAnyAction && (
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Task" action="updateTask">
|
{isAuthorized("Task", "updateTask") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
onClick={() => dialogRef.current?.open()}
|
onClick={() => dialogRef.current?.open()}
|
||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Task" action="deleteTask">
|
{isAuthorized("Task", "deleteTask") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -248,7 +249,7 @@ function TaskRow(props: TaskRowProps) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { useFragment } from "react-relay";
|
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 { sprintf, getAuditStateVariant, getAuditStateLabel, formatDate, getTrustCenterVisibilityOptions } from "@probo/helpers";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql";
|
import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const trustCenterAuditFragment = graphql`
|
const trustCenterAuditFragment = graphql`
|
||||||
fragment TrustCenterAuditsCardFragment on Audit {
|
fragment TrustCenterAuditsCardFragment on Audit {
|
||||||
@@ -124,8 +124,8 @@ function AuditRow(props: {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false;
|
const canUpdate = organizationId ? isAuthorized("TrustCenter", "updateTrustCenter") : false;
|
||||||
|
|
||||||
const handleValueChange = useCallback((value: string | {}) => {
|
const handleValueChange = useCallback((value: string | {}) => {
|
||||||
const stringValue = typeof value === 'string' ? value : '';
|
const stringValue = typeof value === 'string' ? value : '';
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ import {
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import type { TrustCenterDocumentsCardFragment$key } from "./__generated__/TrustCenterDocumentsCardFragment.graphql";
|
import type { TrustCenterDocumentsCardFragment$key } from "./__generated__/TrustCenterDocumentsCardFragment.graphql";
|
||||||
import { useFragment } from "react-relay";
|
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 { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const trustCenterDocumentFragment = graphql`
|
const trustCenterDocumentFragment = graphql`
|
||||||
fragment TrustCenterDocumentsCardFragment on Document {
|
fragment TrustCenterDocumentsCardFragment on Document {
|
||||||
@@ -129,8 +129,8 @@ function DocumentRow(props: {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false;
|
const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter");
|
||||||
|
|
||||||
const handleValueChange = useCallback((value: string | {}) => {
|
const handleValueChange = useCallback((value: string | {}) => {
|
||||||
const stringValue = typeof value === 'string' ? value : '';
|
const stringValue = typeof value === 'string' ? value : '';
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ import {
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import type { TrustCenterFilesCardFragment$key, TrustCenterFilesCardFragment$data } from "./__generated__/TrustCenterFilesCardFragment.graphql";
|
import type { TrustCenterFilesCardFragment$key, TrustCenterFilesCardFragment$data } from "./__generated__/TrustCenterFilesCardFragment.graphql";
|
||||||
import { useFragment } from "react-relay";
|
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 { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
|
||||||
import { formatDate } from "@probo/helpers";
|
import { formatDate } from "@probo/helpers";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
|
|
||||||
const trustCenterFileFragment = graphql`
|
const trustCenterFileFragment = graphql`
|
||||||
fragment TrustCenterFilesCardFragment on TrustCenterFile {
|
fragment TrustCenterFilesCardFragment on TrustCenterFile {
|
||||||
@@ -150,9 +148,8 @@ function FileRow(props: {
|
|||||||
const file = props.file;
|
const file = props.file;
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||||
const { organizationId } = useParams();
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter");
|
||||||
const canUpdate = organizationId ? isAuthorized(organizationId, "TrustCenter", "updateTrustCenter") : false;
|
|
||||||
|
|
||||||
const handleValueChange = useCallback((value: string | {}) => {
|
const handleValueChange = useCallback((value: string | {}) => {
|
||||||
const stringValue = typeof value === 'string' ? value : '';
|
const stringValue = typeof value === 'string' ? value : '';
|
||||||
@@ -207,7 +204,7 @@ function FileRow(props: {
|
|||||||
onClick={() => window.open(file.fileUrl, '_blank', 'noopener,noreferrer')}
|
onClick={() => window.open(file.fileUrl, '_blank', 'noopener,noreferrer')}
|
||||||
title={__("Download")}
|
title={__("Download")}
|
||||||
/>
|
/>
|
||||||
<Authorized entity="TrustCenterFile" action="updateTrustCenterFile">
|
{isAuthorized("TrustCenterFile", "updateTrustCenterFile") && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
@@ -215,8 +212,8 @@ function FileRow(props: {
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
title={__("Edit")}
|
title={__("Edit")}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="TrustCenterFile" action="deleteTrustCenterFile">
|
{isAuthorized("TrustCenterFile", "deleteTrustCenterFile") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -224,7 +221,7 @@ function FileRow(props: {
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
title={__("Delete")}
|
title={__("Delete")}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ import {
|
|||||||
IconPencil,
|
IconPencil,
|
||||||
IconArrowLink,
|
IconArrowLink,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { type ReactNode, useRef, useState } from "react";
|
import { type ReactNode, useRef, useState, use } from "react";
|
||||||
import {
|
import {
|
||||||
useTrustCenterReferences,
|
useTrustCenterReferences,
|
||||||
useUpdateTrustCenterReferenceRankMutation,
|
useUpdateTrustCenterReferenceRankMutation,
|
||||||
} from "/hooks/graph/TrustCenterReferenceGraph";
|
} from "/hooks/graph/TrustCenterReferenceGraph";
|
||||||
import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "./TrustCenterReferenceDialog";
|
import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "./TrustCenterReferenceDialog";
|
||||||
import { DeleteTrustCenterReferenceDialog } from "./DeleteTrustCenterReferenceDialog";
|
import { DeleteTrustCenterReferenceDialog } from "./DeleteTrustCenterReferenceDialog";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
trustCenterId: string;
|
trustCenterId: string;
|
||||||
@@ -41,6 +41,7 @@ type Reference = {
|
|||||||
|
|
||||||
export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
export function TrustCenterReferencesSection({ trustCenterId }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const dialogRef = useRef<TrustCenterReferenceDialogRef>(null);
|
const dialogRef = useRef<TrustCenterReferenceDialogRef>(null);
|
||||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||||
const [dragOverIndex, setDragOverIndex] = 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")}
|
{__("Showcase your customers and partners on your trust center")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Authorized entity="TrustCenter" action="createTrustCenterReference">
|
{isAuthorized("TrustCenter", "createTrustCenterReference") && (
|
||||||
<Button
|
<Button
|
||||||
icon={IconPlusLarge}
|
icon={IconPlusLarge}
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
>
|
>
|
||||||
{__("Add Reference")}
|
{__("Add Reference")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table>
|
<Table>
|
||||||
@@ -190,6 +191,7 @@ function ReferenceRow({
|
|||||||
onDrop,
|
onDrop,
|
||||||
}: ReferenceRowProps) {
|
}: ReferenceRowProps) {
|
||||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const className = [
|
const className = [
|
||||||
isDragging && "opacity-50 cursor-grabbing",
|
isDragging && "opacity-50 cursor-grabbing",
|
||||||
@@ -231,14 +233,14 @@ function ReferenceRow({
|
|||||||
icon={IconArrowLink}
|
icon={IconArrowLink}
|
||||||
onClick={onVisitWebsite}
|
onClick={onVisitWebsite}
|
||||||
/>
|
/>
|
||||||
<Authorized entity="TrustCenterReference" action="updateTrustCenterReference">
|
{isAuthorized("TrustCenterReference", "updateTrustCenterReference") && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="TrustCenterReference" action="deleteTrustCenterReference">
|
{isAuthorized("TrustCenterReference", "deleteTrustCenterReference") && (
|
||||||
<DeleteTrustCenterReferenceDialog
|
<DeleteTrustCenterReferenceDialog
|
||||||
referenceId={reference.id}
|
referenceId={reference.id}
|
||||||
referenceName={reference.name}
|
referenceName={reference.name}
|
||||||
@@ -249,7 +251,7 @@ function ReferenceRow({
|
|||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
/>
|
/>
|
||||||
</DeleteTrustCenterReferenceDialog>
|
</DeleteTrustCenterReferenceDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { useFragment } from "react-relay";
|
import { useFragment } from "react-relay";
|
||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState, use } from "react";
|
||||||
import { sprintf } from "@probo/helpers";
|
import { sprintf } from "@probo/helpers";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { isAuthorized } from "/permissions/permissions";
|
|
||||||
import type { TrustCenterVendorsCardFragment$key } from "./__generated__/TrustCenterVendorsCardFragment.graphql";
|
import type { TrustCenterVendorsCardFragment$key } from "./__generated__/TrustCenterVendorsCardFragment.graphql";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const trustCenterVendorFragment = graphql`
|
const trustCenterVendorFragment = graphql`
|
||||||
fragment TrustCenterVendorsCardFragment on Vendor {
|
fragment TrustCenterVendorsCardFragment on Vendor {
|
||||||
@@ -49,44 +49,14 @@ type Props<Params> = {
|
|||||||
|
|
||||||
export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
|
export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
|
||||||
const [limit, setLimit] = useState<number | null>(100);
|
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(() => {
|
const vendors = useMemo(() => {
|
||||||
return limit ? props.vendors.slice(0, limit) : props.vendors;
|
return limit ? props.vendors.slice(0, limit) : props.vendors;
|
||||||
}, [props.vendors, limit]);
|
}, [props.vendors, limit]);
|
||||||
const showMoreButton = limit !== null && props.vendors.length > 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) => {
|
const onToggleVisibility = (vendorId: string, showOnTrustCenter: boolean) => {
|
||||||
props.onToggleVisibility({
|
props.onToggleVisibility({
|
||||||
variables: {
|
variables: {
|
||||||
|
|||||||
@@ -39,15 +39,14 @@ import {
|
|||||||
UserDropdown as UserDropdownRoot,
|
UserDropdown as UserDropdownRoot,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { Suspense, useEffect, useState } from "react";
|
import { Suspense, use, useEffect, useState } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
import { Link, Navigate, Outlet, useParams } from "react-router";
|
import { Link, Navigate, Outlet, useParams } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import type { MainLayoutQuery as MainLayoutQueryType } from "./__generated__/MainLayoutQuery.graphql";
|
import type { MainLayoutQuery as MainLayoutQueryType } from "./__generated__/MainLayoutQuery.graphql";
|
||||||
import { PageError } from "/components/PageError";
|
import { PageError } from "/components/PageError";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext, PermissionsProvider } from "/providers/PermissionsProvider";
|
||||||
import { PermissionsProvider } from "/providers/PermissionsProvider";
|
|
||||||
import { buildEndpoint } from "/providers/RelayProviders";
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
|
|
||||||
const MainLayoutQuery = graphql`
|
const MainLayoutQuery = graphql`
|
||||||
@@ -98,6 +97,7 @@ function MainLayoutContent({
|
|||||||
prefix: string;
|
prefix: string;
|
||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const data = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
const data = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
||||||
organizationId,
|
organizationId,
|
||||||
});
|
});
|
||||||
@@ -116,132 +116,132 @@ function MainLayoutContent({
|
|||||||
}
|
}
|
||||||
sidebar={
|
sidebar={
|
||||||
<ul className="space-y-[2px]">
|
<ul className="space-y-[2px]">
|
||||||
<Authorized entity="Organization" action="listMeetings">
|
{isAuthorized("Organization", "listMeetings") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Meetings")}
|
label={__("Meetings")}
|
||||||
icon={IconCalendar1}
|
icon={IconCalendar1}
|
||||||
to={`${prefix}/meetings`}
|
to={`${prefix}/meetings`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listTasks">
|
{isAuthorized("Organization", "listTasks") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Tasks")}
|
label={__("Tasks")}
|
||||||
icon={IconInboxEmpty}
|
icon={IconInboxEmpty}
|
||||||
to={`${prefix}/tasks`}
|
to={`${prefix}/tasks`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listMeasures">
|
{isAuthorized("Organization", "listMeasures") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Measures")}
|
label={__("Measures")}
|
||||||
icon={IconTodo}
|
icon={IconTodo}
|
||||||
to={`${prefix}/measures`}
|
to={`${prefix}/measures`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listRisks">
|
{isAuthorized("Organization", "listRisks") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Risks")}
|
label={__("Risks")}
|
||||||
icon={IconFire3}
|
icon={IconFire3}
|
||||||
to={`${prefix}/risks`}
|
to={`${prefix}/risks`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listFrameworks">
|
{isAuthorized("Organization", "listFrameworks") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Frameworks")}
|
label={__("Frameworks")}
|
||||||
icon={IconBank}
|
icon={IconBank}
|
||||||
to={`${prefix}/frameworks`}
|
to={`${prefix}/frameworks`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listPeople">
|
{isAuthorized("Organization", "listPeople") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("People")}
|
label={__("People")}
|
||||||
icon={IconGroup1}
|
icon={IconGroup1}
|
||||||
to={`${prefix}/people`}
|
to={`${prefix}/people`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listVendors">
|
{isAuthorized("Organization", "listVendors") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Vendors")}
|
label={__("Vendors")}
|
||||||
icon={IconStore}
|
icon={IconStore}
|
||||||
to={`${prefix}/vendors`}
|
to={`${prefix}/vendors`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listDocuments">
|
{isAuthorized("Organization", "listDocuments") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Documents")}
|
label={__("Documents")}
|
||||||
icon={IconPageTextLine}
|
icon={IconPageTextLine}
|
||||||
to={`${prefix}/documents`}
|
to={`${prefix}/documents`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listAssets">
|
{isAuthorized("Organization", "listAssets") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Assets")}
|
label={__("Assets")}
|
||||||
icon={IconBox}
|
icon={IconBox}
|
||||||
to={`${prefix}/assets`}
|
to={`${prefix}/assets`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listData">
|
{isAuthorized("Organization", "listData") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Data")}
|
label={__("Data")}
|
||||||
icon={IconListStack}
|
icon={IconListStack}
|
||||||
to={`${prefix}/data`}
|
to={`${prefix}/data`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listAudits">
|
{isAuthorized("Organization", "listAudits") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Audits")}
|
label={__("Audits")}
|
||||||
icon={IconMedal}
|
icon={IconMedal}
|
||||||
to={`${prefix}/audits`}
|
to={`${prefix}/audits`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listNonconformities">
|
{isAuthorized("Organization", "listNonconformities") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Nonconformities")}
|
label={__("Nonconformities")}
|
||||||
icon={IconCrossLargeX}
|
icon={IconCrossLargeX}
|
||||||
to={`${prefix}/nonconformities`}
|
to={`${prefix}/nonconformities`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listObligations">
|
{isAuthorized("Organization", "listObligations") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Obligations")}
|
label={__("Obligations")}
|
||||||
icon={IconBook}
|
icon={IconBook}
|
||||||
to={`${prefix}/obligations`}
|
to={`${prefix}/obligations`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listContinualImprovements">
|
{isAuthorized("Organization", "listContinualImprovements") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Continual Improvements")}
|
label={__("Continual Improvements")}
|
||||||
icon={IconRotateCw}
|
icon={IconRotateCw}
|
||||||
to={`${prefix}/continual-improvements`}
|
to={`${prefix}/continual-improvements`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listProcessingActivities">
|
{isAuthorized("Organization", "listProcessingActivities") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Processing Activities")}
|
label={__("Processing Activities")}
|
||||||
icon={IconCircleProgress}
|
icon={IconCircleProgress}
|
||||||
to={`${prefix}/processing-activities`}
|
to={`${prefix}/processing-activities`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listSnapshots">
|
{isAuthorized("Organization", "listSnapshots") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Snapshots")}
|
label={__("Snapshots")}
|
||||||
icon={IconClock}
|
icon={IconClock}
|
||||||
to={`${prefix}/snapshots`}
|
to={`${prefix}/snapshots`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="getTrustCenter">
|
{isAuthorized("Organization", "getTrustCenter") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Trust Center")}
|
label={__("Trust Center")}
|
||||||
icon={IconShield}
|
icon={IconShield}
|
||||||
to={`${prefix}/trust-center`}
|
to={`${prefix}/trust-center`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="listMembers">
|
{isAuthorized("Organization", "listMembers") && (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
label={__("Settings")}
|
label={__("Settings")}
|
||||||
icon={IconSettingsGear2}
|
icon={IconSettingsGear2}
|
||||||
to={`${prefix}/settings`}
|
to={`${prefix}/settings`}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -255,6 +255,7 @@ function MainLayoutContent({
|
|||||||
function UserDropdown({ organizationId }: { organizationId: string }) {
|
function UserDropdown({ organizationId }: { organizationId: string }) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const user = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
const user = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, {
|
||||||
organizationId,
|
organizationId,
|
||||||
}).viewer.user;
|
}).viewer.user;
|
||||||
@@ -290,13 +291,13 @@ function UserDropdown({ organizationId }: { organizationId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
<UserDropdownRoot fullName={user.fullName} email={user.email}>
|
||||||
<Authorized entity="Organization" action="deleteOrganization">
|
{isAuthorized("Organization", "deleteOrganization") && (
|
||||||
<UserDropdownItem
|
<UserDropdownItem
|
||||||
to="/api-keys"
|
to="/api-keys"
|
||||||
icon={IconKey}
|
icon={IconKey}
|
||||||
label={__("API Keys")}
|
label={__("API Keys")}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<UserDropdownItem
|
<UserDropdownItem
|
||||||
to="mailto:support@getprobo.com"
|
to="mailto:support@getprobo.com"
|
||||||
icon={IconCircleQuestionmark}
|
icon={IconCircleQuestionmark}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectFiel
|
|||||||
import type { AssetGraphNodeQuery } from "/hooks/graph/__generated__/AssetGraphNodeQuery.graphql";
|
import type { AssetGraphNodeQuery } from "/hooks/graph/__generated__/AssetGraphNodeQuery.graphql";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const updateAssetSchema = z.object({
|
const updateAssetSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
@@ -51,7 +52,7 @@ export default function AssetDetailsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
if (!assetEntry || !assetEntry.id) {
|
if (!assetEntry || !assetEntry.id) {
|
||||||
return <div>{__("Asset not found")}</div>;
|
return <div>{__("Asset not found")}</div>;
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,7 @@ export default function AssetDetailsPage(props: Props) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Asset" action="deleteAsset">
|
isAuthorized("Asset", "deleteAsset") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -126,7 +127,7 @@ export default function AssetDetailsPage(props: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -182,11 +183,11 @@ export default function AssetDetailsPage(props: Props) {
|
|||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{formState.isDirty && !isSnapshotMode && (
|
{formState.isDirty && !isSnapshotMode && (
|
||||||
<Authorized entity="Asset" action="updateAsset">
|
isAuthorized("Asset", "updateAsset") && (
|
||||||
<Button type="submit" disabled={formState.isSubmitting}>
|
<Button type="submit" disabled={formState.isSubmitting}>
|
||||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
} from "/hooks/graph/AssetGraph";
|
} from "/hooks/graph/AssetGraph";
|
||||||
import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql";
|
import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { PermissionsContext } from "/providers/PermissionsProvider";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const paginatedAssetsFragment = graphql`
|
const paginatedAssetsFragment = graphql`
|
||||||
fragment AssetsPageFragment on Organization
|
fragment AssetsPageFragment on Organization
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime, formatError, formatDate, type GraphQLError } from "@probo/helpers";
|
import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprintf, formatDatetime, formatError, formatDate, type GraphQLError } from "@probo/helpers";
|
||||||
import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql";
|
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({
|
const updateAuditSchema = z.object({
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
@@ -52,7 +53,7 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
const auditEntry = audit.node;
|
const auditEntry = audit.node;
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
if (!auditEntry || !auditEntry.id || !auditEntry.framework) {
|
if (!auditEntry || !auditEntry.id || !auditEntry.framework) {
|
||||||
return <div>{__("Audit not found")}</div>;
|
return <div>{__("Audit not found")}</div>;
|
||||||
}
|
}
|
||||||
@@ -146,7 +147,7 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<Authorized entity="Audit" action="deleteAudit">
|
{isAuthorized("Audit", "deleteAudit") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -154,7 +155,7 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -187,11 +188,11 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{formState.isDirty && (
|
{formState.isDirty && (
|
||||||
<Authorized entity="Audit" action="updateAudit">
|
isAuthorized("Audit", "updateAudit") && (
|
||||||
<Button type="submit" disabled={formState.isSubmitting}>
|
<Button type="submit" disabled={formState.isSubmitting}>
|
||||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import type {
|
|||||||
AuditsPageFragment$key,
|
AuditsPageFragment$key,
|
||||||
} from "./__generated__/AuditsPageFragment.graphql";
|
} from "./__generated__/AuditsPageFragment.graphql";
|
||||||
import { SortableTable } from "/components/SortableTable";
|
import { SortableTable } from "/components/SortableTable";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
|
||||||
const paginatedAuditsFragment = graphql`
|
const paginatedAuditsFragment = graphql`
|
||||||
fragment AuditsPageFragment on Organization
|
fragment AuditsPageFragment on Organization
|
||||||
@@ -83,6 +83,7 @@ type Props = {
|
|||||||
export default function AuditsPage(props: Props) {
|
export default function AuditsPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const data = usePreloadedQuery(auditsQuery, props.queryRef);
|
const data = usePreloadedQuery(auditsQuery, props.queryRef);
|
||||||
const pagination = usePaginationFragment(
|
const pagination = usePaginationFragment(
|
||||||
@@ -94,8 +95,8 @@ export default function AuditsPage(props: Props) {
|
|||||||
|
|
||||||
usePageTitle(__("Audits"));
|
usePageTitle(__("Audits"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organizationId, "Audit", "updateAudit") ||
|
const hasAnyAction = isAuthorized("Audit", "updateAudit") ||
|
||||||
isAuthorized(organizationId, "Audit", "deleteAudit");
|
isAuthorized("Audit", "deleteAudit");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -105,14 +106,14 @@ export default function AuditsPage(props: Props) {
|
|||||||
"Manage your organization's compliance audits and their progress."
|
"Manage your organization's compliance audits and their progress."
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="createAudit">
|
{isAuthorized("Organization", "createAudit") && (
|
||||||
<CreateAuditDialog
|
<CreateAuditDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
>
|
>
|
||||||
<Button icon={IconPlusLarge}>{__("Add audit")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add audit")}</Button>
|
||||||
</CreateAuditDialog>
|
</CreateAuditDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<SortableTable {...pagination}>
|
<SortableTable {...pagination}>
|
||||||
<Thead>
|
<Thead>
|
||||||
@@ -153,6 +154,7 @@ function AuditRow({
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const deleteAudit = useDeleteAudit(entry, connectionId);
|
const deleteAudit = useDeleteAudit(entry, connectionId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr to={`/organizations/${organizationId}/audits/${entry.id}`}>
|
<Tr to={`/organizations/${organizationId}/audits/${entry.id}`}>
|
||||||
@@ -177,7 +179,7 @@ function AuditRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Audit" action="deleteAudit">
|
{isAuthorized("Audit", "deleteAudit") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={deleteAudit}
|
onClick={deleteAudit}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -185,7 +187,7 @@ function AuditRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ import z from "zod";
|
|||||||
import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency } from "@probo/helpers";
|
import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency } from "@probo/helpers";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import type { ContinualImprovementGraphNodeQuery } from "/hooks/graph/__generated__/ContinualImprovementGraphNodeQuery.graphql";
|
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({
|
const updateImprovementSchema = z.object({
|
||||||
referenceId: z.string().min(1, "Reference ID is required"),
|
referenceId: z.string().min(1, "Reference ID is required"),
|
||||||
@@ -59,7 +60,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
if (!improvement) {
|
if (!improvement) {
|
||||||
return <div>{__("Continual improvement entry not found")}</div>;
|
return <div>{__("Continual improvement entry not found")}</div>;
|
||||||
}
|
}
|
||||||
@@ -150,13 +151,13 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="ContinualImprovement" action="deleteContinualImprovement">
|
isAuthorized("ContinualImprovement", "deleteContinualImprovement") && (
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem onClick={deleteImprovement} variant="danger">
|
<DropdownItem onClick={deleteImprovement} variant="danger">
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -289,7 +290,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
|||||||
|
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<div className="flex justify-end pt-4">
|
<div className="flex justify-end pt-4">
|
||||||
<Authorized entity="ContinualImprovement" action="updateContinualImprovement">
|
{isAuthorized("ContinualImprovement", "updateContinualImprovement") && (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -297,7 +298,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ import type {
|
|||||||
ContinualImprovementsPageFragment$key,
|
ContinualImprovementsPageFragment$key,
|
||||||
ContinualImprovementsPageFragment$data,
|
ContinualImprovementsPageFragment$data,
|
||||||
} from "./__generated__/ContinualImprovementsPageFragment.graphql";
|
} from "./__generated__/ContinualImprovementsPageFragment.graphql";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
interface ContinualImprovementsPageProps {
|
interface ContinualImprovementsPageProps {
|
||||||
queryRef: PreloadedQuery<ContinualImprovementsPageQuery>;
|
queryRef: PreloadedQuery<ContinualImprovementsPageQuery>;
|
||||||
@@ -93,6 +93,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
usePageTitle(__("Continual Improvements"));
|
usePageTitle(__("Continual Improvements"));
|
||||||
|
|
||||||
@@ -130,8 +131,8 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove
|
|||||||
const improvements = data?.continualImprovements?.edges?.map((edge) => edge.node) ?? [];
|
const improvements = data?.continualImprovements?.edges?.map((edge) => edge.node) ?? [];
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "ContinualImprovement", "updateContinualImprovement") ||
|
isAuthorized("ContinualImprovement", "updateContinualImprovement") ||
|
||||||
isAuthorized(organizationId, "ContinualImprovement", "deleteContinualImprovement")
|
isAuthorized("ContinualImprovement", "deleteContinualImprovement")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -141,7 +142,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove
|
|||||||
)}
|
)}
|
||||||
<PageHeader title={__("Continual Improvements")} description={__("Manage your continual improvements.")}>
|
<PageHeader title={__("Continual Improvements")} description={__("Manage your continual improvements.")}>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Organization" action="createContinualImprovement">
|
isAuthorized("Organization", "createContinualImprovement") && (
|
||||||
<CreateContinualImprovementDialog
|
<CreateContinualImprovementDialog
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
connectionId={connectionId}
|
connectionId={connectionId}
|
||||||
@@ -150,7 +151,7 @@ export default function ContinualImprovementsPage({ queryRef }: ContinualImprove
|
|||||||
{__("Add continual improvement")}
|
{__("Add continual improvement")}
|
||||||
</Button>
|
</Button>
|
||||||
</CreateContinualImprovementDialog>
|
</CreateContinualImprovementDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -225,6 +226,7 @@ function ImprovementRow({
|
|||||||
const [deleteImprovement] = useMutation(deleteContinualImprovementMutation);
|
const [deleteImprovement] = useMutation(deleteContinualImprovementMutation);
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
@@ -282,7 +284,7 @@ function ImprovementRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="ContinualImprovement" action="deleteContinualImprovement">
|
{isAuthorized("ContinualImprovement", "deleteContinualImprovement") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -290,7 +292,7 @@ function ImprovementRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ import type {
|
|||||||
import type { DataListQuery } from "./__generated__/DataListQuery.graphql";
|
import type { DataListQuery } from "./__generated__/DataListQuery.graphql";
|
||||||
import { SortableTable } from "/components/SortableTable";
|
import { SortableTable } from "/components/SortableTable";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const paginatedDataFragment = graphql`
|
const paginatedDataFragment = graphql`
|
||||||
fragment DataPageFragment on Organization
|
fragment DataPageFragment on Organization
|
||||||
@@ -93,6 +93,7 @@ export default function DataPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const queryData = usePreloadedQuery<DatumGraphListQuery>(
|
const queryData = usePreloadedQuery<DatumGraphListQuery>(
|
||||||
dataQuery,
|
dataQuery,
|
||||||
@@ -121,8 +122,9 @@ export default function DataPage(props: Props) {
|
|||||||
|
|
||||||
usePageTitle(__("Data"));
|
usePageTitle(__("Data"));
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && ( isAuthorized(organizationId, "Datum", "updateDatum") ||
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "Datum", "deleteDatum")
|
isAuthorized("Datum", "updateDatum") ||
|
||||||
|
isAuthorized("Datum", "deleteDatum")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -137,7 +139,7 @@ export default function DataPage(props: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!snapshotId && (
|
{!snapshotId && (
|
||||||
<Authorized entity="Organization" action="createDatum">
|
isAuthorized("Organization", "createDatum") && (
|
||||||
<CreateDatumDialog
|
<CreateDatumDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
@@ -145,7 +147,7 @@ export default function DataPage(props: Props) {
|
|||||||
>
|
>
|
||||||
<Button icon={IconPlusLarge}>{__("Add data")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add data")}</Button>
|
||||||
</CreateDatumDialog>
|
</CreateDatumDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<SortableTable
|
<SortableTable
|
||||||
@@ -186,7 +188,7 @@ function DataRow({
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const deleteDatum = useDeleteDatum(entry, connectionId);
|
const deleteDatum = useDeleteDatum(entry, connectionId);
|
||||||
const vendors = entry.vendors?.edges.map((edge) => edge.node) ?? [];
|
const vendors = entry.vendors?.edges.map((edge) => edge.node) ?? [];
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const detailUrl = snapshotId
|
const detailUrl = snapshotId
|
||||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/data/${entry.id}`
|
? `/organizations/${organizationId}/snapshots/${snapshotId}/data/${entry.id}`
|
||||||
: `/organizations/${organizationId}/data/${entry.id}`;
|
: `/organizations/${organizationId}/data/${entry.id}`;
|
||||||
@@ -228,7 +230,7 @@ function DataRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Datum" action="deleteDatum">
|
{isAuthorized("Datum", "deleteDatum") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={deleteDatum}
|
onClick={deleteDatum}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -236,7 +238,7 @@ function DataRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import z from "zod";
|
|||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { validateSnapshotConsistency } from "@probo/helpers";
|
import { validateSnapshotConsistency } from "@probo/helpers";
|
||||||
import type { DatumGraphNodeQuery } from "/hooks/graph/__generated__/DatumGraphNodeQuery.graphql";
|
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({
|
const updateDatumSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
@@ -57,6 +58,7 @@ export default function DatumDetailsPage(props: Props) {
|
|||||||
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const deleteDatum = useDeleteDatum(
|
const deleteDatum = useDeleteDatum(
|
||||||
datumEntry,
|
datumEntry,
|
||||||
@@ -125,7 +127,7 @@ export default function DatumDetailsPage(props: Props) {
|
|||||||
<Badge variant="info">{datumEntry?.dataClassification}</Badge>
|
<Badge variant="info">{datumEntry?.dataClassification}</Badge>
|
||||||
</div>
|
</div>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Datum" action="deleteDatum">
|
isAuthorized("Datum", "deleteDatum") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -135,7 +137,7 @@ export default function DatumDetailsPage(props: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -180,11 +182,11 @@ export default function DatumDetailsPage(props: Props) {
|
|||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{formState.isDirty && (
|
{formState.isDirty && (
|
||||||
<Authorized entity="Datum" action="updateDatum">
|
isAuthorized("Datum", "updateDatum") && (
|
||||||
<Button type="submit" disabled={formState.isSubmitting}>
|
<Button type="submit" disabled={formState.isSubmitting}>
|
||||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import {
|
|||||||
PdfDownloadDialog,
|
PdfDownloadDialog,
|
||||||
type PdfDownloadDialogRef,
|
type PdfDownloadDialogRef,
|
||||||
} from "/components/documents/PdfDownloadDialog";
|
} from "/components/documents/PdfDownloadDialog";
|
||||||
import { useRef, useState } from "react";
|
import { use, useRef, useState } from "react";
|
||||||
import type { NodeOf } from "/types.ts";
|
import type { NodeOf } from "/types.ts";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||||
@@ -76,7 +76,7 @@ import { DocumentTypeOptions } from "/components/form/DocumentTypeOptions";
|
|||||||
import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions";
|
import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<DocumentGraphNodeQuery>;
|
queryRef: PreloadedQuery<DocumentGraphNodeQuery>;
|
||||||
@@ -132,8 +132,12 @@ const documentFragment = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
graphql`
|
const publishDocumentVersionMutation = graphql`
|
||||||
fragment DocumentDetailPageRowFragment on Document {
|
mutation DocumentDetailPagePublishMutation(
|
||||||
|
$input: PublishDocumentVersionInput!
|
||||||
|
) {
|
||||||
|
publishDocumentVersion(input: $input) {
|
||||||
|
document {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
description
|
description
|
||||||
@@ -161,17 +165,6 @@ graphql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
|
||||||
const publishDocumentVersionMutation = graphql`
|
|
||||||
mutation DocumentDetailPagePublishMutation(
|
|
||||||
$input: PublishDocumentVersionInput!
|
|
||||||
) {
|
|
||||||
publishDocumentVersion(input: $input) {
|
|
||||||
document {
|
|
||||||
id
|
|
||||||
...DocumentDetailPageRowFragment
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -230,6 +223,7 @@ export default function DocumentDetailPage(props: Props) {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||||
const [isEditingOwner, setIsEditingOwner] = useState(false);
|
const [isEditingOwner, setIsEditingOwner] = useState(false);
|
||||||
@@ -522,16 +516,16 @@ export default function DocumentDetailPage(props: Props) {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<Authorized entity="Document" action="updateDocument">
|
{isAuthorized("Document", "updateDocument") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => updateDialogRef.current?.open()}
|
onClick={() => updateDialogRef.current?.open()}
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
>
|
>
|
||||||
{isDraft ? __("Edit draft document") : __("Create new draft")}
|
{isDraft ? __("Edit draft document") : __("Create new draft")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
{isDraft && versions.length > 1 && (
|
{isDraft && versions.length > 1 && (
|
||||||
<Authorized entity="Document" action="deleteDocument">
|
isAuthorized("Document", "deleteDocument") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={handleDeleteDraft}
|
onClick={handleDeleteDraft}
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -539,7 +533,7 @@ export default function DocumentDetailPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Delete draft document")}
|
{__("Delete draft document")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => pdfDownloadDialogRef.current?.open()}
|
onClick={() => pdfDownloadDialogRef.current?.open()}
|
||||||
@@ -548,7 +542,7 @@ export default function DocumentDetailPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Download PDF")}
|
{__("Download PDF")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<Authorized entity="Document" action="deleteDocument">
|
{isAuthorized("Document", "deleteDocument") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -557,7 +551,7 @@ export default function DocumentDetailPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Delete document")}
|
{__("Delete document")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
useLazyLoadQuery,
|
useLazyLoadQuery,
|
||||||
type PreloadedQuery,
|
type PreloadedQuery,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useRef } from "react";
|
import { use, useRef } from "react";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import type { DocumentGraphListQuery } from "/hooks/graph/__generated__/DocumentGraphListQuery.graphql";
|
import type { DocumentGraphListQuery } from "/hooks/graph/__generated__/DocumentGraphListQuery.graphql";
|
||||||
import {
|
import {
|
||||||
@@ -57,8 +57,7 @@ import {
|
|||||||
type BulkExportDialogRef,
|
type BulkExportDialogRef,
|
||||||
} from "/components/documents/BulkExportDialog";
|
} from "/components/documents/BulkExportDialog";
|
||||||
import type { DocumentsPageUserEmailQuery } from "./__generated__/DocumentsPageUserEmailQuery.graphql";
|
import type { DocumentsPageUserEmailQuery } from "./__generated__/DocumentsPageUserEmailQuery.graphql";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsProvider.tsx";
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
|
|
||||||
const documentsFragment = graphql`
|
const documentsFragment = graphql`
|
||||||
fragment DocumentsPageListFragment on Organization
|
fragment DocumentsPageListFragment on Organization
|
||||||
@@ -107,6 +106,7 @@ const UserEmailQuery = graphql`
|
|||||||
|
|
||||||
export default function DocumentsPage(props: Props) {
|
export default function DocumentsPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const organization = usePreloadedQuery(
|
const organization = usePreloadedQuery(
|
||||||
documentsQuery,
|
documentsQuery,
|
||||||
@@ -137,8 +137,8 @@ export default function DocumentsPage(props: Props) {
|
|||||||
|
|
||||||
usePageTitle(__("Documents"));
|
usePageTitle(__("Documents"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organization.id, "Document", "updateDocument") ||
|
const hasAnyAction = isAuthorized("Document", "updateDocument") ||
|
||||||
isAuthorized(organization.id, "Document", "deleteDocument");
|
isAuthorized("Document", "deleteDocument");
|
||||||
|
|
||||||
const handleSendSigningNotifications = () => {
|
const handleSendSigningNotifications = () => {
|
||||||
sendSigningNotifications({
|
sendSigningNotifications({
|
||||||
@@ -198,7 +198,7 @@ export default function DocumentsPage(props: Props) {
|
|||||||
description={__("Manage your organization's documents")}
|
description={__("Manage your organization's documents")}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Authorized entity="Document" action="sendSigningNotifications">
|
{isAuthorized("Document", "sendSigningNotifications") && (
|
||||||
<Button
|
<Button
|
||||||
icon={IconBell2}
|
icon={IconBell2}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -206,13 +206,13 @@ export default function DocumentsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Send signing notifications")}
|
{__("Send signing notifications")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="createDocument">
|
{isAuthorized("Organization", "createDocument") && (
|
||||||
<CreateDocumentDialog
|
<CreateDocumentDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
|
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
{documents.length > 0 ? (
|
{documents.length > 0 ? (
|
||||||
@@ -258,7 +258,7 @@ export default function DocumentsPage(props: Props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Authorized entity="Document" action="updateDocument">
|
{isAuthorized("Document", "updateDocument") && (
|
||||||
<PublishDocumentsDialog
|
<PublishDocumentsDialog
|
||||||
documentIds={selection}
|
documentIds={selection}
|
||||||
onSave={clear}
|
onSave={clear}
|
||||||
@@ -270,8 +270,8 @@ export default function DocumentsPage(props: Props) {
|
|||||||
{__("Publish")}
|
{__("Publish")}
|
||||||
</Button>
|
</Button>
|
||||||
</PublishDocumentsDialog>
|
</PublishDocumentsDialog>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Document" action="bulkRequestSignatures">
|
{isAuthorized("Document", "bulkRequestSignatures") && (
|
||||||
<SignatureDocumentsDialog
|
<SignatureDocumentsDialog
|
||||||
documentIds={selection}
|
documentIds={selection}
|
||||||
onSave={clear}
|
onSave={clear}
|
||||||
@@ -284,7 +284,7 @@ export default function DocumentsPage(props: Props) {
|
|||||||
{__("Request signature")}
|
{__("Request signature")}
|
||||||
</Button>
|
</Button>
|
||||||
</SignatureDocumentsDialog>
|
</SignatureDocumentsDialog>
|
||||||
</Authorized>
|
)}
|
||||||
<BulkExportDialog
|
<BulkExportDialog
|
||||||
ref={bulkExportDialogRef}
|
ref={bulkExportDialogRef}
|
||||||
onExport={handleBulkExport}
|
onExport={handleBulkExport}
|
||||||
@@ -300,7 +300,7 @@ export default function DocumentsPage(props: Props) {
|
|||||||
{__("Export")}
|
{__("Export")}
|
||||||
</Button>
|
</Button>
|
||||||
</BulkExportDialog>
|
</BulkExportDialog>
|
||||||
<Authorized entity="Document" action="deleteDocument">
|
{isAuthorized("Document", "deleteDocument") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -309,7 +309,7 @@ export default function DocumentsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Th>
|
</Th>
|
||||||
@@ -392,6 +392,7 @@ function DocumentRow({
|
|||||||
onCheck: () => void;
|
onCheck: () => void;
|
||||||
hasAnyAction: boolean;
|
hasAnyAction: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const document = useFragment<DocumentsPageRowFragment$key>(
|
const document = useFragment<DocumentsPageRowFragment$key>(
|
||||||
rowFragment,
|
rowFragment,
|
||||||
documentKey
|
documentKey
|
||||||
@@ -465,7 +466,7 @@ function DocumentRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end w-18">
|
<Td noLink width={50} className="text-end w-18">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Document" action="deleteDocument">
|
{isAuthorized("Document", "deleteDocument") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -473,7 +474,7 @@ function DocumentRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
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 type { ItemOf, NodeOf } from "/types";
|
||||||
import { graphql, useFragment, useRefetchableFragment } from "react-relay";
|
import { graphql, useFragment, useRefetchableFragment } from "react-relay";
|
||||||
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
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_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 { DocumentSignaturesTab_version$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts";
|
||||||
import type { DocumentSignaturesTabRefetchQuery } from "./__generated__/DocumentSignaturesTabRefetchQuery.graphql";
|
import type { DocumentSignaturesTabRefetchQuery } from "./__generated__/DocumentSignaturesTabRefetchQuery.graphql";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Version = NodeOf<DocumentDetailPageDocumentFragment$data["versions"]>;
|
type Version = NodeOf<DocumentDetailPageDocumentFragment$data["versions"]>;
|
||||||
|
|
||||||
@@ -246,6 +246,7 @@ function SignatureItem(props: {
|
|||||||
}) {
|
}) {
|
||||||
const signature = useFragment(signatureFragment, props.signature);
|
const signature = useFragment(signatureFragment, props.signature);
|
||||||
const { __, dateTimeFormat } = useTranslate();
|
const { __, dateTimeFormat } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [requestSignature, isSendingRequest] = useMutationWithToasts(
|
const [requestSignature, isSendingRequest] = useMutationWithToasts(
|
||||||
requestSignatureMutation,
|
requestSignatureMutation,
|
||||||
{
|
{
|
||||||
@@ -275,7 +276,7 @@ function SignatureItem(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{props.signable && (
|
{props.signable && (
|
||||||
<Authorized entity="Document" action="requestSignature">
|
isAuthorized("Document", "requestSignature") && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
@@ -294,7 +295,7 @@ function SignatureItem(props: {
|
|||||||
>
|
>
|
||||||
{__("Request signature")}
|
{__("Request signature")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -331,7 +332,7 @@ function SignatureItem(props: {
|
|||||||
{__("Signed")}
|
{__("Signed")}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Authorized entity="DocumentVersionSignature" action="cancelSignatureRequest">
|
isAuthorized("DocumentVersionSignature", "cancelSignatureRequest") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
@@ -349,7 +350,7 @@ function SignatureItem(props: {
|
|||||||
>
|
>
|
||||||
{__("Cancel request")}
|
{__("Cancel request")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import { promisifyMutation } from "@probo/helpers";
|
|||||||
import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql";
|
import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql";
|
||||||
import { frameworkControlNodeQuery } from "/hooks/graph/FrameworkGraph";
|
import { frameworkControlNodeQuery } from "/hooks/graph/FrameworkGraph";
|
||||||
import type { FrameworkDetailPageFragment$data } from "./__generated__/FrameworkDetailPageFragment.graphql";
|
import type { FrameworkDetailPageFragment$data } from "./__generated__/FrameworkDetailPageFragment.graphql";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const attachMeasureMutation = graphql`
|
const attachMeasureMutation = graphql`
|
||||||
mutation FrameworkControlPageAttachMutation(
|
mutation FrameworkControlPageAttachMutation(
|
||||||
@@ -166,7 +167,7 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [detachMeasure, isDetachingMeasure] = useMutation(detachMeasureMutation);
|
const [detachMeasure, isDetachingMeasure] = useMutation(detachMeasureMutation);
|
||||||
const [attachMeasure, isAttachingMeasure] = useMutation(attachMeasureMutation);
|
const [attachMeasure, isAttachingMeasure] = useMutation(attachMeasureMutation);
|
||||||
const [detachDocument, isDetachingDocument] = useMutation(detachDocumentMutation);
|
const [detachDocument, isDetachingDocument] = useMutation(detachDocumentMutation);
|
||||||
@@ -236,7 +237,7 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Authorized entity="Control" action="updateControl">
|
{isAuthorized("Control", "updateControl") && (
|
||||||
<FrameworkControlDialog
|
<FrameworkControlDialog
|
||||||
frameworkId={framework.id}
|
frameworkId={framework.id}
|
||||||
connectionId={connectionId}
|
connectionId={connectionId}
|
||||||
@@ -246,8 +247,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
{__("Edit control")}
|
{__("Edit control")}
|
||||||
</Button>
|
</Button>
|
||||||
</FrameworkControlDialog>
|
</FrameworkControlDialog>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Control" action="deleteControl">
|
{isAuthorized("Control", "deleteControl") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -257,7 +258,7 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import type { FrameworkDetailPageExportFrameworkMutation } from "./__generated__
|
|||||||
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
|
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
|
||||||
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
|
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const frameworkDetailFragment = graphql`
|
const frameworkDetailFragment = graphql`
|
||||||
fragment FrameworkDetailPageFragment on Framework {
|
fragment FrameworkDetailPageFragment on Framework {
|
||||||
@@ -103,7 +104,7 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
framework,
|
framework,
|
||||||
ConnectionHandler.getConnectionID(organizationId, connectionListKey)!
|
ConnectionHandler.getConnectionID(organizationId, connectionListKey)!
|
||||||
);
|
);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [generateFrameworkStateOfApplicability] =
|
const [generateFrameworkStateOfApplicability] =
|
||||||
useMutationWithToasts<FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation>(
|
useMutationWithToasts<FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation>(
|
||||||
generateFrameworkStateOfApplicabilityMutation,
|
generateFrameworkStateOfApplicabilityMutation,
|
||||||
@@ -150,7 +151,7 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Authorized entity="Framework" action="updateFramework">
|
{isAuthorized("Framework", "updateFramework") && (
|
||||||
<FrameworkFormDialog
|
<FrameworkFormDialog
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
framework={framework}
|
framework={framework}
|
||||||
@@ -159,7 +160,7 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</FrameworkFormDialog>
|
</FrameworkFormDialog>
|
||||||
</Authorized>
|
)}
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -191,11 +192,11 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Export Framework")}
|
{__("Export Framework")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<Authorized entity="Framework" action="deleteFramework">
|
{isAuthorized("Framework", "deleteFramework") && (
|
||||||
<DropdownItem icon={IconTrashCan} variant="danger" onClick={onDelete}>
|
<DropdownItem icon={IconTrashCan} variant="danger" onClick={onDelete}>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div className="text-lg font-semibold">
|
<div className="text-lg font-semibold">
|
||||||
@@ -216,7 +217,7 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
active={selectedControl?.id === control.id}
|
active={selectedControl?.id === control.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Authorized entity="Organization" action="createControl">
|
{isAuthorized("Organization", "createControl") && (
|
||||||
<FrameworkControlDialog
|
<FrameworkControlDialog
|
||||||
frameworkId={framework.id}
|
frameworkId={framework.id}
|
||||||
connectionId={connectionId}
|
connectionId={connectionId}
|
||||||
@@ -226,7 +227,7 @@ export default function FrameworkDetailPage(props: Props) {
|
|||||||
{__("Add new control")}
|
{__("Add new control")}
|
||||||
</button>
|
</button>
|
||||||
</FrameworkControlDialog>
|
</FrameworkControlDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Outlet context={{ framework }} />
|
<Outlet context={{ framework }} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,11 +26,10 @@ import {
|
|||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
|
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { useState, type ChangeEventHandler } from "react";
|
import { useState, type ChangeEventHandler, use } from "react";
|
||||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||||
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
|
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<FrameworkGraphListQuery>;
|
queryRef: PreloadedQuery<FrameworkGraphListQuery>;
|
||||||
@@ -54,6 +53,7 @@ const importFrameworkMutation = graphql`
|
|||||||
|
|
||||||
export default function FrameworksPage(props: Props) {
|
export default function FrameworksPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
usePageTitle(__("Frameworks"));
|
usePageTitle(__("Frameworks"));
|
||||||
const data = usePreloadedQuery(frameworksQuery, props.queryRef);
|
const data = usePreloadedQuery(frameworksQuery, props.queryRef);
|
||||||
const connectionId = data.organization.frameworks!.__id;
|
const connectionId = data.organization.frameworks!.__id;
|
||||||
@@ -120,8 +120,8 @@ export default function FrameworksPage(props: Props) {
|
|||||||
|
|
||||||
const isLoading = isUploading || isImporting;
|
const isLoading = isUploading || isImporting;
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(data.organization.id!, "Framework", "updateFramework") ||
|
const hasAnyAction = isAuthorized("Framework", "updateFramework") ||
|
||||||
isAuthorized(data.organization.id!, "Framework", "deleteFramework");
|
isAuthorized("Framework", "deleteFramework");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -134,7 +134,8 @@ export default function FrameworksPage(props: Props) {
|
|||||||
title={__("Frameworks")}
|
title={__("Frameworks")}
|
||||||
description={__("Manage your compliance frameworks")}
|
description={__("Manage your compliance frameworks")}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="createFramework">
|
{isAuthorized("Organization", "createFramework") && (
|
||||||
|
<>
|
||||||
<FileButton
|
<FileButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
icon={IconFolderUpload}
|
icon={IconFolderUpload}
|
||||||
@@ -147,7 +148,8 @@ export default function FrameworksPage(props: Props) {
|
|||||||
onSelect={importNamedFramework}
|
onSelect={importNamedFramework}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
</>
|
||||||
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<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) {
|
function FrameworkCard(props: FrameworkCardProps) {
|
||||||
const framework = useFragment(frameworkCardFragment, props.framework);
|
const framework = useFragment(frameworkCardFragment, props.framework);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const deleteFramework = useDeleteFrameworkMutation(
|
const deleteFramework = useDeleteFrameworkMutation(
|
||||||
framework,
|
framework,
|
||||||
props.connectionId
|
props.connectionId
|
||||||
@@ -200,7 +203,7 @@ function FrameworkCard(props: FrameworkCardProps) {
|
|||||||
<FrameworkLogo {...framework} />
|
<FrameworkLogo {...framework} />
|
||||||
{props.hasAnyAction && (
|
{props.hasAnyAction && (
|
||||||
<ActionDropdown className="z-10 relative">
|
<ActionDropdown className="z-10 relative">
|
||||||
<Authorized entity="Framework" action="updateFramework">
|
{isAuthorized("Framework", "updateFramework") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -209,8 +212,8 @@ function FrameworkCard(props: FrameworkCardProps) {
|
|||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Framework" action="deleteFramework">
|
{isAuthorized("Framework", "deleteFramework") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
onClick={() => deleteFramework()}
|
onClick={() => deleteFramework()}
|
||||||
@@ -218,7 +221,7 @@ function FrameworkCard(props: FrameworkCardProps) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ import {
|
|||||||
sprintf,
|
sprintf,
|
||||||
} from "@probo/helpers";
|
} from "@probo/helpers";
|
||||||
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<MeasureGraphNodeQuery>;
|
queryRef: PreloadedQuery<MeasureGraphNodeQuery>;
|
||||||
@@ -59,7 +60,7 @@ export default function MeasureDetailPage(props: Props) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const [updateMeasure, isUpdating] = useUpdateMeasure();
|
const [updateMeasure, isUpdating] = useUpdateMeasure();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
if (!measureId) {
|
if (!measureId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Cannot load measure detail page without measureId parameter",
|
"Cannot load measure detail page without measureId parameter",
|
||||||
@@ -136,7 +137,8 @@ export default function MeasureDetailPage(props: Props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<PageHeader title={measure.name} description={measure.description}>
|
<PageHeader title={measure.name} description={measure.description}>
|
||||||
<Authorized entity="Measure" action="updateMeasure">
|
{isAuthorized("Measure", "updateMeasure") && (
|
||||||
|
<>
|
||||||
<MeasureFormDialog measure={measure}>
|
<MeasureFormDialog measure={measure}>
|
||||||
<Button variant="secondary" icon={IconPencil}>
|
<Button variant="secondary" icon={IconPencil}>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
@@ -156,13 +158,14 @@ export default function MeasureDetailPage(props: Props) {
|
|||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</Authorized>
|
</>
|
||||||
|
)}
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<Authorized entity="Measure" action="deleteMeasure">
|
{isAuthorized("Measure", "deleteMeasure") && (
|
||||||
<DropdownItem variant="danger" icon={IconTrashCan} onClick={onDelete}>
|
<DropdownItem variant="danger" icon={IconTrashCan} onClick={onDelete}>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import type {
|
|||||||
MeasuresPageFragment$key,
|
MeasuresPageFragment$key,
|
||||||
} from "./__generated__/MeasuresPageFragment.graphql";
|
} from "./__generated__/MeasuresPageFragment.graphql";
|
||||||
import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers";
|
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 { NodeOf } from "/types";
|
||||||
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
|
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
@@ -48,8 +48,7 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
|
|||||||
import { Link, useParams } from "react-router";
|
import { Link, useParams } from "react-router";
|
||||||
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
||||||
@@ -92,6 +91,7 @@ const importMeasuresMutation = graphql`
|
|||||||
|
|
||||||
export default function MeasuresPage(props: Props) {
|
export default function MeasuresPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const organization = usePreloadedQuery(
|
const organization = usePreloadedQuery(
|
||||||
measuresQuery,
|
measuresQuery,
|
||||||
props.queryRef
|
props.queryRef
|
||||||
@@ -115,8 +115,8 @@ export default function MeasuresPage(props: Props) {
|
|||||||
const importFileRef = useRef<HTMLInputElement>(null);
|
const importFileRef = useRef<HTMLInputElement>(null);
|
||||||
usePageTitle(__("Measures"));
|
usePageTitle(__("Measures"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organization.id, "Measure", "updateMeasure") ||
|
const hasAnyAction = isAuthorized("Measure", "updateMeasure") ||
|
||||||
isAuthorized(organization.id, "Measure", "deleteMeasure");
|
isAuthorized("Measure", "deleteMeasure");
|
||||||
|
|
||||||
const handleImport: ChangeEventHandler<HTMLInputElement> = (event) => {
|
const handleImport: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
@@ -148,7 +148,8 @@ export default function MeasuresPage(props: Props) {
|
|||||||
"Measures are actions taken to reduce the risk. Add them to track their implementation status."
|
"Measures are actions taken to reduce the risk. Add them to track their implementation status."
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="createMeasure">
|
{isAuthorized("Organization", "createMeasure") && (
|
||||||
|
<>
|
||||||
<FileButton
|
<FileButton
|
||||||
ref={importFileRef}
|
ref={importFileRef}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -162,7 +163,8 @@ export default function MeasuresPage(props: Props) {
|
|||||||
{__("New measure")}
|
{__("New measure")}
|
||||||
</Button>
|
</Button>
|
||||||
</MeasureFormDialog>
|
</MeasureFormDialog>
|
||||||
</Authorized>
|
</>
|
||||||
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<MeasureImplementation measures={measures} className="my-10" />
|
<MeasureImplementation measures={measures} className="my-10" />
|
||||||
{objectKeys(measuresPerCategory)
|
{objectKeys(measuresPerCategory)
|
||||||
@@ -269,6 +271,7 @@ function MeasureRow(props: MeasureRowProps) {
|
|||||||
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
@@ -306,15 +309,15 @@ function MeasureRow(props: MeasureRowProps) {
|
|||||||
{props.hasAnyAction && (
|
{props.hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Measure" action="updateMeasure">
|
{isAuthorized("Measure", "updateMeasure") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
onClick={() => dialogRef.current?.open()}
|
onClick={() => dialogRef.current?.open()}
|
||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Measure" action="deleteMeasure">
|
{isAuthorized("Measure", "deleteMeasure") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
@@ -323,7 +326,7 @@ function MeasureRow(props: MeasureRowProps) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import {
|
|||||||
UpdateMeetingMinutesDialog,
|
UpdateMeetingMinutesDialog,
|
||||||
type UpdateMeetingMinutesDialogRef,
|
type UpdateMeetingMinutesDialogRef,
|
||||||
} from "./dialogs/UpdateMeetingMinutesDialog";
|
} from "./dialogs/UpdateMeetingMinutesDialog";
|
||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect, use } from "react";
|
||||||
import {
|
import {
|
||||||
meetingNodeQuery,
|
meetingNodeQuery,
|
||||||
useDeleteMeetingMutation,
|
useDeleteMeetingMutation,
|
||||||
} from "/hooks/graph/MeetingGraph";
|
} from "/hooks/graph/MeetingGraph";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const meetingFragment = graphql`
|
const meetingFragment = graphql`
|
||||||
fragment MeetingDetailPageMeetingFragment on Meeting {
|
fragment MeetingDetailPageMeetingFragment on Meeting {
|
||||||
@@ -54,6 +54,7 @@ export default function MeetingDetailPage(props: Props) {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
if (!meeting) {
|
if (!meeting) {
|
||||||
return <div>{__("Meeting not found")}</div>;
|
return <div>{__("Meeting not found")}</div>;
|
||||||
@@ -74,14 +75,14 @@ export default function MeetingDetailPage(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updateAuth = isAuthorized(organizationId, "Meeting", "updateMeeting");
|
const updateAuth = isAuthorized("Meeting", "updateMeeting");
|
||||||
setCanUpdate(updateAuth);
|
setCanUpdate(updateAuth);
|
||||||
} catch (promise) {
|
} catch (promise) {
|
||||||
if (promise instanceof Promise) {
|
if (promise instanceof Promise) {
|
||||||
promise
|
promise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
try {
|
try {
|
||||||
const updateAuth = isAuthorized(organizationId, "Meeting", "updateMeeting");
|
const updateAuth = isAuthorized("Meeting", "updateMeeting");
|
||||||
setCanUpdate(updateAuth);
|
setCanUpdate(updateAuth);
|
||||||
} catch {
|
} catch {
|
||||||
setCanUpdate(false);
|
setCanUpdate(false);
|
||||||
@@ -96,14 +97,14 @@ export default function MeetingDetailPage(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deleteAuth = isAuthorized(organizationId, "Meeting", "deleteMeeting");
|
const deleteAuth = isAuthorized("Meeting", "deleteMeeting");
|
||||||
setCanDelete(deleteAuth);
|
setCanDelete(deleteAuth);
|
||||||
} catch (promise) {
|
} catch (promise) {
|
||||||
if (promise instanceof Promise) {
|
if (promise instanceof Promise) {
|
||||||
promise
|
promise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
try {
|
try {
|
||||||
const deleteAuth = isAuthorized(organizationId, "Meeting", "deleteMeeting");
|
const deleteAuth = isAuthorized("Meeting", "deleteMeeting");
|
||||||
setCanDelete(deleteAuth);
|
setCanDelete(deleteAuth);
|
||||||
} catch {
|
} catch {
|
||||||
setCanDelete(false);
|
setCanDelete(false);
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import { Link } from "react-router";
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import type { MeetingsPage_UpdateSummaryMutation } from "./__generated__/MeetingsPage_UpdateSummaryMutation.graphql";
|
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`
|
const meetingsFragment = graphql`
|
||||||
fragment MeetingsPageListFragment on Organization
|
fragment MeetingsPageListFragment on Organization
|
||||||
@@ -85,7 +86,7 @@ type Props = {
|
|||||||
|
|
||||||
export default function MeetingsPage(props: Props) {
|
export default function MeetingsPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const organization = usePreloadedQuery(
|
const organization = usePreloadedQuery(
|
||||||
meetingsQuery,
|
meetingsQuery,
|
||||||
props.queryRef
|
props.queryRef
|
||||||
@@ -218,7 +219,7 @@ export default function MeetingsPage(props: Props) {
|
|||||||
<h3 className="text-sm font-semibold text-txt-secondary">
|
<h3 className="text-sm font-semibold text-txt-secondary">
|
||||||
{__("Summary")}
|
{__("Summary")}
|
||||||
</h3>
|
</h3>
|
||||||
<Authorized entity="Meeting" action="updateMeeting">
|
{isAuthorized("Meeting", "updateMeeting") && (
|
||||||
<Button
|
<Button
|
||||||
variant="quaternary"
|
variant="quaternary"
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
@@ -226,7 +227,7 @@ export default function MeetingsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{displayedSummary ? (
|
{displayedSummary ? (
|
||||||
@@ -248,11 +249,11 @@ export default function MeetingsPage(props: Props) {
|
|||||||
"Track and manage your organization's meetings and their minutes."
|
"Track and manage your organization's meetings and their minutes."
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="createMeeting">
|
{isAuthorized("Organization", "createMeeting") && (
|
||||||
<CreateMeetingDialog connectionId={connectionId}>
|
<CreateMeetingDialog connectionId={connectionId}>
|
||||||
<Button icon={IconPlusLarge}>{__("Add meeting")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add meeting")}</Button>
|
||||||
</CreateMeetingDialog>
|
</CreateMeetingDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
{meetingNodes.length > 0 ? (
|
{meetingNodes.length > 0 ? (
|
||||||
<SortableTable {...pagination}>
|
<SortableTable {...pagination}>
|
||||||
@@ -320,7 +321,7 @@ function MeetingRow({
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [deleteMeeting] = useDeleteMeetingMutation();
|
const [deleteMeeting] = useDeleteMeetingMutation();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
() =>
|
() =>
|
||||||
@@ -370,7 +371,7 @@ function MeetingRow({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
<Authorized entity="Meeting" action="deleteMeeting">
|
{isAuthorized("Meeting", "deleteMeeting") && (
|
||||||
<Td noLink width={50} className="text-end w-18">
|
<Td noLink width={50} className="text-end w-18">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
@@ -382,7 +383,7 @@ function MeetingRow({
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
</Authorized>
|
)}
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ import { deleteNonconformityMutation, NonconformitiesConnectionKey } from "../..
|
|||||||
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers";
|
import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel, formatDate } from "@probo/helpers";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { Authorized } from "/permissions";
|
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
import type { NonconformitiesPageQuery } from "./__generated__/NonconformitiesPageQuery.graphql";
|
import type { NonconformitiesPageQuery } from "./__generated__/NonconformitiesPageQuery.graphql";
|
||||||
import type {
|
import type {
|
||||||
NonconformitiesPageFragment$key,
|
NonconformitiesPageFragment$key,
|
||||||
NonconformitiesPageFragment$data,
|
NonconformitiesPageFragment$data,
|
||||||
} from "./__generated__/NonconformitiesPageFragment.graphql";
|
} from "./__generated__/NonconformitiesPageFragment.graphql";
|
||||||
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Nonconformity = NonconformitiesPageFragment$data['nonconformities']['edges'][number]['node'];
|
type Nonconformity = NonconformitiesPageFragment$data['nonconformities']['edges'][number]['node'];
|
||||||
|
|
||||||
@@ -103,6 +103,7 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
usePageTitle(__("Nonconformities"));
|
usePageTitle(__("Nonconformities"));
|
||||||
|
|
||||||
@@ -132,8 +133,8 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro
|
|||||||
const nonconformities: Nonconformity[] = nonconformitiesData?.nonconformities?.edges?.map((edge) => edge.node) ?? [];
|
const nonconformities: Nonconformity[] = nonconformitiesData?.nonconformities?.edges?.map((edge) => edge.node) ?? [];
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "Nonconformity", "updateNonconformity") ||
|
isAuthorized("Nonconformity", "updateNonconformity") ||
|
||||||
isAuthorized(organizationId, "Nonconformity", "deleteNonconformity")
|
isAuthorized("Nonconformity", "deleteNonconformity")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -148,11 +149,11 @@ export default function NonconformitiesPage({ queryRef }: NonconformitiesPagePro
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Organization" action="createNonconformity">
|
isAuthorized("Organization", "createNonconformity") && (
|
||||||
<CreateNonconformityDialog organizationId={organizationId} connection={connectionId}>
|
<CreateNonconformityDialog organizationId={organizationId} connection={connectionId}>
|
||||||
<Button icon={IconPlusLarge}>{__("Add nonconformity")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add nonconformity")}</Button>
|
||||||
</CreateNonconformityDialog>
|
</CreateNonconformityDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -231,7 +232,7 @@ function NonconformityRow({
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const [deleteNonconformity] = useMutation(deleteNonconformityMutation);
|
const [deleteNonconformity] = useMutation(deleteNonconformityMutation);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const nonconformityDetailUrl = isSnapshotMode
|
const nonconformityDetailUrl = isSnapshotMode
|
||||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/nonconformities/${nonconformity.id}`
|
? `/organizations/${organizationId}/snapshots/${snapshotId}/nonconformities/${nonconformity.id}`
|
||||||
@@ -298,7 +299,7 @@ function NonconformityRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Nonconformity" action="deleteNonconformity">
|
{isAuthorized("Nonconformity", "deleteNonconformity") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -306,7 +307,7 @@ function NonconformityRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency, getStatusOptions, formatError, type GraphQLError } from "@probo/helpers";
|
import { getStatusVariant, getStatusLabel, formatDatetime, validateSnapshotConsistency, getStatusOptions, formatError, type GraphQLError } from "@probo/helpers";
|
||||||
import type { NonconformityGraphNodeQuery } from "/hooks/graph/__generated__/NonconformityGraphNodeQuery.graphql";
|
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({
|
const updateNonconformitySchema = z.object({
|
||||||
referenceId: z.string().min(1, "Reference ID is required"),
|
referenceId: z.string().min(1, "Reference ID is required"),
|
||||||
@@ -63,6 +64,7 @@ export default function NonconformityDetailsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
validateSnapshotConsistency(nonconformity, snapshotId);
|
validateSnapshotConsistency(nonconformity, snapshotId);
|
||||||
|
|
||||||
@@ -162,7 +164,7 @@ export default function NonconformityDetailsPage(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<Authorized entity="Nonconformity" action="deleteNonconformity">
|
{isAuthorized("Nonconformity", "deleteNonconformity") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -170,7 +172,7 @@ export default function NonconformityDetailsPage(props: Props) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -279,11 +281,11 @@ export default function NonconformityDetailsPage(props: Props) {
|
|||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{formState.isDirty && !isSnapshotMode && (
|
{formState.isDirty && !isSnapshotMode && (
|
||||||
<Authorized entity="Nonconformity" action="updateNonconformity">
|
isAuthorized("Nonconformity", "updateNonconformity") && (
|
||||||
<Button type="submit" disabled={formState.isSubmitting}>
|
<Button type="submit" disabled={formState.isSubmitting}>
|
||||||
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
{formState.isSubmitting ? __("Updating...") : __("Update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ import z from "zod";
|
|||||||
import { getObligationStatusVariant, getObligationStatusLabel, formatDatetime, getObligationStatusOptions, validateSnapshotConsistency } from "@probo/helpers";
|
import { getObligationStatusVariant, getObligationStatusLabel, formatDatetime, getObligationStatusOptions, validateSnapshotConsistency } from "@probo/helpers";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import type { ObligationGraphNodeQuery } from "/hooks/graph/__generated__/ObligationGraphNodeQuery.graphql";
|
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({
|
const updateObligationSchema = z.object({
|
||||||
area: z.string().optional(),
|
area: z.string().optional(),
|
||||||
@@ -61,6 +62,7 @@ export default function ObligationDetailsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
if (!obligation) {
|
if (!obligation) {
|
||||||
return <div>{__("Obligation not found")}</div>;
|
return <div>{__("Obligation not found")}</div>;
|
||||||
@@ -157,13 +159,13 @@ export default function ObligationDetailsPage(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Obligation" action="deleteObligation">
|
isAuthorized("Obligation", "deleteObligation") && (
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem icon={IconTrashCan} onClick={deleteObligation}>
|
<DropdownItem icon={IconTrashCan} onClick={deleteObligation}>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -301,14 +303,14 @@ export default function ObligationDetailsPage(props: Props) {
|
|||||||
|
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Authorized entity="Obligation" action="updateObligation">
|
{isAuthorized("Obligation", "updateObligation") && (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={formState.isSubmitting}
|
disabled={formState.isSubmitting}
|
||||||
>
|
>
|
||||||
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
{formState.isSubmitting ? __("Saving...") : __("Save Changes")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ import { deleteObligationMutation } from "../../../hooks/graph/ObligationGraph";
|
|||||||
import { promisifyMutation, getObligationStatusVariant, getObligationStatusLabel, formatDate } from "@probo/helpers";
|
import { promisifyMutation, getObligationStatusVariant, getObligationStatusLabel, formatDate } from "@probo/helpers";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import type { ObligationsPageQuery } from "./__generated__/ObligationsPageQuery.graphql";
|
import type { ObligationsPageQuery } from "./__generated__/ObligationsPageQuery.graphql";
|
||||||
import { Authorized } from "/permissions";
|
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
import type {
|
import type {
|
||||||
ObligationsPageFragment$key,
|
ObligationsPageFragment$key,
|
||||||
ObligationsPageFragment$data,
|
ObligationsPageFragment$data,
|
||||||
} from "./__generated__/ObligationsPageFragment.graphql";
|
} from "./__generated__/ObligationsPageFragment.graphql";
|
||||||
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Obligation = ObligationsPageFragment$data['obligations']['edges'][number]['node'];
|
type Obligation = ObligationsPageFragment$data['obligations']['edges'][number]['node'];
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
usePageTitle(__("Obligations"));
|
usePageTitle(__("Obligations"));
|
||||||
|
|
||||||
@@ -120,8 +121,8 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) {
|
|||||||
const obligations: Obligation[] = obligationsData?.obligations?.edges?.map((edge) => edge.node) ?? [];
|
const obligations: Obligation[] = obligationsData?.obligations?.edges?.map((edge) => edge.node) ?? [];
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "Obligation", "updateObligation") ||
|
isAuthorized("Obligation", "updateObligation") ||
|
||||||
isAuthorized(organizationId, "Obligation", "deleteObligation")
|
isAuthorized("Obligation", "deleteObligation")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,11 +137,11 @@ export default function ObligationsPage({ queryRef }: ObligationsPageProps) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!snapshotId && (
|
{!snapshotId && (
|
||||||
<Authorized entity="Organization" action="createObligation">
|
isAuthorized("Organization", "createObligation") && (
|
||||||
<CreateObligationDialog organizationId={organizationId} connection={connectionId}>
|
<CreateObligationDialog organizationId={organizationId} connection={connectionId}>
|
||||||
<Button icon={IconPlusLarge}>{__("Add obligation")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add obligation")}</Button>
|
||||||
</CreateObligationDialog>
|
</CreateObligationDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -214,7 +215,7 @@ function ObligationRow({
|
|||||||
const [deleteObligation] = useMutation(deleteObligationMutation);
|
const [deleteObligation] = useMutation(deleteObligationMutation);
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
@@ -261,7 +262,7 @@ function ObligationRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Obligation" action="deleteObligation">
|
{isAuthorized("Obligation", "deleteObligation") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -269,7 +270,7 @@ function ObligationRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import {
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<PeopleGraphNodeQuery>;
|
queryRef: PreloadedQuery<PeopleGraphNodeQuery>;
|
||||||
@@ -32,6 +33,7 @@ export default function PeopleDetailPage(props: Props) {
|
|||||||
const people = data.node;
|
const people = data.node;
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const deletePeople = useDeletePeople(
|
const deletePeople = useDeletePeople(
|
||||||
people,
|
people,
|
||||||
ConnectionHandler.getConnectionID(organizationId, PeopleConnectionKey)
|
ConnectionHandler.getConnectionID(organizationId, PeopleConnectionKey)
|
||||||
@@ -55,7 +57,7 @@ export default function PeopleDetailPage(props: Props) {
|
|||||||
<Avatar name={people.fullName ?? ""} size="xl" />
|
<Avatar name={people.fullName ?? ""} size="xl" />
|
||||||
<div className="text-2xl">{people.fullName}</div>
|
<div className="text-2xl">{people.fullName}</div>
|
||||||
</div>
|
</div>
|
||||||
<Authorized entity="People" action="deletePeople">
|
{isAuthorized("People", "deletePeople") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -65,7 +67,7 @@ export default function PeopleDetailPage(props: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import { usePageTitle } from "@probo/hooks";
|
|||||||
import { getRole } from "@probo/helpers";
|
import { getRole } from "@probo/helpers";
|
||||||
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
|
||||||
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
|
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
|
||||||
|
|
||||||
@@ -42,14 +42,14 @@ export default function PeopleListPage({
|
|||||||
queryRef: PreloadedQuery<PeopleGraphPaginatedQuery>;
|
queryRef: PreloadedQuery<PeopleGraphPaginatedQuery>;
|
||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const { people, refetch, connectionId, hasNext, loadNext, isLoadingNext } =
|
const { people, refetch, connectionId, hasNext, loadNext, isLoadingNext } =
|
||||||
usePeopleQuery(queryRef);
|
usePeopleQuery(queryRef);
|
||||||
|
|
||||||
usePageTitle(__("Members"));
|
usePageTitle(__("Members"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organizationId, "People", "updatePeople") ||
|
const hasAnyAction = isAuthorized("People", "updatePeople") ||
|
||||||
isAuthorized(organizationId, "People", "deletePeople");
|
isAuthorized("People", "deletePeople");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<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."
|
"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}>
|
<CreatePeopleDialog connectionId={connectionId}>
|
||||||
<Button icon={IconPlusLarge}>{__("Add member")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add member")}</Button>
|
||||||
</CreatePeopleDialog>
|
</CreatePeopleDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<SortableTable
|
<SortableTable
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
@@ -107,6 +107,7 @@ function PeopleRow({
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const deletePeople = useDeletePeople(people, connectionId);
|
const deletePeople = useDeletePeople(people, connectionId);
|
||||||
const contractEnded = isContractEnded(people);
|
const contractEnded = isContractEnded(people);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
@@ -129,7 +130,7 @@ function PeopleRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="People" action="deletePeople">
|
{isAuthorized("People", "deletePeople") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -137,7 +138,7 @@ function PeopleRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import type { PeopleGraphUpdateMutation } from "/hooks/graph/__generated__/Peopl
|
|||||||
import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
|
import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
|
||||||
import { Button, Card, Field, Input } from "@probo/ui";
|
import { Button, Card, Field, Input } from "@probo/ui";
|
||||||
import { EmailsField } from "/components/form/EmailsField";
|
import { EmailsField } from "/components/form/EmailsField";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import { use } from "react";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
fullName: z.string().min(1),
|
fullName: z.string().min(1),
|
||||||
@@ -49,7 +50,7 @@ export default function PeopleProfileTab() {
|
|||||||
errorMessage: __("Failed to update member"),
|
errorMessage: __("Failed to update member"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const onSubmit = handleSubmit((data) => {
|
const onSubmit = handleSubmit((data) => {
|
||||||
const input = {
|
const input = {
|
||||||
id: people.id!,
|
id: people.id!,
|
||||||
@@ -95,11 +96,11 @@ export default function PeopleProfileTab() {
|
|||||||
</Card>
|
</Card>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
{formState.isDirty && (
|
{formState.isDirty && (
|
||||||
<Authorized entity="People" action="updatePeople">
|
isAuthorized("People", "updatePeople") && (
|
||||||
<Button type="submit" disabled={isMutating}>
|
<Button type="submit" disabled={isMutating}>
|
||||||
{__("Update")}
|
{__("Update")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ import { CreateProcessingActivityDialog } from "./dialogs/CreateProcessingActivi
|
|||||||
import { deleteProcessingActivityMutation, ProcessingActivitiesConnectionKey } from "../../../hooks/graph/ProcessingActivityGraph";
|
import { deleteProcessingActivityMutation, ProcessingActivitiesConnectionKey } from "../../../hooks/graph/ProcessingActivityGraph";
|
||||||
import { sprintf, promisifyMutation } from "@probo/helpers";
|
import { sprintf, promisifyMutation } from "@probo/helpers";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
|
||||||
import { isAuthorized } from "/permissions";
|
|
||||||
import type { NodeOf } from "/types";
|
import type { NodeOf } from "/types";
|
||||||
import type { ProcessingActivitiesPageQuery } from "./__generated__/ProcessingActivitiesPageQuery.graphql";
|
import type { ProcessingActivitiesPageQuery } from "./__generated__/ProcessingActivitiesPageQuery.graphql";
|
||||||
import type {
|
import type {
|
||||||
ProcessingActivitiesPageFragment$key,
|
ProcessingActivitiesPageFragment$key,
|
||||||
ProcessingActivitiesPageFragment$data,
|
ProcessingActivitiesPageFragment$data,
|
||||||
} from "./__generated__/ProcessingActivitiesPageFragment.graphql";
|
} from "./__generated__/ProcessingActivitiesPageFragment.graphql";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import { use } from "react";
|
||||||
|
|
||||||
interface ProcessingActivitiesPageProps {
|
interface ProcessingActivitiesPageProps {
|
||||||
queryRef: PreloadedQuery<ProcessingActivitiesPageQuery>;
|
queryRef: PreloadedQuery<ProcessingActivitiesPageQuery>;
|
||||||
@@ -91,7 +91,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
usePageTitle(__("Processing Activities"));
|
usePageTitle(__("Processing Activities"));
|
||||||
|
|
||||||
@@ -129,8 +129,8 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
|||||||
const activities = data?.processingActivities?.edges?.map((edge) => edge.node) ?? [];
|
const activities = data?.processingActivities?.edges?.map((edge) => edge.node) ?? [];
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "ProcessingActivity", "updateProcessingActivity") ||
|
isAuthorized("ProcessingActivity", "updateProcessingActivity") ||
|
||||||
isAuthorized(organizationId, "ProcessingActivity", "deleteProcessingActivity")
|
isAuthorized("ProcessingActivity", "deleteProcessingActivity")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -140,7 +140,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
|||||||
)}
|
)}
|
||||||
<PageHeader title={__("Processing Activities")} description={__("Manage your processing activities under GDPR")}>
|
<PageHeader title={__("Processing Activities")} description={__("Manage your processing activities under GDPR")}>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Organization" action="createProcessingActivity">
|
isAuthorized("Organization", "createProcessingActivity") && (
|
||||||
<CreateProcessingActivityDialog
|
<CreateProcessingActivityDialog
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
connectionId={connectionId}
|
connectionId={connectionId}
|
||||||
@@ -149,7 +149,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
|||||||
{__("Add processing activity")}
|
{__("Add processing activity")}
|
||||||
</Button>
|
</Button>
|
||||||
</CreateProcessingActivityDialog>
|
</CreateProcessingActivityDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -222,6 +222,7 @@ function ActivityRow({
|
|||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
const [deleteActivity] = useMutation(deleteProcessingActivityMutation);
|
const [deleteActivity] = useMutation(deleteProcessingActivityMutation);
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
@@ -270,7 +271,7 @@ function ActivityRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="ProcessingActivity" action="deleteProcessingActivity">
|
{isAuthorized("ProcessingActivity", "deleteProcessingActivity") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -278,7 +279,7 @@ function ActivityRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ import {
|
|||||||
} from "../../../components/form/ProcessingActivityEnumOptions";
|
} from "../../../components/form/ProcessingActivityEnumOptions";
|
||||||
|
|
||||||
import type { ProcessingActivityGraphNodeQuery } from "/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql";
|
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({
|
const updateProcessingActivitySchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
@@ -74,6 +75,7 @@ export default function ProcessingActivityDetailsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
if (!activity) {
|
if (!activity) {
|
||||||
return <div>{__("Processing activity not found")}</div>;
|
return <div>{__("Processing activity not found")}</div>;
|
||||||
@@ -171,13 +173,13 @@ export default function ProcessingActivityDetailsPage(props: Props) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="ProcessingActivity" action="deleteProcessingActivity">
|
isAuthorized("ProcessingActivity", "deleteProcessingActivity") && (
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem onClick={deleteActivity} variant="danger">
|
<DropdownItem onClick={deleteActivity} variant="danger">
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import {
|
|||||||
} from "/hooks/graph/RiskGraph";
|
} from "/hooks/graph/RiskGraph";
|
||||||
import type { RiskGraphNodeQuery } from "/hooks/graph/__generated__/RiskGraphNodeQuery.graphql";
|
import type { RiskGraphNodeQuery } from "/hooks/graph/__generated__/RiskGraphNodeQuery.graphql";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<RiskGraphNodeQuery>;
|
queryRef: PreloadedQuery<RiskGraphNodeQuery>;
|
||||||
@@ -41,6 +42,7 @@ export default function RiskDetailPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
if (!riskId) {
|
if (!riskId) {
|
||||||
throw new Error("Cannot load risk detail page without riskId parameter");
|
throw new Error("Cannot load risk detail page without riskId parameter");
|
||||||
@@ -121,7 +123,7 @@ export default function RiskDetailPage(props: Props) {
|
|||||||
/>
|
/>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Authorized entity="Risk" action="updateRisk">
|
{isAuthorized("Risk", "updateRisk") && (
|
||||||
<FormRiskDialog
|
<FormRiskDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button icon={IconPencil} variant="secondary">
|
<Button icon={IconPencil} variant="secondary">
|
||||||
@@ -130,8 +132,8 @@ export default function RiskDetailPage(props: Props) {
|
|||||||
}
|
}
|
||||||
risk={{ id: riskId, ...risk }}
|
risk={{ id: riskId, ...risk }}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Risk" action="deleteRisk">
|
{isAuthorized("Risk", "deleteRisk") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -141,7 +143,7 @@ export default function RiskDetailPage(props: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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 type { RiskGraphFragment$data } from "/hooks/graph/__generated__/RiskGraphFragment.graphql";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<RiskGraphListQuery>;
|
queryRef: PreloadedQuery<RiskGraphListQuery>;
|
||||||
@@ -41,6 +41,7 @@ export default function RisksPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const { connectionId, risks, ...pagination } = useRisksQuery(props.queryRef);
|
const { connectionId, risks, ...pagination } = useRisksQuery(props.queryRef);
|
||||||
|
|
||||||
@@ -57,8 +58,8 @@ export default function RisksPage(props: Props) {
|
|||||||
usePageTitle(__("Risks"));
|
usePageTitle(__("Risks"));
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "Risk", "updateRisk") ||
|
isAuthorized("Risk", "updateRisk") ||
|
||||||
isAuthorized(organizationId, "Risk", "deleteRisk")
|
isAuthorized("Risk", "deleteRisk")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -71,7 +72,7 @@ export default function RisksPage(props: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Organization" action="createRisk">
|
isAuthorized("Organization", "createRisk") && (
|
||||||
<FormRiskDialog
|
<FormRiskDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
@@ -79,7 +80,7 @@ export default function RisksPage(props: Props) {
|
|||||||
}}
|
}}
|
||||||
trigger={<Button icon={IconPlusLarge}>{__("New Risk")}</Button>}
|
trigger={<Button icon={IconPlusLarge}>{__("New Risk")}</Button>}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -143,6 +144,7 @@ function RiskRow(props: RowProps) {
|
|||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
const [deleteRisk] = useDeleteRiskMutation();
|
const [deleteRisk] = useDeleteRiskMutation();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
() =>
|
() =>
|
||||||
@@ -194,16 +196,16 @@ function RiskRow(props: RowProps) {
|
|||||||
{props.hasAnyAction && (
|
{props.hasAnyAction && (
|
||||||
<Td noLink className="text-end">
|
<Td noLink className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Risk" action="updateRisk">
|
{isAuthorized("Risk", "updateRisk") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
onClick={() => formDialogRef.current?.open()}
|
onClick={() => formDialogRef.current?.open()}
|
||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
|
|
||||||
<Authorized entity="Risk" action="deleteRisk">
|
{isAuthorized("Risk", "deleteRisk") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
@@ -211,7 +213,7 @@ function RiskRow(props: RowProps) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</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 { useOutletContext, useNavigate } from "react-router";
|
||||||
import { useFragment, graphql } from "react-relay";
|
import { useFragment, graphql } from "react-relay";
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +23,7 @@ import { z } from "zod";
|
|||||||
import type { GeneralSettingsTabFragment$key } from "./__generated__/GeneralSettingsTabFragment.graphql";
|
import type { GeneralSettingsTabFragment$key } from "./__generated__/GeneralSettingsTabFragment.graphql";
|
||||||
import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog";
|
import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog";
|
||||||
import { useDeleteOrganizationMutation } from "/hooks/graph/OrganizationGraph";
|
import { useDeleteOrganizationMutation } from "/hooks/graph/OrganizationGraph";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const generalSettingsTabFragment = graphql`
|
const generalSettingsTabFragment = graphql`
|
||||||
fragment GeneralSettingsTabFragment on Organization {
|
fragment GeneralSettingsTabFragment on Organization {
|
||||||
@@ -90,9 +90,10 @@ export default function GeneralSettingsTab() {
|
|||||||
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
||||||
const organization = useFragment(generalSettingsTabFragment, organizationKey);
|
const organization = useFragment(generalSettingsTabFragment, organizationKey);
|
||||||
const deleteDialogRef = useDialogRef();
|
const deleteDialogRef = useDialogRef();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const canUpdate = isAuthorized(organization.id, "Organization", "updateOrganization");
|
const canUpdate = isAuthorized("Organization", "updateOrganization");
|
||||||
const canDelete = isAuthorized(organization.id, "Organization", "deleteOrganization");
|
const canDelete = isAuthorized("Organization", "deleteOrganization");
|
||||||
|
|
||||||
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
||||||
const [horizontalLogoPreview, setHorizontalLogoPreview] = useState<
|
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 { useOutletContext } from "react-router";
|
||||||
import { usePaginationFragment, graphql } from "react-relay";
|
import { usePaginationFragment, graphql } from "react-relay";
|
||||||
import {
|
import {
|
||||||
@@ -29,11 +29,9 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||||
import { InviteUserDialog } from "/components/organizations/InviteUserDialog";
|
import { InviteUserDialog } from "/components/organizations/InviteUserDialog";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { sprintf } from "@probo/helpers";
|
import { getAssignableRoles, sprintf } from "@probo/helpers";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import type { NodeOf } from "/types";
|
import type { NodeOf } from "/types";
|
||||||
import { Authorized } from "/permissions";
|
|
||||||
import { getAssignableRoles, getUserRole } from "/permissions";
|
|
||||||
import type {
|
import type {
|
||||||
MembersSettingsTabMembershipsFragment$data,
|
MembersSettingsTabMembershipsFragment$data,
|
||||||
MembersSettingsTabMembershipsFragment$key
|
MembersSettingsTabMembershipsFragment$key
|
||||||
@@ -42,6 +40,7 @@ import type {
|
|||||||
MembersSettingsTabInvitationsFragment$data,
|
MembersSettingsTabInvitationsFragment$data,
|
||||||
MembersSettingsTabInvitationsFragment$key
|
MembersSettingsTabInvitationsFragment$key
|
||||||
} from "./__generated__/MembersSettingsTabInvitationsFragment.graphql";
|
} from "./__generated__/MembersSettingsTabInvitationsFragment.graphql";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const paginatedMembershipsFragment = graphql`
|
const paginatedMembershipsFragment = graphql`
|
||||||
fragment MembersSettingsTabMembershipsFragment on Organization
|
fragment MembersSettingsTabMembershipsFragment on Organization
|
||||||
@@ -153,7 +152,7 @@ type OutletContext = {
|
|||||||
export default function MembersSettingsTab() {
|
export default function MembersSettingsTab() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const membershipsPagination = usePaginationFragment(
|
const membershipsPagination = usePaginationFragment(
|
||||||
paginatedMembershipsFragment,
|
paginatedMembershipsFragment,
|
||||||
organizationKey as MembersSettingsTabMembershipsFragment$key
|
organizationKey as MembersSettingsTabMembershipsFragment$key
|
||||||
@@ -180,14 +179,14 @@ export default function MembersSettingsTab() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
||||||
<Authorized entity="Organization" action="inviteUser">
|
{isAuthorized("Organization", "inviteUser") && (
|
||||||
<InviteUserDialog
|
<InviteUserDialog
|
||||||
connectionId={invitationsPagination.data.invitations?.__id}
|
connectionId={invitationsPagination.data.invitations?.__id}
|
||||||
onRefetch={refetchInvitations}
|
onRefetch={refetchInvitations}
|
||||||
>
|
>
|
||||||
<Button variant="secondary">{__("Invite member")}</Button>
|
<Button variant="secondary">{__("Invite member")}</Button>
|
||||||
</InviteUserDialog>
|
</InviteUserDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
@@ -314,6 +313,7 @@ function InvitationRow(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [deleteInvitation, isDeleting] = useMutationWithToasts(
|
const [deleteInvitation, isDeleting] = useMutationWithToasts(
|
||||||
deleteInvitationMutation,
|
deleteInvitationMutation,
|
||||||
{
|
{
|
||||||
@@ -376,7 +376,7 @@ function InvitationRow(props: {
|
|||||||
{isDeleting ? (
|
{isDeleting ? (
|
||||||
<Spinner size={16} />
|
<Spinner size={16} />
|
||||||
) : (
|
) : (
|
||||||
<Authorized entity="Invitation" action="deleteInvitation">
|
isAuthorized("Invitation", "deleteInvitation") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
@@ -384,7 +384,7 @@ function InvitationRow(props: {
|
|||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
aria-label={__("Delete invitation")}
|
aria-label={__("Delete invitation")}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
@@ -399,9 +399,9 @@ function MembershipRowContent(props: {
|
|||||||
onRefetch: () => void;
|
onRefetch: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const availableRoles = getAssignableRoles(props.organizationId);
|
const { role: currentUserRole } = use(PermissionsContext);
|
||||||
const currentUserRole = getUserRole(props.organizationId);
|
const availableRoles = getAssignableRoles(currentUserRole);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const [removeMember, isRemoving] = useMutationWithToasts(removeMemberMutation, {
|
const [removeMember, isRemoving] = useMutationWithToasts(removeMemberMutation, {
|
||||||
successMessage: __("Member removed successfully"),
|
successMessage: __("Member removed successfully"),
|
||||||
errorMessage: __("Failed to remove member"),
|
errorMessage: __("Failed to remove member"),
|
||||||
@@ -494,8 +494,7 @@ function MembershipRowContent(props: {
|
|||||||
className="flex gap-2 justify-end"
|
className="flex gap-2 justify-end"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="updateMembership">
|
{isAuthorized("Organization", "updateMembership") && canEditThisRole && (
|
||||||
{canEditThisRole && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleEditClick}
|
onClick={handleEditClick}
|
||||||
@@ -504,12 +503,10 @@ function MembershipRowContent(props: {
|
|||||||
aria-label={__("Edit role")}
|
aria-label={__("Edit role")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Authorized>
|
|
||||||
{isRemoving ? (
|
{isRemoving ? (
|
||||||
<Spinner size={16} />
|
<Spinner size={16} />
|
||||||
) : (
|
) : (
|
||||||
<Authorized entity="Organization" action="removeMember">
|
isAuthorized("Organization", "removeMember") && canEditThisRole && (
|
||||||
{canEditThisRole && (
|
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
@@ -517,8 +514,7 @@ function MembershipRowContent(props: {
|
|||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
aria-label={__("Remove member")}
|
aria-label={__("Remove member")}
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
</Authorized>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, use } from "react";
|
||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
import { useFragment, graphql } from "react-relay";
|
import { useFragment, graphql } from "react-relay";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
@@ -37,7 +37,7 @@ import {
|
|||||||
useVerifyDomainMutation,
|
useVerifyDomainMutation,
|
||||||
} from "/hooks/graph/SAMLConfigurationGraph";
|
} from "/hooks/graph/SAMLConfigurationGraph";
|
||||||
import type { SAMLSettingsTabFragment$key } from "./__generated__/SAMLSettingsTabFragment.graphql";
|
import type { SAMLSettingsTabFragment$key } from "./__generated__/SAMLSettingsTabFragment.graphql";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const samlSettingsTabFragment = graphql`
|
const samlSettingsTabFragment = graphql`
|
||||||
fragment SAMLSettingsTabFragment on Organization {
|
fragment SAMLSettingsTabFragment on Organization {
|
||||||
@@ -97,6 +97,7 @@ type SetupStep = "initiate" | "verify" | "configure";
|
|||||||
export default function SAMLSettingsTab() {
|
export default function SAMLSettingsTab() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
const { organization: organizationKey } = useOutletContext<OutletContext>();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const organization = useFragment(samlSettingsTabFragment, organizationKey);
|
const organization = useFragment(samlSettingsTabFragment, organizationKey);
|
||||||
const configs = organization.samlConfigurations;
|
const configs = organization.samlConfigurations;
|
||||||
|
|
||||||
@@ -372,11 +373,11 @@ export default function SAMLSettingsTab() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-base font-medium">{__("SAML Single Sign-On")}</h2>
|
<h2 className="text-base font-medium">{__("SAML Single Sign-On")}</h2>
|
||||||
<Authorized entity="Organization" action="createSAMLConfiguration">
|
{isAuthorized("Organization", "createSAMLConfiguration") && (
|
||||||
<Button onClick={() => handleOpenModal()}>
|
<Button onClick={() => handleOpenModal()}>
|
||||||
{__("Add Configuration")}
|
{__("Add Configuration")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configs.length === 0 ? (
|
{configs.length === 0 ? (
|
||||||
@@ -388,11 +389,11 @@ export default function SAMLSettingsTab() {
|
|||||||
<p className="text-gray-600 mb-6">
|
<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.")}
|
{__("Set up SAML 2.0 single sign-on for your organization by adding a configuration for each email domain.")}
|
||||||
</p>
|
</p>
|
||||||
<Authorized entity="Organization" action="createSAMLConfiguration">
|
{isAuthorized("Organization", "createSAMLConfiguration") && (
|
||||||
<Button onClick={() => handleOpenModal()}>
|
<Button onClick={() => handleOpenModal()}>
|
||||||
{__("Add Your First Configuration")}
|
{__("Add Your First Configuration")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
@@ -457,7 +458,7 @@ export default function SAMLSettingsTab() {
|
|||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
{config.domainVerified ? (
|
{config.domainVerified ? (
|
||||||
<>
|
<>
|
||||||
<Authorized entity="SAMLConfiguration" action="updateSAMLConfiguration">
|
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
|
||||||
<Button
|
<Button
|
||||||
variant={config.enabled ? "danger" : "primary"}
|
variant={config.enabled ? "danger" : "primary"}
|
||||||
onClick={() => handleToggleEnabled(config)}
|
onClick={() => handleToggleEnabled(config)}
|
||||||
@@ -465,34 +466,34 @@ export default function SAMLSettingsTab() {
|
|||||||
>
|
>
|
||||||
{config.enabled ? __("Disable") : __("Enable")}
|
{config.enabled ? __("Disable") : __("Enable")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="SAMLConfiguration" action="updateSAMLConfiguration">
|
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => handleOpenModal(config)}
|
onClick={() => handleOpenModal(config)}
|
||||||
>
|
>
|
||||||
{__("Edit")}
|
{__("Edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Authorized entity="Organization" action="verifyDomain">
|
{isAuthorized("Organization", "verifyDomain") && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => handleOpenModal(config)}
|
onClick={() => handleOpenModal(config)}
|
||||||
>
|
>
|
||||||
{__("Verify Domain")}
|
{__("Verify Domain")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="Organization" action="deleteOrganization">
|
{isAuthorized("Organization", "deleteOrganization") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={() => handleDelete(config)}
|
onClick={() => handleDelete(config)}
|
||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import type { NodeOf } from "/types";
|
|||||||
import SnapshotFormDialog from "./dialog/SnapshotFormDialog";
|
import SnapshotFormDialog from "./dialog/SnapshotFormDialog";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { isAuthorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<SnapshotGraphListQuery>;
|
queryRef: PreloadedQuery<SnapshotGraphListQuery>;
|
||||||
@@ -71,9 +71,10 @@ export default function SnapshotsPage(props: Props) {
|
|||||||
);
|
);
|
||||||
const connectionId = data.snapshots.__id;
|
const connectionId = data.snapshots.__id;
|
||||||
const snapshots = data.snapshots.edges.map((edge) => edge.node);
|
const snapshots = data.snapshots.edges.map((edge) => edge.node);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
usePageTitle(__("Snapshots"));
|
usePageTitle(__("Snapshots"));
|
||||||
|
|
||||||
const hasAnyAction = isAuthorized(organizationId, "Snapshot", "deleteSnapshot");
|
const hasAnyAction = isAuthorized("Snapshot", "deleteSnapshot");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<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."
|
"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}>
|
<SnapshotFormDialog connection={connectionId}>
|
||||||
<Button variant="primary" icon={IconPlusLarge}>
|
<Button variant="primary" icon={IconPlusLarge}>
|
||||||
{__("New snapshot")}
|
{__("New snapshot")}
|
||||||
</Button>
|
</Button>
|
||||||
</SnapshotFormDialog>
|
</SnapshotFormDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
{snapshots.length > 0 ? (
|
{snapshots.length > 0 ? (
|
||||||
@@ -139,7 +140,7 @@ type SnapshotRowProps = {
|
|||||||
function SnapshotRow(props: SnapshotRowProps) {
|
function SnapshotRow(props: SnapshotRowProps) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId);
|
const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const typePath = getSnapshotTypeUrlPath(props.snapshot.type);
|
const typePath = getSnapshotTypeUrlPath(props.snapshot.type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -159,7 +160,7 @@ function SnapshotRow(props: SnapshotRowProps) {
|
|||||||
{props.hasAnyAction && (
|
{props.hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Snapshot" action="deleteSnapshot">
|
{isAuthorized("Snapshot", "deleteSnapshot") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={deleteSnapshot}
|
onClick={deleteSnapshot}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -167,7 +168,7 @@ function SnapshotRow(props: SnapshotRowProps) {
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { tasksQuery } from "/hooks/graph/TaskGraph";
|
|||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import TasksCard from "/components/tasks/TasksCard";
|
import TasksCard from "/components/tasks/TasksCard";
|
||||||
import TaskFormDialog from "/components/tasks/TaskFormDialog";
|
import TaskFormDialog from "/components/tasks/TaskFormDialog";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import { use } from "react";
|
||||||
|
|
||||||
const tasksFragment = graphql`
|
const tasksFragment = graphql`
|
||||||
fragment TasksPageFragment on Organization
|
fragment TasksPageFragment on Organization
|
||||||
@@ -66,7 +67,7 @@ export default function TasksPage({ queryRef }: Props) {
|
|||||||
);
|
);
|
||||||
const tasks = data.tasks?.edges.map((edge) => edge.node);
|
const tasks = data.tasks?.edges.map((edge) => edge.node);
|
||||||
const connectionId = data.tasks.__id;
|
const connectionId = data.tasks.__id;
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
usePageTitle(__("Tasks"));
|
usePageTitle(__("Tasks"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -77,11 +78,11 @@ export default function TasksPage({ queryRef }: Props) {
|
|||||||
"Track your assigned compliance tasks and keep progress on track."
|
"Track your assigned compliance tasks and keep progress on track."
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Authorized entity="Organization" action="createTask">
|
{isAuthorized("Organization", "createTask") && (
|
||||||
<TaskFormDialog connection={connectionId}>
|
<TaskFormDialog connection={connectionId}>
|
||||||
<Button icon={IconPlusLarge}>{__("New task")}</Button>
|
<Button icon={IconPlusLarge}>{__("New task")}</Button>
|
||||||
</TaskFormDialog>
|
</TaskFormDialog>
|
||||||
</Authorized>
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<TasksCard connectionId={connectionId} tasks={tasks ?? []} />
|
<TasksCard connectionId={connectionId} tasks={tasks ?? []} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { formatDate } from "@probo/helpers";
|
import { formatDate } from "@probo/helpers";
|
||||||
import { useOutletContext } from "react-router";
|
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 { useQueryLoader, usePreloadedQuery } from 'react-relay';
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import {
|
import {
|
||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
} from "/hooks/graph/TrustCenterAccessGraph";
|
} from "/hooks/graph/TrustCenterAccessGraph";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
organization: {
|
organization: {
|
||||||
@@ -130,7 +130,7 @@ function DocumentAccessesLoader({
|
|||||||
export default function TrustCenterAccessTab() {
|
export default function TrustCenterAccessTab() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { organization } = useOutletContext<ContextType>();
|
const { organization } = useOutletContext<ContextType>();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const inviteSchema = z.object({
|
const inviteSchema = z.object({
|
||||||
name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")),
|
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")),
|
email: z.string().min(1, __("Email is required")).email(__("Please enter a valid email address")),
|
||||||
@@ -423,14 +423,14 @@ export default function TrustCenterAccessTab() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{organization.trustCenter?.id && (
|
{organization.trustCenter?.id && (
|
||||||
<Authorized entity="TrustCenter" action="createTrustCenterAccess">
|
{isAuthorized("TrustCenter", "createTrustCenterAccess") && (
|
||||||
<Button icon={IconPlusLarge} onClick={() => {
|
<Button icon={IconPlusLarge} onClick={() => {
|
||||||
inviteForm.reset();
|
inviteForm.reset();
|
||||||
dialogRef.current?.open();
|
dialogRef.current?.open();
|
||||||
}}>
|
}}>
|
||||||
{__("Add Access")}
|
{__("Add Access")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -515,22 +515,22 @@ export default function TrustCenterAccessTab() {
|
|||||||
className="flex gap-2 justify-end"
|
className="flex gap-2 justify-end"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Authorized entity="TrustCenterAccess" action="updateTrustCenterAccess">
|
{isAuthorized("TrustCenterAccess", "updateTrustCenterAccess") && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => handleEditAccess(access)}
|
onClick={() => handleEditAccess(access)}
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
<Authorized entity="TrustCenterAccess" action="deleteTrustCenterAccess">
|
{isAuthorized("TrustCenterAccess", "deleteTrustCenterAccess") && (
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={() => handleDelete(access.id)}
|
onClick={() => handleDelete(access.id)}
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
/>
|
/>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback, use } from "react";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { getTrustCenterVisibilityOptions } from "@probo/helpers";
|
import { getTrustCenterVisibilityOptions } from "@probo/helpers";
|
||||||
import {
|
import {
|
||||||
@@ -23,8 +23,8 @@ import {
|
|||||||
} from "/hooks/graph/TrustCenterFileGraph";
|
} from "/hooks/graph/TrustCenterFileGraph";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { TrustCenterFilesCard } from "/components/trustCenter/TrustCenterFilesCard";
|
import { TrustCenterFilesCard } from "/components/trustCenter/TrustCenterFilesCard";
|
||||||
import { Authorized } from "/permissions";
|
|
||||||
import type { TrustCenterFilesCardFragment$key } from "/components/trustCenter/__generated__/TrustCenterFilesCardFragment.graphql";
|
import type { TrustCenterFilesCardFragment$key } from "/components/trustCenter/__generated__/TrustCenterFilesCardFragment.graphql";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
organization: {
|
organization: {
|
||||||
@@ -41,7 +41,7 @@ type ContextType = {
|
|||||||
export default function TrustCenterFilesTab() {
|
export default function TrustCenterFilesTab() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { organization } = useOutletContext<ContextType>();
|
const { organization } = useOutletContext<ContextType>();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const createSchema = z.object({
|
const createSchema = z.object({
|
||||||
name: z.string().min(1, __("Name is required")),
|
name: z.string().min(1, __("Name is required")),
|
||||||
category: z.string().min(1, __("Category 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")}
|
{__("Upload and manage files for your trust center")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Authorized entity="Organization" action="createTrustCenterFile">
|
{isAuthorized("Organization", "createTrustCenterFile") && (
|
||||||
<Button icon={IconPlusLarge} onClick={() => createDialogRef.current?.open()}>
|
<Button icon={IconPlusLarge} onClick={() => createDialogRef.current?.open()}>
|
||||||
{__("Add File")}
|
{__("Add File")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(isUpdating || isDeleting) && (
|
{(isUpdating || isDeleting) && (
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import {
|
|||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
import { useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
|
import { useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
|
||||||
import type { TrustCenterGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
|
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 { SlackConnections } from "../../../components/organizations/SlackConnection";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
import { useParams } from "react-router";
|
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
organization: TrustCenterGraphQuery$data["organization"];
|
organization: TrustCenterGraphQuery$data["organization"];
|
||||||
@@ -24,14 +23,14 @@ export default function TrustCenterOverviewTab() {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { organization } = useOutletContext<ContextType>();
|
const { organization } = useOutletContext<ContextType>();
|
||||||
const { organizationId } = useParams();
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
|
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
|
||||||
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
|
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
|
||||||
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
|
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
|
||||||
const [isActive, setIsActive] = useState(organization.trustCenter?.active || false);
|
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) => {
|
const handleToggleActive = async (active: boolean) => {
|
||||||
if (!organization.trustCenter?.id) {
|
if (!organization.trustCenter?.id) {
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import { ImportAssessmentDialog } from "./dialogs/ImportAssessmentDialog";
|
|||||||
import { complianceReportsFragment } from "./tabs/VendorComplianceTab";
|
import { complianceReportsFragment } from "./tabs/VendorComplianceTab";
|
||||||
import type { VendorComplianceTabFragment$key } from "./tabs/__generated__/VendorComplianceTabFragment.graphql";
|
import type { VendorComplianceTabFragment$key } from "./tabs/__generated__/VendorComplianceTabFragment.graphql";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<VendorGraphNodeQuery>;
|
queryRef: PreloadedQuery<VendorGraphNodeQuery>;
|
||||||
@@ -42,6 +43,7 @@ export default function VendorDetailPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
if (!vendor) {
|
if (!vendor) {
|
||||||
return <div>{__("Vendor not found")}</div>;
|
return <div>{__("Vendor not found")}</div>;
|
||||||
@@ -98,7 +100,7 @@ export default function VendorDetailPage(props: Props) {
|
|||||||
{__("Assessment From Website")}
|
{__("Assessment From Website")}
|
||||||
</Button>
|
</Button>
|
||||||
</ImportAssessmentDialog>
|
</ImportAssessmentDialog>
|
||||||
<Authorized entity="Vendor" action="deleteVendor">
|
{isAuthorized("Vendor", "deleteVendor") && (
|
||||||
<ActionDropdown variant="secondary">
|
<ActionDropdown variant="secondary">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -108,7 +110,7 @@ export default function VendorDetailPage(props: Props) {
|
|||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ import type {
|
|||||||
} from "/hooks/graph/__generated__/VendorGraphPaginatedFragment.graphql";
|
} from "/hooks/graph/__generated__/VendorGraphPaginatedFragment.graphql";
|
||||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import { Authorized } from "/permissions";
|
import { use } from "react";
|
||||||
import { isAuthorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
type Vendor = NodeOf<VendorGraphPaginatedFragment$data["vendors"]>;
|
type Vendor = NodeOf<VendorGraphPaginatedFragment$data["vendors"]>;
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ export default function VendorsPage(props: Props) {
|
|||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
const data = usePreloadedQuery(vendorsQuery, props.queryRef);
|
const data = usePreloadedQuery(vendorsQuery, props.queryRef);
|
||||||
const pagination = usePaginationFragment(
|
const pagination = usePaginationFragment(
|
||||||
@@ -64,8 +65,8 @@ export default function VendorsPage(props: Props) {
|
|||||||
usePageTitle(__("Vendors"));
|
usePageTitle(__("Vendors"));
|
||||||
|
|
||||||
const hasAnyAction = !isSnapshotMode && (
|
const hasAnyAction = !isSnapshotMode && (
|
||||||
isAuthorized(organizationId, "Vendor", "updateVendor") ||
|
isAuthorized("Vendor", "updateVendor") ||
|
||||||
isAuthorized(organizationId, "Vendor", "deleteVendor")
|
isAuthorized("Vendor", "deleteVendor")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -78,14 +79,14 @@ export default function VendorsPage(props: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<Authorized entity="Organization" action="createVendor">
|
isAuthorized("Organization", "createVendor") && (
|
||||||
<CreateVendorDialog
|
<CreateVendorDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
>
|
>
|
||||||
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||||
</CreateVendorDialog>
|
</CreateVendorDialog>
|
||||||
</Authorized>
|
)
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<SortableTable {...pagination}>
|
<SortableTable {...pagination}>
|
||||||
@@ -129,7 +130,7 @@ function VendorRow({
|
|||||||
const isSnapshotMode = Boolean(snapshotId);
|
const isSnapshotMode = Boolean(snapshotId);
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const deleteVendor = useDeleteVendor(vendor, connectionId);
|
const deleteVendor = useDeleteVendor(vendor, connectionId);
|
||||||
|
|
||||||
const vendorUrl = isSnapshotMode && snapshotId
|
const vendorUrl = isSnapshotMode && snapshotId
|
||||||
@@ -159,7 +160,7 @@ function VendorRow({
|
|||||||
{hasAnyAction && (
|
{hasAnyAction && (
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<Authorized entity="Vendor" action="deleteVendor">
|
{isAuthorized("Vendor", "deleteVendor") && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={deleteVendor}
|
onClick={deleteVendor}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@@ -167,7 +168,7 @@ function VendorRow({
|
|||||||
>
|
>
|
||||||
{__("Delete")}
|
{__("Delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Authorized>
|
)}
|
||||||
</ActionDropdown>
|
</ActionDropdown>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
|||||||
import { ControlledField } from "/components/form/ControlledField";
|
import { ControlledField } from "/components/form/ControlledField";
|
||||||
import { CountriesField } from "/components/form/CountriesField";
|
import { CountriesField } from "/components/form/CountriesField";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { useMemo } from "react";
|
import { use, useMemo } from "react";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { downloadFile, formatDate } from "@probo/helpers";
|
import { downloadFile, formatDate } from "@probo/helpers";
|
||||||
import { useFragment, graphql } from "react-relay";
|
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 { VendorOverviewTabBusinessAssociateAgreementFragment$key } from "./__generated__/VendorOverviewTabBusinessAssociateAgreementFragment.graphql";
|
||||||
import type { VendorOverviewTabDataPrivacyAgreementFragment$key } from "./__generated__/VendorOverviewTabDataPrivacyAgreementFragment.graphql";
|
import type { VendorOverviewTabDataPrivacyAgreementFragment$key } from "./__generated__/VendorOverviewTabDataPrivacyAgreementFragment.graphql";
|
||||||
import type { VendorCategory } from "@probo/vendors";
|
import type { VendorCategory } from "@probo/vendors";
|
||||||
import { Authorized } from "/permissions";
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
|
||||||
const vendorBusinessAssociateAgreementFragment = graphql`
|
const vendorBusinessAssociateAgreementFragment = graphql`
|
||||||
fragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {
|
fragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {
|
||||||
@@ -58,7 +58,7 @@ export default function VendorOverviewTab() {
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const vendorCategories: { value: VendorCategory; label: string }[] = [
|
const vendorCategories: { value: VendorCategory; label: string }[] = [
|
||||||
{ value: "ANALYTICS", label: __("Analytics") },
|
{ value: "ANALYTICS", label: __("Analytics") },
|
||||||
{ value: "CLOUD_MONITORING", label: __("Cloud Monitoring") },
|
{ value: "CLOUD_MONITORING", label: __("Cloud Monitoring") },
|
||||||
@@ -396,11 +396,11 @@ export default function VendorOverviewTab() {
|
|||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Authorized entity="Vendor" action="updateVendor">
|
{isAuthorized("Vendor", "updateVendor") && (
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
{__("Update vendor")}
|
{__("Update vendor")}
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</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 { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createContext, type PropsWithChildren } from "react";
|
import { type PropsWithChildren } from "react";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { PermissionsContext, type PermissionsResponse } from "./PermissionsContext";
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function PermissionsProvider(props: PropsWithChildren) {
|
export function PermissionsProvider(props: PropsWithChildren) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|||||||
@@ -65,3 +65,4 @@ export { fileType, fileSize } from "./file";
|
|||||||
export { formatDatetime, formatDate } from "./date";
|
export { formatDatetime, formatDate } from "./date";
|
||||||
export { getLogoUrl, getTrustCenterUrl } from "./trustCenter";
|
export { getLogoUrl, getTrustCenterUrl } from "./trustCenter";
|
||||||
export { formatError, type GraphQLError } from "./error";
|
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