"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { CheckCircle2, XCircle, Loader2, ExternalLink } from "lucide-react"; interface SettingsGroup { name: string; description: string; docsUrl: string; keys: string[]; } interface SettingConfig { label: string; placeholder: string; secret?: boolean; } const SETTINGS_GROUPS: SettingsGroup[] = [ { name: "Postiz", description: "Self-hosted social media scheduling. Handles Instagram and TikTok publishing.", docsUrl: "https://postiz.com", keys: ["POSTIZ_URL", "POSTIZ_API_KEY"], }, { name: "Tavily", description: "AI-powered web research. Used by the Trend Scout and Research agents.", docsUrl: "https://tavily.com", keys: ["TAVILY_API_KEY"], }, { name: "Gemini", description: "Google Gemini powers NanoBanana MCP for AI image generation in static ads. ~$0.04-0.13/image.", docsUrl: "https://aistudio.google.com/apikey", keys: ["GEMINI_API_KEY"], }, { name: "Nextdoor", description: "Direct Nextdoor Ads API integration for local advertising.", docsUrl: "https://developer.nextdoor.com", keys: ["NEXTDOOR_API_TOKEN", "NEXTDOOR_ADVERTISER_ID"], }, ]; const SETTINGS_CONFIG: Record = { POSTIZ_URL: { label: "Postiz URL", placeholder: "http://localhost:5000" }, POSTIZ_API_KEY: { label: "Postiz API Key", placeholder: "your-postiz-api-key", secret: true, }, TAVILY_API_KEY: { label: "Tavily API Key", placeholder: "tvly-...", secret: true, }, GEMINI_API_KEY: { label: "Google Gemini API Key", placeholder: "AIza...", secret: true, }, NEXTDOOR_API_TOKEN: { label: "Nextdoor API Token", placeholder: "your-nextdoor-token", secret: true, }, NEXTDOOR_ADVERTISER_ID: { label: "Nextdoor Advertiser ID", placeholder: "your-advertiser-id", }, }; type IntegrationStatus = Record< string, { connected: boolean; error?: string } >; export default function SettingsPage() { const [settings, setSettings] = useState>({}); const [status, setStatus] = useState({}); const [editValues, setEditValues] = useState>({}); const [saving, setSaving] = useState(null); const [saved, setSaved] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch("/api/settings?status=true") .then((r) => r.json()) .then((data) => { setSettings(data.settings || {}); setStatus(data.status || {}); setLoading(false); }) .catch(() => setLoading(false)); }, []); async function handleSave(key: string) { const value = editValues[key]; if (value === undefined) return; setSaving(key); const res = await fetch("/api/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value }), }); if (res.ok) { setSaved(key); // Refresh settings const data = await fetch("/api/settings?status=true").then((r) => r.json() ); setSettings(data.settings || {}); setStatus(data.status || {}); setEditValues((prev) => { const next = { ...prev }; delete next[key]; return next; }); setTimeout(() => setSaved(null), 2000); } setSaving(null); } function getGroupStatus(group: SettingsGroup): { connected: boolean; error?: string; } { const key = group.name.toLowerCase(); return status[key] || { connected: false, error: "Unknown" }; } if (loading) { return (
); } return (

Settings

Configure your third-party integrations. Values are stored securely in the database and override environment variables.

{SETTINGS_GROUPS.map((group) => { const groupStatus = getGroupStatus(group); return (
{group.name} {groupStatus.connected ? ( Connected ) : ( {groupStatus.error || "Not connected"} )}
Docs
{group.description}
{group.keys.map((key) => { const config = SETTINGS_CONFIG[key]; const currentValue = settings[key] || ""; const isEditing = key in editValues; const editValue = editValues[key] ?? ""; return (
setEditValues((prev) => ({ ...prev, [key]: e.target.value, })) } onFocus={() => { if (!isEditing) { // Clear masked value on focus for secret fields setEditValues((prev) => ({ ...prev, [key]: "", })); } }} /> {isEditing && ( )} {isEditing && ( )}
); })}
); })}
); }