From 3d02f25bbd006f774638446a5613728829907b89 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 25 Feb 2026 22:06:24 +0000 Subject: [PATCH] V baru --- app/Controllers/AIController.php | 38 ++++++++++ app/Controllers/AdminController.php | 13 +++- app/Controllers/ContactController.php | 45 ++++++++++++ app/Controllers/NewsletterController.php | 33 +++++++++ app/Controllers/SitemapController.php | 13 +++- assets/js/main.js | 94 ++++++++++++++++++++++-- index.php | 14 ++++ views/admin/apks/index.php | 6 +- views/admin/dashboard.php | 62 ++++++++++++++++ views/admin/settings.php | 16 +++- views/contact.php | 78 ++++++++++++++++++++ views/footer.php | 85 ++++++++++++++++++++- views/maintenance.php | 50 +++++++++++++ 13 files changed, 531 insertions(+), 16 deletions(-) create mode 100644 app/Controllers/AIController.php create mode 100644 app/Controllers/ContactController.php create mode 100644 app/Controllers/NewsletterController.php create mode 100644 views/contact.php create mode 100644 views/maintenance.php diff --git a/app/Controllers/AIController.php b/app/Controllers/AIController.php new file mode 100644 index 0000000..a5570b8 --- /dev/null +++ b/app/Controllers/AIController.php @@ -0,0 +1,38 @@ + 'Message is empty']); + return; + } + + require_once __DIR__ . '/../../ai/LocalAIApi.php'; + + $systemPrompt = "You are a helpful assistant for " . get_setting('site_name', 'ApkNusa') . ", an APK downloader and tech blog site. Provide concise and accurate information about Android apps, games, and technology. Be youthful and professional."; + + $resp = \LocalAIApi::createResponse([ + 'input' => [ + ['role' => 'system', 'content' => $systemPrompt], + ['role' => 'user', 'content' => $userMessage], + ], + ]); + + if (!empty($resp['success'])) { + $text = \LocalAIApi::extractText($resp); + echo json_encode(['reply' => $text]); + } else { + echo json_encode(['error' => 'AI Assistant is currently unavailable.']); + } + } +} diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index e1eb15b..de176de 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -54,11 +54,18 @@ class AdminController extends Controller { 'total_downloads' => $this->getTotalDownloads(), 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(), 'pending_withdrawals' => $db->query("SELECT COUNT(*) FROM withdrawals WHERE status = 'pending'")->fetchColumn(), - 'recent_apks' => array_slice($apkService->getAllApks(), 0, 5) + 'recent_apks' => array_slice($apkService->getAllApks(), 0, 5), + 'referral_stats' => $this->getReferralStats() ]; $this->view('admin/dashboard', $stats); } +private function getReferralStats() { + $db = db_pdo(); + $stmt = $db->query("SELECT DATE(created_at) as date, COUNT(*) as count FROM referral_downloads WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(created_at) ORDER BY date ASC"); + return $stmt->fetchAll(); + } + private function getTotalDownloads() { $db = db_pdo(); return $db->query("SELECT SUM(total_downloads) FROM apks")->fetchColumn() ?: 0; @@ -239,6 +246,7 @@ class AdminController extends Controller { $this->checkAuth(); $settings = [ 'site_name' => get_setting('site_name'), + 'contact_email' => get_setting('contact_email'), 'site_icon' => get_setting('site_icon'), 'site_favicon' => get_setting('site_favicon'), 'meta_description' => get_setting('meta_description'), @@ -251,6 +259,7 @@ class AdminController extends Controller { 'github_url' => get_setting('github_url'), 'telegram_url' => get_setting('telegram_url'), 'whatsapp_url' => get_setting('whatsapp_url'), + 'maintenance_mode' => get_setting('maintenance_mode'), ]; $this->view('admin/settings', ['settings' => $settings]); } @@ -260,7 +269,7 @@ class AdminController extends Controller { $db = db_pdo(); $fields = [ - 'site_name', 'meta_description', 'meta_keywords', 'head_js', 'body_js', + 'site_name', 'contact_email', 'meta_description', 'meta_keywords', 'head_js', 'body_js', 'facebook_url', 'twitter_url', 'instagram_url', 'github_url', 'telegram_url', 'whatsapp_url' ]; foreach ($fields as $field) { diff --git a/app/Controllers/ContactController.php b/app/Controllers/ContactController.php new file mode 100644 index 0000000..6853220 --- /dev/null +++ b/app/Controllers/ContactController.php @@ -0,0 +1,45 @@ +view('contact', [ + 'title' => __('contact_us') . ' - ' . get_setting('site_name', 'ApkNusa') + ]); + } + + public function submit() { + $name = $_POST['name'] ?? ''; + $email = $_POST['email'] ?? ''; + $subject = $_POST['subject'] ?? 'New Contact Message'; + $message = $_POST['message'] ?? ''; + + if (empty($name) || empty($email) || empty($message)) { + $_SESSION['error'] = 'All fields are required.'; + $this->redirect('/contact'); + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $_SESSION['error'] = 'Invalid email address.'; + $this->redirect('/contact'); + } + + require_once __DIR__ . '/../../mail/MailService.php'; + + $res = \MailService::sendContactMessage($name, $email, $message, null, $subject); + + if (!empty($res['success'])) { + $_SESSION['success'] = 'Your message has been sent successfully!'; + } else { + $_SESSION['error'] = 'Failed to send message. Please try again later.'; + // Log error if needed: error_log($res['error']); + } + + $this->redirect('/contact'); + } +} diff --git a/app/Controllers/NewsletterController.php b/app/Controllers/NewsletterController.php new file mode 100644 index 0000000..244062d --- /dev/null +++ b/app/Controllers/NewsletterController.php @@ -0,0 +1,33 @@ + 'Please provide a valid email address.']); + return; + } + + $db = db_pdo(); + try { + $stmt = $db->prepare("INSERT INTO newsletter_subscribers (email) VALUES (?)"); + $stmt->execute([$email]); + echo json_encode(['success' => 'Thank you for subscribing!']); + } catch (\PDOException $e) { + if ($e->getCode() == 23000) { // Duplicate entry + echo json_encode(['success' => 'You are already subscribed!']); + } else { + echo json_encode(['error' => 'An error occurred. Please try again.']); + } + } + } +} diff --git a/app/Controllers/SitemapController.php b/app/Controllers/SitemapController.php index 4c8f913..1bdd17d 100644 --- a/app/Controllers/SitemapController.php +++ b/app/Controllers/SitemapController.php @@ -38,10 +38,21 @@ class SitemapController extends Controller { echo ''; } + // Blog Posts + $posts = $db->query("SELECT * FROM posts WHERE status = 'published'")->fetchAll(); + foreach ($posts as $post) { + echo ''; + echo '' . $baseUrl . '/blog/' . htmlspecialchars($post['slug']) . ''; + echo '' . date('Y-m-d', strtotime($post['created_at'] ?? 'now')) . ''; + echo '0.7'; + echo 'monthly'; + echo ''; + } + // Categories (if you have category pages, assuming /category/slug) foreach ($categories as $category) { echo ''; - echo '' . $baseUrl . '/category/' . htmlspecialchars($category['slug']) . ''; + echo '' . $baseUrl . '/?category=' . htmlspecialchars($category['slug']) . ''; echo '0.6'; echo 'weekly'; echo ''; diff --git a/assets/js/main.js b/assets/js/main.js index d4a6888..4a62eb3 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -91,13 +91,6 @@ document.addEventListener('DOMContentLoaded', () => { dropdownBtn.innerHTML = `${categoryName} `; } - // Update title if not searching - if (latestTitle && !url.includes('search=')) { - // We could use translations here but for simplicity we'll just use the category name - // if it's All Categories, we'll reset to original (usually "Latest APKs") - // However, we'll just keep it simple for now. - } - // Update URL without refreshing window.history.pushState({ category: category }, '', url); }) @@ -115,6 +108,90 @@ document.addEventListener('DOMContentLoaded', () => { }); }; + // AI Chat Assistant Logic + const initAIChat = () => { + const toggleBtn = document.getElementById('toggle-ai-chat'); + const closeBtn = document.getElementById('close-ai-chat'); + const chatWindow = document.getElementById('ai-chat-window'); + const chatInput = document.getElementById('ai-chat-input'); + const sendBtn = document.getElementById('send-ai-chat'); + const messagesContainer = document.getElementById('ai-chat-messages'); + + if (!toggleBtn || !chatWindow) return; + + toggleBtn.addEventListener('click', () => { + chatWindow.classList.toggle('d-none'); + if (!chatWindow.classList.contains('d-none')) { + chatInput.focus(); + } + }); + + closeBtn.addEventListener('click', () => { + chatWindow.classList.add('d-none'); + }); + + const appendMessage = (message, isUser = false) => { + const div = document.createElement('div'); + div.className = 'mb-3 d-flex ' + (isUser ? 'justify-content-end' : ''); + + const content = document.createElement('div'); + content.className = (isUser ? 'bg-success text-white' : 'bg-white') + ' p-3 rounded-4 shadow-sm small'; + content.style.maxWidth = '85%'; + if (isUser) { + content.style.borderBottomRightRadius = '0'; + } else { + content.style.borderBottomLeftRadius = '0'; + } + content.textContent = message; + + div.appendChild(content); + messagesContainer.appendChild(div); + messagesContainer.scrollTop = messagesContainer.scrollHeight; + }; + + const sendMessage = () => { + const message = chatInput.value.trim(); + if (!message) return; + + appendMessage(message, true); + chatInput.value = ''; + + // Loading state + const loadingDiv = document.createElement('div'); + loadingDiv.className = 'mb-3 d-flex'; + loadingDiv.innerHTML = '
Thinking...
'; + messagesContainer.appendChild(loadingDiv); + messagesContainer.scrollTop = messagesContainer.scrollHeight; + + fetch('/api/ai/chat', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ message: message }) + }) + .then(response => response.json()) + .then(data => { + messagesContainer.removeChild(loadingDiv); + if (data.reply) { + appendMessage(data.reply); + } else { + appendMessage(data.error || 'Sorry, something went wrong.'); + } + }) + .catch(err => { + messagesContainer.removeChild(loadingDiv); + appendMessage('Error connecting to AI assistant.'); + console.error(err); + }); + }; + + sendBtn.addEventListener('click', sendMessage); + chatInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') sendMessage(); + }); + }; + // Initial Sync const currentTheme = html.getAttribute('data-theme') || 'light'; updateIcons(currentTheme); @@ -132,6 +209,7 @@ document.addEventListener('DOMContentLoaded', () => { initThemeToggle('theme-toggle'); initThemeToggle('theme-toggle-mobile'); initCategoryAjax(); + initAIChat(); console.log('ApkNusa ready.'); -}); +}); \ No newline at end of file diff --git a/index.php b/index.php index 661259a..b740525 100644 --- a/index.php +++ b/index.php @@ -28,7 +28,19 @@ session_start(); use App\Core\Router; +// Maintenance Mode Check +if (get_setting('maintenance_mode') === '1') { + $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + $isAdmin = strpos($uri, '/admin') === 0 || strpos($uri, '/login') === 0 || strpos($uri, '/logout') === 0; + if (!$isAdmin && !isset($_SESSION['admin_id'])) { + require_once 'views/maintenance.php'; + exit; + } +} + $router = new Router(); +$router->post('/api/newsletter/subscribe', 'NewsletterController@subscribe'); +$router->post('/api/ai/chat', 'AIController@chat'); // Sitemap $router->get('/sitemap.xml', 'SitemapController@index'); @@ -51,6 +63,8 @@ $router->get('/blog', 'BlogController@index'); $router->get('/blog/:slug', 'BlogController@detail'); // Static Pages +$router->get('/contact', 'ContactController@index'); +$router->post('/contact', 'ContactController@submit'); $router->get('/help-center', 'HomeController@helpCenter'); $router->get('/privacy-policy', 'HomeController@privacyPolicy'); $router->get('/terms-of-service', 'HomeController@termsOfService'); diff --git a/views/admin/apks/index.php b/views/admin/apks/index.php index a5d9fdb..59bc542 100644 --- a/views/admin/apks/index.php +++ b/views/admin/apks/index.php @@ -1,8 +1,12 @@
-
+

Manage APKs

+
+ + +
Add New APK diff --git a/views/admin/dashboard.php b/views/admin/dashboard.php index df6d2a1..693cb8d 100644 --- a/views/admin/dashboard.php +++ b/views/admin/dashboard.php @@ -79,6 +79,68 @@
+
+
+
+
+
Referral Downloads (Last 7 Days)
+
+
+ +
+
+
+
+ + +
diff --git a/views/admin/settings.php b/views/admin/settings.php index 56f654f..7e6529d 100644 --- a/views/admin/settings.php +++ b/views/admin/settings.php @@ -10,6 +10,10 @@
+
+ + +
@@ -35,6 +39,16 @@
+
+
+ > + +
+
When enabled, regular visitors will see a maintenance page. Admins can still access the site.
+
+
Social Media Settings
@@ -129,4 +143,4 @@
- \ No newline at end of file + diff --git a/views/contact.php b/views/contact.php new file mode 100644 index 0000000..51229a5 --- /dev/null +++ b/views/contact.php @@ -0,0 +1,78 @@ + + +
+
+
+
+
+
+

Get in Touch

+

Have questions or feedback about our APKs? We'd love to hear from you. Send us a message and we'll respond as soon as possible.

+ +
+ + Jakarta, Indonesia +
+
+ + +
+ +
+
Follow Us
+
+ + + + +
+
+
+
+ + + + + + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+
+ + diff --git a/views/footer.php b/views/footer.php index cd160fe..6ca74d0 100644 --- a/views/footer.php +++ b/views/footer.php @@ -30,15 +30,17 @@
  • +
  • Contact Us
  • - - + +
    +

    @@ -71,8 +73,85 @@ + + +
    +
    +
    +
    +
    + +
    + ApkNusa AI +
    + +
    +
    +
    +
    + Hello! How can I help you today? +
    +
    +
    + +
    + +
    + + + + - \ No newline at end of file + diff --git a/views/maintenance.php b/views/maintenance.php new file mode 100644 index 0000000..1b3fb08 --- /dev/null +++ b/views/maintenance.php @@ -0,0 +1,50 @@ + + + + + + + Under Maintenance - <?php echo htmlspecialchars(get_setting('site_name', 'ApkNusa')); ?> + + + + + + +
    +
    + +
    +

    Under Maintenance

    +

    We're currently performing some scheduled maintenance. We'll be back shortly. Thank you for your patience!

    + +
    + +