Files
burmddit/frontend/app/article/[slug]/page.tsx

326 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { sql } from '@vercel/postgres'
import { notFound } from 'next/navigation'
import Link from 'next/link'
import Image from 'next/image'
async function getArticle(slug: string) {
try {
const { rows } = await sql`
SELECT
a.*,
c.name as category_name,
c.name_burmese as category_name_burmese,
c.slug as category_slug
FROM articles a
JOIN categories c ON a.category_id = c.id
WHERE a.slug = ${slug} AND a.status = 'published'
`
if (rows.length === 0) return null
// Increment view count
await sql`SELECT increment_view_count(${slug})`
return rows[0]
} catch (error) {
console.error('Error fetching article:', error)
return null
}
}
async function getRelatedArticles(articleId: number) {
try {
const { rows } = await sql`SELECT * FROM get_related_articles(${articleId}, 5)`
return rows
} catch (error) {
return []
}
}
export default async function ArticlePage({ params }: { params: { slug: string } }) {
const article = await getArticle(params.slug)
if (!article) {
notFound()
}
const relatedArticles = await getRelatedArticles(article.id)
const publishedDate = new Date(article.published_at).toLocaleDateString('my-MM', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Breadcrumb */}
<nav className="mb-6 text-sm">
<Link href="/" className="text-primary-600 hover:text-primary-700">
က
</Link>
<span className="mx-2 text-gray-400">/</span>
<Link
href={`/category/${article.category_slug}`}
className="text-primary-600 hover:text-primary-700 font-burmese"
>
{article.category_name_burmese}
</Link>
<span className="mx-2 text-gray-400">/</span>
<span className="text-gray-600 font-burmese">{article.title_burmese}</span>
</nav>
{/* Article Header */}
<article className="bg-white rounded-lg shadow-lg overflow-hidden">
{/* Category Badge */}
<div className="p-6 pb-0">
<Link
href={`/category/${article.category_slug}`}
className="inline-block px-3 py-1 bg-primary-100 text-primary-700 rounded-full text-sm font-medium font-burmese mb-4 hover:bg-primary-200"
>
{article.category_name_burmese}
</Link>
</div>
{/* Featured Image */}
{article.featured_image && (
<div className="relative h-96 w-full">
<Image
src={article.featured_image}
alt={article.title_burmese}
fill
className="object-cover"
priority
/>
</div>
)}
{/* Article Content */}
<div className="p-6 lg:p-12">
{/* Title */}
<h1 className="text-4xl font-bold text-gray-900 mb-4 font-burmese leading-tight">
{article.title_burmese}
</h1>
{/* Meta Info */}
<div className="flex items-center text-sm text-gray-600 mb-8 pb-8 border-b">
<span className="font-burmese">{publishedDate}</span>
<span className="mx-3"></span>
<span className="font-burmese">{article.reading_time} </span>
<span className="mx-3"></span>
<span className="font-burmese">{article.view_count} က</span>
</div>
{/* Article Body */}
<div className="article-content prose prose-lg max-w-none">
<div dangerouslySetInnerHTML={{ __html: formatContent(article.content_burmese) }} />
{/* 🔥 Additional Images Gallery */}
{article.images && article.images.length > 1 && (
<div className="mt-8 mb-8">
<h3 className="text-xl font-bold mb-4 font-burmese"></h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{article.images.slice(1).map((img: string, idx: number) => (
<div key={idx} className="relative h-48 rounded-lg overflow-hidden">
<Image
src={img}
alt={`${article.title_burmese} - ဓာတ်ပုံ ${idx + 2}`}
fill
className="object-cover hover:scale-105 transition-transform duration-200"
/>
</div>
))}
</div>
</div>
)}
{/* 🔥 Videos */}
{article.videos && article.videos.length > 0 && (
<div className="mt-8 mb-8">
<h3 className="text-xl font-bold mb-4 font-burmese"></h3>
<div className="space-y-4">
{article.videos.map((video: string, idx: number) => (
<div key={idx} className="relative aspect-video rounded-lg overflow-hidden bg-gray-900">
{renderVideo(video)}
</div>
))}
</div>
</div>
)}
</div>
{/* ⭐ SOURCE ATTRIBUTION - THIS IS THE KEY PART! */}
{article.source_articles && article.source_articles.length > 0 && (
<div className="mt-12 pt-8 border-t-2 border-gray-200 bg-gray-50 p-6 rounded-lg">
<h3 className="text-xl font-bold text-gray-900 mb-4 font-burmese flex items-center">
<svg className="w-6 h-6 mr-2 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</h3>
<p className="text-sm text-gray-600 mb-4 font-burmese">
က က က က က
</p>
<ul className="space-y-3">
{article.source_articles.map((source: any, index: number) => (
<li key={index} className="bg-white p-4 rounded border border-gray-200 hover:border-primary-300 transition-colors">
<div className="flex items-start">
<span className="flex-shrink-0 w-6 h-6 bg-primary-100 text-primary-700 rounded-full flex items-center justify-center text-sm font-bold mr-3">
{index + 1}
</span>
<div className="flex-1">
<a
href={source.url}
target="_blank"
rel="noopener noreferrer"
className="text-primary-600 hover:text-primary-700 font-medium break-words"
>
{source.title}
</a>
{source.author && source.author !== 'Unknown' && (
<p className="text-sm text-gray-600 mt-1">
<span className="font-burmese">:</span> {source.author}
</p>
)}
<p className="text-xs text-gray-500 mt-1 break-all">
{source.url}
</p>
</div>
<a
href={source.url}
target="_blank"
rel="noopener noreferrer"
className="ml-2 text-primary-600 hover:text-primary-700"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
</li>
))}
</ul>
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded">
<p className="text-sm text-gray-700 font-burmese">
<strong>က:</strong> က ကက ကက ကက က
</p>
</div>
</div>
)}
{/* Disclaimer */}
<div className="mt-6 p-4 bg-gray-100 rounded text-sm text-gray-600 font-burmese">
<p>
<strong>က:</strong> က AI က
</p>
</div>
</div>
</article>
{/* Related Articles */}
{relatedArticles.length > 0 && (
<div className="mt-12">
<h2 className="text-2xl font-bold text-gray-900 mb-6 font-burmese">
က
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{relatedArticles.map((related: any) => (
<Link
key={related.id}
href={`/article/${related.slug}`}
className="bg-white rounded-lg shadow hover:shadow-lg transition-shadow p-4"
>
{related.featured_image && (
<div className="relative h-32 w-full mb-3 rounded overflow-hidden">
<Image
src={related.featured_image}
alt={related.title_burmese}
fill
className="object-cover"
/>
</div>
)}
<h3 className="font-semibold text-gray-900 font-burmese line-clamp-2 hover:text-primary-600">
{related.title_burmese}
</h3>
<p className="text-sm text-gray-600 font-burmese mt-2 line-clamp-2">
{related.excerpt_burmese}
</p>
</Link>
))}
</div>
</div>
)}
</div>
)
}
function formatContent(content: string): string {
// Convert markdown-like formatting to HTML
// This is a simple implementation - you might want to use a proper markdown parser
let formatted = content
.replace(/\n\n/g, '</p><p>')
.replace(/## (.*?)\n/g, '<h2>$1</h2>')
.replace(/### (.*?)\n/g, '<h3>$1</h3>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
return `<p>${formatted}</p>`
}
function renderVideo(videoUrl: string) {
// Extract YouTube video ID
let videoId = null
// Handle different YouTube URL formats
if (videoUrl.includes('youtube.com/watch')) {
const match = videoUrl.match(/v=([^&]+)/)
videoId = match ? match[1] : null
} else if (videoUrl.includes('youtu.be/')) {
const match = videoUrl.match(/youtu\.be\/([^?]+)/)
videoId = match ? match[1] : null
} else if (videoUrl.includes('youtube.com/embed/')) {
const match = videoUrl.match(/embed\/([^?]+)/)
videoId = match ? match[1] : null
}
if (videoId) {
return (
<iframe
src={`https://www.youtube.com/embed/${videoId}`}
className="w-full h-full"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
)
}
// For other video formats, try generic iframe embed
return (
<iframe
src={videoUrl}
className="w-full h-full"
allowFullScreen
/>
)
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const article = await getArticle(params.slug)
if (!article) {
return {
title: 'Article Not Found',
}
}
return {
title: `${article.title_burmese} - Burmddit`,
description: article.excerpt_burmese,
openGraph: {
title: article.title_burmese,
description: article.excerpt_burmese,
images: article.featured_image ? [article.featured_image] : [],
},
}
}