HEX
Server: Apache/2.4.65 (Debian)
System: Linux kubikelcreative 5.10.0-35-amd64 #1 SMP Debian 5.10.237-1 (2025-05-19) x86_64
User: www-data (33)
PHP: 8.4.13
Disabled: NONE
Upload Files
File: /var/www/indoadvisory_new/webapp/src/routes/articles.tsx
import { Hono } from 'hono'
import { getCookie } from 'hono/cookie'
import { createSlug, formatDate, type Article } from '../utils/database'
import { type User } from '../utils/auth'

type Bindings = {
  DB: D1Database;
}

const articles = new Hono<{ Bindings: Bindings }>()

// Articles List (Admin)
articles.get('/', async (c) => {
  try {
    const articlesList = await c.env.DB.prepare(
      'SELECT * FROM articles ORDER BY created_at DESC'
    ).all() as { results: Article[] }

    const user = c.get('user') as 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') as 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') as 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: User): string {
  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