diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eee2a30
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,43 @@
+# Dependencies
+node_modules/
+frontend/node_modules/
+backend/__pycache__/
+*.pyc
+*.pyo
+*.pyd
+.Python
+
+# Build outputs
+frontend/.next/
+frontend/out/
+frontend/build/
+*.log
+
+# Environment variables
+.env
+.env.local
+.env.production
+*.env
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Backups
+*.backup
+*-backup-*
+
+# Test coverage
+coverage/
+.nyc_output/
+
+# Misc
+*.tar.gz
+*.zip
diff --git a/deploy-ui-improvements.sh b/deploy-ui-improvements.sh
new file mode 100755
index 0000000..b2e93ea
--- /dev/null
+++ b/deploy-ui-improvements.sh
@@ -0,0 +1,159 @@
+#!/bin/bash
+# Deploy Burmddit UI/UX Improvements
+# Run this script to update your live site with the new design
+
+set -e # Exit on error
+
+echo "đ¨ Burmddit UI/UX Deployment Script"
+echo "===================================="
+echo ""
+
+# Colors
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Get current directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+echo -e "${BLUE}Step 1: Backup existing files${NC}"
+echo "--------------------------------"
+cd frontend/app
+
+# Backup old files
+if [ -f "globals.css" ]; then
+ echo "Backing up globals.css..."
+ cp globals.css globals-backup-$(date +%Y%m%d-%H%M%S).css
+fi
+
+if [ -f "page.tsx" ]; then
+ echo "Backing up page.tsx..."
+ cp page.tsx page-backup-$(date +%Y%m%d-%H%M%S).tsx
+fi
+
+if [ -f "article/[slug]/page.tsx" ]; then
+ echo "Backing up article page..."
+ mkdir -p article/[slug]/backup
+ cp article/[slug]/page.tsx "article/[slug]/backup/page-backup-$(date +%Y%m%d-%H%M%S).tsx"
+fi
+
+echo -e "${GREEN}â Backups created${NC}"
+echo ""
+
+echo -e "${BLUE}Step 2: Deploy new frontend files${NC}"
+echo "-----------------------------------"
+
+# Replace CSS
+if [ -f "globals-improved.css" ]; then
+ echo "Deploying new CSS..."
+ mv globals-improved.css globals.css
+ echo -e "${GREEN}â CSS updated${NC}"
+else
+ echo -e "${YELLOW}â globals-improved.css not found${NC}"
+fi
+
+# Replace homepage
+if [ -f "page-improved.tsx" ]; then
+ echo "Deploying new homepage..."
+ mv page-improved.tsx page.tsx
+ echo -e "${GREEN}â Homepage updated${NC}"
+else
+ echo -e "${YELLOW}â page-improved.tsx not found${NC}"
+fi
+
+# Replace article page
+if [ -f "article/[slug]/page-improved.tsx" ]; then
+ echo "Deploying new article page..."
+ mv article/[slug]/page-improved.tsx article/[slug]/page.tsx
+ echo -e "${GREEN}â Article page updated${NC}"
+else
+ echo -e "${YELLOW}â article page-improved.tsx not found${NC}"
+fi
+
+# Tag page should already be in place
+if [ ! -f "tag/[slug]/page.tsx" ]; then
+ echo -e "${YELLOW}â Tag page not found - check if it was copied${NC}"
+else
+ echo -e "${GREEN}â Tag pages ready${NC}"
+fi
+
+echo ""
+
+echo -e "${BLUE}Step 3: Database Migration (Tags System)${NC}"
+echo "-----------------------------------------"
+
+# Check if DATABASE_URL is set
+if [ -z "$DATABASE_URL" ]; then
+ echo -e "${YELLOW}DATABASE_URL not set. Please run migration manually:${NC}"
+ echo ""
+ echo " psql \$DATABASE_URL < $SCRIPT_DIR/database/tags_migration.sql"
+ echo ""
+ echo "Or if you have connection details:"
+ echo " psql -h HOST -U USER -d DATABASE < $SCRIPT_DIR/database/tags_migration.sql"
+ echo ""
+ read -p "Press Enter to continue without migration, or Ctrl+C to exit..."
+else
+ echo "Running tags migration..."
+ psql "$DATABASE_URL" < "$SCRIPT_DIR/database/tags_migration.sql" 2>&1 | grep -v "NOTICE" || true
+ echo -e "${GREEN}â Database migration complete${NC}"
+fi
+
+echo ""
+
+echo -e "${BLUE}Step 4: Install dependencies${NC}"
+echo "------------------------------"
+cd "$SCRIPT_DIR/frontend"
+
+if [ -f "package.json" ]; then
+ if command -v npm &> /dev/null; then
+ echo "Installing/updating npm packages..."
+ npm install
+ echo -e "${GREEN}â Dependencies updated${NC}"
+ else
+ echo -e "${YELLOW}â npm not found - skip dependency update${NC}"
+ fi
+fi
+
+echo ""
+
+echo -e "${BLUE}Step 5: Build frontend${NC}"
+echo "-----------------------"
+
+if command -v npm &> /dev/null; then
+ echo "Building production frontend..."
+ npm run build
+ echo -e "${GREEN}â Build complete${NC}"
+else
+ echo -e "${YELLOW}â npm not found - skip build${NC}"
+ echo "If using Vercel/external deployment, push to Git and it will auto-build"
+fi
+
+echo ""
+
+echo -e "${GREEN}ââââââââââââââââââââââââââââââââââââââââ${NC}"
+echo -e "${GREEN}â DEPLOYMENT COMPLETE!${NC}"
+echo -e "${GREEN}ââââââââââââââââââââââââââââââââââââââââ${NC}"
+echo ""
+echo "đ New features deployed:"
+echo " â Modern design system"
+echo " â Hashtag/tag system"
+echo " â Cover images with overlays"
+echo " â Trending tags section"
+echo " â Better typography"
+echo " â Improved article pages"
+echo ""
+echo -e "${BLUE}Next steps:${NC}"
+echo "1. If using Vercel/Railway: Push to Git to trigger auto-deploy"
+echo " cd $SCRIPT_DIR && git push origin main"
+echo ""
+echo "2. Test the new design at: burmddit.qikbite.asia"
+echo ""
+echo "3. If auto-tagging not working, update backend/publisher.py"
+echo " (see UI-IMPROVEMENTS.md for code snippet)"
+echo ""
+echo "4. Clear browser cache if design doesn't update immediately"
+echo ""
+echo -e "${YELLOW}For rollback:${NC} Restore from backup files created in step 1"
+echo ""
diff --git a/frontend/app/article/[slug]/page-improved.tsx b/frontend/app/article/[slug]/page-improved.tsx
deleted file mode 100644
index 76d66b2..0000000
--- a/frontend/app/article/[slug]/page-improved.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import { sql } from '@vercel/postgres'
-import { notFound } from 'next/navigation'
-import Link from 'next/link'
-import Image from 'next/image'
-
-async function getArticleWithTags(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,
- COALESCE(
- array_agg(t.name_burmese) FILTER (WHERE t.id IS NOT NULL),
- ARRAY[]::VARCHAR[]
- ) as tags_burmese,
- COALESCE(
- array_agg(t.slug) FILTER (WHERE t.id IS NOT NULL),
- ARRAY[]::VARCHAR[]
- ) 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 a.slug = ${slug} AND a.status = 'published'
- GROUP BY a.id, c.id
- `
-
- 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}, 6)`
- return rows
- } catch (error) {
- return []
- }
-}
-
-export default async function ImprovedArticlePage({ params }: { params: { slug: string } }) {
- const article = await getArticleWithTags(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 (
-
- {/* Hero Cover Image */}
- {article.featured_image && (
-
-
-
-
-
-
- {/* Category */}
-
- {article.category_name_burmese}
-
-
- {/* Title */}
-
- {article.title_burmese}
-
-
- {/* Meta */}
-
- {publishedDate}
- â˘
- {article.reading_time} áááá
áş
- â˘
- {article.view_count} views
-
-
-
-
- )}
-
- {/* Article Content */}
-
- {/* Tags */}
- {article.tags_burmese && article.tags_burmese.length > 0 && (
-
- {article.tags_burmese.map((tag: string, idx: number) => (
-
- #{tag}
-
- ))}
-
- )}
-
- {/* Article Body */}
-
-
-
- {/* Additional Images Gallery */}
- {article.images && article.images.length > 1 && (
-
-
ááŹááşááŻáśááťáŹá¸
-
- {article.images.slice(1).map((img: string, idx: number) => (
-
-
-
- ))}
-
-
- )}
-
- {/* Videos */}
- {article.videos && article.videos.length > 0 && (
-
-
ááŽááŽáááŻááťáŹá¸
-
- {article.videos.map((video: string, idx: number) => (
-
- {renderVideo(video)}
-
- ))}
-
-
- )}
-
-
- {/* Source Attribution */}
- {article.source_articles && article.source_articles.length > 0 && (
-
-
-
-
-
- áá°áááşá¸ááááşá¸áááşá¸ááźá
áşááťáŹá¸
-
-
- á¤ááąáŹááşá¸ááŤá¸ááᯠáĄáąáŹááşááŤáá°áááşá¸ááááşá¸ááťáŹá¸ááž á
áŻá
ááşá¸á ááźááşááŹááŹááŹáááŻáˇ ááźááşáááŻááŹá¸ááźááşá¸ ááźá
áşááŤáááşá áĄáŹá¸ááŻáśá¸ááąáŹ áĄááźá˝áąá¸áĄá áá°áááşá¸á
áŹááąá¸áá°ááťáŹá¸áážááˇáş ááŻááşááźááşáá°ááťáŹá¸ááᯠáááşáááŻááşááŤáááşá
-
-
- {article.source_articles.map((source: any, index: number) => (
-
-
-
- {index + 1}
-
-
-
- {source.title}
-
- {source.author && source.author !== 'Unknown' && (
-
- á
áŹááąá¸áá°: {source.author}
-
- )}
-
-
-
-
-
-
-
-
- ))}
-
-
- )}
-
- {/* Share Section */}
-
-
-
ááťážááąááŤ:
-
-
- Facebook
-
-
- Twitter
-
-
- WhatsApp
-
-
-
-
-
-
- {/* Related Articles */}
- {relatedArticles.length > 0 && (
-
-
- áááşá
ááşááąáŹááşá¸ááŤá¸ááťáŹá¸
-
-
- {relatedArticles.map((related: any) => (
-
- {related.featured_image && (
-
-
-
- )}
-
-
- {related.title_burmese}
-
-
- {related.excerpt_burmese}
-
-
-
- ))}
-
-
- )}
-
- )
-}
-
-function formatContent(content: string): string {
- let formatted = content
- .replace(/\n\n/g, '')
- .replace(/## (.*?)\n/g, '
$1 ')
- .replace(/### (.*?)\n/g, '$1 ')
- .replace(/\*\*(.*?)\*\*/g, '$1 ')
- .replace(/\*(.*?)\*/g, '$1 ')
-
- return `${formatted}
`
-}
-
-function renderVideo(videoUrl: string) {
- let videoId = null
-
- 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 (
- VIDEO
- )
- }
-
- return (
-
- )
-}
-
-export async function generateMetadata({ params }: { params: { slug: string } }) {
- const article = await getArticleWithTags(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] : [],
- },
- }
-}
diff --git a/frontend/app/article/[slug]/page.tsx b/frontend/app/article/[slug]/page.tsx
index 8855915..76d66b2 100644
--- a/frontend/app/article/[slug]/page.tsx
+++ b/frontend/app/article/[slug]/page.tsx
@@ -1,21 +1,30 @@
-import { sql } from '@/lib/db'
+import { sql } from '@vercel/postgres'
import { notFound } from 'next/navigation'
-
-export const dynamic = 'force-dynamic'
import Link from 'next/link'
import Image from 'next/image'
-async function getArticle(slug: string) {
+async function getArticleWithTags(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
+ c.slug as category_slug,
+ COALESCE(
+ array_agg(t.name_burmese) FILTER (WHERE t.id IS NOT NULL),
+ ARRAY[]::VARCHAR[]
+ ) as tags_burmese,
+ COALESCE(
+ array_agg(t.slug) FILTER (WHERE t.id IS NOT NULL),
+ ARRAY[]::VARCHAR[]
+ ) 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 a.slug = ${slug} AND a.status = 'published'
+ GROUP BY a.id, c.id
`
if (rows.length === 0) return null
@@ -32,15 +41,15 @@ async function getArticle(slug: string) {
async function getRelatedArticles(articleId: number) {
try {
- const { rows } = await sql`SELECT * FROM get_related_articles(${articleId}, 5)`
+ const { rows } = await sql`SELECT * FROM get_related_articles(${articleId}, 6)`
return rows
} catch (error) {
return []
}
}
-export default async function ArticlePage({ params }: { params: { slug: string } }) {
- const article = await getArticle(params.slug)
+export default async function ImprovedArticlePage({ params }: { params: { slug: string } }) {
+ const article = await getArticleWithTags(params.slug)
if (!article) {
notFound()
@@ -54,185 +63,188 @@ export default async function ArticlePage({ params }: { params: { slug: string }
})
return (
-
- {/* Breadcrumb */}
-
-
- áááşáá
áŹááťááşáážáŹ
-
- /
-
- {article.category_name_burmese}
-
- /
- {article.title_burmese}
-
-
- {/* Article Header */}
-
- {/* Category Badge */}
-
-
- {article.category_name_burmese}
-
+
+ {/* Hero Cover Image */}
+ {article.featured_image && (
+
+
+
+
+
+
+ {/* Category */}
+
+ {article.category_name_burmese}
+
+
+ {/* Title */}
+
+ {article.title_burmese}
+
+
+ {/* Meta */}
+
+ {publishedDate}
+ â˘
+ {article.reading_time} áááá
áş
+ â˘
+ {article.view_count} views
+
+
+
+ )}
- {/* Featured Image */}
- {article.featured_image && (
-
-
+ {/* Article Content */}
+
+ {/* Tags */}
+ {article.tags_burmese && article.tags_burmese.length > 0 && (
+
+ {article.tags_burmese.map((tag: string, idx: number) => (
+
+ #{tag}
+
+ ))}
)}
- {/* Article Content */}
-
- {/* Title */}
-
- {article.title_burmese}
-
-
- {/* Meta Info */}
-
- {publishedDate}
- â˘
- {article.reading_time} áááá
áş
- â˘
- {article.view_count} ááźááˇáşáážáŻáážáŻ
-
-
- {/* Article Body */}
-
-
-
- {/* đĽ Additional Images Gallery */}
- {article.images && article.images.length > 1 && (
-
-
ááŹááşááŻáśááťáŹá¸
-
- {article.images.slice(1).map((img: string, idx: number) => (
-
-
-
- ))}
-
-
- )}
-
- {/* đĽ Videos */}
- {article.videos && article.videos.length > 0 && (
-
-
ááŽááŽáááŻááťáŹá¸
-
- {article.videos.map((video: string, idx: number) => (
-
- {renderVideo(video)}
-
- ))}
-
-
- )}
-
-
- {/* â SOURCE ATTRIBUTION - THIS IS THE KEY PART! */}
- {article.source_articles && article.source_articles.length > 0 && (
-
-
-
-
-
- áá°áááşá¸ááááşá¸áááşá¸ááźá
áşááťáŹá¸
-
-
- á¤ááąáŹááşá¸ááŤá¸ááᯠáĄáąáŹááşááŤáá°áááşá¸ááááşá¸ááťáŹá¸ááž á
áŻá
ááşá¸á ááźááşááŹááŹááŹáááŻáˇ ááźááşáááŻááŹá¸ááźááşá¸ ááźá
áşááŤáááşá áĄáŹá¸ááŻáśá¸ááąáŹ áĄááźá˝áąá¸áĄá áá°áááşá¸á
áŹááąá¸áá°ááťáŹá¸áážááˇáş ááŻááşááźááşáá°ááťáŹá¸ááᯠáááşáááŻááşááŤáááşá
-
-