Fixes duplicate placeholder cover image on the 3 newest blog posts, broken aspect-ratio classes on the albums listing page, and a non-responsive fixed sidebar on the chat page.
404 lines
12 KiB
JavaScript
404 lines
12 KiB
JavaScript
/**
|
|
* Membership System Frontend
|
|
* Handles authentication, paywall checks, and download tracking
|
|
*/
|
|
|
|
const getApiBase = () => {
|
|
const host = window.location.hostname;
|
|
const isLocal = host === 'localhost' || host === '127.0.0.1' || host.startsWith('10.');
|
|
return isLocal ? 'http://' + host + ':5001' : window.location.origin.replace(/\/$/, '');
|
|
};
|
|
const API_BASE = getApiBase() + '/api';
|
|
|
|
let authState = {
|
|
authenticated: false,
|
|
user: null,
|
|
token: null
|
|
};
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
checkAuthStatus();
|
|
setupDownloadTracking();
|
|
setupContactForm();
|
|
});
|
|
|
|
/**
|
|
* Check authentication status
|
|
*/
|
|
async function checkAuthStatus() {
|
|
const token = localStorage.getItem('membership_token');
|
|
if (!token) {
|
|
updateAuthUI(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/check`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.authenticated) {
|
|
authState = {
|
|
authenticated: true,
|
|
user: data,
|
|
token: token
|
|
};
|
|
updateAuthUI(true);
|
|
checkPaywallAccess();
|
|
} else {
|
|
localStorage.removeItem('membership_token');
|
|
updateAuthUI(false);
|
|
}
|
|
} catch (error) {
|
|
console.error('Auth check failed:', error);
|
|
updateAuthUI(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update UI based on authentication state
|
|
*/
|
|
function updateAuthUI(authenticated) {
|
|
const loginElements = document.querySelectorAll('.membership-login');
|
|
const logoutElements = document.querySelectorAll('.membership-logout');
|
|
const memberElements = document.querySelectorAll('.membership-only');
|
|
const guestElements = document.querySelectorAll('.guest-only');
|
|
|
|
if (authenticated) {
|
|
loginElements.forEach(el => el.style.display = 'none');
|
|
logoutElements.forEach(el => el.style.display = 'block');
|
|
memberElements.forEach(el => el.style.display = 'block');
|
|
guestElements.forEach(el => el.style.display = 'none');
|
|
|
|
// Update user name display
|
|
const userNameElements = document.querySelectorAll('.user-name');
|
|
userNameElements.forEach(el => {
|
|
if (authState.user && authState.user.name) {
|
|
el.textContent = authState.user.name;
|
|
}
|
|
});
|
|
} else {
|
|
loginElements.forEach(el => el.style.display = 'block');
|
|
logoutElements.forEach(el => el.style.display = 'none');
|
|
memberElements.forEach(el => el.style.display = 'none');
|
|
guestElements.forEach(el => el.style.display = 'block');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check paywall access and show/hide content
|
|
*/
|
|
async function checkPaywallAccess() {
|
|
if (!authState.authenticated) {
|
|
// Check download count for IP
|
|
try {
|
|
const response = await fetch(`${API_BASE}/download/count`);
|
|
const data = await response.json();
|
|
|
|
if (data.limit_reached || (data.count >= data.limit && !data.unlimited)) {
|
|
showPaywall();
|
|
}
|
|
} catch (error) {
|
|
console.error('Download count check failed:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show paywall message
|
|
*/
|
|
function showPaywall() {
|
|
const paywallElements = document.querySelectorAll('.paywall-message');
|
|
paywallElements.forEach(el => {
|
|
el.style.display = 'block';
|
|
});
|
|
|
|
const paywallContent = document.querySelectorAll('.paywall-content');
|
|
paywallContent.forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hide paywall message
|
|
*/
|
|
function hidePaywall() {
|
|
const paywallElements = document.querySelectorAll('.paywall-message');
|
|
paywallElements.forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
|
|
const paywallContent = document.querySelectorAll('.paywall-content');
|
|
paywallContent.forEach(el => {
|
|
el.style.display = 'block';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup download tracking
|
|
*/
|
|
function setupDownloadTracking() {
|
|
document.addEventListener('click', async function(e) {
|
|
const downloadLink = e.target.closest('a[download], a[href*="/library/"], a[href*="/pdfs/"]');
|
|
if (!downloadLink) return;
|
|
|
|
const filePath = downloadLink.getAttribute('href') || downloadLink.getAttribute('download');
|
|
if (!filePath) return;
|
|
|
|
// Check if user is authenticated
|
|
if (!authState.authenticated) {
|
|
// Check download count
|
|
try {
|
|
const response = await fetch(`${API_BASE}/download/count`);
|
|
const data = await response.json();
|
|
|
|
if (data.count >= data.limit && !data.unlimited) {
|
|
e.preventDefault();
|
|
alert('You have reached the free download limit. Please sign up for membership to download unlimited resources.');
|
|
window.location.href = '/membership/';
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
console.error('Download check failed:', error);
|
|
}
|
|
}
|
|
|
|
// Track download
|
|
try {
|
|
await fetch(`${API_BASE}/download/track`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(authState.token ? { 'Authorization': `Bearer ${authState.token}` } : {})
|
|
},
|
|
body: JSON.stringify({ file_path: filePath })
|
|
});
|
|
} catch (error) {
|
|
console.error('Download tracking failed:', error);
|
|
// Don't prevent download if tracking fails
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup contact form
|
|
*/
|
|
function setupContactForm() {
|
|
const contactForm = document.getElementById('contact-form');
|
|
if (!contactForm) return;
|
|
|
|
contactForm.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
if (!authState.authenticated) {
|
|
alert('Please log in to submit the contact form.');
|
|
window.location.href = '/membership/';
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData(contactForm);
|
|
const data = {
|
|
name: formData.get('name'),
|
|
email: formData.get('email'),
|
|
message: formData.get('message')
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/contact`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authState.token}`
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (response.ok) {
|
|
alert('Message sent successfully!');
|
|
contactForm.reset();
|
|
} else {
|
|
alert(result.error || 'Failed to send message');
|
|
}
|
|
} catch (error) {
|
|
console.error('Contact form error:', error);
|
|
alert('Failed to send message. Please try again.');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* User login
|
|
*/
|
|
async function login(email, password) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
localStorage.setItem('membership_token', data.token);
|
|
authState = {
|
|
authenticated: true,
|
|
user: data,
|
|
token: data.token
|
|
};
|
|
updateAuthUI(true);
|
|
hidePaywall();
|
|
return { success: true };
|
|
} else {
|
|
return { success: false, error: data.error };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: 'Login failed. Please try again.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* User logout
|
|
*/
|
|
async function logout() {
|
|
const token = localStorage.getItem('membership_token');
|
|
if (token) {
|
|
try {
|
|
await fetch(`${API_BASE}/auth/logout`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
}
|
|
|
|
localStorage.removeItem('membership_token');
|
|
authState = {
|
|
authenticated: false,
|
|
user: null,
|
|
token: null
|
|
};
|
|
updateAuthUI(false);
|
|
window.location.reload();
|
|
}
|
|
|
|
/**
|
|
* User registration
|
|
*/
|
|
async function register(email, password, name) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/register`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ email, password, name })
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
return { success: true, message: data.message };
|
|
} else {
|
|
return { success: false, error: data.error };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: 'Registration failed. Please try again.' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submit testimonial
|
|
*/
|
|
async function submitTestimonial(content, authorName) {
|
|
if (!authState.authenticated) {
|
|
return { success: false, error: 'Please log in to submit a testimonial' };
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/testimonials`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authState.token}`
|
|
},
|
|
body: JSON.stringify({ content, author_name: authorName })
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
return { success: true, message: data.message };
|
|
} else {
|
|
return { success: false, error: data.error };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: 'Failed to submit testimonial' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load testimonials
|
|
*/
|
|
async function loadTestimonials() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/testimonials`);
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
return { success: true, testimonials: data.testimonials };
|
|
} else {
|
|
return { success: false, error: data.error };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, error: 'Failed to load testimonials' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the user has reached their download limit (guests only)
|
|
*/
|
|
async function checkDownloadLimit() {
|
|
if (authState.authenticated) {
|
|
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none');
|
|
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block');
|
|
document.querySelectorAll('.guest-only').forEach(el => el.style.display = 'none');
|
|
document.querySelectorAll('.membership-only').forEach(el => el.style.display = 'block');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/download/count`);
|
|
const data = await response.json();
|
|
|
|
if (data.count >= data.limit && !data.unlimited) {
|
|
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'block');
|
|
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'none');
|
|
} else {
|
|
document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none');
|
|
document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block');
|
|
}
|
|
} catch (error) {
|
|
console.error('Download limit check failed:', error);
|
|
}
|
|
}
|
|
|
|
// Export functions for use in Alpine.js or other scripts
|
|
window.MembershipAPI = {
|
|
login,
|
|
logout,
|
|
register,
|
|
submitTestimonial,
|
|
loadTestimonials,
|
|
checkAuthStatus,
|
|
checkDownloadLimit,
|
|
getAuthState: () => authState
|
|
};
|
|
|