Initial commit: site code, layouts, content (images added separately)
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.
This commit is contained in:
403
static/js/membership.js
Normal file
403
static/js/membership.js
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* 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
|
||||
};
|
||||
|
||||
0
static/pdfs/cleaning.pdf
Normal file
0
static/pdfs/cleaning.pdf
Normal file
0
static/pdfs/deficiencies.pdf
Normal file
0
static/pdfs/deficiencies.pdf
Normal file
0
static/pdfs/dutch-buckets.pdf
Normal file
0
static/pdfs/dutch-buckets.pdf
Normal file
0
static/pdfs/dwc-basics.pdf
Normal file
0
static/pdfs/dwc-basics.pdf
Normal file
0
static/pdfs/lighting.pdf
Normal file
0
static/pdfs/lighting.pdf
Normal file
0
static/pdfs/nft-guide.pdf
Normal file
0
static/pdfs/nft-guide.pdf
Normal file
0
static/pdfs/pests.pdf
Normal file
0
static/pdfs/pests.pdf
Normal file
0
static/pdfs/ph-ec-mastery.pdf
Normal file
0
static/pdfs/ph-ec-mastery.pdf
Normal file
0
static/pdfs/seeds.pdf
Normal file
0
static/pdfs/seeds.pdf
Normal file
0
static/pdfs/sterile-res.pdf
Normal file
0
static/pdfs/sterile-res.pdf
Normal file
5
static/robots.txt
Normal file
5
static/robots.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://hydrolord.thetempleofdoom.com/sitemap.xml
|
||||
|
||||
Reference in New Issue
Block a user