Add trust center files

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-24 10:04:54 +02:00
parent e53b1239db
commit 1bd6f7c1c9
55 changed files with 9074 additions and 394 deletions

View File

@@ -0,0 +1,228 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
Table,
Thead,
Tbody,
Th,
IconChevronDown,
Field,
Option,
Badge,
IconPencil,
IconTrashCan,
IconArrowLink,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { TrustCenterFilesCardFragment$key, TrustCenterFilesCardFragment$data } from "./__generated__/TrustCenterFilesCardFragment.graphql";
import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { formatDate } from "@probo/helpers";
import clsx from "clsx";
const trustCenterFileFragment = graphql`
fragment TrustCenterFilesCardFragment on TrustCenterFile {
id
name
category
fileUrl
trustCenterVisibility
createdAt
updatedAt
}
`;
type Mutation<Params> = (p: {
variables: {
input: {
id: string;
trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC";
} & Params;
};
}) => void;
type Props<Params> = {
files: TrustCenterFilesCardFragment$key[];
params: Params;
disabled?: boolean;
onChangeVisibility: Mutation<Params>;
onEdit: (file: { id: string; name: string; category: string }) => void;
onDelete: (id: string) => void;
variant?: "card" | "table";
};
export function TrustCenterFilesCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const files = useMemo(() => {
return limit ? props.files.slice(0, limit) : props.files;
}, [props.files, limit]);
const showMoreButton = limit !== null && props.files.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (fileId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
variables: {
input: {
id: fileId,
trustCenterVisibility,
...props.params,
},
},
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Category")}</Th>
<Th>{__("Upload Date")}</Th>
<Th>{__("Visibility")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{files.length === 0 && (
<Tr>
<Td colSpan={5} className="text-center text-txt-secondary">
{__("No files available")}
</Td>
</Tr>
)}
{files.map((fileFragmentRef, index) => (
<FileRowWrapper
key={index}
fileFragmentRef={fileFragmentRef}
onChangeVisibility={onChangeVisibility}
onEdit={props.onEdit}
onDelete={props.onDelete}
disabled={props.disabled}
/>
))}
</Tbody>
</Table>
{showMoreButton && (
<Button
variant="tertiary"
onClick={() => setLimit(null)}
className="mt-3 mx-auto"
icon={IconChevronDown}
>
{sprintf(__("Show %s more"), props.files.length - limit)}
</Button>
)}
</Wrapper>
);
}
function FileRowWrapper(props: {
fileFragmentRef: TrustCenterFilesCardFragment$key;
onChangeVisibility: (fileId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => void;
onEdit: (file: { id: string; name: string; category: string }) => void;
onDelete: (id: string) => void;
disabled?: boolean;
}) {
const file = useFragment(trustCenterFileFragment, props.fileFragmentRef);
return (
<FileRow
file={file}
onChangeVisibility={props.onChangeVisibility}
onEdit={props.onEdit}
onDelete={props.onDelete}
disabled={props.disabled}
/>
);
}
function FileRow(props: {
file: TrustCenterFilesCardFragment$data;
onChangeVisibility: (fileId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => void;
onEdit: (file: { id: string; name: string; category: string }) => void;
onDelete: (id: string) => void;
disabled?: boolean;
}) {
const file = props.file;
const { __ } = useTranslate();
const [optimisticValue, setOptimisticValue] = useState<string | null>(null);
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]);
useEffect(() => {
if (optimisticValue && file.trustCenterVisibility === optimisticValue) {
setOptimisticValue(null);
}
}, [file.trustCenterVisibility, optimisticValue]);
const currentValue = optimisticValue || file.trustCenterVisibility;
const visibilityOptions = getTrustCenterVisibilityOptions(__);
return (
<Tr>
<Td>
<div className="flex gap-4 items-center">
{file.name}
</div>
</Td>
<Td>{file.category}</Td>
<Td>{formatDate(file.createdAt)}</Td>
<Td noLink width={130} className="pr-0">
<Field
type="select"
value={currentValue}
onValueChange={handleValueChange}
disabled={props.disabled}
className="w-[105px]"
>
{visibilityOptions.map((option) => (
<Option key={option.value} value={option.value}>
<div className="flex items-center justify-between w-full">
<Badge variant={option.variant}>
{option.label}
</Badge>
</div>
</Option>
))}
</Field>
</Td>
<Td noLink width={120}>
<div className="flex gap-2">
<Button
variant="secondary"
icon={IconArrowLink}
onClick={() => window.open(file.fileUrl, '_blank', 'noopener,noreferrer')}
title={__("Download")}
/>
<Button
variant="secondary"
icon={IconPencil}
onClick={() => props.onEdit({ id: file.id, name: file.name, category: file.category })}
disabled={props.disabled}
title={__("Edit")}
/>
<Button
variant="secondary"
icon={IconTrashCan}
onClick={() => props.onDelete(file.id)}
disabled={props.disabled}
title={__("Delete")}
/>
</div>
</Td>
</Tr>
);
}

View File

@@ -0,0 +1,91 @@
/**
* @generated SignedSource<<0defaaf1ce3544420e8fbd9c9f3af139>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC";
import { FragmentRefs } from "relay-runtime";
export type TrustCenterFilesCardFragment$data = {
readonly category: string;
readonly createdAt: any;
readonly fileUrl: string;
readonly id: string;
readonly name: string;
readonly trustCenterVisibility: TrustCenterVisibility;
readonly updatedAt: any;
readonly " $fragmentType": "TrustCenterFilesCardFragment";
};
export type TrustCenterFilesCardFragment$key = {
readonly " $data"?: TrustCenterFilesCardFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"TrustCenterFilesCardFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterFilesCardFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "trustCenterVisibility",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"type": "TrustCenterFile",
"abstractKey": null
};
(node as any).hash = "33cb01782ca37fc776cd8e5dfde20f76";
export default node;