Use framework logo on console & trust

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-16 19:05:34 +01:00
parent 427565c521
commit 8c772661bf
34 changed files with 375 additions and 337 deletions

View File

@@ -1,15 +0,0 @@
import { availableFrameworks } from "@probo/helpers";
import { Avatar } from "@probo/ui";
const availableLogos = new Map(
availableFrameworks.map((framework) => [framework.name, framework.logo])
);
export function FrameworkLogo({ name }: { name: string }) {
const logo = availableLogos.get(name);
return logo ? (
<img src={logo} alt="" className="h-12" />
) : (
<Avatar name={name} size="l" className="size-12" />
);
}

View File

@@ -36,6 +36,8 @@ export const auditNodeQuery = graphql`
framework {
id
name
lightLogoURL
darkLogoURL
}
organization {
id

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<fc4103e0e8b03c1b6b8ea07c4adcf095>>
* @generated SignedSource<<c802dc4c6e5b63e48b0050dfcf13b4b4>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -17,7 +17,9 @@ export type AuditGraphNodeQuery$data = {
readonly node: {
readonly createdAt?: any;
readonly framework?: {
readonly darkLogoURL: string | null | undefined;
readonly id: string;
readonly lightLogoURL: string | null | undefined;
readonly name: string;
};
readonly id?: string;
@@ -151,31 +153,47 @@ v9 = {
"name": "state",
"storageKey": null
},
v10 = [
(v2/*: any*/),
(v3/*: any*/)
],
v11 = {
v10 = {
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": (v10/*: any*/),
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
},
v12 = {
v11 = {
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": (v10/*: any*/),
"selections": [
(v2/*: any*/),
(v3/*: any*/)
],
"storageKey": null
},
v13 = {
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
@@ -207,10 +225,10 @@ return {
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v6/*: any*/),
(v13/*: any*/)
(v12/*: any*/)
],
"type": "Audit",
"abstractKey": null
@@ -253,10 +271,10 @@ return {
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v6/*: any*/),
(v13/*: any*/)
(v12/*: any*/)
],
"type": "Audit",
"abstractKey": null
@@ -267,16 +285,16 @@ return {
]
},
"params": {
"cacheID": "eede00ab53ccd396e92b7fe6c1f1c2a2",
"cacheID": "af5fde4e1522c98b908630fdf1451ae9",
"id": null,
"metadata": {},
"name": "AuditGraphNodeQuery",
"operationKind": "query",
"text": "query AuditGraphNodeQuery(\n $auditId: ID!\n) {\n node(id: $auditId) {\n __typename\n ... on Audit {\n id\n name\n validFrom\n validUntil\n report {\n id\n filename\n mimeType\n size\n downloadUrl\n createdAt\n }\n reportUrl\n state\n framework {\n id\n name\n }\n organization {\n id\n name\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n"
"text": "query AuditGraphNodeQuery(\n $auditId: ID!\n) {\n node(id: $auditId) {\n __typename\n ... on Audit {\n id\n name\n validFrom\n validUntil\n report {\n id\n filename\n mimeType\n size\n downloadUrl\n createdAt\n }\n reportUrl\n state\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n organization {\n id\n name\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "c07896ae39a7d16b42c7ee4216dc28fa";
(node as any).hash = "db92119c6d6f000963a0ff3d0fb74dfd";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<a67eb2a88e39d7eaf96910e0318dd1bc>>
* @generated SignedSource<<7aaf0b32e40fe8c723a28ab9590b62b5>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -243,6 +243,20 @@ return {
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
@@ -275,7 +289,7 @@ return {
]
},
"params": {
"cacheID": "b31afd68395ae800a18885bca3abfa1c",
"cacheID": "90e970007a500be89809ac0f2a77bccd",
"id": null,
"metadata": {
"connection": [
@@ -292,7 +306,7 @@ return {
},
"name": "FrameworkGraphListQuery",
"operationKind": "query",
"text": "query FrameworkGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n frameworks(first: 100) {\n edges {\n node {\n id\n ...FrameworksPageCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
"text": "query FrameworkGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n frameworks(first: 100) {\n edges {\n node {\n id\n ...FrameworksPageCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<8901b5c750861080551542d26c2681d6>>
* @generated SignedSource<<6fcc115e1ab6df38ede8756968ae7a6a>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -123,6 +123,20 @@ return {
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -228,12 +242,12 @@ return {
]
},
"params": {
"cacheID": "29d6f638feb1bb633169abcfe083f5d2",
"cacheID": "68c09567aff1176075300c645f5d117d",
"id": null,
"metadata": {},
"name": "FrameworkGraphNodeQuery",
"operationKind": "query",
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n organization {\n name\n id\n }\n controls(first: 250, orderBy: {field: SECTION_TITLE, direction: ASC}) {\n edges {\n node {\n id\n sectionTitle\n name\n status\n exclusionJustification\n }\n }\n }\n}\n"
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n organization {\n name\n id\n }\n controls(first: 250, orderBy: {field: SECTION_TITLE, direction: ASC}) {\n edges {\n node {\n id\n sectionTitle\n name\n status\n exclusionJustification\n }\n }\n }\n}\n"
}
};
})();

View File

@@ -25,14 +25,24 @@ import {
IconArrowInbox,
useConfirm,
useToast,
FrameworkLogo,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { FrameworkLogo } from "/components/FrameworkLogo";
import { ControlledField } from "/components/form/ControlledField";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
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 { use } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
@@ -42,7 +52,13 @@ const updateAuditSchema = z.object({
name: z.string().nullable().optional(),
validFrom: z.string().optional(),
validUntil: z.string().optional(),
state: z.enum(["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "REJECTED", "OUTDATED"]),
state: z.enum([
"NOT_STARTED",
"IN_PROGRESS",
"COMPLETED",
"REJECTED",
"OUTDATED",
]),
});
type Props = {
@@ -50,7 +66,10 @@ type Props = {
};
export default function AuditDetailsPage(props: Props) {
const audit = usePreloadedQuery<AuditGraphNodeQuery>(auditNodeQuery, props.queryRef);
const audit = usePreloadedQuery<AuditGraphNodeQuery>(
auditNodeQuery,
props.queryRef
);
const auditEntry = audit.node;
const { __ } = useTranslate();
const organizationId = useOrganizationId();
@@ -58,19 +77,20 @@ export default function AuditDetailsPage(props: Props) {
const navigate = useNavigate();
const deleteAudit = useDeleteAudit(
{ id: auditEntry.id!, framework: { name: auditEntry.framework!.name} },
{ id: auditEntry.id!, framework: { name: auditEntry.framework!.name } },
ConnectionHandler.getConnectionID(organizationId, "AuditsPage_audits"),
() => navigate(`/organizations/${organizationId}/audits`)
);
const { control, formState, handleSubmit, register, reset } = useFormWithSchema(updateAuditSchema, {
defaultValues: {
name: auditEntry.name || null,
validFrom: auditEntry.validFrom?.split('T')[0] || "",
validUntil: auditEntry.validUntil?.split('T')[0] || "",
state: auditEntry.state || "NOT_STARTED",
},
});
const { control, formState, handleSubmit, register, reset } =
useFormWithSchema(updateAuditSchema, {
defaultValues: {
name: auditEntry.name || null,
validFrom: auditEntry.validFrom?.split("T")[0] || "",
validUntil: auditEntry.validUntil?.split("T")[0] || "",
state: auditEntry.state || "NOT_STARTED",
},
});
const updateAudit = useUpdateAudit();
const [uploadAuditReport, isUploading] = useUploadAuditReport();
@@ -98,7 +118,10 @@ export default function AuditDetailsPage(props: Props) {
} catch (error) {
toast({
title: __("Error"),
description: formatError(__("Failed to update audit"), error as GraphQLError),
description: formatError(
__("Failed to update audit"),
error as GraphQLError
),
variant: "error",
});
}
@@ -131,7 +154,9 @@ export default function AuditDetailsPage(props: Props) {
to: `/organizations/${organizationId}/audits`,
},
{
label: (auditEntry.name || auditEntry.framework?.name) ?? __("Unknown Audit"),
label:
(auditEntry.name || auditEntry.framework?.name) ??
__("Unknown Audit"),
},
]}
/>
@@ -139,10 +164,16 @@ export default function AuditDetailsPage(props: Props) {
<div className="flex justify-between items-start">
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<FrameworkLogo name={auditEntry.framework?.name || ""} />
<FrameworkLogo
name={auditEntry.framework?.name || ""}
lightLogoURL={auditEntry.framework?.lightLogoURL}
darkLogoURL={auditEntry.framework?.darkLogoURL}
/>
<div className="text-2xl">{auditEntry.framework?.name}</div>
</div>
<Badge variant={getAuditStateVariant(auditEntry.state || "NOT_STARTED")}>
<Badge
variant={getAuditStateVariant(auditEntry.state || "NOT_STARTED")}
>
{getAuditStateLabel(__, auditEntry.state || "NOT_STARTED")}
</Badge>
</div>
@@ -187,12 +218,10 @@ export default function AuditDetailsPage(props: Props) {
</Field>
<div className="flex justify-end">
{formState.isDirty && (
isAuthorized("Audit", "updateAudit") && (
<Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? __("Updating...") : __("Update")}
</Button>
)
{formState.isDirty && isAuthorized("Audit", "updateAudit") && (
<Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? __("Updating...") : __("Update")}
</Button>
)}
</div>
</form>
@@ -201,7 +230,7 @@ export default function AuditDetailsPage(props: Props) {
<div className="space-y-4">
<h3 className="text-lg font-medium">{__("Audit Report")}</h3>
{auditEntry.report ? (
{auditEntry.report ? (
<div className="space-y-4">
<div className="flex items-center justify-between p-4 bg-success-50 border border-success-200 rounded-lg">
<div className="flex items-center gap-3">
@@ -211,11 +240,10 @@ export default function AuditDetailsPage(props: Props) {
{auditEntry.report.filename}
</p>
<div className="flex items-center gap-4 text-sm text-success-700">
<span>{fileSize(__, auditEntry.report.size)}</span>
<span>
{fileSize(__, auditEntry.report.size)}
</span>
<span>
{__("Uploaded")} {formatDate(auditEntry.report.createdAt)}
{__("Uploaded")}{" "}
{formatDate(auditEntry.report.createdAt)}
</span>
</div>
</div>
@@ -224,7 +252,7 @@ export default function AuditDetailsPage(props: Props) {
<DropdownItem
onClick={() => {
if (auditEntry.report?.downloadUrl) {
window.open(auditEntry.report.downloadUrl, '_blank');
window.open(auditEntry.report.downloadUrl, "_blank");
}
}}
icon={IconArrowInbox}
@@ -244,10 +272,14 @@ export default function AuditDetailsPage(props: Props) {
) : (
<div className="space-y-4">
<p className="text-neutral-600">
{__("Upload the final audit report document (PDF recommended)")}
{__(
"Upload the final audit report document (PDF recommended)"
)}
</p>
<Dropzone
description={__("Only PDF, DOCX files up to 25MB are allowed")}
description={__(
"Only PDF, DOCX files up to 25MB are allowed"
)}
isUploading={isUploading}
onDrop={async (files) => {
if (files.length > 0 && auditEntry.id) {

View File

@@ -10,12 +10,12 @@ import {
Button,
ControlItem,
DropdownItem,
FrameworkLogo,
IconPencil,
IconPlusLarge,
IconTrashCan,
PageHeader,
} from "@probo/ui";
import { FrameworkLogo } from "/components/FrameworkLogo";
import {
connectionListKey,
frameworkNodeQuery,
@@ -39,6 +39,8 @@ const frameworkDetailFragment = graphql`
id
name
description
lightLogoURL
darkLogoURL
organization {
name
}
@@ -70,12 +72,8 @@ const generateFrameworkStateOfApplicabilityMutation = graphql`
`;
const exportFrameworkMutation = graphql`
mutation FrameworkDetailPageExportFrameworkMutation(
$frameworkId: ID!
) {
exportFramework(
input: { frameworkId: $frameworkId }
) {
mutation FrameworkDetailPageExportFrameworkMutation($frameworkId: ID!) {
exportFramework(input: { frameworkId: $frameworkId }) {
exportJobId
}
}
@@ -120,7 +118,8 @@ export default function FrameworkDetailPage(props: Props) {
exportFrameworkMutation,
{
errorMessage: "Failed to export framework",
successMessage: "Framework export started successfully. You will receive an email when the export is ready.",
successMessage:
"Framework export started successfully. You will receive an email when the export is ready.",
}
);
@@ -193,7 +192,11 @@ export default function FrameworkDetailPage(props: Props) {
{__("Export Framework")}
</DropdownItem>
{isAuthorized("Framework", "deleteFramework") && (
<DropdownItem icon={IconTrashCan} variant="danger" onClick={onDelete}>
<DropdownItem
icon={IconTrashCan}
variant="danger"
onClick={onDelete}
>
{__("Delete")}
</DropdownItem>
)}

View File

@@ -5,6 +5,7 @@ import {
Card,
DropdownItem,
FileButton,
FrameworkLogo,
FrameworkSelector,
IconFolderUpload,
IconPencil,
@@ -27,7 +28,6 @@ import { Link } from "react-router";
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useState, type ChangeEventHandler, use } from "react";
import { FrameworkLogo } from "/components/FrameworkLogo";
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
import { PermissionsContext } from "/providers/PermissionsContext";
@@ -120,7 +120,8 @@ export default function FrameworksPage(props: Props) {
const isLoading = isUploading || isImporting;
const hasAnyAction = isAuthorized("Framework", "updateFramework") ||
const hasAnyAction =
isAuthorized("Framework", "updateFramework") ||
isAuthorized("Framework", "deleteFramework");
return (
@@ -172,6 +173,8 @@ const frameworkCardFragment = graphql`
id
name
description
lightLogoURL
darkLogoURL
}
`;
@@ -200,7 +203,11 @@ function FrameworkCard(props: FrameworkCardProps) {
framework={framework}
/>
<div className="flex justify-between mb-3">
<FrameworkLogo {...framework} />
<FrameworkLogo
name={framework.name}
lightLogoURL={framework.lightLogoURL}
darkLogoURL={framework.darkLogoURL}
/>
{props.hasAnyAction && (
<ActionDropdown className="z-10 relative">
{isAuthorized("Framework", "updateFramework") && (

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<daf4012a65afffdedc10e4f5c7e2bc3c>>
* @generated SignedSource<<1da75e11f49e841800329ded9f67a1d4>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -24,8 +24,10 @@ export type FrameworkDetailPageFragment$data = {
};
}>;
};
readonly darkLogoURL: string | null | undefined;
readonly description: string | null | undefined;
readonly id: string;
readonly lightLogoURL: string | null | undefined;
readonly name: string;
readonly organization: {
readonly name: string;
@@ -67,6 +69,20 @@ return {
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -167,6 +183,6 @@ return {
};
})();
(node as any).hash = "c31fd5c0f675d2524f736d69c4ebebe3";
(node as any).hash = "34a82531b8812ee16e75eb035bc739e8";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9fdc712e118402371c9951169aac320a>>
* @generated SignedSource<<412cc494e90709fca575c68f6deef112>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,8 +11,10 @@
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type FrameworksPageCardFragment$data = {
readonly darkLogoURL: string | null | undefined;
readonly description: string | null | undefined;
readonly id: string;
readonly lightLogoURL: string | null | undefined;
readonly name: string;
readonly " $fragmentType": "FrameworksPageCardFragment";
};
@@ -47,12 +49,26 @@ const node: ReaderFragment = {
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"type": "Framework",
"abstractKey": null
};
(node as any).hash = "4481f380673963e72f88071031e37d14";
(node as any).hash = "fe86a14971741a77f587ae015ebf0a22";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9436ef052ea63a9785d82ec810e810f1>>
* @generated SignedSource<<2256362d89b6b5c2f5c707acb44d283e>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -158,6 +158,20 @@ return {
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
@@ -187,12 +201,12 @@ return {
]
},
"params": {
"cacheID": "87a38788cb2e060657b552af69acb707",
"cacheID": "5584472f1e68fac3d766662c6d5dab90",
"id": null,
"metadata": {},
"name": "FrameworksPageImportMutation",
"operationKind": "mutation",
"text": "mutation FrameworksPageImportMutation(\n $input: ImportFrameworkInput!\n) {\n importFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
"text": "mutation FrameworksPageImportMutation(\n $input: ImportFrameworkInput!\n) {\n importFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<b8d5c939f1b3a4337be9a4ecc2e8fd98>>
* @generated SignedSource<<af136d6db1a014d6241ad2c9c6dd5f55>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -159,6 +159,20 @@ return {
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
@@ -188,12 +202,12 @@ return {
]
},
"params": {
"cacheID": "931895b4bbb7caacd8ee0ee69de76cd6",
"cacheID": "c66177d87b59d87fdf7cbc9268af431a",
"id": null,
"metadata": {},
"name": "FrameworkFormDialogMutation",
"operationKind": "mutation",
"text": "mutation FrameworkFormDialogMutation(\n $input: CreateFrameworkInput!\n) {\n createFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
"text": "mutation FrameworkFormDialogMutation(\n $input: CreateFrameworkInput!\n) {\n createFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n lightLogoURL\n darkLogoURL\n}\n"
}
};
})();

View File

@@ -1,10 +0,0 @@
<svg width="76" height="76" viewBox="0 0 76 76" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.7021 0H37.702C16.8798 0 0 16.8798 0 37.702V37.7021C0 58.5243 16.8798 75.4041 37.702 75.4041H37.7021C58.5243 75.4041 75.4041 58.5243 75.4041 37.7021V37.702C75.4041 16.8798 58.5243 0 37.7021 0Z" fill="#F0F7E2"/>
<path d="M38.0169 33.2129L36.7589 35.7618L33.9449 36.1706L35.9809 38.1548L35.4999 40.9566L38.0169 39.634L40.5329 40.9568L40.0519 38.1549L42.0879 36.1708L39.2749 35.7618L38.0169 33.2129ZM26.1859 36.7345L24.9279 39.2837L22.1149 39.6925L24.1509 41.6766L23.6699 44.4786L26.1859 43.1556L28.7029 44.4786L28.2219 41.6766L30.2579 39.6925L27.4439 39.2837L26.1859 36.7345ZM49.8469 36.7345L48.5889 39.2837L45.7749 39.6925L47.8109 41.6766L47.3309 44.4786L49.8469 43.1556L52.3629 44.4786L51.8819 41.6766L53.9179 39.6925L51.1049 39.2837L49.8469 36.7345ZM17.2239 45.7542L15.9659 48.3031L13.1519 48.7119L15.1879 50.696L14.7079 53.4979L17.2239 52.1752L19.7399 53.4979L19.2589 50.696L21.2949 48.712L18.4819 48.3031L17.2239 45.7542ZM58.8099 45.7542L57.5509 48.3031L54.7379 48.7119L56.7739 50.696L56.2929 53.4979L58.8099 52.1752L61.3249 53.4979L60.8449 50.696L62.8809 48.7119L60.0669 48.3029L58.8089 45.7539L58.8099 45.7542Z" fill="#93C926" fill-opacity="0.5"/>
<path d="M38.959 49.6387C52.2929 49.6387 64.0618 51.8547 71.085 55.2324C64.8414 67.0976 52.4614 75.2261 38.1631 75.3975C37.4509 75.3927 36.7434 75.3824 36.041 75.3652C22.6698 74.7855 11.1065 67.2433 4.88477 56.2715C11.5348 52.316 24.3014 49.6387 38.959 49.6387Z" fill="#5D770D"/>
<path d="M41.3359 54.0391V58.0684C41.3799 58.0246 41.4519 57.9808 41.5389 57.923C41.6409 57.8513 41.8439 57.7637 42.1489 57.6621C42.4529 57.5605 42.7859 57.5027 43.1209 57.5027C44.0189 57.5027 44.7739 57.8075 45.3809 58.4309C45.9909 59.0404 46.2939 59.7793 46.2939 60.6637C46.2939 61.548 45.9889 62.273 45.3649 62.8965C44.7559 63.5199 44.0029 63.8246 43.1189 63.8246C42.2339 63.8246 41.5089 63.5338 40.8859 62.9702C40.2619 62.4045 39.9439 61.6217 39.9439 60.6219V54.0391H41.3359ZM43.1189 58.8372C42.1189 58.8372 41.3359 59.6061 41.3359 60.6637C41.3359 61.7213 42.0889 62.4901 43.1189 62.4901C44.1479 62.4901 44.9009 61.7074 44.9009 60.6637C44.9009 59.62 44.1189 58.8372 43.1189 58.8372Z" fill="#FCFDFC"/>
<path d="M33.5719 58.4321C34.1949 57.8086 34.9339 57.5039 35.8189 57.5039C36.7029 57.5039 37.4279 57.8086 38.0519 58.4321C38.6749 59.0555 38.9799 59.7944 38.9799 60.6648C38.9799 61.5352 38.6749 62.2881 38.0519 62.9116C37.4279 63.521 36.6889 63.8258 35.8189 63.8258C34.9479 63.8258 34.1949 63.521 33.5719 62.9116C32.9489 62.2881 32.6439 61.5492 32.6439 60.6648C32.6439 59.7805 32.9489 59.0555 33.5719 58.4321ZM34.0359 60.6648C34.0359 61.2006 34.2089 61.6368 34.5439 61.9834C34.8929 62.316 35.3129 62.4913 35.8189 62.4913C36.3249 62.4913 36.7469 62.318 37.0799 61.9834C37.4279 61.6348 37.6009 61.2006 37.6009 60.6648C37.6009 60.1291 37.4279 59.6929 37.0799 59.3602C36.7469 59.0117 36.3269 58.8384 35.8189 58.8384C35.3109 58.8384 34.8909 59.0117 34.5439 59.3602C34.2109 59.6929 34.0359 60.1291 34.0359 60.6648Z" fill="#FCFDFC"/>
<path d="M48.1839 58.4321C48.8079 57.8086 49.5459 57.5039 50.4309 57.5039C51.3149 57.5039 52.0399 57.8086 52.6639 58.4321C53.2869 59.0555 53.5919 59.7944 53.5919 60.6648C53.5919 61.5352 53.2869 62.2881 52.6639 62.9116C52.0399 63.521 51.3009 63.8258 50.4309 63.8258C49.5599 63.8258 48.8079 63.521 48.1839 62.9116C47.5609 62.2881 47.2559 61.5492 47.2559 60.6648C47.2559 59.7805 47.5609 59.0555 48.1839 58.4321ZM48.6479 60.6648C48.6479 61.2006 48.8209 61.6368 49.1559 61.9834C49.5049 62.316 49.9249 62.4913 50.4309 62.4913C50.9369 62.4913 51.3589 62.318 51.6919 61.9834C52.0399 61.6348 52.2129 61.2006 52.2129 60.6648C52.2129 60.1291 52.0399 59.6929 51.6919 59.3602C51.3589 59.0117 50.9389 58.8384 50.4309 58.8384C49.9229 58.8384 49.5029 59.0117 49.1559 59.3602C48.8229 59.6929 48.6479 60.1291 48.6479 60.6648Z" fill="#FCFDFC"/>
<path d="M23.3849 63.2601V67.2456H21.9929V60.7086C21.9929 59.7088 22.3119 58.926 22.9349 58.3603C23.5589 57.7947 24.3109 57.5059 25.1679 57.5059C26.0239 57.5059 26.8049 57.8106 27.4149 58.434C28.0379 59.0574 28.3429 59.7964 28.3429 60.6668C28.3429 61.5372 28.0379 62.2901 27.4309 62.9135C26.8209 63.523 26.0679 63.8277 25.1699 63.8277C24.4889 63.8277 23.8349 63.5967 23.5899 63.4075L23.3869 63.2621L23.3849 63.2601ZM25.1679 58.8384C24.1379 58.8384 23.3849 59.6211 23.3849 60.6648C23.3849 61.7085 24.1679 62.4913 25.1679 62.4913C26.1679 62.4913 26.9499 61.7224 26.9499 60.6648C26.9499 59.6072 26.1679 58.8384 25.1679 58.8384Z" fill="#FCFDFC"/>
<path d="M30.6279 63.7507H29.2519V60.0679C29.2519 58.2853 30.2519 57.5742 31.5999 57.5742H31.9329V58.9087H31.6719C30.9769 58.9087 30.6279 59.3011 30.6279 60.0679V63.7507Z" fill="#FCFDFC"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -1,33 +0,0 @@
<svg width="130" height="133" viewBox="0 0 130 133" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="125" height="125" rx="62.5" fill="#E6FF03"/>
<rect x="2.5" y="2.5" width="125" height="125" rx="62.5" stroke="#101E1C" stroke-width="5"/>
<g filter="url(#filter0_d_920_4676)">
<rect x="5" y="5" width="120" height="120" rx="60" fill="#101E1C"/>
<rect x="6" y="6" width="118" height="118" rx="59" stroke="#E6FF03" stroke-width="2"/>
<path d="M46.6386 60.6641C46.5342 60.3011 46.3876 59.9805 46.1986 59.7021C46.0097 59.4187 45.7785 59.18 45.5051 58.9862C45.2366 58.7873 44.9284 58.6357 44.5804 58.5312C44.2373 58.4268 43.857 58.3746 43.4394 58.3746C42.6589 58.3746 41.9728 58.5685 41.3812 58.9563C40.7945 59.3441 40.3371 59.9084 40.009 60.6491C39.6809 61.3849 39.5168 62.2848 39.5168 63.3487C39.5168 64.4126 39.6784 65.3175 40.0016 66.0632C40.3247 66.8089 40.7821 67.3782 41.3737 67.771C41.9653 68.1587 42.6638 68.3526 43.4692 68.3526C44.2001 68.3526 44.824 68.2234 45.341 67.9648C45.8631 67.7013 46.2608 67.331 46.5342 66.8537C46.8126 66.3764 46.9518 65.8121 46.9518 65.1609L47.6081 65.2578H43.6706V62.8267H50.0616V64.7507C50.0616 66.093 49.7782 67.2464 49.2114 68.2109C48.6447 69.1705 47.8641 69.9112 46.8698 70.4332C45.8755 70.9503 44.737 71.2088 43.4543 71.2088C42.0225 71.2088 40.7647 70.8931 39.6809 70.2617C38.5971 69.6254 37.7519 68.723 37.1454 67.5547C36.5438 66.3814 36.243 64.9893 36.243 63.3786C36.243 62.1406 36.422 61.0369 36.78 60.0675C37.1429 59.093 37.65 58.2678 38.3013 57.5916C38.9525 56.9155 39.7107 56.4009 40.5758 56.0479C41.4408 55.695 42.378 55.5185 43.3872 55.5185C44.2523 55.5185 45.0577 55.6452 45.8034 55.8988C46.5491 56.1474 47.2104 56.5004 47.7871 56.9577C48.3687 57.4151 48.8435 57.9595 49.2114 58.5909C49.5793 59.2173 49.8155 59.9084 49.9199 60.6641H46.6386ZM57.9011 71H52.4871V55.7273H57.9459C59.4821 55.7273 60.8046 56.033 61.9132 56.6445C63.0219 57.2511 63.8745 58.1236 64.4711 59.2621C65.0727 60.4006 65.3734 61.7628 65.3734 63.3487C65.3734 64.9396 65.0727 66.3068 64.4711 67.4503C63.8745 68.5937 63.0169 69.4712 61.8983 70.0827C60.7847 70.6942 59.4523 71 57.9011 71ZM55.7161 68.2333H57.7669C58.7215 68.2333 59.5244 68.0643 60.1756 67.7262C60.8319 67.3832 61.3241 66.8537 61.6522 66.1378C61.9853 65.4169 62.1519 64.4872 62.1519 63.3487C62.1519 62.2202 61.9853 61.2979 61.6522 60.582C61.3241 59.8661 60.8344 59.3391 60.1831 59.0011C59.5318 58.663 58.7289 58.494 57.7744 58.494H55.7161V68.2333ZM67.7654 71V55.7273H73.791C74.9494 55.7273 75.9362 55.9485 76.7516 56.391C77.5669 56.8285 78.1883 57.4375 78.6159 58.218C79.0484 58.9936 79.2647 59.8885 79.2647 60.9027C79.2647 61.9169 79.0459 62.8118 78.6084 63.5874C78.1709 64.3629 77.5371 64.967 76.7068 65.3995C75.8815 65.832 74.8822 66.0483 73.7089 66.0483H69.8684V63.4606H73.1869C73.8084 63.4606 74.3204 63.3537 74.7231 63.1399C75.1308 62.9212 75.4341 62.6204 75.6329 62.2376C75.8368 61.8498 75.9387 61.4048 75.9387 60.9027C75.9387 60.3956 75.8368 59.9531 75.6329 59.5753C75.4341 59.1925 75.1308 58.8967 74.7231 58.6879C74.3155 58.4741 73.7984 58.3672 73.172 58.3672H70.9945V71H67.7654ZM81.3621 71V55.7273H87.3877C88.5411 55.7273 89.5254 55.9336 90.3408 56.3462C91.1611 56.7539 91.785 57.3331 92.2126 58.0838C92.6451 58.8295 92.8614 59.707 92.8614 60.7163C92.8614 61.7305 92.6426 62.603 92.2051 63.3338C91.7676 64.0597 91.1337 64.6165 90.3035 65.0043C89.4782 65.392 88.4789 65.5859 87.3056 65.5859H83.2712V62.9908H86.7836C87.4001 62.9908 87.9122 62.9062 88.3198 62.7372C88.7275 62.5682 89.0308 62.3146 89.2296 61.9766C89.4335 61.6385 89.5354 61.2184 89.5354 60.7163C89.5354 60.2092 89.4335 59.7816 89.2296 59.4336C89.0308 59.0856 88.725 58.8221 88.3124 58.6431C87.9047 58.4592 87.3901 58.3672 86.7687 58.3672H84.5911V71H81.3621ZM89.61 64.0497L93.4058 71H89.8411L86.1274 64.0497H89.61Z" fill="#F1EFED"/>
<path opacity="0.15" d="M64.5 9L66.1839 14.1824H71.6329L67.2245 17.3853L68.9084 22.5676L64.5 19.3647L60.0916 22.5676L61.7755 17.3853L57.3671 14.1824H62.8161L64.5 9Z" fill="#E6FF03"/>
<path opacity="0.15" d="M88.5 15L90.1839 20.1824H95.6329L91.2245 23.3853L92.9084 28.5676L88.5 25.3647L84.0916 28.5676L85.7755 23.3853L81.3671 20.1824H86.8161L88.5 15Z" fill="#E6FF03"/>
<path opacity="0.15" d="M105.5 33L107.184 38.1824H112.633L108.225 41.3853L109.908 46.5676L105.5 43.3647L101.092 46.5676L102.775 41.3853L98.3671 38.1824H103.816L105.5 33Z" fill="#E6FF03"/>
<path opacity="0.15" d="M112.5 57L114.184 62.1824H119.633L115.225 65.3853L116.908 70.5676L112.5 67.3647L108.092 70.5676L109.775 65.3853L105.367 62.1824H110.816L112.5 57Z" fill="#E6FF03"/>
<path opacity="0.15" d="M105.5 81L107.184 86.1824H112.633L108.225 89.3853L109.908 94.5676L105.5 91.3647L101.092 94.5676L102.775 89.3853L98.3671 86.1824H103.816L105.5 81Z" fill="#E6FF03"/>
<path opacity="0.15" d="M88.5 98L90.1839 103.182H95.6329L91.2245 106.385L92.9084 111.568L88.5 108.365L84.0916 111.568L85.7755 106.385L81.3671 103.182H86.8161L88.5 98Z" fill="#E6FF03"/>
<path opacity="0.15" d="M64.5 104L66.1839 109.182H71.6329L67.2245 112.385L68.9084 117.568L64.5 114.365L60.0916 117.568L61.7755 112.385L57.3671 109.182H62.8161L64.5 104Z" fill="#E6FF03"/>
<path opacity="0.15" d="M40.5 98L42.1839 103.182H47.6329L43.2245 106.385L44.9084 111.568L40.5 108.365L36.0916 111.568L37.7755 106.385L33.3671 103.182H38.8161L40.5 98Z" fill="#E6FF03"/>
<path opacity="0.15" d="M22.5 80L24.1839 85.1824H29.6329L25.2245 88.3853L26.9084 93.5676L22.5 90.3647L18.0916 93.5676L19.7755 88.3853L15.3671 85.1824H20.8161L22.5 80Z" fill="#E6FF03"/>
<path opacity="0.15" d="M16.5 56L18.1839 61.1824H23.6329L19.2245 64.3853L20.9084 69.5676L16.5 66.3647L12.0916 69.5676L13.7755 64.3853L9.36708 61.1824H14.8161L16.5 56Z" fill="#E6FF03"/>
<path opacity="0.15" d="M22.5 33L24.1839 38.1824H29.6329L25.2245 41.3853L26.9084 46.5676L22.5 43.3647L18.0916 46.5676L19.7755 41.3853L15.3671 38.1824H20.8161L22.5 33Z" fill="#E6FF03"/>
<path opacity="0.15" d="M40.5 15L42.1839 20.1824H47.6329L43.2245 23.3853L44.9084 28.5676L40.5 25.3647L36.0916 28.5676L37.7755 23.3853L33.3671 20.1824H38.8161L40.5 15Z" fill="#E6FF03"/>
</g>
<defs>
<filter id="filter0_d_920_4676" x="1" y="5" width="128" height="128" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.117647 0 0 0 0 0.109804 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_920_4676"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_920_4676" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -6,6 +6,7 @@ import {
Button,
Dialog,
DialogContent,
FrameworkLogo,
IconArrowInbox,
IconLock,
IconMedal,
@@ -15,7 +16,7 @@ import {
import { useTranslate } from "@probo/i18n";
import type { AuditRowDownloadMutation } from "./__generated__/AuditRowDownloadMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
import { downloadFile, getLogoUrl } from "@probo/helpers";
import { downloadFile } from "@probo/helpers";
import { type PropsWithChildren, useState } from "react";
import { useLocation } from "react-router";
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
@@ -39,6 +40,8 @@ const auditRowFragment = graphql`
framework {
id
name
lightLogoURL
darkLogoURL
}
}
`;
@@ -106,41 +109,19 @@ export function AuditRow(props: { audit: AuditRowFragment$key }) {
export function AuditRowAvatar(props: { audit: AuditRowFragment$key }) {
const audit = useFragment(auditRowFragment, props.audit);
const logos = {
"ISO 27001 (2022)": getLogoUrl("iso27001.svg"),
"SOC 2": getLogoUrl("soc2.svg"),
HIPAA: getLogoUrl("hipaa.svg"),
GDPR: getLogoUrl("gdpr.svg"),
};
return (
<>
<AuditDialog
audit={props.audit}
logo={logos[audit.framework.name as keyof typeof logos]}
>
<AuditDialog audit={props.audit}>
<button
className="block cursor-pointer aspect-square"
title={`Logo ${audit.framework.name}`}
>
{audit.framework.name in logos ? (
<img
src={logos[audit.framework.name as keyof typeof logos]}
alt={`${audit.framework.name} Logo`}
/>
) : (
<div
className="bg-[#F0F7E2] aspect-square w-full rounded-full text-xs text-[#000] font-bold flex items-center justify-center pb-6 px-2"
style={{
background: `url(${getLogoUrl("blank.svg")}) no-repeat`,
}}
>
<span className="line-clamp-2 overflow-hidden">
{" "}
{audit.framework.name}
</span>
</div>
)}
<FrameworkLogo
className="size-19"
lightLogoURL={audit.framework.lightLogoURL}
darkLogoURL={audit.framework.darkLogoURL}
name={audit.framework.name}
/>
</button>
</AuditDialog>
</>
@@ -170,13 +151,12 @@ function AuditDialog(
title={<Breadcrumb items={items} />}
>
<DialogContent className="p-4 lg:p-8 space-y-6">
{props.logo && (
<img
alt={audit.framework.name}
src={props.logo}
className="size-24 block mx-auto"
/>
)}
<FrameworkLogo
className="size-24 mx-auto"
lightLogoURL={audit.framework.lightLogoURL}
darkLogoURL={audit.framework.darkLogoURL}
name={audit.framework.name}
/>
<h2 className="text-xl font-semibold mb-1">{audit.framework.name}</h2>
<Table>
<AuditRow audit={props.audit} />

View File

@@ -90,7 +90,7 @@ export function OrganizationSidebar({
</div>
</div>
<hr className="my-6 -mx-6 h-[1px] bg-border-low border-none" />
<hr className="my-6 -mx-6 h-px bg-border-low border-none" />
</>
)}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<2f595b4efd5e7b13207d433830a64c98>>
* @generated SignedSource<<3265a0991323eee4011c668b68d1b365>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -12,7 +12,9 @@ import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type AuditRowFragment$data = {
readonly framework: {
readonly darkLogoURL: string | null | undefined;
readonly id: string;
readonly lightLogoURL: string | null | undefined;
readonly name: string;
};
readonly report: {
@@ -90,6 +92,20 @@ return {
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
@@ -100,6 +116,6 @@ return {
};
})();
(node as any).hash = "3aabed006b65dc913c81a63f0f6f4523";
(node as any).hash = "417e65b500df4b4cff874dab2bea90ee";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<fe9ca4e5a2ddb60ea7a7f353d431d18c>>
* @generated SignedSource<<ec32dbbc5f596dc323b1b4ff8df21fef>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -533,7 +533,21 @@ return {
"plural": false,
"selections": [
(v0/*: any*/),
(v6/*: any*/)
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
}
@@ -552,12 +566,12 @@ return {
]
},
"params": {
"cacheID": "da8080ccbc08adb03fbf360f1e472f84",
"cacheID": "f098177c07810cbe7609821d63c22b0b",
"id": null,
"metadata": {},
"name": "TrustGraphCurrentQuery",
"operationKind": "query",
"text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n isUserAuthorized\n hasUserRequestedAccess\n }\n framework {\n id\n name\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
"text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n isUserAuthorized\n hasUserRequestedAccess\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<8b9ec6ee6f688ae6f6d1463104c72df4>>
* @generated SignedSource<<6458ae0015d6100916a1d446c608f61c>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -549,7 +549,21 @@ return {
"plural": false,
"selections": [
(v2/*: any*/),
(v8/*: any*/)
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lightLogoURL",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "darkLogoURL",
"storageKey": null
}
],
"storageKey": null
}
@@ -568,12 +582,12 @@ return {
]
},
"params": {
"cacheID": "f3ac933cf00a798d12b1a8e1fedcaee9",
"cacheID": "7f7d3a2db2deb10550db16704993a2d0",
"id": null,
"metadata": {},
"name": "TrustGraphQuery",
"operationKind": "query",
"text": "query TrustGraphQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n isUserAuthorized\n hasUserRequestedAccess\n }\n framework {\n id\n name\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
"text": "query TrustGraphQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewPageFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n isUserAuthorized\n hasUserRequestedAccess\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n hasUserRequestedAccess\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
}
};
})();

View File

@@ -1,20 +0,0 @@
export const availableFrameworks = [
{
id: "ISO27001-2022",
name: "ISO 27001 (2022)",
logo: "/logos/iso27001.png",
description: "Information security management systems",
},
{
id: "SOC2",
name: "SOC 2",
logo: "/logos/soc2.png",
description: "System and Organization Controls 2",
},
{
id: "HIPAA",
name: "HIPAA",
logo: "/logos/hipaa.png",
description: "Health Insurance Portability and Accountability Act",
},
];

View File

@@ -24,7 +24,6 @@ export {
countries,
type CountryCode,
} from "./countries";
export { availableFrameworks } from "./frameworks";
export {
getDocumentTypeLabel,
documentTypes,
@@ -63,7 +62,7 @@ export {
export { promisifyMutation } from "./relay";
export { fileType, fileSize } from "./file";
export { formatDatetime, formatDate } from "./date";
export { getLogoUrl, getTrustCenterUrl } from "./trustCenter";
export { getTrustCenterUrl } from "./trustCenter";
export { formatError, type GraphQLError } from "./error";
export { Role, getAssignableRoles } from "./roles";
export {

View File

@@ -1,15 +1,3 @@
export function getLogoUrl(logoPath: string): string {
const path = window.location.pathname;
const trustMatch = path.match(/^\/trust\/([^/]+)/);
if (!trustMatch) {
return `/logos/${logoPath}`;
}
const slugOrId = trustMatch[1];
return `/trust/${slugOrId}/logos/${logoPath}`;
}
export function getTrustCenterUrl(path: string): string {
const currentPath = window.location.pathname;
const trustMatch = currentPath.match(/^\/trust\/([^/]+)/);

View File

@@ -0,0 +1,34 @@
import clsx from "clsx";
import { Avatar } from "../Avatar/Avatar";
interface FrameworkLogoProps {
className?: string;
darkLogoURL?: string | null;
lightLogoURL?: string | null;
name: string;
}
export function FrameworkLogo(props: FrameworkLogoProps) {
const { className, lightLogoURL, darkLogoURL, name } = props;
return lightLogoURL || darkLogoURL ? (
<>
{lightLogoURL && (
<img
src={lightLogoURL}
className={clsx("size-12 block dark:hidden", className)}
alt={name}
/>
)}
{darkLogoURL && (
<img
src={darkLogoURL}
className={clsx("size-12 hidden dark:block", className)}
alt={name}
/>
)}
</>
) : (
<Avatar name={name} size="l" className={clsx("size-12", className)} />
);
}

View File

@@ -1,6 +1,9 @@
export function CASA() {
export function CASA(props: { className?: string }) {
const { className } = props;
return (
<svg
className={className}
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,6 +1,9 @@
export function HIPAA() {
export function HIPAA(props: { className?: string }) {
const { className } = props;
return (
<svg
className={className}
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,6 +1,9 @@
export function ISO27001() {
export function ISO27001(props: { className?: string }) {
const { className } = props;
return (
<svg
className={className}
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,6 +1,9 @@
export function SOC2() {
export function SOC2(props: { className?: string }) {
const { className } = props;
return (
<svg
className={className}
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,17 +0,0 @@
import { FrameworkLogo } from "./FrameworkLogo";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Molecules/Badges/FrameworkLogo",
component: FrameworkLogo,
argTypes: {},
} satisfies Meta<typeof FrameworkLogo>;
type Story = StoryObj<typeof FrameworkLogo>;
export const Default: Story = {
args: {
name: "SOC 2",
className: "size-12",
},
};

View File

@@ -1,23 +0,0 @@
import { availableFrameworks } from "@probo/helpers";
import { Avatar } from "../../Atoms/Avatar/Avatar.tsx";
const availableLogos = new Map(
availableFrameworks.map((framework) => [framework.name, framework.logo]),
);
export function FrameworkLogo({
name,
alt,
className,
}: {
name: string;
alt?: string;
className?: string;
}) {
const logo = availableLogos.get(name);
return logo ? (
<img src={logo} alt={alt} className={className} />
) : (
<Avatar name={name} size="l" className={className} />
);
}

View File

@@ -1,8 +1,31 @@
import { Dropdown, DropdownItem } from "../../Atoms/Dropdown/Dropdown";
import { IconChevronDown, IconPlusLarge } from "../../Atoms/Icons";
import { Button } from "../../Atoms/Button/Button";
import { availableFrameworks } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { ISO27001 } from "../../Atoms/Frameworks/ISO27001";
import { SOC2 } from "../../Atoms/Frameworks/SOC2";
import { HIPAA } from "../../Atoms/Frameworks/HIPAA";
const availableFrameworks = [
{
id: "ISO27001-2022",
name: "ISO 27001 (2022)",
logo: <ISO27001 className="size-8" />,
description: "Information security management systems",
},
{
id: "SOC2",
name: "SOC 2",
logo: <SOC2 className="size-8" />,
description: "System and Organization Controls 2",
},
{
id: "HIPAA",
name: "HIPAA",
logo: <HIPAA className="size-8" />,
description: "Health Insurance Portability and Accountability Act",
},
];
type Framework = (typeof availableFrameworks)[number];
@@ -58,11 +81,7 @@ function FrameworkItem(props: { framework?: Framework; onClick: () => void }) {
}
return (
<DropdownItem onClick={props.onClick} className="">
<img
src={props.framework.logo}
alt={props.framework.name}
className="size-8"
/>
{props.framework.logo}
<div className="space-y-[2px]">
<div className="text-sm font-medium">
{props.framework.name}

View File

@@ -10,6 +10,7 @@ export {
// Atoms
export * from "./Atoms/Icons";
export { Logo } from "./Atoms/Logo/Logo";
export { FrameworkLogo } from "./Atoms/FrameworkLogo/FrameworkLogo";
export { SidebarItem } from "./Atoms/Sidebar/SidebarItem";
export { Button } from "./Atoms/Button/Button";
export { Card } from "./Atoms/Card/Card";
@@ -77,7 +78,6 @@ export { DocumentClassificationBadge } from "./Molecules/Badge/DocumentClassific
export { SentitivityOptions } from "./Molecules/Select/SentitivityOptions";
export { ImpactOptions } from "./Molecules/Select/ImpactOptions";
export { DurationPicker } from "./Molecules/DurationPicker/DurationPicker";
export { FrameworkLogo } from "./Molecules/Badge/FrameworkLogo";
export { EditableCell } from "./Molecules/Table/EditableCell";
export { TextCell } from "./Molecules/Table/TextCell";
export {