Fix: Add category pages + MCP server for autonomous management

- Created /app/category/[slug]/page.tsx - category navigation now works
- Built Burmddit MCP Server with 10 tools:
  * Site stats, article queries, content management
  * Deployment control, quality checks, pipeline triggers
- Added MCP setup guide and config
- Categories fully functional: ai-news, tutorials, tips-tricks, upcoming
- Modo can now manage Burmddit autonomously via MCP
This commit is contained in:
Zeya Phyo
2026-02-19 15:40:26 +00:00
parent 310fff9d55
commit 785910b81d
10 changed files with 1898 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
import { sql } from '@/lib/db'
export const dynamic = "force-dynamic"
import { notFound } from 'next/navigation'
import Link from 'next/link'
import Image from 'next/image'
async function getCategory(slug: string) {
try {
const { rows } = await sql`
SELECT * FROM categories WHERE slug = ${slug}
`
return rows[0] || null
} catch (error) {
return null
}
}
async function getArticlesByCategory(categorySlug: string) {
try {
const { rows } = await sql`
SELECT a.*, c.name_burmese as category_name_burmese, c.slug as category_slug,
array_agg(DISTINCT t.name_burmese) FILTER (WHERE t.name_burmese IS NOT NULL) as tags_burmese,
array_agg(DISTINCT t.slug) FILTER (WHERE t.slug IS NOT NULL) as tag_slugs
FROM articles a
JOIN categories c ON a.category_id = c.id
LEFT JOIN article_tags at ON a.id = at.article_id
LEFT JOIN tags t ON at.tag_id = t.id
WHERE c.slug = ${categorySlug} AND a.status = 'published'
GROUP BY a.id, c.name_burmese, c.slug
ORDER BY a.published_at DESC
LIMIT 100
`
return rows
} catch (error) {
console.error('Error fetching articles by category:', error)
return []
}
}
export default async function CategoryPage({ params }: { params: { slug: string } }) {
const [category, articles] = await Promise.all([
getCategory(params.slug),
getArticlesByCategory(params.slug)
])
if (!category) {
notFound()
}
// Get category emoji based on slug
const getCategoryEmoji = (slug: string) => {
const emojiMap: { [key: string]: string } = {
'ai-news': '📰',
'tutorials': '📚',
'tips-tricks': '💡',
'upcoming': '🚀',
}
return emojiMap[slug] || '📁'
}
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<div className="bg-gradient-to-r from-primary to-indigo-600 text-white py-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center gap-3 mb-4">
<span className="text-5xl">{getCategoryEmoji(params.slug)}</span>
<h1 className="text-5xl font-bold font-burmese">
{category.name_burmese}
</h1>
</div>
{category.description && (
<p className="text-xl text-white/90 mb-4">
{category.description}
</p>
)}
<p className="text-lg text-white/80">
{articles.length}
</p>
</div>
</div>
{/* Articles */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{articles.length === 0 ? (
<div className="text-center py-20 bg-white rounded-2xl shadow-sm">
<div className="text-6xl mb-4">{getCategoryEmoji(params.slug)}</div>
<p className="text-xl text-gray-500 font-burmese">
က
</p>
<Link
href="/"
className="inline-block mt-6 px-6 py-3 bg-primary text-white rounded-full font-semibold hover:bg-primary-dark transition-all"
>
က
</Link>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{articles.map((article: any) => (
<article key={article.id} className="card card-hover fade-in">
{/* Cover Image */}
{article.featured_image && (
<Link href={`/article/${article.slug}`} className="block image-zoom">
<div className="relative h-56 w-full">
<Image
src={article.featured_image}
alt={article.title_burmese}
fill
className="object-cover"
/>
</div>
</Link>
)}
<div className="p-6">
{/* Category Badge */}
<div className="inline-block mb-3 px-3 py-1 bg-primary/10 text-primary rounded-full text-xs font-semibold">
{article.category_name_burmese}
</div>
{/* Title */}
<h3 className="text-xl font-bold text-gray-900 mb-3 font-burmese line-clamp-2 hover:text-primary transition-colors">
<Link href={`/article/${article.slug}`}>
{article.title_burmese}
</Link>
</h3>
{/* Excerpt */}
<p className="text-gray-600 mb-4 font-burmese line-clamp-3 text-sm leading-relaxed">
{article.excerpt_burmese}
</p>
{/* Tags */}
{article.tags_burmese && article.tags_burmese.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{article.tags_burmese.slice(0, 3).map((tag: string, idx: number) => (
<Link
key={idx}
href={`/tag/${article.tag_slugs[idx]}`}
className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors"
>
#{tag}
</Link>
))}
</div>
)}
{/* Meta */}
<div className="flex items-center justify-between text-sm text-gray-500 pt-4 border-t border-gray-100">
<span className="font-burmese">{article.reading_time} </span>
<span>{article.view_count} views</span>
</div>
</div>
</article>
))}
</div>
)}
</div>
</div>
)
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const category = await getCategory(params.slug)
if (!category) {
return {
title: 'Category Not Found',
}
}
return {
title: `${category.name_burmese} - Burmddit`,
description: category.description || `${category.name_burmese} အမျိုးအစား၏ ဆောင်းပါးများ`,
}
}

5
frontend/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.