Skip to content

Commit 5a0d3d2

Browse files
authored
Merge pull request #561 from trycompai/lewis/comp-trust-center-updates
[dev] [carhartlewis] lewis/comp-trust-center-updates
2 parents bf74fb3 + 52b3d23 commit 5a0d3d2

File tree

9 files changed

+265
-55
lines changed

9 files changed

+265
-55
lines changed

apps/trust/src/app/[id]/components/compliance-header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function ComplianceHeader({ organization, title }: ComplianceHead
2323
className="object-contain"
2424
/>
2525
) : (
26-
<div className="w-10 h-10 bg-muted rounded-md flex items-center justify-center text-white font-bold">
26+
<div className="w-10 h-10 bg-muted-foreground rounded-md flex items-center justify-center text-white font-bold">
2727
{organization.name.charAt(0)}
2828
</div>
2929
)}
@@ -32,12 +32,12 @@ export default function ComplianceHeader({ organization, title }: ComplianceHead
3232
</div>
3333

3434
<div className="flex gap-2">
35-
<Link className={buttonVariants({ variant: "outline", className: "text-xs" })} href={`${organization.website}`}>
35+
<Link className={buttonVariants({ variant: "outline", className: "text-xs" })} href={`${organization.website || "https://trycomp.ai"}`}>
3636
<Globe className="w-3 h-3" />
3737
{organization.name}
3838
</Link>
3939
<Link className={buttonVariants({ variant: "outline", className: "text-xs" })} href="https://trycomp.ai">
40-
<div className="w-2 h-2 bg-green-500 rounded-full" />
40+
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
4141
Monitoring with Comp AI
4242
</Link>
4343
</div>

apps/trust/src/app/[id]/components/compliance-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface ComplianceItemProps {
77

88
export default function ComplianceItem({ text, isCompliant }: ComplianceItemProps) {
99
return (
10-
<div className="flex items-center justify-between py-1 font-mono">
10+
<div className="flex items-center justify-between py-1">
1111
<span className="text-sm">{text}</span>
1212
{isCompliant ? (
1313
<div className="w-2 h-2 bg-green-500 rounded-full" />

apps/trust/src/app/[id]/components/report.tsx

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import type { Organization, Policy, Task } from '@comp/db/types';
66

77
interface ComplianceReportProps {
88
organization: Organization;
9-
policies: Policy[];
10-
controls: Task[];
9+
policies: Pick<Policy, "id" | "name" | "status">[];
10+
controls: Pick<Task, "id" | "title" | "status">[];
1111
}
1212

1313
export default function ComplianceReport({ organization, policies, controls }: ComplianceReportProps) {
1414
return (
15-
<div className="min-h-screen">
16-
<div className="max-w-6xl mx-auto p-6">
15+
<div>
16+
<div className="max-w-6xl mx-auto">
1717
<div className="rounded-lg">
1818
<div>
1919
<ComplianceHeader
@@ -22,30 +22,31 @@ export default function ComplianceReport({ organization, policies, controls }: C
2222
/>
2323

2424
<ComplianceSummary
25-
text={`${organization.name} is using Comp AI to monitor their compliance against frameworks like SOC 2, ISO 27001, and more.`}
25+
text={`${organization.name} is using Comp AI to monitor their compliance against common cybersecurity frameworks like SOC 2, ISO 27001, and more.`}
2626
/>
27-
28-
<div className="border-b mt-4" />
2927
</div>
3028

3129
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
32-
<ComplianceSection title="Policies" isLive>
33-
<div className="space-y-2">
34-
{policies.map((policy) => (
35-
<ComplianceItem key={policy.id} text={policy.name} isCompliant={policy.status === "published"} />
36-
))}
37-
</div>
38-
</ComplianceSection>
30+
{policies.length > 0 && (
31+
<ComplianceSection title="Policies" isLive>
32+
<div className="space-y-2">
33+
{policies.map((policy) => (
34+
<ComplianceItem key={policy.id} text={policy.name} isCompliant={policy.status === "published"} />
35+
))}
36+
</div>
37+
</ComplianceSection>
38+
)}
3939

40-
<ComplianceSection title="Controls" isLive>
41-
<div className="space-y-2">
42-
{controls.map((control) => (
43-
<ComplianceItem key={control.id} text={control.title} isCompliant={control.status === "done"} />
44-
))}
45-
</div>
46-
</ComplianceSection>
40+
{controls.length > 0 && (
41+
<ComplianceSection title="Controls" isLive>
42+
<div className="space-y-2">
43+
{controls.map((control) => (
44+
<ComplianceItem key={control.id} text={control.title} isCompliant={control.status === "done"} />
45+
))}
46+
</div>
47+
</ComplianceSection>
48+
)}
4749
</div>
48-
4950
</div>
5051
</div>
5152
</div>
Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,85 @@
11
"use server";
22

33
import { db } from "@comp/db";
4+
import { redirect } from "next/navigation";
5+
import { cache } from "react";
46

5-
export const findOrganization = async (id: string) => {
7+
export const findOrganization = cache(async (id: string) => {
68
const organization = await db.organization.findFirst({
79
where: { id: id },
10+
include: {
11+
trust: {
12+
where: {
13+
status: "published",
14+
},
15+
},
16+
},
817
});
918

10-
const isPublished = await db.trust.findUnique({
11-
where: { organizationId: organization?.id, status: "published" },
12-
});
13-
14-
if (!organization || !isPublished) {
15-
return null;
19+
if (!organization) {
20+
return redirect("/");
1621
}
1722

1823
return {
1924
...organization,
2025
};
21-
};
26+
});
27+
28+
export const getPublishedPolicies = cache(async (organizationId: string) => {
29+
const organization = await findOrganization(organizationId);
30+
31+
if (!organization) {
32+
return redirect("/");
33+
}
2234

23-
export const getPublishedPolicies = async (organizationId: string) => {
2435
const policies = await db.policy.findMany({
2536
where: { organizationId, status: "published" },
37+
select: {
38+
id: true,
39+
name: true,
40+
status: true,
41+
},
2642
});
2743

2844
return policies;
29-
};
30-
31-
export const getPublishedPolicy = async (
32-
organizationId: string,
33-
policyId: string,
34-
) => {
35-
const policy = await db.policy.findFirst({
36-
where: { organizationId, status: "published", id: policyId },
37-
});
45+
});
3846

39-
return policy;
40-
};
47+
export const getPublishedPolicy = cache(
48+
async (organizationId: string, policyId: string) => {
49+
const organization = await findOrganization(organizationId);
50+
51+
if (!organization) {
52+
return redirect("/");
53+
}
54+
55+
const policy = await db.policy.findFirst({
56+
where: { organizationId, status: "published", id: policyId },
57+
select: {
58+
id: true,
59+
name: true,
60+
status: true,
61+
},
62+
});
63+
64+
return policy;
65+
},
66+
);
67+
68+
export const getPublishedControls = cache(async (organizationId: string) => {
69+
const organization = await findOrganization(organizationId);
70+
71+
if (!organization) {
72+
return redirect("/");
73+
}
4174

42-
export const getPublishedControls = async (organizationId: string) => {
4375
const controls = await db.task.findMany({
4476
where: { organizationId, status: "done" },
77+
select: {
78+
id: true,
79+
title: true,
80+
status: true,
81+
},
4582
});
4683

4784
return controls;
48-
};
85+
});

apps/trust/src/app/[id]/page.tsx

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { findOrganization, getPublishedControls, getPublishedPolicies } from "./lib/data";
22
import ComplianceReport from './components/report';
3+
import { Metadata } from "next";
34

45
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
56
const id = (await params).id;
@@ -13,5 +14,60 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
1314
const policies = await getPublishedPolicies(organization.id);
1415
const controls = await getPublishedControls(organization.id);
1516

16-
return <ComplianceReport organization={organization} policies={policies} controls={controls} />;
17+
return (
18+
<div>
19+
<div className="pb-6 p-6">
20+
<ComplianceReport organization={organization} policies={policies} controls={controls} />
21+
</div>
22+
</div>
23+
)
1724
}
25+
26+
export async function generateMetadata({
27+
params,
28+
}: {
29+
params: Promise<{ id: string }>;
30+
}): Promise<Metadata> {
31+
const id = (await params).id;
32+
const organization = await findOrganization(id);
33+
34+
if (!organization) {
35+
return {
36+
title: "Organization not found",
37+
};
38+
}
39+
40+
const title = `${organization.name} - Trust Center`;
41+
const description = `${organization.name} is using Comp AI to monitor their compliance against common cybersecurity frameworks like SOC 2, ISO 27001, and more.`;
42+
const url = `https://trycomp.ai/trust/${organization.id}`;
43+
44+
return {
45+
title,
46+
description,
47+
alternates: {
48+
canonical: url,
49+
},
50+
openGraph: {
51+
type: "website",
52+
url,
53+
title,
54+
description,
55+
siteName: "Comp AI Trust Center",
56+
images: [
57+
{
58+
url: 'https://trycomp.ai/og.png',
59+
width: 1200,
60+
height: 630,
61+
alt: `${organization.name} Trust Center`,
62+
},
63+
],
64+
},
65+
twitter: {
66+
card: "summary_large_image",
67+
site: "@trycompai",
68+
title,
69+
description,
70+
images: ['https://trycomp.ai/og.png'],
71+
},
72+
};
73+
}

apps/trust/src/app/layout.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@ import { Toaster } from '@comp/ui/toaster';
22
import type { Metadata } from 'next';
33
import { type ReactNode } from 'react';
44
import "@comp/ui/globals.css";
5+
import localFont from 'next/font/local';
6+
import { GeistMono } from "geist/font/mono";
7+
import { cn } from '@comp/ui/cn';
58

69
export const metadata: Metadata = {
710
title: 'Comp AI - Trust Portal',
811
description: 'Trust Portal',
912
}
1013

14+
const font = localFont({
15+
src: "/../../public/fonts/GeneralSans-Variable.ttf",
16+
display: "swap",
17+
variable: "--font-general-sans",
18+
});
19+
1120
export default function RootLayout({
1221
children,
1322
}: {
1423
children: ReactNode
1524
}) {
1625
return (
17-
<html lang="en" className="h-full">
18-
<body>
19-
<div className="flex flex-col container gap-2 h-full">
26+
<html lang="en">
27+
<body
28+
className={cn(
29+
`${GeistMono.variable} ${font.variable}`,
30+
"whitespace-pre-line overscroll-none antialiased",
31+
)}
32+
>
33+
<div className="flex flex-col container min-h-screen">
2034
{children}
21-
<Toaster />
2235
</div>
36+
<Toaster />
2337
</body>
2438
</html>
2539
)

apps/trust/src/app/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { redirect } from "next/navigation";
22

3-
export default async function RootPage() {
4-
return redirect("https://trycomp.ai/");
3+
export default function Page() {
4+
return (
5+
redirect("https://trycomp.ai")
6+
);
57
}

apps/trust/src/app/providers.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { AnalyticsProvider } from "@comp/analytics";
4+
import { GoogleTagManager } from "@next/third-parties/google";
5+
import { ThemeProvider } from "next-themes";
6+
import type { ReactNode } from "react";
7+
8+
type ProviderProps = {
9+
children: ReactNode;
10+
};
11+
12+
export function Providers({ children }: ProviderProps) {
13+
return (
14+
<ThemeProvider
15+
attribute="class"
16+
disableTransitionOnChange
17+
scriptProps={{ "data-cfasync": "false" }}
18+
defaultTheme="dark"
19+
enableSystem={false}
20+
>
21+
<GoogleTagManager
22+
gtmId="GTM-56GW3TVW"
23+
dataLayer={{
24+
user_id: "",
25+
user_email: "",
26+
}}
27+
/>
28+
<GoogleTagManager
29+
gtmId="AW-16886441131"
30+
dataLayer={{
31+
user_id: "",
32+
user_email: "",
33+
}}
34+
/>
35+
<AnalyticsProvider
36+
userId={undefined}
37+
userEmail={undefined}
38+
>
39+
{children}
40+
</AnalyticsProvider>
41+
</ThemeProvider>
42+
);
43+
}

0 commit comments

Comments
 (0)