Aslam vbru
This commit is contained in:
parent
a887a75aed
commit
4f61082b27
239
app/Controllers/AdminController.php
Normal file
239
app/Controllers/AdminController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
157
app/Controllers/AuthController.php
Normal file
157
app/Controllers/AuthController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@ -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']);
|
||||
}
|
||||
}
|
||||
@ -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
16
app/Helpers/functions.php
Normal 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();
|
||||
}
|
||||
@ -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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/Services/LanguageService.php
Normal file
10
app/Services/LanguageService.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class LanguageService {
|
||||
public static function translate($key) {
|
||||
// Mock translation
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
9
app/Services/ThemeService.php
Normal file
9
app/Services/ThemeService.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class ThemeService {
|
||||
public static function getCurrent() {
|
||||
return $_COOKIE['theme'] ?? 'light';
|
||||
}
|
||||
}
|
||||
40
db/migrations/20260224_update_schema.sql
Normal file
40
db/migrations/20260224_update_schema.sql
Normal 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
89
full_schema.sql
Normal 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;
|
||||
67
index.php
67
index.php
@ -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
84
views/admin/apks/form.php
Normal 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
116
views/admin/apks/index.php
Normal 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'; ?>
|
||||
60
views/admin/categories/index.php
Normal file
60
views/admin/categories/index.php
Normal 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
145
views/admin/dashboard.php
Normal 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
12
views/admin/footer.php
Normal 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 © 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
74
views/admin/header.php
Normal 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
53
views/admin/login.php
Normal 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>
|
||||
72
views/admin/withdrawals/index.php
Normal file
72
views/admin/withdrawals/index.php
Normal 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'; ?>
|
||||
@ -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
33
views/auth/login.php
Normal 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
165
views/auth/profile.php
Normal 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
43
views/auth/register.php
Normal 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'; ?>
|
||||
@ -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">
|
||||
@ -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'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user