From dcaf7959acdddeac59eca3e6016362234afb0636 Mon Sep 17 00:00:00 2001 From: Marcio Bevervanso Date: Mon, 8 Jun 2026 14:10:06 -0300 Subject: [PATCH] feat: let users pick the Page/Instagram account when adding ads to a campaign Some ad accounts aren't authorized to use the Instagram account that's auto-detected from the existing ad set, causing Meta error 200/1815199 on publish. The new selector lists all of the user's connected Pages (with linked Instagram accounts) so they can choose a valid combination themselves, falling back to the auto-detected default. Also paginate /me/accounts so accounts beyond the first page show up. Co-Authored-By: Claude Sonnet 4.6 --- src/components/dashboard/AddAdModal.tsx | 110 ++++++++++++++++++++++-- src/lib/mcp/client.ts | 22 ++++- 2 files changed, 125 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/AddAdModal.tsx b/src/components/dashboard/AddAdModal.tsx index 68fad55..ed071bb 100644 --- a/src/components/dashboard/AddAdModal.tsx +++ b/src/components/dashboard/AddAdModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Wand2, Upload, CheckCircle2 } from 'lucide-react'; import { Modal } from '@/components/ui/Modal'; import { Spinner } from '@/components/ui/Spinner'; @@ -8,6 +8,12 @@ import { ErrorBlock } from '@/components/ui/ErrorBlock'; import { parseApiError, type FriendlyError } from '@/lib/errors'; import type { AdCopyVariation } from '@/lib/ai/copywriter'; +type MetaPage = { + id: string; + name: string; + instagram_account: { id: string; username: string; profile_picture_url?: string } | null; +}; + interface AddAdModalProps { isOpen: boolean; onClose: () => void; @@ -45,6 +51,23 @@ export function AddAdModal({ const [defaults, setDefaults] = useState(null); const [defaultsLoading, setDefaultsLoading] = useState(false); + const [pages, setPages] = useState([]); + const [pagesLoading, setPagesLoading] = useState(false); + const [selectedPageId, setSelectedPageId] = useState(''); + + // Página detectada automaticamente a partir do anúncio existente no ad set + // (ou a primeira da lista, se não houver match) — usada como valor inicial + // do seletor até o usuário escolher outra explicitamente. + const autoSelectedPageId = useMemo(() => { + if (pages.length === 0) return ''; + const preferred = defaults?.page_id ? pages.find((p) => p.id === defaults.page_id) : undefined; + return preferred?.id || pages[0].id; + }, [pages, defaults]); + + const effectivePageId = selectedPageId || autoSelectedPageId; + const selectedPage = pages.find((p) => p.id === effectivePageId) || null; + const selectedInstagramAccount = selectedPage?.instagram_account || null; + const [copyLoading, setCopyLoading] = useState(false); const [copyVariations, setCopyVariations] = useState([]); const [selectedCopy, setSelectedCopy] = useState(null); @@ -90,6 +113,32 @@ export function AddAdModal({ }; }, [adsetId, campaignName, adsetName]); + // Carrega as páginas/Instagram conectados para o usuário poder escolher + // explicitamente — em vez de depender só da detecção automática, que pode + // trazer uma combinação não autorizada para esta conta de anúncios. + useEffect(() => { + let cancelled = false; + + (async () => { + setPagesLoading(true); + try { + const res = await fetch('/api/meta/pages'); + const data = await res.json(); + if (!cancelled && data.success) { + setPages(data.data || []); + } + } catch { + // Segue sem a lista — o usuário ainda pode publicar com os defaults detectados. + } finally { + if (!cancelled) setPagesLoading(false); + } + })(); + + return () => { + cancelled = true; + }; + }, []); + const generateCopy = async () => { if (!context.trim()) { setError('Descreva sobre o que é esse anúncio antes de gerar as copies.'); @@ -181,11 +230,12 @@ export function AddAdModal({ const canPublish = !!selectedCopyData && !!selectedImage && + !!effectivePageId && (!!link.trim() || !!defaults?.whatsapp_link || !!defaults?.whatsapp_phone_number); const publish = async () => { if (!canPublish || !selectedCopyData || !selectedImage) { - setError('Selecione uma copy e uma imagem antes de publicar.'); + setError('Selecione uma página, uma copy e uma imagem antes de publicar.'); return; } @@ -200,8 +250,8 @@ export function AddAdModal({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaign_name: campaignName, - page_id: defaults?.page_id, - instagram_actor_id: defaults?.instagram_actor_id, + page_id: effectivePageId || defaults?.page_id, + instagram_actor_id: selectedInstagramAccount?.id || undefined, link: link.trim() || undefined, headline: selectedCopyData.headline, primary_text: selectedCopyData.primary_text, @@ -255,6 +305,51 @@ export function AddAdModal({ ) : ( <> +
+ + + {pagesLoading ? ( +
+ Carregando páginas conectadas... +
+ ) : pages.length === 0 ? ( +

+ Nenhuma página encontrada na sua conta do Facebook. A Meta vai usar a página padrão deste ad set. +

+ ) : ( +
+ +
+ + + +
+
+ )} + +

+ {selectedInstagramAccount ? ( + <>O anúncio também vai rodar no Instagram conectado a essa página: @{selectedInstagramAccount.username}. + ) : ( + 'Esta página não tem uma conta do Instagram vinculada — o anúncio roda só no Facebook.' + )} + {' '}Se a Meta recusar por falta de permissão entre a conta de anúncios e o Instagram, escolha outra página aqui ou autorize o vínculo no Business Manager. +

+
+