File size: 4,000 Bytes
5807365
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import * as LucideIcons from 'lucide-react';
import { X, Search, ChevronRight, BookOpen } from 'lucide-react';
import { useAppConfig } from '../contexts/AppConfigContext';
import { userGuideTopics } from '../data/userGuide';
import '../styles/UserGuide.css';

const UserGuide = () => {
  const { config, advisors } = useAppConfig();
  const [isOpen, setIsOpen] = useState(false);
  const [activeId, setActiveId] = useState(userGuideTopics[0].id);
  const [search, setSearch] = useState('');

  useEffect(() => {
    const open = () => setIsOpen(true);
    window.addEventListener('open-user-guide', open);
    return () => window.removeEventListener('open-user-guide', open);
  }, []);

  useEffect(() => {
    if (!isOpen) return;
    const onKey = (e) => e.key === 'Escape' && setIsOpen(false);
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [isOpen]);

  const appName = config?.app?.title || 'the app';
  const advisorEntries = Object.values(advisors || {});
  const advisorCount = advisorEntries.length;
  const advisorList = advisorEntries
    .map((a) => `- **${a.name}:** ${a.description || a.role || ''}`.trimEnd())
    .join('\n');
  const q = search.toLowerCase().trim();
  const filteredTopics = q
    ? userGuideTopics.filter(t =>
        t.title.toLowerCase().includes(q) || t.content.toLowerCase().includes(q))
    : userGuideTopics;
  const activeTopic = userGuideTopics.find(t => t.id === activeId) || userGuideTopics[0];

  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <div className="ug-overlay" onClick={(e) => e.target === e.currentTarget && setIsOpen(false)}>
      <div className="ug-modal" role="dialog" aria-label="User Guide">
        {/* Header */}
        <div className="ug-header">
          <div className="ug-title">
            <span className="ug-title-icon">
              <BookOpen size={18} />
            </span>
            <span>User Guide</span>
          </div>
          <button className="ug-close" onClick={() => setIsOpen(false)} aria-label="Close">
            <X size={20} />
          </button>
        </div>

        <div className="ug-body">
          {/* Sidebar / TOC */}
          <aside className="ug-sidebar">
            <div className="ug-search">
              <Search size={14} />
              <input
                type="text"
                placeholder="Search the guide…"
                value={search}
                onChange={(e) => setSearch(e.target.value)}
              />
            </div>
            <nav className="ug-toc">
              {filteredTopics.length === 0 && (
                <div className="ug-empty">No matches</div>
              )}
              {filteredTopics.map((t) => {
                const TopicIcon = LucideIcons[t.icon] || BookOpen;
                return (
                  <button
                    key={t.id}
                    className={`ug-toc-item ${t.id === activeId ? 'active' : ''}`}
                    onClick={() => setActiveId(t.id)}
                  >
                    <TopicIcon size={16} />
                    <span>{t.title}</span>
                    <ChevronRight size={14} className="ug-toc-arrow" />
                  </button>
                );
              })}
            </nav>
          </aside>

          {/* Content */}
          <main className="ug-content" key={activeTopic.id}>
            <ReactMarkdown remarkPlugins={[remarkGfm]}>
              {activeTopic.content
                .replace(/\{\{appName\}\}/g, appName)
                .replace(/\{\{advisorCount\}\}/g, String(advisorCount))
                .replace(/\{\{advisorList\}\}/g, advisorList)}
            </ReactMarkdown>
          </main>
        </div>
      </div>
    </div>,
    document.body
  );
};

export default UserGuide;