Home
Profile
Profile
NBTV APP - My Profile
Welcome!
Login or create an account to manage your profile.
Login
Sign Up
Deposit via EasyPaisa / JazzCash
Please send the desired amount to the following number via EasyPaisa or JazzCash:03105784772 After payment, enter the details below.
Add Your Game/Website
Game Submission Policy Cost: A fixed fee of PKR 20 is charged.Duration: Your game will remain live until its play limit is reached.No Violations: No offensive material.
Exchange Currency
Your current balance: PKR 0
Amount to Exchange:
From Currency: PKR INR USD
To Currency: PKR INR USD
Exchange
Cancel
Select Episode
Select an episode to play
Sponsor Message
Please support us by viewing this ad to unlock your episode.
Unlocking in 10 seconds...
Unlock Now!
`;
claimBtn.style.display = 'none';
timerContainer.style.display = 'block';
const totalWaitTime = 10; // User must wait 10 seconds viewing the ad modal
let timeLeft = totalWaitTime;
countdownSpan.textContent = timeLeft;
progressBar.style.width = '0%';
if (adUnlockTimer) clearInterval(adUnlockTimer);
adUnlockTimer = setInterval(() => {
timeLeft--;
countdownSpan.textContent = timeLeft;
const percentage = ((totalWaitTime - timeLeft) / totalWaitTime) * 100;
progressBar.style.width = `${percentage}%`;
if (timeLeft <= 0) {
clearInterval(adUnlockTimer);
timerContainer.style.display = 'none';
claimBtn.style.display = 'block';
claimBtn.onclick = () => claimAdUnlock(episodeId, episodeTitle);
}
}, 1000);
}
/**
* NEW: Grants access to the episode after the Ad Timer completes.
*/
async function claimAdUnlock(episodeId, episodeTitle) {
const userUid = auth.currentUser.uid;
const unlocksRef = db.ref(`users/${userUid}/unlocked_episodes/${episodeId}`);
const unlockExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
try {
await unlocksRef.set(unlockExpiry);
showToast(`"${episodeTitle}" unlocked for 24 hours!`);
if (currentUserData) {
if (!currentUserData.unlocked_episodes) currentUserData.unlocked_episodes = {};
currentUserData.unlocked_episodes[episodeId] = unlockExpiry;
}
toggleModal('adUnlockModal', false);
renderEpisodeButtons(); // Refresh buttons to remove lock icon
} catch (error) {
console.error("Error unlocking via ad:", error);
showToast("Failed to unlock episode. Please try again.", true);
}
}
/**
* Deducts cost from user's wallet and unlocks the episode for 24 hours.
*/
async function deductAndUnlockEpisode(episodeId, episodeTitle, cost) {
const userUid = auth.currentUser.uid;
const walletPKRRef = db.ref(`users/${userUid}/wallet/PKR`);
const unlocksRef = db.ref(`users/${userUid}/unlocked_episodes/${episodeId}`);
try {
await walletPKRRef.transaction(currentBalance => {
const balance = currentBalance !== null && typeof currentBalance === 'number' ? currentBalance : 0;
if (balance >= cost) {
return balance - cost;
}
return undefined; // Abort transaction
}, async (error, committed, snapshot) => {
if (error) {
showToast("Failed to unlock episode. Please try again.", true);
} else if (committed) {
const unlockExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
await unlocksRef.set(unlockExpiry);
await db.ref(`transactions/${userUid}`).push({
amount: cost, type: 'debit', currency: 'PKR',
description: `Unlocked: ${currentSeriesTitle} - ${episodeTitle} (24h)`,
created_at: new Date().toISOString()
});
showToast(`"${episodeTitle}" unlocked for 24 hours!`);
if (currentUserData) {
currentUserData.wallet.PKR = snapshot.val();
if (!currentUserData.unlocked_episodes) currentUserData.unlocked_episodes = {};
currentUserData.unlocked_episodes[episodeId] = unlockExpiry;
}
renderEpisodeButtons();
} else {
showToast("Transaction aborted: Insufficient funds.", true);
}
});
} catch (error) {
console.error("Error during episode unlock by payment:", error);
showToast("An error occurred during unlock.", true);
}
}
/**
* Opens the external unlock link.
*/
async function openUnlockLink(episodeId, episodeTitle, unlockLink) {
if (!unlockLink) { return showToast("No unlock link provided.", true); }
// Smart link already opened in unlockEpisodePrompt
window.open(unlockLink, '_blank');
showToast("Opening unlock link... Follow instructions on the page.", false);
setTimeout(async () => {
const userUid = auth.currentUser.uid;
const unlocksRef = db.ref(`users/${userUid}/unlocked_episodes/${episodeId}`);
const unlockExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
try {
await unlocksRef.set(unlockExpiry);
showToast(`"${episodeTitle}" should now be unlocked for 24 hours!`);
if (currentUserData) {
if (!currentUserData.unlocked_episodes) currentUserData.unlocked_episodes = {};
currentUserData.unlocked_episodes[episodeId] = unlockExpiry;
}
renderEpisodeButtons();
} catch (error) {
console.error("Error setting episode unlock status after link:", error);
showToast("Failed to update unlock status. Please try again.", true);
}
}, 3000);
}
/**
* Helper function to convert a standard YouTube URL to an embeddable URL.
* Also handles Google Drive and direct video links.
* @param {string} url - The original video URL.
* @returns {string} The embeddable URL or direct video URL.
*/
function convertVideoUrlToEmbed(url) {
if (!url) return '';
let youtubeId = getYoutubeVideoId(url);
if (youtubeId) {
return `https://www.youtube.com/embed/${youtubeId}?autoplay=1&rel=0`;
}
if (url.includes('drive.google.com')) {
const match = url.match(/\/d\/([a-zA-Z0-9_-]+)/);
if (match && match[1]) {
return `https://drive.google.com/file/d/${match[1]}/preview`;
}
}
return url;
}
/**
* Renders the Video Player Page dynamically.
* @param {HTMLElement} container - The container element.
* @param {object} data - The video section data. Expected to have `youtubeUrl` (now generic `video_url`), `title`, `description`, `watchTimeSeconds`, `rewardPkr`.
*/
async function renderVideoPlayerPage(container, data) {
// This function is included but would typically not be called in a profile-only standalone file.
// It's here for completeness based on the provided script, but its UI elements are not in the HTML.
console.warn("renderVideoPlayerPage called in profile-only view. This should not display content here.");
showToast("Video player functionality is not available in this profile-only view.", true);
return;
}
/**
* Handles the countdown timer for video rewards. (Not directly used in profile-only HTML but part of original script).
* @param {number} totalSeconds - Total time to watch.
* @param {number} rewardPkr - Reward amount.
* @param {string} videoId - Database ID of the video section.
* @param {string} videoTitle - Title of the video.
* @param {string} sectionTitle - Section title (often same as video title in this structure).
*/
function startVideoRewardTimer(totalSeconds, rewardPkr, videoId, videoTitle, sectionTitle) {
console.warn("startVideoRewardTimer called in profile-only view. Functionality not fully supported here.");
showToast("Video reward timer functionality is not fully available in this profile-only view.", true);
// Minimal implementation to prevent errors if accidentally called:
const statusContainer = document.getElementById('video-reward-status');
if (statusContainer) statusContainer.innerHTML = `
Video rewards not active in this view.
`;
}
/**
* Claims the reward after the video timer finishes. (Not directly used in profile-only HTML but part of original script).
*/
async function claimVideoReward(rewardPkr, videoId, videoTitle, sectionTitle, statusContainer) {
console.warn("claimVideoReward called in profile-only view. Functionality not fully supported here.");
showToast("Video reward claim functionality is not fully available in this profile-only view.", true);
if (statusContainer) statusContainer.innerHTML = `
Video rewards not active in this view.
`;
}
/**
* Initiates playing a game URL, optionally as part of a tournament, and handles play count logic for regular games.
* @param {string} gameId - The ID of the game from the database (relevant for non-tournament games).
* @param {string} url - The URL of the game.
* @param {string} [tournamentId=null] - The ID of the tournament, if applicable.
*/
async function playGameUrl(gameId, url, tournamentId = null) {
if (!auth.currentUser) {
return showToast('Login required to play!', true);
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
// Logic for non-tournament, limited-play games
if (gameId && !tournamentId) {
const gameRef = db.ref(`games/${gameId}`);
try {
const { committed } = await gameRef.transaction(gameData => {
if (gameData && gameData.status === 'approved' && typeof gameData.play_limit === 'number') {
gameData.play_count = (gameData.play_count || 0) + 1;
if (gameData.play_limit !== 0 && gameData.play_count >= gameData.play_limit) {
gameData.status = 'completed';
gameData.notified_play_limit_reached = false;
}
return gameData;
}
return;
});
if (!committed) {
showToast("This game is no longer available or its play limit has been reached.", true);
// In a profile-only view, we can't re-render the homepage, just inform.
return;
}
localStorage.setItem('game_played_pending', 'true');
} catch (error) {
console.error("Error updating play count for game:", error);
showToast("Could not record your play due to an error. Please try again.", true);
return;
}
}
if (tournamentId) {
localStorage.setItem('active_tournament_id', tournamentId);
localStorage.setItem('game_start_time', Date.now());
}
// Smart link already opened by checkLoginAndAct
window.location.href = url; // Redirect to the game URL
}
/**
* NEW: Opens a banner link.
* @param {string} url - The URL to open.
*/
function openBannerLink(url) {
if (!url) {
showToast("Link is missing!", true);
return;
}
// Smart link already opened by checkLoginAndAct
window.open(url, '_blank'); // Open in a new tab/window
}
/**
* Renders the Wallet Page content dynamically. (Not directly used in profile-only HTML).
* @param {HTMLElement} container - The container element for the wallet page.
*/
async function renderWalletPage(container) {
console.warn("renderWalletPage called in profile-only view. This should not display content here.");
showToast("Wallet functionality is not available in this profile-only view.", true);
}
/**
* Renders the My Tournaments Page content dynamically. (Not directly used in profile-only HTML).
* @param {HTMLElement} container - The container element for the My Tournaments page.
*/
async function renderMyTournamentsPage(container) {
console.warn("renderMyTournamentsPage called in profile-only view. This should not display content here.");
showToast("My Matches functionality is not available in this profile-only view.", true);
}
/**
* Renders the Notifications Page content dynamically. (Not directly used in profile-only HTML).
* @param {HTMLElement} container - The container element for the notifications page.
*/
async function renderNotificationsPage(container) {
console.warn("renderNotificationsPage called in profile-only view. This should not display content here.");
showToast("Notifications functionality is not available in this profile-only view.", true);
}
/**
* Formats a timestamp into a human-readable "time ago" string.
* @param {string} timestamp - The ISO string timestamp.
* @returns {string} The formatted time ago string.
*/
function formatTimeAgo(timestamp) {
const now = new Date();
const past = new Date(timestamp);
const diffSeconds = Math.round((now.getTime() - past.getTime()) / 1000);
const minutes = Math.round(diffSeconds / 60);
const hours = Math.round(diffSeconds / 3600);
const days = Math.round(diffSeconds / (3600 * 24));
if (diffSeconds < 60) return `${diffSeconds} sec ago`;
if (minutes < 60) return `${minutes} min ago`;
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
return `${days} day${days > 1 ? 's' : ''} ago`;
}
/**
* Marks a single notification as read in Firebase.
* @param {string} notificationId - The ID of the notification to mark as read.
*/
async function markNotificationAsRead(notificationId) {
if (!auth.currentUser || !notificationId) return;
try {
await db.ref(`notifications/${auth.currentUser.uid}/${notificationId}`).update({ status: 'read', read_at: new Date().toISOString() });
showToast('Notification marked as read!');
// Re-render profile if support messages are open to reflect changes
if (document.getElementById('mySupportMessagesSection').style.display === 'block') {
showMySupportMessages();
}
} catch (error) {
console.error("Error marking notification as read:", error);
showToast('Failed to mark notification as read.', true);
}
}
/**
* Marks all unread notifications for the current user as read.
*/
async function markAllNotificationsAsRead() {
if (!auth.currentUser) return;
try {
const unreadSnap = await db.ref(`notifications/${auth.currentUser.uid}`).orderByChild('status').equalTo('unread').once('value');
const updates = {};
unreadSnap.forEach(childSnap => {
updates[`notifications/${auth.currentUser.uid}/${childSnap.key}/status`] = 'read';
updates[`notifications/${auth.currentUser.uid}/${childSnap.key}/read_at`] = new Date().toISOString();
});
if (Object.keys(updates).length > 0) {
await db.ref().update(updates);
showToast('All notifications marked as read!');
} else {
showToast('No unread notifications to mark.', false);
}
// Re-render profile if support messages are open to reflect changes
if (document.getElementById('mySupportMessagesSection').style.display === 'block') {
showMySupportMessages();
}
} catch (error) {
console.error("Error marking all notifications as read:", error);
showToast('Failed to mark all notifications as read.', true);
}
}
/**
* Renders the Profile Page content dynamically.
* @param {HTMLElement} container - The container element for the profile page.
*/
function renderProfilePage(container) {
if (!auth.currentUser) {
// Display Login Form when not logged in
container.innerHTML = `
Login to view and manage your profile.
`;
// Attach listeners for the dynamically added login/signup forms
attachLoginSignupFormListeners('Page'); // Use 'Page' suffix for these specific elements
return;
}
// Display Profile Details when logged in
const userReferralCode = currentUserData?.username || '';
const referralsEarned = currentUserData?.referrals_earned_count || 0;
const preferredCurrency = currentUserData?.preferred_currency || 'PKR';
container.innerHTML = `
Profile & Settings
${currentUserData?.username ? currentUserData.username[0].toUpperCase() : 'U'}
${escapeJsStringForHtmlAttribute(currentUserData?.username || 'User')}
${escapeJsStringForHtmlAttribute(currentUserData?.email || auth.currentUser?.email || 'N/A')}
Referrals Joined: ${referralsEarned}
My Wallet Settings
Preferred Display Currency:
Pakistani Rupee (PKR)
Indian Rupee (INR)
US Dollar (USD)
This sets the default currency displayed in your wallet and header.
Invite Friends & Earn!
Share your username as referral code. You get PKR ${referralBonusAmount} for every friend who signs up!
Copy Code
Friends must enter your username during signup to count as your referral.
Get the Full App Data delete !
Fill this form to delete your account permanently..
Account Delete Form
⚡ Claim 1-Hour Bonus Now
Claim PKR 100 - 1000 daily!
Withdrawal requires 20 referrals who each deposited PKR 300 .
Add New Website
My Submitted Website
Reset Password
Logout
Back to Profile
Support Messages
New Message
`;
// Ensure event listeners are re-attached for dynamically loaded elements
document.getElementById('preferred-currency-select')?.addEventListener('change', updateUserPreferredCurrency);
updateProfileContent(); // Call to populate specific elements like username, email, etc.
renderMySubmittedGames(); // Call the function to populate the submitted games list
updateFeatureButtonStates(); // Update button states after rendering profile content
}
/**
* Renders the list of games submitted by the current user with their play counts.
*/
function renderMySubmittedGames() {
const listEl = document.getElementById('my-submitted-games-list');
if (!auth.currentUser || !listEl) {
listEl.innerHTML = `
Login to view your submitted games.
`;
return;
}
const userId = auth.currentUser.uid;
db.ref('games').orderByChild('created_by').equalTo(userId).on('value', snapshot => {
const gamesData = snapshot.val();
if (!gamesData) {
listEl.innerHTML = `
You have not submitted any games yet.
`;
return;
}
const submittedGames = [];
for (const gameId in gamesData) {
const game = gamesData[gameId];
if (game.status === 'approved' || game.status === 'completed' || game.status === 'pending') {
submittedGames.push({ id: gameId, ...game });
}
}
if (submittedGames.length === 0) {
listEl.innerHTML = `
You have no active games. Games appear here once approved by admin.
`;
return;
}
submittedGames.sort((a, b) => a.title.localeCompare(b.title));
listEl.innerHTML = submittedGames.map(game => {
const currentPlays = game.play_count || 0;
const playLimit = game.play_limit || 0;
const progressPercentage = playLimit > 0 ? Math.min((currentPlays / playLimit) * 100, 100) : 0;
let gameStatusText;
let statusColorClass;
switch (game.status) {
case 'approved':
gameStatusText = 'Live';
statusColorClass = 'text-green-600 bg-green-100';
break;
case 'completed':
gameStatusText = 'Limit Reached';
statusColorClass = 'text-red-600 bg-red-100';
break;
case 'pending':
gameStatusText = 'Pending Approval';
statusColorClass = 'text-yellow-600 bg-yellow-100';
break;
case 'rejected':
statusColorClass = 'text-red-800 bg-gray-200'; // Corrected variable name
gameStatusText = 'Rejected (Refunded)';
break;
default:
gameStatusText = 'Unknown';
statusColorClass = 'text-gray-600 bg-gray-100';
}
const reAddButton = game.status === 'completed'
? `
Re-add Game `
: '';
return `
${escapeJsStringForHtmlAttribute(game.title)}
${gameStatusText}
Plays: ${currentPlays} / ${playLimit === 0 ? 'Unlimited' : playLimit}
${reAddButton}
`;
}).join('');
}, (error) => {
console.error("Error loading user's submitted games:", error);
listEl.innerHTML = `
Error loading your games.
`;
});
}
/**
* NEW FEATURE: Updates the disabled state and styling of feature buttons based on `appFeatureControls`.
*/
function updateFeatureButtonStates() {
const addMoneyBtn = document.getElementById('addMoneyBtn');
const withdrawMoneyBtn = document.getElementById('withdrawMoneyBtn');
const addGameBtn = document.getElementById('addGameBtn'); // This is from modals, so relevant
const applyState = (button, featureKey) => {
if (!button) return;
const control = appFeatureControls[featureKey] || { locked: false, reason: '' };
if (control.locked) {
button.disabled = true;
button.classList.add('opacity-50', 'cursor-not-allowed');
button.classList.remove('hover:from-green-600', 'hover:to-green-600', 'hover:from-blue-600', 'hover:to-blue-600', 'hover:from-orange-600', 'hover:to-yellow-600');
} else {
button.disabled = false;
button.classList.remove('opacity-50', 'cursor-not-allowed');
// Restore hover effects based on original colors (can be more specific if needed)
// Note: These hover classes are generic; actual classes from Tailwind might be more specific.
if (featureKey === 'add_money') button.classList.add('hover:bg-green-600'); // Example, adjust as per actual button styles
else if (featureKey === 'withdraw_money') button.classList.add('hover:bg-blue-600'); // Example
else if (featureKey === 'add_game') button.classList.add('hover:from-orange-600', 'hover:to-yellow-600'); // Example
}
};
// These buttons are in the modals, so they will be updated when modals are rendered or shown
applyState(addMoneyBtn, 'add_money');
applyState(withdrawMoneyBtn, 'withdraw_money');
applyState(addGameBtn, 'add_game'); // For the button in profile section
}
/**
* NEW FEATURE: Handles click on a locked feature button.
* Displays a toast with the lock reason.
* @param {string} featureName - The name of the feature (e.g., 'add_money').
* @returns {boolean} True if the feature is unlocked, false if locked.
*/
function checkFeatureLock(featureName) {
const control = appFeatureControls[featureName];
if (control && control.locked) {
showToast(control.reason || `This feature (${featureName.replace(/_/g, ' ')}) is currently locked by the admin.`, true);
return false;
}
return true;
}
function updateProfileContent() {
if (currentUserData) {
const usernameEl = document.getElementById('profile-username');
if (usernameEl) usernameEl.textContent = currentUserData.username || 'User';
const emailEl = document.getElementById('profile-email');
if (emailEl) emailEl.textContent = currentUserData.email || auth.currentUser?.email || 'N/A';
const referralsEarnedEl = document.getElementById('profile-referrals-count');
if (referralsEarnedEl) {
const countToDisplay = currentUserData.referrals_earned_count || 0;
referralsEarnedEl.textContent = countToDisplay;
}
const referralLinkInput = document.getElementById('referralLinkInput');
if (referralLinkInput) {
referralLinkInput.value = currentUserData.username || '';
}
const referralBonusTextEl = document.getElementById('referral-bonus-text');
if (referralBonusTextEl) {
referralBonusTextEl.textContent = referralBonusAmount;
}
// Note: withdraw-amount and admin-deposit-number elements are within modals,
// so they are usually updated when the modal is opened, not just on profile content update.
// Keeping these lines for completeness but they might not affect visible profile directly.
const withdrawAmountInput = document.getElementById('withdraw-amount');
if (withdrawAmountInput) {
withdrawAmountInput.placeholder = `Min ${formatCurrency(minWithdrawalAmount, 'PKR')}`;
}
const adminDepositNumEl = document.getElementById('admin-deposit-number');
if (adminDepositNumEl) {
adminDepositNumEl.textContent = adminDepositNumber;
}
} else {
console.warn("DEBUG: updateProfileContent called but currentUserData is null.");
// If currentUserData is null, ensure profile details are cleared or show default.
document.getElementById('profile-username').textContent = 'User';
document.getElementById('profile-email').textContent = 'N/A';
document.getElementById('profile-referrals-count').textContent = '0';
document.getElementById('referralLinkInput').value = '';
}
}
/**
* Copies the referral username to the clipboard.
*/
function copyReferralLink() {
const referralLinkInput = document.getElementById('referralLinkInput');
if (referralLinkInput) {
referralLinkInput.select();
referralLinkInput.setSelectionRange(0, 99999);
document.execCommand('copy');
showToast('Referral username copied!');
}
}
/**
* Attaches event listeners for the login/signup tabs and forms.
* @param {string} suffix - Suffix for element IDs (e.g., 'Modal' or 'Page').
*/
function attachLoginSignupFormListeners(suffix) {
const loginTab = document.getElementById('loginTabBtn' + suffix);
const signupTab = document.getElementById('signupTabBtn' + suffix);
const loginForm = document.getElementById('loginForm' + suffix);
const signupForm = document.getElementById('signupForm' + suffix);
const signupUsernameInput = document.getElementById('signupUsername' + suffix);
const usernameAvailabilityEl = document.getElementById('usernameAvailability' + suffix);
const signupSubmitBtn = document.getElementById('signupSubmitBtn' + suffix);
const signupReferralCodeInput = document.getElementById('signupReferralCode' + suffix);
// Defensive checks
if (!loginTab || !signupTab || !loginForm || !signupForm || !signupUsernameInput || !usernameAvailabilityEl || !signupSubmitBtn || !signupReferralCodeInput) {
console.warn(`Login/Signup form elements with suffix '${suffix}' not found, skipping attaching listeners.`);
return;
}
// Initialize signup button state
signupSubmitBtn.disabled = true;
loginTab.onclick = () => { // Use onclick instead of addEventListener for dynamically added elements to prevent multiple attachments
loginTab.className = "flex-1 py-2 text-center font-bold border-b-4 border-red-500 text-red-600";
signupTab.className = "flex-1 py-2 text-center font-bold text-gray-400 border-b-4 border-transparent";
loginForm.style.display = 'block';
signupForm.style.display = 'none';
signupSubmitBtn.disabled = true;
usernameAvailabilityEl.textContent = '';
signupUsernameInput.value = '';
signupReferralCodeInput.value = '';
};
signupTab.onclick = () => { // Use onclick
signupTab.className = "flex-1 py-2 text-center font-bold border-b-4 border-red-500 text-red-600";
loginTab.className = "flex-1 py-2 text-center font-bold text-gray-400 border-b-4 border-transparent";
signupForm.style.display = 'block';
loginForm.style.display = 'none';
signupSubmitBtn.disabled = true;
usernameAvailabilityEl.textContent = '';
signupUsernameInput.value = '';
signupReferralCodeInput.value = '';
};
let usernameTimer;
signupUsernameInput.oninput = () => { // Use oninput
clearTimeout(usernameTimer);
const username = signupUsernameInput.value.trim();
if (username.length < 3) {
usernameAvailabilityEl.textContent = 'Username must be at least 3 characters.';
usernameAvailabilityEl.className = 'text-xs mt-1 text-red-500';
signupSubmitBtn.disabled = true;
return;
}
if (!/^[a-zA-Z0-9_.-]+$/.test(username)) {
usernameAvailabilityEl.textContent = 'Invalid characters. Use letters, numbers, _, ., -';
usernameAvailabilityEl.className = 'text-xs mt-1 text-red-500';
signupSubmitBtn.disabled = true;
return;
}
usernameAvailabilityEl.textContent = 'Checking availability...';
usernameAvailabilityEl.className = 'text-xs mt-1 text-gray-500';
signupSubmitBtn.disabled = true;
usernameTimer = setTimeout(async () => {
try {
const snap = await db.ref('usernames/' + username.toLowerCase()).once('value');
if (snap.exists()) {
usernameAvailabilityEl.textContent = 'Username is already taken.';
usernameAvailabilityEl.className = 'text-xs mt-1 text-red-500';
signupSubmitBtn.disabled = true;
} else {
usernameAvailabilityEl.textContent = 'Username is available!';
usernameAvailabilityEl.className = 'text-xs mt-1 text-green-500';
signupSubmitBtn.disabled = false;
}
} catch (error) {
console.error("Error checking username availability:", error);
usernameAvailabilityEl.textContent = 'Error checking username.';
usernameAvailabilityEl.className = 'text-xs mt-1 text-red-500';
signupSubmitBtn.disabled = true;
}
}, 500);
};
loginForm.onsubmit = async e => { // Use onsubmit
e.preventDefault();
try {
await auth.signInWithEmailAndPassword(e.target['loginEmail' + suffix].value, e.target['loginPassword' + suffix].value);
showToast('Login successful!');
// If login form is in a modal, close the modal. If on the page, re-render profile.
if (suffix === 'Modal') toggleModal('authModal', false);
e.target.reset();
renderProfilePage(document.getElementById('profilePageContainer')); // Re-render profile content
} catch (err) {
showToast(err.message, true);
}
};
signupForm.onsubmit = async e => { // Use onsubmit
e.preventDefault();
const emailInput = e.target['signupEmail' + suffix];
const passwordInput = e.target['signupPassword' + suffix];
const usernameInput = e.target['signupUsername' + suffix];
const referralCodeInput = e.target['signupReferralCode' + suffix];
const enteredReferralCode = referralCodeInput.value.trim().toLowerCase();
const username = usernameInput.value.trim();
try {
const finalCheckSnap = await db.ref('usernames/' + username.toLowerCase()).once('value');
if (finalCheckSnap.exists()) {
showToast('Username is already taken. Please choose another.', true);
usernameInput.focus();
return;
}
const cred = await auth.createUserWithEmailAndPassword(emailInput.value, passwordInput.value);
const newUserId = cred.user.uid;
const initialSignupBonus = signupBonusAmount;
const referralBonus = referralBonusAmount;
let newUserData = {
username: username,
email: emailInput.value,
wallet: { PKR: initialSignupBonus, INR: 0, USD: 0 },
preferred_currency: 'PKR',
referrals_earned_count: 0,
created_at: new Date().toISOString(),
locked: false,
lockReason: null
};
newUserData.referral_code = username.toLowerCase();
let feedbackMessage = `Signup successful! You got ${formatCurrency(initialSignupBonus, 'PKR')} 🎉`;
if (enteredReferralCode && enteredReferralCode !== username.toLowerCase()) {
const refSnap = await db.ref('usernames/' + enteredReferralCode).once('value');
if (refSnap.exists()) {
const referrerUid = refSnap.val();
await db.ref(`users/${referrerUid}`).transaction((data) => {
if (data) {
if (!data.wallet) data.wallet = { PKR: data.wallet_balance || 0, INR: 0, USD: 0 };
data.wallet.PKR = (data.wallet.PKR !== null && typeof data.wallet.PKR === 'number' ? data.wallet.PKR : 0) + referralBonus;
data.referrals_earned_count = (data.referrals_earned_count || 0) + 1;
}
return data;
});
await db.ref(`transactions/${referrerUid}`).push({
amount: referralBonus,
type: "credit",
currency: 'PKR',
description: `Referral bonus from ${username}`,
created_at: new Date().toISOString()
});
newUserData.referred_by_username = enteredReferralCode;
feedbackMessage = `Signup successful! You got ${formatCurrency(initialSignupBonus, 'PKR')} & referrer rewarded 🎉`;
} else {
feedbackMessage = `Signup successful! You got ${formatCurrency(initialSignupBonus, 'PKR')} (Invalid referral code)`;
}
} else if (enteredReferralCode === username.toLowerCase()) {
feedbackMessage = `Signup successful! You got ${formatCurrency(initialSignupBonus, 'PKR')} (Cannot refer yourself)`;
}
const userRef = db.ref('users/' + newUserId);
const snap = await userRef.once('value');
if (!snap.exists()) {
await userRef.set(newUserData);
} else {
await userRef.update({
wallet: newUserData.wallet,
preferred_currency: newUserData.preferred_currency,
username: newUserData.username,
email: newUserData.email,
referral_code: newUserData.referral_code,
referred_by_username: newUserData.referred_by_username || null,
created_at: newUserData.created_at,
locked: newUserData.locked,
lockReason: newUserData.lockReason
});
}
await db.ref('usernames/' + username.toLowerCase()).set(newUserId);
await db.ref(`transactions/${newUserId}`).push({
amount: initialSignupBonus,
type: "credit",
currency: 'PKR',
description: "Signup Bonus",
created_at: new Date().toISOString()
});
showToast(feedbackMessage);
// If signup form is in a modal, close the modal. If on the page, re-render profile.
if (suffix === 'Modal') toggleModal('authModal', false);
signupForm.reset();
referralCodeInput.value = '';
usernameAvailabilityEl.textContent = '';
renderProfilePage(document.getElementById('profilePageContainer')); // Re-render profile content
} catch (err) {
console.error("Signup Error:", err);
showToast(err.message, true);
}
};
}
/**
* Handles claiming daily bonus.
*/
async function claimDailyBonus() {
if (!auth.currentUser) {
showToast('Login required to claim bonus!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
const userUid = auth.currentUser.uid;
const userRef = db.ref(`users/${userUid}`);
try {
const snap = await userRef.once('value');
const userData = snap.val();
if (!userData) {
showToast('User data not found. Please try logging in again.', true);
toggleModal('authModal', true); // Open login modal
return;
}
const lastClaimTimestamp = userData.last_daily_bonus_claim_timestamp || 0;
const twentyFourHours = 0.10 * 60 * 60 * 1000; // 6 minutes for 0.1 hour test, adjust to 24 * 60 * 60 * 1000 for actual 24 hours
if (Date.now() - lastClaimTimestamp < twentyFourHours) {
const timeLeft = twentyFourHours - (Date.now() - lastClaimTimestamp);
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
showToast(`You can claim your next daily bonus in ${hours}h ${minutes}m.`, true);
window.open(ADSTERRA_SMART_LINK, '_blank'); // Open smart link only if logged in but on cooldown
return;
}
const randomBonus = Math.floor(Math.random() * 200) + 100;
let committed = false;
const walletRef = db.ref(`users/${userUid}/wallet/PKR`);
await walletRef.transaction(data => {
const balance = data !== null && typeof data === 'number' ? data : 0;
return balance + randomBonus;
}, (error, _committed, snapshot) => {
if (error) {
console.error("Daily bonus transaction failed: ", error);
showToast("Failed to claim daily bonus. Please try again.", true);
} else if (_committed) {
committed = true;
db.ref(`transactions/${userUid}`).push({
amount: randomBonus,
type: 'credit',
currency: 'PKR',
description: 'Daily Bonus',
created_at: new Date().toISOString()
});
userRef.update({
last_daily_bonus_claim_timestamp: Date.now(),
daily_bonus_withdrawal_condition_active: true
}).then(() => {
showToast(`💰 You claimed ${formatCurrency(randomBonus, 'PKR')} daily bonus!`, false);
window.open(ADSTERRA_SMART_LINK, '_blank'); // Open smart link after successful claim
updateProfileContent(); // Update profile to show new balance
}).catch(updateError => {
console.error("Failed to update daily bonus timestamp:", updateError);
showToast('Claimed bonus but failed to record timestamp.', true);
});
} else {
console.log("Daily bonus transaction aborted.");
}
});
} catch (error) {
console.error("Error claiming daily bonus:", error);
showToast('An error occurred while claiming bonus.', true);
window.open(ADSTERRA_SMART_LINK, '_blank'); // Open smart link on unexpected error
}
}
/**
* Attaches event listeners for the tabs on the My Tournaments page. (Not directly used in profile-only HTML).
*/
function attachMyTournamentsListeners() {
console.warn("attachMyTournamentsListeners called in profile-only view. Functionality not fully supported here.");
}
/**
* Handles joining a tournament. Deducts entry fee and adds user as participant.
* @param {Event} event - The form submission event.
* @param {string} tournamentId - The ID of the tournament.
* @param {number} entryFee - The entry fee for the tournament.
*/
async function joinTournament(event, tournamentId, entryFee) {
event.preventDefault();
const user = auth.currentUser;
if (!user) {
showToast('Login required!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
// Tournaments entry fee is always PKR
if ((currentUserData.wallet.PKR || 0) < entryFee) return showToast('Insufficient PKR balance!', true);
try {
const tournamentSnap = await db.ref(`tournaments/${tournamentId}/title`).once('value');
const tournamentTitle = tournamentSnap.val() || 'Unknown Tournament';
const newTransactionKey = db.ref().child('transactions').push().key;
const updates = {
[`/users/${user.uid}/wallet/PKR`]: (currentUserData.wallet.PKR || 0) - entryFee,
[`/participants/${tournamentId}/${user.uid}`]: { status: 'Participated', joined_at: new Date().toISOString() },
[`/transactions/${user.uid}/${newTransactionKey}`]: { amount: entryFee, type: 'debit', currency: 'PKR', description: `Entry: ${tournamentTitle}`, created_at: new Date().toISOString() }
};
await db.ref().update(updates);
showToast('Joined successfully!');
// Update profile content to reflect balance change
updateProfileContent();
} catch (error) {
console.error("Error joining tournament:", error);
showToast('Failed to join tournament. ' + error.message, true);
}
}
/**
* Handles adding money to the user's account via deposit request.
* @param {Event} event - The form submission event.
*/
async function addMoney(event) {
event.preventDefault();
const amount = Number(document.getElementById('add-amount').value);
const tid = document.getElementById('deposit-tid').value.trim();
const sourceType = document.getElementById('deposit-source-type').value.trim();
const acceptRulesCheckbox = document.getElementById('acceptDepositRules');
if (amount <= 0) {
return showToast('Amount must be positive!', true);
}
if (!tid) {
return showToast('Please enter the Transaction ID (TID)!', true);
}
if (!sourceType) {
return showToast('Please specify EasyPaisa or JazzCash!', true);
}
if (!acceptRulesCheckbox.checked) {
return showToast('Please accept the Deposit Rules to proceed.', true);
}
const user = auth.currentUser;
if (!user) {
showToast('Login required!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
try {
await db.ref(`pending_deposits/${user.uid}`).push({
amount: amount,
tid: tid,
source_details: sourceType,
status: 'pending',
currency: 'PKR',
created_at: new Date().toISOString(),
user_email: currentUserData.email || user.email,
user_username: currentUserData.username || 'N/A'
});
showToast('Deposit request submitted! Awaiting verification.');
toggleModal('addMoneyModal', false);
event.target.reset();
acceptRulesCheckbox.checked = false;
} catch (error) {
console.error("Error submitting deposit request:", error);
showToast('Failed to submit deposit request. ' + error.message, true);
}
}
/**
* Handles withdrawal requests from the user's account.
* @param {Event} event - The form submission event.
*/
async function withdrawMoney(event) {
event.preventDefault();
const amount = Number(document.getElementById('withdraw-amount').value);
const currency = document.getElementById('withdraw-currency').value;
const withdrawNumber = document.getElementById('withdraw-number').value.trim();
const ownerName = document.getElementById('withdraw-owner-name').value.trim();
const accountType = document.getElementById('withdraw-account-type').value;
const acceptRulesCheckbox = document.getElementById('acceptWithdrawalRules');
let minWithdraw = minWithdrawalAmount;
if(currency !== 'PKR') {
const rate = getExchangeRate('PKR', currency);
minWithdraw = minWithdrawalAmount * rate;
}
if (amount < minWithdraw) {
return showToast(`Minimum withdrawal is ${formatCurrency(minWithdraw, currency)}`, true);
}
if (!withdrawNumber || !ownerName || !accountType) {
return showToast('Please fill all withdrawal details!', true);
}
if (!acceptRulesCheckbox.checked) {
return showToast('Please accept the Withdrawal Rules to proceed.', true);
}
const user = auth.currentUser;
if (!user) {
showToast('Login required!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
if (amount > (currentUserData.wallet[currency] || 0)) {
return showToast(`Insufficient ${currency} funds!`, true);
}
if (currency === 'PKR' && currentUserData.daily_bonus_withdrawal_condition_active) {
const requiredReferrals = 10;
if ((currentUserData.referrals_earned_count || 0) < requiredReferrals) {
showToast(`Withdrawal from PKR requires at least ${requiredReferrals} referrals for bonus funds. You have ${currentUserData.referrals_earned_count || 0}.`, true);
return;
}
}
const uid = user.uid;
try {
const walletCurrencyRef = db.ref(`users/${uid}/wallet/${currency}`);
let committed = false;
await walletCurrencyRef.transaction(currentBalance => {
const balance = currentBalance !== null && typeof currentBalance === 'number' ? currentBalance : 0;
if (balance >= amount) {
return balance - amount;
}
return undefined;
}, async (error, _committed, snapshot) => {
if (error) {
console.error("Withdrawal deduction failed: ", error);
showToast("Withdrawal failed: Could not deduct funds.", true);
} else if (_committed) {
committed = true;
const transactionRef = db.ref(`transactions/${uid}`).push();
const transactionId = transactionRef.key;
await transactionRef.set({
amount: amount,
type: 'debit',
currency: currency,
description: `Withdrawal request for ${accountType} (${withdrawNumber})`,
status: 'pending',
created_at: new Date().toISOString()
});
const withdrawalRequestKey = db.ref("pending_withdrawals/" + uid).push().key;
await db.ref("pending_withdrawals/" + uid + "/" + withdrawalRequestKey).set({
amount: amount,
currency: currency,
status: "pending",
withdrawal_account: withdrawNumber,
withdrawal_owner_name: ownerName,
withdrawal_account_type: accountType,
created_at: new Date().toISOString(),
user_uid: uid,
user_email: currentUserData.email || user.email,
user_username: currentUserData.username || "N/A",
transaction_id_ref: transactionId
});
showToast("Withdrawal request sent! Amount deducted and awaiting admin approval.");
toggleModal("withdrawMoneyModal", false);
event.target.reset();
acceptRulesCheckbox.checked = false;
updateProfileContent(); // Update profile to reflect balance change
} else {
showToast("Withdrawal aborted: Insufficient funds or another operation occurred.", true);
}
});
} catch (error) {
console.error("Error during withdrawal request:", error);
showToast("Withdrawal failed. Please try again.", true);
}
}
/**
* Updates the user's preferred currency in Firebase.
* @param {Event} e - The change event from the select element.
*/
async function updateUserPreferredCurrency(e) {
const newCurrency = e.target.value;
if (!auth.currentUser || !currentUserData) return;
try {
await db.ref(`users/${auth.currentUser.uid}`).update({
preferred_currency: newCurrency
});
showToast(`Preferred currency set to ${newCurrency}!`);
updateProfileContent(); // Update header balance display
} catch (error) {
console.error("Error updating preferred currency:", error);
showToast("Failed to update preference.", true);
}
}
/**
* Handles adding a new game submitted by a user.
* Deducts cost from wallet based on play limit and adds game to Firebase `pending_games` node.
* @param {Event} event - The form submission event.
*/
async function addNewGame(event) {
event.preventDefault();
const user = auth.currentUser;
if (!user) {
showToast('Login required to add games!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
const gameTitle = document.getElementById('gameTitleInput').value.trim();
const gameCategory = document.getElementById('gameCategoryInput').value.trim();
const playLimit = parseInt(document.getElementById('gamePlayLimitInput').value, 10);
const thumbnailSource = document.querySelector('input[name="thumbnailSource"]:checked').value;
let gameImageUrl = '';
const gameImageFile = document.getElementById('gameImageFileInput').files[0];
const gameSource = document.querySelector('input[name="gameSource"]:checked').value;
let gameContent = ''; // This will hold either URL or HTML
if (thumbnailSource === 'url') {
gameImageUrl = document.getElementById('gameImageUrlInput').value.trim();
if (!gameImageUrl) {
showToast('Thumbnail URL is required!', true);
return;
}
} else { // 'upload'
if (!gameImageFile) {
showToast('Thumbnail image file is required!', true);
return;
}
}
if (gameSource === 'url') {
gameContent = document.getElementById('gameUrlInput').value.trim();
if (!gameContent) {
showToast('Game/Website URL is required!', true);
return;
}
} else { // 'html'
gameContent = document.getElementById('gameHtmlCodeInput').value.trim();
if (!gameContent) {
showToast('HTML Code is required!', true);
return;
}
}
if (!gameTitle || !gameCategory || isNaN(playLimit) || playLimit <= 0) {
showToast('Title, Category, and Play Limit (positive number) are required!', true);
return;
}
// NEW FEATURE: Category Restriction Validation
try {
const allApprovedGamesSnapshot = await db.ref('games').orderByChild('status').equalTo('approved').once('value');
const existingCategories = new Set();
allApprovedGamesSnapshot.forEach(childSnap => {
const game = childSnap.val();
if (game.category) {
existingCategories.add(game.category.trim().toLowerCase());
}
});
// If the submitted category is not 'Uncategorized' and doesn't exist among approved categories
if (gameCategory.toLowerCase() !== 'uncategorized' && !existingCategories.has(gameCategory.toLowerCase())) {
const existingCategoriesArray = Array.from(existingCategories);
const categoryList = existingCategoriesArray.length > 0 ? `(e.g., ${existingCategoriesArray.join(', ')})` : '';
showToast(`You must use an existing game category ${categoryList}. "Uncategorized" is also an option if applicable.`, true);
return;
}
} catch (error) {
console.error("Error validating game category:", error);
showToast('Failed to validate game category. Please try again.', true);
return;
}
const gameCost = playLimit;
if (!currentUserData || (currentUserData.wallet.PKR || 0) < gameCost) {
showToast(`Insufficient balance. You need ${formatCurrency(gameCost, 'PKR')} to add this game.`, true);
return;
}
try {
// Upload image to Firebase Storage if selected
if (thumbnailSource === 'upload' && gameImageFile) {
showToast('Uploading thumbnail image...', false);
const storageRef = storage.ref(`game_thumbnails/${user.uid}/${Date.now()}_${gameImageFile.name}`);
const uploadTask = storageRef.put(gameImageFile);
await new Promise((resolve, reject) => {
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
(error) => {
console.error("Image upload failed:", error);
showToast("Image upload failed! " + error.message, true);
reject(error);
},
async () => {
gameImageUrl = await uploadTask.snapshot.ref.getDownloadURL();
resolve();
}
);
});
}
const userWalletPKRRef = db.ref(`users/${user.uid}/wallet/PKR`);
const pendingGameRef = db.ref('pending_games').push();
const transactionRef = db.ref(`transactions/${user.uid}`).push();
let committed = false;
await userWalletPKRRef.transaction(currentBalance => {
if (currentBalance !== null && currentBalance >= gameCost) {
return currentBalance - gameCost;
}
return undefined;
}, async (error, _committed, snapshot) => {
if (error) {
console.error("Transaction failed: ", error);
showToast("Failed to deduct game cost. Please try again.", true);
} else if (_committed) {
committed = true;
await pendingGameRef.set({
title: gameTitle,
image_url: gameImageUrl,
game_url: (gameSource === 'url' ? gameContent : null), // Store URL if source is URL
game_html_code: (gameSource === 'html' ? gameContent : null), // Store HTML if source is HTML
content_type: gameSource, // 'url' or 'html'
created_by: user.uid,
created_by_username: currentUserData.username || 'N/A',
created_at: new Date().toISOString(),
status: 'pending', // Mark as pending for admin approval
play_limit: playLimit,
play_count: 0,
notified_play_limit_reached: false // Flag to track if user has been notified
});
await transactionRef.set({
amount: gameCost,
type: 'debit',
currency: 'PKR',
description: `Cost for game submission (${playLimit} plays): ${gameTitle}`,
created_at: new Date().toISOString()
});
showToast('Game submitted successfully! It will appear on the homepage after admin approval.');
toggleModal('addGameModal', false);
event.target.reset();
document.getElementById('gameSubmissionCost').textContent = 'Cost: PKR 0';
updateProfileContent(); // Update profile content (e.g., submitted games list)
} else {
showToast(`Transaction aborted: Insufficient balance. You need ${formatCurrency(gameCost, 'PKR')} to add a game.`, true);
}
});
} catch (error) {
console.error("Error adding game or deducting balance:", error);
showToast('Failed to submit game. Please try again.', true);
}
}
/**
* Handles sending a contact message from the user to admin.
* @param {Event} event - The form submission event.
*/
async function sendContactMessage(event) {
event.preventDefault();
const user = auth.currentUser;
if (!user) {
showToast('Login required to send a message!', true);
toggleModal('authModal', true); // Open login modal
return;
}
if (!currentUserData || currentUserData.locked) {
auth.signOut();
const lockReason = currentUserData?.lockReason ? `Reason: ${currentUserData.lockReason}` : '';
return showToast(`Your account is locked. Please contact support. ${lockReason}`, true);
}
const subject = document.getElementById('contactSubject').value.trim();
const message = document.getElementById('contactMessage').value.trim();
if (!subject || !message) {
showToast('Subject and Message cannot be empty!', true);
return;
}
try {
await db.ref('contact_messages').push({
userId: user.uid,
username: currentUserData.username || 'N/A',
email: currentUserData.email || user.email,
subject: subject,
message: message,
timestamp: new Date().toISOString(),
status: 'pending'
});
showToast('Message sent successfully!');
toggleModal('contactUsModal', false);
event.target.reset();
if (document.getElementById('mySupportMessagesSection').style.display === 'block') {
showMySupportMessages(); // Refresh support messages if visible
}
} catch (error) {
console.error("Error sending contact message:", error);
showToast('Failed to send message. ' + error.message, true);
}
}
/**
* Opens the currency exchange modal and populates it with current user balances.
*/
function openExchangeCurrencyModal() {
if (!currentUserData || !currentUserData.wallet) {
showToast('Please login to exchange currency.', true);
toggleModal('authModal', true); // Open login modal
return;
}
const balanceInfoEl = document.getElementById('current-balance-exchange-info');
let balancesHtml = 'Your balances: ';
['PKR', 'INR', 'USD'].forEach(currency => {
const balance = currentUserData.wallet[currency] || 0;
balancesHtml += `
${formatCurrency(balance, currency)} `;
});
balanceInfoEl.innerHTML = balancesHtml;
document.getElementById('exchange-amount').value = '';
document.getElementById('exchange-from-currency').value = currentUserData.preferred_currency || 'PKR';
document.getElementById('exchange-to-currency').value = (currentUserData.preferred_currency === 'PKR' ? 'INR' : 'PKR');
document.getElementById('exchange-error-message').style.display = 'none';
document.getElementById('exchange-result-message').style.display = 'none';
toggleModal('exchangeCurrencyModal', true);
}
/**
* Calculates the exchange rate between two currencies.
* @param {string} fromCurrency - The currency to convert from.
* @param {string} toCurrency - The currency to convert to.
* @returns {number} The exchange rate, or 0 if not found/invalid.
*/
function getExchangeRate(fromCurrency, toCurrency) {
if (fromCurrency === toCurrency) {
return 1;
}
const key = `${fromCurrency}_to_${toCurrency}`;
return exchangeRates[key] || 0;
}
/**
* Handles the currency exchange process.
* @param {Event} e - The form submission event.
*/
async function exchangeCurrency(e) {
e.preventDefault();
const amountToExchange = Number(document.getElementById('exchange-amount').value);
const fromCurrency = document.getElementById('exchange-from-currency').value;
const toCurrency = document.getElementById('exchange-to-currency').value;
const errorMessageEl = document.getElementById('exchange-error-message');
const resultMessageEl = document.getElementById('exchange-result-message');
errorMessageEl.style.display = 'none';
resultMessageEl.style.display = 'none';
if (!amountToExchange || amountToExchange <= 0) {
errorMessageEl.textContent = 'Please enter a valid amount to exchange.';
errorMessageEl.style.display = 'block';
return;
}
if (fromCurrency === toCurrency) {
errorMessageEl.textContent = 'Cannot exchange to the same currency.';
errorMessageEl.style.display = 'block';
return;
}
if (!auth.currentUser || !currentUserData || !currentUserData.wallet) {
errorMessageEl.textContent = 'User data not loaded. Please re-login.';
errorMessageEl.style.display = 'block';
return;
}
const availableAmount = currentUserData.wallet[fromCurrency] || 0;
if (availableAmount < amountToExchange) {
errorMessageEl.textContent = `Insufficient ${fromCurrency} balance. You have ${formatCurrency(availableAmount, fromCurrency)}.`;
errorMessageEl.style.display = 'block';
return;
}
const rate = getExchangeRate(fromCurrency, toCurrency);
if (rate === 0) {
errorMessageEl.textContent = `Exchange rate for ${fromCurrency} to ${toCurrency} not found.`;
errorMessageEl.style.display = 'block';
return;
}
const convertedAmount = amountToExchange * rate;
const userId = auth.currentUser.uid;
let shouldChangePreferredCurrency = false;
try {
await db.ref(`users/${userId}/wallet`).transaction(currentWallet => {
if (currentWallet) {
const fromBalance = currentWallet[fromCurrency] || 0;
if (fromBalance >= amountToExchange) {
currentWallet[fromCurrency] = fromBalance - amountToExchange;
currentWallet[toCurrency] = (currentWallet[toCurrency] || 0) + convertedAmount;
if (currentUserData.preferred_currency === fromCurrency && currentWallet[fromCurrency] < 0.01) {
shouldChangePreferredCurrency = true;
}
return currentWallet;
}
}
return undefined;
}, async (error, committed, snapshot) => {
if (error) {
console.error('Currency exchange transaction failed:', error);
errorMessageEl.textContent = `Exchange failed: ${error.message}`;
errorMessageEl.style.display = 'block';
} else if (committed) {
if (shouldChangePreferredCurrency) {
await db.ref(`users/${userId}`).update({ preferred_currency: toCurrency });
}
await db.ref(`transactions/${userId}`).push({
amount: amountToExchange,
type: 'exchange',
currency: fromCurrency,
description: `Exchanged ${formatCurrency(amountToExchange, fromCurrency)} to ${formatCurrency(convertedAmount, toCurrency)}`,
exchange_from_currency: fromCurrency,
exchange_to_currency: toCurrency,
exchanged_amount: convertedAmount,
created_at: new Date().toISOString()
});
resultMessageEl.textContent = `Successfully exchanged ${formatCurrency(amountToExchange, fromCurrency)} for ${formatCurrency(convertedAmount, toCurrency)}!`;
resultMessageEl.style.display = 'block';
showToast(resultMessageEl.textContent);
// Re-open modal to show updated balances, then close after a short delay
setTimeout(() => openExchangeCurrencyModal(), 100);
} else {
errorMessageEl.textContent = 'Exchange aborted: Insufficient funds or another operation occurred.';
errorMessageEl.style.display = 'block';
}
});
} catch (error) {
console.error("Error during exchange:", error);
errorMessageEl.textContent = `An unexpected error occurred: ${error.message}`;
errorMessageEl.style.display = 'block';
}
}
/**
* Toggles the visibility of the main profile view vs. policy content area.
*/
function showMainProfileView() {
document.getElementById('mainProfileView').style.display = 'block';
document.getElementById('policyContentArea').style.display = 'none';
document.getElementById('policy-content-display').style.display = 'none';
document.getElementById('mySupportMessagesSection').style.display = 'none';
}
/**
* Displays a specific policy section or the contact messages.
* @param {string} sectionKey - The key for the policy (e.g., 'privacy_policy') or 'my_support_messages'.
*/
async function showPolicySection(sectionKey) {
document.getElementById('mainProfileView').style.display = 'none';
document.getElementById('policyContentArea').style.display = 'block';
document.getElementById('mySupportMessagesSection').style.display = 'none'; // Hide support messages by default
const policyDisplay = document.getElementById('policy-content-display');
const policyTitle = document.getElementById('policy-display-title');
const policyBody = document.getElementById('policy-display-body');
if (!policyDisplay || !policyTitle || !policyBody) {
console.warn("Policy display elements not found.");
showToast("Error: Policy display elements missing.", true);
return;
}
policyDisplay.style.display = 'block';
policyTitle.textContent = 'Loading...';
policyBody.innerHTML = '
Loading policy content...
';
try {
const policySnap = await db.ref(`app_content/${sectionKey}`).once('value');
const policyData = policySnap.val();
if (policyData) {
policyTitle.textContent = policyData.displayTitle || sectionKey.replace(/_/g, ' ').toUpperCase();
policyBody.innerHTML = policyData.body || '
Content not available.
';
} else {
policyTitle.textContent = 'Content Not Found';
policyBody.innerHTML = '
The requested policy content could not be loaded.
';
}
} catch (error) {
console.error("Error loading policy:", error);
policyTitle.textContent = 'Error';
policyBody.innerHTML = '
Failed to load policy content due to an error.
';
}
}
/**
* Displays the user's support messages.
*/
async function showMySupportMessages() {
if (!auth.currentUser) {
showToast('Login required to view support messages!', true);
toggleModal('authModal', true); // Open login modal
return;
}
document.getElementById('mainProfileView').style.display = 'none';
document.getElementById('policyContentArea').style.display = 'block';
document.getElementById('policy-content-display').style.display = 'none'; // Hide general policy display
document.getElementById('mySupportMessagesSection').style.display = 'block';
const listEl = document.getElementById('user-contact-messages-list');
if (!listEl) { console.warn("user-contact-messages-list not found."); return; }
listEl.innerHTML = '
Loading your messages...
';
const userId = auth.currentUser.uid;
try {
const messagesSnap = await db.ref('contact_messages').orderByChild('userId').equalTo(userId).once('value');
const messagesData = messagesSnap.val();
if (!messagesData) {
listEl.innerHTML = '
You have not sent any support messages yet.
';
return;
}
const messagesArray = [];
for (const id in messagesData) {
messagesArray.push({ id, ...messagesData[id] });
}
messagesArray.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
listEl.innerHTML = messagesArray.map(msg => {
const statusColor = msg.status === 'resolved' ? 'text-green-600' : (msg.status === 'replied' ? 'text-blue-600' : 'text-orange-500');
return `
${escapeJsStringForHtmlAttribute(msg.subject)}
${escapeJsStringForHtmlAttribute(msg.status)}
${escapeJsStringForHtmlAttribute(msg.message)}
${msg.adminReply ? `
Admin Reply: ${escapeJsStringForHtmlAttribute(msg.adminReply)}
` : ''}
${new Date(msg.timestamp).toLocaleString()}
`;
}).join('');
} catch (error) {
console.error("Error loading support messages:", error);
listEl.innerHTML = '
Error loading your support messages.
';
}
}
/**
* Logs the current user out of the application.
*/
function logout() {
auth.signOut();
// After logout, re-render the profile page to show the login form
renderProfilePage(document.getElementById('profilePageContainer'));
}
/**
* Sends a password reset email to the current user.
*/
function changePassword() {
const user = auth.currentUser;
if (user && user.email) {
auth.sendPasswordResetEmail(user.email)
.then(() => showToast(`Password reset link sent to ${user.email}.`))
.catch(err => showToast(err.message, true));
} else {
showToast("No active user or email found.", true);
toggleModal('authModal', true); // Open login modal
}
}
/**
* Initializes the application once the DOM is fully loaded.
*/
document.addEventListener('DOMContentLoaded', async () => {
// Attach event listeners for the modal login/signup forms (if they exist)
attachLoginSignupFormListeners('Modal');
// Attach event listeners for forms and buttons in profile
document.getElementById('addMoneyForm')?.addEventListener('submit', addMoney);
document.getElementById('withdrawMoneyForm')?.addEventListener('submit', withdrawMoney);
document.getElementById('addGameForm')?.addEventListener('submit', addNewGame);
document.getElementById('contactUsForm')?.addEventListener('submit', sendContactMessage);
document.getElementById('exchangeCurrencyForm')?.addEventListener('submit', exchangeCurrency);
// AddGameModal specific listeners for toggling input fields
const thumbnailSourceRadios = document.querySelectorAll('input[name="thumbnailSource"]');
const gameImageUrlInput = document.getElementById('gameImageUrlInput');
const gameImageFileInput = document.getElementById('gameImageFileInput');
thumbnailSourceRadios.forEach(radio => {
radio.addEventListener('change', (event) => {
if (event.target.value === 'url') {
gameImageUrlInput.classList.remove('hidden');
gameImageUrlInput.setAttribute('required', 'required');
gameImageFileInput.classList.add('hidden');
gameImageFileInput.removeAttribute('required');
gameImageFileInput.value = ''; // Clear file input
} else {
gameImageUrlInput.classList.add('hidden');
gameImageUrlInput.removeAttribute('required');
gameImageUrlInput.value = ''; // Clear URL input
gameImageFileInput.classList.remove('hidden');
gameImageFileInput.setAttribute('required', 'required');
}
});
});
const gameSourceRadios = document.querySelectorAll('input[name="gameSource"]');
const gameUrlInput = document.getElementById('gameUrlInput');
const gameHtmlCodeInput = document.getElementById('gameHtmlCodeInput');
gameSourceRadios.forEach(radio => {
radio.addEventListener('change', (event) => {
if (event.target.value === 'url') {
gameUrlInput.classList.remove('hidden');
gameUrlInput.setAttribute('required', 'required');
gameHtmlCodeInput.classList.add('hidden');
gameHtmlCodeInput.removeAttribute('required');
gameHtmlCodeInput.value = ''; // Clear HTML input
} else {
gameUrlInput.classList.add('hidden');
gameUrlInput.removeAttribute('required');
gameUrlInput.value = ''; // Clear URL input
gameHtmlCodeInput.classList.remove('hidden');
gameHtmlCodeInput.setAttribute('required', 'required');
}
});
});
// End AddGameModal specific listeners
const gamePlayLimitInput = document.getElementById('gamePlayLimitInput');
const gameSubmissionCostDisplay = document.getElementById('gameSubmissionCost');
if (gamePlayLimitInput && gameSubmissionCostDisplay) {
gamePlayLimitInput.addEventListener('input', () => {
const playLimit = parseInt(gamePlayLimitInput.value, 10);
if (!isNaN(playLimit) && playLimit > 0) {
gameSubmissionCostDisplay.textContent = `Cost: ${formatCurrency(playLimit, 'PKR')}`;
} else {
gameSubmissionCostDisplay.textContent = 'Cost: PKR 0';
}
});
}
// Initially render the profile page content based on login status
// The onAuthStateChanged listener will call renderProfilePage again once Firebase auth is initialized.
renderProfilePage(document.getElementById('profilePageContainer'));
});