tractatus/public/admin/blog-curation.html
TheFlow 939e207d00 fix(blog): add missing auth headers to submission modal API calls
Fixed 401 Unauthorized errors in blog validation/submission modal:
- Added Authorization Bearer token to /api/blog/admin/:id fetch (line 153)
- Added Authorization Bearer token to /api/submissions/by-blog-post/:id fetch (line 162)
- Added Authorization Bearer token to /api/submissions/:id/export fetch (line 818)

All admin API endpoints require authentication. The submission modal
was making unauthenticated requests, causing 401 errors when trying
to load article data or export submission packages.

The 404 error on by-blog-post is expected when no submission exists
for that blog post ID yet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 13:00:11 +13:00

422 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>External Communications Manager | Tractatus Admin</title>
<link rel="stylesheet" href="/css/tailwind.css?v=">
<link rel="stylesheet" href="/css/tractatus-theme.min.css?v=">
<script defer src="/js/admin/auth-check.js?v="></script>
<style>
.content-type-card input[type="radio"]:checked + div {
border-color: #3b82f6;
background-color: #eff6ff;
}
</style>
</head>
<body class="bg-gray-50">
<!-- Navigation -->
<div id="admin-navbar" data-page-title="External Communications" data-page-icon="blog"></div>
<script src="/js/components/navbar-admin.js?v="></script>
<!-- Main Content -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Section Navigation Tabs -->
<div class="mb-6">
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
<button data-section="validation" class="section-tab border-blue-500 text-blue-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
📋 Pre-Submission
</button>
<button data-section="draft" class="section-tab border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
✨ Generate
</button>
<button data-section="queue" class="section-tab border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
⏳ AI Draft Approval
</button>
<button data-section="guidelines" class="section-tab border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
📖 Guidelines
</button>
<button data-section="published" class="section-tab border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm">
📰 Published
</button>
</nav>
</div>
<div class="mt-2 text-xs text-gray-500 px-1">
<strong>Workflow:</strong> Generate → AI Approval → Pre-Submission (validate & prep) → Submit to publications
</div>
</div>
<!-- Tractatus Enforcement Notice -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg aria-hidden="true" class="h-5 w-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800">Tractatus Framework Enforcement Active</h3>
<div class="mt-2 text-sm text-blue-700">
<p>All AI-generated content is validated against:</p>
<ul class="list-disc list-inside mt-1 space-y-1">
<li><strong>inst_016:</strong> No fabricated statistics or unverifiable claims</li>
<li><strong>inst_017:</strong> No absolute assurance terms (guarantee, 100%, etc.)</li>
<li><strong>inst_018:</strong> No unverified production-ready claims</li>
</ul>
<p class="mt-2 text-xs">🤖 <strong>TRA-OPS-0002:</strong> AI suggests, human decides. All content requires human review and approval.</p>
</div>
</div>
</div>
</div>
<!-- Draft Content Section -->
<div id="draft-section" class="hidden">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Generate External Content</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Draft Form -->
<div class="lg:col-span-2">
<div class="bg-white rounded-lg shadow p-6">
<form id="draft-form">
<div class="space-y-6">
<!-- STEP 1: Content Type -->
<div class="pb-4 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-900 mb-3">Step 1: Content Type</h3>
<div class="grid grid-cols-2 gap-3">
<label class="content-type-card">
<input type="radio" name="contentType" value="blog" class="sr-only" checked>
<div class="p-4 border-2 border-gray-200 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all">
<div class="font-medium text-gray-900">Website Blog</div>
<div class="text-xs text-gray-500 mt-1">Long-form content for agenticgovernance.digital</div>
</div>
</label>
<label class="content-type-card">
<input type="radio" name="contentType" value="letter" class="sr-only">
<div class="p-4 border-2 border-gray-200 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all">
<div class="font-medium text-gray-900">Letter to Editor</div>
<div class="text-xs text-gray-500 mt-1">200-300 words, article reference required</div>
</div>
</label>
<label class="content-type-card">
<input type="radio" name="contentType" value="oped" class="sr-only">
<div class="p-4 border-2 border-gray-200 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all">
<div class="font-medium text-gray-900">Op-Ed / Opinion</div>
<div class="text-xs text-gray-500 mt-1">750-1500 words, external publication</div>
</div>
</label>
<label class="content-type-card">
<input type="radio" name="contentType" value="social" class="sr-only">
<div class="p-4 border-2 border-gray-200 rounded-lg cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all">
<div class="font-medium text-gray-900">Social Media</div>
<div class="text-xs text-gray-500 mt-1">LinkedIn, Substack, Medium</div>
</div>
</label>
</div>
</div>
<!-- STEP 2: Publication Target (conditional) -->
<div id="publication-section" class="pb-4 border-b border-gray-200 hidden">
<h3 class="text-sm font-semibold text-gray-900 mb-3">Step 2: Publication Target</h3>
<select id="publication-target" name="publicationTarget"
class="block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="">Select publication...</option>
</select>
<!-- Publication info display -->
<div id="publication-info" class="mt-4 hidden p-4 bg-gray-50 rounded-md">
<dl class="space-y-2 text-sm">
<div>
<dt class="font-medium text-gray-700">Word Count:</dt>
<dd class="text-gray-600" id="pub-word-count">-</dd>
</div>
<div>
<dt class="font-medium text-gray-700">Submission:</dt>
<dd class="text-gray-600" id="pub-submission">-</dd>
</div>
<div>
<dt class="font-medium text-gray-700">Response Time:</dt>
<dd class="text-gray-600" id="pub-response">-</dd>
</div>
</dl>
</div>
<!-- Publication-Specific Topic Suggestions -->
<button type="button" id="suggest-topics-btn"
class="mt-4 px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed hidden">
<span id="suggest-topics-text">✨ Get Topic Suggestions for This Publication</span>
<span id="suggest-topics-loader" class="hidden">⏳ Generating...</span>
</button>
<!-- Topic Suggestions Display -->
<div id="topic-suggestions" class="mt-4 hidden"></div>
</div>
<!-- STEP 3: Article Reference (for letters only) -->
<div id="article-reference-section" class="pb-4 border-b border-gray-200 hidden">
<h3 class="text-sm font-semibold text-gray-900 mb-3">Step 3: Article Reference</h3>
<div class="space-y-3">
<div>
<label for="article-title" class="block text-sm font-medium text-gray-700">Article Title *</label>
<input type="text" id="article-title" name="articleTitle"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., AI Regulation in Brussels">
</div>
<div>
<label for="article-date" class="block text-sm font-medium text-gray-700">Publication Date *</label>
<input type="date" id="article-date" name="articleDate"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="main-point" class="block text-sm font-medium text-gray-700">Your Main Point *</label>
<textarea id="main-point" name="mainPoint" rows="3"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., EU AI Act needs stronger enforcement mechanisms to be effective"></textarea>
</div>
</div>
</div>
<!-- STEP 4: Topic/Theme (for blog/oped) -->
<div id="topic-section" class="pb-4 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-900 mb-3">
<span id="topic-step-label">Step 2</span>: Topic & Content
</h3>
<div class="space-y-3">
<div>
<label for="topic" class="block text-sm font-medium text-gray-700">Topic *</label>
<input type="text" id="topic" name="topic" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., Understanding AI Boundary Enforcement in Production Systems">
</div>
<div>
<label for="focus" class="block text-sm font-medium text-gray-700">Specific Focus (Optional)</label>
<input type="text" id="focus" name="focus"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="e.g., Real-world implementation challenges, case studies, best practices">
</div>
</div>
</div>
<!-- STEP 5: Audience & Context -->
<div id="context-section" class="pb-4 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-900 mb-3">
<span id="context-step-label">Step 3</span>: Audience & Context
</h3>
<div class="space-y-3">
<div>
<label for="audience" class="block text-sm font-medium text-gray-700">Target Audience *</label>
<select id="audience" name="audience" required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="">Select audience...</option>
<option value="research">Research (Academic, AI safety specialists)</option>
<option value="implementer">Implementer (Engineers, architects)</option>
<option value="leader">Leader (Policy makers, executives)</option>
<option value="general">General (Mixed backgrounds)</option>
</select>
</div>
<div>
<label for="tone" class="block text-sm font-medium text-gray-700">Tone & Approach</label>
<select id="tone" name="tone"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="standard">Standard (Professional, balanced)</option>
<option value="academic">Academic (Rigorous, citations)</option>
<option value="practical">Practical (Action-oriented)</option>
<option value="strategic">Strategic (High-level, business)</option>
<option value="conversational">Conversational (Accessible, engaging)</option>
</select>
</div>
<div>
<label for="culture" class="block text-sm font-medium text-gray-700">Cultural Context</label>
<select id="culture" name="culture"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="universal">Universal (Culture-neutral)</option>
<option value="indigenous">Indigenous (Māori, First Nations)</option>
<option value="global-south">Global South (Emerging economies)</option>
<option value="asia-pacific">Asia-Pacific</option>
<option value="european">European</option>
<option value="north-american">North American</option>
</select>
</div>
<div>
<label for="language" class="block text-sm font-medium text-gray-700">Language</label>
<select id="language" name="language"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500">
<option value="en">English</option>
<option value="mi">Te Reo Māori</option>
<option value="es">Español (Spanish)</option>
<option value="fr">Français (French)</option>
<option value="de">Deutsch (German)</option>
<option value="zh">中文 (Chinese)</option>
<option value="ja">日本語 (Japanese)</option>
</select>
</div>
</div>
</div>
<!-- Submit -->
<div class="flex items-center justify-between pt-2">
<button type="submit" id="draft-btn"
class="bg-blue-600 text-white px-6 py-2 rounded-md font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
Generate Draft
</button>
<span id="draft-status" class="text-sm text-gray-500"></span>
</div>
</div>
</form>
</div>
</div>
<!-- Quick Actions -->
<div class="space-y-4">
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Quick Actions</h3>
<div class="space-y-3">
<button id="suggest-topics-btn"
class="w-full text-left px-4 py-3 bg-gray-50 hover:bg-gray-100 rounded-md border border-gray-200">
<div class="font-medium text-gray-900">Suggest Topics</div>
<div class="text-sm text-gray-500">Get AI topic ideas for editorial calendar</div>
</button>
<button id="analyze-content-btn"
class="w-full text-left px-4 py-3 bg-gray-50 hover:bg-gray-100 rounded-md border border-gray-200">
<div class="font-medium text-gray-900">Analyze Content</div>
<div class="text-sm text-gray-500">Check existing content for compliance</div>
</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-2">Statistics</h3>
<div class="space-y-3">
<div>
<div class="text-2xl font-bold text-gray-900" id="stat-pending-drafts">-</div>
<div class="text-sm text-gray-500">Pending Drafts</div>
</div>
<div>
<div class="text-2xl font-bold text-gray-900" id="stat-published-posts">-</div>
<div class="text-sm text-gray-500">Published Posts</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Validation Section -->
<div id="validation-section">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Pre-Submission Review & Validation</h2>
<p class="text-sm text-gray-600 mb-4">Articles awaiting final validation and submission package preparation before sending to publications.</p>
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg aria-hidden="true" class="h-5 w-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-blue-800">Two-Level Validation</h3>
<div class="mt-2 text-sm text-blue-700">
<p>Articles are validated for both <strong>content similarity</strong> (substantive differences in arguments) and <strong>title similarity</strong> (avoiding confusion in the same market). Both checks must pass before submission.</p>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Pending Review Articles</h3>
<button id="refresh-validation-btn" class="text-sm text-blue-600 hover:text-blue-800">
Refresh
</button>
</div>
</div>
<div id="validation-list" class="divide-y divide-gray-200">
<div class="px-6 py-8 text-center text-gray-500">Loading articles...</div>
</div>
</div>
</div>
<!-- Review Queue Section -->
<div id="queue-section" class="hidden">
<h2 class="text-2xl font-bold text-gray-900 mb-6">AI-Generated Draft Approval Queue</h2>
<p class="text-sm text-gray-600 mb-4">AI-generated drafts requiring human review and approval before becoming articles.</p>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Pending Drafts</h3>
<button id="refresh-queue-btn" class="text-sm text-blue-600 hover:text-blue-800">
Refresh
</button>
</div>
</div>
<div id="draft-queue" class="divide-y divide-gray-200">
<div class="px-6 py-8 text-center text-gray-500">Loading queue...</div>
</div>
</div>
</div>
<!-- Guidelines Section -->
<div id="guidelines-section" class="hidden">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Editorial Guidelines</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Writing Standards</h3>
<dl id="editorial-standards" class="space-y-3">
<div class="text-center text-gray-500">Loading guidelines...</div>
</dl>
</div>
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Forbidden Patterns</h3>
<ul id="forbidden-patterns" class="space-y-2">
<li class="text-center text-gray-500">Loading patterns...</li>
</ul>
</div>
</div>
<div class="mt-6 bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Core Principles</h3>
<ul id="core-principles" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<li class="text-center text-gray-500">Loading principles...</li>
</ul>
</div>
</div>
<!-- Published Posts Section -->
<div id="published-section" class="hidden">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Published Website Content</h2>
<p class="text-sm text-gray-600 mb-4">Articles published on the Tractatus website. These are different from outreach articles submitted to external publications.</p>
<div class="bg-white rounded-lg shadow">
<div class="px-6 py-4 border-b border-gray-200">
<div class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Published Articles</h3>
<button id="refresh-published-btn" class="text-sm text-blue-600 hover:text-blue-800">
Refresh
</button>
</div>
</div>
<div id="published-list" class="divide-y divide-gray-200">
<div class="px-6 py-8 text-center text-gray-500">Loading published posts...</div>
</div>
</div>
</div>
</div>
<!-- Modals -->
<div id="modal-container"></div>
<script src="/js/admin/blog-curation.js?v="></script>
<script src="/js/admin/blog-curation-enhanced.js?v="></script>
<script src="/js/admin/blog-validation.js?v="></script>
<script src="/js/admin/submission-modal-enhanced.js?v="></script>
</body>
</html>