@@ -94,7 +94,7 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
|
||||
{audits.map((audit, index) => (
|
||||
<AuditRow
|
||||
key={index}
|
||||
audit={audit}
|
||||
auditFragmentRef={audit}
|
||||
onChangeVisibility={onChangeVisibility}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
@@ -116,23 +116,24 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
|
||||
}
|
||||
|
||||
function AuditRow(props: {
|
||||
audit: TrustCenterAuditsCardFragment$key;
|
||||
auditFragmentRef: TrustCenterAuditsCardFragment$key;
|
||||
onChangeVisibility: (auditId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const audit = useFragment(trustCenterAuditFragment, props.audit);
|
||||
const { auditFragmentRef, onChangeVisibility, disabled } = props;
|
||||
const audit = useFragment(trustCenterAuditFragment, auditFragmentRef);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const canUpdate = organizationId ? isAuthorized("TrustCenter", "updateTrustCenter") : false;
|
||||
|
||||
const handleValueChange = useCallback((value: string | {}) => {
|
||||
const handleValueChange = useCallback((value: string) => {
|
||||
const stringValue = typeof value === 'string' ? value : '';
|
||||
const typedValue = stringValue as "NONE" | "PRIVATE" | "PUBLIC";
|
||||
setOptimisticValue(typedValue);
|
||||
props.onChangeVisibility(audit.id, typedValue);
|
||||
}, [audit.id, props.onChangeVisibility]);
|
||||
onChangeVisibility(audit.id, typedValue);
|
||||
}, [audit.id, onChangeVisibility]);
|
||||
|
||||
useEffect(() => {
|
||||
if (optimisticValue && audit.trustCenterVisibility === optimisticValue) {
|
||||
@@ -167,7 +168,7 @@ function AuditRow(props: {
|
||||
type="select"
|
||||
value={currentValue}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={props.disabled || !canUpdate}
|
||||
disabled={disabled || !canUpdate}
|
||||
className="w-[105px]"
|
||||
>
|
||||
{visibilityOptions.map((option) => (
|
||||
|
||||
@@ -98,7 +98,7 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
|
||||
{documents.map((document, index) => (
|
||||
<DocumentRow
|
||||
key={index}
|
||||
document={document}
|
||||
documentFragmentRef={document}
|
||||
onChangeVisibility={onChangeVisibility}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
@@ -121,23 +121,24 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
|
||||
}
|
||||
|
||||
function DocumentRow(props: {
|
||||
document: TrustCenterDocumentsCardFragment$key;
|
||||
documentFragmentRef: TrustCenterDocumentsCardFragment$key;
|
||||
onChangeVisibility: (documentId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const document = useFragment(trustCenterDocumentFragment, props.document);
|
||||
const { documentFragmentRef, onChangeVisibility, disabled } = props;
|
||||
const document = useFragment(trustCenterDocumentFragment, documentFragmentRef);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter");
|
||||
|
||||
const handleValueChange = useCallback((value: string | {}) => {
|
||||
const handleValueChange = useCallback((value: string) => {
|
||||
const stringValue = typeof value === 'string' ? value : '';
|
||||
const typedValue = stringValue as "NONE" | "PRIVATE" | "PUBLIC";
|
||||
setOptimisticValue(typedValue);
|
||||
props.onChangeVisibility(document.id, typedValue);
|
||||
}, [document.id, props.onChangeVisibility]);
|
||||
onChangeVisibility(document.id, typedValue);
|
||||
}, [document.id, onChangeVisibility]);
|
||||
|
||||
useEffect(() => {
|
||||
if (optimisticValue && document.trustCenterVisibility === optimisticValue) {
|
||||
@@ -167,7 +168,7 @@ function DocumentRow(props: {
|
||||
type="select"
|
||||
value={currentValue}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={props.disabled || !canUpdate}
|
||||
disabled={disabled || !canUpdate}
|
||||
className="w-[105px]"
|
||||
>
|
||||
{visibilityOptions.map((option) => (
|
||||
|
||||
@@ -145,18 +145,18 @@ function FileRow(props: {
|
||||
onDelete: (id: string) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const file = props.file;
|
||||
const { file, onChangeVisibility, onEdit, onDelete, disabled } = props;
|
||||
const { __ } = useTranslate();
|
||||
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const canUpdate = isAuthorized("TrustCenter", "updateTrustCenter");
|
||||
|
||||
const handleValueChange = useCallback((value: string | {}) => {
|
||||
const handleValueChange = useCallback((value: string) => {
|
||||
const stringValue = typeof value === 'string' ? value : '';
|
||||
const typedValue = stringValue as "NONE" | "PRIVATE" | "PUBLIC";
|
||||
setOptimisticValue(typedValue);
|
||||
props.onChangeVisibility(file.id, typedValue);
|
||||
}, [file.id, props.onChangeVisibility]);
|
||||
onChangeVisibility(file.id, typedValue);
|
||||
}, [file.id, onChangeVisibility]);
|
||||
|
||||
useEffect(() => {
|
||||
if (optimisticValue && file.trustCenterVisibility === optimisticValue) {
|
||||
@@ -182,7 +182,7 @@ function FileRow(props: {
|
||||
type="select"
|
||||
value={currentValue}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={props.disabled || !canUpdate}
|
||||
disabled={disabled || !canUpdate}
|
||||
className="w-[105px]"
|
||||
>
|
||||
{visibilityOptions.map((option) => (
|
||||
@@ -208,8 +208,8 @@ function FileRow(props: {
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconPencil}
|
||||
onClick={() => props.onEdit({ id: file.id, name: file.name, category: file.category })}
|
||||
disabled={props.disabled}
|
||||
onClick={() => onEdit({ id: file.id, name: file.name, category: file.category })}
|
||||
disabled={disabled}
|
||||
title={__("Edit")}
|
||||
/>
|
||||
)}
|
||||
@@ -217,8 +217,8 @@ function FileRow(props: {
|
||||
<Button
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={() => props.onDelete(file.id)}
|
||||
disabled={props.disabled}
|
||||
onClick={() => onDelete(file.id)}
|
||||
disabled={disabled}
|
||||
title={__("Delete")}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { graphql } from 'react-relay';
|
||||
import { useLazyLoadQuery, usePaginationFragment } from 'react-relay';
|
||||
import type {
|
||||
TrustCenterAccessGraphQuery
|
||||
TrustCenterAccessGraphQuery,
|
||||
} from "./__generated__/TrustCenterAccessGraphQuery.graphql";
|
||||
import type { TrustCenterAccessGraph_accesses$data, TrustCenterAccessGraph_accesses$key } from './__generated__/TrustCenterAccessGraph_accesses.graphql';
|
||||
|
||||
export const trustCenterAccessesPaginationFragment = graphql`
|
||||
fragment TrustCenterAccessGraph_accesses on TrustCenter
|
||||
@@ -140,7 +141,7 @@ export const loadTrustCenterAccessDocumentAccessesQuery = graphql`
|
||||
`;
|
||||
|
||||
interface PaginatedData {
|
||||
data: { node: any } | null;
|
||||
data: TrustCenterAccessGraph_accesses$data | null;
|
||||
hasNext: boolean;
|
||||
loadMore: () => void;
|
||||
isLoadingNext: boolean;
|
||||
@@ -157,6 +158,18 @@ export function useTrustCenterAccesses(trustCenterId: string): PaginatedData {
|
||||
{ fetchPolicy: 'store-and-network' }
|
||||
);
|
||||
|
||||
const trustCenter = data?.node;
|
||||
|
||||
const {
|
||||
data: paginationData,
|
||||
loadNext,
|
||||
hasNext,
|
||||
isLoadingNext,
|
||||
} = usePaginationFragment<TrustCenterAccessGraphQuery, TrustCenterAccessGraph_accesses$key>(
|
||||
trustCenterAccessesPaginationFragment,
|
||||
trustCenter
|
||||
);
|
||||
|
||||
if (!trustCenterId) {
|
||||
return {
|
||||
data: null,
|
||||
@@ -166,24 +179,12 @@ export function useTrustCenterAccesses(trustCenterId: string): PaginatedData {
|
||||
};
|
||||
}
|
||||
|
||||
const trustCenter = data?.node as any;
|
||||
|
||||
const {
|
||||
data: paginationData,
|
||||
loadNext,
|
||||
hasNext,
|
||||
isLoadingNext,
|
||||
} = usePaginationFragment(
|
||||
trustCenterAccessesPaginationFragment,
|
||||
trustCenter
|
||||
);
|
||||
|
||||
const loadMore = () => {
|
||||
loadNext(10);
|
||||
};
|
||||
|
||||
return {
|
||||
data: { node: paginationData },
|
||||
data: paginationData,
|
||||
hasNext,
|
||||
loadMore,
|
||||
isLoadingNext,
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useState, useCallback, useEffect, useRef, use } from "react";
|
||||
import { useQueryLoader, usePreloadedQuery } from 'react-relay';
|
||||
import { useState, useCallback, useEffect, useRef, use, useMemo } from "react";
|
||||
import { useQueryLoader, usePreloadedQuery, type PreloadedQuery } from 'react-relay';
|
||||
import z from "zod";
|
||||
import {
|
||||
useTrustCenterAccesses,
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
import type { TrustCenterAccessGraphLoadDocumentAccessesQuery } from "/hooks/graph/__generated__/TrustCenterAccessGraphLoadDocumentAccessesQuery.graphql";
|
||||
|
||||
type ContextType = {
|
||||
organization: {
|
||||
@@ -80,7 +81,6 @@ type ContextType = {
|
||||
};
|
||||
|
||||
type DocumentAccessInfo = {
|
||||
id: string;
|
||||
active: boolean;
|
||||
requested: boolean;
|
||||
document?: {
|
||||
@@ -91,12 +91,12 @@ type DocumentAccessInfo = {
|
||||
report?: {
|
||||
id: string;
|
||||
filename: string;
|
||||
audit: {
|
||||
audit?: {
|
||||
id: string;
|
||||
framework: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
} | null;
|
||||
trustCenterFile?: {
|
||||
id: string;
|
||||
@@ -109,16 +109,16 @@ function DocumentAccessesLoader({
|
||||
queryReference,
|
||||
onDataLoaded
|
||||
}: {
|
||||
queryReference: any;
|
||||
queryReference: PreloadedQuery<TrustCenterAccessGraphLoadDocumentAccessesQuery>;
|
||||
onDataLoaded: (documentAccesses: DocumentAccessInfo[]) => void;
|
||||
}) {
|
||||
const data = usePreloadedQuery(loadTrustCenterAccessDocumentAccessesQuery, queryReference);
|
||||
|
||||
useEffect(() => {
|
||||
if (data && typeof data === 'object' && 'node' in data) {
|
||||
const node = (data as any).node;
|
||||
const node = data.node;
|
||||
if (node?.availableDocumentAccesses?.edges) {
|
||||
const documentAccesses = node.availableDocumentAccesses.edges.map((edge: any) => edge.node);
|
||||
const documentAccesses: DocumentAccessInfo[] = node.availableDocumentAccesses.edges.map((edge) => edge.node);
|
||||
onDataLoaded(documentAccesses);
|
||||
}
|
||||
}
|
||||
@@ -157,10 +157,10 @@ export default function TrustCenterAccessTab() {
|
||||
const dialogRef = useDialogRef();
|
||||
const editDialogRef = useDialogRef();
|
||||
const [editingAccess, setEditingAccess] = useState<AccessType | null>(null);
|
||||
const [editingDocumentAccesses, setEditingDocumentAccesses] = useState<DocumentAccessType[]>([]);
|
||||
const [editingDocumentAccesses, setEditingDocumentAccesses] = useState<DocumentAccessInfo[]>([]);
|
||||
const [selectedDocumentAccesses, setSelectedDocumentAccesses] = useState<Set<string>>(new Set());
|
||||
const [pendingEditEmail, setPendingEditEmail] = useState<string | null>(null);
|
||||
const [documentAccessesQueryReference, loadDocumentAccessesQuery] = useQueryLoader(loadTrustCenterAccessDocumentAccessesQuery);
|
||||
const [documentAccessesQueryReference, loadDocumentAccessesQuery] = useQueryLoader<TrustCenterAccessGraphLoadDocumentAccessesQuery>(loadTrustCenterAccessDocumentAccessesQuery);
|
||||
const loadedAccessIdRef = useRef<string | null>(null);
|
||||
const [isLoadingDocumentAccesses, setIsLoadingDocumentAccesses] = useState(false);
|
||||
|
||||
@@ -197,37 +197,11 @@ export default function TrustCenterAccessTab() {
|
||||
defaultValues: { name: "", active: false },
|
||||
});
|
||||
|
||||
type DocumentAccessType = {
|
||||
id: string;
|
||||
active: boolean;
|
||||
requested: boolean;
|
||||
document?: {
|
||||
id: string;
|
||||
title: string;
|
||||
documentType: string;
|
||||
} | null;
|
||||
report?: {
|
||||
id: string;
|
||||
filename: string;
|
||||
audit: {
|
||||
id: string;
|
||||
framework: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
} | null;
|
||||
trustCenterFile?: {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
function getDocumentAccessInfo(
|
||||
docAccess: DocumentAccessType,
|
||||
docAccess: DocumentAccessInfo,
|
||||
__: (key: string) => string
|
||||
) {
|
||||
if (!!docAccess.document) {
|
||||
if (docAccess.document) {
|
||||
return {
|
||||
variant: "info" as const,
|
||||
name: docAccess.document?.title,
|
||||
@@ -238,7 +212,7 @@ export default function TrustCenterAccessTab() {
|
||||
active: docAccess.active,
|
||||
};
|
||||
}
|
||||
if (!!docAccess.report) {
|
||||
if (docAccess.report) {
|
||||
return {
|
||||
variant: "success" as const,
|
||||
name: docAccess.report?.filename,
|
||||
@@ -249,7 +223,7 @@ export default function TrustCenterAccessTab() {
|
||||
active: docAccess.active,
|
||||
};
|
||||
}
|
||||
if (!!docAccess.trustCenterFile) {
|
||||
if (docAccess.trustCenterFile) {
|
||||
return {
|
||||
variant: "highlight" as const,
|
||||
name: docAccess.trustCenterFile?.name,
|
||||
@@ -274,30 +248,21 @@ export default function TrustCenterAccessTab() {
|
||||
lastTokenExpiresAt: string | null;
|
||||
pendingRequestCount: number;
|
||||
activeCount: number;
|
||||
documentAccesses?: DocumentAccessType[];
|
||||
documentAccesses?: DocumentAccessInfo[];
|
||||
};
|
||||
|
||||
const { data: trustCenterData, loadMore, hasNext, isLoadingNext } = useTrustCenterAccesses(organization.trustCenter?.id || "");
|
||||
|
||||
const accesses: AccessType[] = trustCenterData?.node?.accesses?.edges?.map((edge: any) => ({
|
||||
id: edge.node.id,
|
||||
email: edge.node.email,
|
||||
name: edge.node.name,
|
||||
active: edge.node.active,
|
||||
hasAcceptedNonDisclosureAgreement: edge.node.hasAcceptedNonDisclosureAgreement,
|
||||
createdAt: edge.node.createdAt,
|
||||
lastTokenExpiresAt: edge.node.lastTokenExpiresAt,
|
||||
pendingRequestCount: edge.node.pendingRequestCount || 0,
|
||||
activeCount: edge.node.activeCount || 0,
|
||||
})) ?? [];
|
||||
|
||||
const accesses: AccessType[] = useMemo(
|
||||
() => trustCenterData?.accesses?.edges.map((edge) => edge.node) ?? [], [trustCenterData?.accesses?.edges]
|
||||
);
|
||||
|
||||
const handleInvite = inviteForm.handleSubmit(async (data) => {
|
||||
if (!organization.trustCenter?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionId = trustCenterData?.node?.accesses?.__id;
|
||||
const connectionId = trustCenterData?.accesses?.__id;
|
||||
const email = data.email.trim();
|
||||
|
||||
await createInvitation({
|
||||
@@ -317,7 +282,7 @@ export default function TrustCenterAccessTab() {
|
||||
});
|
||||
|
||||
const handleDelete = useCallback(async (id: string) => {
|
||||
const connectionId = trustCenterData?.node?.accesses?.__id;
|
||||
const connectionId = trustCenterData?.accesses?.__id;
|
||||
|
||||
await deleteInvitation({
|
||||
variables: {
|
||||
|
||||
@@ -8,14 +8,13 @@ import { input } from "../Input/Input";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
variant?: "bordered" | "ghost" | "title";
|
||||
autogrow?: boolean;
|
||||
ref?: RefCallback<HTMLTextAreaElement>;
|
||||
};
|
||||
|
||||
export function Textarea(props: Props) {
|
||||
const ref = useRef<HTMLTextAreaElement>(null);
|
||||
const { autogrow, variant, ref: propsRef, ...restProps } = props;
|
||||
const { autogrow, ref: propsRef, ...restProps } = props;
|
||||
|
||||
const adjustHeight = () => {
|
||||
if (!autogrow || !ref.current) return;
|
||||
|
||||
@@ -14,7 +14,7 @@ type BaseProps<T extends string, P> = {
|
||||
children?: ReactNode;
|
||||
} & P;
|
||||
|
||||
type Props =
|
||||
type Props<T extends string | readonly string[] | number = string> =
|
||||
| BaseProps<never, ComponentProps<typeof Input>>
|
||||
| BaseProps<"text", ComponentProps<typeof Input>>
|
||||
| BaseProps<"email", ComponentProps<typeof Input>>
|
||||
@@ -22,7 +22,7 @@ type Props =
|
||||
| BaseProps<"password", ComponentProps<typeof Input>>
|
||||
| BaseProps<"textarea", ComponentProps<typeof Textarea>>
|
||||
| BaseProps<"number", ComponentProps<typeof Input>>
|
||||
| BaseProps<"select", ComponentProps<typeof Select>>;
|
||||
| BaseProps<"select", ComponentProps<typeof Select<T>>>;
|
||||
|
||||
const field = tv({
|
||||
slots: {
|
||||
@@ -34,7 +34,7 @@ const field = tv({
|
||||
|
||||
const { base: baseClass, label: labelClass, help: helpClass } = field();
|
||||
|
||||
export function Field(props: Props) {
|
||||
export function Field<T extends string | readonly string[] | number = string>(props: Props<T>) {
|
||||
const showHelp = props.help && !props.error;
|
||||
const childrenAsInput = !props.type && props.children;
|
||||
return (
|
||||
@@ -55,8 +55,8 @@ export function Field(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function getInput(props: Props) {
|
||||
const { label, error, onValueChange, type, ...restProps } = props;
|
||||
function getInput<T extends string | readonly string[] | number = string>(props: Props<T>) {
|
||||
const { error, onValueChange, type, ...restProps } = props;
|
||||
const baseProps = {
|
||||
["aria-invalid"]: !!error,
|
||||
name: props.name,
|
||||
@@ -67,7 +67,7 @@ function getInput(props: Props) {
|
||||
case "select":
|
||||
return (
|
||||
// @ts-expect-error Select is too dynamic
|
||||
<Select
|
||||
<Select<T>
|
||||
{...baseProps}
|
||||
{...restProps}
|
||||
onValueChange={onValueChange}
|
||||
|
||||
Reference in New Issue
Block a user