import React, { useCallback, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { LuArrowRight, LuSparkles } from 'react-icons/lu'; import ErrorBanner from '../components/agent-analytics/ErrorBanner'; import { Spinner } from '../components/ui/Spinner'; import ArticleModal from '../components/visibility/ArticleModal'; import GenerateIdeasModal from '../components/visibility/GenerateIdeasModal'; import { toneToBadgeClass } from '../components/visibility/helpers'; import ProblemsList from '../components/visibility/ProblemsList'; import { useAppStateContext } from '../context/user.data.context'; import type { ArticleSummary, BrandResponse, VisibilityProblem, } from '../service/visibility/visibility.interface'; import { listArticles, listBrands, listProblems, } from '../service/visibility/visibility.service'; /** How many of the latest generated pages the hub lists. */ const LATEST_PAGES_LIMIT = 6; /** How many detected problems the hub lists. */ const TOP_PROBLEMS_LIMIT = 5; /** * Map an article status to a badge tone. * * @param {string} status - Article status as returned by the agent. * @return {'success' | 'attention' | 'warning'} Badge tone. */ const statusTone = (status: string): 'success' | 'attention' | 'warning' => { if (status === 'ready' || status === 'published') return 'success'; if (status === 'failed') return 'warning'; return 'attention'; }; /** * "Optimized text pages" page under Content Generator. Turns a brief into * AI-search-optimized articles by reusing the visibility ideas flow, and * doubles as a hub: the main visibility problems worth turning into content, * and the latest pages already generated. * * @return {JSX.Element} The page. */ const OptimizedTextPages = (): JSX.Element => { const { token, clientId } = useAppStateContext(); const hasClientCreds = Boolean(token && clientId); const [brand, setBrand] = useState(null); const [problems, setProblems] = useState([]); const [articles, setArticles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [modalOpen, setModalOpen] = useState(false); const [openArticleId, setOpenArticleId] = useState(null); const loadAll = useCallback(async (): Promise => { if (!hasClientCreds) return; setLoading(true); setError(null); try { const brandList = await listBrands(clientId as string, token as string); const firstBrand = brandList.brands[0] ?? null; setBrand(firstBrand); if (!firstBrand) { setProblems([]); setArticles([]); return; } const [problemList, articleList] = await Promise.all([ listProblems( clientId as string, token as string, firstBrand.brand_id ).catch(() => ({ problems: [] as VisibilityProblem[] })), listArticles(clientId as string, token as string, { brand_id: firstBrand.brand_id, limit: LATEST_PAGES_LIMIT, }).catch(() => ({ articles: [] as ArticleSummary[] })), ]); setProblems(problemList.problems ?? []); setArticles((articleList.articles ?? []).slice(0, LATEST_PAGES_LIMIT)); } catch (err) { setError( err instanceof Error ? err.message : 'Failed to load optimized pages.' ); } finally { setLoading(false); } }, [hasClientCreds, clientId, token]); useEffect(() => { void loadAll(); }, [loadAll]); if (!hasClientCreds || loading) { return (
); } return (

Optimized text pages

Turn an idea or a brief into AI-search-optimized articles. Describe what you want, get a handful of angles back, and generate full pages your customers - and the AI engines - actually surface.

{error && }
  1. 1. Describe it.{' '} Paste a brief or upload a .txt / .csv / .docx and edit it however you like.
  2. 2. Generate ideas. {' '} You get 3 to 5 angles (titles, hooks and target keywords).
  3. 3. Pick what to write. {' '} Generate a full page from any idea - it lands ready to review and publish.
{!brand && ( Set up{' '} AI Visibility {' '} first so we know your brand and catalog. )}

Top opportunities

View all in AI Visibility

The biggest gaps where AI doesn't surface you yet - the best prompts to target with a new page.

{problems.length > 0 ? ( ) : (
No problems detected this week - nice. Generate pages to widen your coverage anyway.
)}

Latest pages

View all
{articles.length > 0 ? (
{articles.map(article => ( ))}
) : (
No pages generated yet. Hit “Create optimized text pages” to make your first one.
)}
{brand && ( {}} onArticleStarted={loadAll} /> )} setOpenArticleId(null)} clientId={clientId as string} token={token as string} />
); }; export default OptimizedTextPages;