UI updates

This commit is contained in:
Min Zeya Phyo
2026-02-26 15:07:05 +06:30
parent f0146c311c
commit 964afce761
12 changed files with 601 additions and 272 deletions

View File

@@ -3,11 +3,12 @@ export const dynamic = "force-dynamic"
import { notFound } from 'next/navigation'
import Link from 'next/link'
import Image from 'next/image'
import ShareButtons from '@/components/ShareButtons'
async function getArticleWithTags(slug: string) {
try {
const { rows } = await sql`
SELECT
SELECT
a.*,
c.name as category_name,
c.name_burmese as category_name_burmese,
@@ -27,12 +28,9 @@ async function getArticleWithTags(slug: string) {
WHERE a.slug = ${slug} AND a.status = 'published'
GROUP BY a.id, c.id
`
if (rows.length === 0) return null
// Increment view count
// Increment view count only here (not in generateMetadata)
await sql`SELECT increment_view_count(${slug})`
return rows[0]
} catch (error) {
console.error('Error fetching article:', error)
@@ -40,34 +38,46 @@ async function getArticleWithTags(slug: string) {
}
}
// Separate metadata fetch — no view count increment
async function getArticleMeta(slug: string) {
try {
const { rows } = await sql`
SELECT title_burmese, excerpt_burmese, featured_image
FROM articles
WHERE slug = ${slug} AND status = 'published'
LIMIT 1
`
return rows[0] || null
} catch {
return null
}
}
async function getRelatedArticles(articleId: number) {
try {
const { rows } = await sql`SELECT * FROM get_related_articles(${articleId}, 6)`
return rows
} catch (error) {
} catch {
return []
}
}
export default async function ImprovedArticlePage({ params }: { params: { slug: string } }) {
export default async function ArticlePage({ params }: { params: { slug: string } }) {
const article = await getArticleWithTags(params.slug)
if (!article) {
notFound()
}
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'
day: 'numeric',
})
return (
<div className="min-h-screen bg-white">
{/* Hero Cover Image */}
{article.featured_image && (
<div className="relative h-[70vh] w-full overflow-hidden">
<div className="relative h-[55vh] w-full overflow-hidden">
<Image
src={article.featured_image}
alt={article.title_burmese}
@@ -75,25 +85,20 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/50 to-black/10" />
<div className="absolute inset-0 flex items-end">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 w-full">
{/* Category */}
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-10 w-full">
<Link
href={`/category/${article.category_slug}`}
className="inline-block mb-4 px-4 py-2 bg-primary rounded-full text-white font-semibold text-sm hover:bg-primary-dark transition-colors"
className="inline-block mb-3 px-4 py-1.5 bg-primary rounded-full text-white font-semibold text-sm hover:bg-primary-dark transition-colors font-burmese"
>
{article.category_name_burmese}
</Link>
{/* Title */}
<h1 className="text-5xl md:text-6xl font-bold text-white mb-6 font-burmese leading-tight">
<h1 className="text-2xl md:text-3xl lg:text-4xl font-bold text-white mb-4 font-burmese leading-snug line-clamp-3">
{article.title_burmese}
</h1>
{/* Meta */}
<div className="flex flex-wrap items-center gap-4 text-white/90">
<div className="flex flex-wrap items-center gap-3 text-white/80 text-sm">
<span className="font-burmese">{publishedDate}</span>
<span></span>
<span className="font-burmese">{article.reading_time} </span>
@@ -106,10 +111,10 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
)}
{/* Article Content */}
<article className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<article className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Tags */}
{article.tags_burmese && article.tags_burmese.length > 0 && (
<div className="flex flex-wrap gap-2 mb-8 pb-8 border-b">
<div className="flex flex-wrap gap-2 mb-8 pb-8 border-b border-gray-100">
{article.tags_burmese.map((tag: string, idx: number) => (
<Link
key={idx}
@@ -122,33 +127,28 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
</div>
)}
{/* Article Body */}
{/* Body */}
<div className="article-content">
<div dangerouslySetInnerHTML={{ __html: formatContent(article.content_burmese) }} />
{/* Additional Images Gallery */}
{/* Image Gallery */}
{article.images && article.images.length > 1 && (
<div className="my-12">
<h3 className="text-2xl font-bold mb-6 font-burmese"></h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div className="my-10 not-prose">
<h3 className="text-xl font-bold mb-4 font-burmese text-gray-900"></h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{article.images.slice(1).map((img: string, idx: number) => (
<div key={idx} className="relative h-56 rounded-xl overflow-hidden image-zoom">
<Image
src={img}
alt={`${article.title_burmese} - ${idx + 2}`}
fill
className="object-cover"
/>
<div key={idx} className="relative h-48 rounded-xl overflow-hidden image-zoom">
<Image src={img} alt={`${article.title_burmese} ${idx + 2}`} fill className="object-cover" />
</div>
))}
</div>
</div>
)}
{/* Videos */}
{article.videos && article.videos.length > 0 && (
<div className="my-12">
<h3 className="text-2xl font-bold mb-6 font-burmese"></h3>
<div className="my-10 not-prose">
<h3 className="text-xl font-bold mb-4 font-burmese text-gray-900"></h3>
<div className="space-y-6">
{article.videos.map((video: string, idx: number) => (
<div key={idx} className="relative aspect-video rounded-xl overflow-hidden bg-gray-900 shadow-xl">
@@ -162,49 +162,36 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
{/* Source Attribution */}
{article.source_articles && article.source_articles.length > 0 && (
<div className="mt-16 p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-2xl shadow-lg">
<h3 className="text-2xl font-bold text-gray-900 mb-4 font-burmese flex items-center">
<svg className="w-7 h-7 mr-3 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="mt-12 p-6 bg-blue-50 rounded-2xl border border-blue-100">
<h3 className="text-lg font-bold text-gray-900 mb-3 font-burmese flex items-center gap-2">
<svg className="w-5 h-5 text-primary flex-shrink-0" 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-700 mb-6 font-burmese leading-relaxed">
က က က က က
<p className="text-sm text-gray-600 mb-4 font-burmese leading-relaxed">
က က
</p>
<div className="space-y-4">
<div className="space-y-3">
{article.source_articles.map((source: any, index: number) => (
<div key={index} className="bg-white p-5 rounded-xl shadow-sm hover:shadow-md transition-shadow border border-gray-100">
<div className="flex items-start gap-4">
<span className="flex-shrink-0 w-8 h-8 bg-primary text-white rounded-full flex items-center justify-center text-sm font-bold">
{index + 1}
</span>
<div className="flex-1 min-w-0">
<a
href={source.url}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:text-primary-dark font-medium break-words hover:underline"
>
{source.title}
</a>
{source.author && source.author !== 'Unknown' && (
<p className="text-sm text-gray-600 mt-2">
<span className="font-burmese font-semibold">:</span> {source.author}
</p>
)}
</div>
<div key={index} className="bg-white p-4 rounded-xl border border-gray-100 flex items-start gap-3">
<span className="flex-shrink-0 w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-xs font-bold">
{index + 1}
</span>
<div className="flex-1 min-w-0">
<a
href={source.url}
target="_blank"
rel="noopener noreferrer"
className="flex-shrink-0 text-primary hover:text-primary-dark"
title="Open source"
className="text-primary hover:underline text-sm font-medium break-words"
>
<svg className="w-6 h-6" 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>
{source.title}
</a>
{source.author && source.author !== 'Unknown' && (
<p className="text-xs text-gray-500 mt-1 font-burmese">
: {source.author}
</p>
)}
</div>
</div>
))}
@@ -212,58 +199,39 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
</div>
)}
{/* Share Section */}
<div className="mt-12 py-8 border-y border-gray-200">
<div className="flex items-center justify-between">
<p className="font-burmese text-gray-700 font-semibold">:</p>
<div className="flex gap-3">
<button className="px-4 py-2 bg-blue-600 text-white rounded-full hover:bg-blue-700 transition-colors">
Facebook
</button>
<button className="px-4 py-2 bg-sky-500 text-white rounded-full hover:bg-sky-600 transition-colors">
Twitter
</button>
<button className="px-4 py-2 bg-green-600 text-white rounded-full hover:bg-green-700 transition-colors">
WhatsApp
</button>
</div>
</div>
{/* Share */}
<div className="mt-10 py-8 border-t border-gray-100">
<p className="font-burmese text-gray-700 font-semibold mb-4">:</p>
<ShareButtons title={article.title_burmese} />
</div>
</article>
{/* Related Articles */}
{relatedArticles.length > 0 && (
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 bg-gray-50">
<h2 className="text-3xl font-bold text-gray-900 mb-10 font-burmese">
က
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{relatedArticles.map((related: any) => (
<Link
key={related.id}
href={`/article/${related.slug}`}
className="card card-hover"
>
{related.featured_image && (
<div className="relative h-48 w-full image-zoom">
<Image
src={related.featured_image}
alt={related.title_burmese}
fill
className="object-cover"
/>
<section className="bg-gray-50 py-14">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-gray-900 mb-8 font-burmese">
က
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{relatedArticles.map((related: any) => (
<Link key={related.id} href={`/article/${related.slug}`} className="card card-hover">
{related.featured_image && (
<div className="relative h-44 w-full image-zoom">
<Image src={related.featured_image} alt={related.title_burmese} fill className="object-cover" />
</div>
)}
<div className="p-5">
<h3 className="font-bold text-gray-900 font-burmese line-clamp-3 hover:text-primary transition-colors leading-snug mb-2">
{related.title_burmese}
</h3>
<p className="text-sm text-gray-500 font-burmese line-clamp-2 leading-relaxed">
{related.excerpt_burmese}
</p>
</div>
)}
<div className="p-6">
<h3 className="font-bold text-gray-900 font-burmese line-clamp-2 hover:text-primary transition-colors text-lg mb-3">
{related.title_burmese}
</h3>
<p className="text-sm text-gray-600 font-burmese line-clamp-2">
{related.excerpt_burmese}
</p>
</div>
</Link>
))}
</Link>
))}
</div>
</div>
</section>
)}
@@ -272,30 +240,30 @@ export default async function ImprovedArticlePage({ params }: { params: { slug:
}
function formatContent(content: string): string {
let formatted = content
.replace(/\n\n/g, '</p><p>')
.replace(/## (.*?)\n/g, '<h2>$1</h2>')
.replace(/### (.*?)\n/g, '<h3>$1</h3>')
const formatted = content
.replace(/### (.*?)(\n|$)/g, '<h3>$1</h3>')
.replace(/## (.*?)(\n|$)/g, '<h2>$1</h2>')
.replace(/# (.*?)(\n|$)/g, '<h1>$1</h1>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/\n\n+/g, '</p><p>')
.replace(/\n/g, '<br/>')
return `<p>${formatted}</p>`
}
function renderVideo(videoUrl: string) {
let videoId = null
let videoId: string | null = null
if (videoUrl.includes('youtube.com/watch')) {
const match = videoUrl.match(/v=([^&]+)/)
videoId = match ? match[1] : null
const m = videoUrl.match(/v=([^&]+)/)
videoId = m ? m[1] : null
} else if (videoUrl.includes('youtu.be/')) {
const match = videoUrl.match(/youtu\.be\/([^?]+)/)
videoId = match ? match[1] : null
const m = videoUrl.match(/youtu\.be\/([^?]+)/)
videoId = m ? m[1] : null
} else if (videoUrl.includes('youtube.com/embed/')) {
const match = videoUrl.match(/embed\/([^?]+)/)
videoId = match ? match[1] : null
const m = videoUrl.match(/embed\/([^?]+)/)
videoId = m ? m[1] : null
}
if (videoId) {
return (
<iframe
@@ -306,25 +274,12 @@ function renderVideo(videoUrl: string) {
/>
)
}
return (
<iframe
src={videoUrl}
className="w-full h-full"
allowFullScreen
/>
)
return null
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const article = await getArticleWithTags(params.slug)
if (!article) {
return {
title: 'Article Not Found',
}
}
const article = await getArticleMeta(params.slug)
if (!article) return { title: 'Article Not Found' }
return {
title: `${article.title_burmese} - Burmddit`,
description: article.excerpt_burmese,