← back to Patty

components/templates/TemplatesTab.tsx

396 lines

'use client';

import { useState, useEffect, useCallback } from 'react';
import { SkeletonList } from '../Skeleton';
import {
  FileText, Plus, Star, Loader2, X, Sparkles,
  Tag, Copy, Megaphone,
} from 'lucide-react';

/* ─── Types ─────────────────────────────────────────────────── */
interface Template {
  id: string;
  title: string;
  category: string | null;
  body_html: string;
  body_text: string | null;
  tags: string[] | null;
  usage_count: number;
  is_featured: boolean;
  created_at: string;
}

function stripHtml(html: string): string {
  return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}

/* ─── Component ─────────────────────────────────────────────── */
export default function TemplatesTab() {
  const [templates, setTemplates] = useState<Template[]>([]);
  const [loading, setLoading] = useState(true);
  const [categoryFilter, setCategoryFilter] = useState('all');

  // Create template modal
  const [showCreate, setShowCreate] = useState(false);
  const [createTitle, setCreateTitle] = useState('');
  const [createCategory, setCreateCategory] = useState('policy');
  const [createBody, setCreateBody] = useState('');
  const [createTags, setCreateTags] = useState('');
  const [createFeatured, setCreateFeatured] = useState(false);
  const [createSaving, setCreateSaving] = useState(false);

  // Use template modal (create petition from template)
  const [useTemplate, setUseTemplate] = useState<Template | null>(null);
  const [petTitle, setPetTitle] = useState('');
  const [petTarget, setPetTarget] = useState('');
  const [petGoal, setPetGoal] = useState(100);
  const [petSaving, setPetSaving] = useState(false);

  /* ── Fetch ──────────────────────────────────────────────────── */
  const fetchTemplates = useCallback(async () => {
    setLoading(true);
    try {
      const params = new URLSearchParams();
      if (categoryFilter !== 'all') params.set('category', categoryFilter);
      const res = await fetch(`/api/templates?${params.toString()}`);
      if (res.ok) {
        const data = await res.json();
        setTemplates(data.rows || []);
      }
    } catch (err) {
      console.error('Fetch templates error:', err);
    } finally {
      setLoading(false);
    }
  }, [categoryFilter]);

  useEffect(() => { fetchTemplates(); }, [fetchTemplates]);

  /* ── Create Template ────────────────────────────────────────── */
  async function handleCreate() {
    if (!createTitle.trim() || !createBody.trim()) return;
    setCreateSaving(true);
    try {
      const tags = createTags.split(',').map((t) => t.trim()).filter(Boolean);
      const res = await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: createTitle,
          category: createCategory,
          body_html: `<p>${createBody}</p>`,
          body_text: createBody,
          tags: tags.length > 0 ? tags : null,
          is_featured: createFeatured,
        }),
      });
      if (res.ok) {
        setShowCreate(false);
        setCreateTitle('');
        setCreateBody('');
        setCreateTags('');
        setCreateFeatured(false);
        fetchTemplates();
      }
    } catch (err) {
      console.error('Create template error:', err);
    } finally {
      setCreateSaving(false);
    }
  }

  /* ── Use Template ───────────────────────────────────────────── */
  function openUseTemplate(t: Template) {
    setUseTemplate(t);
    setPetTitle(t.title);
    setPetTarget('');
    setPetGoal(100);
  }

  async function handleUseSave() {
    if (!useTemplate || !petTitle.trim()) return;
    setPetSaving(true);
    try {
      const res = await fetch('/api/petitions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: petTitle,
          body_html: useTemplate.body_html,
          body_text: useTemplate.body_text,
          target: petTarget || null,
          category: useTemplate.category,
          tags: useTemplate.tags,
          signature_goal: petGoal,
          status: 'draft',
        }),
      });
      if (res.ok) {
        setUseTemplate(null);
      }
    } catch (err) {
      console.error('Use template error:', err);
    } finally {
      setPetSaving(false);
    }
  }

  /* ── Helpers ────────────────────────────────────────────────── */
  const categories = ['all', 'debt_relief', 'policy', 'education_access', 'consumer_protection'];

  const categoryLabels: Record<string, string> = {
    all: 'All',
    debt_relief: 'Debt Relief',
    policy: 'Policy',
    education_access: 'Education Access',
    consumer_protection: 'Consumer Protection',
  };

  /* ─── Render ────────────────────────────────────────────────── */
  return (
    <div style={{ padding: '24px' }}>
      {/* Header */}
      <div className="flex items-center justify-between mb-6">
        <div>
          <h2 className="text-lg font-semibold" style={{ color: 'var(--color-text)' }}>Petition Templates</h2>
          <p className="text-sm mt-1" style={{ color: 'var(--color-text-muted)' }}>
            Pre-built petition templates for common advocacy scenarios
          </p>
        </div>
        <button className="btn btn-primary" onClick={() => setShowCreate(true)}>
          <Plus size={16} />
          New Template
        </button>
      </div>

      {/* Category Filters */}
      <div className="flex items-center gap-2 mb-6 flex-wrap">
        {categories.map((c) => (
          <button
            key={c}
            onClick={() => setCategoryFilter(c)}
            className={`btn btn-sm ${categoryFilter === c ? 'btn-primary' : 'btn-ghost'}`}
          >
            {categoryLabels[c] || c}
          </button>
        ))}
      </div>

      {/* Loading */}
      {loading && (
        <SkeletonList count={4} />
      )}

      {/* Empty State */}
      {!loading && templates.length === 0 && (
        <div className="card flex flex-col items-center justify-center py-16" style={{ textAlign: 'center' }}>
          <div
            className="w-16 h-16 rounded-2xl flex items-center justify-center mb-4"
            style={{
              background: 'linear-gradient(135deg, rgba(124, 58, 237, 0.15), rgba(167, 139, 250, 0.1))',
              border: '1px solid rgba(124, 58, 237, 0.2)',
            }}
          >
            <FileText size={28} style={{ color: 'var(--color-primary)' }} />
          </div>
          <h3 className="text-base font-semibold mb-2" style={{ color: 'var(--color-text)' }}>No templates found</h3>
          <p className="text-sm mb-6 max-w-md" style={{ color: 'var(--color-text-muted)' }}>
            Templates help you quickly create petitions. Try a different category filter or create a new template.
          </p>
          <button className="btn btn-primary" onClick={() => setShowCreate(true)}>
            <Plus size={16} /> Create Template
          </button>
        </div>
      )}

      {/* Template Grid */}
      {!loading && templates.length > 0 && (
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
          {templates.map((t) => (
            <div key={t.id} className="card flex flex-col">
              {/* Header */}
              <div className="flex items-start justify-between gap-2 mb-2">
                <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>
                  {t.title}
                </h3>
                {t.is_featured && (
                  <Star size={14} style={{ color: 'var(--color-warning)', fill: 'var(--color-warning)', flexShrink: 0 }} />
                )}
              </div>

              {/* Category + Usage */}
              <div className="flex items-center gap-2 mb-3">
                {t.category && (
                  <span
                    className="badge"
                    style={{
                      backgroundColor: 'rgba(124, 58, 237, 0.1)',
                      color: 'var(--color-secondary)',
                      border: '1px solid rgba(124, 58, 237, 0.2)',
                    }}
                  >
                    {categoryLabels[t.category] || t.category}
                  </span>
                )}
                <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                  Used {t.usage_count} times
                </span>
              </div>

              {/* Body Preview */}
              <p
                className="text-xs mb-3 flex-1"
                style={{
                  color: 'var(--color-text-muted)',
                  display: '-webkit-box',
                  WebkitLineClamp: 3,
                  WebkitBoxOrient: 'vertical',
                  overflow: 'hidden',
                }}
              >
                {stripHtml(t.body_html).slice(0, 180)}
              </p>

              {/* Tags */}
              {t.tags && t.tags.length > 0 && (
                <div className="flex items-center gap-1 mb-3 flex-wrap">
                  {t.tags.map((tag, i) => (
                    <span key={i} className="text-xs" style={{
                      color: 'var(--color-secondary)',
                      padding: '1px 6px',
                      borderRadius: 9999,
                      backgroundColor: 'rgba(124, 58, 237, 0.08)',
                    }}>
                      {tag}
                    </span>
                  ))}
                </div>
              )}

              {/* Use Template Button */}
              <button
                className="btn btn-sm btn-secondary w-full"
                onClick={() => openUseTemplate(t)}
              >
                <Megaphone size={14} /> Use Template
              </button>
            </div>
          ))}
        </div>
      )}

      {/* ─── Create Template Modal ────────────────────────────────── */}
      {showCreate && (
        <div
          style={{
            position: 'fixed', inset: 0, zIndex: 50,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            backgroundColor: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(4px)',
          }}
          onClick={(e) => { if (e.target === e.currentTarget) setShowCreate(false); }}
        >
          <div style={{
            width: '100%', maxWidth: 500, maxHeight: '90vh', overflow: 'auto',
            backgroundColor: 'var(--color-surface)', border: '1px solid var(--color-border)',
            borderRadius: 12, padding: 24,
          }}>
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-base font-semibold" style={{ color: 'var(--color-text)' }}>Create Template</h3>
              <button className="btn btn-ghost btn-sm" onClick={() => setShowCreate(false)}><X size={16} /></button>
            </div>
            <div className="flex flex-col gap-4">
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Title *</label>
                <input className="input" value={createTitle} onChange={(e) => setCreateTitle(e.target.value)} placeholder="Template title" />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Category</label>
                <select className="input" value={createCategory} onChange={(e) => setCreateCategory(e.target.value)}>
                  <option value="debt_relief">Debt Relief</option>
                  <option value="policy">Policy</option>
                  <option value="education_access">Education Access</option>
                  <option value="consumer_protection">Consumer Protection</option>
                </select>
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Body *</label>
                <textarea
                  className="input"
                  rows={6}
                  value={createBody}
                  onChange={(e) => setCreateBody(e.target.value)}
                  placeholder="Petition template body. Use [TARGET] as placeholder for the petition target."
                  style={{ resize: 'vertical' }}
                />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Tags (comma separated)</label>
                <input className="input" value={createTags} onChange={(e) => setCreateTags(e.target.value)} placeholder="student loans, policy, reform" />
              </div>
              <div className="flex items-center gap-2">
                <input
                  type="checkbox"
                  checked={createFeatured}
                  onChange={(e) => setCreateFeatured(e.target.checked)}
                  style={{ accentColor: 'var(--color-primary)' }}
                  id="featured-check"
                />
                <label htmlFor="featured-check" className="text-xs" style={{ color: 'var(--color-text-secondary)' }}>Featured template</label>
              </div>
              <button className="btn btn-primary w-full" onClick={handleCreate} disabled={createSaving || !createTitle.trim() || !createBody.trim()}>
                {createSaving ? <><Loader2 size={16} className="animate-spin" /> Creating...</> : <><Plus size={16} /> Create Template</>}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* ─── Use Template Modal ───────────────────────────────────── */}
      {useTemplate && (
        <div
          style={{
            position: 'fixed', inset: 0, zIndex: 50,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            backgroundColor: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(4px)',
          }}
          onClick={(e) => { if (e.target === e.currentTarget) setUseTemplate(null); }}
        >
          <div style={{
            width: '100%', maxWidth: 500, maxHeight: '90vh', overflow: 'auto',
            backgroundColor: 'var(--color-surface)', border: '1px solid var(--color-border)',
            borderRadius: 12, padding: 24,
          }}>
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-base font-semibold" style={{ color: 'var(--color-text)' }}>Create Petition from Template</h3>
              <button className="btn btn-ghost btn-sm" onClick={() => setUseTemplate(null)}><X size={16} /></button>
            </div>
            <div className="flex flex-col gap-4">
              <div className="text-xs" style={{
                color: 'var(--color-text-muted)', padding: 8, borderRadius: 6,
                backgroundColor: 'var(--color-surface-el)', whiteSpace: 'pre-wrap',
              }}>
                {stripHtml(useTemplate.body_html).slice(0, 300)}...
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Petition Title *</label>
                <input className="input" value={petTitle} onChange={(e) => setPetTitle(e.target.value)} />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Target</label>
                <input className="input" value={petTarget} onChange={(e) => setPetTarget(e.target.value)} placeholder="e.g., Congress" />
              </div>
              <div>
                <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Signature Goal</label>
                <input className="input" type="number" value={petGoal} onChange={(e) => setPetGoal(Number(e.target.value))} />
              </div>
              <button className="btn btn-primary w-full" onClick={handleUseSave} disabled={petSaving || !petTitle.trim()}>
                {petSaving ? <><Loader2 size={16} className="animate-spin" /> Creating...</> : <><Megaphone size={16} /> Create Petition Draft</>}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}