Add Join The Movement form, fix login route, fix images, update copyright to 2026
- Add /api/join worker endpoint that emails membership details to party contact - Register /api/login route (was missing — emoji login was broken) - Add full AEC-compliant member registration form in index.html (#join section) - Update copyright year to 2026 in index.html and login.html - Fix dictator/target image URLs: radical.omar-c29.workers.dev → theradicalparty.com - Add onerror handlers to all login page images for graceful degradation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b789b35f78
commit
a6e7989a10
3 changed files with 207 additions and 10 deletions
138
index.html
138
index.html
|
|
@ -150,6 +150,28 @@
|
|||
.email-modal-btn:hover{background:#ff33aa}
|
||||
.email-modal-skip{background:none;border:none;color:#555;font-family:'Roboto Mono',monospace;font-size:12px;cursor:pointer;width:100%;text-align:center;padding:4px}
|
||||
.email-modal-skip:hover{color:#aaa}
|
||||
/* ── Join section ── */
|
||||
.join-section{margin:32px 0;border:2px solid #ff0099;padding:0}
|
||||
.join-header{background:#ff0099;color:#000;padding:14px 20px}
|
||||
.join-header h2{font-family:'Roboto Mono',monospace;font-size:18px;font-weight:700;margin:0 0 4px}
|
||||
.join-desc{font-family:'Roboto Mono',monospace;font-size:11px;margin:0;line-height:1.5}
|
||||
.join-form{padding:20px}
|
||||
.join-row{display:flex;gap:12px;flex-wrap:wrap}
|
||||
.join-field{display:flex;flex-direction:column;gap:4px;flex:1;min-width:140px;margin-bottom:12px}
|
||||
.join-field--sm{flex:0 0 100px;min-width:80px}
|
||||
.join-field label{font-family:'Roboto Mono',monospace;font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px}
|
||||
.join-input{background:#0a0a0a;border:1px solid #333;color:#fff;padding:10px 12px;font-family:'Roboto Mono',monospace;font-size:13px;width:100%;box-sizing:border-box}
|
||||
.join-input:focus{outline:none;border-color:#ff0099}
|
||||
.join-input option{background:#111;color:#fff}
|
||||
.join-check{display:flex;align-items:flex-start;gap:10px;margin-bottom:12px}
|
||||
.join-check input[type=checkbox]{margin-top:2px;accent-color:#ff0099;width:16px;height:16px;flex-shrink:0}
|
||||
.join-check label{font-family:'Roboto Mono',monospace;font-size:12px;color:#aaa;line-height:1.5}
|
||||
.join-submit{width:100%;background:#ff0099;color:#000;border:none;padding:14px;font-family:'Roboto Mono',monospace;font-size:15px;font-weight:700;cursor:pointer;margin-top:8px;letter-spacing:1px}
|
||||
.join-submit:hover{background:#ff33aa}
|
||||
.join-submit:disabled{opacity:.5;cursor:not-allowed}
|
||||
.join-msg{font-family:'Roboto Mono',monospace;font-size:13px;min-height:20px;margin-bottom:8px}
|
||||
.join-msg.success{color:#11cc77}
|
||||
.join-msg.error{color:#ff0099}
|
||||
/* ── Flag & share-image buttons ── */
|
||||
.flag-btn{background:none;border:none;color:#444;font-size:12px;cursor:pointer;padding:4px 6px;font-family:'Roboto Mono',monospace;transition:color .15s}
|
||||
.flag-btn:hover{color:#ff0099}
|
||||
|
|
@ -281,8 +303,70 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section id="join" class="join-section">
|
||||
<div class="join-header">
|
||||
<h2>JOIN THE MOVEMENT</h2>
|
||||
<p class="join-desc">Become an official RADICAL Party member. Your details are collected in accordance with the Australian Electoral Commission requirements for registered political parties.</p>
|
||||
</div>
|
||||
<form id="join-form" class="join-form" onsubmit="handleJoinSubmit(event)">
|
||||
<div class="join-row">
|
||||
<div class="join-field">
|
||||
<label for="join-fullname">Full Name (as on electoral roll) *</label>
|
||||
<input type="text" id="join-fullname" class="join-input" required placeholder="Jane Smith" autocomplete="name">
|
||||
</div>
|
||||
<div class="join-field">
|
||||
<label for="join-dob">Date of Birth *</label>
|
||||
<input type="date" id="join-dob" class="join-input" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="join-field">
|
||||
<label for="join-address">Residential Address *</label>
|
||||
<input type="text" id="join-address" class="join-input" required placeholder="123 Rebellion Street" autocomplete="street-address">
|
||||
</div>
|
||||
<div class="join-row">
|
||||
<div class="join-field">
|
||||
<label for="join-suburb">Suburb *</label>
|
||||
<input type="text" id="join-suburb" class="join-input" required placeholder="Sydney" autocomplete="address-level2">
|
||||
</div>
|
||||
<div class="join-field join-field--sm">
|
||||
<label for="join-state">State *</label>
|
||||
<select id="join-state" class="join-input" required>
|
||||
<option value="">—</option>
|
||||
<option>NSW</option><option>VIC</option><option>QLD</option>
|
||||
<option>WA</option><option>SA</option><option>TAS</option>
|
||||
<option>ACT</option><option>NT</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="join-field join-field--sm">
|
||||
<label for="join-postcode">Postcode *</label>
|
||||
<input type="text" id="join-postcode" class="join-input" required placeholder="2000" maxlength="4" pattern="\d{4}" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
<div class="join-row">
|
||||
<div class="join-field">
|
||||
<label for="join-email">Email Address *</label>
|
||||
<input type="email" id="join-email" class="join-input" required placeholder="you@example.com" autocomplete="email">
|
||||
</div>
|
||||
<div class="join-field">
|
||||
<label for="join-phone">Phone (optional)</label>
|
||||
<input type="tel" id="join-phone" class="join-input" placeholder="04xx xxx xxx" autocomplete="tel">
|
||||
</div>
|
||||
</div>
|
||||
<div class="join-check">
|
||||
<input type="checkbox" id="join-roll" required>
|
||||
<label for="join-roll">I confirm I am enrolled to vote in Australian federal elections (or am eligible to enrol) *</label>
|
||||
</div>
|
||||
<div class="join-check">
|
||||
<input type="checkbox" id="join-consent" required>
|
||||
<label for="join-consent">I consent to joining the RADICAL Party as a member and agree that my details may be used for AEC registration purposes *</label>
|
||||
</div>
|
||||
<div id="join-msg" class="join-msg"></div>
|
||||
<button type="submit" class="join-submit" id="join-btn">JOIN THE REBELLION →</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>RADICAL - Direct Democracy Movement 🇦🇺 © 2025</p>
|
||||
<p>RADICAL - Direct Democracy Movement 🇦🇺 © 2026</p>
|
||||
<div class="footer-links">
|
||||
<a href="/login" class="login-link">Login 🔐</a>
|
||||
<a href="#about">About Us</a>
|
||||
|
|
@ -3011,6 +3095,58 @@ async function handleFlag(proposalId, btn) {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleJoinSubmit(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('join-btn');
|
||||
const msg = document.getElementById('join-msg');
|
||||
msg.textContent = '';
|
||||
msg.className = 'join-msg';
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'SUBMITTING...';
|
||||
|
||||
const dob = document.getElementById('join-dob').value;
|
||||
const age = (Date.now() - new Date(dob)) / (365.25 * 24 * 60 * 60 * 1000);
|
||||
if (age < 18) {
|
||||
msg.textContent = 'You must be 18 or older to join.';
|
||||
msg.className = 'join-msg error';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'JOIN THE REBELLION →';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/join`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
fullName: document.getElementById('join-fullname').value.trim(),
|
||||
dob,
|
||||
address: document.getElementById('join-address').value.trim(),
|
||||
suburb: document.getElementById('join-suburb').value.trim(),
|
||||
state: document.getElementById('join-state').value,
|
||||
postcode: document.getElementById('join-postcode').value.trim(),
|
||||
email: document.getElementById('join-email').value.trim(),
|
||||
phone: document.getElementById('join-phone').value.trim(),
|
||||
onRoll: document.getElementById('join-roll').checked,
|
||||
consent: document.getElementById('join-consent').checked
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.success) {
|
||||
msg.textContent = '✓ Welcome to the movement! We\'ll be in touch.';
|
||||
msg.className = 'join-msg success';
|
||||
document.getElementById('join-form').reset();
|
||||
} else {
|
||||
throw new Error(data.error || 'Submission failed');
|
||||
}
|
||||
} catch (err) {
|
||||
msg.textContent = '⚠ ' + (err.message || 'Something went wrong. Try again.');
|
||||
msg.className = 'join-msg error';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'JOIN THE REBELLION →';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="music-prompt" id="music-prompt">hit me</div>
|
||||
|
|
|
|||
20
login.html
20
login.html
|
|
@ -578,7 +578,7 @@
|
|||
</div>
|
||||
|
||||
<div class="footer">
|
||||
RADICAL - Direct Democracy Movement 🇦🇺 © 2025
|
||||
RADICAL - Direct Democracy Movement 🇦🇺 © 2026
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -607,15 +607,15 @@
|
|||
|
||||
// Dictator options
|
||||
const dictators = [
|
||||
{ id: 'stalin', name: 'Joseph Stalin', image: 'https://radical.omar-c29.workers.dev/memes/stalin.avif' },
|
||||
{ id: 'hitler', name: 'Adolf Hitler', image: 'https://radical.omar-c29.workers.dev/memes/hitler.jpg' },
|
||||
{ id: 'kim', name: 'Kim Jong-un', image: 'https://radical.omar-c29.workers.dev/memes/kim.png' },
|
||||
{ id: 'mao', name: 'Mao Zedong', image: 'https://radical.omar-c29.workers.dev/memes/mao.jpg' },
|
||||
{ id: 'castro', name: 'Fidel Castro', image: 'https://radical.omar-c29.workers.dev/memes/fidel.jpg' },
|
||||
{ id: 'putin', name: 'Vladimir Putin', image: 'https://radical.omar-c29.workers.dev/memes/putin.jpg' },
|
||||
{ id: 'mussolini', name: 'Benito Mussolini', image: 'https://radical.omar-c29.workers.dev/memes/benito.jpg' },
|
||||
{ id: 'stalin', name: 'Joseph Stalin', image: 'https://theradicalparty.com/memes/stalin.avif' },
|
||||
{ id: 'hitler', name: 'Adolf Hitler', image: 'https://theradicalparty.com/memes/hitler.jpg' },
|
||||
{ id: 'kim', name: 'Kim Jong-un', image: 'https://theradicalparty.com/memes/kim.png' },
|
||||
{ id: 'mao', name: 'Mao Zedong', image: 'https://theradicalparty.com/memes/mao.jpg' },
|
||||
{ id: 'castro', name: 'Fidel Castro', image: 'https://theradicalparty.com/memes/fidel.jpg' },
|
||||
{ id: 'putin', name: 'Vladimir Putin', image: 'https://theradicalparty.com/memes/putin.jpg' },
|
||||
{ id: 'mussolini', name: 'Benito Mussolini', image: 'https://theradicalparty.com/memes/benito.jpg' },
|
||||
{ id: 'pol', name: 'Pol Pot', image: 'https://i.imgur.com/AZRJW2n.jpg' },
|
||||
{ id: 'kanye', name: 'Kanye West', image: 'https://radical.omar-c29.workers.dev/memes/ye.png' },
|
||||
{ id: 'kanye', name: 'Kanye West', image: 'https://theradicalparty.com/memes/ye.png' },
|
||||
{ id: 'khomeini', name: 'Ayatollah Khomeini', image: 'https://i.imgur.com/M7OPgQd.jpg' },
|
||||
{ id: 'netanyahu', name: 'Benjamin Netanyahu', image: 'https://i.imgur.com/9ZKJFy8.jpg' },
|
||||
{ id: 'zelensky', name: 'Volodymyr Zelensky', image: 'https://i.imgur.com/jANrPvB.jpg' },
|
||||
|
|
@ -691,6 +691,7 @@
|
|||
image.src = target.image;
|
||||
image.alt = target.name;
|
||||
image.loading = 'lazy';
|
||||
image.onerror = function() { this.style.display='none'; };
|
||||
|
||||
const name = document.createElement('div');
|
||||
name.className = 'target-name';
|
||||
|
|
@ -755,6 +756,7 @@
|
|||
image.src = dictator.image;
|
||||
image.alt = dictator.name;
|
||||
image.loading = 'lazy';
|
||||
image.onerror = function() { this.style.display='none'; };
|
||||
|
||||
const name = document.createElement('div');
|
||||
name.className = 'dictator-name';
|
||||
|
|
|
|||
59
worker.js
59
worker.js
|
|
@ -143,6 +143,10 @@ export default {
|
|||
return await saveUserEmail(request, env);
|
||||
} else if (path === '/api/flag' && request.method === 'POST') {
|
||||
return await flagProposal(request, env);
|
||||
} else if (path === '/api/login' && request.method === 'POST') {
|
||||
return await validateLogin(request, env);
|
||||
} else if (path === '/api/join' && request.method === 'POST') {
|
||||
return await handleJoinMovement(request, env);
|
||||
} else if (path === '/api/petition-stats' && request.method === 'GET') {
|
||||
return await getPetitionStats(request, env);
|
||||
} else if (path === '/api/petition-svg' && request.method === 'GET') {
|
||||
|
|
@ -2473,4 +2477,59 @@ async function sendWeeklyDigest(env) {
|
|||
} catch (error) {
|
||||
console.error('sendWeeklyDigest error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleJoinMovement(request, env) {
|
||||
try {
|
||||
const data = await request.json();
|
||||
const { fullName, dob, address, suburb, state, postcode, email, phone, onRoll, consent } = data;
|
||||
|
||||
if (!fullName || !dob || !address || !suburb || !state || !postcode || !email || !onRoll || !consent) {
|
||||
return createResponse({ error: 'Missing required fields' }, 400);
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return createResponse({ error: 'Invalid email address' }, 400);
|
||||
}
|
||||
|
||||
const timestamp = new Date().toLocaleString('en-AU', { timeZone: 'Australia/Sydney' });
|
||||
|
||||
if (env.RESEND_API_KEY) {
|
||||
await fetch('https://api.resend.com/emails', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from: 'RADICAL <noreply@theradicalparty.com>',
|
||||
to: ['kingomartheoneandonly@protonmail.com'],
|
||||
subject: `New RADICAL Member: ${fullName}`,
|
||||
html: `<div style="font-family:monospace;background:#000;color:#fff;padding:24px;max-width:600px">
|
||||
<h2 style="color:#ff0099">NEW RADICAL PARTY MEMBER</h2>
|
||||
<p style="color:#aaa">Submitted: ${timestamp}</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-top:16px">
|
||||
<tr><td style="padding:8px;color:#ff0099;width:140px">Full Name</td><td style="padding:8px">${escapeHTML(fullName)}</td></tr>
|
||||
<tr style="background:#111"><td style="padding:8px;color:#ff0099">Date of Birth</td><td style="padding:8px">${escapeHTML(dob)}</td></tr>
|
||||
<tr><td style="padding:8px;color:#ff0099">Address</td><td style="padding:8px">${escapeHTML(address)}</td></tr>
|
||||
<tr style="background:#111"><td style="padding:8px;color:#ff0099">Suburb</td><td style="padding:8px">${escapeHTML(suburb)}</td></tr>
|
||||
<tr><td style="padding:8px;color:#ff0099">State</td><td style="padding:8px">${escapeHTML(state)}</td></tr>
|
||||
<tr style="background:#111"><td style="padding:8px;color:#ff0099">Postcode</td><td style="padding:8px">${escapeHTML(postcode)}</td></tr>
|
||||
<tr><td style="padding:8px;color:#ff0099">Email</td><td style="padding:8px">${escapeHTML(email)}</td></tr>
|
||||
<tr style="background:#111"><td style="padding:8px;color:#ff0099">Phone</td><td style="padding:8px">${phone ? escapeHTML(phone) : 'Not provided'}</td></tr>
|
||||
<tr><td style="padding:8px;color:#ff0099">On Electoral Roll</td><td style="padding:8px">Yes ✓</td></tr>
|
||||
<tr style="background:#111"><td style="padding:8px;color:#ff0099">Consented</td><td style="padding:8px">Yes ✓</td></tr>
|
||||
</table>
|
||||
<hr style="border-color:#333;margin-top:32px">
|
||||
<p style="font-size:0.8em;color:#666">RADICAL Party Membership Application</p>
|
||||
</div>`
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return createResponse({ success: true }, 200, 0);
|
||||
} catch (error) {
|
||||
return createResponse({ error: 'Failed to process membership application', details: logError(error, { action: 'join_movement' }) }, 500);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue