Spaces:
Running
Running
Download apps/web/src/components/help/HelpSystem.tsx from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 53.6 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/help/HelpSystem.tsx
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/web/src/components/help/HelpSystem.tsx
-
curl -L -o HelpSystem.tsx https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/help/HelpSystem.tsx
53.6 kB
"use client";
import React, { useState, useCallback, useMemo, useEffect, ReactNode } from "react";
import {
X,
Search,
BookOpen,
Rocket,
Users,
Calendar,
Mic,
Scissors,
Send,
FileText,
Settings,
HelpCircle,
ChevronRight,
Keyboard,
Lightbulb,
Zap,
Globe,
Play,
BarChart3,
ExternalLink,
MessageSquare,
Sparkles,
} from "lucide-react";
// βββ Help Content Types ββββββββββββββββββββββββββββββββ
interface HelpSection {
id: string;
title: string;
icon: React.ElementType;
color: string;
articles: HelpArticle[];
}
interface HelpArticle {
id: string;
title: string;
content: ReactNode;
shortcut?: string;
tips?: string[];
}
// βββ Help Content Database βββββββββββββββββββββββββββββ
const helpSections: HelpSection[] = [
{
id: "getting-started",
title: "Getting Started",
icon: Rocket,
color: "var(--color-primary)",
articles: [
{
id: "welcome",
title: "Welcome to MicProtocol",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
MicProtocol is your all-in-one platform for podcast guest management. From discovering guests to publishing episodes, we handle the entire workflow so you can focus on creating great content.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Quick Start Checklist:</h4>
<ol style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<li>Create your first podcast</li>
<li>Add guests to your Pipeline</li>
<li>Send outreach sequences</li>
<li>Schedule recordings</li>
<li>Record with MicProtocol Studio</li>
<li>Create viral clips in Content Studio</li>
</ol>
</div>
),
tips: [
"Press ? or Ctrl+/ anytime to see all keyboard shortcuts",
"Use Ctrl+K to quickly search across the platform",
],
},
{
id: "keyboard-shortcuts",
title: "Keyboard Shortcuts",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
MicProtocol is designed for power users. Use keyboard shortcuts to navigate and perform actions without touching your mouse.
</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-3)" }}>
<ShortcutCard shortcut="Ctrl + K" description="Focus search bar" />
<ShortcutCard shortcut="?" description="Show all shortcuts" />
<ShortcutCard shortcut="Ctrl + 1-9" description="Navigate to section" />
<ShortcutCard shortcut="N" description="Add new item (context-aware)" />
<ShortcutCard shortcut="V" description="Toggle view mode" />
<ShortcutCard shortcut="Escape" description="Close / clear selection" />
</div>
</div>
),
},
{
id: "navigation",
title: "Navigating the Dashboard",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
The sidebar provides quick access to all major sections. The active section is highlighted with an orange indicator bar.
</p>
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<NavHelpItem icon={<BarChart3 size={16} />} label="Dashboard" description="Overview of your podcast activity" />
<NavHelpItem icon={<Users size={16} />} label="Guest Pipeline" description="Kanban board for managing guest stages" />
<NavHelpItem icon={<Calendar size={16} />} label="Schedule" description="Calendar view of recording sessions" />
<NavHelpItem icon={<Scissors size={16} />} label="Content Studio" description="AI-powered clip detection and export" />
<NavHelpItem icon={<Globe size={16} />} label="Network" description="Discover guests in the verified network" />
<NavHelpItem icon={<Send size={16} />} label="Outreach" description="Email sequences and engagement tracking" />
<NavHelpItem icon={<FileText size={16} />} label="Diary" description="Content planning and notes" />
</div>
</div>
),
},
],
},
{
id: "guest-pipeline",
title: "Guest Pipeline",
icon: Users,
color: "var(--status-prospect)",
articles: [
{
id: "pipeline-overview",
title: "Understanding the Pipeline",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
The Guest Pipeline is a kanban board that tracks guests through five stages: <strong>Prospect</strong> β <strong>Contacted</strong> β <strong>Scheduled</strong> β <strong>Recorded</strong> β <strong>Published</strong>.
</p>
<p style={{ marginBottom: "var(--space-3)" }}>
Drag and drop guest cards between columns to update their status. The pipeline provides a visual overview of your entire guest workflow.
</p>
<div style={{ padding: "var(--space-3)", background: "var(--bg-secondary)", borderRadius: "var(--radius-md)", borderLeft: "3px solid var(--color-primary)" }}>
<strong>Pro Tip:</strong> Use the priority filter to focus on high-value guests first. High priority (70+) guests are most likely to convert.
</div>
</div>
),
tips: [
"Drag cards between columns to change guest status",
"Use Ctrl+A to select all visible guests for bulk actions",
"Press N to quickly add a new guest",
],
},
{
id: "adding-guests",
title: "Adding Guests",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Click the "Add Guest" button or press <kbd>N</kbd> to open the add guest modal. You'll need at minimum a name and email address.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Guest Fields:</h4>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li><strong>Full Name</strong> β Required. The guest's display name.</li>
<li><strong>Email</strong> β Required. Used for outreach sequences.</li>
<li><strong>Company</strong> β Optional. Helps with personalization.</li>
<li><strong>Title</strong> β Optional. Job title or role.</li>
<li><strong>Bio</strong> β Optional. Auto-enriched from LinkedIn when available.</li>
<li><strong>Social Links</strong> β Optional. LinkedIn, Twitter, website.</li>
</ul>
</div>
),
},
{
id: "guest-detail",
title: "Guest Detail Panel",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Click any guest card to open the detail panel. This gives you a complete view of the guest including contact info, technical comfort profile, timeline, and episodes.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Tabs:</h4>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li><strong>Overview</strong> β Contact info, bio, technical profile, tags, notes</li>
<li><strong>Timeline</strong> β Journey events, outreach history, notes</li>
<li><strong>Episodes</strong> β Scheduled and past episodes with this guest</li>
</ul>
</div>
),
tips: [
"Press Escape to close the detail panel",
"The technical profile helps you prepare for recording sessions",
],
},
{
id: "bulk-actions",
title: "Bulk Actions",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Select multiple guests using checkboxes (or press <kbd>Ctrl+A</kbd> to select all). A bulk actions bar will appear at the top.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Available Bulk Actions:</h4>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li><strong>Move to Status</strong> β Batch update guest status</li>
<li><strong>Delete</strong> β Remove multiple guests at once</li>
</ul>
</div>
),
},
],
},
{
id: "scheduling",
title: "Scheduling",
icon: Calendar,
color: "var(--status-scheduled)",
articles: [
{
id: "schedule-overview",
title: "Managing Your Schedule",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
The Schedule page shows a weekly calendar view of your recording sessions. Navigate between weeks using the arrow buttons or keyboard shortcuts.
</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-3)" }}>
<ShortcutCard shortcut="β" description="Previous week" />
<ShortcutCard shortcut="β" description="Next week" />
<ShortcutCard shortcut="T" description="Go to today" />
<ShortcutCard shortcut="A" description="Add recording slot" />
</div>
</div>
),
},
{
id: "recording-slots",
title: "Recording Slots",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Click "Add Slot" to create a new recording session. You'll need to select an episode, date, and time range.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Slot Fields:</h4>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li><strong>Episode</strong> β Select a scheduled episode</li>
<li><strong>Date</strong> β Recording date</li>
<li><strong>Start/End Time</strong> β Time window for the session</li>
<li><strong>Timezone</strong> β Auto-detected from your browser</li>
<li><strong>Meet Link</strong> β Optional. Zoom, Google Meet, etc.</li>
</ul>
</div>
),
},
],
},
{
id: "recording",
title: "Recording Studio",
icon: Mic,
color: "var(--color-error)",
articles: [
{
id: "studio-overview",
title: "MicProtocol Studio",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
MicProtocol Studio provides local-first, lossless double-ender recording. Audio is recorded locally on each participant's device and synced via NTP timestamps for perfect quality.
</p>
<div style={{ padding: "var(--space-3)", background: "var(--bg-secondary)", borderRadius: "var(--radius-md)", borderLeft: "3px solid var(--color-error)" }}>
<strong>Important:</strong> Both host and guest should use the Studio for the best quality. The recording continues even if the internet connection drops.
</div>
</div>
),
},
{
id: "recording-controls",
title: "Recording Controls",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
The recording interface is simple and distraction-free. Use keyboard shortcuts for quick control during recording.
</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-3)" }}>
<ShortcutCard shortcut="Space" description="Start/Stop recording" />
<ShortcutCard shortcut="M" description="Toggle mute" />
</div>
<div style={{ marginTop: "var(--space-3)", padding: "var(--space-3)", background: "var(--bg-secondary)", borderRadius: "var(--radius-md)" }}>
<strong>Mute Warning:</strong> If you're speaking while muted, a visual alert will appear. This prevents "Can you hear me?" moments.
</div>
</div>
),
tips: [
"The timer glows red during recording for visibility",
"Audio chunks are persisted to IndexedDB as a safety net",
"Storage estimation helps avoid mid-recording space issues",
],
},
{
id: "double-ender",
title: "Double-End Recording",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Double-ender recording means each participant records their own audio locally. This eliminates quality issues from internet compression and provides studio-quality audio for post-production.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>How It Works:</h4>
<ol style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<li>Both participants join the Studio session</li>
<li>Audio is recorded locally on each device</li>
<li>NTP clock sync ensures timestamps align</li>
<li>After recording, tracks are uploaded and synced</li>
<li>The Content Studio processes the merged audio</li>
</ol>
</div>
),
},
],
},
{
id: "content-studio",
title: "Content Studio",
icon: Scissors,
color: "var(--color-success)",
articles: [
{
id: "studio-overview",
title: "Content Studio Overview",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Content Studio uses AI to automatically detect viral moments in your episodes and generate platform-ready clips. Transform long-form content into TikTok, YouTube Shorts, LinkedIn, and Twitter content.
</p>
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<PlatformBadge name="TikTok / Reels" aspect="9:16" color="#00F2EA" />
<PlatformBadge name="YouTube Shorts" aspect="16:9" color="#FF0000" />
<PlatformBadge name="LinkedIn" aspect="1:1" color="#0A66C2" />
<PlatformBadge name="Twitter / X" aspect="16:9" color="#1DA1F2" />
</div>
</div>
),
tips: [
"Press D to auto-detect clips from the latest episode",
"High virality scores (90+) indicate strong viral potential",
],
},
{
id: "clip-detection",
title: "AI Clip Detection",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Click "Auto-Detect Clips" or press <kbd>D</kbd> to analyze your latest recorded episode. The AI identifies engaging moments based on:
</p>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li>Emotional peaks and energy spikes</li>
<li>Contrarian or surprising statements</li>
<li>Actionable advice and frameworks</li>
<li>Storytelling moments</li>
<li>Quote-worthy soundbites</li>
</ul>
</div>
),
},
{
id: "exporting",
title: "Exporting Clips",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Once you approve a clip, click "Export" to generate a platform-ready file. Choose your target platform and the clip will be reformatted with the correct aspect ratio and dimensions.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Export includes:</h4>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li>Correct aspect ratio for the platform</li>
<li>Suggested caption and hashtags</li>
<li>Subtitle burn-in (when available)</li>
<li>Optimized encoding for each platform</li>
</ul>
</div>
),
},
],
},
{
id: "outreach",
title: "Outreach",
icon: Send,
color: "var(--color-info)",
articles: [
{
id: "outreach-overview",
title: "Email Sequences",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Outreach sequences automate your guest communication. Create multi-step email sequences that adapt based on guest responses.
</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-4)" }}>
<StatCard label="Open Rate" description="Track email opens" />
<StatCard label="Reply Rate" description="Track guest responses" />
</div>
</div>
),
},
{
id: "creating-sequences",
title: "Creating Sequences",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
To start an outreach sequence, open a guest in the Pipeline and click "Send Outreach". You can customize the email template and timing for each step.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Sequence Steps:</h4>
<ol style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<li><strong>Initial Outreach</strong> β Introduction and invitation</li>
<li><strong>Follow-up</strong> β Reminder if no response</li>
<li><strong>Last Chance</strong> β Final attempt with urgency</li>
</ol>
</div>
),
},
],
},
{
id: "network",
title: "Guest Network",
icon: Globe,
color: "var(--color-secondary)",
articles: [
{
id: "network-overview",
title: "Discovering Guests",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
The Guest Network is a verified marketplace of podcast guests. Browse experts by category, view their profiles, and invite them to your show.
</p>
<div style={{ padding: "var(--space-3)", background: "var(--bg-secondary)", borderRadius: "var(--radius-md)", borderLeft: "3px solid var(--color-secondary)" }}>
<strong>Verification:</strong> All network guests have verified profiles with social links, bio, and episode history. Look for the verification badge.
</div>
</div>
),
},
],
},
{
id: "settings",
title: "Settings",
icon: Settings,
color: "var(--text-tertiary)",
articles: [
{
id: "profile",
title: "Profile Settings",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Manage your account details, avatar, and notification preferences. Your profile information is visible to guests in the network.
</p>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
<li><strong>Avatar</strong> β Upload a professional photo</li>
<li><strong>Display Name</strong> β How you appear to guests</li>
<li><strong>Email Notifications</strong> β Configure alert preferences</li>
</ul>
</div>
),
},
{
id: "platform",
title: "Platform Settings",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
Configure your podcast platform integrations, default recording settings, and team permissions.
</p>
</div>
),
},
],
},
];
// βββ Helper Components βββββββββββββββββββββββββββββββββ
function ShortcutCard({ shortcut, description }: { shortcut: string; description: string }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "var(--space-2) var(--space-3)",
background: "var(--bg-secondary)",
borderRadius: "var(--radius-md)",
border: "1px solid var(--border-color-subtle)",
}}
>
<span style={{ fontSize: "var(--text-sm)", color: "var(--text-secondary)" }}>{description}</span>
<kbd
style={{
display: "inline-flex",
alignItems: "center",
padding: "2px 8px",
background: "var(--bg-tertiary)",
border: "1px solid var(--border-color)",
borderRadius: "var(--radius-sm)",
fontSize: "var(--text-xs)",
fontWeight: 600,
fontFamily: "var(--font-mono, monospace)",
color: "var(--text-primary)",
minWidth: 24,
justifyContent: "center",
lineHeight: "1.6",
}}
>
{shortcut}
</kbd>
</div>
);
}
function NavHelpItem({ icon, label, description }: { icon: ReactNode; label: string; description: string }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-3)",
padding: "var(--space-2) var(--space-3)",
background: "var(--bg-secondary)",
borderRadius: "var(--radius-md)",
}}
>
<div style={{ color: "var(--color-primary)", flexShrink: 0 }}>{icon}</div>
<div>
<div style={{ fontWeight: 600, fontSize: "var(--text-sm)" }}>{label}</div>
<div style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }}>{description}</div>
</div>
</div>
);
}
function StatCard({ label, description }: { label: string; description: string }) {
return (
<div style={{ padding: "var(--space-3)", background: "var(--bg-secondary)", borderRadius: "var(--radius-md)" }}>
<div style={{ fontWeight: 600, fontSize: "var(--text-sm)" }}>{label}</div>
<div style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }}>{description}</div>
</div>
);
}
function PlatformBadge({ name, aspect, color }: { name: string; aspect: string; color: string }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-2)",
padding: "var(--space-2) var(--space-3)",
background: `${color}10`,
border: `1px solid ${color}30`,
borderRadius: "var(--radius-md)",
fontSize: "var(--text-sm)",
}}
>
<span style={{ color, fontWeight: 700 }}>{name}</span>
<span style={{ color: "var(--text-tertiary)", fontSize: "var(--text-xs)" }}>{aspect}</span>
</div>
);
}
// βββ Context-Aware Page Help βββββββββββββββββββββββββββ
interface PageHelpConfig {
pattern: RegExp;
title: string;
description: string;
quickActions: { label: string; shortcut?: string; description: string }[];
tips: string[];
}
const pageHelpConfigs: PageHelpConfig[] = [
{
pattern: /\/podcasts\/.*\/guests/,
title: "Guest Pipeline",
description: "Manage your guest workflow from prospect to published. Drag cards between columns to update status.",
quickActions: [
{ label: "Add Guest", shortcut: "N", description: "Open the add guest modal" },
{ label: "Search", shortcut: "/", description: "Focus the search bar" },
{ label: "Toggle View", shortcut: "V", description: "Switch between kanban and list" },
{ label: "Select All", shortcut: "Ctrl+A", description: "Select all visible guests" },
{ label: "Filter: High", shortcut: "2", description: "Show high-priority guests" },
{ label: "Clear Selection", shortcut: "Esc", description: "Deselect all guests" },
],
tips: [
"Drag guests between columns to change their status",
"Click a guest card to see full details and actions",
"Use the priority filter to focus on high-value guests",
],
},
{
pattern: /\/podcasts\/.*\/schedule/,
title: "Schedule",
description: "Manage recording sessions and availabilities. Navigate weeks with arrow keys.",
quickActions: [
{ label: "Previous Week", shortcut: "β", description: "Go to previous week" },
{ label: "Next Week", shortcut: "β", description: "Go to next week" },
{ label: "Go to Today", shortcut: "T", description: "Jump to current week" },
{ label: "Add Slot", shortcut: "A", description: "Create a recording slot" },
],
tips: [
"Slots show confirmed (blue) and pending (yellow) states",
"Click a slot to view details or add a meet link",
"Calendar sync is coming soon",
],
},
{
pattern: /\/podcasts\/.*\/record/,
title: "Recording Studio",
description: "Local-first, lossless double-ender recording. Keep your hands on the keyboard.",
quickActions: [
{ label: "Start/Stop", shortcut: "Space", description: "Toggle recording" },
{ label: "Toggle Mute", shortcut: "M", description: "Mute/unmute microphone" },
],
tips: [
"The timer glows red during active recording",
"A warning appears if you speak while muted",
"Audio is persisted to IndexedDB as a safety net",
],
},
{
pattern: /\/podcasts\/.*\/content-studio/,
title: "Content Studio",
description: "AI-powered clip detection and multi-platform export. Turn episodes into viral content.",
quickActions: [
{ label: "Detect Clips", shortcut: "D", description: "AI-analyze the latest episode" },
],
tips: [
"High virality scores (90+) indicate strong viral potential",
"Approve clips before exporting to ensure quality",
"Each platform has optimized aspect ratios",
],
},
{
pattern: /\/podcasts\/.*\/episodes/,
title: "Episodes",
description: "View and manage all episodes for your podcast. Track status from recording to publication.",
quickActions: [],
tips: [
"Episode status updates automatically as guests move through the pipeline",
"Click an episode to see detailed information and actions",
],
},
{
pattern: /\/outreach/,
title: "Outreach",
description: "Manage email sequences and track engagement metrics. Automate guest communication.",
quickActions: [],
tips: [
"Create sequences from the Guest Pipeline",
"Track open rates and reply rates for optimization",
"Sequences pause automatically when a guest replies",
],
},
{
pattern: /\/guest-network/,
title: "Guest Network",
description: "Discover verified podcast guests. Browse by category and invite experts to your show.",
quickActions: [],
tips: [
"All network guests have verified profiles",
"Use filters to find guests by expertise",
"Invite guests directly to your pipeline",
],
},
{
pattern: /\/diary/,
title: "Diary",
description: "Content planning and episode notes. Organize your thoughts before recording.",
quickActions: [],
tips: [
"Use the flow view to visualize content connections",
"Notes are auto-saved as you type",
],
},
];
function usePageHelp(pathname: string): PageHelpConfig | null {
return useMemo(() => {
return pageHelpConfigs.find((config) => config.pattern.test(pathname)) || null;
}, [pathname]);
}
// βββ Main HelpSystem Component βββββββββββββββββββββββββ
interface HelpSystemProps {
isOpen: boolean;
onClose: () => void;
pathname?: string;
}
export function HelpSystem({ isOpen, onClose, pathname = "" }: HelpSystemProps) {
const [searchQuery, setSearchQuery] = useState("");
const [activeSection, setActiveSection] = useState<string | null>(null);
const [activeArticle, setActiveArticle] = useState<string | null>(null);
const pageHelp = usePageHelp(pathname);
// Close the help system and reset internal state
const handleClose = useCallback(() => {
setSearchQuery("");
setActiveSection(null);
setActiveArticle(null);
onClose();
}, [onClose]);
// Close on Escape
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
e.stopPropagation();
handleClose();
}
};
document.addEventListener("keydown", handleKeyDown, true);
return () => document.removeEventListener("keydown", handleKeyDown, true);
}, [isOpen, onClose, handleClose]);
// Navigate to article
const goToArticle = useCallback((sectionId: string, articleId: string) => {
setActiveSection(sectionId);
setActiveArticle(articleId);
setSearchQuery("");
}, []);
// Go back to section list
const goToSectionList = useCallback(() => {
setActiveSection(null);
setActiveArticle(null);
}, []);
// Filter articles based on search
const filteredSections = useMemo(() => {
if (!searchQuery.trim()) return helpSections;
const q = searchQuery.toLowerCase();
return helpSections
.map((section) => ({
...section,
articles: section.articles.filter(
(article) =>
article.title.toLowerCase().includes(q) ||
section.title.toLowerCase().includes(q)
),
}))
.filter((section) => section.articles.length > 0);
}, [searchQuery]);
// Find active article content
const currentArticle = useMemo(() => {
if (!activeSection || !activeArticle) return null;
const section = helpSections.find((s) => s.id === activeSection);
if (!section) return null;
return section.articles.find((a) => a.id === activeArticle) || null;
}, [activeSection, activeArticle]);
if (!isOpen) return null;
return (
<>
<div className="modal-overlay" onClick={handleClose} style={{ zIndex: 10000 }} />
<div
style={{
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
background: "var(--bg-primary)",
border: "1px solid var(--border-color)",
borderRadius: "var(--radius-lg)",
boxShadow: "var(--shadow-xl)",
width: 780,
maxWidth: "92vw",
height: "80vh",
maxHeight: "80vh",
zIndex: 10001,
overflow: "hidden",
display: "flex",
flexDirection: "column",
animation: "revealScale 0.25s var(--ease-out-expo) forwards",
}}
role="dialog"
aria-label="Help center"
>
{/* Header */}
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "var(--space-4) var(--space-5)",
borderBottom: "1px solid var(--border-color)",
flexShrink: 0,
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-2)" }}>
<HelpCircle size={20} color="var(--color-primary)" />
<h2 style={{ fontWeight: 700, fontSize: "var(--text-lg)", fontFamily: "var(--font-display)" }}>
{activeArticle ? currentArticle?.title : activeSection ? helpSections.find((s) => s.id === activeSection)?.title : "Help Center"}
</h2>
</div>
<button
onClick={handleClose}
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--text-tertiary)",
padding: "var(--space-1)",
borderRadius: "var(--radius-sm)",
}}
aria-label="Close help"
>
<X size={20} />
</button>
</div>
{/* Content */}
<div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
{/* Left Sidebar β Sections */}
<div
style={{
width: 220,
flexShrink: 0,
borderRight: "1px solid var(--border-color)",
overflowY: "auto",
padding: "var(--space-3)",
display: activeSection ? "none" : "block",
}}
>
{/* Search */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-2)",
padding: "var(--space-2) var(--space-3)",
background: "var(--bg-tertiary)",
borderRadius: "var(--radius-md)",
marginBottom: "var(--space-3)",
}}
>
<Search size={14} color="var(--text-tertiary)" />
<input
type="text"
placeholder="Search help..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{
border: "none",
background: "transparent",
outline: "none",
fontSize: "var(--text-sm)",
width: "100%",
color: "var(--text-primary)",
}}
autoFocus
/>
</div>
{/* Page-specific quick help */}
{pageHelp && !searchQuery && (
<div
style={{
padding: "var(--space-3)",
background: "rgba(255, 159, 10, 0.06)",
border: "1px solid rgba(255, 159, 10, 0.15)",
borderRadius: "var(--radius-md)",
marginBottom: "var(--space-3)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-2)", marginBottom: "var(--space-2)" }}>
<Sparkles size={14} color="var(--color-primary)" />
<span style={{ fontWeight: 600, fontSize: "var(--text-sm)", color: "var(--color-primary)" }}>
On this page
</span>
</div>
<p style={{ fontSize: "var(--text-xs)", color: "var(--text-secondary)", marginBottom: "var(--space-2)" }}>
{pageHelp.description}
</p>
{pageHelp.quickActions.length > 0 && (
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{pageHelp.quickActions.slice(0, 4).map((action) => (
<div key={action.label} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", fontSize: "var(--text-xs)" }}>
<span style={{ color: "var(--text-secondary)" }}>{action.label}</span>
{action.shortcut && (
<kbd style={{
padding: "1px 5px",
background: "var(--bg-primary)",
border: "1px solid var(--border-color)",
borderRadius: "var(--radius-sm)",
fontSize: "10px",
fontFamily: "var(--font-mono, monospace)",
color: "var(--text-tertiary)",
}}>
{action.shortcut}
</kbd>
)}
</div>
))}
</div>
)}
</div>
)}
{/* Section list */}
<div className="stagger-children" style={{ display: "flex", flexDirection: "column", gap: 2 }}>
{filteredSections.map((section) => {
const Icon = section.icon;
return (
<button
key={section.id}
onClick={() => goToArticle(section.id, section.articles[0].id)}
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-2)",
padding: "var(--space-2) var(--space-3)",
background: "transparent",
border: "none",
borderRadius: "var(--radius-md)",
cursor: "pointer",
fontSize: "var(--text-sm)",
color: "var(--text-secondary)",
textAlign: "left",
width: "100%",
transition: "background var(--transition-fast), color var(--transition-fast)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "var(--bg-secondary)";
e.currentTarget.style.color = "var(--text-primary)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "transparent";
e.currentTarget.style.color = "var(--text-secondary)";
}}
>
<Icon size={16} color={section.color} />
<span style={{ flex: 1 }}>{section.title}</span>
<ChevronRight size={14} color="var(--text-tertiary)" />
</button>
);
})}
</div>
</div>
{/* Main Content */}
<div style={{ flex: 1, overflowY: "auto", padding: "var(--space-5)" }}>
{/* Section articles list */}
{activeSection && !activeArticle && (
<div className="stagger-children" style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<button
onClick={goToSectionList}
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-2)",
background: "none",
border: "none",
cursor: "pointer",
color: "var(--color-primary)",
fontSize: "var(--text-sm)",
fontWeight: 600,
padding: "var(--space-2) 0",
marginBottom: "var(--space-2)",
}}
>
β Back to all sections
</button>
{helpSections
.find((s) => s.id === activeSection)
?.articles.map((article) => (
<button
key={article.id}
onClick={() => setActiveArticle(article.id)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "var(--space-3) var(--space-4)",
background: "var(--bg-secondary)",
border: "1px solid var(--border-color-subtle)",
borderRadius: "var(--radius-md)",
cursor: "pointer",
fontSize: "var(--text-sm)",
color: "var(--text-primary)",
fontWeight: 500,
textAlign: "left",
width: "100%",
transition: "border-color var(--transition-fast), box-shadow var(--transition-fast)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = "var(--color-primary)";
e.currentTarget.style.boxShadow = "var(--shadow-sm)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = "var(--border-color-subtle)";
e.currentTarget.style.boxShadow = "none";
}}
>
<span>{article.title}</span>
<ChevronRight size={14} color="var(--text-tertiary)" />
</button>
))}
</div>
)}
{/* Article content */}
{currentArticle && (
<div className="animate-fade-in">
<button
onClick={goToSectionList}
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-2)",
background: "none",
border: "none",
cursor: "pointer",
color: "var(--color-primary)",
fontSize: "var(--text-sm)",
fontWeight: 600,
padding: "var(--space-2) 0",
marginBottom: "var(--space-4)",
}}
>
β Back to {helpSections.find((s) => s.id === activeSection)?.title}
</button>
<div style={{ fontSize: "var(--text-sm)", color: "var(--text-secondary)", lineHeight: 1.7 }}>
{currentArticle.content}
</div>
{/* Tips */}
{currentArticle.tips && currentArticle.tips.length > 0 && (
<div
style={{
marginTop: "var(--space-5)",
padding: "var(--space-4)",
background: "rgba(16, 185, 129, 0.06)",
border: "1px solid rgba(16, 185, 129, 0.15)",
borderRadius: "var(--radius-md)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-2)", marginBottom: "var(--space-2)" }}>
<Lightbulb size={16} color="var(--color-success)" />
<span style={{ fontWeight: 600, fontSize: "var(--text-sm)", color: "var(--color-success)" }}>
Tips
</span>
</div>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
{currentArticle.tips.map((tip, i) => (
<li key={i} style={{ fontSize: "var(--text-sm)", color: "var(--text-secondary)" }}>
{tip}
</li>
))}
</ul>
</div>
)}
{/* Page-specific tips */}
{pageHelp && pageHelp.tips.length > 0 && !currentArticle.tips && (
<div
style={{
marginTop: "var(--space-5)",
padding: "var(--space-4)",
background: "rgba(255, 159, 10, 0.06)",
border: "1px solid rgba(255, 159, 10, 0.15)",
borderRadius: "var(--radius-md)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-2)", marginBottom: "var(--space-2)" }}>
<Zap size={16} color="var(--color-primary)" />
<span style={{ fontWeight: 600, fontSize: "var(--text-sm)", color: "var(--color-primary)" }}>
Quick Tips for {pageHelp.title}
</span>
</div>
<ul style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-1)" }}>
{pageHelp.tips.map((tip, i) => (
<li key={i} style={{ fontSize: "var(--text-sm)", color: "var(--text-secondary)" }}>
{tip}
</li>
))}
</ul>
</div>
)}
</div>
)}
{/* Default view β no section/article selected */}
{!activeSection && !activeArticle && !searchQuery && (
<div>
<div style={{ textAlign: "center", marginBottom: "var(--space-6)" }}>
<HelpCircle size={48} color="var(--text-tertiary)" style={{ opacity: 0.3, margin: "0 auto var(--space-3)" }} />
<h3 style={{ fontWeight: 700, fontSize: "var(--text-lg)", marginBottom: "var(--space-1)" }}>
How can we help?
</h3>
<p style={{ fontSize: "var(--text-sm)", color: "var(--text-tertiary)" }}>
Browse sections or search for specific topics
</p>
</div>
{/* Quick Links */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-3)" }}>
{helpSections.slice(0, 6).map((section) => {
const Icon = section.icon;
return (
<button
key={section.id}
onClick={() => goToArticle(section.id, section.articles[0].id)}
style={{
display: "flex",
alignItems: "center",
gap: "var(--space-3)",
padding: "var(--space-3) var(--space-4)",
background: "var(--bg-secondary)",
border: "1px solid var(--border-color-subtle)",
borderRadius: "var(--radius-md)",
cursor: "pointer",
textAlign: "left",
width: "100%",
transition: "border-color var(--transition-fast), transform var(--transition-fast)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = section.color;
e.currentTarget.style.transform = "translateY(-1px)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = "var(--border-color-subtle)";
e.currentTarget.style.transform = "none";
}}
>
<div
style={{
width: 36,
height: 36,
borderRadius: "var(--radius-md)",
background: `${section.color}15`,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}}
>
<Icon size={18} color={section.color} />
</div>
<div>
<div style={{ fontWeight: 600, fontSize: "var(--text-sm)" }}>{section.title}</div>
<div style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }}>
{section.articles.length} article{section.articles.length !== 1 ? "s" : ""}
</div>
</div>
</button>
);
})}
</div>
{/* Keyboard shortcuts hint */}
<div
style={{
marginTop: "var(--space-6)",
padding: "var(--space-4)",
background: "var(--bg-secondary)",
borderRadius: "var(--radius-md)",
border: "1px solid var(--border-color-subtle)",
textAlign: "center",
}}
>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "var(--space-2)", marginBottom: "var(--space-2)" }}>
<Keyboard size={16} color="var(--color-primary)" />
<span style={{ fontWeight: 600, fontSize: "var(--text-sm)" }}>Keyboard Shortcuts</span>
</div>
<p style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)", marginBottom: "var(--space-2)" }}>
Press <kbd style={{ padding: "1px 6px", background: "var(--bg-tertiary)", border: "1px solid var(--border-color)", borderRadius: "var(--radius-sm)", fontSize: "10px", fontFamily: "var(--font-mono, monospace)" }}>?</kbd> or <kbd style={{ padding: "1px 6px", background: "var(--bg-tertiary)", border: "1px solid var(--border-color)", borderRadius: "var(--radius-sm)", fontSize: "10px", fontFamily: "var(--font-mono, monospace)" }}>Ctrl+/</kbd> to see all shortcuts
</p>
</div>
</div>
)}
{/* Search results */}
{searchQuery && (
<div>
{filteredSections.length === 0 ? (
<div style={{ textAlign: "center", padding: "var(--space-8)", color: "var(--text-tertiary)" }}>
<Search size={32} style={{ margin: "0 auto var(--space-2)", opacity: 0.3 }} />
<p style={{ fontSize: "var(--text-sm)" }}>No results for "{searchQuery}"</p>
<p style={{ fontSize: "var(--text-xs)" }}>Try different keywords</p>
</div>
) : (
<div className="stagger-children" style={{ display: "flex", flexDirection: "column", gap: "var(--space-4)" }}>
{filteredSections.map((section) => (
<div key={section.id}>
<h3 style={{ fontSize: "var(--text-xs)", fontWeight: 600, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "var(--space-2)" }}>
{section.title}
</h3>
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
{section.articles.map((article) => (
<button
key={article.id}
onClick={() => goToArticle(section.id, article.id)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "var(--space-3) var(--space-4)",
background: "var(--bg-secondary)",
border: "1px solid var(--border-color-subtle)",
borderRadius: "var(--radius-md)",
cursor: "pointer",
fontSize: "var(--text-sm)",
color: "var(--text-primary)",
fontWeight: 500,
textAlign: "left",
width: "100%",
}}
>
<span>{article.title}</span>
<ChevronRight size={14} color="var(--text-tertiary)" />
</button>
))}
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
{/* Footer */}
<div
style={{
padding: "var(--space-3) var(--space-5)",
borderTop: "1px solid var(--border-color)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexShrink: 0,
}}
>
<span style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }}>
Need more help?{" "}
<a href="mailto:support@micprotocol.com" style={{ color: "var(--color-primary)" }}>
Contact Support
</a>
</span>
<span style={{ fontSize: "var(--text-xs)", color: "var(--text-tertiary)" }}>
Press <kbd style={{ padding: "0 4px", background: "var(--bg-tertiary)", border: "1px solid var(--border-color)", borderRadius: "var(--radius-xs, 3px)", fontSize: "10px", fontFamily: "var(--font-mono, monospace)" }}>Esc</kbd> to close
</span>
</div>
</div>
</>
);
}
"use client";
import React, { useState, useCallback, useMemo, useEffect, ReactNode } from "react";
import {
X,
Search,
BookOpen,
Rocket,
Users,
Calendar,
Mic,
Scissors,
Send,
FileText,
Settings,
HelpCircle,
ChevronRight,
Keyboard,
Lightbulb,
Zap,
Globe,
Play,
BarChart3,
ExternalLink,
MessageSquare,
Sparkles,
} from "lucide-react";
// βββ Help Content Types ββββββββββββββββββββββββββββββββ
interface HelpSection {
id: string;
title: string;
icon: React.ElementType;
color: string;
articles: HelpArticle[];
}
interface HelpArticle {
id: string;
title: string;
content: ReactNode;
shortcut?: string;
tips?: string[];
}
// βββ Help Content Database βββββββββββββββββββββββββββββ
const helpSections: HelpSection[] = [
{
id: "getting-started",
title: "Getting Started",
icon: Rocket,
color: "var(--color-primary)",
articles: [
{
id: "welcome",
title: "Welcome to MicProtocol",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
MicProtocol is your all-in-one platform for podcast guest management. From discovering guests to publishing episodes, we handle the entire workflow so you can focus on creating great content.
</p>
<h4 style={{ fontWeight: 600, marginBottom: "var(--space-2)" }}>Quick Start Checklist:</h4>
<ol style={{ paddingLeft: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
<li>Create your first podcast</li>
<li>Add guests to your Pipeline</li>
<li>Send outreach sequences</li>
<li>Schedule recordings</li>
<li>Record with MicProtocol Studio</li>
<li>Create viral clips in Content Studio</li>
</ol>
</div>
),
tips: [
"Press ? or Ctrl+/ anytime to see all keyboard shortcuts",
"Use Ctrl+K to quickly search across the platform",
],
},
{
id: "keyboard-shortcuts",
title: "Keyboard Shortcuts",
content: (
<div>
<p style={{ marginBottom: "var(--space-3)" }}>
MicProtocol is designed for power users. Use keyboard shortcuts to navigate and perform actions without touching your mouse.
</p>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-3)" }}>
<ShortcutCard shortcut="Ctrl + K" description="Focus search bar" />
<ShortcutCard shortcut="?" description="Show all shortcuts" />
<ShortcutCard shortcut="Ctrl + 1-9" description="Navigate to section" />
<ShortcutCard shortcut="N" description="Add new item (context-aware)" />
<ShortcutCard shortcut="V" description="Toggle view mode" />
<ShortcutCard shortcut="Escape" description="Close / clear selection" />
</div>
</div>
),
},
{
id: "navigation",
title: "Navigating the Dashboard",
content: (