diff --git a/apps/api/src/trust-portal/trust-access.service.ts b/apps/api/src/trust-portal/trust-access.service.ts
index d2c3df5031..407014849c 100644
--- a/apps/api/src/trust-portal/trust-access.service.ts
+++ b/apps/api/src/trust-portal/trust-access.service.ts
@@ -1956,6 +1956,8 @@ export class TrustAccessService {
| 'pci_dss'
| 'nen7510'
| 'iso9001'
+ | 'pipeda'
+ | 'ccpa'
> = {
[TrustFramework.iso_27001]: 'iso27001',
[TrustFramework.iso_42001]: 'iso42001',
@@ -1967,6 +1969,8 @@ export class TrustAccessService {
[TrustFramework.pci_dss]: 'pci_dss',
[TrustFramework.nen_7510]: 'nen7510',
[TrustFramework.iso_9001]: 'iso9001',
+ [TrustFramework.pipeda]: 'pipeda',
+ [TrustFramework.ccpa]: 'ccpa',
};
const enabledField = frameworkFieldMap[framework];
@@ -2741,6 +2745,8 @@ export class TrustAccessService {
'nen 7510': 'nen7510',
iso9001: 'iso9001',
'iso 9001': 'iso9001',
+ pipeda: 'pipeda',
+ ccpa: 'ccpa',
};
const badges: Array<{ type: string; verified: boolean }> = [];
@@ -2789,6 +2795,7 @@ export class TrustAccessService {
hipaa: 'HIPAA',
pci_dss: 'PCI DSS',
nen7510: 'NEN 7510',
+ pipeda: 'PIPEDA',
ccpa: 'CCPA',
};
diff --git a/apps/api/src/trust-portal/trust-portal.service.ts b/apps/api/src/trust-portal/trust-portal.service.ts
index 4d5e0d4fde..f15e1a0dca 100644
--- a/apps/api/src/trust-portal/trust-portal.service.ts
+++ b/apps/api/src/trust-portal/trust-portal.service.ts
@@ -128,7 +128,9 @@ export class TrustPortalService {
| 'soc2type2_status'
| 'pci_dss_status'
| 'nen7510_status'
- | 'iso9001_status';
+ | 'iso9001_status'
+ | 'pipeda_status'
+ | 'ccpa_status';
enabledField:
| 'iso27001'
| 'iso42001'
@@ -139,7 +141,9 @@ export class TrustPortalService {
| 'soc2type2'
| 'pci_dss'
| 'nen7510'
- | 'iso9001';
+ | 'iso9001'
+ | 'pipeda'
+ | 'ccpa';
slug: string;
}
> = {
@@ -193,6 +197,16 @@ export class TrustPortalService {
enabledField: 'soc3',
slug: 'soc3',
},
+ [TrustFramework.pipeda]: {
+ statusField: 'pipeda_status',
+ enabledField: 'pipeda',
+ slug: 'pipeda',
+ },
+ [TrustFramework.ccpa]: {
+ statusField: 'ccpa_status',
+ enabledField: 'ccpa',
+ slug: 'ccpa',
+ },
};
async getDomainStatus(
@@ -628,6 +642,8 @@ export class TrustPortalService {
pci_dss: 'pci_dss',
nen7510: 'nen7510',
iso9001: 'iso9001',
+ pipeda: 'pipeda',
+ ccpa: 'ccpa',
};
// Map framework status fields (frontend sends camelCase like "iso27001Status", DB uses "iso27001_status")
@@ -642,6 +658,8 @@ export class TrustPortalService {
pcidssStatus: 'pci_dss_status',
nen7510Status: 'nen7510_status',
iso9001Status: 'iso9001_status',
+ pipedaStatus: 'pipeda_status',
+ ccpaStatus: 'ccpa_status',
// Also support snake_case input (from other callers)
soc2type1_status: 'soc2type1_status',
soc2type2_status: 'soc2type2_status',
@@ -653,6 +671,8 @@ export class TrustPortalService {
pci_dss_status: 'pci_dss_status',
nen7510_status: 'nen7510_status',
iso9001_status: 'iso9001_status',
+ pipeda_status: 'pipeda_status',
+ ccpa_status: 'ccpa_status',
};
for (const [inputKey, dbField] of Object.entries(boolFieldMap)) {
@@ -1533,6 +1553,8 @@ export class TrustPortalService {
pcidss: trust.pci_dss ?? false,
nen7510: trust.nen7510 ?? false,
iso9001: trust.iso9001 ?? false,
+ pipeda: trust.pipeda ?? false,
+ ccpa: trust.ccpa ?? false,
// Framework statuses
soc2type1Status: trust.soc2type1_status ?? 'started',
soc2type2Status:
@@ -1547,6 +1569,8 @@ export class TrustPortalService {
pcidssStatus: trust.pci_dss_status ?? 'started',
nen7510Status: trust.nen7510_status ?? 'started',
iso9001Status: trust.iso9001_status ?? 'started',
+ pipedaStatus: trust.pipeda_status ?? 'started',
+ ccpaStatus: trust.ccpa_status ?? 'started',
// Overview
overviewTitle: trust.overviewTitle ?? null,
overviewContent: trust.overviewContent ?? defaultOverviewContent,
diff --git a/apps/app/public/badges/ccpa.svg b/apps/app/public/badges/ccpa.svg
new file mode 100644
index 0000000000..c6971f6ae2
--- /dev/null
+++ b/apps/app/public/badges/ccpa.svg
@@ -0,0 +1,7 @@
+
diff --git a/apps/app/public/badges/pipeda.svg b/apps/app/public/badges/pipeda.svg
new file mode 100644
index 0000000000..e256eebf79
--- /dev/null
+++ b/apps/app/public/badges/pipeda.svg
@@ -0,0 +1,6 @@
+
diff --git a/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksTable.tsx b/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksTable.tsx
index b35763256a..10fe350177 100644
--- a/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksTable.tsx
+++ b/apps/app/src/app/(app)/[orgId]/frameworks/components/FrameworksTable.tsx
@@ -48,6 +48,8 @@ const FRAMEWORK_BADGES: Record = {
'NEN 7510': '/badges/nen7510.svg',
'ISO 9001': '/badges/iso9001.svg',
'SOC 2 Type 1': '/badges/soc2.svg',
+ 'CCPA': '/badges/ccpa.svg',
+ 'PIPEDA': '/badges/pipeda.svg',
};
function getFrameworkBadge(name: string): string | null {
diff --git a/apps/app/src/app/(app)/[orgId]/overview/components/FrameworksOverview.tsx b/apps/app/src/app/(app)/[orgId]/overview/components/FrameworksOverview.tsx
index ea6e1436eb..b716a6f111 100644
--- a/apps/app/src/app/(app)/[orgId]/overview/components/FrameworksOverview.tsx
+++ b/apps/app/src/app/(app)/[orgId]/overview/components/FrameworksOverview.tsx
@@ -62,10 +62,19 @@ export function mapFrameworkToBadge(framework: FrameworkInstanceWithControls) {
if (frameworkName === 'ISO 9001') {
return '/badges/iso9001.svg';
}
+
if (frameworkName === 'SOC 2 Type 1') {
return '/badges/soc2.svg';
}
+ if (frameworkName === 'CCPA') {
+ return '/badges/ccpa.svg';
+ }
+
+ if (frameworkName === 'PIPEDA') {
+ return '/badges/pipeda.svg';
+ }
+
return null;
}
diff --git a/apps/app/src/app/(app)/[orgId]/trust/page.tsx b/apps/app/src/app/(app)/[orgId]/trust/page.tsx
index 32b1f80c42..b980b263c3 100644
--- a/apps/app/src/app/(app)/[orgId]/trust/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/trust/page.tsx
@@ -47,6 +47,8 @@ export default async function TrustPage({
pci_dss: 'pcidssFileName',
nen_7510: 'nen7510FileName',
iso_9001: 'iso9001FileName',
+ pipeda: 'pipedaFileName',
+ ccpa: 'ccpaFileName',
};
const certificateFiles: Record = {
@@ -60,6 +62,8 @@ export default async function TrustPage({
pcidssFileName: null,
nen7510FileName: null,
iso9001FileName: null,
+ pipedaFileName: null,
+ ccpaFileName: null,
};
for (const resource of certificateResources) {
@@ -110,6 +114,8 @@ export default async function TrustPage({
pcidss={settings?.pcidss ?? false}
nen7510={settings?.nen7510 ?? false}
iso9001={settings?.iso9001 ?? false}
+ pipeda={settings?.pipeda ?? false}
+ ccpa={settings?.ccpa ?? false}
soc2type1Status={settings?.soc2type1Status ?? 'started'}
soc2type2Status={settings?.soc2type2Status ?? 'started'}
soc3Status={settings?.soc3Status ?? 'started'}
@@ -120,6 +126,8 @@ export default async function TrustPage({
pcidssStatus={settings?.pcidssStatus ?? 'started'}
nen7510Status={settings?.nen7510Status ?? 'started'}
iso9001Status={settings?.iso9001Status ?? 'started'}
+ pipedaStatus={settings?.pipedaStatus ?? 'started'}
+ ccpaStatus={settings?.ccpaStatus ?? 'started'}
faqs={settings?.faqs ?? null}
iso27001FileName={certificateFiles.iso27001FileName}
iso42001FileName={certificateFiles.iso42001FileName}
@@ -131,6 +139,8 @@ export default async function TrustPage({
pcidssFileName={certificateFiles.pcidssFileName}
nen7510FileName={certificateFiles.nen7510FileName}
iso9001FileName={certificateFiles.iso9001FileName}
+ pipedaFileName={certificateFiles.pipedaFileName}
+ ccpaFileName={certificateFiles.ccpaFileName}
additionalDocuments={documents.map((doc: any) => ({
id: doc.id,
name: doc.name,
diff --git a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/TrustPortalSwitch.tsx b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/TrustPortalSwitch.tsx
index 40cf35037c..f8abc03618 100644
--- a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/TrustPortalSwitch.tsx
+++ b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/TrustPortalSwitch.tsx
@@ -36,6 +36,8 @@ import {
GDPRInProgress,
HIPAA,
HIPAAInProgress,
+ CCPA,
+ CCPAInProgress,
ISO27001,
ISO27001InProgress,
ISO42001,
@@ -46,6 +48,8 @@ import {
NEN7510InProgress,
PCIDSS,
PCIDSSInProgress,
+ PIPEDA,
+ PIPEDAInProgress,
SOC2Type1,
SOC2Type1InProgress,
SOC2Type2,
@@ -78,6 +82,8 @@ const trustPortalSwitchSchema = z.object({
pcidss: z.boolean(),
nen7510: z.boolean(),
iso9001: z.boolean(),
+ pipeda: z.boolean(),
+ ccpa: z.boolean(),
soc2type1Status: z.enum(['started', 'in_progress', 'compliant']),
soc2type2Status: z.enum(['started', 'in_progress', 'compliant']),
soc3Status: z.enum(['started', 'in_progress', 'compliant']),
@@ -88,6 +94,8 @@ const trustPortalSwitchSchema = z.object({
pcidssStatus: z.enum(['started', 'in_progress', 'compliant']),
nen7510Status: z.enum(['started', 'in_progress', 'compliant']),
iso9001Status: z.enum(['started', 'in_progress', 'compliant']),
+ pipedaStatus: z.enum(['started', 'in_progress', 'compliant']),
+ ccpaStatus: z.enum(['started', 'in_progress', 'compliant']),
});
// Server action input schema (only fields that the server accepts)
@@ -108,6 +116,8 @@ const FRAMEWORK_KEY_TO_API_SLUG: Record = {
pcidss: 'pci_dss',
nen7510: 'nen_7510',
iso9001: 'iso_9001',
+ pipeda: 'pipeda',
+ ccpa: 'ccpa',
};
interface ComplianceResourceResponse {
@@ -139,7 +149,15 @@ type TrustCustomLink = {
};
type ComplianceBadge = {
- type: 'soc2' | 'iso27001' | 'iso42001' | 'gdpr' | 'hipaa' | 'pci_dss' | 'nen7510' | 'iso9001';
+ type:
+ | 'soc2'
+ | 'iso27001'
+ | 'iso42001'
+ | 'gdpr'
+ | 'hipaa'
+ | 'pci_dss'
+ | 'nen7510'
+ | 'iso9001';
verified: boolean;
};
@@ -181,6 +199,10 @@ export function TrustPortalSwitch({
nen7510Status,
iso9001,
iso9001Status,
+ pipeda,
+ pipedaStatus,
+ ccpa,
+ ccpaStatus,
faqs,
iso27001FileName,
iso42001FileName,
@@ -192,6 +214,8 @@ export function TrustPortalSwitch({
pcidssFileName,
nen7510FileName,
iso9001FileName,
+ pipedaFileName,
+ ccpaFileName,
additionalDocuments,
overview,
customLinks,
@@ -214,6 +238,8 @@ export function TrustPortalSwitch({
hipaa: boolean;
pcidss: boolean;
nen7510: boolean;
+ pipeda: boolean;
+ ccpa: boolean;
soc2type1Status: 'started' | 'in_progress' | 'compliant';
soc2type2Status: 'started' | 'in_progress' | 'compliant';
soc3Status: 'started' | 'in_progress' | 'compliant';
@@ -225,6 +251,8 @@ export function TrustPortalSwitch({
nen7510Status: 'started' | 'in_progress' | 'compliant';
iso9001: boolean;
iso9001Status: 'started' | 'in_progress' | 'compliant';
+ pipedaStatus: 'started' | 'in_progress' | 'compliant';
+ ccpaStatus: 'started' | 'in_progress' | 'compliant';
faqs: any[] | null;
iso27001FileName?: string | null;
iso42001FileName?: string | null;
@@ -236,6 +264,8 @@ export function TrustPortalSwitch({
pcidssFileName?: string | null;
nen7510FileName?: string | null;
iso9001FileName?: string | null;
+ pipedaFileName?: string | null;
+ ccpaFileName?: string | null;
additionalDocuments: TrustPortalDocument[];
overview: TrustOverviewData;
customLinks: TrustCustomLink[];
@@ -262,6 +292,8 @@ export function TrustPortalSwitch({
pcidss: pcidssFileName ?? null,
nen7510: nen7510FileName ?? null,
iso9001: iso9001FileName ?? null,
+ pipeda: pipedaFileName ?? null,
+ ccpa: ccpaFileName ?? null,
});
useEffect(() => {
@@ -276,6 +308,8 @@ export function TrustPortalSwitch({
pcidss: pcidssFileName ?? null,
nen7510: nen7510FileName ?? null,
iso9001: iso9001FileName ?? null,
+ pipeda: pipedaFileName ?? null,
+ ccpa: ccpaFileName ?? null,
});
}, [
iso27001FileName,
@@ -288,6 +322,8 @@ export function TrustPortalSwitch({
pcidssFileName,
nen7510FileName,
iso9001FileName,
+ pipedaFileName,
+ ccpaFileName,
]);
const convertFileToBase64 = async (file: File): Promise => {
@@ -352,6 +388,8 @@ export function TrustPortalSwitch({
pcidss: pcidss ?? false,
nen7510: nen7510 ?? false,
iso9001: iso9001 ?? false,
+ pipeda: pipeda ?? false,
+ ccpa: ccpa ?? false,
soc2type1Status: soc2type1Status ?? 'started',
soc2type2Status: soc2type2Status ?? 'started',
soc3Status: soc3Status ?? 'started',
@@ -362,6 +400,8 @@ export function TrustPortalSwitch({
pcidssStatus: pcidssStatus ?? 'started',
nen7510Status: nen7510Status ?? 'started',
iso9001Status: iso9001Status ?? 'started',
+ pipedaStatus: pipedaStatus ?? 'started',
+ ccpaStatus: ccpaStatus ?? 'started',
},
});
@@ -898,6 +938,84 @@ export function TrustPortalSwitch({
orgId={orgId}
disabled={!canUpdate}
/>
+ {/* PIPEDA */}
+ {
+ try {
+ await updateFrameworkSettings({
+ pipedaStatus: value as 'started' | 'in_progress' | 'compliant',
+ });
+ toast.success('PIPEDA status updated');
+ } catch (error) {
+ console.error('[trust framework update] failed', error);
+ toast.error('Failed to update PIPEDA status', {
+ description: error instanceof Error ? error.message : undefined,
+ });
+ }
+ }}
+ onToggle={async (checked) => {
+ try {
+ await updateFrameworkSettings({
+ pipeda: checked,
+ });
+ toast.success('PIPEDA status updated');
+ } catch (error) {
+ console.error('[trust framework update] failed', error);
+ toast.error('Failed to update PIPEDA status', {
+ description: error instanceof Error ? error.message : undefined,
+ });
+ }
+ }}
+ fileName={certificateFiles.pipeda}
+ onFileUpload={handleFileUpload}
+ onFilePreview={handleFilePreview}
+ frameworkKey="pipeda"
+ orgId={orgId}
+ disabled={!canUpdate}
+ />
+ {/* CCPA */}
+ {
+ try {
+ await updateFrameworkSettings({
+ ccpaStatus: value as 'started' | 'in_progress' | 'compliant',
+ });
+ toast.success('CCPA status updated');
+ } catch (error) {
+ console.error('[trust framework update] failed', error);
+ toast.error('Failed to update CCPA status', {
+ description: error instanceof Error ? error.message : undefined,
+ });
+ }
+ }}
+ onToggle={async (checked) => {
+ try {
+ await updateFrameworkSettings({
+ ccpa: checked,
+ });
+ toast.success('CCPA status updated');
+ } catch (error) {
+ console.error('[trust framework update] failed', error);
+ toast.error('Failed to update CCPA status', {
+ description: error instanceof Error ? error.message : undefined,
+ });
+ }
+ }}
+ fileName={certificateFiles.ccpa}
+ onFileUpload={handleFileUpload}
+ onFilePreview={handleFilePreview}
+ frameworkKey="ccpa"
+ orgId={orgId}
+ disabled={!canUpdate}
+ />
@@ -978,8 +1096,12 @@ function ComplianceFrameworkLogo({
LogoComponent = enabled && isInProgress ? SOC2Type1InProgress : SOC2Type1;
} else if (title === 'SOC 2 Type 2') {
LogoComponent = enabled && isInProgress ? SOC2Type2InProgress : SOC2Type2;
+ } else if (title === 'CCPA') {
+ LogoComponent = enabled && isInProgress ? CCPAInProgress : CCPA;
} else if (title === 'PCI DSS') {
LogoComponent = enabled && isInProgress ? PCIDSSInProgress : PCIDSS;
+ } else if (title === 'PIPEDA') {
+ LogoComponent = enabled && isInProgress ? PIPEDAInProgress : PIPEDA;
} else if (title === 'NEN 7510') {
LogoComponent = enabled && isInProgress ? NEN7510InProgress : NEN7510;
} else if (title === 'ISO 9001') {
diff --git a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/logos.tsx b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/logos.tsx
index cfc333204f..b7105734d7 100644
--- a/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/logos.tsx
+++ b/apps/app/src/app/(app)/[orgId]/trust/portal-settings/components/logos.tsx
@@ -945,3 +945,201 @@ export const SOC3InProgress = (props: React.SVGProps) => (
);
+
+export const PIPEDA = (props: React.SVGProps) => (
+
+);
+
+export const PIPEDAInProgress = (props: React.SVGProps) => (
+
+);
+
+export const CCPA = (props: React.SVGProps) => (
+
+);
+
+export const CCPAInProgress = (props: React.SVGProps) => (
+
+);
diff --git a/packages/db/prisma/migrations/20260507154000_add_pipeda_ccpa_fields/migration.sql b/packages/db/prisma/migrations/20260507154000_add_pipeda_ccpa_fields/migration.sql
new file mode 100644
index 0000000000..a512269f9c
--- /dev/null
+++ b/packages/db/prisma/migrations/20260507154000_add_pipeda_ccpa_fields/migration.sql
@@ -0,0 +1,7 @@
+-- AlterTable
+ALTER TABLE "public"."Trust"
+ADD COLUMN "pipeda" BOOLEAN NOT NULL DEFAULT false,
+ADD COLUMN "pipeda_status" "public"."FrameworkStatus" NOT NULL DEFAULT 'started',
+ADD COLUMN "ccpa" BOOLEAN NOT NULL DEFAULT false,
+ADD COLUMN "ccpa_status" "public"."FrameworkStatus" NOT NULL DEFAULT 'started';
+
diff --git a/packages/db/prisma/migrations/20260507154100_add_pipeda_ccpa_to_trust_framework/migration.sql b/packages/db/prisma/migrations/20260507154100_add_pipeda_ccpa_to_trust_framework/migration.sql
new file mode 100644
index 0000000000..1f62668b85
--- /dev/null
+++ b/packages/db/prisma/migrations/20260507154100_add_pipeda_ccpa_to_trust_framework/migration.sql
@@ -0,0 +1,3 @@
+-- Add PIPEDA and CCPA certificate support to TrustResource framework enum.
+ALTER TYPE "TrustFramework" ADD VALUE IF NOT EXISTS 'pipeda';
+ALTER TYPE "TrustFramework" ADD VALUE IF NOT EXISTS 'ccpa';
diff --git a/packages/db/prisma/schema/trust.prisma b/packages/db/prisma/schema/trust.prisma
index 5edcb4075a..0f34261861 100644
--- a/packages/db/prisma/schema/trust.prisma
+++ b/packages/db/prisma/schema/trust.prisma
@@ -25,6 +25,8 @@ model Trust {
hipaa Boolean @default(false)
pci_dss Boolean @default(false)
iso9001 Boolean @default(false)
+ pipeda Boolean @default(false)
+ ccpa Boolean @default(false)
soc2_status FrameworkStatus @default(started)
soc2type1_status FrameworkStatus @default(started)
@@ -37,6 +39,8 @@ model Trust {
hipaa_status FrameworkStatus @default(started)
pci_dss_status FrameworkStatus @default(started)
iso9001_status FrameworkStatus @default(started)
+ pipeda_status FrameworkStatus @default(started)
+ ccpa_status FrameworkStatus @default(started)
// Overview section for public trust portal
overviewTitle String?
@@ -74,6 +78,8 @@ enum TrustFramework {
pci_dss
nen_7510
iso_9001
+ pipeda
+ ccpa
}
model TrustResource {