import React, { useState, useEffect } from 'react'; import { X, BookOpen, Search } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; // ⚠️ CONVENTION: Avoid hardcoded absolute URL fallback; use relative API path or ensure REACT_APP_API_BASE is always set at build time const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; // Inline styles matching the dark tactical theme const styles = { overlay: { position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(0, 0, 0, 0.8)', backdropFilter: 'blur(4px)', display: 'flex', alignItems: 'center', justifyContent: 'center', }, modal: { width: '90vw', maxWidth: '900px', height: '85vh', background: 'linear-gradient(135deg, #0F1A2E 0%, #1E293B 100%)', border: '1px solid rgba(14, 165, 233, 0.25)', borderRadius: '0.75rem', boxShadow: '0 25px 80px rgba(0, 0, 0, 0.7)', display: 'flex', flexDirection: 'column', overflow: 'hidden', }, header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '1rem 1.5rem', borderBottom: '1px solid rgba(14, 165, 233, 0.15)', flexShrink: 0, }, headerTitle: { display: 'flex', alignItems: 'center', gap: '0.5rem', fontFamily: "'JetBrains Mono', monospace", fontSize: '1rem', fontWeight: 700, color: '#0EA5E9', textTransform: 'uppercase', letterSpacing: '0.1em', }, closeBtn: { background: 'none', border: 'none', color: '#94A3B8', cursor: 'pointer', padding: '0.25rem', lineHeight: 1, }, searchBar: { padding: '0.75rem 1.5rem', borderBottom: '1px solid rgba(14, 165, 233, 0.1)', flexShrink: 0, }, searchInput: { width: '100%', padding: '0.5rem 0.75rem 0.5rem 2.25rem', background: 'rgba(15, 23, 42, 0.6)', border: '1px solid rgba(14, 165, 233, 0.2)', borderRadius: '0.375rem', color: '#E2E8F0', fontSize: '0.85rem', outline: 'none', }, searchIcon: { position: 'absolute', left: '2.25rem', top: '50%', transform: 'translateY(-50%)', color: '#475569', }, content: { flex: 1, overflow: 'auto', padding: '1.5rem 2rem', }, toc: { padding: '1rem 1.5rem', borderBottom: '1px solid rgba(14, 165, 233, 0.1)', maxHeight: '200px', overflow: 'auto', flexShrink: 0, }, tocLink: { display: 'block', padding: '0.2rem 0', fontSize: '0.8rem', color: '#7DD3FC', textDecoration: 'none', cursor: 'pointer', }, }; // Static user guide content embedded at build time const USER_GUIDE_CONTENT = `GUIDE_PLACEHOLDER`; export default function UserGuideModal({ isOpen, onClose }) { const [content, setContent] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { if (!isOpen) return; setLoading(true); // ⚠️ CONVENTION: Use credentials: 'include' on all fetch calls for cookie-based auth consistency fetch('/user-guide.md') .then(res => { if (res.ok) return res.text(); throw new Error('Not found'); }) .then(text => { setContent(text); setLoading(false); }) .catch(() => { // Fallback: try API endpoint fetch(`${API_BASE}/knowledge-base/user-guide`, { credentials: 'include' }) .then(res => res.ok ? res.text() : Promise.reject()) .then(text => { setContent(text); setLoading(false); }) .catch(() => { setContent('# User Guide\n\nThe user guide could not be loaded. Contact your administrator.'); setLoading(false); }); }); }, [isOpen]); if (!isOpen) return null; // Filter content by search term (highlight sections containing the term) const displayContent = searchTerm ? content.split('\n').filter((line, i, arr) => { // Show headers and lines within 3 lines of a match if (line.toLowerCase().includes(searchTerm.toLowerCase())) return true; for (let j = Math.max(0, i - 3); j <= Math.min(arr.length - 1, i + 3); j++) { if (arr[j].toLowerCase().includes(searchTerm.toLowerCase())) return true; } return line.startsWith('#'); }).join('\n') : content; return (
Loading guide...
) : ({children}
, li: ({ children }) =>{children}, }} > {displayContent}