forked from minzeyaphyo/burmddit
✅ 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:
177
frontend/app/category/[slug]/page.tsx
Normal file
177
frontend/app/category/[slug]/page.tsx
Normal 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
5
frontend/next-env.d.ts
vendored
Normal 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.
|
||||
Reference in New Issue
Block a user