Merge branch 'main' of https://github.com/kingomarnajjar/radical
This commit is contained in:
commit
b7b286c2f0
4 changed files with 4906 additions and 91 deletions
2316
cloudflare worker.js
Normal file
2316
cloudflare worker.js
Normal file
File diff suppressed because it is too large
Load diff
212
index.html
212
index.html
|
|
@ -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,8 +265,32 @@ 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;
|
||||
|
|
@ -418,9 +442,9 @@ function smartCopyShare(proposalId, proposalText) {
|
|||
// Create or get user in DB
|
||||
async function createUser(user) {
|
||||
try {
|
||||
console.log("Creating user:", user); // Log the user data
|
||||
console.log("Creating user:", user);
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/users`, {
|
||||
const data = await safeFetch(`${API_BASE_URL}/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
|
@ -431,20 +455,13 @@ function smartCopyShare(proposalId, proposalText) {
|
|||
})
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation for successful post
|
||||
function animatePostSuccess() {
|
||||
|
|
@ -544,6 +561,81 @@ function smartCopyShare(proposalId, proposalText) {
|
|||
}, 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 {
|
||||
|
|
@ -552,19 +644,17 @@ function smartCopyShare(proposalId, proposalText) {
|
|||
|
||||
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 data = await safeFetch(
|
||||
`${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`
|
||||
);
|
||||
|
||||
const proposals = await response.json();
|
||||
renderProposals(proposals);
|
||||
renderProposals(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching proposals:', error);
|
||||
showErrorMessage('Oops! Failed to load petitions. Parliament might be censoring us already!');
|
||||
}
|
||||
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'
|
||||
|
|
|
|||
31
wrangler.toml
Normal file
31
wrangler.toml
Normal 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"
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Reference in a new issue