Autosave: 20260225-222606
This commit is contained in:
parent
3d02f25bbd
commit
b67ab46d1e
@ -200,15 +200,26 @@ class AuthController extends Controller {
|
||||
|
||||
public function requestWithdrawal() {
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
if (is_ajax()) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
$this->redirect('/login');
|
||||
}
|
||||
|
||||
$amount = (float)$_POST['amount'];
|
||||
$method = $_POST['method'];
|
||||
$details = $_POST['details'];
|
||||
$amount = (float)($_POST['amount'] ?? 0);
|
||||
$method = $_POST['method'] ?? '';
|
||||
$details = $_POST['details'] ?? '';
|
||||
|
||||
if ($amount < 10000) { // Minimum WD
|
||||
$_SESSION['error'] = __('error_min_withdraw');
|
||||
$error = __('error_min_withdraw');
|
||||
if (is_ajax()) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => $error]);
|
||||
exit;
|
||||
}
|
||||
$_SESSION['error'] = $error;
|
||||
$this->redirect('/profile');
|
||||
}
|
||||
|
||||
@ -218,7 +229,13 @@ class AuthController extends Controller {
|
||||
$balance = $stmt->fetchColumn();
|
||||
|
||||
if ($balance < $amount) {
|
||||
$_SESSION['error'] = __('error_insufficient_balance');
|
||||
$error = __('error_insufficient_balance');
|
||||
if (is_ajax()) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => $error]);
|
||||
exit;
|
||||
}
|
||||
$_SESSION['error'] = $error;
|
||||
$this->redirect('/profile');
|
||||
}
|
||||
|
||||
@ -230,7 +247,14 @@ class AuthController extends Controller {
|
||||
$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'] = __('success_withdraw_submitted');
|
||||
$success = __('success_withdraw_submitted');
|
||||
if (is_ajax()) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => $success, 'new_balance' => $balance - $amount]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['success'] = $success;
|
||||
$this->redirect('/profile');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,9 +37,43 @@ class ContactController extends Controller {
|
||||
$_SESSION['success'] = 'Your message has been sent successfully!';
|
||||
} else {
|
||||
$_SESSION['error'] = 'Failed to send message. Please try again later.';
|
||||
// Log error if needed: error_log($res['error']);
|
||||
}
|
||||
|
||||
$this->redirect('/contact');
|
||||
}
|
||||
}
|
||||
|
||||
public function ajaxReport() {
|
||||
if (!is_ajax()) {
|
||||
$this->redirect('/');
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$email = $_POST['email'] ?? '';
|
||||
$subject = $_POST['subject'] ?? 'App Report';
|
||||
$message = $_POST['message'] ?? '';
|
||||
$apk_name = $_POST['apk_name'] ?? 'Unknown App';
|
||||
|
||||
if (empty($email) || empty($message)) {
|
||||
echo json_encode(['error' => 'Email and message are required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['error' => 'Invalid email address.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../../mail/MailService.php';
|
||||
|
||||
$full_message = "Report for App: $apk_name\n\nUser Email: $email\n\nMessage:\n$message";
|
||||
$res = \MailService::sendContactMessage('System Report', $email, $full_message, null, $subject);
|
||||
|
||||
if (!empty($res['success'])) {
|
||||
echo json_encode(['success' => 'Report submitted successfully!']);
|
||||
} else {
|
||||
echo json_encode(['error' => 'Failed to submit report.']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@ -75,3 +75,7 @@ function get_client_ip() {
|
||||
|
||||
return $ipaddress;
|
||||
}
|
||||
|
||||
function is_ajax() {
|
||||
return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
|
||||
}
|
||||
@ -362,8 +362,234 @@ body.animated-bg {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Mobile Bottom Navigation */
|
||||
.mobile-bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 65px;
|
||||
background: var(--card-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
|
||||
z-index: 1040;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.mobile-nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
color: var(--muted-text);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mobile-nav-item i {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mobile-nav-item.active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.mobile-nav-item.active i {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Sticky Download Button for Mobile */
|
||||
.sticky-download-bar {
|
||||
position: fixed;
|
||||
bottom: 65px; /* Above mobile-bottom-nav */
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
padding: 0.75rem 1rem;
|
||||
box-shadow: 0 -5px 15px rgba(0,0,0,0.05);
|
||||
z-index: 1030;
|
||||
border-top: 1px solid var(--border-color);
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.sticky-download-bar.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Search Overlay */
|
||||
.search-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-color);
|
||||
z-index: 1100;
|
||||
display: none;
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.search-overlay.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.search-overlay .btn-close-search {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-color);
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Share FAB */
|
||||
.share-fab {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
right: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);
|
||||
z-index: 1020;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.share-fab:hover {
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* WhatsApp FAB */
|
||||
.whatsapp-fab {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #25D366;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 15px rgba(37, 211, 102, 0.4);
|
||||
z-index: 1020;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.whatsapp-fab:hover {
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Back to Top */
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 140px;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
z-index: 1010;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.back-to-top.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Category Chips Scroll */
|
||||
.category-scroll .btn {
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.category-scroll .btn-light {
|
||||
background: var(--subtle-bg);
|
||||
color: var(--muted-text);
|
||||
}
|
||||
|
||||
.category-scroll .btn-light:hover {
|
||||
background: var(--border-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Featured Scroll Hover */
|
||||
.featured-scroll .card:hover {
|
||||
background: var(--subtle-bg);
|
||||
}
|
||||
|
||||
/* Hide desktop elements on mobile and vice-versa */
|
||||
@media (min-width: 992px) {
|
||||
.mobile-bottom-nav, .sticky-download-bar, .search-overlay, .share-fab, .whatsapp-fab {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
body {
|
||||
padding-bottom: 70px; /* Space for bottom nav */
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
/* Improve card layout on mobile for 3 columns */
|
||||
.card-body {
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 0.75rem !important;
|
||||
margin-bottom: 0.25rem !important;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 0.2rem 0.4rem !important;
|
||||
}
|
||||
|
||||
#ai-chat-wrapper {
|
||||
bottom: 75px !important;
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const html = document.documentElement;
|
||||
|
||||
const updateIcons = (theme) => {
|
||||
// Update all theme toggle icons
|
||||
const icons = document.querySelectorAll('#theme-toggle i, #theme-toggle-mobile i');
|
||||
icons.forEach(icon => {
|
||||
if (theme === 'dark') {
|
||||
@ -25,7 +24,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Update all theme status texts
|
||||
const textLabels = document.querySelectorAll('.theme-status-text');
|
||||
textLabels.forEach(label => {
|
||||
label.textContent = theme === 'dark' ? 'Dark Mode' : 'Light Mode';
|
||||
@ -40,71 +38,140 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const currentTheme = html.getAttribute('data-theme') || 'light';
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
// Update UI
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
|
||||
// Update All Icons and Labels
|
||||
updateIcons(newTheme);
|
||||
|
||||
// Save preference
|
||||
document.cookie = `theme=${newTheme}; path=/; max-age=${365 * 24 * 60 * 60}`;
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
};
|
||||
|
||||
// Unified AJAX Content Loader
|
||||
const loadContent = (url, updateUrl = true) => {
|
||||
const gridContainer = document.getElementById('apk-grid-container');
|
||||
if (!gridContainer) return;
|
||||
|
||||
gridContainer.style.opacity = '0.5';
|
||||
gridContainer.style.pointerEvents = 'none';
|
||||
|
||||
fetch(url, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
gridContainer.innerHTML = data;
|
||||
gridContainer.style.opacity = '1';
|
||||
gridContainer.style.pointerEvents = 'all';
|
||||
|
||||
if (updateUrl) {
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
|
||||
// Scroll to grid top on mobile if needed
|
||||
if (window.innerWidth < 992) {
|
||||
const latestSection = document.getElementById('latest');
|
||||
if (latestSection) {
|
||||
window.scrollTo({
|
||||
top: latestSection.offsetTop - 100,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Fetch error:', err);
|
||||
gridContainer.style.opacity = '1';
|
||||
gridContainer.style.pointerEvents = 'all';
|
||||
});
|
||||
};
|
||||
|
||||
// AJAX Category Filtering
|
||||
const initCategoryAjax = () => {
|
||||
const filters = document.querySelectorAll('.category-filter');
|
||||
const gridContainer = document.getElementById('apk-grid-container');
|
||||
const dropdownBtn = document.getElementById('category-dropdown-btn');
|
||||
const latestTitle = document.getElementById('latest-title');
|
||||
document.addEventListener('click', (e) => {
|
||||
const filter = e.target.closest('.category-filter, .ajax-cat-link');
|
||||
if (!filter) return;
|
||||
|
||||
if (!gridContainer || filters.length === 0) return;
|
||||
e.preventDefault();
|
||||
const url = filter.getAttribute('href');
|
||||
const categoryName = filter.textContent.trim();
|
||||
const dropdownBtn = document.getElementById('category-dropdown-btn');
|
||||
|
||||
filters.forEach(filter => {
|
||||
filter.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const category = filter.getAttribute('data-category');
|
||||
const url = filter.getAttribute('href');
|
||||
const categoryName = filter.textContent;
|
||||
if (dropdownBtn && filter.classList.contains('category-filter')) {
|
||||
dropdownBtn.innerHTML = `${categoryName} <i class="bi bi-chevron-down ms-1 small"></i>`;
|
||||
}
|
||||
|
||||
// Update UI state
|
||||
gridContainer.style.opacity = '0.5';
|
||||
gridContainer.style.pointerEvents = 'none';
|
||||
|
||||
// Fetch data
|
||||
fetch(url, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
gridContainer.innerHTML = data;
|
||||
gridContainer.style.opacity = '1';
|
||||
gridContainer.style.pointerEvents = 'all';
|
||||
|
||||
// Update dropdown button text
|
||||
if (dropdownBtn) {
|
||||
dropdownBtn.innerHTML = `${categoryName} <i class="bi bi-chevron-down ms-1 small"></i>`;
|
||||
}
|
||||
|
||||
// Update URL without refreshing
|
||||
window.history.pushState({ category: category }, '', url);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Fetch error:', err);
|
||||
gridContainer.style.opacity = '1';
|
||||
gridContainer.style.pointerEvents = 'all';
|
||||
// Update active state for chips if they are chips
|
||||
if (filter.classList.contains('ajax-cat-link')) {
|
||||
document.querySelectorAll('.ajax-cat-link').forEach(btn => {
|
||||
btn.classList.remove('btn-success');
|
||||
btn.classList.add('btn-light');
|
||||
});
|
||||
});
|
||||
});
|
||||
filter.classList.remove('btn-light');
|
||||
filter.classList.add('btn-success');
|
||||
}
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', (e) => {
|
||||
window.location.reload(); // Simple solution for now
|
||||
loadContent(url);
|
||||
|
||||
// Close search overlay if open
|
||||
const searchOverlay = document.getElementById('search-overlay');
|
||||
if (searchOverlay) searchOverlay.classList.remove('active');
|
||||
});
|
||||
};
|
||||
|
||||
// AJAX Search
|
||||
const initSearchAjax = () => {
|
||||
const searchForm = document.getElementById('ajax-search-form');
|
||||
if (!searchForm) return;
|
||||
|
||||
searchForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(searchForm);
|
||||
const query = formData.get('search');
|
||||
const url = `/?search=${encodeURIComponent(query)}`;
|
||||
|
||||
loadContent(url);
|
||||
|
||||
const searchOverlay = document.getElementById('search-overlay');
|
||||
if (searchOverlay) searchOverlay.classList.remove('active');
|
||||
});
|
||||
};
|
||||
|
||||
// Newsletter AJAX
|
||||
const initNewsletterAjax = () => {
|
||||
const btn = document.getElementById('newsletter-btn');
|
||||
const emailInput = document.getElementById('newsletter-email');
|
||||
const msg = document.getElementById('newsletter-msg');
|
||||
|
||||
if (!btn || !emailInput) return;
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const email = emailInput.value;
|
||||
if (!email) return;
|
||||
|
||||
btn.disabled = true;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||
|
||||
fetch('/api/newsletter/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: email })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalText;
|
||||
if (data.success) {
|
||||
msg.innerHTML = `<span class="text-success">${data.success}</span>`;
|
||||
emailInput.value = '';
|
||||
} else {
|
||||
msg.innerHTML = `<span class="text-danger">${data.error}</span>`;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalText;
|
||||
msg.innerHTML = '<span class="text-danger">An error occurred.</span>';
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -137,11 +204,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const content = document.createElement('div');
|
||||
content.className = (isUser ? 'bg-success text-white' : 'bg-white') + ' p-3 rounded-4 shadow-sm small';
|
||||
content.style.maxWidth = '85%';
|
||||
if (isUser) {
|
||||
content.style.borderBottomRightRadius = '0';
|
||||
} else {
|
||||
content.style.borderBottomLeftRadius = '0';
|
||||
}
|
||||
content.style.borderBottomRightRadius = isUser ? '0' : 'inherit';
|
||||
content.style.borderBottomLeftRadius = isUser ? 'inherit' : '0';
|
||||
content.textContent = message;
|
||||
|
||||
div.appendChild(content);
|
||||
@ -156,7 +220,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
appendMessage(message, true);
|
||||
chatInput.value = '';
|
||||
|
||||
// Loading state
|
||||
const loadingDiv = document.createElement('div');
|
||||
loadingDiv.className = 'mb-3 d-flex';
|
||||
loadingDiv.innerHTML = '<div class="bg-white p-3 rounded-4 shadow-sm small" style="border-bottom-left-radius: 0 !important;"><div class="spinner-border spinner-border-sm text-success" role="status"></div> Thinking...</div>';
|
||||
@ -165,9 +228,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
fetch('/api/ai/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: message })
|
||||
})
|
||||
.then(response => response.json())
|
||||
@ -180,7 +241,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
messagesContainer.removeChild(loadingDiv);
|
||||
if (messagesContainer.contains(loadingDiv)) messagesContainer.removeChild(loadingDiv);
|
||||
appendMessage('Error connecting to AI assistant.');
|
||||
console.error(err);
|
||||
});
|
||||
@ -192,24 +253,60 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Mobile Overlays & Utils
|
||||
const initMobileUtils = () => {
|
||||
const searchTrigger = document.getElementById('mobile-search-trigger');
|
||||
const searchOverlay = document.getElementById('search-overlay');
|
||||
const closeSearch = document.getElementById('close-search-overlay');
|
||||
|
||||
if (searchTrigger && searchOverlay) {
|
||||
searchTrigger.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
searchOverlay.classList.add('active');
|
||||
const input = searchOverlay.querySelector('input');
|
||||
if (input) setTimeout(() => input.focus(), 300);
|
||||
});
|
||||
}
|
||||
|
||||
if (closeSearch && searchOverlay) {
|
||||
closeSearch.addEventListener('click', () => {
|
||||
searchOverlay.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Back to Top
|
||||
const backToTop = document.getElementById('back-to-top');
|
||||
if (backToTop) {
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.pageYOffset > 300) {
|
||||
backToTop.classList.add('show');
|
||||
} else {
|
||||
backToTop.classList.remove('show');
|
||||
}
|
||||
});
|
||||
backToTop.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initial Sync
|
||||
const currentTheme = html.getAttribute('data-theme') || 'light';
|
||||
updateIcons(currentTheme);
|
||||
|
||||
// Sync theme from localStorage if cookie is missing but localStorage has it
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const currentCookie = document.cookie.split('; ').find(row => row.startsWith('theme='))?.split('=')[1];
|
||||
|
||||
if (savedTheme && !currentCookie) {
|
||||
html.setAttribute('data-theme', savedTheme);
|
||||
updateIcons(savedTheme);
|
||||
document.cookie = `theme=${savedTheme}; path=/; max-age=${365 * 24 * 60 * 60}`;
|
||||
}
|
||||
|
||||
initThemeToggle('theme-toggle');
|
||||
initThemeToggle('theme-toggle-mobile');
|
||||
initCategoryAjax();
|
||||
initSearchAjax();
|
||||
initNewsletterAjax();
|
||||
initAIChat();
|
||||
initMobileUtils();
|
||||
|
||||
console.log('ApkNusa ready.');
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
loadContent(window.location.href, false);
|
||||
});
|
||||
|
||||
console.log('ApkNusa AJAX Engine active.');
|
||||
});
|
||||
@ -40,6 +40,7 @@ if (get_setting('maintenance_mode') === '1') {
|
||||
|
||||
$router = new Router();
|
||||
$router->post('/api/newsletter/subscribe', 'NewsletterController@subscribe');
|
||||
$router->post('/api/report', 'ContactController@ajaxReport');
|
||||
$router->post('/api/ai/chat', 'AIController@chat');
|
||||
|
||||
// Sitemap
|
||||
|
||||
@ -2,14 +2,15 @@
|
||||
|
||||
<div class="container py-3">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<nav aria-label="breadcrumb" class="mb-4 d-none d-md-block">
|
||||
<ol class="breadcrumb mb-0 small">
|
||||
<li class="breadcrumb-item"><a href="/" class="text-success text-decoration-none"><?php echo __('home'); ?></a></li>
|
||||
<?php
|
||||
$db = db_pdo();
|
||||
$catName = $db->query("SELECT name FROM categories WHERE id = " . ($apk['category_id'] ?: 0))->fetchColumn();
|
||||
$catSlug = $db->query("SELECT slug 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"><a href="/?category=<?php echo $catSlug; ?>" class="text-success text-decoration-none"><?php echo $catName ?: 'Apps'; ?></a></li>
|
||||
<li class="breadcrumb-item active text-muted" aria-current="page"><?php echo $apk['title']; ?></li>
|
||||
</ol>
|
||||
</nav>
|
||||
@ -17,15 +18,15 @@
|
||||
<div class="row g-4">
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-8">
|
||||
<div class="bg-white p-4 p-md-5 rounded-4 shadow-sm mb-4">
|
||||
<div class="bg-white p-3 p-md-5 rounded-4 shadow-sm mb-4">
|
||||
<!-- Header Section -->
|
||||
<div class="d-flex align-items-start mb-4">
|
||||
<?php
|
||||
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
|
||||
?>
|
||||
<img src="<?php echo $icon; ?>" class="rounded-4 me-3 me-md-4 shadow-sm" width="100" height="100" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; min-width: 100px;">
|
||||
<img src="<?php echo $icon; ?>" class="rounded-4 me-3 me-md-4 shadow-sm" width="80" height="80" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; min-width: 80px;">
|
||||
<div>
|
||||
<h1 class="h2 fw-bold mb-1 d-flex align-items-center flex-wrap">
|
||||
<h1 class="h4 fw-bold mb-1 d-flex align-items-center flex-wrap">
|
||||
<?php echo $apk['title']; ?>
|
||||
<span class="badge bg-light text-muted fw-normal ms-2 fs-6">v<?php echo $apk['version']; ?></span>
|
||||
</h1>
|
||||
@ -33,20 +34,17 @@
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mb-0">
|
||||
<span class="badge bg-success-subtle text-success border border-success-subtle px-2 py-1 fw-medium rounded">
|
||||
<i class="bi bi-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?> <?php echo __('downloads'); ?>
|
||||
<i class="bi bi-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?>
|
||||
</span>
|
||||
<span class="badge bg-info-subtle text-info border border-info-subtle px-2 py-1 fw-medium rounded">
|
||||
<i class="bi bi-shield-check me-1"></i> <?php echo __('verified_safe'); ?>
|
||||
<i class="bi bi-shield-check me-1"></i> <?php echo __('verified'); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2 text-muted small">
|
||||
<i class="bi bi-calendar3 me-1"></i> <?php echo date('M d, Y', strtotime($apk['created_at'])); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="mb-4">
|
||||
<div class="mb-4" id="main-download-btn-area">
|
||||
<a href="/download/<?php echo $apk['slug']; ?>" target="_blank" class="btn btn-success btn-lg w-100 py-3 rounded-pill fw-bold shadow-sm mb-2">
|
||||
<i class="bi bi-download me-2"></i> <?php echo __('download_now'); ?>
|
||||
</a>
|
||||
@ -107,6 +105,30 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Apps Section -->
|
||||
<?php
|
||||
$relatedApks = $db->query("SELECT * FROM apks WHERE category_id = " . ($apk['category_id'] ?: 0) . " AND id != " . $apk['id'] . " LIMIT 6")->fetchAll();
|
||||
if ($relatedApks):
|
||||
?>
|
||||
<div class="mb-5">
|
||||
<h4 class="fw-bold h5 mb-3 d-flex align-items-center">
|
||||
<i class="bi bi-grid-fill text-success me-2"></i> Similar Apps
|
||||
</h4>
|
||||
<div class="row g-2">
|
||||
<?php foreach ($relatedApks as $rapk): ?>
|
||||
<div class="col-4 col-md-4">
|
||||
<a href="/apk/<?php echo $rapk['slug']; ?>" class="text-decoration-none">
|
||||
<div class="card border-0 shadow-sm rounded-4 text-center p-2 h-100 hover-lift">
|
||||
<img src="<?php echo !empty($rapk['icon_path']) ? '/'.$rapk['icon_path'] : $rapk['image_url']; ?>" class="rounded-3 mx-auto mb-2 shadow-sm" width="48" height="48" style="object-fit: cover;">
|
||||
<h6 class="card-title fw-bold mb-0 text-truncate small" style="font-size: 0.7rem;"><?php echo $rapk['title']; ?></h6>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar / Additional Info -->
|
||||
@ -128,7 +150,23 @@
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark text-white p-4 rounded-4 shadow-sm text-center border-0">
|
||||
<!-- Report / Request Section -->
|
||||
<div class="bg-light p-4 rounded-4 mb-4 border-0">
|
||||
<h6 class="fw-bold mb-3 d-flex align-items-center">
|
||||
<i class="bi bi-flag-fill text-danger me-2"></i> Support & Issues
|
||||
</h6>
|
||||
<p class="text-muted small mb-3">Found a problem with this app or want to request a newer version?</p>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" class="btn btn-outline-danger btn-sm rounded-pill" data-bs-toggle="modal" data-bs-target="#reportModal" onclick="setReportType('Report Issue')">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i> Report Issue
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-dark btn-sm rounded-pill" data-bs-toggle="modal" data-bs-target="#reportModal" onclick="setReportType('Request Update')">
|
||||
<i class="bi bi-arrow-repeat me-1"></i> Request Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark text-white p-4 rounded-4 shadow-sm text-center border-0 d-none d-lg-block">
|
||||
<div class="bg-success bg-opacity-10 rounded-circle d-inline-flex p-3 mb-3">
|
||||
<i class="bi bi-trophy text-success h4 mb-0"></i>
|
||||
</div>
|
||||
@ -141,6 +179,56 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report/Request Modal -->
|
||||
<div class="modal fade" id="reportModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 rounded-4 overflow-hidden">
|
||||
<div class="modal-header bg-dark text-white py-4 border-0">
|
||||
<h5 class="modal-title fw-bold" id="reportModalLabel">Report Issue</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="report-form">
|
||||
<input type="hidden" name="apk_name" value="<?php echo htmlspecialchars($apk['title']); ?>">
|
||||
<input type="hidden" name="subject" id="report-subject" value="Report Issue">
|
||||
<div class="modal-body p-4">
|
||||
<div id="report-alert"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Your Email</label>
|
||||
<input type="email" class="form-control" name="email" placeholder="email@example.com" required>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label fw-bold">Message / Details</label>
|
||||
<textarea class="form-control" name="message" rows="4" placeholder="Please describe the issue or your request..." 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" id="report-submit-btn">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sticky Download Bar (Mobile Only) -->
|
||||
<div class="sticky-download-bar" id="sticky-download-bar">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="<?php echo $icon; ?>" class="rounded-2 me-3" width="40" height="40" alt="<?php echo $apk['title']; ?>">
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<h6 class="fw-bold mb-0 text-truncate" style="font-size: 0.9rem;"><?php echo $apk['title']; ?></h6>
|
||||
<span class="x-small text-muted" style="font-size: 0.7rem;">v<?php echo $apk['version']; ?></span>
|
||||
</div>
|
||||
<a href="/download/<?php echo $apk['slug']; ?>" class="btn btn-success btn-sm rounded-pill px-3 fw-bold">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share FAB (Mobile Only) -->
|
||||
<a href="#" class="share-fab" id="mobile-share-btn">
|
||||
<i class="bi bi-share-fill"></i>
|
||||
</a>
|
||||
|
||||
<script>
|
||||
function copyShareLink() {
|
||||
var copyText = document.getElementById("shareLink");
|
||||
@ -148,16 +236,86 @@ function copyShareLink() {
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
// Simple toast or alert replacement
|
||||
const btn = event.target;
|
||||
const originalText = btn.innerText;
|
||||
btn.innerText = "<?php echo __('copy'); ?>ed!";
|
||||
btn.innerText = "Copied!";
|
||||
btn.classList.replace('btn-success', 'btn-dark');
|
||||
setTimeout(() => {
|
||||
btn.innerText = originalText;
|
||||
btn.classList.replace('btn-dark', 'btn-success');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function setReportType(type) {
|
||||
document.getElementById('reportModalLabel').innerText = type;
|
||||
document.getElementById('report-subject').value = type + ': <?php echo addslashes($apk['title']); ?>';
|
||||
}
|
||||
|
||||
document.getElementById('report-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('report-submit-btn');
|
||||
const alertBox = document.getElementById('report-alert');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Sending...';
|
||||
|
||||
const formData = new FormData(this);
|
||||
|
||||
fetch('/api/report', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
body: formData
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = 'Send';
|
||||
if (data.success) {
|
||||
alertBox.innerHTML = '<div class="alert alert-success border-0 small">' + data.success + '</div>';
|
||||
setTimeout(() => {
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('reportModal'));
|
||||
modal.hide();
|
||||
alertBox.innerHTML = '';
|
||||
this.reset();
|
||||
}, 2000);
|
||||
} else {
|
||||
alertBox.innerHTML = '<div class="alert alert-danger border-0 small">' + data.error + '</div>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = 'Send';
|
||||
alertBox.innerHTML = '<div class="alert alert-danger border-0 small">An error occurred.</div>';
|
||||
});
|
||||
});
|
||||
|
||||
// Sticky Bar Logic
|
||||
window.addEventListener('scroll', function() {
|
||||
const mainBtn = document.getElementById('main-download-btn-area');
|
||||
const stickyBar = document.getElementById('sticky-download-bar');
|
||||
if (mainBtn && stickyBar) {
|
||||
const rect = mainBtn.getBoundingClientRect();
|
||||
if (rect.bottom < 0) {
|
||||
stickyBar.classList.add('show');
|
||||
} else {
|
||||
stickyBar.classList.remove('show');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Native Web Share API
|
||||
document.getElementById('mobile-share-btn').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: '<?php echo $apk['title']; ?>',
|
||||
text: 'Check out this app on ApkNusa!',
|
||||
url: '<?php echo $shareLink; ?>',
|
||||
}).catch((error) => console.log('Error sharing', error));
|
||||
} else {
|
||||
copyShareLink();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<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"><?php echo __('balance'); ?></h6>
|
||||
<h2 class="fw-bold text-success mb-3">Rp <?php echo number_format($user['balance'], 0, ',', '.'); ?></h2>
|
||||
<h2 class="fw-bold text-success mb-3" id="user-balance">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="fas fa-wallet me-2"></i> <?php echo __('withdraw'); ?>
|
||||
</button>
|
||||
@ -37,12 +37,14 @@
|
||||
</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 id="alert-container">
|
||||
<?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>
|
||||
|
||||
<div class="card shadow border-0 rounded-4 mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
@ -68,7 +70,7 @@
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="withdrawal-table">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4"><?php echo __('date'); ?></th>
|
||||
@ -79,7 +81,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($withdrawals)): ?>
|
||||
<tr>
|
||||
<tr id="no-history-row">
|
||||
<td colspan="4" class="text-center py-5 text-muted"><?php echo __('no_history'); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
@ -118,8 +120,9 @@
|
||||
<h5 class="modal-title fw-bold"><?php echo __('request_withdrawal'); ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="/withdraw" method="POST">
|
||||
<form id="withdraw-form" action="/withdraw" method="POST">
|
||||
<div class="modal-body p-4">
|
||||
<div id="modal-alert"></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><?php echo __('amount_to_withdraw'); ?></label>
|
||||
<div class="input-group">
|
||||
@ -145,7 +148,7 @@
|
||||
</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"><?php echo __('cancel'); ?></button>
|
||||
<button type="submit" class="btn btn-success px-4 rounded-pill"><?php echo __('submit_request'); ?></button>
|
||||
<button type="submit" class="btn btn-success px-4 rounded-pill" id="withdraw-submit-btn"><?php echo __('submit_request'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -160,6 +163,74 @@ function copyText(id) {
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
alert("<?php echo __('ref_copy_success_js'); ?>");
|
||||
}
|
||||
|
||||
document.getElementById('withdraw-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const form = this;
|
||||
const btn = document.getElementById('withdraw-submit-btn');
|
||||
const modalAlert = document.getElementById('modal-alert');
|
||||
const alertContainer = document.getElementById('alert-container');
|
||||
const balanceEl = document.getElementById('user-balance');
|
||||
const tableBody = document.querySelector('#withdrawal-table tbody');
|
||||
const noHistoryRow = document.getElementById('no-history-row');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Processing...';
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
fetch('/withdraw', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<?php echo __('submit_request'); ?>';
|
||||
|
||||
if (data.success) {
|
||||
// Update balance
|
||||
if (data.new_balance !== undefined) {
|
||||
balanceEl.textContent = 'Rp ' + data.new_balance.toLocaleString('id-ID');
|
||||
}
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('withdrawModal'));
|
||||
modal.hide();
|
||||
|
||||
// Show success message on main page
|
||||
alertContainer.innerHTML = '<div class="alert alert-success border-0 rounded-4 mb-4">' + data.success + '</div>';
|
||||
|
||||
// Add to table (simplified, just reload or prepend)
|
||||
// For now, let's just prepend a row if we can
|
||||
if (noHistoryRow) noHistoryRow.remove();
|
||||
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleDateString('id-ID', { day: '2-digit', month: 'short', year: 'numeric' }) + ', ' +
|
||||
now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
const newRow = `<tr>
|
||||
<td class="ps-4">${dateStr}</td>
|
||||
<td class="fw-bold text-success">Rp ${parseInt(formData.get('amount')).toLocaleString('id-ID')}</td>
|
||||
<td>${formData.get('method')}</td>
|
||||
<td><span class="badge bg-warning">Pending</span></td>
|
||||
</tr>`;
|
||||
|
||||
tableBody.insertAdjacentHTML('afterbegin', newRow);
|
||||
form.reset();
|
||||
} else {
|
||||
modalAlert.innerHTML = '<div class="alert alert-danger border-0 small">' + data.error + '</div>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<?php echo __('submit_request'); ?>';
|
||||
modalAlert.innerHTML = '<div class="alert alert-danger border-0 small">An error occurred. Please try again.</div>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include __DIR__ . '/../footer.php'; ?>
|
||||
<?php include __DIR__ . '/../footer.php'; ?>
|
||||
@ -74,6 +74,70 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Mobile Bottom Navigation -->
|
||||
<nav class="mobile-bottom-nav">
|
||||
<a href="/" class="mobile-nav-item <?php echo $_SERVER['REQUEST_URI'] == '/' ? 'active' : ''; ?>">
|
||||
<i class="bi bi-house-door"></i>
|
||||
<span><?php echo __('home'); ?></span>
|
||||
</a>
|
||||
<a href="#" class="mobile-nav-item" id="mobile-search-trigger">
|
||||
<i class="bi bi-search"></i>
|
||||
<span><?php echo __('search'); ?></span>
|
||||
</a>
|
||||
<a href="/blog" class="mobile-nav-item <?php echo strpos($_SERVER['REQUEST_URI'], '/blog') !== false ? 'active' : ''; ?>">
|
||||
<i class="bi bi-newspaper"></i>
|
||||
<span>Blog</span>
|
||||
</a>
|
||||
<a href="<?php echo isset($_SESSION['user_id']) ? '/profile' : '/login'; ?>" class="mobile-nav-item <?php echo (strpos($_SERVER['REQUEST_URI'], '/profile') !== false || strpos($_SERVER['REQUEST_URI'], '/login') !== false) ? 'active' : ''; ?>">
|
||||
<i class="bi bi-person"></i>
|
||||
<span><?php echo __('profile'); ?></span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- WhatsApp FAB (Mobile) -->
|
||||
<?php if ($wa = get_setting('whatsapp_url')): ?>
|
||||
<a href="<?php echo $wa; ?>" target="_blank" class="whatsapp-fab">
|
||||
<i class="bi bi-whatsapp fs-3"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Back to Top -->
|
||||
<a href="#" class="back-to-top" id="back-to-top">
|
||||
<i class="bi bi-arrow-up"></i>
|
||||
</a>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<div class="search-overlay" id="search-overlay">
|
||||
<button class="btn-close-search" id="close-search-overlay">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
<div class="mt-5">
|
||||
<h4 class="fw-bold mb-4">Search APKs</h4>
|
||||
<form action="/" method="GET" id="ajax-search-form">
|
||||
<div class="input-group input-group-lg border rounded-pill overflow-hidden shadow-sm">
|
||||
<span class="input-group-text bg-white border-0 ps-3">
|
||||
<i class="bi bi-search text-muted"></i>
|
||||
</span>
|
||||
<input type="text" name="search" class="form-control border-0 ps-1" placeholder="Search for apps or games..." autofocus>
|
||||
<button class="btn btn-success px-4" type="submit">Go</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-5">
|
||||
<h6 class="text-muted small fw-bold text-uppercase mb-3">Popular Categories</h6>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<?php
|
||||
$db = db();
|
||||
$popCats = $db->query("SELECT * FROM categories LIMIT 6")->fetchAll();
|
||||
foreach ($popCats as $cat):
|
||||
?>
|
||||
<a href="/?category=<?php echo $cat['slug']; ?>" class="btn btn-light btn-sm rounded-pill px-3 ajax-cat-link" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Chat Assistant -->
|
||||
<div id="ai-chat-wrapper" class="fixed-bottom p-3 d-flex flex-column align-items-end" style="z-index: 1050; pointer-events: none;">
|
||||
<div id="ai-chat-window" class="card shadow-lg border-0 mb-3 d-none" style="width: 350px; max-width: 90vw; height: 450px; pointer-events: auto; border-radius: 20px;">
|
||||
@ -119,39 +183,6 @@
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
<script>
|
||||
document.getElementById('newsletter-btn').addEventListener('click', function() {
|
||||
const email = document.getElementById('newsletter-email').value;
|
||||
const msg = document.getElementById('newsletter-msg');
|
||||
|
||||
if (!email) return;
|
||||
|
||||
this.disabled = true;
|
||||
this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||
|
||||
fetch('/api/newsletter/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: email })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this.disabled = false;
|
||||
this.innerHTML = '<?php echo __("subscribe"); ?>';
|
||||
if (data.success) {
|
||||
msg.innerHTML = '<span class="text-success">' + data.success + '</span>';
|
||||
document.getElementById('newsletter-email').value = '';
|
||||
} else {
|
||||
msg.innerHTML = '<span class="text-danger">' + data.error + '</span>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.disabled = false;
|
||||
this.innerHTML = '<?php echo __("subscribe"); ?>';
|
||||
msg.innerHTML = '<span class="text-danger">An error occurred.</span>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php echo get_setting('body_js'); ?>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -1,42 +1,66 @@
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<div class="row align-items-center mb-5 mt-5">
|
||||
<div class="row align-items-center mb-5 mt-4 mt-lg-5 text-center text-lg-start">
|
||||
<div class="col-lg-7">
|
||||
<h1 class="display-4 fw-bold mb-3"><?php echo __('hero_title'); ?></h1>
|
||||
<p class="lead text-muted mb-4"><?php echo __('hero_subtitle'); ?></p>
|
||||
<p class="lead text-muted mb-4 px-3 px-lg-0"><?php echo __('hero_subtitle'); ?></p>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<div class="d-flex gap-2 justify-content-center justify-content-lg-start">
|
||||
<a href="#latest" class="btn btn-success btn-lg px-4 rounded-pill"><?php echo __('explore_apps'); ?></a>
|
||||
<a href="/register" class="btn btn-outline-dark btn-lg px-4 rounded-pill border-1"><?php echo __('join_referral'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5 d-none d-lg-block text-center">
|
||||
<div class="position-relative">
|
||||
<!-- Dynamic Hero Circle -->
|
||||
<div class="position-absolute rounded-circle" style="width: 400px; height: 400px; top: -50px; right: -50px; z-index: -1; filter: blur(60px); opacity: 0.15; animation: color-cycle 20s infinite, floating 10s infinite ease-in-out;"></div>
|
||||
<img src="https://img.icons8.com/color/512/android-os.png" class="img-fluid floating-animation" alt="Android APKs" style="max-height: 350px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Featured Section (Horizontal Scroll on Mobile) -->
|
||||
<?php
|
||||
$featuredApks = $db->query("SELECT * FROM apks WHERE is_vip = 1 LIMIT 10")->fetchAll();
|
||||
if ($featuredApks):
|
||||
?>
|
||||
<section class="mb-5">
|
||||
<h4 class="fw-bold mb-3 d-flex align-items-center">
|
||||
<i class="bi bi-fire text-danger me-2"></i> Featured Apps
|
||||
</h4>
|
||||
<div class="d-flex overflow-auto pb-3 featured-scroll" style="scrollbar-width: none; -ms-overflow-style: none;">
|
||||
<?php foreach ($featuredApks as $f): ?>
|
||||
<div class="flex-shrink-0 me-3" style="width: 130px;">
|
||||
<a href="/apk/<?php echo $f['slug']; ?>" class="text-decoration-none">
|
||||
<div class="card border-0 shadow-sm rounded-4 text-center p-2 h-100 hover-lift">
|
||||
<img src="<?php echo !empty($f['icon_path']) ? '/'.$f['icon_path'] : $f['image_url']; ?>" class="rounded-3 mx-auto mb-2 shadow-sm" width="56" height="56" style="object-fit: cover;">
|
||||
<h6 class="card-title fw-bold mb-0 text-truncate small" style="font-size: 0.75rem;"><?php echo $f['title']; ?></h6>
|
||||
<span class="x-small text-muted" style="font-size: 0.65rem;"><?php echo $f['version']; ?></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<style>.featured-scroll::-webkit-scrollbar { display: none; }</style>
|
||||
<?php endif; ?>
|
||||
|
||||
<section id="latest" class="mb-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0" id="latest-title">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="fw-bold mb-0 h4" id="latest-title">
|
||||
<?php if (!empty($_GET['search'])): ?>
|
||||
<?php echo __('search_results_for', 'Search results for'); ?>: "<?php echo htmlspecialchars($_GET['search']); ?>"
|
||||
<?php else: ?>
|
||||
<?php echo __('latest_apks'); ?>
|
||||
<?php endif; ?>
|
||||
</h2>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-white shadow-sm border rounded-pill dropdown-toggle" type="button" data-bs-toggle="dropdown" id="category-dropdown-btn">
|
||||
<?php echo __('categories'); ?> <i class="bi bi-chevron-down ms-1 small"></i>
|
||||
<div class="dropdown d-none d-md-block">
|
||||
<button class="btn btn-white shadow-sm border rounded-pill dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown" id="category-dropdown-btn">
|
||||
<?php echo __('categories'); ?>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0" id="category-menu">
|
||||
<li><a class="dropdown-item category-filter" href="/" data-category=""><?php echo __('all_categories'); ?></a></li>
|
||||
<?php
|
||||
$db = db();
|
||||
$categories = $db->query("SELECT * FROM categories")->fetchAll();
|
||||
foreach ($categories as $cat): ?>
|
||||
<li><a class="dropdown-item category-filter" href="/?category=<?php echo $cat['slug']; ?>" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a></li>
|
||||
@ -44,26 +68,56 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Chips for Mobile & Quick Filter -->
|
||||
<div class="d-flex overflow-auto pb-3 mb-4 category-scroll" style="scrollbar-width: none; -ms-overflow-style: none;">
|
||||
<a href="/" class="btn <?php echo !isset($_GET['category']) ? 'btn-success' : 'btn-light'; ?> rounded-pill px-4 me-2 flex-shrink-0 btn-sm shadow-sm ajax-cat-link" data-category=""><?php echo __('all'); ?></a>
|
||||
<?php foreach ($categories as $cat): ?>
|
||||
<?php $isActive = isset($_GET['category']) && $_GET['category'] == $cat['slug']; ?>
|
||||
<a href="/?category=<?php echo $cat['slug']; ?>" class="btn <?php echo $isActive ? 'btn-success' : 'btn-light'; ?> rounded-pill px-4 me-2 flex-shrink-0 btn-sm shadow-sm ajax-cat-link" data-category="<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<style>.category-scroll::-webkit-scrollbar { display: none; }</style>
|
||||
|
||||
<div id="apk-grid-container">
|
||||
<?php include 'partials/apk_list.php'; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="bg-dark text-white p-5 rounded-5 mt-5 mb-5 shadow-lg position-relative overflow-hidden">
|
||||
<!-- Subtle dark mode blob -->
|
||||
<!-- Referral Banner -->
|
||||
<div class="bg-dark text-white p-4 p-md-5 rounded-5 mt-5 mb-5 shadow-lg position-relative overflow-hidden">
|
||||
<div class="position-absolute bg-success opacity-10 rounded-circle" style="width: 300px; height: 300px; bottom: -100px; left: -100px; filter: blur(50px);"></div>
|
||||
|
||||
<div class="row align-items-center text-center text-lg-start position-relative">
|
||||
<div class="col-lg-8">
|
||||
<h2 class="fw-bold mb-3"><?php echo __('referral_journey_title'); ?></h2>
|
||||
<p class="mb-0 text-white-50"><?php echo __('referral_journey_text'); ?></p>
|
||||
<h2 class="fw-bold mb-3 h3"><?php echo __('referral_journey_title'); ?></h2>
|
||||
<p class="mb-0 text-white-50 small"><?php echo __('referral_journey_text'); ?></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"><?php echo __('get_started'); ?></a>
|
||||
<a href="/register" class="btn btn-success btn-lg px-5 rounded-pill w-100 w-lg-auto"><?php echo __('get_started'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Newsletter Section -->
|
||||
<section class="py-5 mb-5">
|
||||
<div class="card border-0 shadow-lg rounded-5 overflow-hidden">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 mb-4 mb-lg-0">
|
||||
<h3 class="fw-bold mb-2">Subscribe to our Newsletter</h3>
|
||||
<p class="text-muted mb-0">Get notified about the latest APKs and updates directly in your inbox.</p>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<form action="/newsletter/subscribe" method="POST" class="d-flex gap-2 p-1 bg-light rounded-pill border shadow-sm">
|
||||
<input type="email" name="email" class="form-control border-0 bg-transparent px-4 py-2" placeholder="Enter your email" required>
|
||||
<button type="submit" class="btn btn-success rounded-pill px-4">Subscribe</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php include 'footer.php'; ?>
|
||||
@ -6,31 +6,29 @@
|
||||
<a href="/" class="btn btn-outline-success rounded-pill px-4 mt-2"><?php echo __('view_all_apks', 'View All APKs'); ?></a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-3 g-md-4">
|
||||
<div class="row g-2 g-md-4">
|
||||
<?php foreach ($apks as $apk): ?>
|
||||
<div class="col-4 col-md-4">
|
||||
<div class="col-4 col-md-4 col-lg-3">
|
||||
<div class="card h-100 border-0 shadow-sm rounded-4 hover-lift">
|
||||
<div class="card-body p-2 p-md-4 text-center text-md-start">
|
||||
<div class="d-md-flex align-items-center mb-2 mb-md-3">
|
||||
<?php
|
||||
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
|
||||
?>
|
||||
<div class="mx-auto mx-md-0 mb-2 mb-md-0" style="width: 50px; height: 50px;">
|
||||
<img src="<?php echo $icon; ?>" class="rounded-3" width="50" height="50" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; width: 50px; height: 50px;">
|
||||
</div>
|
||||
<div class="ms-md-3 flex-grow-1 overflow-hidden">
|
||||
<h6 class="card-title fw-bold mb-0 text-truncate"><?php echo $apk['title']; ?></h6>
|
||||
<span class="badge bg-light text-dark fw-normal d-none d-md-inline-block">v<?php echo $apk['version']; ?></span>
|
||||
</div>
|
||||
<div class="card-body p-2 p-md-3 text-center d-flex flex-column h-100">
|
||||
<?php
|
||||
$icon = !empty($apk['icon_path']) ? '/'.$apk['icon_path'] : $apk['image_url'];
|
||||
?>
|
||||
<div class="mx-auto mb-2" style="width: 50px; height: 50px; min-height: 50px;">
|
||||
<img src="<?php echo $icon; ?>" class="rounded-3 shadow-sm" width="50" height="50" alt="<?php echo $apk['title']; ?>" style="object-fit: cover; width: 50px; height: 50px;">
|
||||
</div>
|
||||
<p class="card-text text-muted small mb-3 line-clamp-2 d-none d-md-block"><?php echo $apk['description']; ?></p>
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-2">
|
||||
<span class="text-muted small d-none d-md-inline-block"><i class="fas fa-download me-1"></i> <?php echo number_format($apk['total_downloads']); ?></span>
|
||||
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-success rounded-pill px-3 btn-sm fw-medium w-100 w-md-auto"><?php echo __('details'); ?></a>
|
||||
|
||||
<h6 class="card-title fw-bold mb-1 text-truncate" style="font-size: 0.75rem;"><?php echo $apk['title']; ?></h6>
|
||||
<div class="mb-2">
|
||||
<span class="badge bg-light text-muted fw-normal x-small" style="font-size: 0.65rem;">v<?php echo $apk['version']; ?></span>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto">
|
||||
<a href="/apk/<?php echo $apk['slug']; ?>" class="btn btn-success rounded-pill py-1 btn-sm fw-medium w-100" style="font-size: 0.7rem;"><?php echo __('details'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user