221 lines
7.8 KiB
JavaScript
221 lines
7.8 KiB
JavaScript
const form = document.getElementById('rsvp-form');
|
||
const statusNode = document.getElementById('form-status');
|
||
const attendanceFields = document.getElementById('attendance-fields');
|
||
const attendanceInputs = Array.from(document.querySelectorAll('input[name="attendance"]'));
|
||
const successModal = document.getElementById('success-modal');
|
||
const successOkButton = document.getElementById('success-ok');
|
||
const closeModalNodes = Array.from(document.querySelectorAll('[data-close-modal]'));
|
||
const googleCalendarLink = document.getElementById('google-calendar-link');
|
||
const iCalDownloadButton = document.getElementById('ical-download');
|
||
const copyIbanButton = document.getElementById('copy-iban');
|
||
const ibanFeedback = document.getElementById('iban-feedback');
|
||
const giftHolder = document.getElementById('gift-holder');
|
||
const giftIban = document.getElementById('gift-iban');
|
||
const giftCausal = document.getElementById('gift-causal');
|
||
const giftNote = document.getElementById('gift-note');
|
||
|
||
// Modifica qui i dati del calendario evento.
|
||
const EVENT_CALENDAR = {
|
||
title: 'I 40 anni del Gianca',
|
||
location: 'Lost Paradise Beach Club, Bacoli',
|
||
description: 'Conferma la tua presenza per i 40 anni del Gianca.',
|
||
date: '2026-04-25',
|
||
startTime: '12:00',
|
||
durationHours: 6,
|
||
timezone: 'Europe/Rome'
|
||
};
|
||
|
||
// Modifica qui i dati regalo / quota viaggio.
|
||
const GIFT_DETAILS = {
|
||
holder: 'Giancarlo ...',
|
||
iban: 'IT00X0000000000000000000000',
|
||
causal: 'Regalo 40 anni Gianca',
|
||
note: 'Nota facoltativa: un pensiero semplice e sempre gradito.'
|
||
};
|
||
|
||
function updateAttendanceFields() {
|
||
const selected = document.querySelector('input[name="attendance"]:checked');
|
||
const attending = selected?.value === 'si';
|
||
|
||
attendanceFields.classList.toggle('attendance-fields--hidden', !attending);
|
||
|
||
attendanceFields.querySelectorAll('input, select, textarea').forEach((field) => {
|
||
field.disabled = !attending;
|
||
if (!attending && field.tagName === 'TEXTAREA') field.value = '';
|
||
if (!attending && field.tagName === 'SELECT') field.value = '0';
|
||
});
|
||
}
|
||
|
||
function openSuccessModal() {
|
||
if (!successModal) return;
|
||
successModal.classList.add('success-modal--open');
|
||
successModal.setAttribute('aria-hidden', 'false');
|
||
document.body.classList.add('modal-open');
|
||
successOkButton?.focus();
|
||
}
|
||
|
||
function closeSuccessModal() {
|
||
if (!successModal) return;
|
||
successModal.classList.remove('success-modal--open');
|
||
successModal.setAttribute('aria-hidden', 'true');
|
||
document.body.classList.remove('modal-open');
|
||
}
|
||
|
||
function pad(value) {
|
||
return String(value).padStart(2, '0');
|
||
}
|
||
|
||
function createFloatingDateTime(date, time, plusHours = 0) {
|
||
const [year, month, day] = date.split('-').map(Number);
|
||
const [hours, minutes] = time.split(':').map(Number);
|
||
const normalizedDate = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
|
||
normalizedDate.setUTCHours(normalizedDate.getUTCHours() + plusHours);
|
||
|
||
return `${normalizedDate.getUTCFullYear()}${pad(normalizedDate.getUTCMonth() + 1)}${pad(normalizedDate.getUTCDate())}T${pad(normalizedDate.getUTCHours())}${pad(normalizedDate.getUTCMinutes())}00`;
|
||
}
|
||
|
||
function buildGoogleCalendarUrl(eventData) {
|
||
const start = createFloatingDateTime(eventData.date, eventData.startTime, 0);
|
||
const end = createFloatingDateTime(eventData.date, eventData.startTime, eventData.durationHours);
|
||
const params = new URLSearchParams({
|
||
action: 'TEMPLATE',
|
||
text: eventData.title,
|
||
details: eventData.description,
|
||
location: eventData.location,
|
||
ctz: eventData.timezone,
|
||
dates: `${start}/${end}`
|
||
});
|
||
return `https://calendar.google.com/calendar/render?${params.toString()}`;
|
||
}
|
||
|
||
function escapeIcsText(value) {
|
||
return value
|
||
.replace(/\\/g, '\\\\')
|
||
.replace(/\n/g, '\\n')
|
||
.replace(/,/g, '\\,')
|
||
.replace(/;/g, '\\;');
|
||
}
|
||
|
||
function buildIcsContent(eventData) {
|
||
const start = createFloatingDateTime(eventData.date, eventData.startTime, 0);
|
||
const end = createFloatingDateTime(eventData.date, eventData.startTime, eventData.durationHours);
|
||
const stamp = `${createFloatingDateTime(eventData.date, eventData.startTime, 0)}Z`;
|
||
const uid = `gianca-40-${start}@gianca-event`;
|
||
|
||
return [
|
||
'BEGIN:VCALENDAR',
|
||
'VERSION:2.0',
|
||
'PRODID:-//Gianca40//Event Landing//IT',
|
||
'CALSCALE:GREGORIAN',
|
||
'BEGIN:VEVENT',
|
||
`UID:${uid}`,
|
||
`DTSTAMP:${stamp}`,
|
||
`DTSTART;TZID=${eventData.timezone}:${start}`,
|
||
`DTEND;TZID=${eventData.timezone}:${end}`,
|
||
`SUMMARY:${escapeIcsText(eventData.title)}`,
|
||
`LOCATION:${escapeIcsText(eventData.location)}`,
|
||
`DESCRIPTION:${escapeIcsText(eventData.description)}`,
|
||
'END:VEVENT',
|
||
'END:VCALENDAR'
|
||
].join('\r\n');
|
||
}
|
||
|
||
function triggerIcsDownload() {
|
||
const content = buildIcsContent(EVENT_CALENDAR);
|
||
const blob = new Blob([content], { type: 'text/calendar;charset=utf-8' });
|
||
const downloadUrl = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.href = downloadUrl;
|
||
link.download = 'i-40-anni-del-gianca.ics';
|
||
document.body.append(link);
|
||
link.click();
|
||
link.remove();
|
||
URL.revokeObjectURL(downloadUrl);
|
||
}
|
||
|
||
function setIbanFeedback(message) {
|
||
if (!ibanFeedback) return;
|
||
ibanFeedback.textContent = message;
|
||
}
|
||
|
||
async function copyIbanToClipboard() {
|
||
if (!copyIbanButton) return;
|
||
const ibanNodeId = copyIbanButton.dataset.copyTarget;
|
||
const ibanText = document.getElementById(ibanNodeId || '')?.textContent?.trim();
|
||
if (!ibanText) return;
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(ibanText);
|
||
setIbanFeedback('IBAN copiato negli appunti.');
|
||
} catch (error) {
|
||
const fallbackField = document.createElement('input');
|
||
fallbackField.value = ibanText;
|
||
document.body.append(fallbackField);
|
||
fallbackField.select();
|
||
const copied = document.execCommand('copy');
|
||
fallbackField.remove();
|
||
setIbanFeedback(copied ? 'IBAN copiato negli appunti.' : 'Copia non riuscita. Riprova.');
|
||
}
|
||
}
|
||
|
||
attendanceInputs.forEach((input) => input.addEventListener('change', updateAttendanceFields));
|
||
closeModalNodes.forEach((node) => node.addEventListener('click', closeSuccessModal));
|
||
successOkButton?.addEventListener('click', closeSuccessModal);
|
||
iCalDownloadButton?.addEventListener('click', triggerIcsDownload);
|
||
copyIbanButton?.addEventListener('click', async () => {
|
||
await copyIbanToClipboard();
|
||
window.clearTimeout(copyIbanButton.dataset.feedbackTimeoutId);
|
||
const timeoutId = window.setTimeout(() => setIbanFeedback(''), 2500);
|
||
copyIbanButton.dataset.feedbackTimeoutId = String(timeoutId);
|
||
});
|
||
document.addEventListener('keydown', (event) => {
|
||
if (event.key === 'Escape' && successModal?.classList.contains('success-modal--open')) {
|
||
closeSuccessModal();
|
||
}
|
||
});
|
||
updateAttendanceFields();
|
||
if (googleCalendarLink) {
|
||
googleCalendarLink.href = buildGoogleCalendarUrl(EVENT_CALENDAR);
|
||
}
|
||
if (giftHolder) giftHolder.textContent = GIFT_DETAILS.holder;
|
||
if (giftIban) giftIban.textContent = GIFT_DETAILS.iban;
|
||
if (giftCausal) giftCausal.textContent = GIFT_DETAILS.causal;
|
||
if (giftNote) giftNote.textContent = GIFT_DETAILS.note;
|
||
|
||
function encode(data) {
|
||
return new URLSearchParams(data).toString();
|
||
}
|
||
|
||
form?.addEventListener('submit', async (event) => {
|
||
event.preventDefault();
|
||
|
||
if (!form.reportValidity()) return;
|
||
|
||
const submitButton = form.querySelector('button[type="submit"]');
|
||
const formData = new FormData(form);
|
||
|
||
submitButton.disabled = true;
|
||
statusNode.textContent = 'Invio in corso...';
|
||
|
||
try {
|
||
const response = await fetch('/', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||
body: encode(formData)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Submission failed');
|
||
}
|
||
|
||
form.reset();
|
||
updateAttendanceFields();
|
||
statusNode.textContent = '';
|
||
openSuccessModal();
|
||
} catch (error) {
|
||
statusNode.textContent = 'C’è stato un problema nell’invio. Riprova tra poco.';
|
||
} finally {
|
||
submitButton.disabled = false;
|
||
}
|
||
});
|