ppt-tool/frontend/app/(presentation-generator)/presentation/components/Help.tsx
Vadym Samoilenko cf21ba4516 Phase 1-2: Foundation + Admin Panel & Client Management
Phase 1 (Foundation):
- Project restructure (presenton-main → backend/ + frontend/)
- Database schema (8 new models, Alembic config, seed script)
- Auth (Azure AD SSO + dev bypass, JWT sessions, AuthMiddleware)
- RBAC (access_service, rbac_middleware, admin routers)
- Audit logging (fire-and-forget, AuditMiddleware, admin router)
- i18n (react-i18next with 5 namespace files)

Phase 2 (Admin Panel & Client Management):
- Admin panel shell (sidebar layout, role guard, 12 pages)
- Redux admin slice with 18 async thunks
- User management (role changes, deactivation)
- Client management (CRUD, brand config, team management)
- Brand config editor (colors, fonts, logos, voice rules)
- Master deck upload & parser (PPTX → HTML → React pipeline)
- Audit log viewer with filters and CSV/JSON export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:37:17 +00:00

312 lines
11 KiB
TypeScript

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { HelpCircle, X, Search } from "lucide-react";
import React, { useState, useEffect, useRef } from "react";
const helpQuestions = [
{
id: 1,
category: "Images",
question: "How do I change an image?",
answer:
"Click on any image to reveal the image toolbar. You'll see options to Edit, Adjust position, and change how the image fits within its container. The Edit option allows you to replace or modify the current image.",
},
{
id: 2,
category: "Images",
question: "Can I generate new images with AI?",
answer:
"Yes! Click on any image and select the Edit option from the toolbar. In the side panel that appears, you'll find the AI Generate tab. Enter your prompt describing the image you want, and our AI will generate an image based on your description.",
},
{
id: 3,
category: "Images",
question: "How do I upload my own images?",
answer:
"Click on any image, then select Edit from the toolbar. In the side panel, click on the Upload tab at the top. You can browse your files to select one. Once uploaded, you can apply it to your design.",
},
{
id: 11,
category: "AI Prompts",
question: "Can I change slide layout through prompt?",
answer:
"Yes you can! Click on the WandSparkles icon on the top left of each slide and it will give you a prompt input box. Describe your layout requirements and the AI will change the slide layout accordingly.",
},
{
id: 12,
category: "AI Prompts",
question: "Can I change slide image through prompt?",
answer:
"Yes you can! Click on the WandSparkles icon on the top left of each slide and it will give you a prompt input box. Describe the image you want and the AI will update the slide image based on your requirements.",
},
{
id: 14,
category: "AI Prompts",
question: "Can I change content through prompt?",
answer:
"Yes you can! Click on the WandSparkles icon on the top left of each slide and it will give you a prompt input box. Describe what content you want and the AI will update the slide's text and content based on your description.",
},
{
id: 4,
category: "Text",
question: "How can I format and highlight text?",
answer:
"Select any text to see the formatting toolbar appear. You'll have options for Bold, Italic, Underline, Strikethrough,and more.",
},
{
id: 5,
category: "Icons",
question: "How do I change icons?",
answer:
"Click on any existing icon to modify it. In the icon selector panel, you can browse icos or use the search function to find specific icons. We offer thousands of icons in various styles.",
},
{
id: 16,
category: "Layout",
question: "Can I change the position of slide?",
answer:
"Of course, On side panel you can drag the slide and place wherever you want.",
},
{
id: 15,
category: "Layout",
question: "Can I add new slide between the slide?",
answer:
"Yes you can just click on the plus icon below each slide.It will display the all the layouts and choose required one.",
},
{
id: 6,
category: "Layout",
question: "Can I add more sections to my slides?",
answer:
"Absolutely! Hover near the bottom of any text box or content block, and you'll see a + icon appear. Click this button to add a new section below the current one. You can also use the Insert menu to add specific section types.",
},
{
id: 8,
category: "Export",
question: "How do I download or export my presentation?",
answer:
"Click the Export button in the top right menu. You can choose to download as PDF, PowerPoint.",
},
];
const Help = () => {
const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [filteredQuestions, setFilteredQuestions] = useState(helpQuestions);
const [categories, setCategories] = useState<string[]>([]);
const [selectedCategory, setSelectedCategory] = useState("All");
const modalRef = useRef<HTMLDivElement>(null);
// Extract unique categories and create "All" category list
useEffect(() => {
const uniqueCategories = Array.from(
new Set(helpQuestions.map((q) => q.category))
);
setCategories(["All", ...uniqueCategories]);
}, []);
// Filter questions based on search query and selected category
useEffect(() => {
let results = helpQuestions;
// Filter by category if not "All"
if (selectedCategory !== "All") {
results = results.filter((q) => q.category === selectedCategory);
}
// Filter by search query
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
results = results.filter(
(q) =>
q.question.toLowerCase().includes(query) ||
q.answer.toLowerCase().includes(query)
);
}
setFilteredQuestions(results);
}, [searchQuery, selectedCategory]);
// Close modal when clicking outside
useEffect(() => {
const handleClickOutside = (event: any) => {
if (
modalRef.current &&
!modalRef.current.contains(event.target) &&
!event.target.closest(".help-button")
) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen]);
const handleOpenClose = () => {
setIsOpen(!isOpen);
};
// Animation helpers
const modalClass = isOpen
? "opacity-100 scale-100"
: "opacity-0 scale-95 pointer-events-none";
return (
<>
{/* Help Button */}
<button
onClick={handleOpenClose}
className="help-button hidden fixed bottom-6 right-6 h-12 w-12 z-50 bg-emerald-600 hover:bg-emerald-700 rounded-full md:flex justify-center items-center cursor-pointer shadow-lg transition-all duration-300 hover:shadow-xl"
aria-label="Help Center"
>
{isOpen ? (
<X className="text-white h-5 w-5" />
) : (
<HelpCircle className="text-white h-5 w-5" />
)}
</button>
{/* Help Modal */}
<div
className={`fixed bottom-20 right-6 z-50 max-w-md w-full transition-all duration-300 transform ${modalClass}`}
ref={modalRef}
>
<div className="bg-white rounded-lg shadow-2xl border border-gray-200 overflow-hidden">
{/* Header */}
<div className="bg-emerald-600 text-white px-6 py-4 flex justify-between items-center">
<h2 className="text-lg font-medium">Help Center</h2>
<button
onClick={() => setIsOpen(false)}
className="hover:bg-emerald-700 p-1 rounded"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Search */}
<div className="px-6 pt-4 pb-2">
<div className="relative">
<input
type="text"
placeholder="Search help topics..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
/>
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
</div>
</div>
{/* Category Pills */}
<div className="px-6 pb-3 flex gap-2 overflow-x-auto hide-scrollbar">
{categories.map((category) => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-3 py-1 rounded-full text-sm whitespace-nowrap ${selectedCategory === category
? "bg-emerald-600 text-white"
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`}
>
{category}
</button>
))}
</div>
{/* FAQ Accordion */}
<div className="max-h-96 overflow-y-auto px-6 pb-6">
{filteredQuestions.length > 0 ? (
<Accordion type="single" collapsible className="w-full">
{filteredQuestions.map((faq, index) => (
<AccordionItem
key={index}
value={`item-${index}`}
className="border-b border-gray-200 last:border-b-0"
>
<AccordionTrigger className="hover:no-underline py-3 px-1 text-left flex">
<div className="flex-1 pr-2">
<span className="text-gray-900 font-medium text-sm md:text-base">
{faq.question}
</span>
<span className="block text-xs text-emerald-600 mt-0.5">
{faq.category}
</span>
</div>
</AccordionTrigger>
<AccordionContent className="px-1 pb-3">
<div className="text-sm text-gray-600 leading-relaxed rounded bg-gray-50 p-3">
{faq.answer}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
) : (
<div className="py-8 text-center text-gray-500">
<p>No results found for "{searchQuery}"</p>
<button
onClick={() => {
setSearchQuery("");
setSelectedCategory("All");
}}
className="mt-2 text-emerald-600 hover:underline text-sm"
>
Clear search
</button>
</div>
)}
</div>
{/* Footer */}
<div className="bg-gray-50 px-6 py-3 border-t border-gray-200 text-xs text-gray-500 text-center">
Still need help?{" "}
<a href="/contact" className="text-emerald-600 hover:underline">
Contact Support
</a>
</div>
</div>
</div>
{/* Custom AccordionTrigger implementation (since shadcn's might not be available) */}
{!AccordionTrigger && (
<style jsx>{`
.accordion-trigger {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
padding: 0.75rem 0;
text-align: left;
transition: all 0.2s;
}
.accordion-trigger:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.accordion-content {
overflow: hidden;
height: 0;
transition: height 0.2s ease;
}
.accordion-content[data-state="open"] {
height: auto;
}
`}</style>
)}
</>
);
};
export default Help;