"use client"; import { useState, useEffect } from "react"; import { saveResendCredentials, saveTwilioCredentials, testResendConnection, testTwilioConnection, getResendCredentials, getTwilioCredentials } from "@/actions/integrations/credentials"; // Icons const MailIcon = ({ className }: { className?: string }) => ( ); const MessageIcon = ({ className }: { className?: string }) => ( ); const ShieldIcon = ({ className }: { className?: string }) => ( ); const CheckIcon = ({ className }: { className?: string }) => ( ); const EyeIcon = ({ className }: { className?: string }) => ( ); const EyeOffIcon = ({ className }: { className?: string }) => ( ); const AlertIcon = ({ className }: { className?: string }) => ( ); type Props = { brandId: string; brands: { id: string; name: string }[]; }; export default function AdvancedIntegrations({ brandId, brands }: Props) { // The selected brand is always the current `brandId` prop — there is no // in-component brand picker, so derive it directly to avoid a stale copy // when the parent re-renders with a different brand. const selectedBrandId = brandId; const [credentials, setCredentials] = useState<{ resend: { api_key: string; from_email: string; from_name: string }; twilio: { account_sid: string; auth_token: string; phone_number: string }; }>({ resend: { api_key: "", from_email: "", from_name: "" }, twilio: { account_sid: "", auth_token: "", phone_number: "" }, }); const [showSecrets, setShowSecrets] = useState>({}); const [loading, setLoading] = useState(false); const [testResults, setTestResults] = useState>({}); const [saveStatus, setSaveStatus] = useState<{ type: "success" | "error"; message: string } | null>(null); useEffect(() => { async function fetchCredentials() { const [resendCreds, twilioCreds] = await Promise.all([ getResendCredentials(selectedBrandId), getTwilioCredentials(selectedBrandId), ]); setCredentials({ resend: { api_key: resendCreds.api_key ?? "", from_email: resendCreds.from_email ?? "", from_name: resendCreds.from_name ?? "", }, twilio: { account_sid: twilioCreds.account_sid ?? "", auth_token: twilioCreds.auth_token ?? "", phone_number: twilioCreds.phone_number ?? "", }, }); } fetchCredentials(); }, [selectedBrandId]); function toggleSecret(key: string) { setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); } async function handleTest(service: "resend" | "twilio") { // Clear previous result for this service const newResults = { ...testResults }; delete newResults[service]; setTestResults(newResults); if (service === "resend") { if (!credentials.resend.api_key?.trim()) { setTestResults((prev) => ({ ...prev, resend: { ok: false, message: "API key is required" } })); return; } const result = await testResendConnection(credentials.resend.api_key); setTestResults((prev) => ({ ...prev, resend: result })); } else { if (!credentials.twilio.account_sid?.trim() || !credentials.twilio.auth_token?.trim()) { setTestResults((prev) => ({ ...prev, twilio: { ok: false, message: "Account SID and Auth Token are required" } })); return; } const result = await testTwilioConnection(credentials.twilio.account_sid, credentials.twilio.auth_token); setTestResults((prev) => ({ ...prev, twilio: result })); } } async function handleSave(service: "resend" | "twilio") { setLoading(true); setSaveStatus(null); // Clear test result for this service const clearedResults = { ...testResults }; delete clearedResults[service]; setTestResults(clearedResults); try { if (service === "resend") { const result = await saveResendCredentials(selectedBrandId, { 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) { setSaveStatus({ type: "success", message: "Resend credentials saved" }); setTestResults((prev) => ({ ...prev, resend: { ok: true, message: "Saved successfully" } })); } else { setSaveStatus({ type: "error", message: result.error ?? "Failed to save" }); } } else { const result = await saveTwilioCredentials(selectedBrandId, { 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) { setSaveStatus({ type: "success", message: "Twilio credentials saved" }); setTestResults((prev) => ({ ...prev, twilio: { ok: true, message: "Saved successfully" } })); } else { setSaveStatus({ type: "error", message: result.error ?? "Failed to save" }); } } } catch (err) { setSaveStatus({ type: "error", message: err instanceof Error ? err.message : "Failed to save" }); } finally { setLoading(false); } } return ( {/* Security Warning Banner */} API Key Security • Never share keys — Don't paste in Slack, email, or commit to GitHub. • SMS costs add up fast — Twilio charges $0.008-$0.05 per message. • Set spending caps — Use provider budget controls to avoid surprise bills. {/* Resend Card */} Resend Email delivery for Harvest Reach campaigns {/* API Key */} API Key setCredentials((p) => ({ ...p, resend: { ...p.resend, api_key: e.target.value }, }))} placeholder="re_..." className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> toggleSecret("resendKey")} className="absolute right-3 top-1/2 -translate-y-1/2 text-stone-400 hover:text-stone-600" > {showSecrets.resendKey ? : } {/* From Email */} From Email setCredentials((p) => ({ ...p, resend: { ...p.resend, from_email: e.target.value }, }))} placeholder="orders@yourbrand.com" className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> {/* From Name */} From Name setCredentials((p) => ({ ...p, resend: { ...p.resend, from_name: e.target.value }, }))} placeholder="Your Brand Name" className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> {/* Status & Actions */} {testResults.resend && ( {testResults.resend.ok ? : } {testResults.resend.message} )} handleTest("resend")} disabled={loading} className="rounded-lg border border-[var(--admin-border)] px-4 py-2 text-xs font-medium text-[var(--admin-text-secondary)] hover:bg-stone-50 disabled:opacity-50" > Test Connection handleSave("resend")} disabled={loading} className="flex-1 rounded-lg bg-emerald-600 px-4 py-2 text-xs font-bold text-white hover:bg-emerald-700 disabled:opacity-50" > {loading ? "Saving..." : "Save Resend"} {/* Twilio Card */} Twilio SMS campaigns and alerts via Harvest Reach {/* Account SID */} Account SID setCredentials((p) => ({ ...p, twilio: { ...p.twilio, account_sid: e.target.value }, }))} placeholder="AC..." className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> {/* Auth Token */} Auth Token setCredentials((p) => ({ ...p, twilio: { ...p.twilio, auth_token: e.target.value }, }))} placeholder="Your Twilio auth token" className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm pr-10 text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> toggleSecret("twilioToken")} className="absolute right-3 top-1/2 -translate-y-1/2 text-stone-400 hover:text-stone-600" > {showSecrets.twilioToken ? : } {/* Phone Number */} Phone Number setCredentials((p) => ({ ...p, twilio: { ...p.twilio, phone_number: e.target.value }, }))} placeholder="+1234567890" className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" /> {/* Status & Actions */} {testResults.twilio && ( {testResults.twilio.ok ? : } {testResults.twilio.message} )} handleTest("twilio")} disabled={loading} className="rounded-lg border border-[var(--admin-border)] px-4 py-2 text-xs font-medium text-[var(--admin-text-secondary)] hover:bg-stone-50 disabled:opacity-50" > Test Connection handleSave("twilio")} disabled={loading} className="flex-1 rounded-lg bg-emerald-600 px-4 py-2 text-xs font-bold text-white hover:bg-emerald-700 disabled:opacity-50" > {loading ? "Saving..." : "Save Twilio"} {/* Save Status */} {saveStatus && ( {saveStatus.message} )} ); }
Email delivery for Harvest Reach campaigns
SMS campaigns and alerts via Harvest Reach