Update certifications tab UI
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
3
apps/console/public/assets/cross-icon.svg
Normal file
3
apps/console/public/assets/cross-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.239641 0.239641C0.559163 -0.0798804 1.0772 -0.0798804 1.39672 0.239641L4.5 3.34292L7.60326 0.239641C7.92278 -0.0798804 8.44086 -0.0798804 8.76038 0.239641C9.07985 0.559163 9.07985 1.0772 8.76038 1.39672L5.65707 4.5L8.76038 7.60326C9.07985 7.92278 9.07985 8.44086 8.76038 8.76038C8.44086 9.07985 7.92278 9.07985 7.60326 8.76038L4.5 5.65707L1.39672 8.76038C1.0772 9.07985 0.559163 9.07985 0.239641 8.76038C-0.0798804 8.44086 -0.0798804 7.92278 0.239641 7.60326L3.34292 4.5L0.239641 1.39672C-0.0798804 1.0772 -0.0798804 0.559163 0.239641 0.239641Z" fill="#6B716A"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 714 B |
@@ -311,7 +311,6 @@ function ListVendorContent({
|
|||||||
return (
|
return (
|
||||||
<PageTemplate
|
<PageTemplate
|
||||||
title="Vendors"
|
title="Vendors"
|
||||||
description="Vendors are third-party services that your company uses. Add them to keep track of their risk and compliance status."
|
|
||||||
actions={
|
actions={
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
|
|||||||
@@ -453,6 +453,188 @@ function TagList({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CertificationsView({
|
||||||
|
certifications,
|
||||||
|
onAdd,
|
||||||
|
onRemove,
|
||||||
|
}: {
|
||||||
|
certifications: readonly string[];
|
||||||
|
onAdd: (tag: string) => void;
|
||||||
|
onRemove: (tag: string) => void;
|
||||||
|
}) {
|
||||||
|
// Common security standards
|
||||||
|
const securityStandards = ["SOC 2", "ISO 27001", "HITRUST", "NIST"];
|
||||||
|
|
||||||
|
// Regulatory and legal certifications
|
||||||
|
const regulatoryLegal = ["HIPAA", "FERPA", "FISMA", "PIPEDA"];
|
||||||
|
|
||||||
|
// Industry-specific certifications
|
||||||
|
const industrySpecific = ["FinTech", "MPAA", "GSMA"];
|
||||||
|
|
||||||
|
// International & government certifications
|
||||||
|
const internationalGov = ["FedRAMP", "ENS High", "IRAP", "CJIS"];
|
||||||
|
|
||||||
|
// Check if certification is in vendor's list
|
||||||
|
const isCertificationActive = (cert: string) => {
|
||||||
|
return certifications.includes(cert);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border border-[#ECEFEC] bg-white overflow-hidden">
|
||||||
|
<div className="flex items-center justify-between px-5 py-3 border-b border-[rgba(2,42,2,0.08)]">
|
||||||
|
<h3 className="text-base font-medium text-[#141E12]">Certifications</h3>
|
||||||
|
<Button
|
||||||
|
onClick={() => {}} // Will open modal to add custom certifications
|
||||||
|
className="rounded-full bg-[rgba(0,39,0,0.05)] text-[#141E12] hover:bg-[rgba(0,39,0,0.08)] h-8 px-3"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="mr-1.5">
|
||||||
|
<path d="M8 3.5V12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M3.5 8H12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Add certification
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-5 space-y-6">
|
||||||
|
{/* Security Standards */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-sm font-medium text-[#141E12]">Security Standards</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{securityStandards.map(cert => (
|
||||||
|
<button
|
||||||
|
key={cert}
|
||||||
|
onClick={() => isCertificationActive(cert) ? onRemove(cert) : onAdd(cert)}
|
||||||
|
className={`flex items-center py-1.5 px-2 rounded text-sm font-medium ${
|
||||||
|
isCertificationActive(cert)
|
||||||
|
? "bg-[rgba(5,77,5,0.03)] text-[#6B716A]"
|
||||||
|
: "bg-[rgba(0,39,0,0.05)] text-[#141E12]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cert}
|
||||||
|
{isCertificationActive(cert) && (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="ml-1">
|
||||||
|
<path d="M12 4L4 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M4 4L12 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regulatory & Legal */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-sm font-medium text-[#141E12]">Regulatory & Legal</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{regulatoryLegal.map(cert => (
|
||||||
|
<button
|
||||||
|
key={cert}
|
||||||
|
onClick={() => isCertificationActive(cert) ? onRemove(cert) : onAdd(cert)}
|
||||||
|
className={`flex items-center py-1.5 px-2 rounded text-sm font-medium ${
|
||||||
|
isCertificationActive(cert)
|
||||||
|
? "bg-[rgba(5,77,5,0.03)] text-[#6B716A]"
|
||||||
|
: "bg-[rgba(0,39,0,0.05)] text-[#141E12]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cert}
|
||||||
|
{isCertificationActive(cert) && (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="ml-1">
|
||||||
|
<path d="M12 4L4 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M4 4L12 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Industry-Specific */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-sm font-medium text-[#141E12]">Industry-Specific</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{industrySpecific.map(cert => (
|
||||||
|
<button
|
||||||
|
key={cert}
|
||||||
|
onClick={() => isCertificationActive(cert) ? onRemove(cert) : onAdd(cert)}
|
||||||
|
className={`flex items-center py-1.5 px-2 rounded text-sm font-medium ${
|
||||||
|
isCertificationActive(cert)
|
||||||
|
? "bg-[rgba(5,77,5,0.03)] text-[#6B716A]"
|
||||||
|
: "bg-[rgba(0,39,0,0.05)] text-[#141E12]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cert}
|
||||||
|
{isCertificationActive(cert) && (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="ml-1">
|
||||||
|
<path d="M12 4L4 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M4 4L12 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* International & Government */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-sm font-medium text-[#141E12]">International & Government</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{internationalGov.map(cert => (
|
||||||
|
<button
|
||||||
|
key={cert}
|
||||||
|
onClick={() => isCertificationActive(cert) ? onRemove(cert) : onAdd(cert)}
|
||||||
|
className={`flex items-center py-1.5 px-2 rounded text-sm font-medium ${
|
||||||
|
isCertificationActive(cert)
|
||||||
|
? "bg-[rgba(5,77,5,0.03)] text-[#6B716A]"
|
||||||
|
: "bg-[rgba(0,39,0,0.05)] text-[#141E12]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cert}
|
||||||
|
{isCertificationActive(cert) && (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="ml-1">
|
||||||
|
<path d="M12 4L4 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M4 4L12 12" stroke="#6B716A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Custom certifications */}
|
||||||
|
{certifications.length > 0 && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-sm font-medium text-[#141E12]">Custom Certifications</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{certifications
|
||||||
|
.filter(cert =>
|
||||||
|
![...securityStandards, ...regulatoryLegal, ...industrySpecific, ...internationalGov].includes(cert)
|
||||||
|
)
|
||||||
|
.map(cert => (
|
||||||
|
<div
|
||||||
|
key={cert}
|
||||||
|
className="flex items-center gap-1 rounded bg-[rgba(5,77,5,0.03)] px-2 py-1.5 text-sm font-medium text-[#6B716A]"
|
||||||
|
>
|
||||||
|
<span>{cert}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => onRemove(cert)}
|
||||||
|
className="text-[#6B716A] hover:text-[#141E12]"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 4L4 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M4 4L12 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface RiskAssessment {
|
interface RiskAssessment {
|
||||||
id: string;
|
id: string;
|
||||||
assessedAt: string;
|
assessedAt: string;
|
||||||
@@ -1193,33 +1375,21 @@ function VendorViewContent({
|
|||||||
);
|
);
|
||||||
case 'certifications':
|
case 'certifications':
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-border bg-white p-0 overflow-hidden">
|
<CertificationsView
|
||||||
<div className="px-5 py-4 border-b border-[rgba(2,42,2,0.08)]">
|
certifications={[...formData.certifications]}
|
||||||
<h3 className="text-base font-medium">Certifications</h3>
|
onAdd={(cert) =>
|
||||||
</div>
|
handleFieldChange("certifications", [
|
||||||
<div className="p-5 space-y-4">
|
...formData.certifications.filter(c => c !== cert),
|
||||||
<div className="space-y-1">
|
cert,
|
||||||
<p className="text-sm font-medium text-[#6B716A]">
|
])
|
||||||
List of certifications held by the vendor
|
}
|
||||||
</p>
|
onRemove={(cert) =>
|
||||||
<TagList
|
handleFieldChange(
|
||||||
tags={[...formData.certifications]}
|
"certifications",
|
||||||
onAdd={(tag) =>
|
formData.certifications.filter((c) => c !== cert)
|
||||||
handleFieldChange("certifications", [
|
)
|
||||||
...formData.certifications,
|
}
|
||||||
tag,
|
/>
|
||||||
])
|
|
||||||
}
|
|
||||||
onRemove={(tag) =>
|
|
||||||
handleFieldChange(
|
|
||||||
"certifications",
|
|
||||||
formData.certifications.filter((t) => t !== tag)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case 'complianceReports':
|
case 'complianceReports':
|
||||||
return (
|
return (
|
||||||
@@ -1238,7 +1408,7 @@ function VendorViewContent({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<RiskAssessmentsTable
|
<RiskAssessmentsTable
|
||||||
assessments={
|
assessments={
|
||||||
data.node.riskAssessments?.edges.map((edge) => edge.node) ?? []
|
(data.node.riskAssessments?.edges.map(edge => edge.node) || []) as RiskAssessment[]
|
||||||
}
|
}
|
||||||
onCreateAssessment={handleCreateRiskAssessment}
|
onCreateAssessment={handleCreateRiskAssessment}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user