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/admin.tsx
import { Hono } from 'hono'
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
import { generateSessionId, validatePassword, isValidSession, type User, type Session } from '../utils/auth'
import { initializeDatabase, createSlug, formatDate, type Article, type ContactInquiry, type SiteSetting } from '../utils/database'

type Bindings = {
  DB: D1Database;
}

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

// Middleware to check admin authentication
admin.use('/*', async (c, next) => {
  // Skip auth check for login page and login POST
  if (c.req.path === '/admin/login' && (c.req.method === 'GET' || c.req.method === 'POST')) {
    return next()
  }

  const sessionId = getCookie(c, 'admin_session')
  if (!sessionId) {
    return c.redirect('/admin/login')
  }

  try {
    const session = await c.env.DB.prepare(
      'SELECT s.*, u.id as user_id, u.username, u.email, u.name, u.role FROM admin_sessions s JOIN admin_users u ON s.user_id = u.id WHERE s.id = ?'
    ).bind(sessionId).first() as Session & User

    if (!session || !isValidSession(session)) {
      deleteCookie(c, 'admin_session')
      return c.redirect('/admin/login')
    }

    c.set('user', session)
    return next()
  } catch (error) {
    return c.redirect('/admin/login')
  }
})

// Admin Login Page
admin.get('/login', (c) => {
  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>Admin Login - 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 min-h-screen flex items-center justify-center">
        <div class="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
            <div class="text-center mb-8">
                <h1 class="text-2xl font-bold text-gray-800">
                    <i class="fas fa-chart-line text-blue-600 mr-2"></i>
                    IndoPrivate Admin
                </h1>
                <p class="text-gray-600 mt-2">Masuk ke dashboard admin</p>
            </div>
            
            <form action="/admin/login" method="POST" class="space-y-6">
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
                    <input 
                        type="text" 
                        name="username" 
                        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 username"
                    />
                </div>
                
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
                    <input 
                        type="password" 
                        name="password" 
                        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 password"
                    />
                </div>
                
                <button 
                    type="submit" 
                    class="w-full bg-blue-600 text-white py-3 px-6 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
                >
                    <i class="fas fa-sign-in-alt mr-2"></i>
                    Masuk
                </button>
            </form>
            
            <div class="mt-6 text-center text-sm text-gray-500">
                <p>Demo credentials: <strong>admin / admin123</strong></p>
            </div>
        </div>
    </body>
    </html>
  `)
})

// Admin Login Handler
admin.post('/login', async (c) => {
  const { username, password } = await c.req.parseBody()
  
  try {
    await initializeDatabase(c.env.DB)
    
    // Insert demo admin if not exists
    await c.env.DB.prepare(
      'INSERT OR IGNORE INTO admin_users (username, password_hash, email, name, role) VALUES (?, ?, ?, ?, ?)'
    ).bind('admin', 'admin123', 'admin@indoprivate.co.id', 'Administrator', 'admin').run()

    const user = await c.env.DB.prepare(
      'SELECT * FROM admin_users WHERE username = ?'
    ).bind(username).first() as User

    if (!user || !validatePassword(password as string, user.password_hash)) {
      return c.html(`
        <script>
          alert('Username atau password salah');
          window.location.href = '/admin/login';
        </script>
      `)
    }

    // Create session
    const sessionId = generateSessionId()
    const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours

    await c.env.DB.prepare(
      'INSERT INTO admin_sessions (id, user_id, expires_at) VALUES (?, ?, ?)'
    ).bind(sessionId, user.id, expiresAt.toISOString()).run()

    setCookie(c, 'admin_session', sessionId, {
      maxAge: 24 * 60 * 60,
      httpOnly: true,
      secure: false, // Set to true in production with HTTPS
      sameSite: 'Lax'
    })

    return c.redirect('/admin/dashboard')
  } catch (error) {
    return c.html(`
      <script>
        alert('Terjadi kesalahan sistem');
        window.location.href = '/admin/login';
      </script>
    `)
  }
})

// Admin Dashboard
admin.get('/dashboard', async (c) => {
  const user = c.get('user') as User
  
  try {
    // Get dashboard statistics
    const articlesCount = await c.env.DB.prepare('SELECT COUNT(*) as count FROM articles').first() as { count: number }
    const inquiriesCount = await c.env.DB.prepare('SELECT COUNT(*) as count FROM contact_inquiries WHERE status = "new"').first() as { count: number }
    const publishedArticles = await c.env.DB.prepare('SELECT COUNT(*) as count FROM articles WHERE status = "published"').first() as { count: number }
    const clientsCount = await c.env.DB.prepare('SELECT COUNT(*) as count FROM clients WHERE is_active = 1').first() as { count: number }
    
    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>Dashboard 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="mb-8">
                  <h1 class="text-3xl font-bold text-gray-800">Dashboard Admin</h1>
                  <p class="text-gray-600 mt-2">Selamat datang kembali, ${user.name}</p>
              </div>
              
              <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <div class="flex items-center">
                          <div class="bg-blue-100 p-3 rounded-full">
                              <i class="fas fa-newspaper text-blue-600 text-xl"></i>
                          </div>
                          <div class="ml-4">
                              <h3 class="text-lg font-semibold text-gray-800">Total Artikel</h3>
                              <p class="text-2xl font-bold text-blue-600">${articlesCount.count}</p>
                          </div>
                      </div>
                  </div>
                  
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <div class="flex items-center">
                          <div class="bg-green-100 p-3 rounded-full">
                              <i class="fas fa-check-circle text-green-600 text-xl"></i>
                          </div>
                          <div class="ml-4">
                              <h3 class="text-lg font-semibold text-gray-800">Artikel Published</h3>
                              <p class="text-2xl font-bold text-green-600">${publishedArticles.count}</p>
                          </div>
                      </div>
                  </div>
                  
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <div class="flex items-center">
                          <div class="bg-orange-100 p-3 rounded-full">
                              <i class="fas fa-envelope text-orange-600 text-xl"></i>
                          </div>
                          <div class="ml-4">
                              <h3 class="text-lg font-semibold text-gray-800">Inquiry Baru</h3>
                              <p class="text-2xl font-bold text-orange-600">${inquiriesCount.count}</p>
                          </div>
                      </div>
                  </div>
                  
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <div class="flex items-center">
                          <div class="bg-purple-100 p-3 rounded-full">
                              <i class="fas fa-building text-purple-600 text-xl"></i>
                          </div>
                          <div class="ml-4">
                              <h3 class="text-lg font-semibold text-gray-800">Klien Aktif</h3>
                              <p class="text-2xl font-bold text-purple-600">${clientsCount.count}</p>
                          </div>
                      </div>
                  </div>
              </div>
              
              <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <h2 class="text-xl font-bold text-gray-800 mb-4">
                          <i class="fas fa-newspaper mr-2"></i>
                          Quick Actions
                      </h2>
                      <div class="space-y-4">
                          <a href="/admin/articles/create" class="flex items-center p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors">
                              <i class="fas fa-plus text-blue-600 mr-3"></i>
                              <div>
                                  <h3 class="font-semibold text-gray-800">Buat Artikel Baru</h3>
                                  <p class="text-gray-600 text-sm">Tulis dan publikasikan artikel terbaru</p>
                              </div>
                          </a>
                          
                          <a href="/admin/articles" class="flex items-center p-4 bg-green-50 rounded-lg hover:bg-green-100 transition-colors">
                              <i class="fas fa-list text-green-600 mr-3"></i>
                              <div>
                                  <h3 class="font-semibold text-gray-800">Kelola Artikel</h3>
                                  <p class="text-gray-600 text-sm">Edit atau hapus artikel yang ada</p>
                              </div>
                          </a>
                          
                          <a href="/admin/inquiries" class="flex items-center p-4 bg-orange-50 rounded-lg hover:bg-orange-100 transition-colors">
                              <i class="fas fa-envelope text-orange-600 mr-3"></i>
                              <div>
                                  <h3 class="font-semibold text-gray-800">Lihat Inquiry</h3>
                                  <p class="text-gray-600 text-sm">Respon pertanyaan dari prospek</p>
                              </div>
                          </a>
                          
                          <a href="/admin/clients" class="flex items-center p-4 bg-purple-50 rounded-lg hover:bg-purple-100 transition-colors">
                              <i class="fas fa-building text-purple-600 mr-3"></i>
                              <div>
                                  <h3 class="font-semibold text-gray-800">Kelola Klien</h3>
                                  <p class="text-gray-600 text-sm">Manage client showcase dan portfolio</p>
                              </div>
                          </a>
                          
                          <a href="/admin/team" class="flex items-center p-4 bg-teal-50 rounded-lg hover:bg-teal-100 transition-colors">
                              <i class="fas fa-users text-teal-600 mr-3"></i>
                              <div>
                                  <h3 class="font-semibold text-gray-800">Kelola Tim</h3>
                                  <p class="text-gray-600 text-sm">Manage profil anggota tim</p>
                              </div>
                          </a>
                      </div>
                  </div>
                  
                  <div class="bg-white p-6 rounded-xl shadow-lg">
                      <h2 class="text-xl font-bold text-gray-800 mb-4">
                          <i class="fas fa-chart-bar mr-2"></i>
                          Website Statistics
                      </h2>
                      <div class="space-y-4">
                          <div class="flex justify-between items-center">
                              <span class="text-gray-600">Artikel Terbaru</span>
                              <span class="font-semibold">7 hari terakhir</span>
                          </div>
                          <div class="flex justify-between items-center">
                              <span class="text-gray-600">Total Halaman</span>
                              <span class="font-semibold">8 halaman</span>
                          </div>
                          <div class="flex justify-between items-center">
                              <span class="text-gray-600">Status Website</span>
                              <span class="text-green-600 font-semibold">
                                  <i class="fas fa-check-circle mr-1"></i>
                                  Online
                              </span>
                          </div>
                      </div>
                  </div>
              </div>
          </div>
      </body>
      </html>
    `)
  } catch (error) {
    console.error('Dashboard error:', error)
    return c.html(`<div>Error loading dashboard</div>`)
  }
})

// Admin Navbar Component
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-gray-700 hover:text-blue-600 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/clients" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
                        <i class="fas fa-building mr-1"></i>
                        Klien
                    </a>
                    <a href="/admin/team" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
                        <i class="fas fa-users mr-1"></i>
                        Tim
                    </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>
  `
}

// Logout
admin.get('/logout', (c) => {
  deleteCookie(c, 'admin_session')
  return c.redirect('/admin/login')
})

export default admin