Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/app/policy-board/_components/compliance-notes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ComplianceNote } from "../_data/policy-board-data";
import styles from "../policy-board.module.css";

const priorityLabels: Record<ComplianceNote["priority"], string> = {
info: "Info",
action: "Action",
warning: "Warning",
};

const priorityBadgeStyles: Record<ComplianceNote["priority"], string> = {
info: "border-cyan-300/20 bg-cyan-300/10 text-cyan-50",
action: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50",
warning: "border-amber-300/20 bg-amber-300/10 text-amber-50",
};

type ComplianceNotesProps = {
notes: ComplianceNote[];
};

export function ComplianceNotes({ notes }: ComplianceNotesProps) {
return (
<section aria-label="Compliance notes" className="space-y-5">
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-slate-400">
Audit trail
</p>
<h2 className="text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Compliance notes from reviewers
</h2>
<p className="max-w-2xl text-sm leading-6 text-slate-300">
Notes, action items, and warnings captured during the current review
cycle. Each note is tied to a reviewer and timestamped.
</p>
</div>

<div className="space-y-4" role="list" aria-label="Compliance notes list">
{notes.map((note) => (
<article
key={note.id}
className={`${styles.noteCard} rounded-[1.5rem] border border-white/10 p-5`}
role="listitem"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1">
<p className="text-sm font-semibold text-white">{note.author}</p>
<p className="text-xs text-slate-400">{note.timestamp}</p>
</div>
<span
className={`${styles.statusBadge} inline-flex rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] ${priorityBadgeStyles[note.priority]}`}
>
{priorityLabels[note.priority]}
</span>
</div>
<p className="mt-3 text-sm leading-6 text-slate-300">{note.body}</p>
</article>
))}
</div>
</section>
);
}
68 changes: 68 additions & 0 deletions src/app/policy-board/_components/policy-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Policy } from "../_data/policy-board-data";
import styles from "../policy-board.module.css";

const statusLabels: Record<Policy["status"], string> = {
active: "Active",
"under-review": "Under review",
deprecated: "Deprecated",
};

const statusBadgeStyles: Record<Policy["status"], string> = {
active: "border-emerald-300/20 bg-emerald-300/10 text-emerald-50",
"under-review": "border-amber-300/20 bg-amber-300/10 text-amber-50",
deprecated: "border-slate-300/20 bg-slate-300/10 text-slate-300",
};

const statusSurfaceStyles: Record<Policy["status"], string> = {
active: styles.policyActive,
"under-review": styles.policyUnderReview,
deprecated: styles.policyDeprecated,
};

type PolicyCardProps = {
policy: Policy;
};

export function PolicyCard({ policy }: PolicyCardProps) {
return (
<article
className={`${styles.policyCard} ${statusSurfaceStyles[policy.status]} rounded-[1.7rem] border p-6`}
role="listitem"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1">
<h3 className="text-lg font-semibold tracking-tight text-white">
{policy.name}
</h3>
<p className="text-sm text-slate-300">{policy.category}</p>
</div>
<span
className={`${styles.statusBadge} inline-flex rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] ${statusBadgeStyles[policy.status]}`}
>
{statusLabels[policy.status]}
</span>
</div>

<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="rounded-2xl border border-white/8 bg-white/5 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">
Effective date
</p>
<p className="mt-1 text-sm font-medium text-slate-100">
{policy.effectiveDate}
</p>
</div>
<div className="rounded-2xl border border-white/8 bg-white/5 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400">
Owner
</p>
<p className="mt-1 text-sm font-medium text-slate-100">
{policy.owner}
</p>
</div>
</div>

<p className="mt-4 text-sm leading-6 text-slate-300">{policy.summary}</p>
</article>
);
}
146 changes: 146 additions & 0 deletions src/app/policy-board/_data/policy-board-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
export type PolicyStatus = "active" | "under-review" | "deprecated";
export type ComplianceLevel = "compliant" | "partial" | "non-compliant";
export type NotePriority = "info" | "action" | "warning";

export interface Policy {
id: string;
name: string;
status: PolicyStatus;
category: string;
effectiveDate: string;
owner: string;
summary: string;
}

export interface ComplianceNote {
id: string;
author: string;
timestamp: string;
body: string;
priority: NotePriority;
}

export interface ReviewSummary {
label: string;
value: string;
detail: string;
}

export interface PolicyBoardOverview {
eyebrow: string;
title: string;
description: string;
reviewCycle: string;
scope: string;
}

export const policyBoardOverview: PolicyBoardOverview = {
eyebrow: "Policy Board",
title: "Track active policies, compliance status, and review summaries in one place.",
description:
"A centralized view of organizational policies, compliance notes from auditors and stakeholders, and a compact summary of the latest review cycle.",
reviewCycle: "Q2 2026 — Review 3",
scope: "Engineering & Data divisions",
};

export const reviewSummaries: ReviewSummary[] = [
{
label: "Active policies",
value: "5",
detail: "Covering data retention, access control, incident response, change management, and vendor risk",
},
{
label: "Compliance notes",
value: "4",
detail: "Two action items, one warning, and one informational note from the latest audit cycle",
},
{
label: "Next review deadline",
value: "May 12",
detail: "All policy owners must submit updated compliance evidence before the review window closes",
},
];

export const policies: Policy[] = [
{
id: "pol-data-retention",
name: "Data Retention Policy",
status: "active",
category: "Data governance",
effectiveDate: "2025-01-15",
owner: "Data Platform team",
summary:
"Defines retention periods for all data classes. Production logs are retained for 90 days, analytics data for 2 years, and PII is purged within 30 days of account closure.",
},
{
id: "pol-access-control",
name: "Access Control Policy",
status: "active",
category: "Security",
effectiveDate: "2024-11-01",
owner: "Security engineering",
summary:
"Enforces least-privilege access across all internal systems. Role-based access is reviewed quarterly, and elevated permissions require manager approval with a 72-hour expiry.",
},
{
id: "pol-incident-response",
name: "Incident Response Policy",
status: "under-review",
category: "Operations",
effectiveDate: "2025-03-10",
owner: "SRE team",
summary:
"Outlines escalation paths, severity classifications, and post-incident review timelines. Currently under review to add requirements for customer notification within 4 hours of SEV-1 incidents.",
},
{
id: "pol-change-mgmt",
name: "Change Management Policy",
status: "active",
category: "Operations",
effectiveDate: "2025-06-01",
owner: "Release engineering",
summary:
"Requires all production changes to pass through a staged rollout with automated canary analysis. Emergency hotfixes must be retroactively documented within 24 hours.",
},
{
id: "pol-vendor-risk",
name: "Vendor Risk Assessment Policy",
status: "deprecated",
category: "Compliance",
effectiveDate: "2023-08-20",
owner: "Legal & compliance",
summary:
"Legacy vendor evaluation framework. Replaced by the updated third-party risk management policy. Retained for reference on contracts signed before 2025.",
},
];

export const complianceNotes: ComplianceNote[] = [
{
id: "cn-001",
author: "R. Nakamura",
timestamp: "2026-04-25 10:30 PDT",
body: "Data retention evidence for Q1 has been uploaded. Two edge cases around ephemeral processing logs still need clarification from the data platform team.",
priority: "action",
},
{
id: "cn-002",
author: "L. Fernandez",
timestamp: "2026-04-25 09:15 PDT",
body: "Access control audit flagged three service accounts with elevated permissions that haven't been rotated in over 90 days. Escalating to security engineering.",
priority: "warning",
},
{
id: "cn-003",
author: "D. Kim",
timestamp: "2026-04-24 16:45 PDT",
body: "Incident response policy revision is on track. Draft includes the new customer notification SLA. Expecting final sign-off by May 5.",
priority: "info",
},
{
id: "cn-004",
author: "T. Okonkwo",
timestamp: "2026-04-24 14:20 PDT",
body: "Change management compliance check for April deployments is complete. All 23 production changes followed the staged rollout process with no exceptions.",
priority: "action",
},
];
73 changes: 73 additions & 0 deletions src/app/policy-board/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { cleanup, render, screen, within } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";

import {
complianceNotes,
policies,
policyBoardOverview,
reviewSummaries,
} from "./_data/policy-board-data";
import PolicyBoardPage from "./page";

afterEach(() => {
cleanup();
});

describe("PolicyBoardPage", () => {
it("renders the hero with primary heading, overview metadata, and back link", () => {
render(<PolicyBoardPage />);

expect(
screen.getByText(policyBoardOverview.title, { selector: "h1" }),
).toBeInTheDocument();
expect(screen.getByText(policyBoardOverview.reviewCycle)).toBeInTheDocument();
expect(screen.getByText(policyBoardOverview.scope)).toBeInTheDocument();
expect(
screen.getByRole("link", { name: /back to overview/i }),
).toHaveAttribute("href", "/");
});

it("renders review summary stats", () => {
render(<PolicyBoardPage />);

const summarySection = screen.getByLabelText(/review summary/i);

for (const stat of reviewSummaries) {
const statEl = within(summarySection).getByText(stat.label).closest("article");

expect(statEl).toBeTruthy();
expect(within(statEl as HTMLElement).getByText(stat.value)).toBeInTheDocument();
}
});

it("renders all policy cards with names, categories, and status badges", () => {
render(<PolicyBoardPage />);

const policyList = screen.getByRole("list", { name: /policy cards/i });
const policyItems = within(policyList).getAllByRole("listitem");

expect(policyItems).toHaveLength(policies.length);

for (const policy of policies) {
expect(within(policyList).getByText(policy.name)).toBeInTheDocument();
expect(within(policyList).getByText(policy.category)).toBeInTheDocument();
expect(within(policyList).getByText(policy.owner)).toBeInTheDocument();
}
});

it("renders compliance notes with authors, timestamps, and priority badges", () => {
render(<PolicyBoardPage />);

const notesSection = screen.getByLabelText(/compliance notes$/i);
const notesList = within(notesSection).getByRole("list", { name: /compliance notes list/i });
const noteItems = within(notesList).getAllByRole("listitem");

expect(noteItems).toHaveLength(complianceNotes.length);

for (const note of complianceNotes) {
expect(within(notesList).getByText(note.author)).toBeInTheDocument();
expect(within(notesList).getByText(note.timestamp)).toBeInTheDocument();
expect(within(notesList).getByText(note.body)).toBeInTheDocument();
}
});
});
Loading
Loading