File: /var/www/indoadvisory_new/webapp/src/routes/articles.js
import { Hono } from 'hono';
import { formatDate } from '../utils/database';
const articles = new Hono();
// Articles List (Admin)
articles.get('/', async (c) => {
try {
const articlesList = await c.env.DB.prepare('SELECT * FROM articles ORDER BY created_at DESC').all();
const user = c.get('user');
return c.html(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kelola Artikel - Admin IndoPrivate</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
${adminNavbar(user)}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-gray-800">Kelola Artikel</h1>
<a href="/admin/articles/create" class="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors">
<i class="fas fa-plus mr-2"></i>
Buat Artikel Baru
</a>
</div>
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Judul</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kategori</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tanggal</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${articlesList.results.map(article => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4">
<div>
<div class="text-sm font-medium text-gray-900">${article.title}</div>
<div class="text-sm text-gray-500">${article.excerpt?.substring(0, 100)}...</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
${article.category}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-semibold rounded-full ${article.status === 'published' ? 'bg-green-100 text-green-800' : article.status === 'draft' ? 'bg-yellow-100 text-yellow-800' : 'bg-gray-100 text-gray-800'}">
${article.status}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${formatDate(article.created_at)}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<a href="/admin/articles/edit/${article.id}" class="text-blue-600 hover:text-blue-900">
<i class="fas fa-edit mr-1"></i>Edit
</a>
<a href="/artikel/${article.slug}" target="_blank" class="text-green-600 hover:text-green-900">
<i class="fas fa-eye mr-1"></i>Lihat
</a>
<button onclick="deleteArticle(${article.id})" class="text-red-600 hover:text-red-900">
<i class="fas fa-trash mr-1"></i>Hapus
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
<script>
function deleteArticle(id) {
if (confirm('Apakah Anda yakin ingin menghapus artikel ini?')) {
fetch('/admin/articles/' + id + '/delete', { method: 'POST' })
.then(() => window.location.reload());
}
}
</script>
</body>
</html>
`);
}
catch (error) {
return c.html(`<div>Error: ${error.message}</div>`);
}
});
// Create Article Form
articles.get('/create', (c) => {
const user = c.get('user');
return c.html(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buat Artikel Baru - Admin IndoPrivate</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Include Quill.js for rich text editing -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
</head>
<body class="bg-gray-100">
${adminNavbar(user)}
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">Buat Artikel Baru</h1>
<p class="text-gray-600 mt-2">Tulis dan publikasikan artikel untuk website</p>
</div>
<form id="articleForm" action="/admin/articles/create" method="POST" class="bg-white rounded-xl shadow-lg p-8 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Judul Artikel</label>
<input
type="text"
name="title"
id="title"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Masukkan judul artikel"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Slug URL</label>
<input
type="text"
name="slug"
id="slug"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="url-artikel-ini"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Kategori</label>
<select
name="category"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="news">Berita</option>
<option value="analysis">Analisis</option>
<option value="guide">Panduan</option>
<option value="insights">Insights</option>
<option value="market-update">Update Pasar</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Status</label>
<select
name="status"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Excerpt (Ringkasan)</label>
<textarea
name="excerpt"
rows="3"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Tuliskan ringkasan artikel untuk preview..."
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Konten Artikel</label>
<div id="editor" style="height: 400px;"></div>
<input type="hidden" name="content" id="content" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Tags (pisahkan dengan koma)</label>
<input
type="text"
name="tags"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="private equity, investasi, IPO, valuasi"
/>
</div>
<div class="flex justify-between items-center pt-6">
<a href="/admin/articles" class="px-6 py-3 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 transition-colors">
<i class="fas fa-arrow-left mr-2"></i>
Kembali
</a>
<button
type="submit"
class="px-8 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors"
>
<i class="fas fa-save mr-2"></i>
Simpan Artikel
</button>
</div>
</form>
</div>
<script>
// Initialize Quill editor
const quill = new Quill('#editor', {
theme: 'snow',
placeholder: 'Tulis konten artikel di sini...',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'color': [] }, { 'background': [] }],
['link', 'image'],
['clean']
]
}
});
// Auto-generate slug from title
document.getElementById('title').addEventListener('input', function() {
const title = this.value;
const slug = title.toLowerCase()
.trim()
.replace(/[^\\w\\s-]/g, '')
.replace(/[\\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
document.getElementById('slug').value = slug;
});
// Handle form submission
document.getElementById('articleForm').addEventListener('submit', function() {
const content = quill.root.innerHTML;
document.getElementById('content').value = content;
});
</script>
</body>
</html>
`);
});
// Create Article Handler
articles.post('/create', async (c) => {
try {
const formData = await c.req.parseBody();
const user = c.get('user');
const now = new Date().toISOString();
const publishedAt = formData.status === 'published' ? now : null;
const result = await c.env.DB.prepare(`INSERT INTO articles (title, slug, excerpt, content, category, status, tags, author_id, published_at, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(formData.title, formData.slug, formData.excerpt || '', formData.content, formData.category, formData.status, formData.tags || '', user.id, publishedAt, now, now).run();
return c.redirect('/admin/articles');
}
catch (error) {
return c.html(`<script>alert('Error: ${error.message}'); window.history.back();</script>`);
}
});
// Delete Article
articles.post('/:id/delete', async (c) => {
try {
const id = c.req.param('id');
await c.env.DB.prepare('DELETE FROM articles WHERE id = ?').bind(id).run();
return c.json({ success: true });
}
catch (error) {
return c.json({ error: error.message }, 500);
}
});
// Admin Navbar Component (reused from admin.tsx)
function adminNavbar(user) {
return `
<nav class="bg-white shadow-lg border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<h1 class="text-xl font-bold text-gray-800">
<i class="fas fa-chart-line text-blue-600 mr-2"></i>
IndoPrivate Admin
</h1>
</div>
<div class="hidden md:flex items-center space-x-6">
<a href="/admin/dashboard" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-tachometer-alt mr-1"></i>
Dashboard
</a>
<a href="/admin/articles" class="text-blue-600 font-semibold px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-newspaper mr-1"></i>
Artikel
</a>
<a href="/admin/inquiries" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-envelope mr-1"></i>
Inquiry
</a>
<a href="/admin/settings" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-cog mr-1"></i>
Settings
</a>
<a href="/" target="_blank" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-external-link-alt mr-1"></i>
Lihat Website
</a>
</div>
<div class="flex items-center">
<span class="text-gray-700 mr-4">
<i class="fas fa-user mr-1"></i>
${user.name}
</span>
<a href="/admin/logout" class="text-red-600 hover:text-red-700 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-sign-out-alt mr-1"></i>
Logout
</a>
</div>
</div>
</div>
</nav>
`;
}
export default articles;