From c7107a50348da3787f692ade67777eddb14d1469 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sat, 17 Jan 2026 10:49:16 -0500 Subject: [PATCH] feat: add user guide documentation sidebar - Add comprehensive user guide with 10 documentation collections - Include 49 articles covering all Checkmate features - Implement sidebar navigation with search functionality - Support light/dark theme with proper color tokens - Add article content renderer with multiple block types - Include image lightbox for documentation screenshots --- client/src/App.jsx | 9 +- .../v1/Layouts/HomeLayout/index.jsx | 2 + client/src/Components/v1/Sidebar/index.jsx | 2 + .../Components/v1/UserGuide/ArticlePage.css | 157 + .../Components/v1/UserGuide/ArticlePage.jsx | 329 ++ .../v1/UserGuide/CollectionPage.css | 31 + .../v1/UserGuide/CollectionPage.jsx | 173 ++ .../v1/UserGuide/ContentRenderer.css | 45 + .../v1/UserGuide/ContentRenderer.jsx | 902 ++++++ .../Components/v1/UserGuide/HelpSection.css | 14 + .../Components/v1/UserGuide/HelpSection.jsx | 126 + .../Components/v1/UserGuide/ImageLightbox.jsx | 121 + .../Components/v1/UserGuide/STYLE_GUIDE.md | 606 ++++ .../Components/v1/UserGuide/SearchResults.css | 10 + .../Components/v1/UserGuide/SearchResults.jsx | 173 ++ .../Components/v1/UserGuide/SidebarHeader.css | 40 + .../Components/v1/UserGuide/SidebarHeader.jsx | 381 +++ .../v1/UserGuide/SidebarWrapper.css | 11 + .../v1/UserGuide/SidebarWrapper.jsx | 475 +++ client/src/Components/v1/UserGuide/TabBar.css | 13 + client/src/Components/v1/UserGuide/TabBar.jsx | 86 + .../v1/UserGuide/UserGuideLanding.css | 39 + .../v1/UserGuide/UserGuideLanding.jsx | 210 ++ .../v1/UserGuide/UserGuideSidebarContext.jsx | 99 + .../v1/UserGuide/content/contentTypes.js | 176 ++ .../Components/v1/UserGuide/content/index.js | 2666 +++++++++++++++++ .../v1/UserGuide/content/userGuideConfig.js | 524 ++++ client/src/Components/v1/UserGuide/index.jsx | 24 + .../Components/v1/UserGuide/styles/theme.js | 222 ++ .../v1/UserGuide/styles/useUserGuideTheme.js | 123 + 30 files changed, 7786 insertions(+), 3 deletions(-) create mode 100644 client/src/Components/v1/UserGuide/ArticlePage.css create mode 100644 client/src/Components/v1/UserGuide/ArticlePage.jsx create mode 100644 client/src/Components/v1/UserGuide/CollectionPage.css create mode 100644 client/src/Components/v1/UserGuide/CollectionPage.jsx create mode 100644 client/src/Components/v1/UserGuide/ContentRenderer.css create mode 100644 client/src/Components/v1/UserGuide/ContentRenderer.jsx create mode 100644 client/src/Components/v1/UserGuide/HelpSection.css create mode 100644 client/src/Components/v1/UserGuide/HelpSection.jsx create mode 100644 client/src/Components/v1/UserGuide/ImageLightbox.jsx create mode 100644 client/src/Components/v1/UserGuide/STYLE_GUIDE.md create mode 100644 client/src/Components/v1/UserGuide/SearchResults.css create mode 100644 client/src/Components/v1/UserGuide/SearchResults.jsx create mode 100644 client/src/Components/v1/UserGuide/SidebarHeader.css create mode 100644 client/src/Components/v1/UserGuide/SidebarHeader.jsx create mode 100644 client/src/Components/v1/UserGuide/SidebarWrapper.css create mode 100644 client/src/Components/v1/UserGuide/SidebarWrapper.jsx create mode 100644 client/src/Components/v1/UserGuide/TabBar.css create mode 100644 client/src/Components/v1/UserGuide/TabBar.jsx create mode 100644 client/src/Components/v1/UserGuide/UserGuideLanding.css create mode 100644 client/src/Components/v1/UserGuide/UserGuideLanding.jsx create mode 100644 client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx create mode 100644 client/src/Components/v1/UserGuide/content/contentTypes.js create mode 100644 client/src/Components/v1/UserGuide/content/index.js create mode 100644 client/src/Components/v1/UserGuide/content/userGuideConfig.js create mode 100644 client/src/Components/v1/UserGuide/index.jsx create mode 100644 client/src/Components/v1/UserGuide/styles/theme.js create mode 100644 client/src/Components/v1/UserGuide/styles/useUserGuideTheme.js diff --git a/client/src/App.jsx b/client/src/App.jsx index d21850ebb..b3f3f40dc 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -10,6 +10,7 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Routes } from "./Routes"; import AppLayout from "@/Components/v1/Layouts/AppLayout"; +import { UserGuideSidebarProvider } from "@/Components/v1/UserGuide"; function App() { const mode = useSelector((state) => state.ui.mode); @@ -25,9 +26,11 @@ function App() { return ( - - - + + + + + ); diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx index 3e63aff37..cb28ddc83 100644 --- a/client/src/Components/v1/Layouts/HomeLayout/index.jsx +++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx @@ -1,6 +1,7 @@ import Sidebar from "../../Sidebar/index.jsx"; import { Outlet } from "react-router"; import { Stack } from "@mui/material"; +import { UserGuideSidebar } from "../../UserGuide"; import "./index.css"; @@ -15,6 +16,7 @@ const HomeLayout = () => { + ); }; diff --git a/client/src/Components/v1/Sidebar/index.jsx b/client/src/Components/v1/Sidebar/index.jsx index b66f081fb..cff8fb8c8 100644 --- a/client/src/Components/v1/Sidebar/index.jsx +++ b/client/src/Components/v1/Sidebar/index.jsx @@ -89,6 +89,8 @@ const Sidebar = () => { gap={theme.spacing(6)} sx={{ transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", + backgroundColor: theme.palette.primaryBackground.main, + zIndex: 1000, }} > diff --git a/client/src/Components/v1/UserGuide/ArticlePage.css b/client/src/Components/v1/UserGuide/ArticlePage.css new file mode 100644 index 000000000..fd1e1b7e6 --- /dev/null +++ b/client/src/Components/v1/UserGuide/ArticlePage.css @@ -0,0 +1,157 @@ +/* ArticlePage styles - hover states and nested selectors (Dark theme) */ + +/* Breadcrumb hover */ +.user-guide-breadcrumb-link { + transition: color 150ms ease; +} + +.user-guide-breadcrumb-link:hover { + color: #1570EF; +} + +/* ToC link hover */ +.user-guide-toc-link { + transition: all 150ms ease; +} + +.user-guide-toc-link:hover { + color: #1570EF; + text-decoration: underline !important; +} + +/* Navigation button hover */ +.user-guide-nav-button .nav-text { + transition: color 150ms ease; +} + +.user-guide-nav-button:hover .nav-text { + color: #1570EF; +} + +/* Article body typography */ +.user-guide-article-body h2 { + font-size: 18px; + font-weight: 600; + color: #ffffff; + margin-top: 32px; + margin-bottom: 16px; + line-height: 1.4; +} + +.user-guide-article-body h3 { + font-size: 14px; + font-weight: 600; + color: #ffffff; + margin-top: 24px; + margin-bottom: 12px; + line-height: 1.4; +} + +.user-guide-article-body p { + font-size: 13px; + color: #d0d5dd; + line-height: 1.5; + margin-bottom: 16px; + margin-top: 0; +} + +.user-guide-article-body ul, +.user-guide-article-body ol { + padding-left: 24px; + margin-bottom: 16px; + margin-top: 0; +} + +.user-guide-article-body li { + font-size: 13px; + color: #d0d5dd; + line-height: 1.5; + margin-bottom: 8px; +} + +.user-guide-article-body a { + color: #1570EF; + text-decoration: none; + font-weight: 500; +} + +.user-guide-article-body a:hover { + text-decoration: underline; +} + +.user-guide-article-body code { + background-color: #2d2d2d; + padding: 2px 6px; + border-radius: 4px; + font-size: 10px; + font-family: "Geist Mono", "Fira Code", "Consolas", monospace; + color: #d0d5dd; +} + +.user-guide-article-body pre { + background-color: #1c2130; + border-radius: 4px; + padding: 16px; + overflow: auto; + margin-bottom: 16px; + margin-top: 0; +} + +.user-guide-article-body pre code { + background-color: transparent; + padding: 0; + color: #e5e7eb; +} + +.user-guide-article-body blockquote { + border-left: 3px solid #1570EF; + background-color: rgba(255, 255, 255, 0.05); + padding: 12px 16px; + border-radius: 0 4px 4px 0; + margin-bottom: 16px; + margin-top: 0; + margin-left: 0; + margin-right: 0; +} + +.user-guide-article-body blockquote p { + margin-bottom: 0; + color: #d0d5dd; +} + +.user-guide-article-body table { + width: 100%; + border-collapse: collapse; + margin-bottom: 16px; +} + +.user-guide-article-body th, +.user-guide-article-body td { + border: 1px solid #344054; + padding: 12px; + text-align: left; + font-size: 10px; +} + +.user-guide-article-body th { + background-color: rgba(255, 255, 255, 0.05); + font-weight: 600; + color: #ffffff; +} + +.user-guide-article-body td { + color: #d0d5dd; +} + +.user-guide-article-body img { + max-width: 100%; + border-radius: 4px; + border: 1px solid #344054; + margin-bottom: 16px; +} + +.user-guide-article-body hr { + border: none; + border-top: 1px solid #344054; + margin: 32px 0; +} diff --git a/client/src/Components/v1/UserGuide/ArticlePage.jsx b/client/src/Components/v1/UserGuide/ArticlePage.jsx new file mode 100644 index 000000000..b90072a3f --- /dev/null +++ b/client/src/Components/v1/UserGuide/ArticlePage.jsx @@ -0,0 +1,329 @@ +import { useState, useEffect } from "react"; +import { + ChevronLeft, + ChevronRight, + Rocket, + Globe, + Gauge, + Link, + Bell, + AlertTriangle, + Settings, + FileText, +} from "lucide-react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./ArticlePage.css"; + +// Map icon names to actual Lucide components +const iconMap = { + Rocket, + Globe, + Gauge, + Link, + Bell, + AlertTriangle, + Settings, + FileText, +}; + +const ArticlePage = ({ + collection, + article, + onBack, + onBackToHome, + onNextArticle, + onPrevArticle, + nextArticle, + prevArticle, + tocItems, + children, + mode = "in-app", +}) => { + const { colors, typography, spacing, border, isDark } = useUserGuideTheme(); + const IconComponent = iconMap[collection.icon] || Rocket; + const isInApp = mode === "in-app"; + const [activeSection, setActiveSection] = useState(""); + + // Track active section on scroll + useEffect(() => { + if (!tocItems || tocItems.length === 0) return; + + const handleScroll = () => { + const sections = tocItems + .map((item) => { + const id = item.href.replace("#", ""); + const element = document.getElementById(id); + return { id: item.href, element }; + }) + .filter((s) => s.element); + + if (sections.length === 0) return; + + let currentSection = sections[0].id; + + for (const section of sections) { + if (section.element) { + const rect = section.element.getBoundingClientRect(); + if (rect.top <= 150) { + currentSection = section.id; + } + } + } + + setActiveSection(currentSection); + }; + + window.addEventListener("scroll", handleScroll, { passive: true }); + document.addEventListener("scroll", handleScroll, { passive: true, capture: true }); + handleScroll(); + + return () => { + window.removeEventListener("scroll", handleScroll); + document.removeEventListener("scroll", handleScroll, { capture: true }); + }; + }, [tocItems]); + + return ( +
+ {/* Main content area */} +
+ {/* Article Content */} +
+ {/* Title */} +

+ {article.title} +

+ + {/* Description */} +

+ {article.description} +

+ + {/* Table of Contents - at top */} + {tocItems && tocItems.length > 0 && ( + + )} + + {/* Article Body */} +
+ {children || ( +
+ + Article content coming soon. + +
+ )} +
+ + {/* Navigation */} + {(prevArticle || nextArticle) && ( +
+ {prevArticle ? ( +
+ +
+ + Previous + + + {prevArticle.title} + +
+
+ ) : ( +
+ )} + {nextArticle && ( +
+
+ + Next + + + {nextArticle.title} + +
+ +
+ )} +
+ )} +
+
+
+ ); +}; + +export default ArticlePage; diff --git a/client/src/Components/v1/UserGuide/CollectionPage.css b/client/src/Components/v1/UserGuide/CollectionPage.css new file mode 100644 index 000000000..6adf5bf44 --- /dev/null +++ b/client/src/Components/v1/UserGuide/CollectionPage.css @@ -0,0 +1,31 @@ +/* CollectionPage styles - hover states */ + +/* Back link hover */ +.user-guide-back-link { + transition: color 150ms ease; +} + +.user-guide-back-link:hover { + color: #1570EF; +} + +/* Article row hover */ +.user-guide-article-row { + transition: all 150ms ease; +} + +.user-guide-article-row:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +[data-mui-color-scheme="dark"] .user-guide-article-row:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-article-row .arrow-icon { + transition: all 150ms ease; +} + +.user-guide-article-row:hover .arrow-icon { + color: #1570EF; +} diff --git a/client/src/Components/v1/UserGuide/CollectionPage.jsx b/client/src/Components/v1/UserGuide/CollectionPage.jsx new file mode 100644 index 000000000..fa2e83c7c --- /dev/null +++ b/client/src/Components/v1/UserGuide/CollectionPage.jsx @@ -0,0 +1,173 @@ +import { + ArrowRight, + FileText, + Rocket, + Globe, + Gauge, + Link, + Bell, + AlertTriangle, + Settings, +} from "lucide-react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./CollectionPage.css"; + +// Map icon names to actual Lucide components +const iconMap = { + Rocket, + Globe, + Gauge, + Link, + Bell, + AlertTriangle, + Settings, + FileText, +}; + +const CollectionPage = ({ collection, onBack, onArticleClick }) => { + const { colors, typography, spacing, border } = useUserGuideTheme(); + const IconComponent = iconMap[collection.icon] || Rocket; + + return ( +
+ {/* Header */} +
+
+ {/* Collection Header */} +
+ +
+

+ {collection.title} +

+

+ {collection.description} +

+ + {collection.articleCount} articles in this collection + +
+
+
+
+ + {/* Articles List */} +
+
+ {collection.articles.map((article, index) => ( +
onArticleClick(article.id)} + className="user-guide-article-row" + style={{ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + padding: spacing.lg, + borderBottom: + index < collection.articles.length - 1 ? border.default : "none", + cursor: "pointer", + }} + > +
+ +
+ + {article.title} + + + {article.description} + +
+
+ +
+ ))} +
+
+
+ ); +}; + +export default CollectionPage; diff --git a/client/src/Components/v1/UserGuide/ContentRenderer.css b/client/src/Components/v1/UserGuide/ContentRenderer.css new file mode 100644 index 000000000..563563fe0 --- /dev/null +++ b/client/src/Components/v1/UserGuide/ContentRenderer.css @@ -0,0 +1,45 @@ +/* ContentRenderer styles - hover states and list styling */ + +/* List styling - colors handled by inline styles for theme support */ +.user-guide-content-list li { + /* Font styles handled inline for theme-aware colors */ +} + +/* Table row hover */ +.user-guide-table-row { + transition: background-color 150ms ease; +} + +.user-guide-table-row:hover { + background-color: rgba(0, 0, 0, 0.04) !important; +} + +[data-mui-color-scheme="dark"] .user-guide-table-row:hover { + background-color: rgba(255, 255, 255, 0.05) !important; +} + +/* Article links hover */ +.user-guide-article-link-item { + transition: all 150ms ease; +} + +.user-guide-article-link-item:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +[data-mui-color-scheme="dark"] .user-guide-article-link-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-article-link-item:hover .link-title { + color: #1570EF; +} + +.user-guide-article-link-item .arrow-icon { + transition: all 150ms ease; +} + +.user-guide-article-link-item:hover .arrow-icon { + transform: translateX(4px); + color: #1570EF; +} diff --git a/client/src/Components/v1/UserGuide/ContentRenderer.jsx b/client/src/Components/v1/UserGuide/ContentRenderer.jsx new file mode 100644 index 000000000..2dafd3485 --- /dev/null +++ b/client/src/Components/v1/UserGuide/ContentRenderer.jsx @@ -0,0 +1,902 @@ +import { useState } from "react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import ImageLightbox from "./ImageLightbox"; +import { + CheckCircle, + Clock, + Rocket, + Shield, + Brain, + AlertTriangle, + Server, + Database, + Terminal, + FileText, + FolderKanban, + Gauge, + LayoutDashboard, + Menu, + Settings, + Info, + Lightbulb, + ArrowRight, + Globe, + Bell, + Link, +} from "lucide-react"; +import "./ContentRenderer.css"; + +// Icon mapping +const iconMap = { + CheckCircle, + Clock, + Rocket, + Shield, + Brain, + AlertTriangle, + Server, + Database, + Terminal, + FileText, + FolderKanban, + Gauge, + LayoutDashboard, + Menu, + Settings, + Info, + Lightbulb, + Globe, + Bell, + Link, +}; + +const getIcon = (name) => iconMap[name] || Info; + +const ContentRenderer = ({ content, onNavigate }) => { + const { colors, typography, spacing, border, chipStyles, imageStyles, isDark } = useUserGuideTheme(); + const [lightboxImage, setLightboxImage] = useState(null); + + // Parse basic markdown in text (inside component to access onNavigate) + const parseMarkdown = (text) => { + const parts = []; + let key = 0; + + // Supports: **bold**, `code`, [[link text]](collection/article) + const combinedRegex = /\*\*([^*]+)\*\*|`([^`]+)`|\[\[([^\]]+)\]\]\(([^)]+)\)/g; + let lastIndex = 0; + let match; + + const getChipStyle = (codeContent) => { + const isUrl = /^https?:\/\//.test(codeContent); + return isUrl ? chipStyles.url : chipStyles.code; + }; + + while ((match = combinedRegex.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + + if (match[1]) { + // Bold: **text** + parts.push({match[1]}); + } else if (match[2]) { + // Code: `text` + const style = getChipStyle(match[2]); + parts.push( + + {match[2]} + + ); + } else if (match[3] && match[4]) { + // Article link: [[text]](collection/article) + const linkText = match[3]; + const linkPath = match[4]; + const [collectionId, articleId] = linkPath.split("/"); + parts.push( + onNavigate?.(collectionId, articleId)} + style={{ + color: colors.brand.primary, + cursor: "pointer", + textDecoration: "underline", + textDecorationColor: "transparent", + transition: "text-decoration-color 150ms ease", + }} + onMouseEnter={(e) => { + e.currentTarget.style.textDecorationColor = colors.brand.primary; + }} + onMouseLeave={(e) => { + e.currentTarget.style.textDecorationColor = "transparent"; + }} + > + {linkText} + + ); + } + + lastIndex = match.index + match[0].length; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts.length > 0 ? parts : text; + }; + + const renderBlock = (block, index) => { + switch (block.type) { + case "heading": + return renderHeading(block, index); + case "paragraph": + return renderParagraph(block, index); + case "callout": + return renderCallout(block, index); + case "bullet-list": + return renderBulletList(block, index); + case "ordered-list": + return renderOrderedList(block, index); + case "checklist": + return renderChecklist(block, index); + case "icon-cards": + return renderIconCards(block, index); + case "grid-cards": + return renderGridCards(block, index); + case "time-estimate": + return renderTimeEstimate(block, index); + case "info-box": + return renderInfoBox(block, index); + case "code": + return renderCode(block, index); + case "requirements": + return renderRequirements(block, index); + case "table": + return renderTable(block, index); + case "image": + return renderImage(block, index); + case "article-links": + return renderArticleLinks(block, index); + default: + return null; + } + }; + + const renderHeading = (block, index) => { + const Tag = block.level === 2 ? "h2" : "h3"; + return ( + 0 ? spacing["2xl"] : 0, + lineHeight: typography.lineHeight.normal, + }} + > + {block.text} + + ); + }; + + const renderParagraph = (block, index) => ( +

+ {parseMarkdown(block.text)} +

+ ); + + const renderCallout = (block, index) => { + const calloutColors = { + info: colors.brand.primary, + tip: colors.brand.primary, + warning: colors.brand.warning, + success: colors.brand.success, + }; + const accentColor = calloutColors[block.variant || "info"]; + + return ( +
+ {block.title && ( + + {block.title} + + )} + + {parseMarkdown(block.text)} + +
+ ); + }; + + const renderBulletList = (block, index) => ( +
    + {block.items.map((item, i) => ( +
  • + {item.bold && ( + {item.bold} + )} + {item.bold && — } + + {item.bold ? parseMarkdown(item.text) : parseMarkdown(item.text)} + +
  • + ))} +
+ ); + + const renderOrderedList = (block, index) => ( +
    + {block.items.map((item, i) => ( +
  1. + {item.bold && ( + {item.bold} + )} + {item.bold && " "} + + {item.bold ? parseMarkdown(item.text) : parseMarkdown(item.text)} + +
  2. + ))} +
+ ); + + const renderChecklist = (block, index) => ( +
+ {block.items.map((item, i) => ( +
+ + + {parseMarkdown(item)} + +
+ ))} +
+ ); + + const renderIconCards = (block, index) => ( +
+ {block.items.map((item, i) => { + const IconComponent = getIcon(item.icon); + return ( +
+
+ +
+
+ + {item.title} + + + {parseMarkdown(item.description)} + +
+
+ ); + })} +
+ ); + + const renderGridCards = (block, index) => ( +
+ {block.items.map((item, i) => { + const IconComponent = item.icon ? getIcon(item.icon) : null; + return ( +
+
+ {IconComponent && ( + + + + )} + + {item.title} + +
+ + {parseMarkdown(item.description)} + +
+ ); + })} +
+ ); + + const renderTimeEstimate = (block, index) => ( +
+ + + + + {parseMarkdown(block.text)} + +
+ ); + + const renderInfoBox = (block, index) => { + const IconComponent = block.icon ? getIcon(block.icon) : FolderKanban; + return ( +
+ +
+ + {block.title} + +
    + {block.items.map((item, i) => ( +
  • + {parseMarkdown(item)} +
  • + ))} +
+
+
+ ); + }; + + const renderCode = (block, index) => ( +
+
+				{block.code}
+			
+
+ ); + + const renderRequirements = (block, index) => ( +
+ {block.items.map((req, i) => { + const IconComponent = getIcon(req.icon); + return ( +
+
+ + + {req.title} + +
+
    + {req.items.map((item, j) => ( +
  • + {item} +
  • + ))} +
+
+ ); + })} +
+ ); + + const renderTableCell = (value) => { + if (value === "✓") { + return ( + + ✓ + + ); + } + if (value === "✗" || value === "—") { + return ( + + — + + ); + } + return parseMarkdown(value); + }; + + const renderTable = (block, index) => ( +
+ {/* Header */} +
col.width || "1fr").join(" "), + backgroundColor: isDark ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.03)", + borderBottom: border.default, + padding: `${spacing.md} ${spacing.lg}`, + gap: spacing.md, + }} + > + {block.columns.map((col) => ( + + {col.label} + + ))} +
+ {/* Rows */} + {block.rows.map((row, i) => ( +
col.width || "1fr").join(" "), + borderBottom: i < block.rows.length - 1 ? border.default : "none", + backgroundColor: i % 2 === 1 ? (isDark ? "rgba(255, 255, 255, 0.02)" : "rgba(0, 0, 0, 0.02)") : "transparent", + padding: `10px ${spacing.lg}`, + gap: spacing.md, + alignItems: "center", + }} + > + {block.columns.map((col, j) => ( +
+ {renderTableCell(row[col.key])} +
+ ))} +
+ ))} +
+ ); + + const renderImage = (block, index) => ( +
+ {block.alt} + setLightboxImage({ src: block.src, alt: block.alt, caption: block.caption }) + } + style={{ + ...imageStyles.image, + cursor: "zoom-in", + }} + /> + {block.caption && ( +

+ {block.caption} +

+ )} +
+ ); + + const renderArticleLinks = (block, index) => ( +
+
+ {block.title && ( + + {block.title} + + )} +
+ {block.items.map((item, i) => ( +
onNavigate?.(item.collectionId, item.articleId)} + className="user-guide-article-link-item" + style={{ + display: "flex", + alignItems: "center", + justifyContent: "space-between", + padding: spacing.sm, + margin: `0 -${spacing.sm}`, + borderRadius: "4px", + cursor: "pointer", + }} + > + + {item.title} + + +
+ ))} +
+
+ ); + + return ( + <> +
{content.blocks.map((block, index) => renderBlock(block, index))}
+ + {/* Image Lightbox */} + {lightboxImage && ( + setLightboxImage(null)} + /> + )} + + ); +}; + +export default ContentRenderer; diff --git a/client/src/Components/v1/UserGuide/HelpSection.css b/client/src/Components/v1/UserGuide/HelpSection.css new file mode 100644 index 000000000..297490a74 --- /dev/null +++ b/client/src/Components/v1/UserGuide/HelpSection.css @@ -0,0 +1,14 @@ +/* HelpSection styles */ + +.user-guide-help-button { + transition: all 150ms ease; +} + +.user-guide-help-button:hover { + border-color: #1570EF; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06); +} + +[data-mui-color-scheme="dark"] .user-guide-help-button:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); +} diff --git a/client/src/Components/v1/UserGuide/HelpSection.jsx b/client/src/Components/v1/UserGuide/HelpSection.jsx new file mode 100644 index 000000000..453afefa4 --- /dev/null +++ b/client/src/Components/v1/UserGuide/HelpSection.jsx @@ -0,0 +1,126 @@ +import { ExternalLink } from "lucide-react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./HelpSection.css"; + +const HelpSection = () => { + const { colors, typography, spacing, border } = useUserGuideTheme(); + + return ( +
+ {/* Contact us */} +
+

+ Contact us +

+

+ Can't find what you need? Our support team is here to help. +

+ + Join our Discord + + +
+ + {/* Ask the community */} +
+

+ GitHub discussions +

+

+ Questions about features, how-tos, or use cases? Join the community discussion. +

+ + GitHub discussions + + +
+
+ ); +}; + +export default HelpSection; diff --git a/client/src/Components/v1/UserGuide/ImageLightbox.jsx b/client/src/Components/v1/UserGuide/ImageLightbox.jsx new file mode 100644 index 000000000..ab78b9501 --- /dev/null +++ b/client/src/Components/v1/UserGuide/ImageLightbox.jsx @@ -0,0 +1,121 @@ +import { useEffect, useCallback } from "react"; +import { X } from "lucide-react"; +import { typography, spacing } from "./styles/theme"; + +const ImageLightbox = ({ src, alt, caption, onClose }) => { + // Close on escape key + const handleKeyDown = useCallback( + (e) => { + if (e.key === "Escape") { + onClose(); + } + }, + [onClose] + ); + + useEffect(() => { + document.addEventListener("keydown", handleKeyDown); + document.body.style.overflow = "hidden"; + + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.body.style.overflow = ""; + }; + }, [handleKeyDown]); + + // Close when clicking backdrop + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + return ( +
+ {/* Close button */} + + + {/* Image container */} +
+ {alt} e.stopPropagation()} + /> + {caption && ( +

+ {caption} +

+ )} +
+
+ ); +}; + +export default ImageLightbox; diff --git a/client/src/Components/v1/UserGuide/STYLE_GUIDE.md b/client/src/Components/v1/UserGuide/STYLE_GUIDE.md new file mode 100644 index 000000000..5733595f8 --- /dev/null +++ b/client/src/Components/v1/UserGuide/STYLE_GUIDE.md @@ -0,0 +1,606 @@ +# Checkmate User Guide Style Guide + +This document defines the writing standards, formatting rules, and content patterns for all Checkmate user documentation. + +--- + +## Voice & Tone + +### Core Principles + +1. **Second person, active voice** - Address the reader directly + - ✅ "You can configure monitors from the dashboard" + - ❌ "Monitors can be configured from the dashboard" + +2. **Professional but approachable** - Clear and helpful, not stiff or overly casual + - ✅ "Select the notification channel you want to use" + - ❌ "Go ahead and pick whatever notification channel works for you!" + - ❌ "The user shall select the appropriate notification channel" + +3. **Direct and instructional** - Tell users what to do, not what the system does + - ✅ "Click Save to apply your changes" + - ❌ "The Save button will apply your changes" + +4. **User-focused** - Lead with what users can accomplish + - ✅ "You can track server performance with infrastructure monitors" + - ❌ "Infrastructure monitors use the Capture agent to collect metrics" + +### Language Patterns + +**Opening phrases:** +- "The [Feature] allows you to..." +- "[Feature] is..." +- "You can..." +- "To [action]:" + +**Navigation instructions:** +- "Navigate to [location]" +- "Click [element]" +- "Select [option]" +- "From the [page], click..." + +**Avoid:** +- Passive voice where active is clearer +- Jargon without explanation +- Assuming prior knowledge +- Marketing language ("powerful", "seamless", "robust") + +--- + +## Content Structure + +### Standard Article Pattern + +Every article should follow this flow: + +``` +1. OVERVIEW (H2) + - 1-2 paragraphs explaining what and why + - Set context for the topic + +2. VISUAL (Optional) + - Screenshot showing the feature + - Include after intro, not before + +3. MAIN CONTENT (H2/H3 sections) + - Procedures with ordered lists + - Features with bullet lists + - Reference info in tables + +4. TIPS & WARNINGS (Callouts) + - Best practices + - Common pitfalls + - Important restrictions + +5. RELATED ARTICLES (Links) + - 2-4 related topics + - Logical next steps +``` + +### Heading Hierarchy + +Use only two heading levels within articles: + +- **H2** - Major sections (Overview, Configuration, Settings) +- **H3** - Subsections within H2s (Creating a monitor, Editing settings) + +Never use H4 or deeper. If content requires more nesting, restructure into separate sections. + +### Section Length + +| Element | Target Length | +|---------|---------------| +| Paragraphs | 2-4 sentences | +| H2 sections | 100-300 words | +| H3 sections | 50-150 words | +| Full articles | 800-1500 words | + +--- + +## Formatting Rules + +### Lists + +**Bullet lists** - Use for features, options, characteristics (unordered items) +```javascript +{ + type: 'bullet-list', + items: [ + { text: 'HTTP monitors check website availability' }, + { text: 'Ping monitors verify server reachability' }, + { text: 'Port monitors test specific service ports' } + ] +} +``` + +**Ordered lists** - Use for step-by-step procedures (sequential actions) +```javascript +{ + type: 'ordered-list', + items: [ + { text: 'Navigate to **Uptime** in the sidebar' }, + { text: 'Click **Create monitor**' }, + { text: 'Enter the URL you want to monitor' }, + { text: 'Click **Save** to create the monitor' } + ] +} +``` + +**List guidelines:** +- Keep procedures to 4-6 steps maximum +- If longer, break into multiple procedures +- Start each item with an action verb for procedures +- Use parallel structure (all items same grammatical form) + +### Callouts + +Use callouts sparingly for important information that shouldn't be missed. + +**Types:** + +| Variant | Use For | Example | +|---------|---------|---------| +| `info` | Additional context, clarification | "Monitors check at the interval you specify, starting from creation time." | +| `tip` | Best practices, recommendations | "Use descriptive names to easily identify monitors in alerts." | +| `warning` | Restrictions, potential issues | "Deleting a monitor removes all associated check history." | +| `success` | Confirmations, positive outcomes | "Your monitor is now active and checking every 5 minutes." | + +```javascript +{ + type: 'callout', + variant: 'tip', + title: 'Best practice', + text: 'Group related monitors together for easier management.' +} +``` + +**Callout guidelines:** +- Maximum 2-3 callouts per article +- Keep callout text concise (1-2 sentences) +- Title is optional but helps scannability +- Don't use callouts for routine information + +### Inline Formatting + +**Bold** - Use for: +- UI element names: "Click **Save**" +- Key terms on first use: "**Uptime** measures availability over time" +- Emphasis in lists: "**Name** — The display name for your monitor" + +**Code/monospace** - Use for: +- URLs: `https://example.com` +- File paths: `/api/v1/monitors` +- Technical values: `200`, `timeout` +- Commands: `npm run dev` + +```javascript +// In paragraph text, use backticks in markdown: +{ text: 'Enter the URL (e.g., `https://example.com`) in the field' } +``` + +### Tables + +Use tables for: +- Feature comparisons +- Configuration options +- Reference data with multiple attributes + +```javascript +{ + type: 'table', + columns: [ + { key: 'type', label: 'Monitor Type', width: '1fr' }, + { key: 'use', label: 'Best For', width: '2fr' } + ], + rows: [ + { type: 'HTTP', use: 'Websites and APIs' }, + { type: 'Ping', use: 'Server availability' }, + { type: 'Port', use: 'Specific services (databases, mail servers)' } + ] +} +``` + +--- + +## Content Types + +### Block Types Reference + +| Type | Purpose | When to Use | +|------|---------|-------------| +| `heading` | Section titles | Start each major topic | +| `paragraph` | Body text | Explanations, context | +| `bullet-list` | Unordered items | Features, options | +| `ordered-list` | Sequential steps | Procedures | +| `checklist` | Checkable items | Requirements, checklists | +| `callout` | Important notices | Tips, warnings | +| `code` | Code blocks | Commands, config examples | +| `table` | Structured data | Comparisons, reference | +| `image` | Screenshots | UI demonstration | +| `icon-cards` | Feature grids | Capability overviews | +| `article-links` | Related content | End-of-article navigation | + +### Icon Cards + +Use for presenting 4-8 related features or concepts: + +```javascript +{ + type: 'icon-cards', + items: [ + { + icon: 'Globe', + title: 'HTTP monitoring', + description: 'Check website availability and response times' + }, + { + icon: 'Server', + title: 'Infrastructure', + description: 'Monitor CPU, memory, and disk usage' + } + ] +} +``` + +### Article Links + +Always end articles with related content: + +```javascript +{ + type: 'article-links', + title: 'Related articles', + items: [ + { + collectionId: 'uptime-monitoring', + articleId: 'http-monitors', + title: 'Creating HTTP monitors', + description: 'Set up your first website monitor' + } + ] +} +``` + +--- + +## Images & Screenshots + +### When to Include Images + +- After introducing a feature (not before) +- For complex UI that benefits from visual reference +- For multi-step workflows +- Skip for simple, text-describable actions + +### Image Requirements + +```javascript +{ + type: 'image', + src: '/images/user-guide/monitor-create.png', + alt: 'Monitor creation form showing URL field, name field, and interval selector', + caption: 'The monitor creation form with required fields highlighted' +} +``` + +**Alt text guidelines:** +- Describe what the image shows (15-30 words) +- Include key UI elements visible +- Make it useful for screen readers + +**Caption guidelines:** +- Explain the context or what to notice +- Keep to one sentence +- Optional but recommended + +### Screenshot Standards + +- Capture at consistent browser width (1200px recommended) +- Use light theme for screenshots +- Highlight relevant areas if needed +- Avoid personal data in screenshots +- Crop to show relevant UI only + +--- + +## Writing Procedures + +### Step-by-Step Instructions + +**Format:** +```javascript +{ + type: 'ordered-list', + items: [ + { text: 'Navigate to **Uptime** in the sidebar' }, + { text: 'Click **Create monitor** in the top right' }, + { text: 'Enter the URL you want to monitor' }, + { text: 'Set the check interval (default: 5 minutes)' }, + { text: 'Click **Save** to create the monitor' } + ] +} +``` + +**Guidelines:** +- Start each step with an action verb (Click, Enter, Select, Navigate) +- One action per step +- Include the location of UI elements +- Bold all clickable elements +- Add context in parentheses when helpful + +### Describing Features + +**Format:** +```javascript +{ + type: 'bullet-list', + items: [ + { bold: 'Name', text: 'A descriptive label for your monitor' }, + { bold: 'URL', text: 'The address to check (must include protocol)' }, + { bold: 'Interval', text: 'How often to perform checks (1-60 minutes)' } + ] +} +``` + +--- + +## Terminology + +### Consistent Terms + +Use these terms consistently throughout documentation: + +| Term | Use | Don't Use | +|------|-----|-----------| +| Monitor | The configured check | Watcher, probe, check | +| Check | A single monitoring event | Ping, request, poll | +| Incident | A detected problem | Outage, alert, issue | +| Notification | Alert sent to user | Message, email, ping | +| Dashboard | Main overview page | Home, overview | +| Sidebar | Left navigation | Menu, nav | + +### Feature Names + +Always match UI exactly: +- **Uptime** (not "Uptime Monitoring") +- **PageSpeed** (not "Page Speed" or "Pagespeed") +- **Infrastructure** (not "Server Monitoring") +- **Status pages** (not "Status Pages" - lowercase 'p') + +--- + +## Article Templates + +### Getting Started Article + +```javascript +{ + blocks: [ + { + type: 'heading', + level: 2, + text: 'Overview', + id: 'overview' + }, + { + type: 'paragraph', + text: '[1-2 sentences: What this feature/concept is]' + }, + { + type: 'paragraph', + text: '[1-2 sentences: Why it matters to users]' + }, + { + type: 'heading', + level: 2, + text: '[Main topic]', + id: 'main-topic' + }, + // ... content blocks + { + type: 'article-links', + title: 'Next steps', + items: [ + // 2-3 logical next articles + ] + } + ] +} +``` + +### How-To Article + +```javascript +{ + blocks: [ + { + type: 'heading', + level: 2, + text: 'Overview', + id: 'overview' + }, + { + type: 'paragraph', + text: '[Brief description of what user will accomplish]' + }, + { + type: 'heading', + level: 2, + text: '[Action: Creating/Configuring/Setting up...]', + id: 'action' + }, + { + type: 'ordered-list', + items: [ + // 4-6 steps + ] + }, + { + type: 'callout', + variant: 'tip', + text: '[Best practice or helpful hint]' + }, + { + type: 'article-links', + title: 'Related articles', + items: [] + } + ] +} +``` + +### Reference Article + +```javascript +{ + blocks: [ + { + type: 'heading', + level: 2, + text: 'Overview', + id: 'overview' + }, + { + type: 'paragraph', + text: '[What this reference covers]' + }, + { + type: 'heading', + level: 2, + text: '[Category 1]', + id: 'category-1' + }, + { + type: 'table', + columns: [...], + rows: [...] + }, + // More categories as needed + { + type: 'article-links', + title: 'Related articles', + items: [] + } + ] +} +``` + +--- + +## Quality Checklist + +Before publishing any article, verify: + +### Content +- [ ] Overview explains what AND why +- [ ] Procedures are tested and accurate +- [ ] All UI element names match exactly +- [ ] No assumptions about user knowledge +- [ ] Related articles are linked + +### Formatting +- [ ] Only H2 and H3 headings used +- [ ] All headings have unique IDs +- [ ] Lists use correct type (bullet vs ordered) +- [ ] Callouts are used sparingly (max 3) +- [ ] Bold used for UI elements + +### Style +- [ ] Active voice throughout +- [ ] Second person ("you") +- [ ] Action verbs start procedure steps +- [ ] Consistent terminology +- [ ] No marketing language + +### Technical +- [ ] All links work +- [ ] Images have alt text +- [ ] Code examples are correct +- [ ] No placeholder content + +--- + +## Available Icons + +For icon-cards and other icon references, use these Lucide icon names: + +**Navigation:** +- `Globe` - Uptime/HTTP +- `Gauge` - PageSpeed +- `Server` - Infrastructure +- `Bell` - Notifications +- `AlertTriangle` - Incidents +- `Wifi` - Status pages +- `Wrench` - Maintenance +- `Settings` - Settings +- `Users` - Team + +**Actions:** +- `CheckCircle` - Success/complete +- `Clock` - Time/scheduling +- `Info` - Information +- `Lightbulb` - Tips +- `ArrowRight` - Navigation + +**Features:** +- `Database` - Data/storage +- `Terminal` - Commands +- `FileText` - Documentation +- `Shield` - Security +- `Rocket` - Getting started + +--- + +## File Structure + +Articles are stored in `/content/index.js`: + +```javascript +export const articleContents = { + 'collection-id': { + 'article-id': { + blocks: [ + // Content blocks + ] + } + } +}; +``` + +Article metadata is in `/content/userGuideConfig.js`: + +```javascript +{ + id: 'article-id', + title: 'Article Title', + description: 'Brief description for search and previews', + keywords: ['search', 'terms', 'for', 'this', 'article'] +} +``` + +--- + +## Examples + +### Good Paragraph +> "HTTP monitors check your websites and APIs at regular intervals. When a check fails—due to a timeout, error response, or connection issue—Checkmate creates an incident and sends notifications to your configured channels." + +### Good Procedure +> 1. Navigate to **Uptime** in the sidebar +> 2. Click **Create monitor** in the top right +> 3. Enter the URL you want to monitor (include `https://`) +> 4. Set your preferred check interval +> 5. Click **Save** to create the monitor + +### Good Callout +> **Tip:** Use descriptive monitor names like "Production API" or "Marketing Website" to quickly identify issues in notifications. + +### Good Feature List +> - **Name** — A descriptive label for easy identification +> - **URL** — The address to monitor (must include protocol) +> - **Interval** — How often to check (1-60 minutes) +> - **Timeout** — How long to wait before marking as failed + +--- + +*Last updated: January 2025* diff --git a/client/src/Components/v1/UserGuide/SearchResults.css b/client/src/Components/v1/UserGuide/SearchResults.css new file mode 100644 index 000000000..3d027be4e --- /dev/null +++ b/client/src/Components/v1/UserGuide/SearchResults.css @@ -0,0 +1,10 @@ +/* SearchResults styles (Dark theme) */ + +.user-guide-search-result-item { + transition: background-color 150ms ease, border-color 150ms ease; +} + +.user-guide-search-result-item:hover { + background-color: rgba(255, 255, 255, 0.05) !important; + border-color: #4a5568 !important; +} diff --git a/client/src/Components/v1/UserGuide/SearchResults.jsx b/client/src/Components/v1/UserGuide/SearchResults.jsx new file mode 100644 index 000000000..86cb2f4c5 --- /dev/null +++ b/client/src/Components/v1/UserGuide/SearchResults.jsx @@ -0,0 +1,173 @@ +import { FileText, ArrowRight } from "lucide-react"; +import { searchArticles } from "./content/userGuideConfig"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./SearchResults.css"; + +const SearchResults = ({ query, onNavigate, onClearSearch }) => { + const { colors, typography, spacing, border } = useUserGuideTheme(); + const results = searchArticles(query); + + const handleResultClick = (result) => { + onNavigate(result.collectionId, result.articleId); + onClearSearch(); + }; + + if (query.trim().length < 2) { + return ( +
+

+ Type at least 2 characters to search... +

+
+ ); + } + + if (results.length === 0) { + return ( +
+

+ No results found for "{query}" +

+

+ Try different keywords or browse by topic +

+
+ ); + } + + return ( +
+
+ + {results.length} result{results.length !== 1 ? "s" : ""} for "{query}" + + +
+ +
+ {results.map((result) => ( +
handleResultClick(result)} + className="user-guide-search-result-item" + style={{ + display: "flex", + alignItems: "flex-start", + gap: spacing.md, + padding: spacing.md, + backgroundColor: colors.background.white, + border: border.default, + borderRadius: border.radius, + cursor: "pointer", + }} + > + +
+ + {result.articleTitle} + + + {result.articleDescription} + + + in {result.collectionTitle} + +
+ +
+ ))} +
+
+ ); +}; + +export default SearchResults; diff --git a/client/src/Components/v1/UserGuide/SidebarHeader.css b/client/src/Components/v1/UserGuide/SidebarHeader.css new file mode 100644 index 000000000..11b69c153 --- /dev/null +++ b/client/src/Components/v1/UserGuide/SidebarHeader.css @@ -0,0 +1,40 @@ +/* SidebarHeader styles - Dark theme */ + +.user-guide-header-icon-button { + transition: all 150ms ease; +} + +.user-guide-header-icon-button:hover { + background-color: rgba(255, 255, 255, 0.1) !important; + color: #1570EF !important; +} + +.user-guide-header-icon-button.active { + background-color: #0d0d0d; + color: #1570EF; +} + +.user-guide-breadcrumb-dropdown-trigger { + transition: background-color 150ms ease; +} + +.user-guide-breadcrumb-dropdown-trigger:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-breadcrumb-item { + transition: background-color 150ms ease; +} + +.user-guide-breadcrumb-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-search-input:focus { + outline: none; + border-color: #1570EF; +} + +.user-guide-search-input::placeholder { + color: #667085; +} diff --git a/client/src/Components/v1/UserGuide/SidebarHeader.jsx b/client/src/Components/v1/UserGuide/SidebarHeader.jsx new file mode 100644 index 000000000..cc3137d3d --- /dev/null +++ b/client/src/Components/v1/UserGuide/SidebarHeader.jsx @@ -0,0 +1,381 @@ +import { useState, useRef, useEffect } from "react"; +import { + Home, + ChevronDown, + ChevronLeft, + ChevronRight, + Search, + ExternalLink, + X, +} from "lucide-react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./SidebarHeader.css"; + +const SidebarHeader = ({ + breadcrumbs, + onHomeClick, + onBack, + onForward, + canGoBack, + canGoForward, + onClose, + onOpenInNewTab, + isSearchOpen, + onSearchToggle, + searchQuery, + onSearchChange, + showOpenInNewTab = true, + showNavigation = true, +}) => { + const { colors, typography, spacing, border, isDark } = useUserGuideTheme(); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + const searchInputRef = useRef(null); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsDropdownOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + // Focus search input when opened + useEffect(() => { + if (isSearchOpen && searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [isSearchOpen]); + + // Get current breadcrumb (last item) + const currentBreadcrumb = breadcrumbs[breadcrumbs.length - 1]; + + return ( +
+ {/* Main header row */} +
+ {/* Left side: Navigation + Breadcrumb dropdown */} +
+ {/* Navigation buttons - only shown when showNavigation is true */} + {showNavigation && ( + <> + {/* Back button */} + + + {/* Forward button */} + + + {/* Home button */} + + + )} + + {/* Breadcrumb dropdown */} +
+ + + {/* Dropdown menu */} + {isDropdownOpen && breadcrumbs.length > 1 && ( +
+ {breadcrumbs.slice(0, -1).map((item, index) => ( + + ))} +
+ )} +
+
+ + {/* Right side: Actions */} +
+ {/* Open in new tab - only shown when showOpenInNewTab is true */} + {showOpenInNewTab && ( + + )} + + {/* Search toggle */} + + + {/* Close button */} + +
+
+ + {/* Search input (collapsible) */} + {isSearchOpen && ( +
+
+ + onSearchChange(e.target.value)} + className="user-guide-search-input" + style={{ + width: "100%", + height: 32, + fontSize: typography.fontSize.sm, + fontFamily: typography.fontFamily.sans, + backgroundColor: colors.background.alt, + borderRadius: border.radius, + border: border.default, + paddingLeft: 32, + paddingRight: 12, + boxSizing: "border-box", + color: colors.text.primary, + }} + /> +
+
+ )} +
+ ); +}; + +export default SidebarHeader; diff --git a/client/src/Components/v1/UserGuide/SidebarWrapper.css b/client/src/Components/v1/UserGuide/SidebarWrapper.css new file mode 100644 index 000000000..d5035982c --- /dev/null +++ b/client/src/Components/v1/UserGuide/SidebarWrapper.css @@ -0,0 +1,11 @@ +/* SidebarWrapper styles (Dark theme) */ + +.user-guide-sidebar-container { + box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3); +} + +/* Push main content when sidebar is open */ +body.user-guide-sidebar-open .home-content-wrapper { + margin-right: var(--help-sidebar-padding, 440px); + transition: margin-right 300ms ease-in-out; +} diff --git a/client/src/Components/v1/UserGuide/SidebarWrapper.jsx b/client/src/Components/v1/UserGuide/SidebarWrapper.jsx new file mode 100644 index 000000000..85ac7e9e5 --- /dev/null +++ b/client/src/Components/v1/UserGuide/SidebarWrapper.jsx @@ -0,0 +1,475 @@ +import { useState, useEffect, useCallback, useRef } from "react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import TabBar from "./TabBar"; +import SidebarHeader from "./SidebarHeader"; +import UserGuideLanding from "./UserGuideLanding"; +import CollectionPage from "./CollectionPage"; +import ArticlePage from "./ArticlePage"; +import ContentRenderer from "./ContentRenderer"; +import HelpSection from "./HelpSection"; +import SearchResults from "./SearchResults"; +import { getCollection, getArticle } from "./content/userGuideConfig"; +import { getArticleContent } from "./content"; +import { extractToc } from "./content/contentTypes"; +import { useUserGuideSidebarContext, DEFAULT_CONTENT_WIDTH } from "./UserGuideSidebarContext"; +import "./SidebarWrapper.css"; + +const STORAGE_KEY = "checkmate-sidebar-state"; +const MIN_CONTENT_WIDTH = DEFAULT_CONTENT_WIDTH; +const MAX_CONTENT_WIDTH = DEFAULT_CONTENT_WIDTH * 2; // 100% wider + +const SidebarWrapper = ({ isOpen, onClose, onOpen, initialPath, onOpenInNewTab }) => { + const { setContentWidth: setContextContentWidth } = useUserGuideSidebarContext(); + const { colors, border } = useUserGuideTheme(); + const [activeTab, setActiveTab] = useState("user-guide"); + const [collectionId, setCollectionId] = useState(undefined); + const [articleId, setArticleId] = useState(undefined); + const [searchQuery, setSearchQuery] = useState(""); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [contentWidth, setContentWidthLocal] = useState(DEFAULT_CONTENT_WIDTH); + const [isResizing, setIsResizing] = useState(false); + const [isHoveringHandle, setIsHoveringHandle] = useState(false); + const [mouseY, setMouseY] = useState(0); + const resizeRef = useRef(null); + const handleRef = useRef(null); + + // Sync content width with context whenever it changes + const setContentWidth = useCallback( + (width) => { + setContentWidthLocal(width); + setContextContentWidth(width); + }, + [setContextContentWidth] + ); + + // Initialize context with current width on mount + useEffect(() => { + setContextContentWidth(DEFAULT_CONTENT_WIDTH); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Only run once on mount + + // Navigation history + const [history, setHistory] = useState([ + { collectionId: undefined, articleId: undefined }, + ]); + const [historyIndex, setHistoryIndex] = useState(0); + const isNavigatingRef = useRef(false); + + // Parse initial path on mount + useEffect(() => { + if (initialPath) { + const parts = initialPath.split("/"); + if (parts.length >= 1 && parts[0]) { + setCollectionId(parts[0]); + if (parts.length >= 2 && parts[1]) { + setArticleId(parts[1]); + } + } + } + }, [initialPath]); + + // Persist sidebar state to localStorage + useEffect(() => { + if (isOpen) { + const state = { activeTab, collectionId, articleId }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } + }, [isOpen, activeTab, collectionId, articleId]); + + // Restore state from localStorage on mount + useEffect(() => { + const savedState = localStorage.getItem(STORAGE_KEY); + if (savedState && !initialPath) { + try { + const { + activeTab: savedTab, + collectionId: savedCollection, + articleId: savedArticle, + } = JSON.parse(savedState); + if (savedTab) setActiveTab(savedTab); + if (savedCollection) setCollectionId(savedCollection); + if (savedArticle) setArticleId(savedArticle); + } catch (e) { + // Ignore parse errors + } + } + }, [initialPath]); + + // Track navigation changes in history + useEffect(() => { + if (isNavigatingRef.current) { + isNavigatingRef.current = false; + return; + } + // Only track if this is a new navigation (not from back/forward) + const currentEntry = history[historyIndex]; + if ( + currentEntry?.collectionId !== collectionId || + currentEntry?.articleId !== articleId + ) { + // Truncate forward history and add new entry + const newHistory = [ + ...history.slice(0, historyIndex + 1), + { collectionId, articleId }, + ]; + setHistory(newHistory); + setHistoryIndex(newHistory.length - 1); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [collectionId, articleId]); + + // Navigation handlers + const handleNavigate = useCallback((newCollectionId, newArticleId) => { + setCollectionId(newCollectionId); + setArticleId(newArticleId); + }, []); + + const handleBackToHome = useCallback(() => { + setCollectionId(undefined); + setArticleId(undefined); + }, []); + + // Back/Forward navigation + const canGoBack = historyIndex > 0; + const canGoForward = historyIndex < history.length - 1; + + const handleBack = useCallback(() => { + if (historyIndex > 0) { + isNavigatingRef.current = true; + const prevIndex = historyIndex - 1; + const prevEntry = history[prevIndex]; + setHistoryIndex(prevIndex); + setCollectionId(prevEntry.collectionId); + setArticleId(prevEntry.articleId); + } + }, [historyIndex, history]); + + const handleForward = useCallback(() => { + if (historyIndex < history.length - 1) { + isNavigatingRef.current = true; + const nextIndex = historyIndex + 1; + const nextEntry = history[nextIndex]; + setHistoryIndex(nextIndex); + setCollectionId(nextEntry.collectionId); + setArticleId(nextEntry.articleId); + } + }, [historyIndex, history]); + + const handleBackToCollection = useCallback(() => { + setArticleId(undefined); + }, []); + + const handleHomeClick = useCallback(() => { + setCollectionId(undefined); + setArticleId(undefined); + }, []); + + // Get current collection and article + const collection = collectionId ? getCollection(collectionId) : undefined; + const article = collectionId && articleId ? getArticle(collectionId, articleId) : undefined; + + // Get prev/next articles for navigation + const getAdjacentArticles = () => { + if (!collection || !articleId) return { prev: undefined, next: undefined }; + const currentIndex = collection.articles.findIndex((a) => a.id === articleId); + return { + prev: currentIndex > 0 ? collection.articles[currentIndex - 1] : undefined, + next: + currentIndex < collection.articles.length - 1 + ? collection.articles[currentIndex + 1] + : undefined, + }; + }; + + const { prev: prevArticle, next: nextArticle } = getAdjacentArticles(); + + // Get article content + const articleContent = + collectionId && articleId ? getArticleContent(collectionId, articleId) : undefined; + + // Auto-generate TOC from content headings + const tocItems = articleContent ? extractToc(articleContent.blocks) : undefined; + + // Build breadcrumb items for dropdown + const buildBreadcrumbs = () => { + let items = []; + switch (activeTab) { + case "user-guide": + items = [{ label: "User guide", onClick: handleHomeClick }]; + if (collection) { + items.push({ label: collection.title, onClick: () => setArticleId(undefined) }); + } + if (article) { + items.push({ label: article.title, onClick: () => {} }); + } + return items; + default: + return [{ label: "Help", onClick: () => {} }]; + } + }; + + // Handle "Open in new tab" + const handleOpenInNewTab = () => { + let path = "https://bluewavelabs.gitbook.io/checkmate"; + if (onOpenInNewTab) { + onOpenInNewTab(); + } else { + window.open(path, "_blank", "noopener,noreferrer"); + } + }; + + // Clear search and close search bar + const handleClearSearch = useCallback(() => { + setSearchQuery(""); + setIsSearchOpen(false); + }, []); + + // Render User Guide content + const renderUserGuideContent = () => { + // Show search results when search is active + if (isSearchOpen && searchQuery.trim().length > 0) { + return ( + { + handleNavigate(colId, artId); + handleClearSearch(); + }} + onClearSearch={handleClearSearch} + /> + ); + } + + if (collection && article) { + return ( + handleNavigate(collectionId, prevArticle.id) : undefined + } + onNextArticle={ + nextArticle ? () => handleNavigate(collectionId, nextArticle.id) : undefined + } + tocItems={tocItems} + mode="in-app" + > + {articleContent && ( + + )} + + ); + } + + if (collection) { + return ( + handleNavigate(collectionId, id)} + /> + ); + } + + return ( + + ); + }; + + const contentArea = (tabValue) => { + switch (tabValue) { + case "user-guide": + return renderUserGuideContent(); + default: + return ; + } + }; + + // Handle tab click - open sidebar if closed, close if clicking active tab, or switch tab + const handleTabClick = (tab) => { + if (!isOpen) { + onOpen(); + setActiveTab(tab); + } else if (activeTab === tab) { + // Clicking the already-active tab closes the sidebar + onClose(); + } else { + // Clicking a different tab switches to it + setActiveTab(tab); + } + }; + + // Resize handlers + const handleResizeStart = useCallback( + (e) => { + e.preventDefault(); + setIsResizing(true); + resizeRef.current = { startX: e.clientX, startWidth: contentWidth }; + }, + [contentWidth] + ); + + const handleResizeMove = useCallback( + (e) => { + if (!isResizing || !resizeRef.current) return; + + const deltaX = resizeRef.current.startX - e.clientX; + const newWidth = Math.min( + MAX_CONTENT_WIDTH, + Math.max(MIN_CONTENT_WIDTH, resizeRef.current.startWidth + deltaX) + ); + setContentWidth(newWidth); + }, + [isResizing, setContentWidth] + ); + + const handleResizeEnd = useCallback(() => { + setIsResizing(false); + setIsHoveringHandle(false); + resizeRef.current = null; + }, []); + + // Add/remove resize event listeners + useEffect(() => { + if (isResizing) { + document.addEventListener("mousemove", handleResizeMove); + document.addEventListener("mouseup", handleResizeEnd); + document.body.style.cursor = "ew-resize"; + document.body.style.userSelect = "none"; + } + return () => { + document.removeEventListener("mousemove", handleResizeMove); + document.removeEventListener("mouseup", handleResizeEnd); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + }, [isResizing, handleResizeMove, handleResizeEnd]); + + return ( +
+ {/* Resize Handle - positioned absolutely on the left edge */} +
setIsHoveringHandle(true)} + onMouseLeave={() => !isResizing && setIsHoveringHandle(false)} + onMouseMove={(e) => { + if (handleRef.current) { + const rect = handleRef.current.getBoundingClientRect(); + setMouseY(e.clientY - rect.top); + } + }} + style={{ + position: "absolute", + left: 0, + top: 0, + width: 8, + height: "100%", + cursor: "ew-resize", + zIndex: 20, + display: "flex", + alignItems: "center", + justifyContent: "flex-start", + }} + > + {/* Visual indicator line */} +
+ {/* Grab indicator follows mouse */} + {(isHoveringHandle || isResizing) && ( +
+ )} +
+
+ + {/* Tab Bar - Always visible */} + + + {/* Main Sidebar Content - Slides in/out */} +
+ {isOpen && ( + <> + {/* Header */} + setIsSearchOpen(!isSearchOpen)} + searchQuery={searchQuery} + onSearchChange={setSearchQuery} + /> + + {/* Content Area */} +
+ {contentArea(activeTab)} +
+ + )} +
+
+ ); +}; + +export default SidebarWrapper; diff --git a/client/src/Components/v1/UserGuide/TabBar.css b/client/src/Components/v1/UserGuide/TabBar.css new file mode 100644 index 000000000..733ac7106 --- /dev/null +++ b/client/src/Components/v1/UserGuide/TabBar.css @@ -0,0 +1,13 @@ +/* TabBar styles */ + +.user-guide-tab-item { + transition: all 150ms ease; +} + +.user-guide-tab-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-tab-item.active:hover { + background-color: #1a1a1a; +} diff --git a/client/src/Components/v1/UserGuide/TabBar.jsx b/client/src/Components/v1/UserGuide/TabBar.jsx new file mode 100644 index 000000000..7a8999f94 --- /dev/null +++ b/client/src/Components/v1/UserGuide/TabBar.jsx @@ -0,0 +1,86 @@ +import { BookOpen, HelpCircle } from "lucide-react"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./TabBar.css"; + +const TabItem = ({ label, icon, isActive, onClick, colors, typography }) => { + return ( +
+
+ {icon} +
+ + {label} + +
+ ); +}; + +const TabBar = ({ activeTab, onTabChange }) => { + const { colors, typography } = useUserGuideTheme(); + + return ( +
+ } + isActive={activeTab === "user-guide"} + onClick={() => onTabChange("user-guide")} + colors={colors} + typography={typography} + /> + } + isActive={activeTab === "help"} + onClick={() => onTabChange("help")} + colors={colors} + typography={typography} + /> +
+ ); +}; + +export default TabBar; diff --git a/client/src/Components/v1/UserGuide/UserGuideLanding.css b/client/src/Components/v1/UserGuide/UserGuideLanding.css new file mode 100644 index 000000000..7fe9a6a9a --- /dev/null +++ b/client/src/Components/v1/UserGuide/UserGuideLanding.css @@ -0,0 +1,39 @@ +/* UserGuideLanding styles - hover states */ + +/* Collection card hover */ +.user-guide-collection-card { + transition: all 150ms ease; +} + +.user-guide-collection-card:hover { + border-color: rgba(21, 112, 239, 0.3) !important; + background-color: rgba(21, 112, 239, 0.02) !important; + box-shadow: 0px 2px 8px -2px rgba(0, 0, 0, 0.08); +} + +[data-mui-color-scheme="dark"] .user-guide-collection-card:hover { + border-color: rgba(21, 112, 239, 0.5) !important; + background-color: rgba(21, 112, 239, 0.05) !important; + box-shadow: 0px 4px 16px -4px rgba(0, 0, 0, 0.4); +} + +/* Fast find item hover */ +.user-guide-fast-find-item { + transition: all 150ms ease; +} + +.user-guide-fast-find-item:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +[data-mui-color-scheme="dark"] .user-guide-fast-find-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-fast-find-item .arrow-icon { + transition: all 150ms ease; +} + +.user-guide-fast-find-item:hover .arrow-icon { + color: #1570EF; +} diff --git a/client/src/Components/v1/UserGuide/UserGuideLanding.jsx b/client/src/Components/v1/UserGuide/UserGuideLanding.jsx new file mode 100644 index 000000000..563d7ae31 --- /dev/null +++ b/client/src/Components/v1/UserGuide/UserGuideLanding.jsx @@ -0,0 +1,210 @@ +import { + ArrowRight, + Rocket, + Globe, + Gauge, + Server, + Bell, + AlertTriangle, + Settings, + Wifi, + Wrench, + Users, +} from "lucide-react"; +import { collections, fastFinds, getCollection } from "./content/userGuideConfig"; +import { useUserGuideTheme } from "./styles/useUserGuideTheme"; +import "./UserGuideLanding.css"; + +// Map icon names to actual Lucide components +const iconMap = { + Rocket, + Globe, + Gauge, + Server, + Bell, + AlertTriangle, + Settings, + Wifi, + Wrench, + Users, +}; + +const UserGuideLanding = ({ onNavigate, searchQuery, onSearchChange, mode = "in-app" }) => { + const { colors, typography, spacing, border } = useUserGuideTheme(); + const isInApp = mode === "in-app"; + + return ( +
+ {/* Main Content */} +
+ {/* Collections Grid */} +

+ Browse by topic +

+ +
+ {collections.map((collection) => { + const IconComponent = iconMap[collection.icon] || Rocket; + return ( +
onNavigate(collection.id)} + className="user-guide-collection-card" + style={{ + backgroundColor: colors.background.white, + border: border.default, + borderRadius: border.radius, + padding: spacing.lg, + cursor: "pointer", + }} + > +
+ +
+
+ + {collection.title} + + + {collection.articleCount} articles + +
+ + {collection.description} + +
+
+
+ ); + })} +
+ + {/* About our docs - Only shown in in-app mode */} + {isInApp && ( +
+

+ About our docs +

+

+ There are a few ways to explore our docs: +

+ +
+

+ In the product +

+

+ Look for help icons and tooltips throughout the interface. +

+
+ +
+ )} +
+
+ ); +}; + +export default UserGuideLanding; diff --git a/client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx b/client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx new file mode 100644 index 000000000..bfcd09973 --- /dev/null +++ b/client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx @@ -0,0 +1,99 @@ +import { createContext, useContext, useState, useCallback, useEffect } from "react"; + +const SIDEBAR_STATE_KEY = "checkmate-sidebar-open"; + +// Constants for sidebar dimensions +export const TAB_BAR_WIDTH = 40; +export const DEFAULT_CONTENT_WIDTH = 400; +// Additional gap beyond the sidebar width +export const MIN_GAP = 0; + +const UserGuideSidebarContext = createContext(null); + +export const UserGuideSidebarProvider = ({ children }) => { + const [isOpen, setIsOpen] = useState(() => { + if (typeof window === "undefined") return false; + const saved = localStorage.getItem(SIDEBAR_STATE_KEY); + return saved === "true"; + }); + + const [currentPath, setCurrentPath] = useState(undefined); + const [contentWidth, setContentWidth] = useState(DEFAULT_CONTENT_WIDTH); + + // Calculate total sidebar width and required padding + const totalSidebarWidth = isOpen ? TAB_BAR_WIDTH + contentWidth : TAB_BAR_WIDTH; + const requiredPaddingRight = totalSidebarWidth + MIN_GAP; + + // Set CSS custom properties on document root for global access + useEffect(() => { + if (typeof document === "undefined") return; + + const root = document.documentElement; + root.style.setProperty("--help-sidebar-width", `${totalSidebarWidth}px`); + root.style.setProperty("--help-sidebar-padding", `${requiredPaddingRight}px`); + }, [totalSidebarWidth, requiredPaddingRight]); + + // Update body class for content push effect + useEffect(() => { + if (typeof document === "undefined") return; + + if (isOpen) { + document.body.classList.add("user-guide-sidebar-open"); + } else { + document.body.classList.remove("user-guide-sidebar-open"); + } + + return () => { + document.body.classList.remove("user-guide-sidebar-open"); + }; + }, [isOpen]); + + // Persist state to localStorage + useEffect(() => { + if (typeof window === "undefined") return; + localStorage.setItem(SIDEBAR_STATE_KEY, String(isOpen)); + }, [isOpen]); + + const open = useCallback((path) => { + setCurrentPath(path); + setIsOpen(true); + }, []); + + const close = useCallback(() => { + setIsOpen(false); + }, []); + + const toggle = useCallback(() => { + setIsOpen((prev) => !prev); + }, []); + + return ( + + {children} + + ); +}; + +export const useUserGuideSidebarContext = () => { + const context = useContext(UserGuideSidebarContext); + if (!context) { + throw new Error( + "useUserGuideSidebarContext must be used within a UserGuideSidebarProvider" + ); + } + return context; +}; + +export default UserGuideSidebarContext; diff --git a/client/src/Components/v1/UserGuide/content/contentTypes.js b/client/src/Components/v1/UserGuide/content/contentTypes.js new file mode 100644 index 000000000..3348c9a26 --- /dev/null +++ b/client/src/Components/v1/UserGuide/content/contentTypes.js @@ -0,0 +1,176 @@ +// Content block types for the templatic article system + +/** + * @typedef {'heading' | 'paragraph' | 'callout' | 'bullet-list' | 'ordered-list' | 'checklist' | 'icon-cards' | 'grid-cards' | 'time-estimate' | 'info-box' | 'code' | 'requirements' | 'table' | 'image' | 'article-links'} ContentBlockType + */ + +/** + * @typedef {Object} HeadingBlock + * @property {'heading'} type + * @property {string} id - Used for TOC anchors + * @property {2 | 3} level - h2 or h3 + * @property {string} text + */ + +/** + * @typedef {Object} ParagraphBlock + * @property {'paragraph'} type + * @property {string} text - Supports basic markdown: **bold**, *italic*, `code` + */ + +/** + * @typedef {Object} CalloutBlock + * @property {'callout'} type + * @property {'info' | 'tip' | 'warning' | 'success'} [variant] + * @property {string} [title] + * @property {string} text + */ + +/** + * @typedef {Object} ListItem + * @property {string} text + * @property {string} [bold] - Optional bold prefix + */ + +/** + * @typedef {Object} BulletListBlock + * @property {'bullet-list'} type + * @property {ListItem[]} items + */ + +/** + * @typedef {Object} OrderedListBlock + * @property {'ordered-list'} type + * @property {ListItem[]} items + */ + +/** + * @typedef {Object} ChecklistBlock + * @property {'checklist'} type + * @property {string[]} items + */ + +/** + * @typedef {Object} IconCardItem + * @property {string} icon - Icon name from lucide-react + * @property {string} title + * @property {string} description + */ + +/** + * @typedef {Object} IconCardsBlock + * @property {'icon-cards'} type + * @property {IconCardItem[]} items + */ + +/** + * @typedef {Object} GridCardItem + * @property {string} title + * @property {string} description + * @property {string} [icon] + */ + +/** + * @typedef {Object} GridCardsBlock + * @property {'grid-cards'} type + * @property {GridCardItem[]} items + * @property {number} [columns] + */ + +/** + * @typedef {Object} TimeEstimateBlock + * @property {'time-estimate'} type + * @property {string} text + */ + +/** + * @typedef {Object} InfoBoxBlock + * @property {'info-box'} type + * @property {string} [icon] + * @property {string} title + * @property {string[]} items + */ + +/** + * @typedef {Object} CodeBlock + * @property {'code'} type + * @property {string} code + * @property {string} [language] + */ + +/** + * @typedef {Object} RequirementItem + * @property {string} icon + * @property {string} title + * @property {string[]} items + */ + +/** + * @typedef {Object} RequirementsBlock + * @property {'requirements'} type + * @property {RequirementItem[]} items + */ + +/** + * @typedef {Object} TableColumn + * @property {string} key + * @property {string} label + * @property {string} [width] + */ + +/** + * @typedef {Object} TableBlock + * @property {'table'} type + * @property {TableColumn[]} columns + * @property {Object[]} rows + */ + +/** + * @typedef {Object} ImageBlock + * @property {'image'} type + * @property {string} src + * @property {string} alt + * @property {string} [caption] + */ + +/** + * @typedef {Object} ArticleLink + * @property {string} collectionId + * @property {string} articleId + * @property {string} title + * @property {string} [description] + */ + +/** + * @typedef {Object} ArticleLinksBlock + * @property {'article-links'} type + * @property {string} [title] - e.g., "Next steps", "Related articles" + * @property {ArticleLink[]} items + */ + +/** + * @typedef {Object} ArticleContent + * @property {ContentBlock[]} blocks + */ + +/** + * @typedef {Object} TocItem + * @property {string} label + * @property {string} href + * @property {number} level + */ + +/** + * Helper to extract TOC from content blocks + * @param {ContentBlock[]} blocks + * @returns {TocItem[]} + */ +export const extractToc = (blocks) => { + return blocks + .filter((block) => block.type === "heading") + .map((block) => ({ + label: block.text, + href: `#${block.id}`, + level: block.level, + })); +}; diff --git a/client/src/Components/v1/UserGuide/content/index.js b/client/src/Components/v1/UserGuide/content/index.js new file mode 100644 index 000000000..6d6ddaf18 --- /dev/null +++ b/client/src/Components/v1/UserGuide/content/index.js @@ -0,0 +1,2666 @@ +// Article content - each article has its content defined here +// Import content as needed to keep bundle size manageable + +const articleContents = { + // ============================================ + // GETTING STARTED COLLECTION + // ============================================ + + "getting-started/what-is-checkmate": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate is an open-source uptime and infrastructure monitoring platform. It helps you track website availability, server performance, and service health—all from a single dashboard.", + }, + { + type: "paragraph", + text: "Whether you're monitoring a personal project or managing enterprise infrastructure, Checkmate provides the tools you need to stay informed about your systems.", + }, + { + type: "heading", + id: "core-features", + level: 2, + text: "Core features", + }, + { + type: "icon-cards", + items: [ + { + icon: "Globe", + title: "Uptime monitoring", + description: + "Monitor websites and APIs with HTTP, ping, and TCP port checks.", + }, + { + icon: "Gauge", + title: "PageSpeed insights", + description: + "Track website performance with Google Lighthouse integration.", + }, + { + icon: "Server", + title: "Infrastructure monitoring", + description: "Monitor CPU, memory, disk, and network with the Capture agent.", + }, + { + icon: "Bell", + title: "Instant alerts", + description: "Get notified via email, Slack, Discord, PagerDuty, or webhooks.", + }, + ], + }, + { + type: "heading", + id: "monitoring-types", + level: 2, + text: "Monitoring types", + }, + { + type: "bullet-list", + items: [ + { + bold: "HTTP/HTTPS", + text: "Check website availability and response validation", + }, + { + bold: "Ping", + text: "Verify server reachability using ICMP", + }, + { + bold: "TCP port", + text: "Monitor specific services like databases and mail servers", + }, + { + bold: "Docker", + text: "Monitor Docker container status and health", + }, + { + bold: "PageSpeed", + text: "Track Core Web Vitals and performance scores", + }, + { + bold: "Infrastructure", + text: "Monitor hardware metrics with the Capture agent", + }, + ], + }, + { + type: "heading", + id: "next-steps", + level: 2, + text: "Next steps", + }, + { + type: "article-links", + items: [ + { + collectionId: "getting-started", + articleId: "quick-start", + title: "Quick start guide", + description: "Create your first monitor in 5 minutes", + }, + { + collectionId: "getting-started", + articleId: "dashboard", + title: "Understanding the dashboard", + description: "Learn to navigate the interface", + }, + ], + }, + ], + }, + + "getting-started/quick-start": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "This guide walks you through creating your first monitor and setting up notifications. You'll have a working monitoring setup in about 5 minutes.", + }, + { + type: "heading", + id: "create-monitor", + level: 2, + text: "Creating your first monitor", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Uptime** in the sidebar" }, + { text: "Click **Create monitor** in the top right" }, + { text: "Select **HTTP/HTTPS** as the monitor type" }, + { text: "Enter the URL you want to monitor (e.g., `https://example.com`)" }, + { text: "Set the check interval (1-5 minutes recommended for critical services)" }, + { text: "Click **Create monitor** to start monitoring" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "Use descriptive names like \"Production API\" or \"Marketing Website\" to easily identify monitors in alerts.", + }, + { + type: "heading", + id: "verify-monitor", + level: 2, + text: "Verifying your monitor", + }, + { + type: "paragraph", + text: "After creating a monitor, Checkmate immediately performs the first check. You can verify it's working by:", + }, + { + type: "ordered-list", + items: [ + { text: "Click on your new monitor to open the details page" }, + { text: "Check the status indicator shows **Up** (green)" }, + { text: "View the response time in the metrics panel" }, + ], + }, + { + type: "heading", + id: "setup-notifications", + level: 2, + text: "Setting up notifications", + }, + { + type: "paragraph", + text: "To receive alerts when your monitor goes down:", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Notifications** in the sidebar" }, + { text: "Click **Create notification**" }, + { text: "Select your preferred channel (email, Slack, Discord, etc.)" }, + { text: "Configure the notification settings" }, + { text: "Click **Test** to verify the notification works" }, + ], + }, + { + type: "article-links", + title: "Next steps", + items: [ + { + collectionId: "uptime-monitoring", + articleId: "http-monitors", + title: "Creating HTTP monitors", + description: "Advanced configuration options", + }, + { + collectionId: "notifications", + articleId: "email-notifications", + title: "Email notifications", + description: "Configure SMTP for email alerts", + }, + ], + }, + ], + }, + + "getting-started/dashboard": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "The Checkmate dashboard gives you a complete view of your monitoring status. This article explains the main navigation areas and key metrics.", + }, + { + type: "heading", + id: "sidebar-navigation", + level: 2, + text: "Sidebar navigation", + }, + { + type: "paragraph", + text: "The left sidebar provides access to all major features:", + }, + { + type: "bullet-list", + items: [ + { bold: "Uptime", text: "View and manage HTTP, ping, port, and Docker monitors" }, + { bold: "PageSpeed", text: "Access performance monitoring and Lighthouse reports" }, + { bold: "Infrastructure", text: "Monitor server hardware metrics" }, + { bold: "Incidents", text: "View current and past downtime events" }, + { bold: "Status pages", text: "Create and manage public status pages" }, + { bold: "Maintenance", text: "Schedule planned maintenance windows" }, + { bold: "Notifications", text: "Configure alert channels (email, Slack, Discord, etc.)" }, + { bold: "Settings", text: "Manage account and global settings" }, + ], + }, + { + type: "heading", + id: "monitors-list", + level: 2, + text: "Monitors list", + }, + { + type: "paragraph", + text: "The main monitors view shows all your monitors with key information at a glance:", + }, + { + type: "bullet-list", + items: [ + { bold: "Status indicator", text: "Green (up), red (down), or yellow (degraded)" }, + { bold: "Name", text: "The monitor's display name" }, + { bold: "URL/Host", text: "The target being monitored" }, + { bold: "Response time", text: "Latest response time in milliseconds" }, + { bold: "Uptime", text: "Percentage uptime over the selected period" }, + ], + }, + { + type: "heading", + id: "filtering-sorting", + level: 2, + text: "Filtering and sorting", + }, + { + type: "paragraph", + text: "You can filter monitors by status (up, down, paused) and sort by name, status, or response time. Use the search box to quickly find specific monitors.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "uptime-monitoring", + articleId: "uptime-metrics", + title: "Understanding uptime metrics", + description: "Learn what the numbers mean", + }, + { + collectionId: "incidents", + articleId: "understanding-incidents", + title: "Understanding incidents", + description: "How downtime is tracked", + }, + ], + }, + ], + }, + + "getting-started/roles-permissions": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate uses a role-based access control system with three user roles: User, Admin, and Superadmin. Each role has different permissions for managing monitors, team members, and settings.", + }, + { + type: "heading", + id: "user-role", + level: 2, + text: "User role", + }, + { + type: "paragraph", + text: "Users have standard access to monitoring features:", + }, + { + type: "bullet-list", + items: [ + { text: "View all monitors and their status" }, + { text: "View incidents and history" }, + { text: "Access status pages" }, + ], + }, + { + type: "heading", + id: "admin-role", + level: 2, + text: "Admin role", + }, + { + type: "paragraph", + text: "Admins have all User permissions plus management capabilities:", + }, + { + type: "bullet-list", + items: [ + { text: "Create, edit, and delete monitors" }, + { text: "Invite new team members" }, + { text: "Remove team members" }, + { text: "Change user roles (except Superadmin)" }, + { text: "Configure notification channels" }, + { text: "Manage status pages and maintenance windows" }, + { text: "Access system diagnostics and logs" }, + ], + }, + { + type: "heading", + id: "superadmin-role", + level: 2, + text: "Superadmin role", + }, + { + type: "paragraph", + text: "Superadmins have full system access:", + }, + { + type: "bullet-list", + items: [ + { text: "All Admin permissions" }, + { text: "Configure global application settings" }, + { text: "Set up SMTP email configuration" }, + { text: "Manage user accounts and passwords" }, + { text: "Access all administrative functions" }, + ], + }, + { + type: "callout", + variant: "info", + text: "The first user to register becomes the Superadmin. Only existing Superadmins can promote other users to Superadmin.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "team-management", + articleId: "inviting-members", + title: "Inviting team members", + description: "Add people to your team", + }, + { + collectionId: "team-management", + articleId: "user-roles", + title: "User roles", + description: "Detailed role comparison", + }, + ], + }, + ], + }, + + // ============================================ + // UPTIME MONITORING COLLECTION + // ============================================ + + "uptime-monitoring/http-monitors": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "HTTP monitors check website and API availability by making HTTP GET requests and validating responses. They verify that the server returns a successful response (status 200-299).", + }, + { + type: "heading", + id: "creating-http-monitor", + level: 2, + text: "Creating an HTTP monitor", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Uptime** in the sidebar" }, + { text: "Click **Create monitor**" }, + { text: "Select **HTTP/HTTPS** as the monitor type" }, + { text: "Enter the target URL (include `https://` or `http://`)" }, + { text: "Configure the settings below" }, + { text: "Click **Create monitor**" }, + ], + }, + { + type: "heading", + id: "configuration-options", + level: 2, + text: "Configuration options", + }, + { + type: "bullet-list", + items: [ + { bold: "Name", text: "A descriptive name for the monitor (max 50 characters)" }, + { bold: "URL", text: "The full URL to monitor (must include protocol)" }, + { bold: "Check interval", text: "How often to check (15 seconds to 30 minutes)" }, + { bold: "Ignore TLS errors", text: "Skip SSL certificate validation if needed" }, + ], + }, + { + type: "heading", + id: "advanced-options", + level: 3, + text: "Response validation", + }, + { + type: "paragraph", + text: "You can validate API response content using these options:", + }, + { + type: "bullet-list", + items: [ + { bold: "Match method", text: "How to compare: equal, include, or regex" }, + { bold: "Expected value", text: "The value to match against" }, + { bold: "JSON path", text: "JMESPath expression to extract data from JSON responses" }, + ], + }, + { + type: "callout", + variant: "info", + text: "HTTP monitors check for successful responses (status 200-299). The monitor uses GET requests by default.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "uptime-monitoring", + articleId: "json-path-matching", + title: "JSON path matching", + description: "Validate API response content", + }, + { + collectionId: "uptime-monitoring", + articleId: "ssl-tls-monitoring", + title: "SSL/TLS monitoring", + description: "Track certificate expiration", + }, + ], + }, + ], + }, + + "uptime-monitoring/ping-monitors": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Ping monitors verify server availability using ICMP echo requests. They're useful for monitoring servers, network devices, and infrastructure that doesn't expose HTTP endpoints.", + }, + { + type: "heading", + id: "creating-ping-monitor", + level: 2, + text: "Creating a ping monitor", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Uptime** in the sidebar" }, + { text: "Click **Create monitor**" }, + { text: "Select **Ping** as the monitor type" }, + { text: "Enter the hostname or IP address" }, + { text: "Set the check interval" }, + { text: "Click **Create monitor**" }, + ], + }, + { + type: "heading", + id: "configuration", + level: 2, + text: "Configuration options", + }, + { + type: "bullet-list", + items: [ + { bold: "Host", text: "IP address or hostname to ping" }, + { bold: "Check interval", text: "How often to send ping requests" }, + { bold: "Timeout", text: "Maximum time to wait for a response" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Some cloud providers and firewalls block ICMP traffic. If ping monitors fail but the server is accessible, consider using HTTP or TCP port monitors instead.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "uptime-monitoring", + articleId: "port-monitors", + title: "Port monitors", + description: "Monitor specific TCP ports", + }, + ], + }, + ], + }, + + "uptime-monitoring/port-monitors": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Port monitors check TCP port availability on servers. Use them to monitor databases, mail servers, game servers, and other services that listen on specific ports.", + }, + { + type: "heading", + id: "creating-port-monitor", + level: 2, + text: "Creating a port monitor", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Uptime** in the sidebar" }, + { text: "Click **Create monitor**" }, + { text: "Select **TCP port** as the monitor type" }, + { text: "Enter the hostname or IP address" }, + { text: "Enter the port number" }, + { text: "Set the check interval" }, + { text: "Click **Create monitor**" }, + ], + }, + { + type: "heading", + id: "common-ports", + level: 2, + text: "Common ports", + }, + { + type: "table", + columns: [ + { key: "port", label: "Port", width: "1fr" }, + { key: "service", label: "Service", width: "2fr" }, + ], + rows: [ + { port: "22", service: "SSH" }, + { port: "25", service: "SMTP (email)" }, + { port: "443", service: "HTTPS" }, + { port: "3306", service: "MySQL" }, + { port: "5432", service: "PostgreSQL" }, + { port: "6379", service: "Redis" }, + { port: "27017", service: "MongoDB" }, + ], + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "uptime-monitoring", + articleId: "http-monitors", + title: "HTTP monitors", + description: "For web services and APIs", + }, + ], + }, + ], + }, + + "uptime-monitoring/intervals-timing": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "The check interval determines how often Checkmate tests your monitors. Choosing the right interval balances quick detection with resource usage.", + }, + { + type: "heading", + id: "available-intervals", + level: 2, + text: "Available intervals", + }, + { + type: "paragraph", + text: "Available intervals vary by monitor type:", + }, + { + type: "heading", + id: "uptime-intervals", + level: 3, + text: "Uptime monitors", + }, + { + type: "table", + columns: [ + { key: "interval", label: "Interval", width: "1fr" }, + { key: "use", label: "Best for", width: "2fr" }, + ], + rows: [ + { interval: "15 seconds", use: "Ultra-critical services" }, + { interval: "30 seconds", use: "Critical production services" }, + { interval: "1-5 minutes", use: "Standard production monitoring" }, + { interval: "10-15 minutes", use: "Less critical services" }, + { interval: "30 minutes", use: "Development/staging environments" }, + ], + }, + { + type: "heading", + id: "pagespeed-intervals", + level: 3, + text: "PageSpeed monitors", + }, + { + type: "paragraph", + text: "PageSpeed checks use longer intervals (3 minutes to 1 week) since Lighthouse audits are resource-intensive.", + }, + { + type: "heading", + id: "choosing-interval", + level: 2, + text: "Choosing an interval", + }, + { + type: "paragraph", + text: "Consider these factors when selecting an interval:", + }, + { + type: "bullet-list", + items: [ + { bold: "Service criticality", text: "Mission-critical services need shorter intervals" }, + { bold: "Expected downtime impact", text: "High-traffic sites benefit from faster detection" }, + { bold: "Resource usage", text: "Shorter intervals use more system resources" }, + { bold: "False positive tolerance", text: "Longer intervals reduce noise from brief glitches" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "Start with 3-minute intervals for most monitors. Decrease to 1 minute only for your most critical services.", + }, + ], + }, + + "uptime-monitoring/json-path-matching": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "JSON path matching allows you to validate API response content beyond just status codes. You can verify that specific fields contain expected values.", + }, + { + type: "heading", + id: "enabling-json-matching", + level: 2, + text: "Enabling JSON matching", + }, + { + type: "ordered-list", + items: [ + { text: "Create or edit an HTTP monitor" }, + { text: "In advanced settings, enable **Response body validation**" }, + { text: "Enter the JSON path expression" }, + { text: "Enter the expected value" }, + { text: "Save the monitor" }, + ], + }, + { + type: "heading", + id: "json-path-syntax", + level: 2, + text: "JSON path syntax", + }, + { + type: "paragraph", + text: "JSON path uses dot notation to navigate JSON structures:", + }, + { + type: "code", + language: "json", + text: '// Example API response\n{\n "status": "healthy",\n "data": {\n "version": "2.1.0",\n "services": ["api", "database"]\n }\n}', + }, + { + type: "table", + columns: [ + { key: "path", label: "JSON path", width: "1fr" }, + { key: "result", label: "Returns", width: "1fr" }, + ], + rows: [ + { path: "status", result: '"healthy"' }, + { path: "data.version", result: '"2.1.0"' }, + { path: "data.services[0]", result: '"api"' }, + ], + }, + { + type: "callout", + variant: "warning", + text: "JSON path matching only works with APIs that return valid JSON. Ensure the response Content-Type is application/json.", + }, + ], + }, + + "uptime-monitoring/ssl-tls-monitoring": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate automatically monitors SSL/TLS certificates for HTTPS monitors. You can track certificate expiration and receive alerts before certificates expire.", + }, + { + type: "heading", + id: "certificate-tracking", + level: 2, + text: "Certificate tracking", + }, + { + type: "paragraph", + text: "For every HTTPS monitor, Checkmate tracks:", + }, + { + type: "bullet-list", + items: [ + { bold: "Issuer", text: "The certificate authority that issued the certificate" }, + { bold: "Valid from", text: "When the certificate became valid" }, + { bold: "Expiration date", text: "When the certificate expires" }, + { bold: "Days until expiry", text: "Countdown to expiration" }, + ], + }, + { + type: "heading", + id: "expiration-alerts", + level: 2, + text: "Expiration alerts", + }, + { + type: "paragraph", + text: "Checkmate sends notifications when certificates are approaching expiration. You can configure the warning threshold in monitor settings.", + }, + { + type: "callout", + variant: "tip", + text: "Set up certificate expiration alerts at least 14 days before expiry to give yourself time to renew.", + }, + { + type: "heading", + id: "certificate-errors", + level: 2, + text: "Certificate errors", + }, + { + type: "paragraph", + text: "Checkmate detects common SSL/TLS issues:", + }, + { + type: "bullet-list", + items: [ + { text: "Expired certificates" }, + { text: "Self-signed certificates (unless explicitly allowed)" }, + { text: "Certificate hostname mismatch" }, + { text: "Incomplete certificate chains" }, + ], + }, + ], + }, + + "uptime-monitoring/uptime-metrics": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate tracks several metrics for each monitor to help you understand availability and performance. This article explains what each metric means.", + }, + { + type: "heading", + id: "uptime-percentage", + level: 2, + text: "Uptime percentage", + }, + { + type: "paragraph", + text: "Uptime percentage shows how often your service was available over a time period. It's calculated as:", + }, + { + type: "code", + language: "text", + text: "Uptime % = (Successful checks / Total checks) × 100", + }, + { + type: "table", + columns: [ + { key: "uptime", label: "Uptime", width: "1fr" }, + { key: "downtime", label: "Monthly downtime", width: "1fr" }, + ], + rows: [ + { uptime: "99.9%", downtime: "~43 minutes" }, + { uptime: "99.5%", downtime: "~3.6 hours" }, + { uptime: "99.0%", downtime: "~7.2 hours" }, + { uptime: "95.0%", downtime: "~36 hours" }, + ], + }, + { + type: "heading", + id: "response-time", + level: 2, + text: "Response time", + }, + { + type: "paragraph", + text: "Response time measures how long it takes to receive a response from your service. Checkmate shows:", + }, + { + type: "bullet-list", + items: [ + { bold: "Current", text: "The most recent response time" }, + { bold: "Average", text: "Mean response time over the period" }, + { bold: "P95", text: "95th percentile (95% of requests were faster)" }, + ], + }, + { + type: "heading", + id: "status-codes", + level: 2, + text: "Status codes", + }, + { + type: "paragraph", + text: "For HTTP monitors, Checkmate tracks response status codes. A check is considered successful if the status code matches your expected codes (default: 200).", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "incidents", + articleId: "understanding-incidents", + title: "Understanding incidents", + description: "How downtime creates incidents", + }, + ], + }, + ], + }, + + // ============================================ + // PAGESPEED MONITORING COLLECTION + // ============================================ + + "pagespeed/creating-pagespeed": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "PageSpeed monitors track website performance using Google Lighthouse. They measure loading speed, accessibility, SEO, and best practices to help you optimize your sites.", + }, + { + type: "heading", + id: "creating-monitor", + level: 2, + text: "Creating a PageSpeed monitor", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **PageSpeed** in the sidebar" }, + { text: "Click **Create monitor**" }, + { text: "Enter the URL to monitor" }, + { text: "Set the check frequency (daily recommended)" }, + { text: "Click **Create monitor**" }, + ], + }, + { + type: "heading", + id: "configuration", + level: 2, + text: "Configuration options", + }, + { + type: "bullet-list", + items: [ + { bold: "URL", text: "The page to analyze (must be publicly accessible)" }, + { bold: "Display name", text: "Optional friendly name for the monitor" }, + { bold: "Check frequency", text: "How often to run audits (3 min to 1 week)" }, + ], + }, + { + type: "callout", + variant: "info", + text: "PageSpeed checks use Google's PageSpeed Insights API. Configure an API key in Settings → PageSpeed for higher rate limits.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "pagespeed", + articleId: "understanding-scores", + title: "Understanding scores", + description: "What each score means", + }, + { + collectionId: "pagespeed", + articleId: "pagespeed-api", + title: "PageSpeed API configuration", + description: "Set up your API key", + }, + ], + }, + ], + }, + + "pagespeed/understanding-scores": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "PageSpeed monitors report four main scores from Google Lighthouse audits. Each score ranges from 0-100, with higher scores indicating better performance.", + }, + { + type: "heading", + id: "score-categories", + level: 2, + text: "Score categories", + }, + { + type: "bullet-list", + items: [ + { bold: "Performance", text: "Measures loading speed and interactivity" }, + { bold: "Accessibility", text: "Evaluates usability for people with disabilities" }, + { bold: "Best practices", text: "Checks for modern web development standards" }, + { bold: "SEO", text: "Assesses search engine optimization basics" }, + ], + }, + { + type: "heading", + id: "score-ranges", + level: 2, + text: "Score ranges", + }, + { + type: "table", + columns: [ + { key: "range", label: "Score", width: "1fr" }, + { key: "rating", label: "Rating", width: "1fr" }, + { key: "color", label: "Indicator", width: "1fr" }, + ], + rows: [ + { range: "90-100", rating: "Good", color: "Green" }, + { range: "50-89", rating: "Needs improvement", color: "Orange" }, + { range: "0-49", rating: "Poor", color: "Red" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "Focus on Performance first, as it directly impacts user experience and SEO rankings.", + }, + ], + }, + + "pagespeed/core-web-vitals": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Core Web Vitals are Google's key metrics for measuring user experience. Checkmate tracks these metrics to help you understand and improve page performance.", + }, + { + type: "heading", + id: "lcp", + level: 2, + text: "Largest Contentful Paint (LCP)", + }, + { + type: "paragraph", + text: "LCP measures how long it takes for the largest visible element to load. Target: under 2.5 seconds.", + }, + { + type: "heading", + id: "fcp", + level: 2, + text: "First Contentful Paint (FCP)", + }, + { + type: "paragraph", + text: "FCP measures when the first content appears on screen. Target: under 1.8 seconds.", + }, + { + type: "heading", + id: "cls", + level: 2, + text: "Cumulative Layout Shift (CLS)", + }, + { + type: "paragraph", + text: "CLS measures visual stability—how much the page layout shifts during loading. Target: under 0.1.", + }, + { + type: "heading", + id: "tbt", + level: 2, + text: "Total Blocking Time (TBT)", + }, + { + type: "paragraph", + text: "TBT measures how long the page is unresponsive to user input. Target: under 200 milliseconds.", + }, + { + type: "heading", + id: "speed-index", + level: 2, + text: "Speed Index", + }, + { + type: "paragraph", + text: "Speed Index measures how quickly content is visually displayed. Target: under 3.4 seconds.", + }, + ], + }, + + "pagespeed/pagespeed-api": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate uses the Google PageSpeed Insights API to run Lighthouse audits. While the API works without a key, adding one increases your rate limits.", + }, + { + type: "heading", + id: "getting-api-key", + level: 2, + text: "Getting an API key", + }, + { + type: "ordered-list", + items: [ + { text: "Go to the Google Cloud Console" }, + { text: "Create a new project or select an existing one" }, + { text: "Enable the PageSpeed Insights API" }, + { text: "Create credentials (API key)" }, + { text: "Copy the generated API key" }, + ], + }, + { + type: "heading", + id: "configuring-key", + level: 2, + text: "Configuring the API key", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Scroll to the **PageSpeed** section" }, + { text: "Enter your API key in the **PageSpeed API key** field" }, + { text: "Click **Save** at the bottom of the page" }, + ], + }, + { + type: "callout", + variant: "warning", + text: "Only Admins and Superadmins can configure the PageSpeed API key.", + }, + { + type: "callout", + variant: "info", + text: "Without an API key, you're limited to a few requests per minute. With a key, you can make thousands of requests per day.", + }, + ], + }, + + "pagespeed/performance-trends": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Performance trends show how your PageSpeed scores change over time. Use them to track the impact of optimizations and catch regressions.", + }, + { + type: "heading", + id: "viewing-trends", + level: 2, + text: "Viewing trends", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **PageSpeed** in the sidebar" }, + { text: "Click on a monitor to view details" }, + { text: "Select the time range (7 days, 30 days, etc.)" }, + { text: "View the trend charts for each metric" }, + ], + }, + { + type: "heading", + id: "interpreting-trends", + level: 2, + text: "Interpreting trends", + }, + { + type: "bullet-list", + items: [ + { bold: "Upward trends", text: "Scores improving—your optimizations are working" }, + { bold: "Downward trends", text: "Scores declining—investigate recent changes" }, + { bold: "Flat trends", text: "Stable performance—no significant changes" }, + { bold: "Spikes", text: "Temporary issues—check for one-time events" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "Compare trends before and after deployments to measure the impact of code changes on performance.", + }, + ], + }, + + // ============================================ + // INFRASTRUCTURE MONITORING COLLECTION + // ============================================ + + "infrastructure/installing-capture": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Capture is the companion agent for infrastructure monitoring. Install it on your servers to collect CPU, memory, disk, and network metrics.", + }, + { + type: "heading", + id: "requirements", + level: 2, + text: "Requirements", + }, + { + type: "bullet-list", + items: [ + { text: "Linux, macOS, or Windows server" }, + { text: "Network access to your Checkmate instance" }, + { text: "Minimal resources (< 50MB RAM, < 1% CPU)" }, + ], + }, + { + type: "heading", + id: "installation", + level: 2, + text: "Installation", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Infrastructure** in the sidebar" }, + { text: "Click **Add server**" }, + { text: "Copy the installation command" }, + { text: "Run the command on your server" }, + { text: "The server appears automatically once connected" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Capture is open source. View the source code at github.com/bluewave-labs/capture", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "infrastructure", + articleId: "cpu-monitoring", + title: "CPU monitoring", + description: "Track processor usage", + }, + { + collectionId: "infrastructure", + articleId: "hardware-thresholds", + title: "Setting hardware thresholds", + description: "Configure alerts", + }, + ], + }, + ], + }, + + "infrastructure/cpu-monitoring": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "CPU monitoring tracks processor usage across your servers. High CPU usage can indicate performance issues, runaway processes, or capacity problems.", + }, + { + type: "heading", + id: "metrics", + level: 2, + text: "CPU metrics", + }, + { + type: "bullet-list", + items: [ + { bold: "Usage percentage", text: "Current CPU utilization (0-100%)" }, + { bold: "Load average", text: "Average system load over 1, 5, and 15 minutes" }, + { bold: "Core count", text: "Number of CPU cores available" }, + { bold: "Temperature", text: "CPU temperature (where supported)" }, + ], + }, + { + type: "heading", + id: "typical-values", + level: 2, + text: "Typical values", + }, + { + type: "table", + columns: [ + { key: "usage", label: "Usage", width: "1fr" }, + { key: "status", label: "Status", width: "2fr" }, + ], + rows: [ + { usage: "0-30%", status: "Normal—plenty of headroom" }, + { usage: "30-70%", status: "Moderate—acceptable for production" }, + { usage: "70-90%", status: "High—consider scaling" }, + { usage: "90-100%", status: "Critical—immediate attention needed" }, + ], + }, + ], + }, + + "infrastructure/memory-monitoring": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Memory monitoring tracks RAM usage on your servers. Running out of memory can cause crashes, slowdowns, and service failures.", + }, + { + type: "heading", + id: "metrics", + level: 2, + text: "Memory metrics", + }, + { + type: "bullet-list", + items: [ + { bold: "Total memory", text: "Total RAM installed" }, + { bold: "Used memory", text: "RAM currently in use" }, + { bold: "Available memory", text: "RAM available for new processes" }, + { bold: "Usage percentage", text: "Percentage of RAM in use" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Linux systems use available RAM for disk caching. High memory usage isn't always a problem if available memory remains adequate.", + }, + ], + }, + + "infrastructure/disk-monitoring": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Disk monitoring tracks storage space and I/O performance. Running out of disk space can cause application failures and data loss.", + }, + { + type: "heading", + id: "metrics", + level: 2, + text: "Disk metrics", + }, + { + type: "bullet-list", + items: [ + { bold: "Total space", text: "Total disk capacity" }, + { bold: "Used space", text: "Space currently in use" }, + { bold: "Available space", text: "Free space remaining" }, + { bold: "Usage percentage", text: "Percentage of disk used" }, + ], + }, + { + type: "callout", + variant: "warning", + text: "Set alerts at 80% disk usage to give yourself time to add storage or clean up files before running out of space.", + }, + ], + }, + + "infrastructure/network-monitoring": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Network monitoring tracks bandwidth usage and packet statistics. Use it to identify bottlenecks and unusual traffic patterns.", + }, + { + type: "heading", + id: "metrics", + level: 2, + text: "Network metrics", + }, + { + type: "bullet-list", + items: [ + { bold: "Bytes received", text: "Incoming data volume" }, + { bold: "Bytes sent", text: "Outgoing data volume" }, + { bold: "Packets received", text: "Incoming packet count" }, + { bold: "Packets sent", text: "Outgoing packet count" }, + ], + }, + ], + }, + + "infrastructure/hardware-thresholds": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Hardware thresholds define when Checkmate sends alerts about resource usage. Set thresholds to catch problems before they affect your services.", + }, + { + type: "heading", + id: "configuring-thresholds", + level: 2, + text: "Configuring thresholds", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Infrastructure** in the sidebar" }, + { text: "Click on a server to view details" }, + { text: "Click **Settings** or the gear icon" }, + { text: "Set threshold percentages for CPU, memory, and disk" }, + { text: "Click **Save**" }, + ], + }, + { + type: "heading", + id: "recommended-thresholds", + level: 2, + text: "Recommended thresholds", + }, + { + type: "table", + columns: [ + { key: "resource", label: "Resource", width: "1fr" }, + { key: "warning", label: "Warning", width: "1fr" }, + { key: "critical", label: "Critical", width: "1fr" }, + ], + rows: [ + { resource: "CPU", warning: "70%", critical: "90%" }, + { resource: "Memory", warning: "80%", critical: "95%" }, + { resource: "Disk", warning: "80%", critical: "90%" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "Start with these defaults and adjust based on your application's normal behavior.", + }, + ], + }, + + // ============================================ + // INCIDENTS COLLECTION + // ============================================ + + "incidents/understanding-incidents": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "An incident is created when a monitor detects a failure. Checkmate tracks incident start time, duration, and resolution to help you analyze downtime patterns.", + }, + { + type: "heading", + id: "incident-creation", + level: 2, + text: "When incidents are created", + }, + { + type: "paragraph", + text: "Incidents are created when:", + }, + { + type: "bullet-list", + items: [ + { text: "An HTTP monitor receives an unexpected status code" }, + { text: "A ping or port monitor fails to connect" }, + { text: "A monitor times out without response" }, + { text: "Infrastructure thresholds are exceeded" }, + ], + }, + { + type: "heading", + id: "incident-states", + level: 2, + text: "Incident states", + }, + { + type: "bullet-list", + items: [ + { bold: "Active", text: "The monitor is currently down" }, + { bold: "Resolved", text: "The monitor has recovered" }, + ], + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "incidents", + articleId: "incident-timeline", + title: "Incident timeline", + description: "Track incident progression", + }, + { + collectionId: "incidents", + articleId: "resolving-incidents", + title: "Resolving incidents", + description: "Manual vs automatic resolution", + }, + ], + }, + ], + }, + + "incidents/incident-timeline": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "The incident timeline shows the progression of an incident from detection to resolution. Use it to understand what happened and when.", + }, + { + type: "heading", + id: "timeline-events", + level: 2, + text: "Timeline events", + }, + { + type: "bullet-list", + items: [ + { bold: "Started", text: "When the first failure was detected" }, + { bold: "Notifications sent", text: "When alerts were dispatched" }, + { bold: "Acknowledged", text: "When someone began investigating (if applicable)" }, + { bold: "Resolved", text: "When the monitor recovered" }, + ], + }, + { + type: "heading", + id: "duration", + level: 2, + text: "Duration tracking", + }, + { + type: "paragraph", + text: "Checkmate calculates incident duration from the first failed check to the first successful check after the incident. This appears in both the timeline and incident summary.", + }, + ], + }, + + "incidents/resolving-incidents": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Incidents can be resolved automatically when the monitor recovers, or manually if you want to mark an issue as fixed.", + }, + { + type: "heading", + id: "automatic-resolution", + level: 2, + text: "Automatic resolution", + }, + { + type: "paragraph", + text: "By default, incidents resolve automatically when the monitor returns to a healthy state. A recovery notification is sent to all configured channels.", + }, + { + type: "heading", + id: "manual-resolution", + level: 2, + text: "Manual resolution", + }, + { + type: "paragraph", + text: "You can also manually resolve incidents:", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Incidents** in the sidebar" }, + { text: "Find the active incident" }, + { text: "Click **Resolve**" }, + { text: "Optionally add a resolution note" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Manual resolution is useful when you know the underlying issue is fixed but the monitor hasn't run its next check yet.", + }, + ], + }, + + "incidents/incident-history": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Incident history provides a record of all past outages. Use it to identify patterns, measure reliability, and report on SLA compliance.", + }, + { + type: "heading", + id: "viewing-history", + level: 2, + text: "Viewing incident history", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Incidents** in the sidebar" }, + { text: "Use filters to narrow by monitor, date range, or status" }, + { text: "Click an incident to view its full timeline" }, + ], + }, + { + type: "heading", + id: "analyzing-patterns", + level: 2, + text: "Analyzing patterns", + }, + { + type: "paragraph", + text: "Look for these patterns in your incident history:", + }, + { + type: "bullet-list", + items: [ + { bold: "Recurring times", text: "Incidents at the same time might indicate scheduled jobs or traffic spikes" }, + { bold: "Duration trends", text: "Longer incidents may indicate slower recovery processes" }, + { bold: "Frequency changes", text: "Increasing incidents could signal infrastructure problems" }, + ], + }, + ], + }, + + // ============================================ + // NOTIFICATIONS COLLECTION + // ============================================ + + "notifications/email-notifications": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Email notifications send alerts directly to your inbox when monitors detect issues. Configure SMTP settings to enable email delivery.", + }, + { + type: "heading", + id: "smtp-setup", + level: 2, + text: "Setting up SMTP", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Select the **Email** tab" }, + { text: "Enter your SMTP server details" }, + { text: "Configure authentication (username/password)" }, + { text: "Set the sender email address" }, + { text: "Click **Save** and **Test** to verify" }, + ], + }, + { + type: "heading", + id: "smtp-settings", + level: 2, + text: "SMTP settings", + }, + { + type: "bullet-list", + items: [ + { bold: "Host", text: "SMTP server address (e.g., smtp.gmail.com)" }, + { bold: "Port", text: "SMTP port (typically 587 for TLS, 465 for SSL)" }, + { bold: "Username", text: "Authentication username" }, + { bold: "Password", text: "Authentication password or app password" }, + { bold: "From address", text: "Email address that alerts come from" }, + ], + }, + { + type: "callout", + variant: "tip", + text: "For Gmail, use an App Password instead of your regular password. Enable 2FA and generate an app password in your Google Account settings.", + }, + ], + }, + + "notifications/slack-integration": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Slack integration sends incident alerts to your Slack channels using webhooks. Your team can see and discuss issues in real-time.", + }, + { + type: "heading", + id: "creating-webhook", + level: 2, + text: "Creating a Slack webhook", + }, + { + type: "ordered-list", + items: [ + { text: "Go to api.slack.com/apps and create a new app" }, + { text: "Select **Incoming Webhooks** and enable them" }, + { text: "Click **Add New Webhook to Workspace**" }, + { text: "Choose the channel for alerts" }, + { text: "Copy the webhook URL" }, + ], + }, + { + type: "heading", + id: "configuring-checkmate", + level: 2, + text: "Configuring in Checkmate", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Integrations** in the sidebar" }, + { text: "Click **Create integration**" }, + { text: "Select **Slack**" }, + { text: "Paste your webhook URL" }, + { text: "Click **Test** to send a test message" }, + { text: "Click **Save**" }, + ], + }, + ], + }, + + "notifications/discord-integration": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Discord integration sends alerts to your Discord server using webhooks. Ideal for development teams already using Discord for communication.", + }, + { + type: "heading", + id: "creating-webhook", + level: 2, + text: "Creating a Discord webhook", + }, + { + type: "ordered-list", + items: [ + { text: "Open Discord and go to your server" }, + { text: "Click the gear icon next to the target channel" }, + { text: "Select **Integrations** → **Webhooks**" }, + { text: "Click **New Webhook**" }, + { text: "Name the webhook and copy the URL" }, + ], + }, + { + type: "heading", + id: "configuring-checkmate", + level: 2, + text: "Configuring in Checkmate", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Integrations** in the sidebar" }, + { text: "Click **Create integration**" }, + { text: "Select **Discord**" }, + { text: "Paste your webhook URL" }, + { text: "Click **Test** then **Save**" }, + ], + }, + ], + }, + + "notifications/pagerduty-integration": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "PagerDuty integration connects Checkmate to your on-call schedules. Incidents trigger PagerDuty alerts with proper escalation policies.", + }, + { + type: "heading", + id: "setup", + level: 2, + text: "Setting up PagerDuty", + }, + { + type: "ordered-list", + items: [ + { text: "In PagerDuty, go to **Services** and create or select a service" }, + { text: "Add a new integration of type **Events API v2**" }, + { text: "Copy the Integration Key (routing key)" }, + { text: "In Checkmate, navigate to **Integrations**" }, + { text: "Create a new **PagerDuty** integration" }, + { text: "Paste the integration key and save" }, + ], + }, + { + type: "callout", + variant: "info", + text: "PagerDuty incidents are automatically resolved when Checkmate detects recovery.", + }, + ], + }, + + "notifications/webhook-notifications": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Webhook notifications send HTTP POST requests to your custom endpoint when incidents occur. Use them to integrate with any service.", + }, + { + type: "heading", + id: "setup", + level: 2, + text: "Setting up webhooks", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Integrations** in the sidebar" }, + { text: "Click **Create integration**" }, + { text: "Select **Webhook**" }, + { text: "Enter your endpoint URL" }, + { text: "Click **Test** then **Save**" }, + ], + }, + { + type: "heading", + id: "payload", + level: 2, + text: "Webhook payload", + }, + { + type: "paragraph", + text: "Checkmate sends a JSON payload with incident details:", + }, + { + type: "code", + language: "json", + text: '{\n "type": "incident.created",\n "monitor": {\n "id": "abc123",\n "name": "Production API",\n "url": "https://api.example.com"\n },\n "incident": {\n "id": "inc_456",\n "status": "active",\n "startedAt": "2024-01-15T10:30:00Z"\n }\n}', + }, + ], + }, + + "notifications/matrix-integration": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Matrix integration sends alerts to Matrix rooms using the Matrix protocol. Ideal for teams using decentralized messaging.", + }, + { + type: "heading", + id: "setup", + level: 2, + text: "Setting up Matrix", + }, + { + type: "ordered-list", + items: [ + { text: "Get your Matrix homeserver URL" }, + { text: "Create an access token for your bot account" }, + { text: "Get the room ID where alerts should be sent" }, + { text: "In Checkmate, navigate to **Integrations**" }, + { text: "Create a new **Matrix** integration" }, + { text: "Enter the homeserver, token, and room ID" }, + { text: "Click **Test** then **Save**" }, + ], + }, + ], + }, + + "notifications/testing-notifications": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Always test your notification integrations to ensure alerts reach your team. Checkmate provides a test function for every integration type.", + }, + { + type: "heading", + id: "testing", + level: 2, + text: "Testing an integration", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Integrations** in the sidebar" }, + { text: "Find the integration you want to test" }, + { text: "Click the **Test** button" }, + { text: "Check the destination for the test message" }, + { text: "Verify the message arrived correctly" }, + ], + }, + { + type: "heading", + id: "troubleshooting", + level: 2, + text: "Troubleshooting", + }, + { + type: "paragraph", + text: "If test messages don't arrive:", + }, + { + type: "bullet-list", + items: [ + { text: "Verify the webhook URL or credentials are correct" }, + { text: "Check if your firewall allows outbound connections" }, + { text: "Ensure the destination service is operational" }, + { text: "Review server logs for error messages" }, + ], + }, + ], + }, + + // ============================================ + // STATUS PAGES COLLECTION + // ============================================ + + "status-pages/creating-status-page": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Status pages provide a public view of your service health. Share them with customers to communicate uptime and incidents transparently.", + }, + { + type: "heading", + id: "creating", + level: 2, + text: "Creating a status page", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Status pages** in the sidebar" }, + { text: "Click **Create status page**" }, + { text: "Enter a name for your status page" }, + { text: "Configure the URL slug (e.g., status.yourcompany.com)" }, + { text: "Click **Create**" }, + ], + }, + { + type: "heading", + id: "adding-monitors", + level: 2, + text: "Adding monitors", + }, + { + type: "paragraph", + text: "After creation, add monitors to display on the status page. Visitors can see the status of each service without accessing your dashboard.", + }, + { + type: "article-links", + title: "Related articles", + items: [ + { + collectionId: "status-pages", + articleId: "customizing-appearance", + title: "Customizing appearance", + description: "Brand your status page", + }, + { + collectionId: "status-pages", + articleId: "adding-monitors", + title: "Adding monitors", + description: "Configure which services to show", + }, + ], + }, + ], + }, + + "status-pages/customizing-appearance": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Customize your status page to match your brand. Add your logo, choose colors, and configure the layout.", + }, + { + type: "heading", + id: "branding", + level: 2, + text: "Branding options", + }, + { + type: "bullet-list", + items: [ + { bold: "Logo", text: "Upload your company logo (JPEG/PNG, max 3MB)" }, + { bold: "Company name", text: "Display name at the top of the page" }, + { bold: "Color", text: "Primary color for the status page theme" }, + { bold: "Timezone", text: "Timezone for displaying check times" }, + ], + }, + { + type: "heading", + id: "applying", + level: 2, + text: "Applying customizations", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Status pages** and select your page" }, + { text: "Click **Settings** or the gear icon" }, + { text: "Upload your logo and configure branding" }, + { text: "Click **Save**" }, + ], + }, + ], + }, + + "status-pages/adding-monitors": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Choose which monitors appear on your status page. You can group related services and display them with custom names.", + }, + { + type: "heading", + id: "adding", + level: 2, + text: "Adding monitors", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Status pages** and select your page" }, + { text: "Click **Add monitor**" }, + { text: "Select monitors from the list" }, + { text: "Optionally set display names" }, + { text: "Click **Save**" }, + ], + }, + { + type: "heading", + id: "organizing", + level: 2, + text: "Organizing monitors", + }, + { + type: "paragraph", + text: "Drag monitors to reorder them. Group related services together for a logical presentation.", + }, + ], + }, + + "status-pages/display-options": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Configure what information visitors see on your status page. Balance transparency with the level of detail you want to share.", + }, + { + type: "heading", + id: "options", + level: 2, + text: "Display options", + }, + { + type: "bullet-list", + items: [ + { bold: "Show uptime percentage", text: "Display overall uptime statistics for each monitor" }, + { bold: "Show charts", text: "Display response time performance graphs" }, + { bold: "Show admin login link", text: "Add a link to the Checkmate login page" }, + { bold: "Publish/Unpublish", text: "Control whether the page is publicly accessible" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Unpublished status pages are only accessible by team members logged into Checkmate.", + }, + ], + }, + + "status-pages/sharing": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Share your status page with customers and stakeholders to communicate service health transparently.", + }, + { + type: "heading", + id: "public-url", + level: 2, + text: "Public URL", + }, + { + type: "paragraph", + text: "Each status page has a unique URL based on the slug you configured. Share this URL with anyone who needs to check your service status.", + }, + { + type: "callout", + variant: "tip", + text: "Use a descriptive URL slug like 'api-status' or 'app-health' to make the link memorable.", + }, + ], + }, + + // ============================================ + // MAINTENANCE COLLECTION + // ============================================ + + "maintenance/creating-maintenance": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Maintenance windows suppress alerts during planned downtime. Schedule them before deployments or infrastructure work to avoid false alarms.", + }, + { + type: "heading", + id: "creating", + level: 2, + text: "Creating a maintenance window", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Maintenance** in the sidebar" }, + { text: "Click **Create maintenance window**" }, + { text: "Enter a name describing the maintenance" }, + { text: "Select the affected monitors" }, + { text: "Set the start and end times" }, + { text: "Click **Create**" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Monitors continue running during maintenance, but incidents won't trigger notifications.", + }, + ], + }, + + "maintenance/recurring-maintenance": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Choose between one-time and recurring maintenance windows based on your needs.", + }, + { + type: "heading", + id: "one-time", + level: 2, + text: "One-time maintenance", + }, + { + type: "paragraph", + text: "Use one-time windows for specific events like deployments, migrations, or emergency repairs. They run once at the scheduled time.", + }, + { + type: "heading", + id: "recurring", + level: 2, + text: "Recurring maintenance", + }, + { + type: "paragraph", + text: "Use recurring windows for regular maintenance like nightly backups or weekly updates. Options include:", + }, + { + type: "bullet-list", + items: [ + { bold: "Daily", text: "Runs every day at the specified time" }, + { bold: "Weekly", text: "Runs every 7 days at the specified time" }, + ], + }, + ], + }, + + "maintenance/managing-maintenance": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Manage active maintenance windows to extend, cancel, or modify them as needed.", + }, + { + type: "heading", + id: "managing", + level: 2, + text: "Managing windows", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Maintenance** in the sidebar" }, + { text: "Find the active or scheduled window" }, + { text: "Click to view details" }, + { text: "Use **Edit** to modify or **Cancel** to remove" }, + ], + }, + { + type: "heading", + id: "extending", + level: 2, + text: "Extending maintenance", + }, + { + type: "paragraph", + text: "If maintenance takes longer than expected, edit the window to extend the end time. This prevents alerts from firing while work continues.", + }, + ], + }, + + // ============================================ + // TEAM MANAGEMENT COLLECTION + // ============================================ + + "team-management/inviting-members": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Invite team members to collaborate on monitoring. New members receive an email invitation to join your workspace.", + }, + { + type: "heading", + id: "inviting", + level: 2, + text: "Sending invitations", + }, + { + type: "ordered-list", + items: [ + { text: "Click your profile icon and select **Account**" }, + { text: "Go to the **Team** section" }, + { text: "Click **Invite member**" }, + { text: "Enter their email address" }, + { text: "Select their role (User or Admin)" }, + { text: "Click **Send invitation**" }, + ], + }, + { + type: "callout", + variant: "warning", + text: "Invitations expire after 1 hour. Send a new invitation if the link has expired.", + }, + ], + }, + + "team-management/user-roles": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Checkmate has three user roles with different permission levels. Assign roles based on what each team member needs to do.", + }, + { + type: "heading", + id: "comparison", + level: 2, + text: "Role comparison", + }, + { + type: "table", + columns: [ + { key: "permission", label: "Permission", width: "2fr" }, + { key: "user", label: "User", width: "1fr" }, + { key: "admin", label: "Admin", width: "1fr" }, + { key: "superadmin", label: "Superadmin", width: "1fr" }, + ], + rows: [ + { permission: "View monitors", user: "✓", admin: "✓", superadmin: "✓" }, + { permission: "Create/edit monitors", user: "—", admin: "✓", superadmin: "✓" }, + { permission: "Manage notifications", user: "—", admin: "✓", superadmin: "✓" }, + { permission: "Invite team members", user: "—", admin: "✓", superadmin: "✓" }, + { permission: "Configure SMTP", user: "—", admin: "—", superadmin: "✓" }, + { permission: "Manage user accounts", user: "—", admin: "—", superadmin: "✓" }, + ], + }, + ], + }, + + "team-management/managing-access": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Manage team member access by changing roles or removing members who no longer need access.", + }, + { + type: "heading", + id: "changing-roles", + level: 2, + text: "Changing roles", + }, + { + type: "ordered-list", + items: [ + { text: "Click your profile icon and select **Account**" }, + { text: "Go to the **Team** section" }, + { text: "Find the team member" }, + { text: "Click their current role to change it" }, + { text: "Select the new role and confirm" }, + ], + }, + { + type: "heading", + id: "removing", + level: 2, + text: "Removing members", + }, + { + type: "ordered-list", + items: [ + { text: "Click your profile icon and select **Account**" }, + { text: "Go to the **Team** section" }, + { text: "Find the team member" }, + { text: "Click the remove button" }, + { text: "Confirm removal" }, + ], + }, + { + type: "callout", + variant: "warning", + text: "Removed members lose access immediately but their past actions remain in the audit log.", + }, + ], + }, + + // ============================================ + // SETTINGS COLLECTION + // ============================================ + + "settings/email-configuration": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Configure SMTP settings to enable email notifications. This is required for email alerts and team invitations.", + }, + { + type: "heading", + id: "configuration", + level: 2, + text: "SMTP configuration", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Scroll to the **Email** section" }, + { text: "Enter SMTP server hostname and port" }, + { text: "Enter authentication credentials (username and password)" }, + { text: "Set the from email address" }, + { text: "Configure TLS/SSL options as needed" }, + { text: "Click **Send test email** to verify, then **Save**" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Only Admins and Superadmins can configure SMTP settings.", + }, + ], + }, + + "settings/global-thresholds": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Global thresholds set default alert levels for infrastructure monitors. These thresholds determine when alerts are triggered for resource usage.", + }, + { + type: "heading", + id: "configuring", + level: 2, + text: "Configuring thresholds", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Scroll to the **Global thresholds** section" }, + { text: "Set threshold percentages for CPU, memory, disk, and temperature" }, + { text: "Click **Save** at the bottom of the page" }, + ], + }, + { + type: "bullet-list", + items: [ + { bold: "CPU threshold", text: "Alert when CPU usage exceeds this percentage (1-100%)" }, + { bold: "Memory threshold", text: "Alert when memory usage exceeds this percentage (1-100%)" }, + { bold: "Disk threshold", text: "Alert when disk usage exceeds this percentage (1-100%)" }, + { bold: "Temperature threshold", text: "Alert when temperature exceeds this value (1-150°C)" }, + ], + }, + ], + }, + + "settings/appearance": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Customize the appearance of Checkmate to match your preferences.", + }, + { + type: "heading", + id: "theme", + level: 2, + text: "Theme settings", + }, + { + type: "bullet-list", + items: [ + { bold: "Light/Dark mode", text: "Toggle between light and dark themes" }, + { bold: "Language", text: "Select your preferred language" }, + { bold: "Show URLs", text: "Toggle whether to display monitor URLs in the interface" }, + ], + }, + { + type: "paragraph", + text: "These settings are saved to your browser and persist across sessions.", + }, + ], + }, + + "settings/timezone-settings": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Timezone settings control how dates and times are displayed throughout the application. Set this to your local timezone for easier incident correlation.", + }, + { + type: "heading", + id: "configuring", + level: 2, + text: "Setting your timezone", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Find the **Timezone** section at the top" }, + { text: "Search for and select your timezone" }, + { text: "Click **Save** at the bottom of the page" }, + ], + }, + ], + }, + + "settings/export-monitors": { + blocks: [ + { + type: "heading", + id: "overview", + level: 2, + text: "Overview", + }, + { + type: "paragraph", + text: "Export your monitors to backup your configuration or migrate to another Checkmate instance.", + }, + { + type: "heading", + id: "exporting", + level: 2, + text: "Exporting monitors", + }, + { + type: "ordered-list", + items: [ + { text: "Navigate to **Settings** in the sidebar" }, + { text: "Scroll to the **Export monitors** section" }, + { text: "Click **Export monitors to JSON**" }, + { text: "Save the downloaded file" }, + ], + }, + { + type: "callout", + variant: "info", + text: "Only Admins and Superadmins can export monitors.", + }, + ], + }, +}; + +/** + * Get article content by collection and article ID + * @param {string} collectionId + * @param {string} articleId + * @returns {Object|undefined} + */ +export const getArticleContent = (collectionId, articleId) => { + const key = `${collectionId}/${articleId}`; + return articleContents[key]; +}; + +export default articleContents; diff --git a/client/src/Components/v1/UserGuide/content/userGuideConfig.js b/client/src/Components/v1/UserGuide/content/userGuideConfig.js new file mode 100644 index 000000000..420dcd44e --- /dev/null +++ b/client/src/Components/v1/UserGuide/content/userGuideConfig.js @@ -0,0 +1,524 @@ +// Icon names as strings - resolved to actual components in UserGuideLanding.jsx +// Available icons: Rocket, Globe, Gauge, Server, Bell, AlertTriangle, Settings, Wifi, Wrench, Users + +/** + * @typedef {Object} Article + * @property {string} id + * @property {string} title + * @property {string} description + * @property {string[]} keywords + */ + +/** + * @typedef {Object} Collection + * @property {string} id + * @property {string} title + * @property {string} description + * @property {string} icon + * @property {number} articleCount + * @property {Article[]} articles + */ + +/** + * @typedef {Object} FastFind + * @property {string} id + * @property {string} title + * @property {string} collectionId + * @property {string} articleId + */ + +// Collections configuration for Checkmate +export const collections = [ + { + id: "getting-started", + title: "Getting started", + description: "Essential first steps for new users.", + icon: "Rocket", + articleCount: 4, + articles: [ + { + id: "what-is-checkmate", + title: "What is Checkmate", + description: "Overview of monitoring capabilities and core features.", + keywords: ["introduction", "overview", "platform", "monitoring", "uptime"], + }, + { + id: "quick-start", + title: "Quick start guide", + description: "Create your first monitor in 5 minutes.", + keywords: ["quick", "start", "first", "monitor", "setup", "tutorial"], + }, + { + id: "dashboard", + title: "Understanding the dashboard", + description: "Navigation and key metrics explained.", + keywords: ["dashboard", "navigation", "interface", "home", "metrics"], + }, + { + id: "roles-permissions", + title: "User roles and permissions", + description: "User, Admin, and Superadmin roles explained.", + keywords: ["role", "permission", "admin", "superadmin", "user", "access"], + }, + ], + }, + { + id: "uptime-monitoring", + title: "Uptime monitoring", + description: "Monitor website and service availability.", + icon: "Globe", + articleCount: 7, + articles: [ + { + id: "http-monitors", + title: "Creating HTTP monitors", + description: "URL monitoring with response validation.", + keywords: ["http", "https", "url", "website", "monitor", "create"], + }, + { + id: "ping-monitors", + title: "Ping monitors", + description: "ICMP-based availability checks.", + keywords: ["ping", "icmp", "availability", "monitor"], + }, + { + id: "port-monitors", + title: "Port monitors", + description: "TCP port availability monitoring.", + keywords: ["port", "tcp", "availability", "monitor", "service"], + }, + { + id: "intervals-timing", + title: "Monitor intervals and timing", + description: "Configure how often to check your services.", + keywords: ["interval", "timing", "frequency", "check", "schedule"], + }, + { + id: "json-path-matching", + title: "JSON path matching", + description: "Validate API response content.", + keywords: ["json", "api", "response", "validation", "match", "path"], + }, + { + id: "ssl-tls-monitoring", + title: "SSL/TLS monitoring", + description: "Certificate tracking and error handling.", + keywords: ["ssl", "tls", "certificate", "https", "security", "expiry"], + }, + { + id: "uptime-metrics", + title: "Understanding uptime metrics", + description: "Response time, status codes, and uptime percentage.", + keywords: ["metrics", "response", "time", "status", "code", "uptime", "percentage"], + }, + ], + }, + { + id: "pagespeed", + title: "PageSpeed monitoring", + description: "Track website performance and Core Web Vitals.", + icon: "Gauge", + articleCount: 5, + articles: [ + { + id: "creating-pagespeed", + title: "Creating PageSpeed monitors", + description: "Setup and configuration for performance monitoring.", + keywords: ["pagespeed", "create", "setup", "performance", "lighthouse"], + }, + { + id: "understanding-scores", + title: "Understanding scores", + description: "Performance, Accessibility, SEO, and Best Practices explained.", + keywords: ["score", "performance", "accessibility", "seo", "best", "practices"], + }, + { + id: "core-web-vitals", + title: "Core Web Vitals explained", + description: "LCP, FCP, CLS, TBT, and Speed Index.", + keywords: ["core", "web", "vitals", "lcp", "fcp", "cls", "tbt", "speed", "index"], + }, + { + id: "pagespeed-api", + title: "PageSpeed API configuration", + description: "Setting up your Google PageSpeed API key.", + keywords: ["api", "key", "google", "pagespeed", "configure", "setup"], + }, + { + id: "performance-trends", + title: "Performance trends", + description: "Tracking improvements over time.", + keywords: ["trend", "history", "improvement", "tracking", "chart"], + }, + ], + }, + { + id: "infrastructure", + title: "Infrastructure monitoring", + description: "Monitor server hardware with Capture agent.", + icon: "Server", + articleCount: 6, + articles: [ + { + id: "installing-capture", + title: "Installing Capture agent", + description: "Setup the monitoring agent on your servers.", + keywords: ["install", "capture", "agent", "setup", "server"], + }, + { + id: "cpu-monitoring", + title: "CPU monitoring", + description: "Usage, temperature, and core metrics.", + keywords: ["cpu", "processor", "usage", "temperature", "cores"], + }, + { + id: "memory-monitoring", + title: "Memory monitoring", + description: "RAM usage and availability tracking.", + keywords: ["memory", "ram", "usage", "available", "free"], + }, + { + id: "disk-monitoring", + title: "Disk monitoring", + description: "Storage space and read/write speeds.", + keywords: ["disk", "storage", "space", "read", "write", "speed"], + }, + { + id: "network-monitoring", + title: "Network monitoring", + description: "Bandwidth and packet statistics.", + keywords: ["network", "bandwidth", "packet", "interface", "traffic"], + }, + { + id: "hardware-thresholds", + title: "Setting hardware thresholds", + description: "Alert when resources become critical.", + keywords: ["threshold", "alert", "critical", "warning", "limit"], + }, + ], + }, + { + id: "incidents", + title: "Incidents & alerts", + description: "Respond to downtime and issues.", + icon: "AlertTriangle", + articleCount: 4, + articles: [ + { + id: "understanding-incidents", + title: "Understanding incidents", + description: "How incidents are detected and created.", + keywords: ["incident", "detection", "downtime", "outage", "alert"], + }, + { + id: "incident-timeline", + title: "Incident timeline", + description: "Tracking start, duration, and resolution.", + keywords: ["timeline", "duration", "start", "end", "resolution"], + }, + { + id: "resolving-incidents", + title: "Resolving incidents", + description: "Manual vs automatic resolution.", + keywords: ["resolve", "manual", "automatic", "acknowledge", "fix"], + }, + { + id: "incident-history", + title: "Incident history and analysis", + description: "Learn from past outages.", + keywords: ["history", "analysis", "report", "past", "outage"], + }, + ], + }, + { + id: "notifications", + title: "Notifications", + description: "Get alerted when things go wrong.", + icon: "Bell", + articleCount: 7, + articles: [ + { + id: "email-notifications", + title: "Email notifications", + description: "SMTP setup and configuration.", + keywords: ["email", "smtp", "mail", "setup", "configure"], + }, + { + id: "slack-integration", + title: "Slack integration", + description: "Webhook setup for Slack channels.", + keywords: ["slack", "webhook", "integration", "channel"], + }, + { + id: "discord-integration", + title: "Discord integration", + description: "Channel notifications via webhooks.", + keywords: ["discord", "webhook", "integration", "channel"], + }, + { + id: "pagerduty-integration", + title: "PagerDuty integration", + description: "On-call management integration.", + keywords: ["pagerduty", "oncall", "integration", "escalation"], + }, + { + id: "webhook-notifications", + title: "Webhook notifications", + description: "Custom HTTP callbacks for alerts.", + keywords: ["webhook", "http", "callback", "custom", "api"], + }, + { + id: "matrix-integration", + title: "Matrix integration", + description: "Decentralized messaging notifications.", + keywords: ["matrix", "messaging", "integration", "homeserver"], + }, + { + id: "testing-notifications", + title: "Testing notifications", + description: "Verify your notification setup works.", + keywords: ["test", "verify", "notification", "check"], + }, + ], + }, + { + id: "status-pages", + title: "Status pages", + description: "Share uptime with your users.", + icon: "Wifi", + articleCount: 5, + articles: [ + { + id: "creating-status-page", + title: "Creating a status page", + description: "Public-facing uptime dashboard setup.", + keywords: ["status", "page", "create", "public", "dashboard"], + }, + { + id: "customizing-appearance", + title: "Customizing appearance", + description: "Logo, colors, and branding options.", + keywords: ["customize", "logo", "color", "brand", "style"], + }, + { + id: "adding-monitors", + title: "Adding monitors to status page", + description: "Main monitors and sub-components.", + keywords: ["add", "monitor", "component", "group"], + }, + { + id: "display-options", + title: "Display options", + description: "Charts, uptime percentage, and login links.", + keywords: ["display", "chart", "uptime", "percentage", "option"], + }, + { + id: "sharing", + title: "Sharing your status page", + description: "Share your status page URL with users.", + keywords: ["share", "url", "public", "link", "access"], + }, + ], + }, + { + id: "maintenance", + title: "Maintenance windows", + description: "Schedule planned downtime.", + icon: "Wrench", + articleCount: 3, + articles: [ + { + id: "creating-maintenance", + title: "Creating maintenance windows", + description: "Suppress alerts during planned maintenance.", + keywords: ["maintenance", "create", "schedule", "suppress", "alert"], + }, + { + id: "recurring-maintenance", + title: "One-time vs recurring", + description: "Different scheduling options.", + keywords: ["recurring", "one-time", "schedule", "repeat", "frequency"], + }, + { + id: "managing-maintenance", + title: "Managing active maintenance", + description: "Edit, extend, or cancel maintenance windows.", + keywords: ["manage", "edit", "cancel", "extend", "active"], + }, + ], + }, + { + id: "team-management", + title: "Team management", + description: "Collaborate with your team.", + icon: "Users", + articleCount: 3, + articles: [ + { + id: "inviting-members", + title: "Inviting team members", + description: "Send email invitations to your team.", + keywords: ["invite", "team", "member", "email", "add"], + }, + { + id: "user-roles", + title: "User roles", + description: "Admin vs User permissions explained.", + keywords: ["role", "admin", "user", "permission", "access"], + }, + { + id: "managing-access", + title: "Managing team access", + description: "Add and remove team members.", + keywords: ["manage", "access", "remove", "team", "member"], + }, + ], + }, + { + id: "settings", + title: "Settings & configuration", + description: "Customize your Checkmate instance.", + icon: "Settings", + articleCount: 5, + articles: [ + { + id: "email-configuration", + title: "Email configuration", + description: "SMTP server setup for notifications.", + keywords: ["email", "smtp", "configure", "server", "mail"], + }, + { + id: "global-thresholds", + title: "Global thresholds", + description: "Default alert levels for all monitors.", + keywords: ["threshold", "global", "default", "alert", "level"], + }, + { + id: "appearance", + title: "Appearance settings", + description: "Theme and display preferences.", + keywords: ["appearance", "theme", "dark", "light", "display", "mode"], + }, + { + id: "timezone-settings", + title: "Timezone settings", + description: "Display preferences for dates and times.", + keywords: ["timezone", "time", "date", "display", "preference"], + }, + { + id: "export-monitors", + title: "Export monitors", + description: "Download your monitors as JSON.", + keywords: ["export", "download", "backup", "json", "monitors"], + }, + ], + }, +]; + +// Fast Finds - Popular/quick access articles +export const fastFinds = [ + { + id: "ff-1", + title: "Creating your first monitor", + collectionId: "getting-started", + articleId: "quick-start", + }, + { + id: "ff-2", + title: "Setting up email notifications", + collectionId: "notifications", + articleId: "email-notifications", + }, + { + id: "ff-3", + title: "Installing Capture agent", + collectionId: "infrastructure", + articleId: "installing-capture", + }, + { + id: "ff-4", + title: "Creating a status page", + collectionId: "status-pages", + articleId: "creating-status-page", + }, + { + id: "ff-5", + title: "Understanding Core Web Vitals", + collectionId: "pagespeed", + articleId: "core-web-vitals", + }, +]; + +// Helper functions +export const getCollection = (collectionId) => { + return collections.find((c) => c.id === collectionId); +}; + +export const getArticle = (collectionId, articleId) => { + const collection = getCollection(collectionId); + return collection?.articles.find((a) => a.id === articleId); +}; + +export const getTotalArticleCount = () => { + return collections.reduce((sum, collection) => sum + collection.articleCount, 0); +}; + +/** + * @typedef {Object} SearchResult + * @property {string} collectionId + * @property {string} collectionTitle + * @property {string} articleId + * @property {string} articleTitle + * @property {string} articleDescription + * @property {'title' | 'description' | 'keyword'} matchType + */ + +/** + * Search articles by query + * @param {string} query + * @returns {SearchResult[]} + */ +export const searchArticles = (query) => { + if (!query || query.trim().length < 2) return []; + + const normalizedQuery = query.toLowerCase().trim(); + const results = []; + + for (const collection of collections) { + for (const article of collection.articles) { + let matchType = null; + + // Check title match (highest priority) + if (article.title.toLowerCase().includes(normalizedQuery)) { + matchType = "title"; + } + // Check description match + else if (article.description.toLowerCase().includes(normalizedQuery)) { + matchType = "description"; + } + // Check keywords match + else if ( + article.keywords.some((kw) => kw.toLowerCase().includes(normalizedQuery)) + ) { + matchType = "keyword"; + } + + if (matchType) { + results.push({ + collectionId: collection.id, + collectionTitle: collection.title, + articleId: article.id, + articleTitle: article.title, + articleDescription: article.description, + matchType, + }); + } + } + } + + // Sort by match type priority: title > description > keyword + return results.sort((a, b) => { + const priority = { title: 0, description: 1, keyword: 2 }; + return priority[a.matchType] - priority[b.matchType]; + }); +}; diff --git a/client/src/Components/v1/UserGuide/index.jsx b/client/src/Components/v1/UserGuide/index.jsx new file mode 100644 index 000000000..c30b5d724 --- /dev/null +++ b/client/src/Components/v1/UserGuide/index.jsx @@ -0,0 +1,24 @@ +import SidebarWrapper from "./SidebarWrapper"; +import { + UserGuideSidebarProvider, + useUserGuideSidebarContext, + TAB_BAR_WIDTH, + DEFAULT_CONTENT_WIDTH, +} from "./UserGuideSidebarContext"; + +// Main component that wraps SidebarWrapper with context consumer +const UserGuideSidebar = () => { + const { isOpen, open, close } = useUserGuideSidebarContext(); + + return ; +}; + +export { + UserGuideSidebar, + UserGuideSidebarProvider, + useUserGuideSidebarContext, + TAB_BAR_WIDTH, + DEFAULT_CONTENT_WIDTH, +}; + +export default UserGuideSidebar; diff --git a/client/src/Components/v1/UserGuide/styles/theme.js b/client/src/Components/v1/UserGuide/styles/theme.js new file mode 100644 index 000000000..f3746657d --- /dev/null +++ b/client/src/Components/v1/UserGuide/styles/theme.js @@ -0,0 +1,222 @@ +/** + * Design Tokens for UserGuide + * Supports both light and dark themes via MUI theme integration + */ + +// Typography (theme-independent) +export const typography = { + fontFamily: { + sans: "'Inter', system-ui, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif", + mono: "'Geist Mono', 'Fira Code', 'Consolas', monospace", + }, + fontSize: { + xs: 12, + sm: 12, + base: 13, + md: 14, + lg: 15, + xl: 16, + "2xl": 20, + "3xl": 22, + "4xl": 26, + "5xl": 30, + }, + lineHeight: { + tight: 1.2, + snug: 1.3, + normal: 1.4, + relaxed: 1.5, + }, + fontWeight: { + normal: 400, + medium: 500, + semibold: 600, + bold: 700, + }, +}; + +// Spacing (theme-independent) +export const spacing = { + xs: "4px", + sm: "8px", + md: "12px", + lg: "16px", + xl: "24px", + "2xl": "32px", + "3xl": "40px", + "4xl": "48px", +}; + +// Brand colors (theme-independent) - Checkmate blue +export const brandColors = { + primary: "#1570EF", + primaryDark: "#1054B0", + primaryLight: "#5BA3F5", + success: "#17B26A", + warning: "#F79009", + error: "#F04438", +}; + +/** + * Get theme-aware colors based on MUI theme mode + * @param {'light' | 'dark'} mode - The current theme mode + * @returns {Object} Theme-aware color tokens + */ +export const getColors = (mode) => { + const isDark = mode === "dark"; + + return { + text: { + primary: isDark ? "#ffffff" : "#1C2130", + secondary: isDark ? "#d0d5dd" : "#475467", + muted: isDark ? "#98a2b3" : "#667085", + white: "#ffffff", + whiteMuted: "rgba(255, 255, 255, 0.8)", + }, + brand: brandColors, + border: { + default: isDark ? "#344054" : "#d0d5dd", + }, + background: { + white: isDark ? "#1a1a1a" : "#ffffff", + alt: isDark ? "#0d0d0d" : "#f9fafb", + code: isDark ? "#1C2130" : "#f3f4f6", + sidebar: isDark ? "#101010" : "#ffffff", + hover: isDark ? "#2a2a2a" : "#f3f4f6", + }, + }; +}; + +/** + * Get theme-aware border styles + * @param {'light' | 'dark'} mode - The current theme mode + * @returns {Object} Border styles + */ +export const getBorder = (mode) => { + const colors = getColors(mode); + return { + radius: "4px", + default: `1px solid ${colors.border.default}`, + }; +}; + +/** + * Get theme-aware image styles + * @param {'light' | 'dark'} mode - The current theme mode + * @returns {Object} Image styles + */ +export const getImageStyles = (mode) => { + const colors = getColors(mode); + const border = getBorder(mode); + const isDark = mode === "dark"; + + return { + image: { + width: "100%", + borderRadius: border.radius, + border: border.default, + display: "block", + boxShadow: isDark ? "0 2px 8px rgba(0, 0, 0, 0.3)" : "0 2px 8px rgba(0, 0, 0, 0.1)", + }, + caption: { + fontSize: 10, + color: colors.text.muted, + textAlign: "center", + fontStyle: "italic", + }, + }; +}; + +/** + * Get theme-aware chip styles + * @param {'light' | 'dark'} mode - The current theme mode + * @returns {Object} Chip styles + */ +export const getChipStyles = (mode) => { + const isDark = mode === "dark"; + + return { + url: { + backgroundColor: isDark ? "rgba(21, 112, 239, 0.2)" : "rgba(21, 112, 239, 0.1)", + color: isDark ? "#5BA3F5" : "#1570EF", + padding: "2px 8px", + borderRadius: "4px", + fontFamily: typography.fontFamily.mono, + fontSize: "0.9em", + }, + code: { + backgroundColor: isDark ? "#2d2d2d" : "#f3f4f6", + color: isDark ? "#d0d5dd" : "#1C2130", + padding: "2px 8px", + borderRadius: "4px", + fontFamily: typography.fontFamily.mono, + fontSize: "0.9em", + }, + }; +}; + +/** + * Get theme-aware component styles + * @param {'light' | 'dark'} mode - The current theme mode + * @returns {Object} Component styles + */ +export const getComponentStyles = (mode) => { + const colors = getColors(mode); + const border = getBorder(mode); + + return { + card: { + backgroundColor: colors.background.white, + border: border.default, + borderRadius: border.radius, + }, + heading: { + h2: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize["2xl"], + fontWeight: typography.fontWeight.semibold, + color: colors.text.primary, + lineHeight: typography.lineHeight.normal, + }, + h3: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.semibold, + color: colors.text.primary, + lineHeight: typography.lineHeight.normal, + }, + }, + paragraph: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize.base, + color: colors.text.primary, + lineHeight: typography.lineHeight.relaxed, + }, + listItem: { + fontSize: typography.fontSize.base, + color: colors.text.primary, + lineHeight: typography.lineHeight.snug, + }, + }; +}; + +// Legacy exports for backwards compatibility (dark mode defaults) +// These will be replaced by useUserGuideTheme hook usage +export const colors = getColors("dark"); +export const border = getBorder("dark"); +export const imageStyles = getImageStyles("dark"); +export const chipStyles = getChipStyles("dark"); +export const componentStyles = getComponentStyles("dark"); + +// Export everything as a single theme object (dark mode default) +export const theme = { + colors, + typography, + spacing, + border, + imageStyles, + chipStyles, + componentStyles, +}; + +export default theme; diff --git a/client/src/Components/v1/UserGuide/styles/useUserGuideTheme.js b/client/src/Components/v1/UserGuide/styles/useUserGuideTheme.js new file mode 100644 index 000000000..feb6d01dc --- /dev/null +++ b/client/src/Components/v1/UserGuide/styles/useUserGuideTheme.js @@ -0,0 +1,123 @@ +/** + * Hook to get theme-aware styles for UserGuide components + * Uses MUI's theme system from Checkmate + */ +import { useTheme } from "@mui/material/styles"; +import { typography, spacing, brandColors } from "./theme"; + +/** + * Returns theme-aware styles for UserGuide components + */ +export const useUserGuideTheme = () => { + const theme = useTheme(); + const isDark = theme.palette.mode === "dark"; + + const colors = { + text: { + primary: theme.palette.primary.contrastText, + secondary: theme.palette.primary.contrastTextSecondary, + muted: theme.palette.primary.contrastTextTertiary, + white: "#ffffff", + }, + brand: brandColors, + border: { + default: theme.palette.primary.lowContrast, + }, + background: { + white: isDark ? "#1a1a1a" : "#ffffff", + alt: theme.palette.primaryBackground.main, + code: isDark ? "#1C2130" : "#f3f4f6", + sidebar: theme.palette.primaryBackground.main, + hover: isDark ? "#2a2a2a" : "#f3f4f6", + }, + }; + + const border = { + radius: "4px", + default: `1px solid ${colors.border.default}`, + }; + + const imageStyles = { + image: { + width: "100%", + borderRadius: border.radius, + border: border.default, + display: "block", + boxShadow: isDark ? "0 2px 8px rgba(0, 0, 0, 0.3)" : "0 2px 8px rgba(0, 0, 0, 0.1)", + }, + caption: { + fontSize: 10, + color: colors.text.muted, + textAlign: "center", + fontStyle: "italic", + }, + }; + + const chipStyles = { + url: { + backgroundColor: isDark ? "rgba(21, 112, 239, 0.2)" : "rgba(21, 112, 239, 0.1)", + color: isDark ? "#5BA3F5" : "#1570EF", + padding: "2px 8px", + borderRadius: "4px", + fontFamily: typography.fontFamily.mono, + fontSize: "0.9em", + }, + code: { + backgroundColor: isDark ? "#2d2d2d" : "#f3f4f6", + color: isDark ? "#d0d5dd" : "#1C2130", + padding: "2px 8px", + borderRadius: "4px", + fontFamily: typography.fontFamily.mono, + fontSize: "0.9em", + }, + }; + + const componentStyles = { + card: { + backgroundColor: colors.background.white, + border: border.default, + borderRadius: border.radius, + }, + heading: { + h2: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize["2xl"], + fontWeight: typography.fontWeight.semibold, + color: colors.text.primary, + lineHeight: typography.lineHeight.normal, + }, + h3: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize.lg, + fontWeight: typography.fontWeight.semibold, + color: colors.text.primary, + lineHeight: typography.lineHeight.normal, + }, + }, + paragraph: { + fontFamily: typography.fontFamily.sans, + fontSize: typography.fontSize.base, + color: colors.text.primary, + lineHeight: typography.lineHeight.relaxed, + }, + listItem: { + fontSize: typography.fontSize.base, + color: colors.text.primary, + lineHeight: typography.lineHeight.snug, + }, + }; + + return { + colors, + typography, + spacing, + border, + imageStyles, + chipStyles, + componentStyles, + isDark, + theme, + }; +}; + +export default useUserGuideTheme;