From 8efa89c045126f58dccbcdd1b5efd97c3d72a1e6 Mon Sep 17 00:00:00 2001 From: bugman-007 Date: Fri, 27 Mar 2026 03:09:16 -0400 Subject: [PATCH] feat: add Legal Thoughts panel for editorial legal analysis Implement a collapsible side panel that displays editorial legal analysis content alongside the chat interface. This feature allows organizations to showcase thought leadership and domain expertise. Features: - 38 practice area categories for CLE content organization - Expandable cards with author info, read time, and tags - Featured content highlighting - Responsive slide-over panel (mobile) and inline sidebar (desktop) - CMS-ready data layer (currently JSON, easily migratable to Payload/Airtable) Files added: - components/legal-analysis/ - UI components (panel, toggle, sidebar) - lib/legal-analysis/ - Types, schemas, and sample data Usage: Click the "Legal Thoughts" button in the header to view legal analysis content. Content can be filtered by practice area. Future enhancements: - CMS integration (Payload/Airtable) - Full article pages with slug-based routing - Search functionality - Related content suggestions alongside chat results --- app/(main)/o/[slug]/header.tsx | 4 +- components/legal-analysis/README.md | 208 ++++++++++++++++++ components/legal-analysis/index.ts | 3 + .../legal-analysis/legal-analysis-content.tsx | 18 ++ .../legal-analysis/legal-analysis-panel.tsx | 156 +++++++++++++ .../legal-analysis/legal-analysis-toggle.tsx | 124 +++++++++++ lib/legal-analysis/data.ts | 205 +++++++++++++++++ lib/legal-analysis/types.ts | 113 ++++++++++ 8 files changed, 830 insertions(+), 1 deletion(-) create mode 100644 components/legal-analysis/README.md create mode 100644 components/legal-analysis/index.ts create mode 100644 components/legal-analysis/legal-analysis-content.tsx create mode 100644 components/legal-analysis/legal-analysis-panel.tsx create mode 100644 components/legal-analysis/legal-analysis-toggle.tsx create mode 100644 lib/legal-analysis/data.ts create mode 100644 lib/legal-analysis/types.ts diff --git a/app/(main)/o/[slug]/header.tsx b/app/(main)/o/[slug]/header.tsx index 478e9b7d..9748bd5d 100644 --- a/app/(main)/o/[slug]/header.tsx +++ b/app/(main)/o/[slug]/header.tsx @@ -24,6 +24,7 @@ import PlusIcon from "@/public/icons/plus.svg"; import { Banner, BannerLink } from "./banner"; import ConversationHistory from "./conversation-history"; +import { LegalAnalysisToggle } from "@/components/legal-analysis/legal-analysis-toggle"; const errorSchema = z.object({ error: z.string(), @@ -121,7 +122,7 @@ export default function Header({ return (
-
+
Expand chats @@ -133,6 +134,7 @@ export default function Header({ New chat +
{billingEnabled && ( <> diff --git a/components/legal-analysis/README.md b/components/legal-analysis/README.md new file mode 100644 index 00000000..ec323df2 --- /dev/null +++ b/components/legal-analysis/README.md @@ -0,0 +1,208 @@ +# Legal Analysis Panel ("Legal Thoughts") + +A collapsible side panel that displays editorial legal analysis content alongside the chat interface. + +## Features + +- **Practice Area Organization**: Content is organized by 38 legal practice areas +- **Expandable Cards**: Click to expand and read full analysis excerpts +- **Author Information**: Displays author name, title, and firm +- **Read Time**: Shows estimated reading time for each article +- **Featured Content**: Highlights featured analyses +- **Tag Filtering**: Content can be filtered by tags +- **Responsive Design**: Works as slide-over panel on mobile, inline sidebar on desktop + +## File Structure + +``` +components/legal-analysis/ +├── index.ts # Public exports +├── legal-analysis-panel.tsx # Main panel component (client) +├── legal-analysis-content.tsx # Server component for data fetching +├── legal-analysis-toggle.tsx # Toggle button and slide-out panel +├── README.md # This file +└── ... + +lib/legal-analysis/ +├── types.ts # TypeScript types and schemas +├── data.ts # Sample content (replace with CMS) +└── ... +``` + +## Data Source + +Currently uses static JSON data in `lib/legal-analysis/data.ts`. To integrate with a CMS: + +### Payload CMS + +```typescript +// lib/legal-analysis/payload.ts +import { getPayloadClient } from '@/lib/payload' + +export async function getLegalAnalyses(filters) { + const payload = await getPayloadClient() + const results = await payload.find({ + collection: 'legal-analyses', + where: { + ...(filters.practiceArea && { + practiceArea: { equals: filters.practiceArea } + }), + ...(filters.featured && { + featured: { equals: true } + }) + } + }) + return results.docs +} +``` + +### Airtable + +```typescript +// lib/legal-analysis/airtable.ts +import Airtable from 'airtable' + +const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }) + .base(process.env.AIRTABLE_BASE_ID) + +export async function getLegalAnalyses(filters) { + const records = await base('LegalAnalyses').select({ + filterByFormula: buildFilterFormula(filters) + }).all() + + return records.map(record => mapToLegalAnalysis(record)) +} +``` + +## Usage + +### In Header (Global Toggle) + +```tsx +import { LegalAnalysisToggle } from '@/components/legal-analysis' + +export function Header() { + return ( +
+ +
+ ) +} +``` + +### With Practice Area Filter + +```tsx +import { LegalAnalysisToggle } from '@/components/legal-analysis' +import { PracticeArea } from '@/lib/legal-analysis/types' + +export function ClassPage({ practiceArea }: { practiceArea: PracticeArea }) { + return ( + + ) +} +``` + +### Inline Sidebar (Desktop) + +```tsx +import { LegalAnalysisSidebar } from '@/components/legal-analysis' + +export function ConversationLayout() { + return ( +
+
...
+ +
+ ) +} +``` + +## Adding Content + +### Via Data File (Quick Start) + +Edit `lib/legal-analysis/data.ts`: + +```typescript +export const legalAnalyses: LegalAnalysis[] = [ + { + id: "la-006", + title: "Your Article Title", + excerpt: "Brief summary...", + content: `Full markdown content...`, + practiceArea: "employment-law", + author: { + name: "Author Name", + title: "Partner", + firm: "Law Firm", + }, + publishedAt: "2025-03-20T10:00:00Z", + readTimeMinutes: 8, + featured: true, + tags: ["tag1", "tag2"], + }, + // ... more articles +] +``` + +### Practice Areas + +Available practice area keys: + +```typescript +"administrative-law" +"alternative-dispute-resolution" +"antitrust" +"appellate-practice" +"bankruptcy" +"business-law" +"civil-rights" +"construction-law" +"consumer-protection" +"corporate-law" +"criminal-law" +"employment-law" +"environmental-law" +"estate-planning" +"family-law" +"healthcare-law" +"immigration-law" +"insurance-law" +"intellectual-property" +"international-law" +"labor-law" +"litigation" +"municipal-law" +"personal-injury" +"product-liability" +"professional-responsibility" +"real-estate" +"securities-law" +"tax-law" +"technology-law" +"trusts-estates" +"workers-compensation" +``` + +## Styling + +Uses existing Base Chat design system: +- Radix UI components +- Tailwind CSS +- Lucide icons + +To customize colors or spacing, modify the component classes or update `tailwind.config.ts`. + +## Future Enhancements + +- [ ] Full article page with slug-based routing +- [ ] Search functionality across all analyses +- [ ] Related content suggestions +- [ ] Bookmark/favorite functionality +- [ ] Print/export to PDF +- [ ] Social sharing +- [ ] Comment/annotation system +- [ ] CLE credit tracking integration diff --git a/components/legal-analysis/index.ts b/components/legal-analysis/index.ts new file mode 100644 index 00000000..c938b0fb --- /dev/null +++ b/components/legal-analysis/index.ts @@ -0,0 +1,3 @@ +export { LegalAnalysisPanel } from "./legal-analysis-panel"; +export { LegalAnalysisContent } from "./legal-analysis-content"; +export { LegalAnalysisToggle, LegalAnalysisSidebar } from "./legal-analysis-toggle"; diff --git a/components/legal-analysis/legal-analysis-content.tsx b/components/legal-analysis/legal-analysis-content.tsx new file mode 100644 index 00000000..699cb641 --- /dev/null +++ b/components/legal-analysis/legal-analysis-content.tsx @@ -0,0 +1,18 @@ +import { LegalAnalysisPanel } from "./legal-analysis-panel"; +import { getLegalAnalyses } from "@/lib/legal-analysis/data"; +import { LegalAnalysisFilters } from "@/lib/legal-analysis/types"; + +interface LegalAnalysisContentProps { + filters?: LegalAnalysisFilters; + className?: string; +} + +/** + * Server component that fetches legal analysis data + * and renders the panel + */ +export async function LegalAnalysisContent({ filters, className }: LegalAnalysisContentProps) { + const analyses = getLegalAnalyses(filters); + + return ; +} diff --git a/components/legal-analysis/legal-analysis-panel.tsx b/components/legal-analysis/legal-analysis-panel.tsx new file mode 100644 index 00000000..daa45779 --- /dev/null +++ b/components/legal-analysis/legal-analysis-panel.tsx @@ -0,0 +1,156 @@ +"use client"; + +import { BookOpen, ChevronRight, Clock, User } from "lucide-react"; +import { useState } from "react"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + LegalAnalysis, + LegalAnalysisFilters, + PRACTICE_AREA_LABELS, + PracticeArea, +} from "@/lib/legal-analysis/types"; + +interface LegalAnalysisPanelProps { + analyses: LegalAnalysis[]; + filters?: LegalAnalysisFilters; + onFilterChange?: (filters: LegalAnalysisFilters) => void; + className?: string; +} + +export function LegalAnalysisPanel({ + analyses, + filters, + onFilterChange, + className, +}: LegalAnalysisPanelProps) { + const [expandedId, setExpandedId] = useState(null); + + const handleCardClick = (id: string) => { + setExpandedId(expandedId === id ? null : id); + }; + + if (analyses.length === 0) { + return ( +
+ +

No legal analysis content available

+
+ ); + } + + return ( +
+
+

Legal Thoughts

+ {analyses.length} articles +
+ +
+ {analyses.map((analysis) => ( + handleCardClick(analysis.id)} + /> + ))} +
+
+ ); +} + +interface LegalAnalysisCardProps { + analysis: LegalAnalysis; + isExpanded: boolean; + onToggle: () => void; +} + +function LegalAnalysisCard({ analysis, isExpanded, onToggle }: LegalAnalysisCardProps) { + return ( + + +
+
+ {analysis.title} + {analysis.excerpt} +
+ +
+ +
+
+ + {analysis.author.name} +
+
+ + {analysis.readTimeMinutes} min read +
+
+ + {PRACTICE_AREA_LABELS[analysis.practiceArea as PracticeArea]} +
+
+ + {analysis.featured && ( +
+ + Featured + +
+ )} +
+ + {isExpanded && ( + +
+
+
+
+ + {analysis.tags && analysis.tags.length > 0 && ( +
+ {analysis.tags.map((tag) => ( + + #{tag} + + ))} +
+ )} + +
+
+ Published {new Date(analysis.publishedAt).toLocaleDateString()} +
+ +
+
+ + )} + + ); +} diff --git a/components/legal-analysis/legal-analysis-toggle.tsx b/components/legal-analysis/legal-analysis-toggle.tsx new file mode 100644 index 00000000..21ffae80 --- /dev/null +++ b/components/legal-analysis/legal-analysis-toggle.tsx @@ -0,0 +1,124 @@ +"use client"; + +import { BookOpen, ChevronRight, X } from "lucide-react"; +import { useState } from "react"; + +import { LegalAnalysisContent } from "./legal-analysis-content"; +import { Button } from "@/components/ui/button"; +import { PracticeArea } from "@/lib/legal-analysis/types"; + +interface LegalAnalysisToggleProps { + practiceArea?: PracticeArea; +} + +/** + * Toggle button and slide-out panel for Legal Analysis content + */ +export function LegalAnalysisToggle({ practiceArea }: LegalAnalysisToggleProps) { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + {/* Toggle Button */} + + + {/* Slide-over Panel */} + {isOpen && ( + <> + {/* Backdrop */} +
setIsOpen(false)} + /> + + {/* Panel */} +
+
+

+ + Legal Thoughts +

+ +
+ +
+ +
+
+ + )} + + ); +} + +/** + * Desktop inline version - displays as a collapsible sidebar section + */ +export function LegalAnalysisSidebar({ practiceArea }: LegalAnalysisToggleProps) { + const [isExpanded, setIsExpanded] = useState(true); + + if (!isExpanded) { + return ( +
+ +
+ ); + } + + return ( +
+
+

+ + Legal Thoughts +

+ +
+ +
+ +
+
+ ); +} diff --git a/lib/legal-analysis/data.ts b/lib/legal-analysis/data.ts new file mode 100644 index 00000000..92a269d2 --- /dev/null +++ b/lib/legal-analysis/data.ts @@ -0,0 +1,205 @@ +import { LegalAnalysis } from "./types"; + +/** + * Sample legal analysis content + * + * This is a placeholder data source. In production, this would be replaced by: + * - Payload CMS collection + * - Airtable API + * - Database-backed content system + * + * To add content, simply add new entries to this array. + */ +export const legalAnalyses: LegalAnalysis[] = [ + { + id: "la-001", + title: "Recent Developments in Employment Law: What Practitioners Need to Know", + excerpt: + "A comprehensive review of recent case law and regulatory changes affecting employment practice, including remote work considerations and evolving classification standards.", + content: ` +## Overview + +The landscape of employment law continues to evolve rapidly, particularly in the post-pandemic era. This analysis examines key developments that every employment law practitioner should understand. + +## Remote Work and Jurisdictional Issues + +The shift to remote work has created novel jurisdictional questions... + +## Worker Classification Standards + +Recent decisions have clarified the independent contractor vs. employee analysis... + +## Practical Takeaways + +1. Review client policies for remote work compliance +2. Update classification analyses in light of new standards +3. Consider multi-jurisdictional implications +`, + practiceArea: "employment-law", + author: { + name: "Jane Smith", + title: "Partner", + firm: "Employment Law Group", + }, + publishedAt: "2025-03-15T10:00:00Z", + readTimeMinutes: 8, + featured: true, + tags: ["remote work", "worker classification", "compliance"], + }, + { + id: "la-002", + title: "Intellectual Property Considerations in AI-Generated Content", + excerpt: + "As AI tools become ubiquitous in content creation, practitioners must navigate unsettled questions around copyrightability and infringement.", + content: ` +## The Current State of AI and Copyright + +The Copyright Office has issued guidance on AI-generated works... + +## Key Cases to Watch + +Several pending cases will shape the future of AI copyright law... + +## Recommendations for Clients + +Practitioners should advise clients on protective measures... +`, + practiceArea: "intellectual-property", + author: { + name: "Michael Chen", + title: "IP Practice Chair", + }, + publishedAt: "2025-03-10T14:30:00Z", + readTimeMinutes: 12, + featured: true, + tags: ["AI", "copyright", "technology"], + }, + { + id: "la-003", + title: "Healthcare Regulatory Update: Telehealth Compliance After the Public Health Emergency", + excerpt: + "With the end of the public health emergency, healthcare providers face new compliance requirements for telehealth services.", + content: ` +## Regulatory Changes + +The expiration of pandemic-era waivers has significant implications... + +## State-by-State Variations + +Telehealth licensing requirements vary significantly... + +## Compliance Checklist + +Providers should ensure they meet all applicable requirements... +`, + practiceArea: "healthcare-law", + author: { + name: "Sarah Johnson", + title: "Healthcare Regulatory Counsel", + }, + publishedAt: "2025-03-01T09:00:00Z", + readTimeMinutes: 10, + tags: ["telehealth", "compliance", "licensing"], + }, + { + id: "la-004", + title: "Corporate Governance Trends in 2025", + excerpt: + "ESG considerations, board diversity, and stakeholder governance continue to reshape corporate law practice.", + content: ` +## ESG Disclosure Requirements + +New SEC rules have transformed the ESG disclosure landscape... + +## Board Composition and Oversight + +Courts continue to emphasize the importance of board oversight... + +## Stakeholder Governance Models + +The debate between shareholder and stakeholder primacy evolves... +`, + practiceArea: "corporate-law", + author: { + name: "Robert Williams", + title: "Corporate Governance Partner", + }, + publishedAt: "2025-02-20T11:00:00Z", + readTimeMinutes: 15, + featured: true, + tags: ["ESG", "board governance", "SEC"], + }, + { + id: "la-005", + title: "Data Privacy Enforcement: Lessons from Recent Actions", + excerpt: + "Analysis of recent enforcement actions provides insights into regulator priorities and compliance expectations.", + content: ` +## FTC Enforcement Priorities + +Recent FTC actions reveal key focus areas... + +## State Privacy Laws + +The patchwork of state laws creates compliance challenges... + +## Best Practices + +Organizations should implement comprehensive privacy programs... +`, + practiceArea: "technology-law", + author: { + name: "Emily Davis", + title: "Privacy Practice Lead", + }, + publishedAt: "2025-02-15T16:00:00Z", + readTimeMinutes: 9, + tags: ["privacy", "FTC", "data protection"], + }, +]; + +/** + * Get legal analyses with optional filtering + */ +export function getLegalAnalyses(filters?: { + practiceArea?: string; + featured?: boolean; + searchQuery?: string; +}): LegalAnalysis[] { + let results = [...legalAnalyses]; + + if (filters?.practiceArea) { + results = results.filter((la) => la.practiceArea === filters.practiceArea); + } + + if (filters?.featured) { + results = results.filter((la) => la.featured); + } + + if (filters?.searchQuery) { + const query = filters.searchQuery.toLowerCase(); + results = results.filter( + (la) => + la.title.toLowerCase().includes(query) || + la.excerpt.toLowerCase().includes(query) || + la.tags?.some((tag) => tag.toLowerCase().includes(query)), + ); + } + + // Sort by published date (newest first) + return results.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()); +} + +/** + * Get a single legal analysis by ID + */ +export function getLegalAnalysisById(id: string): LegalAnalysis | undefined { + return legalAnalyses.find((la) => la.id === id); +} + +/** + * Get all unique practice areas that have content + */ +export function getPracticeAreasWithContent(): string[] { + return [...new Set(legalAnalyses.map((la) => la.practiceArea))]; +} diff --git a/lib/legal-analysis/types.ts b/lib/legal-analysis/types.ts new file mode 100644 index 00000000..615801e4 --- /dev/null +++ b/lib/legal-analysis/types.ts @@ -0,0 +1,113 @@ +import { z } from "zod"; + +/** + * Practice areas for legal content organization + * Based on standard CLE categorization + */ +export const practiceAreaSchema = z.enum([ + "administrative-law", + "alternative-dispute-resolution", + "antitrust", + "appellate-practice", + "bankruptcy", + "business-law", + "civil-rights", + "construction-law", + "consumer-protection", + "corporate-law", + "criminal-law", + "employment-law", + "environmental-law", + "estate-planning", + "family-law", + "healthcare-law", + "immigration-law", + "insurance-law", + "intellectual-property", + "international-law", + "labor-law", + "litigation", + "municipal-law", + "personal-injury", + "product-liability", + "professional-responsibility", + "real-estate", + "securities-law", + "tax-law", + "technology-law", + "trusts-estates", + "workers-compensation", +]); + +export type PracticeArea = z.infer; + +export const PRACTICE_AREA_LABELS: Record = { + "administrative-law": "Administrative Law", + "alternative-dispute-resolution": "Alternative Dispute Resolution", + antitrust: "Antitrust", + "appellate-practice": "Appellate Practice", + bankruptcy: "Bankruptcy", + "business-law": "Business Law", + "civil-rights": "Civil Rights", + "construction-law": "Construction Law", + "consumer-protection": "Consumer Protection", + "corporate-law": "Corporate Law", + "criminal-law": "Criminal Law", + "employment-law": "Employment Law", + "environmental-law": "Environmental Law", + "estate-planning": "Estate Planning", + "family-law": "Family Law", + "healthcare-law": "Healthcare Law", + "immigration-law": "Immigration Law", + "insurance-law": "Insurance Law", + "intellectual-property": "Intellectual Property", + "international-law": "International Law", + "labor-law": "Labor Law", + litigation: "Litigation", + "municipal-law": "Municipal Law", + "personal-injury": "Personal Injury", + "product-liability": "Product Liability", + "professional-responsibility": "Professional Responsibility", + "real-estate": "Real Estate", + "securities-law": "Securities Law", + "tax-law": "Tax Law", + "technology-law": "Technology Law", + "trusts-estates": "Trusts & Estates", + "workers-compensation": "Workers' Compensation", +}; + +/** + * Legal analysis content schema + * Represents editorial thought pieces and analysis + */ +export const legalAnalysisSchema = z.object({ + id: z.string(), + title: z.string(), + excerpt: z.string(), // Short summary for cards + content: z.string(), // Full markdown/HTML content + practiceArea: practiceAreaSchema, + author: z.object({ + name: z.string(), + title: z.string().optional(), + firm: z.string().optional(), + imageUrl: z.string().optional(), + }), + publishedAt: z.string().datetime(), + updatedAt: z.string().datetime().optional(), + readTimeMinutes: z.number().int().positive(), + tags: z.array(z.string()).optional(), + relatedClassIds: z.array(z.string()).optional(), // Links to CLE classes + featured: z.boolean().default(false), +}); + +export type LegalAnalysis = z.infer; + +/** + * Filter options for legal analysis content + */ +export interface LegalAnalysisFilters { + practiceArea?: PracticeArea; + searchQuery?: string; + featured?: boolean; + tags?: string[]; +}