This commit is contained in:
Omar Najjar 2025-04-13 00:44:33 +10:00
commit b7b286c2f0
4 changed files with 4906 additions and 91 deletions

2316
cloudflare worker.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -25,62 +25,62 @@
<!-- Icon -->
<meta name="twitter:image"
content="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
content="https://theradicalparty.com/memes/favicon.jpg">
<meta name="twitter:image:src"
content="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
content="https://theradicalparty.com/memes/favicon.jpg">
<meta name="twitter:image:alt" content="RADICAL Logo">
<link rel="shortcut icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg" type="image/x-icon">
<link rel="shortcut icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg"
<link rel="shortcut icon" href="https://theradicalparty.com/memes/favicon.jpg" type="image/x-icon">
<link rel="shortcut icon" href="https://theradicalparty.com/memes/favicon.jpg"
type="image/jpg">
<link rel="mask-icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg"
<link rel="mask-icon" href="https://theradicalparty.com/memes/favicon.jpg"
color="#ff0099">
<meta property="og:image"
content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
content="https://theradicalparty.com/memes/mickeymeta.PNG">
<meta property="og:image:url"
content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
content="https://theradicalparty.com/memes/mickeymeta.PNG">
<meta property="og:image:secure_url"
content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
content="https://theradicalparty.com/memes/mickeymeta.PNG">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="256">
<meta property="og:image:height" content="256">
<meta property="og:image:alt" content="The RADICAL logo.">
<meta itemprop="image" content="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
<meta itemprop="image" content="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg" sizes="512x512"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg" sizes="256x256"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg" sizes="128x128"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg" sizes="64x64"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg" sizes="32x32"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="icon" type="image/jpg"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="512x512"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="256x256"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="128x128"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="64x64"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="32x32"
href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
href="https://theradicalparty.com/memes/favicon.jpg">
<!-- APPLE TOUCH ICONS -->
<link rel="apple-touch-icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="152x152" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="180x180" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="167x167" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg">
<link rel="apple-touch-icon" href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="152x152" href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="180x180" href="https://theradicalparty.com/memes/favicon.jpg">
<link rel="apple-touch-icon" sizes="167x167" href="https://theradicalparty.com/memes/favicon.jpg">
<!-- SPLASH SCREEN IMAGES -->
<!-- iPhone X (1125px x 2436px) -->
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" href="https://radical.omar-c29.workers.dev/memes/splash-1125x2436.png">
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" href="https://theradicalparty.com/memes/splash-1125x2436.png">
<!-- iPhone 8, 7, 6s, 6 (750px x 1334px) -->
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="https://radical.omar-c29.workers.dev/memes/splash-750x1334.png">
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="https://theradicalparty.com/memes/splash-750x1334.png">
<!-- iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus (1242px x 2208px) -->
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3)" href="https://radical.omar-c29.workers.dev/memes/splash-1242x2208.png">
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3)" href="https://theradicalparty.com/memes/splash-1242x2208.png">
<!-- Author -->
@ -156,7 +156,7 @@
"@type": "WebSite",
"name": "RADICAL",
"description": "Australia's direct democracy movement. Share policy ideas, vote on proposals, take back control. Vibe, Vote, Veto.",
"image": "https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG",
"image": "https://theradicalparty.com/memes/mickeymeta.PNG",
"url": "https://theradicalparty.com",
"author": {
"@type": "Organization",
@ -265,9 +265,33 @@ function smartCopyShare(proposalId, proposalText) {
copyToClipboard(link);
}
}
// Configuration
const API_BASE_URL = 'https://radical.omar-c29.workers.dev/api'; // Replace with your actual worker URL
function getAPIBaseURL() {
const hostname = window.location.hostname;
const protocol = window.location.protocol;
switch(hostname) {
case 'localhost':
return 'http://localhost:8787/api';
case 'theradicalparty.com':
case 'www.theradicalparty.com':
return `${protocol}//theradicalparty.com/api`;
case 'radical.omar-c29.workers.dev':
return `${protocol}//radical.omar-c29.workers.dev/api`;
default:
return `${protocol}//${hostname}/api`;
}
}
const API_BASE_URL = getAPIBaseURL();
function getMediaURL(path) {
const baseURL = window.location.hostname === 'localhost'
? 'http://localhost:8787'
: `https://${window.location.hostname}`;
return `${baseURL}${path}`;
}
// Character limit
const CHAR_LIMIT = 280;
@ -417,34 +441,27 @@ function smartCopyShare(proposalId, proposalText) {
// Create or get user in DB
async function createUser(user) {
try {
console.log("Creating user:", user); // Log the user data
const response = await fetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: user.id,
name: user.name
})
});
if (!response.ok) {
const errorData = await response.json();
console.error("User creation API error:", errorData);
throw new Error('Failed to create user');
}
const data = await response.json();
console.log("User creation successful:", data);
return data;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
try {
console.log("Creating user:", user);
const data = await safeFetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: user.id,
name: user.name
})
});
console.log("User creation successful:", data);
return data;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Animation for successful post
function animatePostSuccess() {
@ -543,28 +560,101 @@ function smartCopyShare(proposalId, proposalText) {
}, 500);
}, 3000);
}
// Improved fetch implementation
async function safeFetch(url, options = {}) {
try {
// Ensure URL is a string
const requestUrl = typeof url === 'string' ? url : url.toString();
// Default options
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
// Add any additional default headers
},
mode: 'cors',
credentials: 'same-origin'
};
// Merge provided options with defaults
const fetchOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...(options.headers || {})
}
};
// Log fetch details for debugging
console.log('Fetch Request:', {
url: requestUrl,
method: fetchOptions.method,
headers: Object.keys(fetchOptions.headers)
});
// Perform fetch with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
fetchOptions.signal = controller.signal;
const response = await window.fetch(requestUrl, fetchOptions);
clearTimeout(timeoutId);
// Check for successful response
if (!response.ok) {
const errorBody = await response.text();
console.error('Fetch Error Response:', {
status: response.status,
statusText: response.statusText,
body: errorBody
});
throw new Error(`HTTP error! status: ${response.status}, message: ${errorBody}`);
}
return await response.json();
} catch (error) {
console.error('Fetch Error:', {
message: error.message,
name: error.name,
stack: error.stack
});
// Handle specific error types
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
// Fetch proposals from API
async function fetchProposals() {
try {
// Show loading indicator
showLoadingIndicator();
const sortSelect = document.getElementById('sort-select');
const sortBy = sortSelect ? sortSelect.value : 'newest';
const response = await fetch(`${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch proposals');
}
const proposals = await response.json();
renderProposals(proposals);
} catch (error) {
console.error('Error fetching proposals:', error);
showErrorMessage('Oops! Failed to load petitions. Parliament might be censoring us already!');
}
}
try {
// Show loading indicator
showLoadingIndicator();
const sortSelect = document.getElementById('sort-select');
const sortBy = sortSelect ? sortSelect.value : 'newest';
const data = await safeFetch(
`${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`
);
renderProposals(data);
} catch (error) {
console.error('Error fetching proposals:', error);
showErrorMessage('Oops! Failed to load petitions. Try refreshing the page.');
}
}
// Handle voting
async function handleVote(proposalId, voteType) {
@ -760,7 +850,7 @@ async function submitVoteToServer(proposalId, voteType, isPetition = false, peti
voteData.petitionDetails = petitionDetails;
}
const response = await fetch(`${API_BASE_URL}/votes`, {
const response = await safeFetch(`${API_BASE_URL}/votes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -881,7 +971,7 @@ async function submitVote(proposalId, voteType, isPetition = false, petitionDeta
</button>
<span class="net-votes ${voteClass}">${netVotes > 0 ? '+' : ''}${netVotes}</span>
<div class="custom-copy-button" data-id="${proposal.id}" style="display: inline-block; background-color: #000000; border: 0px solid #333333; color: #aaaaaa; padding: 4px 8px; font-size: 20px; position: relative; z-index: 10; cursor: pointer; vertical-align: middle;" onclick="event.stopPropagation(); event.preventDefault(); smartCopyShare('${proposal.id}', '${encodeURIComponent(proposal.text)}');">
<img src="https://radical.omar-c29.workers.dev/memes/share-arrow-icon-md.png" alt="Share" style="width: 20px; height: 14px; filter: brightness(0) invert(1); pointer-events: none;">
<img src="https://theradicalparty.com/memes/share-arrow-icon-md.png" alt="Share" style="width: 20px; height: 14px; filter: brightness(0) invert(1); pointer-events: none;">
</div>
</div>
<div class="proposal-time">${formatDate(proposal.timestamp)}</div>
@ -981,7 +1071,7 @@ updateCommentCount(proposal.id);
const uploadIcon = document.createElement('img');
uploadIcon.className = 'meme-upload-icon';
uploadIcon.src = 'https://radical.omar-c29.workers.dev/memes/pepe.jpg';
uploadIcon.src = 'https://theradicalparty.com/memes/pepe.jpg';
uploadIcon.alt = 'Pepe';
uploadIcon.style.width = '20px';
uploadIcon.style.height = '20px';
@ -1117,7 +1207,7 @@ uploadIcon.style.marginRight = '8px';
const trending = Math.random() > 0.8; // Randomly mark some as trending
// Create the proposal first
const createResponse = await fetch(`${API_BASE_URL}/proposals`, {
const createResponse = await safeFetch(`${API_BASE_URL}/proposals`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -1466,7 +1556,7 @@ uploadIcon.style.marginRight = '8px';
showToastMessage('Loading petition...', '#11cc77');
// Fetch the specific proposal
const response = await fetch(`${API_BASE_URL}/proposals?userId=${currentUser.id}`);
const response = await safeFetch(`${API_BASE_URL}/proposals?userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch proposal');
@ -1761,7 +1851,7 @@ async function loadModalComments(proposalId) {
try {
// Include the current user ID to get their votes
const response = await fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`);
const response = await safeFetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch comments');
@ -1880,7 +1970,7 @@ async function handleCommentVote(commentId, voteType) {
// Submit vote to server
try {
const response = await fetch(`${API_BASE_URL}/comment-votes`, {
const response = await safeFetch(`${API_BASE_URL}/comment-votes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -1938,7 +2028,7 @@ async function loadComments(proposalId) {
try {
// Include the current user ID to get their votes
const response = await fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`);
const response = await safeFetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch comments');
@ -2028,7 +2118,7 @@ async function submitComment(proposalId, form) {
submitButton.disabled = true;
try {
const response = await fetch(`${API_BASE_URL}/comments`, {
const response = await safeFetch(`${API_BASE_URL}/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -2257,7 +2347,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.log("Music prompt element found, adding click listener");
// Audio file URL - this should point to your audio file in R2
const audioUrl = 'https://radical.omar-c29.workers.dev/memes/steppingout.mp3';
const audioUrl = getMediaURL('/audio/steppingout.mp3');
// Initialize music only when the "hit me" button is clicked
musicPrompt.addEventListener('click', function(e) {
@ -2342,7 +2432,7 @@ function updateCommentCount(proposalId) {
const commentsToggle = document.getElementById(`comments-toggle-${proposalId}`);
if (!commentsToggle) return;
fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}`)
safeFetch(`${API_BASE_URL}/comments?proposalId=${proposalId}`)
.then(response => response.json())
.then(comments => {
const commentCount = comments.length;
@ -2360,7 +2450,7 @@ function loadComments(proposalId) {
commentsList.innerHTML = '<div class="comments-loading">Loading comments...</div>';
fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}`)
safeFetch(`${API_BASE_URL}/comments?proposalId=${proposalId}`)
.then(response => response.json())
.then(comments => {
if (comments.length === 0) {
@ -2389,7 +2479,7 @@ function loadComments(proposalId) {
// Update submitComment function to handle promises
function submitComment(proposalId, commentText) {
return fetch(`${API_BASE_URL}/comments`, {
return safeFetch(`${API_BASE_URL}/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'

2378
worker.js Normal file

File diff suppressed because it is too large Load diff

31
wrangler.toml Normal file
View file

@ -0,0 +1,31 @@
name = "radical"
main = "worker.js"
compatibility_date = "2024-03-31"
# D1 Database Binding
[[d1_databases]]
binding = "DB" # Use this name in your code
database_name = "radical-db"
database_id = "1f2a5273-bfcb-45c8-a759-5d675c05620c"
# R2 Bucket Binding
[[r2_buckets]]
binding = "MEMES_BUCKET"
bucket_name = "memes"
# Environment variables (optional)
[vars]
ENVIRONMENT = "production"
# wrangler.toml (wrangler v3.88.0^)
[observability.logs]
enabled = true
# Route API requests to your worker
[[routes]]
pattern = "theradicalparty.com/api/*"
zone_name = "theradicalparty.com"