loreal-global-kickoff/js/app.js
DJP dbf7090d09 Initial commit: L'Oréal Box Asset Submission Form
- Set up PHP application with Composer and JWT library
- Implemented SSO authentication with local dev mode
- Created Box API service for folder validation
- Built two-column form interface (form + preview)
- Added real-time Box ID validation with AJAX
- Integrated webhook submission with status response
- Auto-populate Master Campaign Number from Box folder hierarchy
- Responsive design with Montserrat font and black/yellow theme

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 14:43:36 -05:00

391 lines
12 KiB
JavaScript

/**
* L'Oréal Box Asset Submission Form - Frontend JavaScript
*/
// State management
let validatedBoxData = null;
let isBoxIdValid = false;
// DOM Elements
const boxIdInput = document.getElementById('boxId');
const boxIdValidation = document.getElementById('boxIdValidation');
const submitBtn = document.getElementById('submitBtn');
const assetForm = document.getElementById('assetForm');
const messageDiv = document.getElementById('message');
const previewContainer = document.getElementById('previewContainer');
// Debounce timer for Box ID validation
let validationTimer = null;
/**
* Initialize the application
*/
function init() {
// Box ID input event listener
boxIdInput.addEventListener('input', handleBoxIdInput);
boxIdInput.addEventListener('blur', validateBoxIdImmediate);
// Form submission
assetForm.addEventListener('submit', handleFormSubmit);
}
/**
* Handle Box ID input with debouncing
*/
function handleBoxIdInput(e) {
const boxId = e.target.value.trim();
// Clear previous validation state
isBoxIdValid = false;
validatedBoxData = null;
submitBtn.disabled = true;
// Reset validation message
boxIdValidation.textContent = '';
boxIdValidation.className = 'validation-message';
boxIdInput.classList.remove('valid', 'invalid', 'validating');
// Clear existing timer
if (validationTimer) {
clearTimeout(validationTimer);
}
// Don't validate empty input
if (!boxId) {
showPreviewPlaceholder();
return;
}
// Show validating state
boxIdInput.classList.add('validating');
boxIdValidation.textContent = 'Validating...';
boxIdValidation.className = 'validation-message loading';
// Debounce validation (wait 800ms after user stops typing)
validationTimer = setTimeout(() => {
validateBoxId(boxId);
}, 800);
}
/**
* Validate Box ID immediately (on blur)
*/
function validateBoxIdImmediate() {
const boxId = boxIdInput.value.trim();
if (!boxId) {
return;
}
// Clear debounce timer
if (validationTimer) {
clearTimeout(validationTimer);
}
// Validate immediately
boxIdInput.classList.add('validating');
boxIdValidation.textContent = 'Validating...';
boxIdValidation.className = 'validation-message loading';
validateBoxId(boxId);
}
/**
* Validate Box ID via API
*/
async function validateBoxId(boxId) {
try {
const response = await fetch('validate-box.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ boxId })
});
const result = await response.json();
if (result.success) {
// Valid Box ID
isBoxIdValid = true;
validatedBoxData = result.data;
boxIdInput.classList.remove('validating', 'invalid');
boxIdInput.classList.add('valid');
boxIdValidation.textContent = '✓ Valid Box ID';
boxIdValidation.className = 'validation-message success';
// Enable submit button if all other fields are filled
checkFormValidity();
// Show preview
showPreview(result.data);
} else {
// Invalid Box ID
isBoxIdValid = false;
validatedBoxData = null;
boxIdInput.classList.remove('validating', 'valid');
boxIdInput.classList.add('invalid');
boxIdValidation.textContent = '✗ ' + (result.error || 'Invalid Box ID');
boxIdValidation.className = 'validation-message error';
submitBtn.disabled = true;
// Show placeholder
showPreviewPlaceholder();
}
} catch (error) {
console.error('Validation error:', error);
boxIdInput.classList.remove('validating', 'valid');
boxIdInput.classList.add('invalid');
boxIdValidation.textContent = '✗ Error validating Box ID';
boxIdValidation.className = 'validation-message error';
submitBtn.disabled = true;
showPreviewPlaceholder();
}
}
/**
* Show preview placeholder
*/
function showPreviewPlaceholder() {
previewContainer.innerHTML = `
<div class="preview-placeholder">
<svg width="100" height="100" viewBox="0 0 100 100" fill="none">
<path d="M50 10L80 30V70L50 90L20 70V30L50 10Z" stroke="#FFC407" stroke-width="3" fill="none"/>
<path d="M50 35V65M35 50H65" stroke="#FFC407" stroke-width="3" stroke-linecap="round"/>
</svg>
<p>Enter a Box ID to preview contents</p>
</div>
`;
}
/**
* Show Box preview
*/
function showPreview(data) {
const { folderName, masterCampaignNumber, masterCampaignId, contents } = data;
const { total, folders, files } = contents;
previewContainer.innerHTML = `
<div class="preview-content">
<div class="preview-header">
<h2>Box Preview</h2>
</div>
<div class="preview-info">
<div class="preview-info-label">Master Campaign Number</div>
<div class="preview-info-value">${escapeHtml(masterCampaignNumber)}</div>
</div>
<div class="preview-info">
<div class="preview-info-label">Master Campaign ID</div>
<div class="preview-info-value">${escapeHtml(masterCampaignId || 'N/A')}</div>
</div>
<div class="preview-info">
<div class="preview-info-label">Folder Name</div>
<div class="preview-info-value">${escapeHtml(folderName)}</div>
</div>
<div class="preview-section">
<h3>Contents Summary</h3>
<div class="contents-summary">
<div class="summary-item">
<div class="summary-count">${total}</div>
<div class="summary-label">Total Items</div>
</div>
<div class="summary-item">
<div class="summary-count">${folders.length}</div>
<div class="summary-label">Folders</div>
</div>
<div class="summary-item">
<div class="summary-count">${files.length}</div>
<div class="summary-label">Files</div>
</div>
</div>
</div>
${folders.length > 0 ? `
<div class="preview-section">
<h3>Folders (${folders.length})</h3>
<div class="items-list">
${folders.map(folder => `
<div class="item">
<div class="item-icon">📁</div>
<div class="item-name">${escapeHtml(folder.name)}</div>
</div>
`).join('')}
</div>
</div>
` : ''}
${files.length > 0 ? `
<div class="preview-section">
<h3>Files (${files.length})</h3>
<div class="items-list">
${files.map(file => `
<div class="item">
<div class="item-icon">📄</div>
<div class="item-name">${escapeHtml(file.name)}</div>
</div>
`).join('')}
</div>
</div>
` : ''}
</div>
`;
}
/**
* Check if form is valid and enable/disable submit button
*/
function checkFormValidity() {
const supplyDate = document.getElementById('supplyDate').value;
const liveDate = document.getElementById('liveDate').value;
const endDate = document.getElementById('endDate').value;
// Enable submit only if Box ID is valid and all dates are filled
if (isBoxIdValid && supplyDate && liveDate && endDate) {
submitBtn.disabled = false;
} else {
submitBtn.disabled = true;
}
}
/**
* Handle form submission
*/
async function handleFormSubmit(e) {
e.preventDefault();
// Double-check validation
if (!isBoxIdValid || !validatedBoxData) {
showMessage('Please enter a valid Box ID first', 'error');
return;
}
const supplyDate = document.getElementById('supplyDate').value;
const liveDate = document.getElementById('liveDate').value;
const endDate = document.getElementById('endDate').value;
if (!supplyDate || !liveDate || !endDate) {
showMessage('Please fill in all date fields', 'error');
return;
}
// Disable submit button and show loading state
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
try {
const payload = {
boxId: boxIdInput.value.trim(),
supplyDate: formatDate(supplyDate),
liveDate: formatDate(liveDate),
endDate: formatDate(endDate),
boxData: validatedBoxData
};
const response = await fetch('submit.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(payload)
});
const result = await response.json();
if (result.success) {
// Success - show message
showMessage(
result.message || 'Form submitted successfully! ' +
(result.webhookResponse?.message || ''),
'success'
);
// Reset form after successful submission
setTimeout(() => {
assetForm.reset();
isBoxIdValid = false;
validatedBoxData = null;
boxIdInput.classList.remove('valid', 'invalid', 'validating');
boxIdValidation.textContent = '';
showPreviewPlaceholder();
submitBtn.disabled = true;
}, 2000);
} else {
// Error from webhook or server
showMessage(
'Submission failed: ' + (result.error || 'Unknown error') +
(result.webhookResponse?.message ? ' - ' + result.webhookResponse.message : ''),
'error'
);
}
} catch (error) {
console.error('Submission error:', error);
showMessage('Error submitting form. Please try again.', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Submit';
}
}
/**
* Format date to DD/MM/YYYY 00:00
*/
function formatDate(dateString) {
const date = new Date(dateString);
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year} 00:00`;
}
/**
* Show message to user
*/
function showMessage(text, type) {
messageDiv.textContent = text;
messageDiv.className = `message ${type}`;
messageDiv.style.display = 'block';
// Auto-hide after 5 seconds
setTimeout(() => {
messageDiv.style.display = 'none';
}, 5000);
}
/**
* Escape HTML to prevent XSS
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Add event listeners to date fields to check form validity
document.getElementById('supplyDate').addEventListener('change', checkFormValidity);
document.getElementById('liveDate').addEventListener('change', checkFormValidity);
document.getElementById('endDate').addEventListener('change', checkFormValidity);
// Initialize app when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}