forked from minzeyaphyo/burmddit
Initial Burmddit deployment - AI news aggregator in Burmese
This commit is contained in:
325
frontend/app/article/[slug]/page.tsx
Normal file
325
frontend/app/article/[slug]/page.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
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] : [],
|
||||
},
|
||||
}
|
||||
}
|
||||
82
frontend/app/globals.css
Normal file
82
frontend/app/globals.css
Normal file
@@ -0,0 +1,82 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Burmese font support */
|
||||
@font-face {
|
||||
font-family: 'Pyidaungsu';
|
||||
src: url('https://myanmar-tools-website.appspot.com/fonts/Pyidaungsu-2.5.3_Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Pyidaungsu';
|
||||
src: url('https://myanmar-tools-website.appspot.com/fonts/Pyidaungsu-2.5.3_Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Article content styling */
|
||||
.article-content {
|
||||
@apply font-burmese text-gray-800 leading-relaxed;
|
||||
}
|
||||
|
||||
.article-content h1 {
|
||||
@apply text-3xl font-bold mt-8 mb-4;
|
||||
}
|
||||
|
||||
.article-content h2 {
|
||||
@apply text-2xl font-bold mt-6 mb-3;
|
||||
}
|
||||
|
||||
.article-content h3 {
|
||||
@apply text-xl font-semibold mt-4 mb-2;
|
||||
}
|
||||
|
||||
.article-content p {
|
||||
@apply mb-4 text-lg leading-loose;
|
||||
}
|
||||
|
||||
.article-content a {
|
||||
@apply text-primary-600 hover:text-primary-700 underline;
|
||||
}
|
||||
|
||||
.article-content ul, .article-content ol {
|
||||
@apply ml-6 mb-4 space-y-2;
|
||||
}
|
||||
|
||||
.article-content li {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
.article-content code {
|
||||
@apply bg-gray-100 px-2 py-1 rounded text-sm font-mono;
|
||||
}
|
||||
|
||||
.article-content pre {
|
||||
@apply bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto mb-4;
|
||||
}
|
||||
|
||||
.article-content blockquote {
|
||||
@apply border-l-4 border-primary-500 pl-4 italic my-4;
|
||||
}
|
||||
|
||||
/* Card hover effects */
|
||||
.article-card {
|
||||
@apply transition-transform duration-200 hover:scale-105 hover:shadow-xl;
|
||||
}
|
||||
|
||||
/* Loading skeleton */
|
||||
.skeleton {
|
||||
@apply animate-pulse bg-gray-200 rounded;
|
||||
}
|
||||
36
frontend/app/layout.tsx
Normal file
36
frontend/app/layout.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import Header from '@/components/Header'
|
||||
import Footer from '@/components/Footer'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Burmddit - Myanmar AI News & Tutorials',
|
||||
description: 'Daily AI news, tutorials, and tips in Burmese. Stay updated with the latest in artificial intelligence.',
|
||||
keywords: 'AI, Myanmar, Burmese, AI news, AI tutorials, machine learning, ChatGPT',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="my" className="font-burmese">
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Myanmar:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body className={`${inter.className} bg-gray-50`}>
|
||||
<Header />
|
||||
<main className="min-h-screen">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
124
frontend/app/page.tsx
Normal file
124
frontend/app/page.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { sql } from '@vercel/postgres'
|
||||
import ArticleCard from '@/components/ArticleCard'
|
||||
import TrendingSection from '@/components/TrendingSection'
|
||||
import CategoryNav from '@/components/CategoryNav'
|
||||
|
||||
async function getRecentArticles() {
|
||||
try {
|
||||
const { rows } = await sql`
|
||||
SELECT * FROM published_articles
|
||||
ORDER BY published_at DESC
|
||||
LIMIT 20
|
||||
`
|
||||
return rows
|
||||
} catch (error) {
|
||||
console.error('Error fetching articles:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function getTrendingArticles() {
|
||||
try {
|
||||
const { rows } = await sql`SELECT * FROM get_trending_articles(10)`
|
||||
return rows
|
||||
} catch (error) {
|
||||
console.error('Error fetching trending:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Home() {
|
||||
const [articles, trending] = await Promise.all([
|
||||
getRecentArticles(),
|
||||
getTrendingArticles()
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* Hero Section */}
|
||||
<section className="mb-12 text-center">
|
||||
<h1 className="text-5xl font-bold text-gray-900 mb-4 font-burmese">
|
||||
Burmddit
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 font-burmese">
|
||||
AI သတင်းများ၊ သင်ခန်းစာများနှင့် အကြံပြုချက်များ
|
||||
</p>
|
||||
<p className="text-lg text-gray-500 mt-2">
|
||||
Daily AI News, Tutorials & Tips in Burmese
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Category Navigation */}
|
||||
<CategoryNav />
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-8">
|
||||
{/* Main Articles (Left 2/3) */}
|
||||
<div className="lg:col-span-2">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6 font-burmese">
|
||||
နောက်ဆုံးရ သတင်းများ
|
||||
</h2>
|
||||
|
||||
{articles.length === 0 ? (
|
||||
<div className="text-center py-12 bg-white rounded-lg shadow">
|
||||
<p className="text-gray-500 font-burmese">
|
||||
သတင်းမရှိသေးပါ။ မကြာမီ ထပ်စစ်ကြည့်ပါ။
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{articles.map((article) => (
|
||||
<ArticleCard key={article.id} article={article} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sidebar (Right 1/3) */}
|
||||
<aside className="space-y-8">
|
||||
{/* Trending Articles */}
|
||||
<TrendingSection articles={trending} />
|
||||
|
||||
{/* Categories Card */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4 font-burmese">
|
||||
အမျိုးအစားများ
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<a href="/category/ai-news" className="text-primary-600 hover:text-primary-700 font-burmese">
|
||||
AI သတင်းများ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/tutorials" className="text-primary-600 hover:text-primary-700 font-burmese">
|
||||
သင်ခန်းစာများ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/tips-tricks" className="text-primary-600 hover:text-primary-700 font-burmese">
|
||||
အကြံပြုချက်များ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/upcoming" className="text-primary-600 hover:text-primary-700 font-burmese">
|
||||
လာမည့်အရာများ
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* About Card */}
|
||||
<div className="bg-gradient-to-br from-primary-50 to-primary-100 rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-3 font-burmese">
|
||||
Burmddit အကြောင်း
|
||||
</h3>
|
||||
<p className="text-gray-700 text-sm leading-relaxed font-burmese">
|
||||
Burmddit သည် AI နှင့် ပတ်သက်သော နောက်ဆုံးရ သတင်းများ၊ သင်ခန်းစာများနှင့် အကြံပြုချက်များကို မြန်မာဘာသာဖြင့် နေ့စဉ် ထုတ်ပြန်ပေးသော ပလက်ဖောင်း ဖြစ်ပါသည်။
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
67
frontend/components/ArticleCard.tsx
Normal file
67
frontend/components/ArticleCard.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
interface Article {
|
||||
id: number
|
||||
title_burmese: string
|
||||
slug: string
|
||||
excerpt_burmese: string
|
||||
category_name_burmese: string
|
||||
category_slug: string
|
||||
reading_time: number
|
||||
published_at: string
|
||||
featured_image?: string
|
||||
}
|
||||
|
||||
export default function ArticleCard({ article }: { article: Article }) {
|
||||
const publishedDate = new Date(article.published_at).toLocaleDateString('my-MM', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
|
||||
return (
|
||||
<article className="bg-white rounded-lg shadow hover:shadow-lg transition-shadow duration-200 overflow-hidden article-card">
|
||||
<Link href={`/article/${article.slug}`}>
|
||||
{article.featured_image && (
|
||||
<div className="relative h-48 w-full">
|
||||
<Image
|
||||
src={article.featured_image}
|
||||
alt={article.title_burmese}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-6">
|
||||
{/* Category Badge */}
|
||||
<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-3 hover:bg-primary-200"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{article.category_name_burmese}
|
||||
</Link>
|
||||
|
||||
{/* Title */}
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2 font-burmese hover:text-primary-600 line-clamp-2">
|
||||
{article.title_burmese}
|
||||
</h2>
|
||||
|
||||
{/* Excerpt */}
|
||||
<p className="text-gray-600 mb-4 font-burmese line-clamp-3 leading-relaxed">
|
||||
{article.excerpt_burmese}
|
||||
</p>
|
||||
|
||||
{/* Meta */}
|
||||
<div className="flex items-center text-sm text-gray-500 space-x-4">
|
||||
<span>{publishedDate}</span>
|
||||
<span>•</span>
|
||||
<span className="font-burmese">{article.reading_time} မိနစ်</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
23
frontend/components/CategoryNav.tsx
Normal file
23
frontend/components/CategoryNav.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
const categories = [
|
||||
{ name: 'AI သတင်းများ', slug: 'ai-news', icon: '📰' },
|
||||
{ name: 'သင်ခန်းစာများ', slug: 'tutorials', icon: '📚' },
|
||||
{ name: 'အကြံပြုချက်များ', slug: 'tips-tricks', icon: '💡' },
|
||||
{ name: 'လာမည့်အရာများ', slug: 'upcoming', icon: '🚀' },
|
||||
]
|
||||
|
||||
export default function CategoryNav() {
|
||||
return (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{categories.map((category) => (
|
||||
<a
|
||||
key={category.slug}
|
||||
href={`/category/${category.slug}`}
|
||||
className="bg-white rounded-lg shadow p-4 hover:shadow-lg transition-shadow duration-200 text-center"
|
||||
>
|
||||
<div className="text-3xl mb-2">{category.icon}</div>
|
||||
<h3 className="font-semibold text-gray-900 font-burmese">{category.name}</h3>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
72
frontend/components/Footer.tsx
Normal file
72
frontend/components/Footer.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white mt-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{/* About */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4 font-burmese">Burmddit အကြောင်း</h3>
|
||||
<p className="text-gray-400 text-sm font-burmese">
|
||||
AI နှင့် နည်းပညာဆိုင်ရာ သတင်းများကို မြန်မာဘာသာဖြင့် နေ့စဉ် ထုတ်ပြန်ပေးသော ပလက်ဖောင်း
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4 font-burmese">အမျိုးအစားများ</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>
|
||||
<a href="/category/ai-news" className="text-gray-400 hover:text-white font-burmese">
|
||||
AI သတင်းများ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/tutorials" className="text-gray-400 hover:text-white font-burmese">
|
||||
သင်ခန်းစာများ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/tips-tricks" className="text-gray-400 hover:text-white font-burmese">
|
||||
အကြံပြုချက်များ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/category/upcoming" className="text-gray-400 hover:text-white font-burmese">
|
||||
လာမည့်အရာများ
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-4">Contact</h3>
|
||||
<p className="text-gray-400 text-sm">
|
||||
Built with ❤️ for Myanmar tech community
|
||||
</p>
|
||||
<div className="mt-4 flex space-x-4">
|
||||
<a href="#" className="text-gray-400 hover:text-white">
|
||||
<span className="sr-only">Twitter</span>
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" className="text-gray-400 hover:text-white">
|
||||
<span className="sr-only">GitHub</span>
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-800 text-center">
|
||||
<p className="text-gray-400 text-sm">
|
||||
© {new Date().getFullYear()} Burmddit. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
54
frontend/components/Header.tsx
Normal file
54
frontend/components/Header.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header className="bg-white shadow-sm sticky top-0 z-50">
|
||||
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="text-2xl font-bold text-primary-600">B</span>
|
||||
<span className="text-xl font-bold text-gray-900 font-burmese">
|
||||
Burmddit
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="hidden md:flex space-x-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-gray-700 hover:text-primary-600 font-medium font-burmese"
|
||||
>
|
||||
ပင်မစာမျက်နှာ
|
||||
</Link>
|
||||
<Link
|
||||
href="/category/ai-news"
|
||||
className="text-gray-700 hover:text-primary-600 font-medium font-burmese"
|
||||
>
|
||||
AI သတင်းများ
|
||||
</Link>
|
||||
<Link
|
||||
href="/category/tutorials"
|
||||
className="text-gray-700 hover:text-primary-600 font-medium font-burmese"
|
||||
>
|
||||
သင်ခန်းစာများ
|
||||
</Link>
|
||||
<Link
|
||||
href="/category/tips-tricks"
|
||||
className="text-gray-700 hover:text-primary-600 font-medium font-burmese"
|
||||
>
|
||||
အကြံပြုချက်များ
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Search Icon */}
|
||||
<button className="p-2 text-gray-600 hover:text-primary-600">
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
39
frontend/components/TrendingSection.tsx
Normal file
39
frontend/components/TrendingSection.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
interface TrendingArticle {
|
||||
id: number
|
||||
title_burmese: string
|
||||
slug: string
|
||||
view_count: number
|
||||
category_name_burmese: string
|
||||
}
|
||||
|
||||
export default function TrendingSection({ articles }: { articles: TrendingArticle[] }) {
|
||||
if (articles.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4 font-burmese flex items-center">
|
||||
<svg className="w-5 h-5 text-red-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1011.95-4.95c-.592-.591-.98-.985-1.348-1.467-.363-.476-.724-1.063-1.207-2.03zM12.12 15.12A3 3 0 017 13s.879.5 2.5.5c0-1 .5-4 1.25-4.5.5 1 .786 1.293 1.371 1.879A2.99 2.99 0 0113 13a2.99 2.99 0 01-.879 2.121z" clipRule="evenodd" />
|
||||
</svg>
|
||||
လူကြိုက်များသော
|
||||
</h3>
|
||||
<ol className="space-y-3">
|
||||
{articles.map((article, index) => (
|
||||
<li key={article.id} className="flex items-start space-x-3">
|
||||
<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">
|
||||
{index + 1}
|
||||
</span>
|
||||
<Link
|
||||
href={`/article/${article.slug}`}
|
||||
className="flex-1 text-gray-700 hover:text-primary-600 font-burmese text-sm line-clamp-2 leading-snug"
|
||||
>
|
||||
{article.title_burmese}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
16
frontend/next.config.js
Normal file
16
frontend/next.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: '**',
|
||||
},
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
serverActions: true,
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
31
frontend/package.json
Normal file
31
frontend/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "burmddit",
|
||||
"version": "1.0.0",
|
||||
"description": "Myanmar AI News & Tutorials Platform",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "14.1.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"pg": "^8.11.3",
|
||||
"@vercel/postgres": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
32
frontend/tailwind.config.ts
Normal file
32
frontend/tailwind.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'burmese': ['Pyidaungsu', 'Noto Sans Myanmar', 'Myanmar Text', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
export default config
|
||||
Reference in New Issue
Block a user