Aslam vbru

This commit is contained in:
Flatlogic Bot 2026-02-24 22:45:41 +00:00
parent a887a75aed
commit 4f61082b27
26 changed files with 1694 additions and 120 deletions

View File

@ -0,0 +1,239 @@
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Services\ApkService;
class AdminController extends Controller {
private function checkAuth() {
if (!isset($_SESSION['user_id'])) {
$this->redirect('/admin/login');
}
}
public function loginForm() {
if (isset($_SESSION['user_id'])) {
$this->redirect('/admin/dashboard');
}
$this->view('admin/login');
}
public function login() {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$db = db_pdo();
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$this->redirect('/admin/dashboard');
} else {
$error = "Invalid username or password";
$this->view('admin/login', ['error' => $error]);
}
}
public function logout() {
session_destroy();
$this->redirect('/admin/login');
}
public function dashboard() {
$this->checkAuth();
$apkService = new ApkService();
$db = db_pdo();
$stats = [
'total_apks' => count($apkService->getAllApks()),
'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)
];
$this->view('admin/dashboard', $stats);
}
private function getTotalDownloads() {
$db = db_pdo();
return $db->query("SELECT SUM(total_downloads) FROM apks")->fetchColumn() ?: 0;
}
// APK Management
public function apks() {
$this->checkAuth();
$apkService = new ApkService();
$apks = $apkService->getAllApks();
$this->view('admin/apks/index', ['apks' => $apks]);
}
public function addApkForm() {
$this->checkAuth();
$db = db_pdo();
$categories = $db->query("SELECT * FROM categories")->fetchAll();
$this->view('admin/apks/form', ['action' => 'add', 'categories' => $categories]);
}
public function addApk() {
$this->checkAuth();
$title = $_POST['title'];
$slug = $this->slugify($title);
$description = $_POST['description'];
$version = $_POST['version'];
$image_url = $_POST['image_url'];
$download_url = $_POST['download_url'];
$category_id = $_POST['category_id'] ?? null;
$status = $_POST['status'] ?? 'published';
$is_vip = isset($_POST['is_vip']) ? 1 : 0;
$icon_path = $this->handleUpload('icon_file');
$db = db_pdo();
$stmt = $db->prepare("INSERT INTO apks (title, slug, description, version, image_url, icon_path, download_url, category_id, status, is_vip, display_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)");
$stmt->execute([$title, $slug, $description, $version, $image_url, $icon_path, $download_url, $category_id, $status, $is_vip]);
$this->redirect('/admin/apks');
}
public function editApkForm($params) {
$this->checkAuth();
$apkService = new ApkService();
$apk = $apkService->getApkById($params['id']);
$db = db_pdo();
$categories = $db->query("SELECT * FROM categories")->fetchAll();
$this->view('admin/apks/form', ['action' => 'edit', 'apk' => $apk, 'categories' => $categories]);
}
public function editApk($params) {
$this->checkAuth();
$title = $_POST['title'];
$description = $_POST['description'];
$version = $_POST['version'];
$image_url = $_POST['image_url'];
$download_url = $_POST['download_url'];
$category_id = $_POST['category_id'] ?? null;
$status = $_POST['status'];
$is_vip = isset($_POST['is_vip']) ? 1 : 0;
$db = db_pdo();
$apk = $db->query("SELECT * FROM apks WHERE id = " . $params['id'])->fetch();
$icon_path = $this->handleUpload('icon_file') ?: $apk['icon_path'];
$stmt = $db->prepare("UPDATE apks SET title = ?, description = ?, version = ?, image_url = ?, icon_path = ?, download_url = ?, category_id = ?, status = ?, is_vip = ? WHERE id = ?");
$stmt->execute([$title, $description, $version, $image_url, $icon_path, $download_url, $category_id, $status, $is_vip, $params['id']]);
$this->redirect('/admin/apks');
}
public function updateOrder() {
$this->checkAuth();
$order = $_POST['order'] ?? [];
$db = db_pdo();
foreach ($order as $index => $id) {
$stmt = $db->prepare("UPDATE apks SET display_order = ? WHERE id = ?");
$stmt->execute([$index, $id]);
}
header('Content-Type: application/json');
echo json_encode(['success' => true]);
}
private function handleUpload($field) {
if (!isset($_FILES[$field]) || $_FILES[$field]['error'] !== UPLOAD_ERR_OK) {
return null;
}
$uploadDir = 'assets/uploads/icons/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
$ext = pathinfo($_FILES[$field]['name'], PATHINFO_EXTENSION);
$fileName = uniqid() . '.' . $ext;
$targetPath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES[$field]['tmp_name'], $targetPath)) {
return $targetPath;
}
return null;
}
// Category Management
public function categories() {
$this->checkAuth();
$db = db_pdo();
$categories = $db->query("SELECT * FROM categories")->fetchAll();
$this->view('admin/categories/index', ['categories' => $categories]);
}
public function addCategory() {
$this->checkAuth();
$name = $_POST['name'];
$slug = $this->slugify($name);
$db = db_pdo();
$stmt = $db->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)");
$stmt->execute([$name, $slug]);
$this->redirect('/admin/categories');
}
public function deleteCategory($params) {
$this->checkAuth();
$db = db_pdo();
$stmt = $db->prepare("DELETE FROM categories WHERE id = ?");
$stmt->execute([$params['id']]);
$this->redirect('/admin/categories');
}
// Withdrawal Management
public function withdrawals() {
$this->checkAuth();
$db = db_pdo();
$withdrawals = $db->query("SELECT w.*, u.username FROM withdrawals w JOIN users u ON w.user_id = u.id ORDER BY w.created_at DESC")->fetchAll();
$this->view('admin/withdrawals/index', ['withdrawals' => $withdrawals]);
}
public function approveWithdrawal($params) {
$this->checkAuth();
$db = db_pdo();
$stmt = $db->prepare("UPDATE withdrawals SET status = 'approved' WHERE id = ?");
$stmt->execute([$params['id']]);
$this->redirect('/admin/withdrawals');
}
public function rejectWithdrawal($params) {
$this->checkAuth();
$db = db_pdo();
// Refund balance if rejected? The user didn't specify, but let's do it for fairness
$wd = $db->query("SELECT * FROM withdrawals WHERE id = " . $params['id'])->fetch();
if ($wd && $wd['status'] === 'pending') {
$stmt = $db->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
$stmt->execute([$wd['amount'], $wd['user_id']]);
$stmt = $db->prepare("UPDATE withdrawals SET status = 'rejected' WHERE id = ?");
$stmt->execute([$params['id']]);
}
$this->redirect('/admin/withdrawals');
}
public function deleteApk($params) {
$this->checkAuth();
$db = db_pdo();
$stmt = $db->prepare("DELETE FROM apks WHERE id = ?");
$stmt->execute([$params['id']]);
$this->redirect('/admin/apks');
}
private function slugify($text) {
$text = preg_replace('~[^\pL\d]+~u', '-', $text);
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
$text = preg_replace('~[^-\w]+~', '', $text);
$text = trim($text, '-');
$text = preg_replace('~-+~', '-', $text);
$text = strtolower($text);
return empty($text) ? 'n-a' : $text;
}
}

View File

@ -28,11 +28,26 @@ class ApkController extends Controller {
public function download($params) {
$apk = $this->apkService->getBySlug($params['slug']);
if ($apk) {
$this->apkService->incrementDownload($apk['id']);
// In a real app, this would be a link to a file or a CDN.
// For now, let's redirect to a mock download URL or back.
$this->redirect($apk['download_url'] === '#' ? '/apk/' . $apk['slug'] . '?downloaded=1' : $apk['download_url']);
if (!$apk) {
header("HTTP/1.0 404 Not Found");
echo "APK Not Found";
return;
}
// Increment download counter
$this->apkService->incrementDownload($apk['id']);
// Get the download URL
$downloadUrl = $apk['download_url'];
// If URL is empty or #, redirect back to detail with a message
if (empty($downloadUrl) || $downloadUrl === '#') {
$this->redirect('/apk/' . $apk['slug'] . '?error=no_url');
return;
}
// Redirect to the actual download link (External URL)
$this->redirect($downloadUrl);
}
}
}

View File

@ -0,0 +1,157 @@
<?php
namespace App\Controllers;
use App\Core\Controller;
class AuthController extends Controller {
public function loginForm() {
if (isset($_SESSION['user_id'])) {
$this->redirect('/profile');
}
$this->view('auth/login');
}
public function registerForm() {
if (isset($_SESSION['user_id'])) {
$this->redirect('/profile');
}
$ref = $_GET['ref'] ?? '';
$this->view('auth/register', ['ref' => $ref]);
}
public function login() {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$db = db_pdo();
$stmt = $db->prepare("SELECT * FROM users WHERE username = ? AND role = 'user'");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
$this->redirect('/profile');
} else {
$this->view('auth/login', ['error' => 'Invalid username or password']);
}
}
public function register() {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
$ref_code = $_POST['ref_code'] ?? '';
if ($password !== $confirm_password) {
$this->view('auth/register', ['error' => 'Passwords do not match', 'ref' => $ref_code]);
return;
}
$db = db_pdo();
// Check if username exists
$stmt = $db->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
$this->view('auth/register', ['error' => 'Username already exists', 'ref' => $ref_code]);
return;
}
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$referral_code = substr(md5(uniqid($username, true)), 0, 8);
$referred_by = null;
if (!empty($ref_code)) {
$stmt = $db->prepare("SELECT id FROM users WHERE referral_code = ?");
$stmt->execute([$ref_code]);
$referrer = $stmt->fetch();
if ($referrer) {
$referred_by = $referrer['id'];
}
}
$stmt = $db->prepare("INSERT INTO users (username, password, referral_code, referred_by, role, balance) VALUES (?, ?, ?, ?, 'user', 0)");
$stmt->execute([$username, $hashed_password, $referral_code, $referred_by]);
$userId = $db->lastInsertId();
if ($referred_by) {
// Reward referrer with points (not balance yet, as per previous logic)
$stmt = $db->prepare("UPDATE users SET points = points + 10, total_referrals = total_referrals + 1 WHERE id = ?");
$stmt->execute([$referred_by]);
}
$_SESSION['user_id'] = $userId;
$_SESSION['username'] = $username;
$_SESSION['role'] = 'user';
$this->redirect('/profile');
}
public function logout() {
session_destroy();
$this->redirect('/');
}
public function profile() {
if (!isset($_SESSION['user_id'])) {
$this->redirect('/login');
}
$db = db_pdo();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
$stmt = $db->prepare("SELECT * FROM withdrawals WHERE user_id = ? ORDER BY created_at DESC");
$stmt->execute([$user['id']]);
$withdrawals = $stmt->fetchAll();
$this->view('auth/profile', [
'user' => $user,
'withdrawals' => $withdrawals,
'success' => $_SESSION['success'] ?? null,
'error' => $_SESSION['error'] ?? null
]);
unset($_SESSION['success'], $_SESSION['error']);
}
public function requestWithdrawal() {
if (!isset($_SESSION['user_id'])) {
$this->redirect('/login');
}
$amount = (float)$_POST['amount'];
$method = $_POST['method'];
$details = $_POST['details'];
if ($amount < 10000) { // Minimum WD
$_SESSION['error'] = "Minimum withdrawal is Rp 10.000";
$this->redirect('/profile');
}
$db = db_pdo();
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$balance = $stmt->fetchColumn();
if ($balance < $amount) {
$_SESSION['error'] = "Insufficient balance";
$this->redirect('/profile');
}
// Deduct balance
$stmt = $db->prepare("UPDATE users SET balance = balance - ? WHERE id = ?");
$stmt->execute([$amount, $_SESSION['user_id']]);
// Create WD request
$stmt = $db->prepare("INSERT INTO withdrawals (user_id, amount, method, account_details, status) VALUES (?, ?, ?, ?, 'pending')");
$stmt->execute([$_SESSION['user_id'], $amount, $method, $details]);
$_SESSION['success'] = "Withdrawal request submitted successfully";
$this->redirect('/profile');
}
}

View File

@ -13,10 +13,93 @@ class HomeController extends Controller {
}
public function index() {
$apks = $this->apkService->getLatest(12);
$db = db_pdo();
$category = $_GET['category'] ?? null;
$sql = "SELECT * FROM apks WHERE status = 'published'";
$params = [];
if ($category) {
$sql .= " AND category_id = (SELECT id FROM categories WHERE slug = ?)";
$params[] = $category;
}
$sql .= " ORDER BY display_order ASC, created_at DESC LIMIT 12";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$apks = $stmt->fetchAll();
return $this->view('home', [
'apks' => $apks,
'title' => 'ApkNusa - Professional APK Download Portal'
]);
}
}
public function apkDetail($params) {
$slug = $params['slug'];
$db = db_pdo();
$stmt = $db->prepare("SELECT * FROM apks WHERE slug = ?");
$stmt->execute([$slug]);
$apk = $stmt->fetch();
if (!$apk) {
$this->redirect('/');
}
// Store referral code if present
if (isset($_GET['ref'])) {
$_SESSION['ref_download_' . $apk['id']] = $_GET['ref'];
}
$this->view('apk_detail', ['apk' => $apk]);
}
public function download($params) {
$slug = $params['slug'];
$db = db_pdo();
$stmt = $db->prepare("SELECT * FROM apks WHERE slug = ?");
$stmt->execute([$slug]);
$apk = $stmt->fetch();
if (!$apk) {
$this->redirect('/');
}
// Check for referral earnings
$ref_code = $_SESSION['ref_download_' . $apk['id']] ?? null;
if ($ref_code) {
$stmt = $db->prepare("SELECT id FROM users WHERE referral_code = ?");
$stmt->execute([$ref_code]);
$referrer = $stmt->fetch();
if ($referrer) {
$referrer_id = $referrer['id'];
$ip = $_SERVER['REMOTE_ADDR'];
// Check if this IP already earned for this APK today (prevent abuse)
$stmt = $db->prepare("SELECT id FROM referral_downloads WHERE referrer_id = ? AND apk_id = ? AND ip_address = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 DAY)");
$stmt->execute([$referrer_id, $apk['id'], $ip]);
if (!$stmt->fetch()) {
// Credit 500 IDR
$stmt = $db->prepare("UPDATE users SET balance = balance + 500 WHERE id = ?");
$stmt->execute([$referrer_id]);
// Log download
$stmt = $db->prepare("INSERT INTO referral_downloads (referrer_id, apk_id, ip_address, amount) VALUES (?, ?, ?, 500)");
$stmt->execute([$referrer_id, $apk['id'], $ip]);
}
}
// Clear session after processing
unset($_SESSION['ref_download_' . $apk['id']]);
}
// Increment total downloads
$stmt = $db->prepare("UPDATE apks SET total_downloads = total_downloads + 1 WHERE id = ?");
$stmt->execute([$apk['id']]);
// Redirect to actual file
$this->redirect($apk['download_url']);
}
}

View File

@ -3,36 +3,49 @@
namespace App\Core;
class Router {
protected $routes = [];
private $routes = [];
public function add($method, $path, $handler) {
$path = preg_replace('/\{([a-z]+)\}/', '(?P<\1>[^/]+)', $path);
public function get($path, $handler) {
$this->add('GET', $path, $handler);
}
public function post($path, $handler) {
$this->add('POST', $path, $handler);
}
private function add($method, $path, $handler) {
$path = preg_replace('/:([^\/]+)/', '(?P<$1>[^/]+)', $path);
$path = '#^' . $path . '$#';
$this->routes[] = [
'method' => strtoupper($method),
'path' => '#^' . $path . '$#',
'method' => $method,
'path' => $path,
'handler' => $handler
];
}
public function dispatch($method, $uri) {
$uri = parse_url($uri, PHP_URL_PATH);
public function dispatch() {
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
foreach ($this->routes as $route) {
if ($route['method'] === strtoupper($method) && preg_match($route['path'], $uri, $matches)) {
if ($route['method'] === $method && preg_match($route['path'], $uri, $matches)) {
$handler = $route['handler'];
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
if (is_array($handler)) {
[$controllerClass, $methodName] = $handler;
if (is_string($handler) && strpos($handler, '@') !== false) {
[$controllerName, $methodName] = explode('@', $handler);
$controllerClass = "App\\Controllers\\".$controllerName;
$controller = new $controllerClass();
return $controller->$methodName($params);
}
return $handler($params);
if (is_callable($handler)) {
return call_user_func_array($handler, [$params]);
}
}
}
header("HTTP/1.0 404 Not Found");
echo "404 Not Found";
}
}
}

16
app/Helpers/functions.php Normal file
View File

@ -0,0 +1,16 @@
<?php
use App\Services\LanguageService;
function lang($key) {
return LanguageService::translate($key);
}
function asset($path) {
return '/' . ltrim($path, '/');
}
function db_pdo() {
require_once __DIR__ . '/../../db/config.php';
return db();
}

View File

@ -24,6 +24,18 @@ class ApkService {
return $stmt->fetch();
}
public function getAllApks() {
$stmt = $this->db->prepare("SELECT * FROM apks ORDER BY created_at DESC");
$stmt->execute();
return $stmt->fetchAll();
}
public function getApkById($id) {
$stmt = $this->db->prepare("SELECT * FROM apks WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public function incrementDownload($apkId) {
$stmt = $this->db->prepare("UPDATE apks SET total_downloads = total_downloads + 1 WHERE id = :id");
$stmt->execute(['id' => $apkId]);
@ -34,4 +46,4 @@ class ApkService {
'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'
]);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Services;
class LanguageService {
public static function translate($key) {
// Mock translation
return $key;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Services;
class ThemeService {
public static function getCurrent() {
return $_COOKIE['theme'] ?? 'light';
}
}

View File

@ -0,0 +1,40 @@
-- Create categories table
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE
);
-- Add categories if not exist
INSERT IGNORE INTO categories (name, slug) VALUES ('Games', 'games'), ('Apps', 'apps'), ('Tools', 'tools');
-- Update apks table
ALTER TABLE apks ADD COLUMN icon_path VARCHAR(255) DEFAULT NULL;
ALTER TABLE apks ADD COLUMN display_order INT DEFAULT 0;
-- Update users table
ALTER TABLE users ADD COLUMN balance DECIMAL(15, 2) DEFAULT 0.00;
-- Create withdrawals table
CREATE TABLE IF NOT EXISTS withdrawals (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
amount DECIMAL(15, 2) NOT NULL,
method VARCHAR(50) NOT NULL,
account_details TEXT NOT NULL,
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Create referral_downloads table to track earnings per download
CREATE TABLE IF NOT EXISTS referral_downloads (
id INT AUTO_INCREMENT PRIMARY KEY,
referrer_id INT NOT NULL,
apk_id INT NOT NULL,
ip_address VARCHAR(45) NOT NULL,
amount DECIMAL(15, 2) DEFAULT 500.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (referrer_id) REFERENCES users(id),
FOREIGN KEY (apk_id) REFERENCES apks(id)
);

89
full_schema.sql Normal file
View File

@ -0,0 +1,89 @@
-- Full Schema for APK Portal
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
-- --------------------------------------------------------
-- Table structure for table `users`
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`referral_code` varchar(20) DEFAULT NULL,
`referred_by` int(11) DEFAULT NULL,
`role` varchar(20) DEFAULT 'user',
`points` int(11) DEFAULT 0,
`total_referrals` int(11) DEFAULT 0,
`balance` decimal(15,2) DEFAULT 0.00,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `referral_code` (`referral_code`),
KEY `referred_by` (`referred_by`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for table `categories`
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`slug` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for table `apks`
CREATE TABLE IF NOT EXISTS `apks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`version` varchar(50) DEFAULT NULL,
`description` text DEFAULT NULL,
`image_url` varchar(255) DEFAULT NULL,
`icon_path` varchar(255) DEFAULT NULL,
`download_url` varchar(255) DEFAULT NULL,
`category_id` int(11) DEFAULT NULL,
`total_downloads` int(11) DEFAULT 0,
`is_vip` tinyint(1) DEFAULT 0,
`status` enum('published','draft') DEFAULT 'published',
`display_order` int(11) DEFAULT 0,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for table `withdrawals`
CREATE TABLE IF NOT EXISTS `withdrawals` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`amount` decimal(15,2) NOT NULL,
`method` varchar(50) NOT NULL,
`account_details` text NOT NULL,
`status` enum('pending','approved','rejected') DEFAULT 'pending',
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `withdrawals_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Table structure for table `referral_downloads`
CREATE TABLE IF NOT EXISTS `referral_downloads` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`referrer_id` int(11) NOT NULL,
`apk_id` int(11) NOT NULL,
`ip_address` varchar(45) NOT NULL,
`amount` decimal(15,2) DEFAULT 500.00,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `referrer_id` (`referrer_id`),
KEY `apk_id` (`apk_id`),
CONSTRAINT `referral_downloads_ibfk_1` FOREIGN KEY (`referrer_id`) REFERENCES `users` (`id`),
CONSTRAINT `referral_downloads_ibfk_2` FOREIGN KEY (`apk_id`) REFERENCES `apks` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default Data
INSERT IGNORE INTO `users` (`username`, `password`, `role`) VALUES ('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin'); -- password: admin123
INSERT IGNORE INTO `categories` (`name`, `slug`) VALUES ('Games', 'games'), ('Apps', 'apps'), ('Tools', 'tools');
COMMIT;

View File

@ -1,32 +1,55 @@
<?php
spl_autoload_register(function ($class) {
$prefix = 'App\\';
$base_dir = __DIR__ . '/app/';
require_once 'app/Core/Router.php';
require_once 'app/Core/Controller.php';
require_once 'app/Helpers/functions.php';
require_once 'db/config.php';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
session_start();
use App\Core\Router;
$router = new Router();
// Routes
$router->add('GET', '/', ['App\Controllers\HomeController', 'index']);
$router->add('GET', '/apk/{slug}', ['App\Controllers\ApkController', 'detail']);
$router->add('GET', '/apk/{slug}/download', ['App\Controllers\ApkController', 'download']);
// Home & APKs
$router->get('/', 'HomeController@index');
$router->get('/apk/:slug', 'HomeController@apkDetail');
$router->get('/download/:slug', 'HomeController@download');
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// Auth
$router->get('/login', 'AuthController@loginForm');
$router->post('/login', 'AuthController@login');
$router->get('/register', 'AuthController@registerForm');
$router->post('/register', 'AuthController@register');
$router->get('/logout', 'AuthController@logout');
$router->get('/profile', 'AuthController@profile');
$router->post('/withdraw', 'AuthController@requestWithdrawal');
$router->dispatch($method, $uri);
// Admin Auth
$router->get('/admin/login', 'AdminController@loginForm');
$router->post('/admin/login', 'AdminController@login');
$router->get('/admin/logout', 'AdminController@logout');
// Admin Dashboard
$router->get('/admin/dashboard', 'AdminController@dashboard');
// Admin APKs
$router->get('/admin/apks', 'AdminController@apks');
$router->get('/admin/apks/add', 'AdminController@addApkForm');
$router->post('/admin/apks/add', 'AdminController@addApk');
$router->get('/admin/apks/edit/:id', 'AdminController@editApkForm');
$router->post('/admin/apks/edit/:id', 'AdminController@editApk');
$router->get('/admin/apks/delete/:id', 'AdminController@deleteApk');
$router->post('/admin/apks/reorder', 'AdminController@updateOrder');
// Admin Categories
$router->get('/admin/categories', 'AdminController@categories');
$router->post('/admin/categories/add', 'AdminController@addCategory');
$router->get('/admin/categories/delete/:id', 'AdminController@deleteCategory');
// Admin Withdrawals
$router->get('/admin/withdrawals', 'AdminController@withdrawals');
$router->get('/admin/withdrawals/approve/:id', 'AdminController@approveWithdrawal');
$router->get('/admin/withdrawals/reject/:id', 'AdminController@rejectWithdrawal');
$router->dispatch();

84
views/admin/apks/form.php Normal file
View File

@ -0,0 +1,84 @@
<?php include __DIR__ . '/../header.php'; ?>
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg border-0 rounded-4">
<div class="card-header bg-white py-3">
<h5 class="m-0 fw-bold"><?php echo $action === 'add' ? 'Add New APK' : 'Edit APK: ' . $apk['title']; ?></h5>
</div>
<div class="card-body p-4">
<form action="<?php echo $action === 'add' ? '/admin/apks/add' : '/admin/apks/edit/' . $apk['id']; ?>" method="POST" enctype="multipart/form-data">
<div class="mb-3">
<label for="title" class="form-label fw-medium">APK Title</label>
<input type="text" class="form-control" id="title" name="title" value="<?php echo $apk['title'] ?? ''; ?>" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="version" class="form-label fw-medium">Version</label>
<input type="text" class="form-control" id="version" name="version" value="<?php echo $apk['version'] ?? ''; ?>" required>
</div>
<div class="col-md-6 mb-3">
<label for="category_id" class="form-label fw-medium">Category</label>
<select class="form-select" id="category_id" name="category_id">
<option value="">Uncategorized</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo $cat['id']; ?>" <?php echo (isset($apk['category_id']) && $apk['category_id'] == $cat['id']) ? 'selected' : ''; ?>><?php echo $cat['name']; ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label fw-medium">Description</label>
<textarea class="form-control" id="description" name="description" rows="4" required><?php echo $apk['description'] ?? ''; ?></textarea>
</div>
<div class="mb-4">
<label class="form-label fw-medium">APK Icon</label>
<div class="d-flex align-items-center mb-2">
<?php if (isset($apk['icon_path'])): ?>
<img src="/<?php echo $apk['icon_path']; ?>" class="rounded me-3 border" width="60" height="60">
<?php endif; ?>
<input type="file" class="form-control" id="icon_file" name="icon_file" accept="image/*">
</div>
<div class="mb-3">
<label for="image_url" class="form-label fw-medium">External Icon URL (Optional)</label>
<input type="url" class="form-control" id="image_url" name="image_url" value="<?php echo $apk['image_url'] ?? ''; ?>" placeholder="https://example.com/icon.png">
</div>
</div>
<div class="mb-3">
<label for="download_url" class="form-label fw-medium">Download URL</label>
<input type="url" class="form-control" id="download_url" name="download_url" value="<?php echo $apk['download_url'] ?? ''; ?>" required>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="status" class="form-label fw-medium">Status</label>
<select class="form-select" id="status" name="status">
<option value="published" <?php echo (isset($apk['status']) && $apk['status'] === 'published') ? 'selected' : ''; ?>>Published</option>
<option value="draft" <?php echo (isset($apk['status']) && $apk['status'] === 'draft') ? 'selected' : ''; ?>>Draft</option>
</select>
</div>
<div class="col-md-6 d-flex align-items-center mt-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_vip" name="is_vip" <?php echo (isset($apk['is_vip']) && $apk['is_vip']) ? 'checked' : ''; ?>>
<label class="form-check-label" for="is_vip">VIP APK</label>
</div>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="/admin/apks" class="btn btn-light px-4">Cancel</a>
<button type="submit" class="btn btn-primary px-5">Save APK</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/../footer.php'; ?>

116
views/admin/apks/index.php Normal file
View File

@ -0,0 +1,116 @@
<?php include __DIR__ . '/../header.php'; ?>
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-gray-800">Manage APKs</h1>
<a href="/admin/apks/add" class="btn btn-primary shadow-sm">
<i class="bi bi-plus-lg me-1"></i> Add New APK
</a>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">APK List (Drag to Reorder)</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover" id="apkTable">
<thead class="table-light">
<tr>
<th width="50"></th>
<th>Icon</th>
<th>Title</th>
<th>Version</th>
<th>Category</th>
<th>Downloads</th>
<th>Status</th>
<th>VIP</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="sortableList">
<?php foreach ($apks as $apk): ?>
<tr data-id="<?php echo $apk['id']; ?>">
<td class="align-middle text-center cursor-move">
<i class="bi bi-grip-vertical text-muted fs-5"></i>
</td>
<td class="align-middle">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<img src="<?php echo $icon; ?>" class="rounded" width="40" height="40" alt="">
</td>
<td class="align-middle fw-bold"><?php echo $apk['title']; ?></td>
<td class="align-middle"><?php echo $apk['version']; ?></td>
<td class="align-middle">
<?php
$db = db_pdo();
$cat = $db->query("SELECT name FROM categories WHERE id = " . ($apk['category_id'] ?: 0))->fetchColumn();
echo $cat ?: 'Uncategorized';
?>
</td>
<td class="align-middle"><?php echo number_format($apk['total_downloads']); ?></td>
<td class="align-middle">
<span class="badge bg-<?php echo $apk['status'] === 'published' ? 'success' : 'secondary'; ?>">
<?php echo ucfirst($apk['status']); ?>
</span>
</td>
<td class="align-middle">
<?php if ($apk['is_vip']): ?>
<span class="badge bg-warning text-dark"><i class="bi bi-star-fill"></i> VIP</span>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<td class="align-middle">
<div class="btn-group btn-group-sm">
<a href="/admin/apks/edit/<?php echo $apk['id']; ?>" class="btn btn-outline-primary">
<i class="bi bi-pencil"></i>
</a>
<a href="/admin/apks/delete/<?php echo $apk['id']; ?>" class="btn btn-outline-danger" onclick="return confirm('Are you sure?')">
<i class="bi bi-trash"></i>
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
const el = document.getElementById('sortableList');
const sortable = Sortable.create(el, {
animation: 150,
handle: '.cursor-move',
onEnd: function (evt) {
const rows = el.querySelectorAll('tr');
const order = Array.from(rows).map(row => row.dataset.id);
fetch('/admin/apks/reorder', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'order[]=' + order.join('&order[]=')
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Order updated');
}
});
}
});
</script>
<style>
.cursor-move { cursor: move; }
.sortable-ghost { opacity: 0.4; background-color: #f8f9fa; }
</style>
<?php include __DIR__ . '/../footer.php'; ?>

View File

@ -0,0 +1,60 @@
<?php include __DIR__ . '/../header.php'; ?>
<div class="container py-4">
<div class="row">
<div class="col-md-5 mb-4">
<div class="card shadow border-0 rounded-4">
<div class="card-header bg-white py-3">
<h5 class="m-0 fw-bold">Add New Category</h5>
</div>
<div class="card-body p-4">
<form action="/admin/categories/add" method="POST">
<div class="mb-3">
<label for="name" class="form-label fw-medium">Category Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="e.g. Games, Social Media" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary rounded-pill py-2">Create Category</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-7">
<div class="card shadow border-0 rounded-4">
<div class="card-header bg-white py-3">
<h5 class="m-0 fw-bold">All Categories</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="ps-4">Name</th>
<th>Slug</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $cat): ?>
<tr>
<td class="ps-4 align-middle fw-medium"><?php echo $cat['name']; ?></td>
<td class="align-middle text-muted"><?php echo $cat['slug']; ?></td>
<td class="text-end pe-4 align-middle">
<a href="/admin/categories/delete/<?php echo $cat['id']; ?>" class="btn btn-outline-danger btn-sm rounded-pill px-3" onclick="return confirm('Are you sure you want to delete this category?')">
<i class="bi bi-trash"></i> Delete
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/../footer.php'; ?>

145
views/admin/dashboard.php Normal file
View File

@ -0,0 +1,145 @@
<?php include __DIR__ . '/header.php'; ?>
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0 text-gray-800">Dashboard Overview</h1>
<div class="d-flex gap-2">
<a href="/admin/apks/add" class="btn btn-primary shadow-sm rounded-pill px-4">
<i class="bi bi-plus-lg me-1"></i> Add APK
</a>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-xl-3 col-md-6">
<div class="card border-0 shadow-sm rounded-4 h-100 py-2 border-start border-primary border-4">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Total APKs</div>
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($total_apks); ?></div>
</div>
<div class="col-auto">
<i class="bi bi-android2 fs-1 text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card border-0 shadow-sm rounded-4 h-100 py-2 border-start border-success border-4">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Total Downloads</div>
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($total_downloads); ?></div>
</div>
<div class="col-auto">
<i class="bi bi-download fs-1 text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card border-0 shadow-sm rounded-4 h-100 py-2 border-start border-info border-4">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Total Users</div>
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($total_users); ?></div>
</div>
<div class="col-auto">
<i class="bi bi-people fs-1 text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card border-0 shadow-sm rounded-4 h-100 py-2 border-start border-warning border-4">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Pending Withdrawals</div>
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($pending_withdrawals); ?></div>
</div>
<div class="col-auto">
<i class="bi bi-wallet2 fs-1 text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-header bg-white py-3">
<h6 class="m-0 font-weight-bold text-primary">Recent APKs</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Title</th>
<th>Version</th>
<th>Downloads</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($recent_apks as $apk): ?>
<tr>
<td>
<div class="fw-bold text-gray-800"><?php echo $apk['title']; ?></div>
<small class="text-muted"><?php echo $apk['slug']; ?></small>
</td>
<td>v<?php echo $apk['version']; ?></td>
<td><?php echo number_format($apk['total_downloads']); ?></td>
<td>
<span class="badge bg-<?php echo $apk['status'] === 'published' ? 'success' : 'secondary'; ?>">
<?php echo ucfirst($apk['status']); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow border-0 rounded-4">
<div class="card-header bg-white py-3">
<h6 class="m-0 font-weight-bold text-primary">Quick Navigation</h6>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a href="/admin/apks" class="list-group-item list-group-item-action py-3 px-4">
<i class="bi bi-android2 me-2 text-primary"></i> Manage APKs
</a>
<a href="/admin/categories" class="list-group-item list-group-item-action py-3 px-4">
<i class="bi bi-tags me-2 text-info"></i> Categories
</a>
<a href="/admin/withdrawals" class="list-group-item list-group-item-action py-3 px-4">
<i class="bi bi-cash-stack me-2 text-success"></i> Withdrawal Requests
<?php if ($pending_withdrawals > 0): ?>
<span class="badge bg-danger rounded-pill float-end"><?php echo $pending_withdrawals; ?></span>
<?php endif; ?>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/footer.php'; ?>

12
views/admin/footer.php Normal file
View File

@ -0,0 +1,12 @@
<footer class="sticky-footer bg-white mt-auto py-4">
<div class="container my-auto">
<div class="copyright text-center my-auto">
<span class="text-muted small">Copyright &copy; APK Portal Admin 2026</span>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
</body>
</html>

74
views/admin/header.php Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - APK Portal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #4e73df;
--success-color: #1cc88a;
}
body {
background-color: #f8f9fc;
font-family: 'Nunito', sans-serif;
}
.navbar-admin {
background-color: #fff;
box-shadow: 0 .15rem 1.75rem 0 rgba(58,59,69,.15);
}
.sidebar {
min-height: calc(100vh - 56px);
background-color: #4e73df;
background-image: linear-gradient(180deg,#4e73df 10%,#224abe 100%);
background-size: cover;
}
.nav-link {
color: rgba(255,255,255,.8);
padding: 1rem 1.5rem;
border-bottom: 1px solid rgba(255,255,255,.05);
}
.nav-link:hover {
color: #fff;
background-color: rgba(255,255,255,.1);
}
.nav-link.active {
color: #fff;
font-weight: 700;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-admin sticky-top py-3">
<div class="container-fluid px-4">
<a class="navbar-brand fw-bold text-primary" href="/admin/dashboard">
<i class="bi bi-shield-lock-fill me-2"></i>APK ADMIN
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link text-dark px-3" href="/admin/dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark px-3" href="/admin/apks">APKs</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark px-3" href="/admin/categories">Categories</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark px-3" href="/admin/withdrawals">Withdrawals</a>
</li>
</ul>
<div class="d-flex align-items-center">
<span class="text-muted me-3">Welcome, <b><?php echo $_SESSION['username'] ?? 'Admin'; ?></b></span>
<a href="/" class="btn btn-outline-secondary btn-sm me-2" target="_blank">View Site</a>
<a href="/admin/logout" class="btn btn-danger btn-sm">Logout</a>
</div>
</div>
</div>
</nav>

53
views/admin/login.php Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login - ApkNusa</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
background: white;
}
.btn-primary {
background-color: #a4c639; /* Android Green */
border-color: #a4c639;
}
.btn-primary:hover {
background-color: #8fb132;
border-color: #8fb132;
}
</style>
</head>
<body>
<div class="login-card">
<h2 class="text-center mb-4">ApkNusa Admin</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif; ?>
<form action="/admin/login" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,72 @@
<?php include __DIR__ . '/../header.php'; ?>
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4 px-3">
<h1 class="h3 mb-0 text-gray-800 fw-bold">Withdrawal Requests</h1>
</div>
<div class="card shadow border-0 rounded-4 mx-3">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">User</th>
<th>Amount (IDR)</th>
<th>Method</th>
<th>Details</th>
<th>Date</th>
<th>Status</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($withdrawals)): ?>
<tr>
<td colspan="7" class="text-center py-5 text-muted">No withdrawal requests found.</td>
</tr>
<?php endif; ?>
<?php foreach ($withdrawals as $wd): ?>
<tr>
<td class="ps-4">
<div class="fw-bold"><?php echo $wd['username']; ?></div>
</td>
<td>
<span class="text-success fw-bold">Rp <?php echo number_format($wd['amount'], 0, ',', '.'); ?></span>
</td>
<td><span class="badge bg-info text-dark"><?php echo $wd['method']; ?></span></td>
<td><small class="text-muted"><?php echo nl2br($wd['account_details']); ?></small></td>
<td><?php echo date('d M Y, H:i', strtotime($wd['created_at'])); ?></td>
<td>
<?php
$statusClass = 'secondary';
if ($wd['status'] === 'pending') $statusClass = 'warning';
if ($wd['status'] === 'approved') $statusClass = 'success';
if ($wd['status'] === 'rejected') $statusClass = 'danger';
?>
<span class="badge bg-<?php echo $statusClass; ?>">
<?php echo ucfirst($wd['status']); ?>
</span>
</td>
<td class="text-end pe-4">
<?php if ($wd['status'] === 'pending'): ?>
<div class="btn-group btn-group-sm">
<a href="/admin/withdrawals/approve/<?php echo $wd['id']; ?>" class="btn btn-success" onclick="return confirm('Approve this withdrawal?')">
Approve
</a>
<a href="/admin/withdrawals/reject/<?php echo $wd['id']; ?>" class="btn btn-danger" onclick="return confirm('Reject this withdrawal? Balance will be refunded.')">
Reject
</a>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/../footer.php'; ?>

View File

@ -4,7 +4,11 @@
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="/" class="text-success text-decoration-none">Home</a></li>
<li class="breadcrumb-item"><a href="#" class="text-success text-decoration-none">Apps</a></li>
<?php
$db = db_pdo();
$catName = $db->query("SELECT name FROM categories WHERE id = " . ($apk['category_id'] ?: 0))->fetchColumn();
?>
<li class="breadcrumb-item"><a href="/?category=<?php echo strtolower($catName); ?>" class="text-success text-decoration-none"><?php echo $catName ?: 'Apps'; ?></a></li>
<li class="breadcrumb-item active" aria-current="page"><?php echo $apk['title']; ?></li>
</ol>
</nav>
@ -13,7 +17,10 @@
<div class="col-lg-8">
<div class="bg-white p-5 rounded-4 border-0 shadow-sm mb-5">
<div class="d-flex flex-column flex-md-row align-items-center align-items-md-start mb-5">
<img src="<?php echo $apk['image_url']; ?>" class="rounded-4 me-md-5 mb-4 mb-md-0" width="160" height="160" alt="<?php echo $apk['title']; ?>">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<img src="<?php echo $icon; ?>" class="rounded-4 me-md-5 mb-4 mb-md-0 shadow-sm" width="160" height="160" alt="<?php echo $apk['title']; ?>" style="object-fit: cover;">
<div class="text-center text-md-start">
<h1 class="display-5 fw-bold mb-2"><?php echo $apk['title']; ?> <span class="badge bg-light text-dark fs-6 fw-normal align-middle">v<?php echo $apk['version']; ?></span></h1>
<p class="lead text-muted mb-4">Official and original version. Verified safe for Android device.</p>
@ -25,10 +32,11 @@
<span class="badge bg-warning-subtle text-warning px-3 py-2 rounded-pill fw-medium"><i class="bi bi-star-fill me-1"></i> VIP</span>
<?php endif; ?>
</div>
<a href="/apk/<?php echo $apk['slug']; ?>/download" class="btn btn-success btn-lg px-5 rounded-pill shadow-sm py-3 fw-bold w-100 w-md-auto mb-3">
<i class="bi bi-download me-2"></i> Download APK Now
<a href="/download/<?php echo $apk['slug']; ?>" class="btn btn-success btn-lg px-5 rounded-pill shadow-sm py-3 fw-bold w-100 w-md-auto mb-3">
<i class="bi bi-download me-2"></i> Download Now
</a>
<p class="text-muted small">File size: ~45MB (approx.) | Android 6.0 or higher</p>
<p class="text-muted small">By clicking Download, you agree to our terms of service.</p>
</div>
</div>
@ -64,67 +72,48 @@
<div class="text-center p-4 rounded-4 bg-success bg-opacity-10">
<h6 class="fw-bold mb-3">Is this safe to download?</h6>
<p class="text-muted small mb-0">Yes, every APK on ApkNusa is scanned for malware and verified to ensure it is the original, unmodified file from the developer.</p>
<p class="text-muted small mb-0">Yes, every app on ApkNusa is scanned and verified to ensure it is original and safe from the official developers.</p>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="position-sticky" style="top: 2rem;">
<div class="bg-white p-4 rounded-4 border-0 shadow-sm mb-4">
<h5 class="fw-bold mb-4">Popular Similar Apps</h5>
<div class="vstack gap-4">
<div class="d-flex align-items-center gap-3">
<img src="https://img.icons8.com/color/144/facebook-new.png" class="rounded-3" width="48" height="48" alt="Facebook">
<div>
<h6 class="fw-bold mb-0">Facebook</h6>
<span class="text-muted small">Social Media</span>
</div>
<a href="#" class="btn btn-light btn-sm ms-auto rounded-pill px-3 fw-medium">View</a>
</div>
<div class="d-flex align-items-center gap-3">
<img src="https://img.icons8.com/color/144/tiktok.png" class="rounded-3" width="48" height="48" alt="TikTok">
<div>
<h6 class="fw-bold mb-0">TikTok</h6>
<span class="text-muted small">Entertainment</span>
</div>
<a href="#" class="btn btn-light btn-sm ms-auto rounded-pill px-3 fw-medium">View</a>
</div>
<div class="d-flex align-items-center gap-3">
<img src="https://img.icons8.com/color/144/youtube-play.png" class="rounded-3" width="48" height="48" alt="YouTube">
<div>
<h6 class="fw-bold mb-0">YouTube</h6>
<span class="text-muted small">Video Player</span>
</div>
<a href="#" class="btn btn-light btn-sm ms-auto rounded-pill px-3 fw-medium">View</a>
</div>
<div class="bg-white p-4 rounded-4 border-0 shadow-sm mb-4 text-center py-5">
<h5 class="fw-bold mb-3">Share & Earn</h5>
<p class="text-muted small mb-4">Share this link and earn <b>Rp 500</b> for every download!</p>
<div class="input-group mb-3">
<?php
$ref = isset($_SESSION['user_id']) ? $db->query("SELECT referral_code FROM users WHERE id = ".$_SESSION['user_id'])->fetchColumn() : '';
$shareLink = 'http://'.$_SERVER['HTTP_HOST'].'/apk/'.$apk['slug'].($ref ? '?ref='.$ref : '');
?>
<input type="text" class="form-control form-control-sm bg-light" id="shareLink" value="<?php echo $shareLink; ?>" readonly>
<button class="btn btn-outline-success btn-sm" type="button" onclick="copyShareLink()">Copy</button>
</div>
<hr class="my-4 opacity-10">
<a href="#" class="btn btn-outline-success w-100 rounded-pill fw-medium">See all Apps</a>
<?php if (!$ref): ?>
<a href="/login" class="small text-success text-decoration-none">Login to earn money</a>
<?php endif; ?>
</div>
<div class="bg-success text-white p-4 rounded-4 border-0 shadow-sm text-center">
<i class="bi bi-trophy display-4 mb-3"></i>
<h5 class="fw-bold mb-3">Join our community</h5>
<p class="small text-white-50 mb-4">Register today to enjoy premium features, earn rewards, and track your download history.</p>
<a href="/register" class="btn btn-white text-success fw-bold w-100 rounded-pill py-2">Join Now</a>
<div class="bg-dark text-white p-4 rounded-4 border-0 shadow-sm text-center">
<i class="bi bi-trophy display-4 mb-3 text-success"></i>
<h5 class="fw-bold mb-3">Referral Program</h5>
<p class="small text-white-50 mb-4">Join our community, share APKs, and get paid directly to your e-wallet or bank account.</p>
<a href="/register" class="btn btn-success fw-bold w-100 rounded-pill py-2">Get Started</a>
</div>
</div>
</div>
</div>
</div>
<?php if (isset($_GET['downloaded'])): ?>
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
<div id="liveToast" class="toast show bg-success text-white" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<i class="bi bi-check-circle-fill me-2"></i> Download started successfully!
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<?php endif; ?>
<script>
function copyShareLink() {
var copyText = document.getElementById("shareLink");
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
alert("Share link copied! Send this to your friends to earn money.");
}
</script>
<?php include 'footer.php'; ?>

33
views/auth/login.php Normal file
View File

@ -0,0 +1,33 @@
<?php require_once __DIR__ . '/../header.php'; ?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-5">
<div class="card shadow-sm border-0 rounded-4 p-4">
<h2 class="text-center mb-4">Login to ApkNusa</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif; ?>
<form action="/login" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control rounded-3" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control rounded-3" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100 rounded-3 py-2">Login</button>
</form>
<div class="text-center mt-4">
<p class="mb-0">Don't have an account? <a href="/register" class="text-decoration-none text-primary">Register here</a></p>
</div>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../footer.php'; ?>

165
views/auth/profile.php Normal file
View File

@ -0,0 +1,165 @@
<?php include __DIR__ . '/../header.php'; ?>
<div class="container py-5">
<div class="row">
<div class="col-lg-4">
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-body text-center p-5">
<div class="bg-success text-white rounded-circle d-inline-flex align-items-center justify-content-center mb-4" style="width: 80px; height: 80px;">
<i class="bi bi-person-fill fs-1"></i>
</div>
<h3 class="fw-bold mb-0"><?php echo $user['username']; ?></h3>
<p class="text-muted">Member since <?php echo date('M Y', strtotime($user['created_at'])); ?></p>
<hr>
<div class="row g-0">
<div class="col-6 border-end">
<h4 class="fw-bold text-success mb-0"><?php echo $user['points']; ?></h4>
<small class="text-muted text-uppercase">Points</small>
</div>
<div class="col-6">
<h4 class="fw-bold text-primary mb-0"><?php echo $user['total_referrals']; ?></h4>
<small class="text-muted text-uppercase">Referrals</small>
</div>
</div>
</div>
</div>
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-body p-4 text-center bg-light">
<h6 class="text-uppercase text-muted fw-bold mb-2">Current Balance</h6>
<h2 class="fw-bold text-success mb-3">Rp <?php echo number_format($user['balance'], 0, ',', '.'); ?></h2>
<button class="btn btn-success btn-lg px-5 rounded-pill" data-bs-toggle="modal" data-bs-target="#withdrawModal">
<i class="bi bi-wallet2 me-2"></i> Withdraw
</button>
<p class="small text-muted mt-3 mb-0">Min. withdraw: Rp 10.000</p>
</div>
</div>
</div>
<div class="col-lg-8">
<?php if (isset($success)): ?>
<div class="alert alert-success border-0 rounded-4 mb-4"><?php echo $success; ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger border-0 rounded-4 mb-4"><?php echo $error; ?></div>
<?php endif; ?>
<div class="card shadow border-0 rounded-4 mb-4">
<div class="card-header bg-white py-3">
<h5 class="m-0 fw-bold">My Referral Code</h5>
</div>
<div class="card-body p-4">
<p>Share your referral link to earn <b>Rp 500</b> for every download.</p>
<div class="input-group mb-3">
<input type="text" class="form-control bg-light" id="refLink" value="<?php echo 'http://' . $_SERVER['HTTP_HOST'] . '/register?ref=' . $user['referral_code']; ?>" readonly>
<button class="btn btn-outline-success" type="button" onclick="copyText('refLink')">Copy Link</button>
</div>
<div class="small text-muted">Example APK referral link:</div>
<div class="input-group">
<input type="text" class="form-control bg-light" value="<?php echo 'http://' . $_SERVER['HTTP_HOST'] . '/apk/example-slug?ref=' . $user['referral_code']; ?>" readonly>
</div>
</div>
</div>
<div class="card shadow border-0 rounded-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="m-0 fw-bold">Withdrawal History</h5>
<span class="badge bg-light text-dark">Recent activities</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Date</th>
<th>Amount</th>
<th>Method</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (empty($withdrawals)): ?>
<tr>
<td colspan="4" class="text-center py-5 text-muted">No withdrawal history yet.</td>
</tr>
<?php endif; ?>
<?php foreach ($withdrawals as $wd): ?>
<tr>
<td class="ps-4"><?php echo date('d M Y, H:i', strtotime($wd['created_at'])); ?></td>
<td class="fw-bold text-success">Rp <?php echo number_format($wd['amount'], 0, ',', '.'); ?></td>
<td><?php echo $wd['method']; ?></td>
<td>
<?php
$statusClass = 'secondary';
if ($wd['status'] === 'pending') $statusClass = 'warning';
if ($wd['status'] === 'approved') $statusClass = 'success';
if ($wd['status'] === 'rejected') $statusClass = 'danger';
?>
<span class="badge bg-<?php echo $statusClass; ?>">
<?php echo ucfirst($wd['status']); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Withdraw Modal -->
<div class="modal fade" id="withdrawModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-4 overflow-hidden">
<div class="modal-header bg-success text-white py-4 border-0">
<h5 class="modal-title fw-bold">Request Withdrawal</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="/withdraw" method="POST">
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Amount to Withdraw (IDR)</label>
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="number" class="form-control" name="amount" min="10000" max="<?php echo (int)$user['balance']; ?>" step="1000" placeholder="Min 10.000" required>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Payment Method</label>
<select class="form-select" name="method" required>
<option value="">Select method...</option>
<option value="DANA">DANA</option>
<option value="OVO">OVO</option>
<option value="GOPAY">GoPay</option>
<option value="ShopeePay">ShopeePay</option>
<option value="BANK">Bank Transfer</option>
</select>
</div>
<div class="mb-0">
<label class="form-label fw-bold">Account Details</label>
<textarea class="form-control" name="details" rows="3" placeholder="Enter phone number or bank account number with name" required></textarea>
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light px-4 rounded-pill" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success px-4 rounded-pill">Submit Request</button>
</div>
</form>
</div>
</div>
</div>
<script>
function copyText(id) {
var copyText = document.getElementById(id);
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
alert("Referral link copied to clipboard!");
}
</script>
<?php include __DIR__ . '/../footer.php'; ?>

43
views/auth/register.php Normal file
View File

@ -0,0 +1,43 @@
<?php require_once __DIR__ . '/../header.php'; ?>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm border-0 rounded-4 p-4">
<h2 class="text-center mb-4">Register for ApkNusa</h2>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif; ?>
<form action="/register" method="POST">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control rounded-3" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control rounded-3" id="password" name="password" required>
</div>
<div class="mb-3">
<label for="confirm_password" class="form-label">Confirm Password</label>
<input type="password" class="form-control rounded-3" id="confirm_password" name="confirm_password" required>
</div>
<div class="mb-4">
<label for="ref_code" class="form-label">Referral Code (Optional)</label>
<input type="text" class="form-control rounded-3" id="ref_code" name="ref_code" value="<?= $ref ?? '' ?>" placeholder="e.g. abcdef12">
</div>
<button type="submit" class="btn btn-primary w-100 rounded-3 py-2">Create Account</button>
</form>
<div class="text-center mt-4">
<p class="mb-0">Already have an account? <a href="/login" class="text-decoration-none text-primary">Login here</a></p>
</div>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../footer.php'; ?>

View File

@ -13,12 +13,12 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom py-3">
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom py-3 sticky-top">
<div class="container">
<a class="navbar-brand fw-bold text-success" href="/">
<i class="bi bi-robot"></i> ApkNusa
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
@ -32,17 +32,23 @@
<li class="nav-item">
<a class="nav-link px-3" href="#">Apps</a>
</li>
<li class="nav-item">
<a class="nav-link px-3" href="#">Categories</a>
</li>
<li class="nav-item ms-lg-3">
<a class="btn btn-outline-dark rounded-pill px-4" href="/login">Login</a>
</li>
<li class="nav-item ms-lg-2">
<a class="btn btn-success rounded-pill px-4" href="/register">Join Free</a>
</li>
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item ms-lg-3">
<a class="btn btn-outline-success rounded-pill px-4 d-flex align-items-center" href="/profile">
<i class="bi bi-person-circle me-2"></i> Profile
</a>
</li>
<?php else: ?>
<li class="nav-item ms-lg-3">
<a class="btn btn-outline-dark rounded-pill px-4" href="/login">Login</a>
</li>
<li class="nav-item ms-lg-2">
<a class="btn btn-success rounded-pill px-4" href="/register">Join Free</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>
<main class="py-5">
<main class="min-vh-100">

View File

@ -6,8 +6,8 @@
<h1 class="display-4 fw-bold mb-3">Download the Best <span class="text-success">Android APKs</span> Professionally</h1>
<p class="lead text-muted mb-4">Fast, safe, and secure downloads for your favorite mobile apps and games. No registration required to browse.</p>
<div class="d-flex gap-2">
<a href="#" class="btn btn-success btn-lg px-4 rounded-pill">Explore Apps</a>
<a href="#" class="btn btn-outline-dark btn-lg px-4 rounded-pill">Top Games</a>
<a href="#latest" class="btn btn-success btn-lg px-4 rounded-pill">Explore Apps</a>
<a href="/register" class="btn btn-outline-dark btn-lg px-4 rounded-pill">Join Referral</a>
</div>
</div>
<div class="col-lg-5 d-none d-lg-block">
@ -18,20 +18,36 @@
</div>
</div>
<section class="mb-5">
<section id="latest" class="mb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">Latest Releases</h2>
<a href="#" class="text-success text-decoration-none fw-medium">View All <i class="bi bi-arrow-right"></i></a>
<div class="dropdown">
<button class="btn btn-light dropdown-toggle rounded-pill" type="button" data-bs-toggle="dropdown">
Categories
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/">All</a></li>
<?php
$db = db();
$categories = $db->query("SELECT * FROM categories")->fetchAll();
foreach ($categories as $cat): ?>
<li><a class="dropdown-item" href="/?category=<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="row g-4">
<?php foreach ($apks as $apk): ?>
<div class="col-md-6 col-lg-4 col-xl-3">
<div class="col-12 col-md-6 col-lg-4">
<div class="card h-100 border-0 shadow-sm rounded-4 hover-lift">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-3">
<img src="<?php echo $apk['image_url']; ?>" class="rounded-3 me-3" width="60" height="60" alt="<?php echo $apk['title']; ?>">
<?php
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
?>
<img src="<?php echo $icon; ?>" class="rounded-3 me-3" width="60" height="60" alt="<?php echo $apk['title']; ?>" style="object-fit: cover;">
<div>
<h5 class="card-title fw-bold mb-0 text-truncate" style="max-width: 150px;"><?php echo $apk['title']; ?></h5>
<h5 class="card-title fw-bold mb-0 text-truncate" style="max-width: 180px;"><?php echo $apk['title']; ?></h5>
<span class="badge bg-light text-dark fw-normal">v<?php echo $apk['version']; ?></span>
<?php if ($apk['is_vip']): ?>
<span class="badge bg-warning text-dark ms-1"><i class="bi bi-star-fill"></i> VIP</span>
@ -41,7 +57,7 @@
<p class="card-text text-muted small mb-4 line-clamp-2"><?php echo $apk['description']; ?></p>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted small"><i class="bi bi-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?></span>
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-light rounded-pill px-3 btn-sm fw-medium">Details</a>
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-success rounded-pill px-4 btn-sm fw-medium">Details</a>
</div>
</div>
</div>
@ -54,7 +70,7 @@
<div class="row align-items-center text-center text-lg-start">
<div class="col-lg-8">
<h2 class="fw-bold mb-3">Start your referral journey today</h2>
<p class="mb-0 text-white-50">Join our community, share your favorite APKs, and earn reward points for every successful referral download.</p>
<p class="mb-0 text-white-50">Earn <b>Rp 500</b> for every download via your link. Join our community and share your favorite APKs.</p>
</div>
<div class="col-lg-4 text-center text-lg-end mt-4 mt-lg-0">
<a href="/register" class="btn btn-success btn-lg px-5 rounded-pill">Get Started</a>
@ -63,4 +79,4 @@
</div>
</div>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>