diff --git a/src/app/policy-board/_components/compliance-notes.tsx b/src/app/policy-board/_components/compliance-notes.tsx new file mode 100644 index 0000000..08e8e60 --- /dev/null +++ b/src/app/policy-board/_components/compliance-notes.tsx @@ -0,0 +1,60 @@ +import type { ComplianceNote } from "../_data/policy-board-data"; +import styles from "../policy-board.module.css"; + +const priorityLabels: Record = { + info: "Info", + action: "Action", + warning: "Warning", +}; + +const priorityBadgeStyles: Record = { + 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 ( +
+
+

+ Audit trail +

+

+ Compliance notes from reviewers +

+

+ Notes, action items, and warnings captured during the current review + cycle. Each note is tied to a reviewer and timestamped. +

+
+ +
+ {notes.map((note) => ( +
+
+
+

{note.author}

+

{note.timestamp}

+
+ + {priorityLabels[note.priority]} + +
+

{note.body}

+
+ ))} +
+
+ ); +} diff --git a/src/app/policy-board/_components/policy-card.tsx b/src/app/policy-board/_components/policy-card.tsx new file mode 100644 index 0000000..b59e2c9 --- /dev/null +++ b/src/app/policy-board/_components/policy-card.tsx @@ -0,0 +1,68 @@ +import type { Policy } from "../_data/policy-board-data"; +import styles from "../policy-board.module.css"; + +const statusLabels: Record = { + active: "Active", + "under-review": "Under review", + deprecated: "Deprecated", +}; + +const statusBadgeStyles: Record = { + 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 = { + active: styles.policyActive, + "under-review": styles.policyUnderReview, + deprecated: styles.policyDeprecated, +}; + +type PolicyCardProps = { + policy: Policy; +}; + +export function PolicyCard({ policy }: PolicyCardProps) { + return ( +
+
+
+

+ {policy.name} +

+

{policy.category}

+
+ + {statusLabels[policy.status]} + +
+ +
+
+

+ Effective date +

+

+ {policy.effectiveDate} +

+
+
+

+ Owner +

+

+ {policy.owner} +

+
+
+ +

{policy.summary}

+
+ ); +} diff --git a/src/app/policy-board/_data/policy-board-data.ts b/src/app/policy-board/_data/policy-board-data.ts new file mode 100644 index 0000000..de56312 --- /dev/null +++ b/src/app/policy-board/_data/policy-board-data.ts @@ -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", + }, +]; diff --git a/src/app/policy-board/page.test.tsx b/src/app/policy-board/page.test.tsx new file mode 100644 index 0000000..3a1c806 --- /dev/null +++ b/src/app/policy-board/page.test.tsx @@ -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(); + + 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(); + + 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(); + + 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(); + + 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(); + } + }); +}); diff --git a/src/app/policy-board/page.tsx b/src/app/policy-board/page.tsx new file mode 100644 index 0000000..becc069 --- /dev/null +++ b/src/app/policy-board/page.tsx @@ -0,0 +1,113 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { ComplianceNotes } from "./_components/compliance-notes"; +import { PolicyCard } from "./_components/policy-card"; +import styles from "./policy-board.module.css"; +import { + complianceNotes, + policies, + policyBoardOverview, + reviewSummaries, +} from "./_data/policy-board-data"; + +export const metadata: Metadata = { + title: "Policy Board", + description: + "Policy cards, compliance notes, and review summaries for organizational governance and audit readiness.", +}; + +export default function PolicyBoardPage() { + return ( +
+
+ {/* Hero */} +
+
+
+ + {policyBoardOverview.eyebrow} + +

+ {policyBoardOverview.title} +

+

+ {policyBoardOverview.description} +

+
+ +
+
+

+ Review cycle +

+

+ {policyBoardOverview.reviewCycle} +

+

+ {policyBoardOverview.scope} +

+
+ + + Back to overview + +
+
+
+ + {/* Review summary */} +
+ {reviewSummaries.map((stat) => ( +
+

+ {stat.label} +

+

+ {stat.value} +

+

{stat.detail}

+
+ ))} +
+ + {/* Policy cards */} +
+
+
+

+ Governance +

+

+ Active and reviewed policies +

+
+

+ Each card represents an organizational policy with its current status, + ownership, and a brief summary of scope. +

+
+ +
+ {policies.map((policy) => ( + + ))} +
+
+ + {/* Compliance notes */} + +
+
+ ); +} diff --git a/src/app/policy-board/policy-board.module.css b/src/app/policy-board/policy-board.module.css new file mode 100644 index 0000000..1512974 --- /dev/null +++ b/src/app/policy-board/policy-board.module.css @@ -0,0 +1,96 @@ +.shell { + position: relative; + min-height: 100vh; + overflow: hidden; + background: + radial-gradient(circle at 18% 14%, rgba(99, 102, 241, 0.14), transparent 22%), + radial-gradient(circle at 82% 18%, rgba(34, 211, 238, 0.16), transparent 24%), + radial-gradient(circle at 50% 96%, rgba(168, 85, 247, 0.16), transparent 28%), + linear-gradient(180deg, #08111f 0%, #0e1c34 44%, #030712 100%); +} + +.shell::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(125deg, rgba(255, 255, 255, 0.03), transparent 45%), + repeating-linear-gradient( + 90deg, + rgba(148, 163, 184, 0.06) 0, + rgba(148, 163, 184, 0.06) 1px, + transparent 1px, + transparent 72px + ); + mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.95), transparent 96%); +} + +.heroPanel { + position: relative; + background: + linear-gradient(140deg, rgba(15, 23, 42, 0.82), rgba(15, 23, 42, 0.58)), + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.12)); + box-shadow: 0 38px 120px rgba(2, 6, 23, 0.42); +} + +.heroPanel::after { + content: ""; + position: absolute; + right: -5rem; + top: -4rem; + width: 18rem; + height: 18rem; + border-radius: 9999px; + background: radial-gradient(circle, rgba(99, 102, 241, 0.22), transparent 66%); + filter: blur(20px); + pointer-events: none; +} + +.statCard { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.28)); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.policyCard { + position: relative; + overflow: hidden; + background: + linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(6, 11, 24, 0.94)); + box-shadow: 0 26px 70px rgba(2, 6, 23, 0.26); +} + +.policyActive { + border-color: rgba(52, 211, 153, 0.22); +} + +.policyUnderReview { + border-color: rgba(251, 191, 36, 0.24); +} + +.policyDeprecated { + border-color: rgba(148, 163, 184, 0.18); +} + +.noteCard { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(15, 23, 42, 0.32)); +} + +.statusBadge { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.floatingInfo { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.26)); +} + +@media (max-width: 640px) { + .heroPanel, + .policyCard, + .noteCard, + .statCard { + border-radius: 1.5rem; + } +}