← back to Patty

components/settings/SettingsTab.tsx

414 lines

'use client';

import { useState, useEffect } from 'react';
import {
  Save, RefreshCw, Building2, Mail, Sparkles, Database,
  Globe, FileText, Clock, Shield, Download, Megaphone,
  Users, Target, Settings, Share2,
} from 'lucide-react';
import SocialCredentialsSection from './SocialCredentialsSection';

/* ─── Types ──────────────────────────────────────────────────────────────── */
interface OrgProfile {
  name: string;
  website: string;
  email: string;
  mission: string;
}

interface PetitionDefaults {
  default_signature_goal: number;
  default_distribution: string;
  auto_compliance_check: boolean;
}

interface CampaignSettings {
  default_send_time: string;
  email_footer: string;
  unsubscribe_text: string;
}

interface AIConfig {
  model: string;
  creativity_level: number;
  default_tone: string;
}

interface DataStats {
  total_petitions: number;
  total_signatures: number;
  total_subscribers: number;
  total_campaigns: number;
  last_backup: string;
}

interface SettingsData {
  org_profile: OrgProfile;
  petition_defaults: PetitionDefaults;
  campaign_settings: CampaignSettings;
  ai_config: AIConfig;
  data_stats: DataStats;
}

/* ─── Defaults ───────────────────────────────────────────────────────────── */
const DEFAULTS: SettingsData = {
  org_profile: {
    name: 'Student Debt Crisis Center',
    website: 'https://studentdebtcrisis.org',
    email: 'info@studentdebtcrisis.org',
    mission: 'Empowering borrowers through organized petition campaigns and advocacy for systemic student debt reform.',
  },
  petition_defaults: {
    default_signature_goal: 10000,
    default_distribution: 'email',
    auto_compliance_check: true,
  },
  campaign_settings: {
    default_send_time: '09:00',
    email_footer: 'Student Debt Crisis Center | studentdebtcrisis.org',
    unsubscribe_text: 'Click here to unsubscribe from future petition updates.',
  },
  ai_config: {
    model: 'Gemini Flash',
    creativity_level: 60,
    default_tone: 'passionate',
  },
  data_stats: {
    total_petitions: 0,
    total_signatures: 0,
    total_subscribers: 0,
    total_campaigns: 0,
    last_backup: new Date().toISOString(),
  },
};

/* ─── Main Component ─────────────────────────────────────────────────────── */
export default function SettingsTab() {
  const [settings, setSettings] = useState<SettingsData>(DEFAULTS);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [saveMessage, setSaveMessage] = useState('');

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

  async function fetchSettings() {
    try {
      const res = await fetch('/api/settings');
      if (!res.ok) throw new Error('Failed to fetch');
      const data = await res.json();
      const s = data.settings || {};
      setSettings({
        org_profile: { ...DEFAULTS.org_profile, ...(s.org_profile || {}) },
        petition_defaults: { ...DEFAULTS.petition_defaults, ...(s.petition_defaults || {}) },
        campaign_settings: { ...DEFAULTS.campaign_settings, ...(s.campaign_settings || {}) },
        ai_config: { ...DEFAULTS.ai_config, ...(s.ai_config || {}) },
        data_stats: { ...DEFAULTS.data_stats, ...(s.data_stats || {}) },
      });
    } catch (err) {
      console.error('Failed to fetch settings:', err);
    } finally {
      setLoading(false);
    }
  }

  async function handleSave() {
    setSaving(true);
    setSaveMessage('');
    try {
      const res = await fetch('/api/settings', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          org_profile: settings.org_profile,
          petition_defaults: settings.petition_defaults,
          campaign_settings: settings.campaign_settings,
          ai_config: settings.ai_config,
        }),
      });
      if (!res.ok) throw new Error('Failed to save');
      setSaveMessage('Settings saved successfully');
      setTimeout(() => setSaveMessage(''), 3000);
    } catch (err) {
      setSaveMessage('Failed to save settings');
    } finally {
      setSaving(false);
    }
  }

  function updateOrg(field: string, value: string) {
    setSettings((prev) => ({
      ...prev,
      org_profile: { ...prev.org_profile, [field]: value },
    }));
  }

  function updatePetition(field: string, value: string | number | boolean) {
    setSettings((prev) => ({
      ...prev,
      petition_defaults: { ...prev.petition_defaults, [field]: value },
    }));
  }

  function updateCampaign(field: string, value: string) {
    setSettings((prev) => ({
      ...prev,
      campaign_settings: { ...prev.campaign_settings, [field]: value },
    }));
  }

  function updateAI(field: string, value: string | number) {
    setSettings((prev) => ({
      ...prev,
      ai_config: { ...prev.ai_config, [field]: value },
    }));
  }

  if (loading) {
    return (
      <div className="flex items-center justify-center py-20">
        <RefreshCw size={20} className="animate-spin" style={{ color: 'var(--color-text-muted)' }} />
      </div>
    );
  }

  return (
    <div className="p-5 space-y-5 max-w-4xl">
      {/* Save bar */}
      <div className="flex items-center justify-between">
        <h2 className="text-lg font-semibold" style={{ color: 'var(--color-text)' }}>Settings</h2>
        <div className="flex items-center gap-3">
          {saveMessage && (
            <span className="text-sm" style={{ color: saveMessage.includes('success') ? '#34d399' : '#f87171' }}>
              {saveMessage}
            </span>
          )}
          <button className="btn btn-primary" onClick={handleSave} disabled={saving}>
            {saving ? (
              <><RefreshCw size={14} className="animate-spin" /><span>Saving...</span></>
            ) : (
              <><Save size={14} /><span>Save Changes</span></>
            )}
          </button>
        </div>
      </div>

      {/* ── Organization Profile ─────────────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-4">
          <Building2 size={16} style={{ color: '#7c3aed' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>Organization Profile</h3>
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Organization Name</label>
            <input className="input w-full" value={settings.org_profile.name} onChange={(e) => updateOrg('name', e.target.value)} />
          </div>
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Website URL</label>
            <div className="relative">
              <Globe size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2" style={{ color: 'var(--color-text-muted)' }} />
              <input className="input w-full pl-8" value={settings.org_profile.website} onChange={(e) => updateOrg('website', e.target.value)} />
            </div>
          </div>
          <div className="col-span-2">
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Contact Email</label>
            <div className="relative">
              <Mail size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2" style={{ color: 'var(--color-text-muted)' }} />
              <input className="input w-full pl-8" value={settings.org_profile.email} onChange={(e) => updateOrg('email', e.target.value)} />
            </div>
          </div>
        </div>

        <div className="mt-4">
          <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Mission Statement</label>
          <textarea className="input w-full" rows={3} value={settings.org_profile.mission} onChange={(e) => updateOrg('mission', e.target.value)} />
        </div>
      </div>

      {/* ── Petition Defaults ────────────────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-4">
          <Megaphone size={16} style={{ color: '#f59e0b' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>Petition Defaults</h3>
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Default Signature Goal</label>
            <div className="relative">
              <Target size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2" style={{ color: 'var(--color-text-muted)' }} />
              <input
                type="number"
                className="input w-full pl-8"
                value={settings.petition_defaults.default_signature_goal}
                onChange={(e) => updatePetition('default_signature_goal', parseInt(e.target.value) || 0)}
              />
            </div>
          </div>
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Default Distribution</label>
            <select
              className="input w-full"
              value={settings.petition_defaults.default_distribution}
              onChange={(e) => updatePetition('default_distribution', e.target.value)}
            >
              <option value="email">Email</option>
              <option value="social">Social Media</option>
              <option value="both">Both</option>
            </select>
          </div>
        </div>

        <div className="mt-4">
          <label className="flex items-center gap-2 cursor-pointer">
            <input
              type="checkbox"
              checked={settings.petition_defaults.auto_compliance_check}
              onChange={(e) => updatePetition('auto_compliance_check', e.target.checked)}
            />
            <span className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>
              <Shield size={12} className="inline mr-1" style={{ verticalAlign: 'middle', color: '#10b981' }} />
              Auto-compliance check on new petitions
            </span>
          </label>
        </div>
      </div>

      {/* ── Campaign Settings ────────────────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-4">
          <Mail size={16} style={{ color: '#06b6d4' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>Campaign Settings</h3>
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Default Send Time</label>
            <input
              type="time"
              className="input w-full"
              value={settings.campaign_settings.default_send_time}
              onChange={(e) => updateCampaign('default_send_time', e.target.value)}
            />
          </div>
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Email Footer</label>
            <input className="input w-full" value={settings.campaign_settings.email_footer} onChange={(e) => updateCampaign('email_footer', e.target.value)} />
          </div>
        </div>

        <div className="mt-4">
          <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Unsubscribe Text</label>
          <textarea
            className="input w-full"
            rows={2}
            value={settings.campaign_settings.unsubscribe_text}
            onChange={(e) => updateCampaign('unsubscribe_text', e.target.value)}
          />
        </div>
      </div>

      {/* ── AI Configuration ─────────────────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-4">
          <Sparkles size={16} style={{ color: '#f59e0b' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>AI Configuration</h3>
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>AI Model</label>
            <input className="input w-full" value={settings.ai_config.model} readOnly style={{ opacity: 0.7, cursor: 'not-allowed' }} />
          </div>
          <div>
            <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>Default Tone</label>
            <select className="input w-full" value={settings.ai_config.default_tone} onChange={(e) => updateAI('default_tone', e.target.value)}>
              <option value="formal">Formal</option>
              <option value="passionate">Passionate</option>
              <option value="urgent">Urgent</option>
              <option value="conversational">Conversational</option>
            </select>
          </div>
        </div>

        <div className="mt-4">
          <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
            Creativity Level: {settings.ai_config.creativity_level}%
          </label>
          <input
            type="range"
            min={0}
            max={100}
            value={settings.ai_config.creativity_level}
            onChange={(e) => updateAI('creativity_level', parseInt(e.target.value))}
            className="w-full"
            style={{ accentColor: 'var(--color-primary)' }}
          />
          <div className="flex justify-between text-xs mt-1" style={{ color: 'var(--color-text-muted)' }}>
            <span>Conservative</span>
            <span>Creative</span>
          </div>
        </div>
      </div>

      {/* ── Data Stats ───────────────────────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-4">
          <Database size={16} style={{ color: '#10b981' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>Data Management</h3>
        </div>

        <div className="grid grid-cols-4 gap-3 mb-4">
          {[
            { label: 'Petitions', value: settings.data_stats.total_petitions, icon: Megaphone },
            { label: 'Signatures', value: settings.data_stats.total_signatures, icon: FileText },
            { label: 'Subscribers', value: settings.data_stats.total_subscribers, icon: Users },
            { label: 'Campaigns', value: settings.data_stats.total_campaigns, icon: Mail },
          ].map((stat) => (
            <div
              key={stat.label}
              className="rounded-lg p-3 text-center"
              style={{ backgroundColor: 'var(--color-surface-el)', border: '1px solid var(--color-border)' }}
            >
              <stat.icon size={16} className="mx-auto mb-1" style={{ color: 'var(--color-text-muted)' }} />
              <p className="text-lg font-bold" style={{ color: 'var(--color-text)' }}>{stat.value}</p>
              <p className="text-xs" style={{ color: 'var(--color-text-muted)' }}>{stat.label}</p>
            </div>
          ))}
        </div>

        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2 text-xs" style={{ color: 'var(--color-text-muted)' }}>
            <Clock size={12} />
            <span>
              Last backup: {new Date(settings.data_stats.last_backup).toLocaleString('en-US', {
                timeZone: 'America/Los_Angeles', month: 'short', day: 'numeric',
                hour: 'numeric', minute: '2-digit', hour12: true,
              })} PT
            </span>
          </div>
          <button className="btn btn-ghost btn-sm">
            <Download size={13} />
            <span>Export All Data</span>
          </button>
        </div>
      </div>

      {/* ── Social Platform Credentials ───────────────────────────────────── */}
      <div className="card p-5">
        <div className="flex items-center gap-2 mb-1">
          <Share2 size={16} style={{ color: '#8b5cf6' }} />
          <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>Social Platform Credentials</h3>
        </div>
        <p className="text-xs mb-4" style={{ color: 'var(--color-text-muted)' }}>
          Configure API credentials for each platform. Credentials are saved to the agent servers and take effect immediately on save.
        </p>
        <SocialCredentialsSection />
      </div>
    </div>
  );
}