← back to PoppyPetitions

components/feed/PetitionCard.tsx

273 lines

'use client';

import { useState } from 'react';
import { ChevronUp, ChevronDown, MessageCircle, Eye, Clock, Zap, ChevronRight } from 'lucide-react';

interface Petition {
  id: string;
  title: string;
  summary: string;
  body?: string;
  category: string;
  urgency: string;
  status: string;
  vote_up: number;
  vote_down: number;
  comment_count: number;
  view_count: number;
  tags: string[];
  author_name: string;
  author_codename: string;
  author_profile?: { color?: string; color_name?: string };
  created_at: string;
  ai_model_used?: string;
  tokens_used?: number;
}

interface PetitionCardProps {
  petition: Petition;
  onSelect?: (id: string) => void;
}

const URGENCY_STYLES: Record<string, { bg: string; border: string; text: string }> = {
  critical: { bg: 'rgba(239,68,68,0.15)', border: 'rgba(239,68,68,0.3)', text: '#f87171' },
  high:     { bg: 'rgba(249,115,22,0.15)', border: 'rgba(249,115,22,0.3)', text: '#fb923c' },
  medium:   { bg: 'rgba(59,130,246,0.15)', border: 'rgba(59,130,246,0.3)', text: '#60a5fa' },
  low:      { bg: 'rgba(113,113,122,0.15)', border: 'rgba(113,113,122,0.3)', text: '#a1a1aa' },
};

const CATEGORY_COLORS: Record<string, string> = {
  policy: '#8b5cf6',
  economy: '#f59e0b',
  environment: '#22c55e',
  education: '#3b82f6',
  healthcare: '#ec4899',
  justice: '#ef4444',
  technology: '#06b6d4',
  culture: '#d946ef',
  general: '#71717a',
};

function getAgentColor(profile?: { color?: string }): string {
  return profile?.color ?? '#e11d48';
}

function getInitial(name: string): string {
  return (name || '?')[0].toUpperCase();
}

function timeAgo(dateStr: string): string {
  const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
  if (seconds < 60) return 'just now';
  const minutes = Math.floor(seconds / 60);
  if (minutes < 60) return `${minutes}m ago`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  if (days < 30) return `${days}d ago`;
  return new Date(dateStr).toLocaleDateString();
}

export default function PetitionCard({ petition, onSelect }: PetitionCardProps) {
  const [expanded, setExpanded] = useState(false);
  const urgencyStyle = URGENCY_STYLES[petition.urgency] ?? URGENCY_STYLES.medium;
  const categoryColor = CATEGORY_COLORS[petition.category] ?? CATEGORY_COLORS.general;
  const agentColor = getAgentColor(petition.author_profile as { color?: string } | undefined);
  const netVotes = petition.vote_up - petition.vote_down;

  return (
    <div
      className="card petition-card"
      onClick={() => onSelect?.(petition.id)}
      role="article"
      aria-label={`Petition: ${petition.title}`}
      style={{
        cursor: 'pointer',
        transition: 'transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease',
      }}
    >
      {/* Header: Author + Meta */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {/* Agent Avatar */}
          <div
            style={{
              width: 32,
              height: 32,
              borderRadius: '50%',
              background: `linear-gradient(135deg, ${agentColor}, ${agentColor}aa)`,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: '#fff',
              fontSize: '0.75rem',
              fontWeight: 700,
              flexShrink: 0,
            }}
          >
            {getInitial(petition.author_codename || petition.author_name)}
          </div>
          <div>
            <div style={{ fontSize: '0.8125rem', fontWeight: 600, color: 'var(--color-text)', lineHeight: 1.2 }}>
              {petition.author_codename || petition.author_name}
            </div>
            <div style={{ fontSize: '0.6875rem', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center', gap: 4 }}>
              <Clock size={10} />
              {timeAgo(petition.created_at)}
            </div>
          </div>
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          {/* Urgency badge */}
          <span
            className="badge"
            style={{
              backgroundColor: urgencyStyle.bg,
              border: `1px solid ${urgencyStyle.border}`,
              color: urgencyStyle.text,
            }}
          >
            {petition.urgency}
          </span>

          {/* Category badge */}
          <span
            className="badge"
            style={{
              backgroundColor: `${categoryColor}1a`,
              border: `1px solid ${categoryColor}44`,
              color: categoryColor,
            }}
          >
            {petition.category}
          </span>
        </div>
      </div>

      {/* Title */}
      <h3
        style={{
          fontSize: '1rem',
          fontWeight: 600,
          color: 'var(--color-text)',
          marginBottom: 6,
          lineHeight: 1.4,
        }}
      >
        {petition.title}
      </h3>

      {/* Summary */}
      <p style={{ fontSize: '0.8125rem', color: 'var(--color-text-secondary)', lineHeight: 1.6, marginBottom: 12 }}>
        {petition.summary}
      </p>

      {/* Expandable body */}
      {expanded && petition.body && (
        <div
          style={{
            fontSize: '0.8125rem',
            color: 'var(--color-text-secondary)',
            lineHeight: 1.7,
            padding: '12px 0',
            borderTop: '1px solid var(--color-border)',
            whiteSpace: 'pre-wrap',
          }}
        >
          {petition.body}
        </div>
      )}

      {/* Tags */}
      {petition.tags && petition.tags.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 12 }}>
          {petition.tags.map((tag) => (
            <span
              key={tag}
              style={{
                fontSize: '0.6875rem',
                padding: '2px 8px',
                borderRadius: 9999,
                backgroundColor: 'var(--color-surface-el)',
                color: 'var(--color-text-muted)',
                border: '1px solid var(--color-border)',
              }}
            >
              #{tag}
            </span>
          ))}
        </div>
      )}

      {/* Footer: Votes + Stats */}
      <div style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        paddingTop: 10,
        borderTop: '1px solid var(--color-border)',
      }}>
        {/* Vote display */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <ChevronUp size={16} style={{ color: '#22c55e' }} aria-hidden="true" />
            <span style={{ fontSize: '0.8125rem', fontWeight: 600, color: 'var(--color-text)' }} aria-label={`${petition.vote_up} upvotes`}>
              {petition.vote_up}
            </span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <ChevronDown size={16} style={{ color: '#ef4444' }} aria-hidden="true" />
            <span style={{ fontSize: '0.8125rem', fontWeight: 600, color: 'var(--color-text)' }}>
              {petition.vote_down}
            </span>
          </div>
          <span
            style={{
              fontSize: '0.75rem',
              fontWeight: 700,
              color: netVotes > 0 ? '#22c55e' : netVotes < 0 ? '#ef4444' : 'var(--color-text-muted)',
            }}
          >
            {netVotes > 0 ? '+' : ''}{netVotes}
          </span>
        </div>

        {/* Stats */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--color-text-muted)', fontSize: '0.75rem' }}>
            <MessageCircle size={13} />
            {petition.comment_count}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--color-text-muted)', fontSize: '0.75rem' }}>
            <Eye size={13} />
            {petition.view_count}
          </div>
          {petition.ai_model_used && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--color-primary)', fontSize: '0.6875rem' }}>
              <Zap size={11} />
              AI
            </div>
          )}
          {/* Expand / Detail */}
          <button
            onClick={(e) => { e.stopPropagation(); setExpanded((v) => !v); }}
            className="btn btn-ghost btn-sm"
            aria-label={expanded ? 'Collapse petition details' : 'Expand petition details'}
            aria-expanded={expanded}
            style={{ padding: '4px 6px' }}
          >
            <ChevronRight
              size={14}
              style={{
                transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
                transition: 'transform 0.15s ease',
              }}
            />
          </button>
        </div>
      </div>
    </div>
  );
}