← back to Norma

app/pulse/petition/[id]/page.tsx

629 lines

'use client';

import React, { useEffect, useState, use } from 'react';
import Link from 'next/link';
import {
  Target, Calendar, CheckCircle2, Users, Share2, Twitter,
  Facebook, LinkIcon, ArrowLeft, Loader2, AlertCircle, Tag,
  ChevronRight, Heart,
} from 'lucide-react';

/* ────────────────────────── Types ────────────────────────── */

interface Petition {
  id: string;
  title: string;
  body: string | null;
  description: string | null;
  target: string | null;
  category: string | null;
  tags: string[] | null;
  signature_count: number | null;
  signature_goal: number | null;
  is_featured: boolean;
  talking_points: string[] | null;
  created_at: string;
  updated_at: string;
}

interface RelatedPetition {
  id: string;
  title: string;
  body: string | null;
  category: string | null;
  signature_count: number | null;
  signature_goal: number | null;
  is_featured: boolean;
}

/* ────────────────────────── Colors ────────────────────────── */

const C = {
  darkGreen: '#1B4332',
  gold: '#DAA520',
  cream: '#FAFAF5',
  text: '#1a1a1a',
  textLight: '#fafafa',
  textMuted: '#6b7280',
  border: '#e5e7eb',
  success: '#059669',
  error: '#dc2626',
};

/* ────────────────────────── Helpers ────────────────────────── */

function categoryLabel(cat: string | null): string {
  if (!cat) return 'General';
  return cat.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}

function categoryColor(cat: string | null): string {
  const colors: Record<string, string> = {
    debt_cancellation: '#dc2626', consumer_protection: '#6366f1',
    voting_rights: '#7c3aed', legal: '#d97706', trending: '#059669',
  };
  return colors[cat || ''] || '#6b7280';
}

function formatNumber(n: number | null): string {
  if (n === null || n === undefined) return '0';
  if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
  if (n >= 1000) return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}K`;
  return n.toLocaleString();
}

function formatDate(dateStr: string): string {
  return new Date(dateStr).toLocaleDateString('en-US', {
    year: 'numeric', month: 'long', day: 'numeric',
  });
}

function progressPercent(count: number | null, goal: number | null): number {
  if (!goal || goal <= 0) return 0;
  return Math.min(100, Math.round(((count || 0) / goal) * 100));
}

/* ────────────────────────── Page Component ────────────────────────── */

export default function PetitionDetailPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = use(params);

  const [petition, setPetition] = useState<Petition | null>(null);
  const [related, setRelated] = useState<RelatedPetition[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  // Sign form state
  const [showSignForm, setShowSignForm] = useState(false);
  const [signName, setSignName] = useState('');
  const [signEmail, setSignEmail] = useState('');
  const [signZip, setSignZip] = useState('');
  const [signing, setSigning] = useState(false);
  const [signResult, setSignResult] = useState<{ success: boolean; message: string } | null>(null);

  // Share
  const [copied, setCopied] = useState(false);

  useEffect(() => {
    async function load() {
      try {
        const res = await fetch(`/api/pulse/petitions/${id}`);
        if (!res.ok) {
          if (res.status === 404) setError('Petition not found');
          else setError('Failed to load petition');
          return;
        }
        const data = await res.json();
        setPetition(data.petition);
        setRelated(data.related || []);
      } catch (err) {
        setError('Failed to load petition');
      } finally {
        setLoading(false);
      }
    }
    load();
  }, [id]);

  const handleSign = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!signName.trim() || !signEmail.trim()) return;

    setSigning(true);
    setSignResult(null);
    try {
      const res = await fetch(`/api/pulse/petitions/${id}/sign`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: signName.trim(),
          email: signEmail.trim(),
          zip_code: signZip.trim() || undefined,
        }),
      });

      const data = await res.json();
      setSignResult({ success: data.success !== false, message: data.message || 'Thank you!' });

      if (data.success && petition) {
        setPetition({ ...petition, signature_count: data.totalSignatures });
      }
    } catch {
      setSignResult({ success: false, message: 'Something went wrong. Please try again.' });
    } finally {
      setSigning(false);
    }
  };

  const shareUrl = typeof window !== 'undefined' ? window.location.href : '';

  const handleCopyLink = () => {
    navigator.clipboard.writeText(shareUrl).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };

  // ─── Loading state ───
  if (loading) {
    return (
      <div style={{ maxWidth: 900, margin: '0 auto', padding: '60px 24px' }}>
        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 300 }}>
          <Loader2 size={32} style={{ color: C.gold, animation: 'spin 1s linear infinite' }} />
        </div>
        <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
      </div>
    );
  }

  // ─── Error state ───
  if (error || !petition) {
    return (
      <div style={{ maxWidth: 600, margin: '0 auto', padding: '80px 24px', textAlign: 'center' }}>
        <AlertCircle size={48} style={{ color: C.error, marginBottom: 16 }} />
        <h2 style={{ fontSize: 24, color: C.text, margin: '0 0 12px', fontFamily: 'Georgia, serif' }}>
          {error || 'Petition not found'}
        </h2>
        <p style={{ fontSize: 15, color: C.textMuted, margin: '0 0 24px', fontFamily: 'system-ui, sans-serif' }}>
          This petition may have been removed or is no longer active.
        </p>
        <Link
          href="/pulse/petitions"
          style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            color: C.darkGreen, textDecoration: 'none', fontSize: 15, fontWeight: 600,
            fontFamily: 'system-ui, sans-serif',
          }}
        >
          <ArrowLeft size={16} />
          Back to all petitions
        </Link>
      </div>
    );
  }

  const pct = progressPercent(petition.signature_count, petition.signature_goal);
  const bodyText = petition.body || petition.description || '';

  return (
    <div style={{ maxWidth: 1200, margin: '0 auto', padding: '32px 24px 64px' }}>
      {/* Back link */}
      <Link
        href="/pulse/petitions"
        style={{
          display: 'inline-flex', alignItems: 'center', gap: 6,
          color: C.textMuted, textDecoration: 'none', fontSize: 14, fontWeight: 500,
          fontFamily: 'system-ui, sans-serif', marginBottom: 24,
        }}
      >
        <ArrowLeft size={16} />
        All petitions
      </Link>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 32 }} className="poa-detail-grid">
        {/* ─── Main content ─── */}
        <div style={{ gridColumn: '1', minWidth: 0 }}>
          {/* Category + date */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
            <span style={{
              fontSize: 12, fontWeight: 600, fontFamily: 'system-ui, sans-serif',
              textTransform: 'uppercase', letterSpacing: '0.5px',
              padding: '4px 12px', borderRadius: 9999,
              backgroundColor: `${categoryColor(petition.category)}12`,
              color: categoryColor(petition.category),
            }}>
              {categoryLabel(petition.category)}
            </span>
            <span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 13, color: C.textMuted, fontFamily: 'system-ui, sans-serif' }}>
              <Calendar size={14} />
              {formatDate(petition.created_at)}
            </span>
          </div>

          {/* Title */}
          <h1 style={{
            fontSize: 'clamp(28px, 4vw, 44px)', fontWeight: 'bold', lineHeight: 1.2,
            color: C.text, margin: '0 0 16px', fontFamily: 'Georgia, serif',
          }}>
            {petition.title}
          </h1>

          {/* Target */}
          {petition.target && (
            <div style={{
              display: 'inline-flex', alignItems: 'center', gap: 8,
              padding: '10px 18px', borderRadius: 12,
              backgroundColor: '#d1fae5', color: '#065f46',
              marginBottom: 24, fontSize: 14, fontWeight: 600,
              fontFamily: 'system-ui, sans-serif',
            }}>
              <Target size={16} />
              Petition to: {petition.target}
            </div>
          )}

          {/* ─── Progress Bar (large) ─── */}
          <div style={{
            backgroundColor: '#fff', borderRadius: 16, padding: 24, marginBottom: 24,
            border: '1px solid #e5e7eb',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 12 }}>
              <div>
                <span style={{ fontSize: 32, fontWeight: 'bold', color: C.text, fontFamily: 'Georgia, serif' }}>
                  {formatNumber(petition.signature_count)}
                </span>
                <span style={{ fontSize: 16, color: C.textMuted, fontFamily: 'system-ui, sans-serif', marginLeft: 8 }}>
                  signatures
                </span>
              </div>
              {petition.signature_goal && petition.signature_goal > 0 && (
                <span style={{ fontSize: 15, color: C.textMuted, fontFamily: 'system-ui, sans-serif' }}>
                  Goal: {formatNumber(petition.signature_goal)}
                </span>
              )}
            </div>
            <div style={{ height: 12, borderRadius: 6, backgroundColor: '#e5e7eb', overflow: 'hidden' }}>
              <div style={{
                height: '100%', borderRadius: 6, backgroundColor: C.gold,
                width: petition.signature_goal ? `${pct}%` : `${Math.min(100, (petition.signature_count || 0) / 100)}%`,
                minWidth: (petition.signature_count || 0) > 0 ? 12 : 0,
                transition: 'width 500ms ease',
              }} />
            </div>
            {petition.signature_goal && petition.signature_goal > 0 && (
              <div style={{ marginTop: 8, fontSize: 13, color: C.textMuted, fontFamily: 'system-ui, sans-serif' }}>
                {pct}% of goal reached
              </div>
            )}
          </div>

          {/* ─── Sign button (before form) ─── */}
          {!showSignForm && !signResult?.success && (
            <button
              onClick={() => setShowSignForm(true)}
              style={{
                width: '100%', padding: '18px 32px', borderRadius: 16,
                backgroundColor: C.gold, color: C.darkGreen, border: 'none',
                fontSize: 18, fontWeight: 700, fontFamily: 'system-ui, sans-serif',
                cursor: 'pointer', transition: 'transform 150ms, background-color 150ms',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
                marginBottom: 32,
              }}
              onMouseEnter={e => { e.currentTarget.style.transform = 'scale(1.01)'; e.currentTarget.style.backgroundColor = '#c8941a'; }}
              onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.backgroundColor = C.gold; }}
            >
              <Heart size={20} />
              Sign This Petition
            </button>
          )}

          {/* ─── Sign Form ─── */}
          {showSignForm && !signResult?.success && (
            <div style={{
              backgroundColor: '#fff', borderRadius: 16, padding: 28, marginBottom: 32,
              border: `2px solid ${C.gold}`,
            }}>
              <h3 style={{ fontSize: 20, fontWeight: 'bold', color: C.text, margin: '0 0 4px', fontFamily: 'Georgia, serif' }}>
                Sign This Petition
              </h3>
              <p style={{ fontSize: 14, color: C.textMuted, margin: '0 0 20px', fontFamily: 'system-ui, sans-serif' }}>
                Add your name to make your voice heard
              </p>

              <form onSubmit={handleSign}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
                  <div>
                    <label style={{ display: 'block', fontSize: 13, fontWeight: 600, color: C.text, marginBottom: 5, fontFamily: 'system-ui, sans-serif' }}>
                      Full Name *
                    </label>
                    <input
                      type="text"
                      required
                      value={signName}
                      onChange={e => setSignName(e.target.value)}
                      placeholder="Your full name"
                      style={{
                        width: '100%', padding: '12px 16px', borderRadius: 10,
                        border: '1px solid #d1d5db', fontSize: 15, fontFamily: 'system-ui, sans-serif',
                        color: C.text, backgroundColor: C.cream, outline: 'none',
                        boxSizing: 'border-box',
                      }}
                      onFocus={e => (e.target.style.borderColor = C.gold)}
                      onBlur={e => (e.target.style.borderColor = '#d1d5db')}
                    />
                  </div>
                  <div>
                    <label style={{ display: 'block', fontSize: 13, fontWeight: 600, color: C.text, marginBottom: 5, fontFamily: 'system-ui, sans-serif' }}>
                      Email Address *
                    </label>
                    <input
                      type="email"
                      required
                      value={signEmail}
                      onChange={e => setSignEmail(e.target.value)}
                      placeholder="you@example.com"
                      style={{
                        width: '100%', padding: '12px 16px', borderRadius: 10,
                        border: '1px solid #d1d5db', fontSize: 15, fontFamily: 'system-ui, sans-serif',
                        color: C.text, backgroundColor: C.cream, outline: 'none',
                        boxSizing: 'border-box',
                      }}
                      onFocus={e => (e.target.style.borderColor = C.gold)}
                      onBlur={e => (e.target.style.borderColor = '#d1d5db')}
                    />
                  </div>
                  <div>
                    <label style={{ display: 'block', fontSize: 13, fontWeight: 600, color: C.text, marginBottom: 5, fontFamily: 'system-ui, sans-serif' }}>
                      ZIP Code <span style={{ fontWeight: 400, color: C.textMuted }}>(optional)</span>
                    </label>
                    <input
                      type="text"
                      value={signZip}
                      onChange={e => setSignZip(e.target.value)}
                      placeholder="12345"
                      maxLength={10}
                      style={{
                        width: '100%', padding: '12px 16px', borderRadius: 10,
                        border: '1px solid #d1d5db', fontSize: 15, fontFamily: 'system-ui, sans-serif',
                        color: C.text, backgroundColor: C.cream, outline: 'none',
                        boxSizing: 'border-box',
                      }}
                      onFocus={e => (e.target.style.borderColor = C.gold)}
                      onBlur={e => (e.target.style.borderColor = '#d1d5db')}
                    />
                  </div>
                </div>

                {signResult && !signResult.success && (
                  <div style={{
                    marginTop: 14, padding: '10px 14px', borderRadius: 10,
                    backgroundColor: '#fef2f2', color: C.error, fontSize: 14,
                    fontFamily: 'system-ui, sans-serif', display: 'flex', alignItems: 'center', gap: 8,
                  }}>
                    <AlertCircle size={16} />
                    {signResult.message}
                  </div>
                )}

                <button
                  type="submit"
                  disabled={signing}
                  style={{
                    width: '100%', padding: '16px', borderRadius: 12, marginTop: 18,
                    backgroundColor: signing ? '#9ca3af' : C.gold,
                    color: C.darkGreen, border: 'none',
                    fontSize: 16, fontWeight: 700, fontFamily: 'system-ui, sans-serif',
                    cursor: signing ? 'not-allowed' : 'pointer',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                    transition: 'background-color 150ms',
                  }}
                >
                  {signing ? (
                    <>
                      <Loader2 size={18} style={{ animation: 'spin 1s linear infinite' }} />
                      Signing...
                    </>
                  ) : (
                    <>
                      <CheckCircle2 size={18} />
                      Add My Name
                    </>
                  )}
                </button>

                <p style={{ fontSize: 12, color: C.textMuted, margin: '12px 0 0', textAlign: 'center', fontFamily: 'system-ui, sans-serif' }}>
                  By signing, you agree that your name may be displayed publicly as a supporter.
                </p>
              </form>
            </div>
          )}

          {/* ─── Sign Success ─── */}
          {signResult?.success && (
            <div style={{
              backgroundColor: '#ecfdf5', borderRadius: 16, padding: 28, marginBottom: 32,
              border: '2px solid #059669', textAlign: 'center',
            }}>
              <CheckCircle2 size={48} style={{ color: C.success, marginBottom: 12 }} />
              <h3 style={{ fontSize: 22, fontWeight: 'bold', color: '#065f46', margin: '0 0 8px', fontFamily: 'Georgia, serif' }}>
                Thank You for Signing!
              </h3>
              <p style={{ fontSize: 15, color: '#047857', margin: '0 0 16px', fontFamily: 'system-ui, sans-serif' }}>
                Your signature has been recorded. Help spread the word by sharing this petition.
              </p>
            </div>
          )}

          {/* ─── Body ─── */}
          <div style={{
            backgroundColor: '#fff', borderRadius: 16, padding: 28, marginBottom: 24,
            border: '1px solid #e5e7eb',
          }}>
            <div style={{ fontSize: 16, lineHeight: '28px', color: C.text, fontFamily: 'system-ui, sans-serif' }}>
              {bodyText.split('\n').map((paragraph, i) => (
                paragraph.trim() ? (
                  <p key={i} style={{ margin: '0 0 16px' }}>{paragraph}</p>
                ) : null
              ))}
              {!bodyText && (
                <p style={{ color: C.textMuted, fontStyle: 'italic' }}>No detailed description available.</p>
              )}
            </div>
          </div>

          {/* ─── Talking Points ─── */}
          {petition.talking_points && petition.talking_points.length > 0 && (
            <div style={{
              backgroundColor: '#fff', borderRadius: 16, padding: 28, marginBottom: 24,
              border: '1px solid #e5e7eb',
            }}>
              <h3 style={{ fontSize: 20, fontWeight: 'bold', color: C.text, margin: '0 0 16px', fontFamily: 'Georgia, serif' }}>
                Key Points
              </h3>
              <ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
                {petition.talking_points.map((tp, i) => (
                  <li key={i} style={{ display: 'flex', gap: 12, marginBottom: 14, alignItems: 'flex-start' }}>
                    <CheckCircle2
                      size={20}
                      style={{ color: C.gold, flexShrink: 0, marginTop: 2 }}
                    />
                    <span style={{ fontSize: 15, lineHeight: '24px', color: C.text, fontFamily: 'system-ui, sans-serif' }}>
                      {tp}
                    </span>
                  </li>
                ))}
              </ul>
            </div>
          )}

          {/* ─── Tags ─── */}
          {petition.tags && petition.tags.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 24 }}>
              {petition.tags.map(tag => (
                <span key={tag} style={{
                  display: 'inline-flex', alignItems: 'center', gap: 4,
                  padding: '5px 12px', borderRadius: 9999,
                  backgroundColor: '#f3f4f6', color: C.textMuted,
                  fontSize: 13, fontFamily: 'system-ui, sans-serif',
                }}>
                  <Tag size={12} />
                  {tag}
                </span>
              ))}
            </div>
          )}

          {/* ─── Share Buttons ─── */}
          <div style={{
            backgroundColor: '#fff', borderRadius: 16, padding: 24, marginBottom: 24,
            border: '1px solid #e5e7eb',
          }}>
            <h3 style={{ fontSize: 16, fontWeight: 600, color: C.text, margin: '0 0 14px', fontFamily: 'system-ui, sans-serif', display: 'flex', alignItems: 'center', gap: 8 }}>
              <Share2 size={18} />
              Share This Petition
            </h3>
            <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
              <a
                href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(petition.title)}&url=${encodeURIComponent(shareUrl)}`}
                target="_blank"
                rel="noopener noreferrer"
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 8,
                  padding: '10px 20px', borderRadius: 10,
                  backgroundColor: '#1DA1F2', color: '#fff', textDecoration: 'none',
                  fontSize: 14, fontWeight: 600, fontFamily: 'system-ui, sans-serif',
                  transition: 'opacity 150ms',
                }}
                onMouseEnter={e => (e.currentTarget.style.opacity = '0.9')}
                onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
              >
                <Twitter size={16} />
                Twitter
              </a>
              <a
                href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`}
                target="_blank"
                rel="noopener noreferrer"
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 8,
                  padding: '10px 20px', borderRadius: 10,
                  backgroundColor: '#1877F2', color: '#fff', textDecoration: 'none',
                  fontSize: 14, fontWeight: 600, fontFamily: 'system-ui, sans-serif',
                  transition: 'opacity 150ms',
                }}
                onMouseEnter={e => (e.currentTarget.style.opacity = '0.9')}
                onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
              >
                <Facebook size={16} />
                Facebook
              </a>
              <button
                onClick={handleCopyLink}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 8,
                  padding: '10px 20px', borderRadius: 10,
                  backgroundColor: '#f3f4f6', color: C.text, border: '1px solid #d1d5db',
                  fontSize: 14, fontWeight: 600, fontFamily: 'system-ui, sans-serif',
                  cursor: 'pointer', transition: 'all 150ms',
                }}
              >
                <LinkIcon size={16} />
                {copied ? 'Copied!' : 'Copy Link'}
              </button>
            </div>
          </div>

          {/* ─── Related Petitions ─── */}
          {related.length > 0 && (
            <div>
              <h3 style={{ fontSize: 22, fontWeight: 'bold', color: C.text, margin: '0 0 16px', fontFamily: 'Georgia, serif' }}>
                Related Petitions
              </h3>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                {related.map(r => (
                  <Link
                    key={r.id}
                    href={`/pulse/petition/${r.id}`}
                    style={{
                      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                      padding: '16px 20px', borderRadius: 12,
                      backgroundColor: '#fff', border: '1px solid #e5e7eb',
                      textDecoration: 'none', color: 'inherit',
                      transition: 'box-shadow 150ms, transform 150ms',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.08)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
                    onMouseLeave={e => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }}
                  >
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <h4 style={{ fontSize: 15, fontWeight: 600, color: C.text, margin: 0, fontFamily: 'Georgia, serif' }}>
                        {r.title}
                      </h4>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
                        <Users size={13} style={{ color: C.textMuted }} />
                        <span style={{ fontSize: 13, color: C.textMuted, fontFamily: 'system-ui, sans-serif' }}>
                          {formatNumber(r.signature_count)} signatures
                        </span>
                      </div>
                    </div>
                    <ChevronRight size={18} style={{ color: C.textMuted, flexShrink: 0 }} />
                  </Link>
                ))}
              </div>
            </div>
          )}
        </div>
      </div>

      {/* Responsive grid and spinner styles */}
      <style>{`
        @keyframes spin { to { transform: rotate(360deg); } }
        @media (min-width: 900px) {
          .poa-detail-grid {
            grid-template-columns: 1fr 360px !important;
          }
        }
      `}</style>
    </div>
  );
}