"use client"; import Link from "next/link"; import { useState, useEffect } from "react"; import AIProviderPanel from "@/components/admin/AIProviderPanel"; import { savePaymentSettings } from "@/actions/payments"; import { saveResendCredentials, saveTwilioCredentials, testResendConnection, testTwilioConnection, getResendCredentials, getTwilioCredentials } from "@/actions/integrations/credentials"; import { AdminInput, AdminTextInput, AdminSelect, AdminButton } from "@/components/admin/design-system"; import { AdminToggle } from "@/components/admin/design-system/AdminToggle"; type Props = { brandId: string; brands: { id: string; name: string }[]; isPlatformAdmin: boolean; }; type CredentialField = { key: string; label: string; placeholder: string; isSecret?: boolean; }; type Integration = { id: string; name: string; icon: string; description: string; accentColor: string; credentials: CredentialField[]; connected?: boolean; }; // Simplified integration cards for display (AI is handled separately) const COMMUNICATION_INTEGRATIONS: { id: string; name: string; icon: string; description: string; accentColor: string }[] = [ { id: "resend", name: "Resend", icon: "📧", description: "Send transactional and marketing emails via Harvest Reach.", accentColor: "border-amber-200 bg-amber-50/50", }, { id: "stripe", name: "Stripe", icon: "💳", description: "Process online payments for orders.", accentColor: "border-stone-200 bg-stone-50/50", }, { id: "twilio", name: "Twilio", icon: "📱", description: "Send SMS campaigns and alerts via Harvest Reach.", accentColor: "border-blue-200 bg-blue-50/50", }, ]; const INTEGRATIONS: Integration[] = [ { id: "openai", name: "OpenAI", icon: "🤖", description: "Power AI tools like Campaign Writer, Report Explainer, and Pricing Advisor.", accentColor: "border-violet-200 bg-violet-50/50", credentials: [], }, { id: "resend", name: "Resend", icon: "📧", description: "Send transactional and marketing emails via Harvest Reach.", accentColor: "border-amber-200 bg-amber-50/50", credentials: [ { key: "RESEND_API_KEY", label: "API Key", placeholder: "re_...", isSecret: true }, { key: "RESEND_FROM_EMAIL", label: "From Email Address", placeholder: "orders@yourbrand.com" }, { key: "RESEND_FROM_NAME", label: "From Name", placeholder: "Your Brand Name" }, ], }, { id: "stripe", name: "Stripe", icon: "💳", description: "Process online payments for orders.", accentColor: "border-stone-200 bg-stone-50/50", credentials: [ { key: "STRIPE_PUBLISHABLE_KEY", label: "Publishable Key", placeholder: "pk_live_..." }, { key: "STRIPE_SECRET_KEY", label: "Secret Key", placeholder: "sk_live_...", isSecret: true }, ], }, { id: "twilio", name: "Twilio", icon: "📱", description: "Send SMS campaigns and alerts via Harvest Reach.", accentColor: "border-blue-200 bg-blue-50/50", credentials: [ { key: "TWILIO_ACCOUNT_SID", label: "Account SID", placeholder: "AC..." }, { key: "TWILIO_AUTH_TOKEN", label: "Auth Token", placeholder: "Your Twilio auth token", isSecret: true }, { key: "TWILIO_PHONE_NUMBER", label: "Phone Number", placeholder: "+1234567890" }, ], }, ]; function IntegrationCard({ integration, initialCredentials, brandId, }: { integration: Integration; initialCredentials?: Record; brandId: string; }) { const [credentials, setCredentials] = useState>( initialCredentials ?? {} ); const [showSecrets, setShowSecrets] = useState>({}); const [testResult, setTestResult] = useState<{ ok: boolean; message: string } | null>(null); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); // Derived from `credentials` + `initialCredentials` during render so we // don't pay for an extra commit and stale-frame window. const hasValues = Object.values(credentials).some((v) => v.trim().length > 0); const dirty = hasValues && JSON.stringify(initialCredentials) !== JSON.stringify(credentials); async function handleTest() { setTestResult(null); setError(null); if (integration.id === "resend") { const apiKey = credentials["RESEND_API_KEY"]; if (!apiKey?.trim()) { setTestResult({ ok: false, message: "API key is required" }); return; } const result = await testResendConnection(apiKey); setTestResult(result); } else if (integration.id === "twilio") { const accountSid = credentials["TWILIO_ACCOUNT_SID"]; const authToken = credentials["TWILIO_AUTH_TOKEN"]; if (!accountSid?.trim() || !authToken?.trim()) { setTestResult({ ok: false, message: "Account SID and Auth Token are required" }); return; } const result = await testTwilioConnection(accountSid, authToken); setTestResult(result); } else { // Default test (placeholder) await new Promise((r) => setTimeout(r, 500)); const hasKey = Object.values(credentials).some((v) => v.trim().length > 0); setTestResult( hasKey ? { ok: true, message: `Successfully connected to ${integration.name}` } : { ok: false, message: `No API key configured for ${integration.name}` } ); } } async function handleSave() { setSaving(true); setError(null); setTestResult(null); try { if (integration.id === "resend") { const result = await saveResendCredentials(brandId, { api_key: credentials["RESEND_API_KEY"]?.trim() || null, from_email: credentials["RESEND_FROM_EMAIL"]?.trim() || null, from_name: credentials["RESEND_FROM_NAME"]?.trim() || null, }); if (!result.success) { setError(result.error ?? "Failed to save"); return; } setTestResult({ ok: true, message: "Resend credentials saved successfully" }); setSaved(true); setTimeout(() => setSaved(false), 3000); } else if (integration.id === "twilio") { const result = await saveTwilioCredentials(brandId, { account_sid: credentials["TWILIO_ACCOUNT_SID"]?.trim() || null, auth_token: credentials["TWILIO_AUTH_TOKEN"]?.trim() || null, phone_number: credentials["TWILIO_PHONE_NUMBER"]?.trim() || null, }); if (!result.success) { setError(result.error ?? "Failed to save"); return; } setTestResult({ ok: true, message: "Twilio credentials saved successfully" }); setSaved(true); setTimeout(() => setSaved(false), 3000); } else if (integration.id === "stripe") { const result = await savePaymentSettings({ brandId, provider: "stripe", stripePublishableKey: credentials["STRIPE_PUBLISHABLE_KEY"]?.trim() || undefined, stripeSecretKey: credentials["STRIPE_SECRET_KEY"]?.trim() || undefined, }); if (!result.success) { setError(result.error ?? "Failed to save"); return; } setTestResult({ ok: true, message: "Stripe credentials saved successfully" }); setSaved(true); setTimeout(() => setSaved(false), 3000); } else { // Other integrations - just show success for now setTestResult({ ok: true, message: `${integration.name} settings saved` }); setSaved(true); setTimeout(() => setSaved(false), 3000); } } catch (err) { setError(err instanceof Error ? err.message : "Failed to save settings"); } finally { setSaving(false); } } function toggleSecret(key: string) { setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); } function updateCredential(key: string, value: string) { setCredentials((prev) => ({ ...prev, [key]: value })); } return (
{integration.icon}

{integration.name}

{integration.description}

{testResult?.ok && ( Connected )}
{/* Messages */} {error && (
{error}
)} {saved && (
Settings saved successfully!
)} {testResult && (
{testResult.ok ? ( ) : ( )} {testResult.message}
)} {/* Credentials form */}
{integration.credentials.map((field) => (
updateCredential(field.key, e.target.value)} placeholder={field.placeholder} /> {field.isSecret && ( )}
))}
{/* Action buttons */}
{testResult?.ok ? "Re-test" : "Test Connection"} {saving ? ( Saving... ) : ( <> Save )} {dirty && !saving && ( Unsaved changes )}
); } export default function IntegrationsClientPage({ brandId, brands, isPlatformAdmin }: Props) { // `selectedBrandId` is the platform admin's explicit brand selection. The // initial value is not derived from a prop so the value stays in sync with // the server-truth `brandId` once a user makes a choice: we always render // off `effectiveBrandId = selectedBrandId || brandId`, so any change to // `brandId` (e.g., route nav) is reflected until the user picks another. const [selectedBrandId, setSelectedBrandId] = useState(""); const effectiveBrandId = selectedBrandId || brandId; const [initialCredentials, setInitialCredentials] = useState>>({}); const [loading, setLoading] = useState(true); // Fetch initial credentials for each integration useEffect(() => { async function fetchCredentials() { setLoading(true); try { const [resendCreds, twilioCreds] = await Promise.all([ getResendCredentials(effectiveBrandId), getTwilioCredentials(effectiveBrandId), ]); setInitialCredentials({ resend: { RESEND_API_KEY: resendCreds.api_key ?? "", RESEND_FROM_EMAIL: resendCreds.from_email ?? "", RESEND_FROM_NAME: resendCreds.from_name ?? "", }, twilio: { TWILIO_ACCOUNT_SID: twilioCreds.account_sid ?? "", TWILIO_AUTH_TOKEN: twilioCreds.auth_token ?? "", TWILIO_PHONE_NUMBER: twilioCreds.phone_number ?? "", }, }); } finally { setLoading(false); } } fetchCredentials(); }, [effectiveBrandId]); return (
{/* Brand selector for platform admins */} {isPlatformAdmin && brands.length > 0 && (
setSelectedBrandId(e.target.value)} options={brands.map((b) => ({ value: b.id, label: b.name }))} />
)} {/* AI Provider Section */}
🤖

AI Provider

Configure AI models for intelligent features

Configure AI
{/* Payment & Email integrations grid */}

Communication & Payments

{loading ? (
Loading integrations...
) : ( INTEGRATIONS.filter(i => i.id !== "openai").map((integration) => ( )) )}
{/* Help text */}

Need help?

Contact us at support@cielohermosa.com for assistance setting up integrations.

); }