Redesign trust center console

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-28 13:44:41 +01:00
parent abeaaec734
commit 1f0a5dff5c
39 changed files with 2308 additions and 2423 deletions

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -18,7 +17,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getAuditStateVariant, getAuditStateLabel, formatDate, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql";
const trustCenterAuditFragment = graphql`
@@ -50,17 +48,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onChangeVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const audits = useMemo(() => {
return limit ? props.audits.slice(0, limit) : props.audits;
}, [props.audits, limit]);
const showMoreButton = limit !== null && props.audits.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (auditId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
@@ -74,11 +70,9 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper {...(variant === "card" ? { padded: true } : {})} className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Framework")}</Th>
@@ -116,7 +110,7 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.audits.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -21,7 +20,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
const trustCenterDocumentFragment = graphql`
fragment TrustCenterDocumentsCardFragment on Document {
@@ -55,17 +53,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onChangeVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const documents = useMemo(() => {
return limit ? props.documents.slice(0, limit) : props.documents;
}, [props.documents, limit]);
const showMoreButton = limit !== null && props.documents.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (documentId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
@@ -79,11 +75,9 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -121,7 +115,7 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.documents.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -22,7 +21,6 @@ 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 {
@@ -52,17 +50,15 @@ type Props<Params> = {
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 [limit, setLimit] = useState<number | null>(100);
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({
@@ -76,11 +72,9 @@ export function TrustCenterFilesCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -120,7 +114,7 @@ export function TrustCenterFilesCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.files.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}
@@ -215,7 +209,7 @@ function FileRow(props: {
title={__("Edit")}
/>
<Button
variant="secondary"
variant="danger"
icon={IconTrashCan}
onClick={() => props.onDelete(file.id)}
disabled={props.disabled}

View File

@@ -3,10 +3,16 @@ import { safeOpenUrl } from "@probo/helpers";
import {
Avatar,
Button,
Card,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
IconPlusLarge,
IconTrashCan,
IconPencil,
IconArrowLink,
} from "@probo/ui";
import { type ReactNode, useRef } from "react";
import {
@@ -64,7 +70,6 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
</p>
</div>
<Button
variant="secondary"
icon={IconPlusLarge}
onClick={handleCreate}
>
@@ -72,33 +77,33 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
</Button>
</div>
<Card padded>
{references.length === 0 ? (
<div className="text-center py-12">
<div className="mx-auto w-12 h-12 bg-tertiary rounded-lg flex items-center justify-center mb-4">
<IconPlusLarge size={24} className="text-txt-tertiary" />
</div>
<h3 className="text-lg font-medium text-txt-primary mb-2">
{__("No references yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("Add customer testimonials and partner references to build trust")}
</p>
</div>
) : (
<div className="space-y-4">
{references.map((reference: Reference) => (
<ReferenceRow
key={reference.id}
reference={reference}
onEdit={() => handleEdit(reference)}
connectionId={referencesConnectionId}
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
/>
))}
</div>
)}
</Card>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Description")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{references.length === 0 && (
<Tr>
<Td colSpan={3} className="text-center text-txt-secondary">
{__("No references available")}
</Td>
</Tr>
)}
{references.map((reference: Reference) => (
<ReferenceRow
key={reference.id}
reference={reference}
onEdit={() => handleEdit(reference)}
connectionId={referencesConnectionId}
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
/>
))}
</Tbody>
</Table>
<TrustCenterReferenceDialog ref={dialogRef} />
</div>
@@ -113,52 +118,47 @@ type ReferenceRowProps = {
};
function ReferenceRow({ reference, onEdit, connectionId, onVisitWebsite }: ReferenceRowProps) {
return (
<div className="flex items-center justify-between p-4 bg-level-1 rounded-lg">
<div className="flex items-center space-x-4 flex-1">
<Avatar
src={reference.logoUrl}
name={reference.name}
size="l"
/>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 mb-1">
<button
type="button"
onClick={onVisitWebsite}
className="font-medium text-txt-primary truncate hover:text-primary hover:underline text-left cursor-pointer"
>
{reference.name}
</button>
</div>
<p className="text-sm text-txt-secondary line-clamp-2 mb-2">
{reference.description}
</p>
</div>
</div>
<div className="flex items-center justify-center space-x-2 ml-4">
<Button
variant="tertiary"
icon={IconPencil}
onClick={onEdit}
aria-label="Edit reference"
/>
<DeleteTrustCenterReferenceDialog
referenceId={reference.id}
referenceName={reference.name}
connectionId={connectionId}
>
<Button
variant="danger"
icon={IconTrashCan}
aria-label="Delete reference"
<Tr>
<Td>
<div className="flex items-center gap-3">
<Avatar
src={reference.logoUrl}
name={reference.name}
size="m"
/>
</DeleteTrustCenterReferenceDialog>
</div>
</div>
<span className="font-medium">{reference.name}</span>
</div>
</Td>
<Td>
<span className="text-txt-secondary line-clamp-2">
{reference.description}
</span>
</Td>
<Td noLink width={200} className="text-end">
<div className="flex gap-2 justify-end">
<Button
variant="secondary"
icon={IconArrowLink}
onClick={onVisitWebsite}
/>
<Button
variant="secondary"
icon={IconPencil}
onClick={onEdit}
/>
<DeleteTrustCenterReferenceDialog
referenceId={reference.id}
referenceName={reference.name}
connectionId={connectionId}
>
<Button
variant="danger"
icon={IconTrashCan}
/>
</DeleteTrustCenterReferenceDialog>
</div>
</Td>
</Tr>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -18,7 +17,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState } from "react";
import { sprintf } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
import type { TrustCenterVendorsCardFragment$key } from "./__generated__/TrustCenterVendorsCardFragment.graphql";
const trustCenterVendorFragment = graphql`
@@ -46,17 +44,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onToggleVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const vendors = useMemo(() => {
return limit ? props.vendors.slice(0, limit) : props.vendors;
}, [props.vendors, limit]);
const showMoreButton = limit !== null && props.vendors.length > limit;
const variant = props.variant ?? "table";
const onToggleVisibility = (vendorId: string, showOnTrustCenter: boolean) => {
props.onToggleVisibility({
@@ -70,11 +66,9 @@ export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -111,7 +105,7 @@ export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.vendors.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}