← back to Norma

components/agents/AgentAppsHub.tsx

230 lines

'use client';

import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import {
  Bot, ScrollText, Search, Target, UserPlus, ChevronDown, Building2, Sparkles,
} from 'lucide-react';

interface Organization {
  id: string;
  name: string;
  city: string | null;
  state: string | null;
  type: string;
}

interface AgentAppsHubProps {
  onNavigate: (tab: string) => void;
  selectedOrgId: string;
  onOrgChange: (id: string) => void;
}

const TOOLS = [
  {
    id: 'agent-onboard',
    title: 'Organization Onboarding',
    description: 'Register your nonprofit organization to unlock the full advocacy agent toolkit. Set up your mission, key issues, and contact information.',
    Icon: UserPlus,
    color: '#22c55e',
    bg: 'rgba(34,197,94,0.12)',
    borderColor: 'rgba(34,197,94,0.25)',
  },
  {
    id: 'agent-petition',
    title: 'Petition Builder',
    description: 'AI-assisted petition creation targeting Congress members, state legislatures, federal agencies, and institutions. Generate drafts, talking points, and signature goals.',
    Icon: ScrollText,
    color: '#6366f1',
    bg: 'rgba(99,102,241,0.12)',
    borderColor: 'rgba(99,102,241,0.25)',
  },
  {
    id: 'agent-grants',
    title: 'Grant Finder',
    description: 'AI-powered grant matching that scores federal, state, foundation, and corporate grants against your organization\'s mission and focus areas.',
    Icon: Search,
    color: '#f59e0b',
    bg: 'rgba(245,158,11,0.12)',
    borderColor: 'rgba(245,158,11,0.25)',
  },
  {
    id: 'agent-advocacy',
    title: 'Advocacy Toolkit',
    description: 'Generate complete advocacy campaigns with talking points, email templates, social media kits, and call scripts for grassroots organizing.',
    Icon: Target,
    color: '#8b5cf6',
    bg: 'rgba(139,92,246,0.12)',
    borderColor: 'rgba(139,92,246,0.25)',
  },
];

export default function AgentAppsHub({ onNavigate, selectedOrgId, onOrgChange }: AgentAppsHubProps) {
  const [orgs, setOrgs] = useState<Organization[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      try {
        const res = await fetch('/api/orgs?limit=200&sort=name&sort_dir=asc');
        if (res.ok) {
          const data = await res.json();
          setOrgs(data.rows || []);
        }
      } catch {
        // silently fail
      }
      setLoading(false);
    })();
  }, []);

  return (
    <div style={{ padding: 24, maxWidth: 1100, margin: '0 auto' }}>
      {/* Header */}
      <div style={{ textAlign: 'center', marginBottom: 32 }}>
        <div style={{
          width: 56, height: 56, borderRadius: 16, margin: '0 auto 16px',
          background: 'linear-gradient(135deg, #10b981, #7c3aed)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          boxShadow: '0 4px 20px rgba(16,185,129,0.3)',
        }}>
          <Bot size={28} style={{ color: '#fff' }} />
        </div>
        <h1 style={{ fontSize: 28, fontWeight: 700, color: 'var(--color-text)', margin: '0 0 8px' }}>
          Norma Advocacy Agent Apps
        </h1>
        <p style={{ fontSize: 15, color: 'var(--color-text-secondary)', maxWidth: 600, margin: '0 auto' }}>
          AI-powered tools for nonprofit organizations. Select your organization to get started.
        </p>
      </div>

      {/* Organization Selector */}
      <div className="card" style={{
        marginBottom: 28, padding: 16,
        display: 'flex', alignItems: 'center', gap: 12,
        flexWrap: 'wrap',
      }}>
        <Building2 size={18} style={{ color: 'var(--color-primary)', flexShrink: 0 }} />
        <label style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text)', flexShrink: 0 }}>
          Active Organization:
        </label>
        <div style={{ position: 'relative', flex: 1, minWidth: 240 }}>
          <select
            className="input"
            value={selectedOrgId}
            onChange={e => onOrgChange(e.target.value)}
            aria-label="Active organization"
            style={{ paddingRight: 32, cursor: 'pointer' }}
            disabled={loading}
          >
            <option value="">-- Select an organization --</option>
            {orgs.map(o => (
              <option key={o.id} value={o.id}>
                {o.name}{o.city ? ` (${o.city}, ${o.state || ''})` : ''}
              </option>
            ))}
          </select>
          <ChevronDown size={14} style={{
            position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)',
            color: 'var(--color-text-muted)', pointerEvents: 'none',
          }} />
        </div>
        {!selectedOrgId && (
          <button
            className="btn btn-primary btn-sm"
            onClick={() => onNavigate('agent-onboard')}
          >
            <UserPlus size={14} />
            Register New Org
          </button>
        )}
      </div>

      {/* Tools Grid */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
        gap: 20,
      }}>
        {TOOLS.map((tool, idx) => (
          <motion.div
            key={tool.id}
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ delay: idx * 0.08, duration: 0.3 }}
          >
            <div
              className="card"
              style={{
                padding: 24,
                cursor: 'pointer',
                borderColor: 'var(--color-border)',
                transition: 'border-color 0.2s, box-shadow 0.2s, transform 0.2s',
                height: '100%',
                display: 'flex',
                flexDirection: 'column',
              }}
              onMouseEnter={e => {
                (e.currentTarget as HTMLElement).style.borderColor = tool.borderColor;
                (e.currentTarget as HTMLElement).style.boxShadow = `0 4px 20px ${tool.bg}`;
                (e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)';
              }}
              onMouseLeave={e => {
                (e.currentTarget as HTMLElement).style.borderColor = 'var(--color-border)';
                (e.currentTarget as HTMLElement).style.boxShadow = 'none';
                (e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
              }}
              onClick={() => onNavigate(tool.id)}
            >
              <div style={{
                width: 44, height: 44, borderRadius: 12,
                backgroundColor: tool.bg,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                marginBottom: 16,
              }}>
                <tool.Icon size={22} style={{ color: tool.color }} />
              </div>
              <h3 style={{ fontSize: 16, fontWeight: 600, color: 'var(--color-text)', margin: '0 0 8px' }}>
                {tool.title}
              </h3>
              <p style={{
                fontSize: 13, color: 'var(--color-text-secondary)',
                lineHeight: 1.6, margin: '0 0 20px', flex: 1,
              }}>
                {tool.description}
              </p>
              <button className="btn btn-primary btn-sm" style={{ alignSelf: 'flex-start' }}>
                <Sparkles size={14} />
                Launch
              </button>
            </div>
          </motion.div>
        ))}
      </div>

      {/* Stats footer */}
      <div style={{
        marginTop: 32, padding: '16px 20px', borderRadius: 12,
        backgroundColor: 'var(--color-surface-el)',
        border: '1px solid var(--color-border)',
        display: 'flex', justifyContent: 'center', gap: 40, flexWrap: 'wrap',
      }}>
        {[
          { label: 'Organizations', value: orgs.length.toString() },
          { label: 'Agent Tools', value: '4' },
          { label: 'AI Model', value: 'Gemini 2.0 Flash' },
        ].map(stat => (
          <div key={stat.label} style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 20, fontWeight: 700, color: 'var(--color-primary)' }}>
              {stat.value}
            </div>
            <div style={{ fontSize: 11, color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
              {stat.label}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}