This commit is contained in:
Iliyan Angelov
2025-11-24 03:52:08 +02:00
parent dfcaebaf8c
commit 366f28677a
18241 changed files with 865352 additions and 567 deletions

View File

@@ -0,0 +1,217 @@
"use client";
import { useState } from 'react';
import { useKnowledgeBaseCategories, useFeaturedArticles, useKnowledgeBaseArticles } from '@/lib/hooks/useSupport';
import KnowledgeBaseArticleModal from './KnowledgeBaseArticleModal';
const KnowledgeBase = () => {
const { categories, loading: categoriesLoading } = useKnowledgeBaseCategories();
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [selectedArticleSlug, setSelectedArticleSlug] = useState<string | null>(null);
// Fetch all articles (for browsing and category filtering)
const { articles: allArticles, loading: allArticlesLoading } = useKnowledgeBaseArticles();
// Fetch featured articles (for default view)
const { articles: featuredArticles, loading: featuredLoading } = useFeaturedArticles();
// Determine which articles to display
let displayArticles = featuredArticles;
let isLoading = featuredLoading;
let headerText = 'Featured Articles';
if (searchTerm) {
// If searching, filter all articles by search term
displayArticles = allArticles.filter(article =>
article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.content.toLowerCase().includes(searchTerm.toLowerCase())
);
isLoading = allArticlesLoading;
headerText = 'Search Results';
} else if (selectedCategory) {
// If a category is selected, filter articles by that category
displayArticles = allArticles.filter(article => article.category_slug === selectedCategory);
isLoading = allArticlesLoading;
const categoryName = categories.find(cat => cat.slug === selectedCategory)?.name || 'Category';
headerText = `${categoryName} Articles`;
}
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
// The search is already being performed by the hook
};
const filteredCategories = selectedCategory
? categories.filter(cat => cat.slug === selectedCategory)
: categories;
return (
<div className="knowledge-base">
<div className="row justify-content-center">
<div className="col-12 col-lg-10">
<div className="form-header text-center">
<h2>Knowledge Base</h2>
<p>Find answers to frequently asked questions and explore our documentation.</p>
</div>
{/* Search Bar */}
<form onSubmit={handleSearch} className="kb-search-form">
<div className="search-input-group">
<i className="fa-solid fa-search search-icon"></i>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search articles, topics, or keywords..."
className="form-control"
/>
{searchTerm && (
<button
type="button"
className="clear-search"
onClick={() => setSearchTerm('')}
aria-label="Clear search"
>
<i className="fa-solid fa-times"></i>
</button>
)}
</div>
</form>
{/* Categories */}
{!searchTerm && (
<div className="kb-categories">
<h3>Browse by Category</h3>
{categoriesLoading ? (
<div className="loading-state">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : (
<div className="row g-4">
{Array.isArray(categories) && categories.map(category => (
<div key={category.id} className="col-md-6 col-lg-4">
<div
className="category-card"
onClick={() => setSelectedCategory(category.slug)}
style={{ borderLeftColor: category.color }}
>
<div
className="category-icon"
style={{ color: category.color }}
>
<i className={`fa-solid ${category.icon}`}></i>
</div>
<div className="category-content">
<h4>{category.name}</h4>
<p>{category.description}</p>
<div className="category-meta">
<span className="article-count">
{category.article_count} {category.article_count === 1 ? 'article' : 'articles'}
</span>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Featured/Search Results Articles */}
<div className="kb-articles">
<div className="articles-header">
<div className="d-flex align-items-center justify-content-between">
<h3>{headerText}</h3>
{selectedCategory && !searchTerm && (
<button
className="btn btn-outline-primary btn-sm"
onClick={() => setSelectedCategory(null)}
>
<i className="fa-solid fa-arrow-left me-2"></i>
Back to All Articles
</button>
)}
</div>
{searchTerm && (
<p className="search-info">
Found {displayArticles.length} {displayArticles.length === 1 ? 'article' : 'articles'} for "{searchTerm}"
</p>
)}
</div>
{isLoading ? (
<div className="loading-state">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : displayArticles.length === 0 ? (
<div className="empty-state">
<i className="fa-solid fa-search empty-icon"></i>
<h4>No articles found</h4>
<p>
{searchTerm
? `We couldn't find any articles matching "${searchTerm}". Try different keywords.`
: 'No articles available at the moment.'}
</p>
</div>
) : (
<div className="articles-list">
{Array.isArray(displayArticles) && displayArticles.map(article => (
<div
key={article.id}
className="article-item"
onClick={() => setSelectedArticleSlug(article.slug)}
>
<div className="article-header">
<h4>{article.title}</h4>
{article.is_featured && (
<span className="featured-badge">
<i className="fa-solid fa-star me-1"></i>
Featured
</span>
)}
</div>
<p className="article-summary">{article.summary}</p>
<div className="article-meta">
<span className="article-category">
<i className="fa-solid fa-folder me-1"></i>
{article.category_name}
</span>
<span className="article-stats">
<i className="fa-solid fa-eye me-1"></i>
{article.view_count} views
</span>
<span className="article-stats">
<i className="fa-solid fa-thumbs-up me-1"></i>
{article.helpful_count} helpful
</span>
</div>
<button className="article-read-more">
Read More <i className="fa-solid fa-arrow-right ms-2"></i>
</button>
</div>
))}
</div>
)}
</div>
</div>
</div>
{/* Article Modal */}
{selectedArticleSlug && (
<KnowledgeBaseArticleModal
slug={selectedArticleSlug}
onClose={() => setSelectedArticleSlug(null)}
/>
)}
</div>
);
};
export default KnowledgeBase;