const section = document.getElementById('registration-section');
if (section) {
section.classList.toggle('collapsed');
}
}
// Reload template with warning
function reloadTemplate() {
const textarea = document.getElementById('quick-register-text');
const currentValue = textarea ? textarea.value.trim() : '';
const templateText = getTemplateText();
const hasContent = currentValue && currentValue !== templateText;
// Always show warning before reloading
const warningDiv = document.createElement('div');
warningDiv.id = 'template-reload-warning';
warningDiv.className = 'fixed inset-0 bg-black bg-opacity-50 z-[60] flex items-center justify-center p-4';
warningDiv.innerHTML = `
确认重新加载模板
${hasContent ? '当前内容将被替换,此操作无法撤销' : '模板将被重新加载'}
${hasContent ? `
当前内容预览:
${currentValue.substring(0, 200)}${currentValue.length > 200 ? '...' : ''}
` : ''}
`;
document.body.appendChild(warningDiv);
}
// Confirm reload template
function confirmReloadTemplate() {
const textarea = document.getElementById('quick-register-text');
const templateText = getTemplateText();
if (textarea && templateText) {
textarea.value = templateText;
// Remove warning if exists
const warning = document.getElementById('template-reload-warning');
if (warning) {
warning.remove();
}
// Show success message
const successMsg = document.createElement('div');
successMsg.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-[70] font-chinese text-sm';
successMsg.textContent = '✓ 模板已重新加载';
document.body.appendChild(successMsg);
setTimeout(() => successMsg.remove(), 2000);
}
}
// Quick Registration Modal Functions
function openQuickRegisterModal() {
const modal = document.getElementById('quick-register-modal');
if (modal) {
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
// Reset button state
const submitBtn = document.getElementById('quick-register-submit-btn');
const submitText = document.getElementById('quick-register-submit-text');
const submitLoading = document.getElementById('quick-register-submit-loading');
if (submitBtn) {
submitBtn.disabled = false;
}
if (submitText) {
submitText.classList.remove('hidden');
}
if (submitLoading) {
submitLoading.classList.add('hidden');
}
// Clear any previous alerts
const alertDiv = document.getElementById('quick-register-alert');
if (alertDiv) {
alertDiv.classList.add('hidden');
alertDiv.innerHTML = '';
}
// Pre-fill textarea with template
const textarea = document.getElementById('quick-register-text');
const templateText = getTemplateText();
if (textarea && templateText && !textarea.value.trim()) {
textarea.value = templateText;
}
}
}
function closeQuickRegisterModal() {
const modal = document.getElementById('quick-register-modal');
if (modal) {
modal.style.display = 'none';
document.body.style.overflow = '';
}
}
// Close modal when clicking outside
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('quick-register-modal');
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal) {
closeQuickRegisterModal();
}
});
}
// Auto-open modal if URL has #quick-register hash
if (window.location.hash === '#quick-register') {
setTimeout(function() {
openQuickRegisterModal();
}, 300);
}
});
// Parse text, fill form, and submit
async function parseAndFillForm() {
const text = document.getElementById('quick-register-text').value.trim();
const alertDiv = document.getElementById('quick-register-alert');
const submitBtn = document.getElementById('quick-register-submit-btn');
const submitText = document.getElementById('quick-register-submit-text');
const submitLoading = document.getElementById('quick-register-submit-loading');
// Clear previous alerts
alertDiv.classList.add('hidden');
alertDiv.innerHTML = '';
if (!text) {
showQuickRegisterAlert('请先粘贴报名信息文本', 'error');
return;
}
// Disable button and show loading
submitBtn.disabled = true;
submitText.classList.add('hidden');
submitLoading.classList.remove('hidden');
try {
// Parse text
const data = parseRegistrationText(text);
// Fill form fields
fillFormFields(data);
// Get form and prepare submission data
const form = document.getElementById('event-registration-form');
if (!form) {
throw new Error('找不到注册表单');
}
// Collect form data (same logic as regular form submission)
const formData = new FormData(form);
const submissionData = {};
// Get phone_number directly from input
const phoneInput = form.querySelector('input[name="phone_number"]');
if (phoneInput) {
submissionData['phone_number'] = (phoneInput.value || '').trim();
} else {
submissionData['phone_number'] = '';
}
// Get city from hidden input
const cityValue = document.getElementById('city-value');
if (cityValue && cityValue.value) {
submissionData['city'] = cityValue.value.trim();
} else {
submissionData['city'] = '';
}
// Convert FormData to object
for (let [key, value] of formData.entries()) {
if (key === 'hobbies') {
if (!submissionData[key]) {
submissionData[key] = [];
}
submissionData[key].push(value);
} else if (key === 'city' || key === 'phone_number') {
// Skip - already handled above
continue;
} else {
if (!submissionData.hasOwnProperty(key)) {
submissionData[key] = typeof value === 'string' ? value.trim() : value;
}
}
}
// Ensure all required fields are strings
const requiredFields = ['name', 'phone_number', 'gender', 'age', 'birth_year', 'height', 'education', 'city'];
requiredFields.forEach(field => {
if (!submissionData.hasOwnProperty(field) || submissionData[field] === null || submissionData[field] === undefined) {
submissionData[field] = '';
} else {
submissionData[field] = String(submissionData[field]);
}
});
// Get CSRF token
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
// Submit form
const response = await fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(submissionData)
});
// Check response
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch (e) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Show error in modal (keep modal open)
if (errorData.errors) {
let errorMessages = [];
const errorKeys = Array.isArray(errorData.errors) ?
errorData.errors.map((_, i) => i) :
Object.keys(errorData.errors);
errorKeys.forEach(function(field) {
const errorMsg = Array.isArray(errorData.errors) ?
errorData.errors[field] :
errorData.errors[field];
if (errorMsg) {
errorMessages.push(errorMsg);
}
});
showQuickRegisterAlert(
errorMessages.length > 0 ? errorMessages.join('
') : '注册失败,请检查输入信息',
'error'
);
} else if (errorData.message) {
showQuickRegisterAlert(errorData.message, 'error');
} else {
showQuickRegisterAlert('注册失败,请稍后重试', 'error');
}
// Re-enable button
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
return;
}
const result = await response.json();
if (result.success) {
// Success - show success message and close modal
showQuickRegisterAlert(
result.message || '报名成功!',
'success'
);
// Close modal after a short delay
setTimeout(() => {
closeQuickRegisterModal();
// Redirect to event detail page
window.location.href = "/events/25-12-13-cheng-shu-mei-li-xiang-yue/";
}, 1500);
} else {
// Error - show error messages (keep modal open)
if (result.errors) {
let errorMessages = [];
const errorKeys = Array.isArray(result.errors) ?
result.errors.map((_, i) => i) :
Object.keys(result.errors);
errorKeys.forEach(function(field) {
const errorMsg = Array.isArray(result.errors) ?
result.errors[field] :
result.errors[field];
if (errorMsg) {
errorMessages.push(errorMsg);
}
});
showQuickRegisterAlert(
errorMessages.length > 0 ? errorMessages.join('
') : '注册失败,请检查输入信息',
'error'
);
} else {
showQuickRegisterAlert('注册失败,请稍后重试', 'error');
}
// Re-enable button
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
}
} catch (error) {
// Network or other error
showQuickRegisterAlert(
`提交失败: ${error.message || '网络错误,请稍后重试'}`,
'error'
);
// Re-enable button
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
}
}
// Show styled alert in quick register modal
function showQuickRegisterAlert(message, type = 'error') {
const alertDiv = document.getElementById('quick-register-alert');
if (!alertDiv) return;
const isError = type === 'error';
const bgColor = isError ? 'bg-red-50' : 'bg-green-50';
const borderColor = isError ? 'border-red-500' : 'border-green-500';
const textColor = isError ? 'text-red-700' : 'text-green-700';
const iconColor = isError ? 'text-red-500' : 'text-green-500';
const icon = isError ? '❌' : '✅';
const title = isError ? '注册失败' : '注册成功';
alertDiv.className = `mb-2 sm:mb-4 p-3 sm:p-4 ${bgColor} border-l-4 ${borderColor} rounded-lg shadow-md`;
alertDiv.innerHTML = `
${icon} ${title}
${message}
`;
alertDiv.classList.remove('hidden');
// Scroll alert into view
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// Parse registration text
function parseRegistrationText(text) {
const data = {};
// Helper function to extract value after label
function extractValue(pattern, text) {
const regex = new RegExp(pattern, 'i');
const match = text.match(regex);
if (match && match[1]) {
return match[1].trim();
}
return null;
}
// Extract name (姓名)
data.name = extractValue(/姓名[::]\s*([^\n\r]+)/, text) ||
extractValue(/姓名\s*([^\n\r]+)/, text);
// Extract gender (性别)
const genderMatch = extractValue(/性别[::]\s*([^\n\r]+)/, text) ||
extractValue(/性别\s*([^\n\r]+)/, text);
if (genderMatch) {
if (genderMatch.includes('男') || genderMatch.includes('M') || genderMatch.includes('m')) {
data.gender = 'M';
} else if (genderMatch.includes('女') || genderMatch.includes('F') || genderMatch.includes('f')) {
data.gender = 'F';
}
}
// Extract phone (联系电话)
const phoneMatch = extractValue(/联系电话[::]\s*([^\n\r]+)/, text) ||
extractValue(/电话[::]\s*([^\n\r]+)/, text);
if (phoneMatch) {
// Remove all non-digits
data.phone_number = phoneMatch.replace(/\D/g, '');
}
// Extract age (年龄)
const ageMatch = extractValue(/1️⃣年龄[::]\s*(\d+)/, text) ||
extractValue(/年龄[::]\s*(\d+)/, text);
if (ageMatch) {
data.age = ageMatch;
}
// Extract height (身高)
const heightMatch = extractValue(/2️⃣身高[::]\s*(\d+)/, text) ||
extractValue(/身高[::]\s*(\d+)/, text);
if (heightMatch) {
data.height = heightMatch;
}
// Extract work (工作) - store in interests for now
const workMatch = extractValue(/3️⃣工作[::]\s*([^\n\r]+)/, text) ||
extractValue(/工作[::]\s*([^\n\r]+)/, text);
if (workMatch) {
data.work = workMatch;
}
// Extract education (学历)
const eduMatch = extractValue(/4️⃣学历[::]\s*([^\n\r]+)/, text) ||
extractValue(/学历[::]\s*([^\n\r]+)/, text);
if (eduMatch) {
// Map Chinese education to form values
const eduMap = {
'高中': 'high_school',
'大专': 'college',
'本科': 'bachelor',
'硕士': 'master',
'博士': 'phd',
'其他': 'other'
};
data.education = eduMap[eduMatch] || eduMatch.toLowerCase().replace(/\s+/g, '_');
}
// Extract hometown (籍贯) - store in interests
const hometownMatch = extractValue(/5️⃣籍贯[::]\s*([^\n\r]+)/, text) ||
extractValue(/籍贯[::]\s*([^\n\r]+)/, text);
if (hometownMatch) {
data.hometown = hometownMatch;
}
// Extract city (定居城市)
const cityMatch = extractValue(/6️⃣定居城市[::]\s*([^\n\r]+)/, text) ||
extractValue(/定居城市[::]\s*([^\n\r]+)/, text);
if (cityMatch) {
// Map Chinese city names to English
const cityMap = {
'温哥华': 'Vancouver',
'素里': 'Surrey',
'本拿比': 'Burnaby',
'列治文': 'Richmond',
'高贵林': 'Coquitlam',
'兰里': 'Langley',
'北三角洲': 'North Delta',
'三角洲': 'Delta (Ladner/Tsawwassen)',
'新西敏': 'New Westminster',
'枫树岭': 'Maple Ridge',
'北温': 'North Vancouver',
'西温': 'West Vancouver',
'高贵林港': 'Port Coquitlam',
'白石': 'White Rock/South Surrey',
'南素里': 'White Rock/South Surrey',
'西雅图': 'Seattle'
};
data.city = cityMap[cityMatch] || cityMatch;
}
// Extract hobbies/interests (特长或兴趣爱好)
const hobbiesMatch = extractValue(/7️⃣特长或兴趣爱好等[::]\s*([^\n\r]+)/, text) ||
extractValue(/特长或兴趣爱好[::]\s*([^\n\r]+)/, text) ||
extractValue(/兴趣爱好[::]\s*([^\n\r]+)/, text);
if (hobbiesMatch) {
data.interests = hobbiesMatch;
// Try to match hobbies to checkboxes
const hobbyKeywords = {
'阅读': 'reading',
'电影': 'movies',
'旅游': 'travel',
'运动': 'sports',
'烹饪': 'cooking',
'音乐': 'music',
'艺术': 'art',
'登山': 'hiking',
'摄影': 'photography',
'瑜伽': 'yoga',
'跳舞': 'dancing',
'美食': 'food',
'游戏': 'gaming',
'健身': 'fitness',
'跑步': 'running',
'游泳': 'swimming',
'购物': 'shopping',
'咖啡': 'coffee',
'宠物': 'pets',
'园艺': 'gardening',
'写作': 'writing',
'科技': 'technology',
'志愿者': 'volunteering',
'网球': 'tennis',
'羽毛球': 'badminton',
'高尔夫': 'golf',
'滑雪': 'skiing'
};
data.hobbies = [];
for (const [chinese, english] of Object.entries(hobbyKeywords)) {
if (hobbiesMatch.includes(chinese)) {
data.hobbies.push(english);
}
}
}
// Extract partner age range (年龄范围)
const partnerAgeMatch = extractValue(/1️⃣年龄范围[::]\s*([^\n\r]+)/, text) ||
extractValue(/年龄范围[::]\s*([^\n\r]+)/, text);
if (partnerAgeMatch) {
const ageRange = partnerAgeMatch.match(/(\d+)[-~至](\d+)/);
if (ageRange) {
data.partner_min_age = ageRange[1];
data.partner_max_age = ageRange[2];
}
}
// Extract partner height range (身高范围)
const partnerHeightMatch = extractValue(/2️⃣身高范围[::]\s*([^\n\r]+)/, text) ||
extractValue(/身高范围[::]\s*([^\n\r]+)/, text);
if (partnerHeightMatch) {
const heightRange = partnerHeightMatch.match(/(\d+)[-~至](\d+)/);
if (heightRange) {
data.partner_min_height = heightRange[1];
data.partner_max_height = heightRange[2];
}
}
// Extract partner work type (工作类型)
const partnerWorkMatch = extractValue(/3️⃣工作类型[::]\s*([^\n\r]+)/, text) ||
extractValue(/工作类型[::]\s*([^\n\r]+)/, text);
if (partnerWorkMatch && !partnerWorkMatch.includes('不限')) {
if (!data.interests) data.interests = '';
data.interests += (data.interests ? ';' : '') + '理想伴侣工作:' + partnerWorkMatch;
}
// Extract partner personality/hobbies (性格爱好)
const partnerPersonalityMatch = extractValue(/4️⃣性格爱好等[::]\s*([^\n\r]+)/, text) ||
extractValue(/性格爱好[::]\s*([^\n\r]+)/, text);
if (partnerPersonalityMatch) {
if (!data.interests) data.interests = '';
data.interests += (data.interests ? ';' : '') + '理想伴侣性格爱好:' + partnerPersonalityMatch;
}
return data;
}
// Sanitize input to prevent SQL injection and XSS
function sanitizeInput(value, type = 'text') {
if (!value) return '';
// Convert to string
let sanitized = String(value).trim();
// Remove potential SQL injection patterns
sanitized = sanitized.replace(/['";\\]/g, '');
sanitized = sanitized.replace(/--/g, '');
sanitized = sanitized.replace(/\/\*/g, '');
sanitized = sanitized.replace(/\*\//g, '');
sanitized = sanitized.replace(/;/g, '');
// Remove HTML/script tags to prevent XSS
const div = document.createElement('div');
div.textContent = sanitized;
sanitized = div.textContent || div.innerText || '';
// Type-specific sanitization
if (type === 'number') {
// Only allow digits
sanitized = sanitized.replace(/\D/g, '');
} else if (type === 'phone') {
// Only allow digits for phone
sanitized = sanitized.replace(/\D/g, '');
// Limit to 10 digits
sanitized = sanitized.substring(0, 10);
} else if (type === 'text') {
// Limit length for text fields
sanitized = sanitized.substring(0, 200);
}
return sanitized;
}
// Fill form fields with parsed data
function fillFormFields(data) {
// Fill name
if (data.name) {
const nameInput = document.querySelector('input[name="name"]');
if (nameInput) nameInput.value = sanitizeInput(data.name, 'text');
}
// Fill phone
if (data.phone_number) {
const phoneInput = document.querySelector('input[name="phone_number"]');
if (phoneInput) phoneInput.value = sanitizeInput(data.phone_number, 'phone');
}
// Fill gender (validate against allowed values)
if (data.gender && (data.gender === 'M' || data.gender === 'F')) {
const genderInput = document.querySelector(`input[name="gender"][value="${data.gender}"]`);
if (genderInput) genderInput.checked = true;
}
// Fill age
if (data.age) {
const ageInput = document.querySelector('input[name="age"]');
if (ageInput) {
const sanitizedAge = sanitizeInput(data.age, 'number');
if (sanitizedAge && parseInt(sanitizedAge) >= 18 && parseInt(sanitizedAge) <= 100) {
ageInput.value = sanitizedAge;
// Make sure age input is visible
toggleAgeInput('age');
}
}
}
// Fill height
if (data.height) {
const heightInput = document.querySelector('input[name="height"]');
if (heightInput) {
const sanitizedHeight = sanitizeInput(data.height, 'number');
if (sanitizedHeight && parseInt(sanitizedHeight) >= 140 && parseInt(sanitizedHeight) <= 230) {
heightInput.value = sanitizedHeight;
}
}
}
// Fill education (validate against allowed values)
if (data.education) {
const allowedEducation = ['high_school', 'college', 'bachelor', 'master', 'phd', 'other'];
const educationSelect = document.querySelector('select[name="education"]');
if (educationSelect && allowedEducation.includes(data.education)) {
educationSelect.value = data.education;
}
}
// Fill city (validate against allowed city values)
if (data.city) {
const allowedCities = ['Vancouver', 'Surrey', 'Burnaby', 'Richmond', 'Coquitlam', 'Langley',
'North Delta', 'Delta (Ladner/Tsawwassen)', 'New Westminster',
'Maple Ridge', 'North Vancouver', 'West Vancouver', 'Port Coquitlam',
'White Rock/South Surrey', 'Seattle', 'Other'];
const sanitizedCity = sanitizeInput(data.city, 'text');
if (allowedCities.includes(sanitizedCity)) {
const cityValueInput = document.getElementById('city-value');
const citySearch = document.getElementById('city-search');
const cityCheckboxes = document.querySelectorAll('#city-dropdown input[type="checkbox"]');
if (cityValueInput) cityValueInput.value = sanitizedCity;
if (citySearch) {
// Find the matching city checkbox
cityCheckboxes.forEach(checkbox => {
if (checkbox.value === sanitizedCity) {
checkbox.checked = true;
const cityName = checkbox.closest('label').querySelector('.city-button').textContent;
citySearch.value = sanitizeInput(cityName, 'text');
} else {
checkbox.checked = false;
}
});
}
}
}
// Fill hobbies (validate against allowed hobby values)
if (data.hobbies && data.hobbies.length > 0) {
const allowedHobbies = ['reading', 'movies', 'travel', 'sports', 'cooking', 'music', 'art',
'hiking', 'photography', 'yoga', 'dancing', 'food', 'gaming', 'fitness',
'running', 'swimming', 'shopping', 'coffee', 'pets', 'gardening',
'writing', 'technology', 'volunteering', 'tennis', 'badminton', 'golf', 'skiing'];
data.hobbies.forEach(hobby => {
if (allowedHobbies.includes(hobby)) {
const hobbyCheckbox = document.querySelector(`input[name="hobbies"][value="${hobby}"]`);
if (hobbyCheckbox) hobbyCheckbox.checked = true;
}
});
}
// Fill interests (sanitize text)
if (data.interests) {
const interestsTextarea = document.querySelector('textarea[name="interests"]');
if (interestsTextarea) {
interestsTextarea.value = sanitizeInput(data.interests, 'text').substring(0, 500);
}
}
// Fill partner preferences (validate numeric ranges)
if (data.partner_min_age) {
const minAgeInput = document.querySelector('input[name="partner_min_age"]');
if (minAgeInput) {
const sanitized = sanitizeInput(data.partner_min_age, 'number');
if (sanitized && parseInt(sanitized) >= 18 && parseInt(sanitized) <= 100) {
minAgeInput.value = sanitized;
}
}
}
if (data.partner_max_age) {
const maxAgeInput = document.querySelector('input[name="partner_max_age"]');
if (maxAgeInput) {
const sanitized = sanitizeInput(data.partner_max_age, 'number');
if (sanitized && parseInt(sanitized) >= 18 && parseInt(sanitized) <= 100) {
maxAgeInput.value = sanitized;
}
}
}
if (data.partner_min_height) {
const minHeightInput = document.querySelector('input[name="partner_min_height"]');
if (minHeightInput) {
const sanitized = sanitizeInput(data.partner_min_height, 'number');
if (sanitized && parseInt(sanitized) >= 140 && parseInt(sanitized) <= 230) {
minHeightInput.value = sanitized;
}
}
}
if (data.partner_max_height) {
const maxHeightInput = document.querySelector('input[name="partner_max_height"]');
if (maxHeightInput) {
const sanitized = sanitizeInput(data.partner_max_height, 'number');
if (sanitized && parseInt(sanitized) >= 140 && parseInt(sanitized) <= 230) {
maxHeightInput.value = sanitized;
}
}
}
}
// Toggle between age input and birth year input
function toggleAgeInput(type) {
const ageInput = document.getElementById('age-input');
const birthYearInput = document.getElementById('birth-year-input');
if (type === 'age') {
ageInput.style.display = 'block';
birthYearInput.style.display = 'none';
} else {
ageInput.style.display = 'none';
birthYearInput.style.display = 'block';
}
}
// Calculate age from birth year
function calculateAge() {
const birthYearInput = document.querySelector('input[name="birth_year"]');
const ageInput = document.querySelector('input[name="age"]');
if (birthYearInput && ageInput && birthYearInput.value) {
const birthYear = parseInt(birthYearInput.value);
const currentYear = new Date().getFullYear();
const age = currentYear - birthYear;
if (age > 0 && age < 150) {
ageInput.value = age;
}
}
}
// City dropdown functionality
document.addEventListener('DOMContentLoaded', function() {
const citySearch = document.getElementById('city-search');
const cityDropdown = document.getElementById('city-dropdown');
const cityValue = document.getElementById('city-value');
const cityCheckboxes = document.querySelectorAll('#city-dropdown input[type="checkbox"]');
if (citySearch && cityDropdown) {
citySearch.addEventListener('focus', function() {
cityDropdown.style.display = 'block';
});
citySearch.addEventListener('blur', function(e) {
// Delay to allow checkbox clicks
setTimeout(() => {
if (!cityDropdown.contains(e.relatedTarget)) {
cityDropdown.style.display = 'none';
}
}, 200);
});
citySearch.addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
cityCheckboxes.forEach(function(checkbox) {
const label = checkbox.closest('label');
const cityName = label.querySelector('.city-button').textContent.toLowerCase();
if (cityName.includes(searchTerm)) {
label.style.display = '';
} else {
label.style.display = 'none';
}
});
});
cityCheckboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
if (this.checked) {
// Uncheck all others
cityCheckboxes.forEach(function(cb) {
if (cb !== checkbox) {
cb.checked = false;
}
});
const cityName = this.closest('label').querySelector('.city-button').textContent;
citySearch.value = cityName;
cityValue.value = this.value;
cityDropdown.style.display = 'none';
}
});
});
}
// Hobby checkboxes now use standard HTML checkboxes - no custom JS needed
// Form submission with Fetch API
const form = document.getElementById('event-registration-form');
const submitBtn = document.getElementById('submit-btn');
const submitText = document.getElementById('submit-text');
const submitLoading = document.getElementById('submit-loading');
const formMessages = document.getElementById('form-messages');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
// Clear previous messages
formMessages.classList.add('hidden');
formMessages.innerHTML = '';
// Disable submit button and show loading
submitBtn.disabled = true;
submitText.classList.add('hidden');
submitLoading.classList.remove('hidden');
// Collect form data
const formData = new FormData(form);
const data = {};
// First, explicitly collect phone_number directly from input to ensure it's captured
const phoneInput = form.querySelector('input[name="phone_number"]');
if (phoneInput) {
data['phone_number'] = (phoneInput.value || '').trim();
} else {
data['phone_number'] = '';
}
// Get city from hidden input
const cityValue = document.getElementById('city-value');
if (cityValue && cityValue.value) {
data['city'] = cityValue.value.trim();
} else {
data['city'] = '';
}
// Convert FormData to object
for (let [key, value] of formData.entries()) {
if (key === 'hobbies') {
// Handle multiple hobbies
if (!data[key]) {
data[key] = [];
}
data[key].push(value);
} else if (key === 'city') {
// Skip - already handled above from hidden input
continue;
} else if (key === 'phone_number') {
// Skip - already handled above directly from input
continue;
} else {
// Trim string values, but don't overwrite phone_number or city
if (!data.hasOwnProperty(key)) {
data[key] = typeof value === 'string' ? value.trim() : value;
}
}
}
// Ensure all required fields are strings (not null/undefined)
const requiredFields = ['name', 'phone_number', 'gender', 'age', 'birth_year', 'height', 'education', 'city'];
requiredFields.forEach(field => {
if (!data.hasOwnProperty(field) || data[field] === null || data[field] === undefined) {
data[field] = '';
} else {
data[field] = String(data[field]);
}
});
// Get CSRF token
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
try {
const response = await fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(data)
});
// Check if response is ok
if (!response.ok) {
// Try to parse error response
let errorData;
try {
errorData = await response.json();
} catch (e) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Show error from response
if (errorData.errors) {
formMessages.className = 'mb-4 p-4 bg-red-50 border-l-4 border-red-500 rounded-lg shadow-md';
let errorHtml = '
❌ 注册失败,请修正以下错误:
';
// Handle both object and array error formats
const errorKeys = Array.isArray(errorData.errors) ? errorData.errors.map((_, i) => i) : Object.keys(errorData.errors);
errorKeys.forEach(function(field) {
const errorMsg = Array.isArray(errorData.errors) ? errorData.errors[field] : errorData.errors[field];
if (errorMsg) {
errorHtml += `- ${errorMsg}
`;
// Try to find and highlight the field
const fieldElement = form.querySelector(`[name="${field}"]`) ||
form.querySelector(`#${field}`) ||
(field === 'general' ? null : form.querySelector(`[name="${field}"]`));
if (fieldElement) {
fieldElement.classList.add('border-red-500', 'ring-2', 'ring-red-500');
fieldElement.addEventListener('input', function() {
this.classList.remove('border-red-500', 'ring-2', 'ring-red-500');
}, { once: true });
}
}
});
errorHtml += '
';
formMessages.innerHTML = errorHtml;
formMessages.classList.remove('hidden');
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
return;
} else if (errorData.message) {
// Handle single error message
formMessages.className = 'mb-4 p-4 bg-red-50 border-l-4 border-red-500 rounded-lg shadow-md';
formMessages.innerHTML = `
❌ 注册失败
${errorData.message}
`;
formMessages.classList.remove('hidden');
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
return;
}
}
const result = await response.json();
// Handle case where response doesn't have success field
if (!result.hasOwnProperty('success')) {
formMessages.className = 'mb-4 p-4 bg-yellow-50 border-l-4 border-yellow-500 rounded-lg shadow-md';
formMessages.innerHTML = `
⚠️ 响应异常
服务器响应格式异常,请刷新页面后重试
`;
formMessages.classList.remove('hidden');
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
return;
}
if (result.success) {
// Success - redirect to event detail page (traditional redirect, no SPA)
window.location.href = "/events/25-12-13-cheng-shu-mei-li-xiang-yue/";
} else {
// Error - show error messages
formMessages.className = 'mb-4 p-4 bg-red-50 border-l-4 border-red-500 rounded-lg shadow-md';
let errorHtml = '
❌ 注册失败,请修正以下错误:
';
if (result.errors) {
// Handle both object and array error formats
const errorKeys = Array.isArray(result.errors) ? result.errors.map((_, i) => i) : Object.keys(result.errors);
errorKeys.forEach(function(field) {
const errorMsg = Array.isArray(result.errors) ? result.errors[field] : result.errors[field];
if (errorMsg) {
errorHtml += `- ${errorMsg}
`;
// Highlight field with error (skip 'general' field)
if (field !== 'general') {
const fieldElement = form.querySelector(`[name="${field}"]`) ||
form.querySelector(`#${field}`) ||
form.querySelector(`select[name="${field}"]`) ||
form.querySelector(`textarea[name="${field}"]`);
if (fieldElement) {
fieldElement.classList.add('border-red-500', 'ring-2', 'ring-red-500');
fieldElement.addEventListener('input', function() {
this.classList.remove('border-red-500', 'ring-2', 'ring-red-500');
}, { once: true });
fieldElement.addEventListener('change', function() {
this.classList.remove('border-red-500', 'ring-2', 'ring-red-500');
}, { once: true });
}
}
}
});
} else if (result.message) {
// Handle single error message
errorHtml = '❌ 注册失败
' + result.message + '
';
formMessages.innerHTML = errorHtml;
formMessages.classList.remove('hidden');
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
return;
}
errorHtml += '
';
formMessages.innerHTML = errorHtml;
formMessages.classList.remove('hidden');
// Scroll to first error
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Re-enable submit button
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
}
} catch (error) {
// Network or other error
formMessages.className = 'mb-4 p-4 bg-red-50 border-l-4 border-red-500 rounded-lg shadow-md';
formMessages.innerHTML = `
❌ 注册失败
${error.message || '网络错误,请稍后重试'}
`;
formMessages.classList.remove('hidden');
formMessages.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Re-enable submit button
submitBtn.disabled = false;
submitText.classList.remove('hidden');
submitLoading.classList.add('hidden');
}
});
}