Initial commit of existing project
This commit is contained in:
134
app.js
Normal file
134
app.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/* HOA LedgerIQ — Frontend form handler + countdown */
|
||||
|
||||
// ── Dynamic Countdown ────────────────────────────────────
|
||||
// Launch date = March 1, 2026 + 60 days = April 30, 2026
|
||||
// On 3/1/2026 the banner shows "60 days", then counts down daily.
|
||||
(function initCountdown() {
|
||||
const launchDate = new Date('2026-04-30T00:00:00');
|
||||
const now = new Date();
|
||||
const msPerDay = 1000 * 60 * 60 * 24;
|
||||
const daysLeft = Math.max(0, Math.ceil((launchDate - now) / msPerDay));
|
||||
|
||||
const label = daysLeft === 1 ? 'Day' : 'Days';
|
||||
const text = daysLeft > 0
|
||||
? `Launching in ${daysLeft} ${label}`
|
||||
: 'Now Live!';
|
||||
|
||||
const bannerEl = document.getElementById('bannerCountdown');
|
||||
const signupEl = document.getElementById('signupCountdown');
|
||||
if (bannerEl) bannerEl.textContent = text;
|
||||
if (signupEl) signupEl.textContent = text;
|
||||
})();
|
||||
|
||||
// ── Form Handler ─────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('signupForm');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const btnText = submitBtn?.querySelector('.btn-text');
|
||||
const btnLoading = submitBtn?.querySelector('.btn-loading');
|
||||
const successDiv = document.getElementById('signupSuccess');
|
||||
|
||||
if (!form) return;
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const firstName = form.firstName.value.trim();
|
||||
const lastName = form.lastName.value.trim();
|
||||
const email = form.email.value.trim();
|
||||
|
||||
const orgName = form.orgName?.value.trim() || '';
|
||||
const state = form.state?.value || '';
|
||||
|
||||
// Basic client-side validation
|
||||
if (!firstName || !lastName || !email || !orgName || !state) {
|
||||
highlightEmpty(form);
|
||||
return;
|
||||
}
|
||||
if (!isValidEmail(email)) {
|
||||
form.email.style.borderColor = '#ef4444';
|
||||
form.email.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoading(true);
|
||||
|
||||
const payload = {
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
orgName: form.orgName?.value.trim() || '',
|
||||
state: form.state?.value || '',
|
||||
role: form.role.value,
|
||||
unitCount: form.unitCount.value,
|
||||
betaInterest: form.betaInterest?.checked || false,
|
||||
source: 'landing_page',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/leads', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
showSuccess();
|
||||
} else {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.status === 409) {
|
||||
// Already registered
|
||||
showSuccess('You\'re already on the list! We\'ll be in touch soon.');
|
||||
} else {
|
||||
alert(data.error || 'Something went wrong. Please try again.');
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Network error — fall back to localStorage so we don't lose the lead
|
||||
saveLocally(payload);
|
||||
showSuccess();
|
||||
}
|
||||
});
|
||||
|
||||
function setLoading(on) {
|
||||
if (!btnText || !btnLoading) return;
|
||||
submitBtn.disabled = on;
|
||||
btnText.classList.toggle('hidden', on);
|
||||
btnLoading.classList.toggle('hidden', !on);
|
||||
}
|
||||
|
||||
function showSuccess(msg) {
|
||||
form.classList.add('hidden');
|
||||
if (msg && successDiv) {
|
||||
const p = successDiv.querySelector('p');
|
||||
if (p) p.textContent = msg;
|
||||
}
|
||||
successDiv?.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function highlightEmpty(f) {
|
||||
['firstName', 'lastName', 'email', 'orgName', 'state'].forEach(name => {
|
||||
const el = f[name];
|
||||
if (el && !el.value.trim()) {
|
||||
el.style.borderColor = '#ef4444';
|
||||
el.addEventListener('input', () => { el.style.borderColor = ''; }, { once: true });
|
||||
el.addEventListener('change', () => { el.style.borderColor = ''; }, { once: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
}
|
||||
|
||||
function saveLocally(payload) {
|
||||
try {
|
||||
const existing = JSON.parse(localStorage.getItem('hoa_leads') || '[]');
|
||||
existing.push(payload);
|
||||
localStorage.setItem('hoa_leads', JSON.stringify(existing));
|
||||
} catch (_) { /* best-effort */ }
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user