// ============================================= // PrintPro - Admin Panel Script // ============================================= // --- DEFAULT PRICING (same as frontend) --- const DEFAULT_PRICING = { products: { 'business-cards': { name: 'Business Cards', icon: 'credit-card', image: 'http://static.photos/office/320x240/42', basePrice: 19.00, sizes: [ { id: '3.5x2', name: '3.5" x 2"', price: 0 }, { id: '3.5x2-rounded', name: '3.5" x 2" Rounded', price: 4 }, { id: '3.5x2-square', name: '3.5" x 3.5" Square', price: 6 } ], paperTypes: [ { id: '14pt-gloss', name: '14pt Gloss', price: 0 }, { id: '16pt-matte', name: '16pt Matte', price: 6 }, { id: '14pt-uncoated', name: '14pt Uncoated', price: 3 }, { id: '16pt-silk', name: '16pt Silk', price: 8 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'matte', name: 'Matte Coating', price: 5 }, { id: 'gloss', name: 'Gloss Coating', price: 5 }, { id: 'uv', name: 'UV Spot Gloss', price: 12 }, { id: 'foil', name: 'Foil Stamp', price: 18 } ], quantityTiers: [ { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, { min: 251, max: 500, multiplier: 0.75, label: '251-500' }, { min: 501, max: 1000, multiplier: 0.55, label: '501-1K' }, { min: 1001, max: 2500, multiplier: 0.40, label: '1K-2.5K' }, { min: 2501, max: 5000, multiplier: 0.30, label: '2.5K-5K' }, { min: 5001, max: 10000, multiplier: 0.22, label: '5K-10K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 15 }, { id: 'express', name: 'Express (1-2 days)', price: 30 } ], defaultQuantity: 500 }, 'flyers': { name: 'Flyers', icon: 'file-text', image: 'http://static.photos/office/320x240/55', basePrice: 35.00, sizes: [ { id: '8.5x11', name: '8.5" x 11"', price: 0 }, { id: '5.5x8.5', name: '5.5" x 8.5"', price: -3 }, { id: '11x17', name: '11" x 17"', price: 8 } ], paperTypes: [ { id: '100lb-gloss', name: '100lb Gloss', price: 0 }, { id: '80lb-matte', name: '80lb Matte', price: -2 }, { id: '100lb-uncoated', name: '100lb Uncoated', price: 2 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'matte', name: 'Matte Coating', price: 8 }, { id: 'gloss', name: 'Gloss Coating', price: 8 } ], quantityTiers: [ { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, { min: 251, max: 500, multiplier: 0.70, label: '251-500' }, { min: 501, max: 1000, multiplier: 0.50, label: '501-1K' }, { min: 1001, max: 5000, multiplier: 0.35, label: '1K-5K' }, { min: 5001, max: 10000, multiplier: 0.25, label: '5K-10K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 20 }, { id: 'express', name: 'Express (1-2 days)', price: 40 } ], defaultQuantity: 250 }, 'brochures': { name: 'Brochures', icon: 'book-open', image: 'http://static.photos/office/320x240/67', basePrice: 55.00, sizes: [ { id: '8.5x11-bi', name: '8.5"x11" Bi-Fold', price: 0 }, { id: '8.5x11-tri', name: '8.5"x11" Tri-Fold', price: 3 }, { id: '11x17-bi', name: '11"x17" Bi-Fold', price: 10 } ], paperTypes: [ { id: '100lb-gloss', name: '100lb Gloss', price: 0 }, { id: '80lb-matte', name: '80lb Matte', price: -3 }, { id: '100lb-silk', name: '100lb Silk', price: 5 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'matte', name: 'Matte Coating', price: 10 }, { id: 'gloss', name: 'Gloss Coating', price: 10 } ], quantityTiers: [ { min: 25, max: 100, multiplier: 1.0, label: '25-100' }, { min: 101, max: 250, multiplier: 0.70, label: '101-250' }, { min: 251, max: 500, multiplier: 0.52, label: '251-500' }, { min: 501, max: 1000, multiplier: 0.40, label: '501-1K' }, { min: 1001, max: 5000, multiplier: 0.30, label: '1K-5K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 25 }, { id: 'express', name: 'Express (1-2 days)', price: 50 } ], defaultQuantity: 100 }, 'banners': { name: 'Banners', icon: 'flag', image: 'http://static.photos/office/320x240/78', basePrice: 45.00, sizes: [ { id: '24x36', name: '24" x 36"', price: 0 }, { id: '36x48', name: '36" x 48"', price: 15 }, { id: '48x72', name: '48" x 72"', price: 35 } ], paperTypes: [ { id: 'vinyl', name: '13oz Vinyl', price: 0 }, { id: 'mesh', name: 'Mesh Vinyl', price: 8 }, { id: 'fabric', name: 'Fabric', price: 20 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'grommets', name: 'Grommets', price: 5 }, { id: 'pole-pockets', name: 'Pole Pockets', price: 10 } ], quantityTiers: [ { min: 1, max: 5, multiplier: 1.0, label: '1-5' }, { min: 6, max: 10, multiplier: 0.80, label: '6-10' }, { min: 11, max: 25, multiplier: 0.65, label: '11-25' }, { min: 26, max: 50, multiplier: 0.50, label: '26-50' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 20 }, { id: 'express', name: 'Express (1-2 days)', price: 45 } ], defaultQuantity: 1 }, 'stickers': { name: 'Stickers & Labels', icon: 'sticky-note', image: 'http://static.photos/office/320x240/91', basePrice: 25.00, sizes: [ { id: '2x2', name: '2" x 2"', price: 0 }, { id: '3x3', name: '3" x 3"', price: 5 }, { id: '4x4', name: '4" x 4"', price: 8 }, { id: 'custom-die', name: 'Custom Die-Cut', price: 15 } ], paperTypes: [ { id: 'white-vinyl', name: 'White Vinyl', price: 0 }, { id: 'clear-vinyl', name: 'Clear Vinyl', price: 5 }, { id: 'metallic', name: 'Metallic', price: 10 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'matte-lam', name: 'Matte Laminate', price: 6 }, { id: 'gloss-lam', name: 'Gloss Laminate', price: 6 } ], quantityTiers: [ { min: 50, max: 250, multiplier: 1.0, label: '50-250' }, { min: 251, max: 500, multiplier: 0.70, label: '251-500' }, { min: 501, max: 1000, multiplier: 0.48, label: '501-1K' }, { min: 1001, max: 2500, multiplier: 0.35, label: '1K-2.5K' }, { min: 2501, max: 5000, multiplier: 0.25, label: '2.5K-5K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 12 }, { id: 'express', name: 'Express (1-2 days)', price: 28 } ], defaultQuantity: 250 }, 'postcards': { name: 'Postcards', icon: 'mail', image: 'http://static.photos/office/320x240/33', basePrice: 28.00, sizes: [ { id: '4x6', name: '4" x 6"', price: 0 }, { id: '5x7', name: '5" x 7"', price: 4 }, { id: '6x9', name: '6" x 9"', price: 7 }, { id: '6x11', name: '6" x 11"', price: 10 } ], paperTypes: [ { id: '14pt-gloss', name: '14pt Gloss', price: 0 }, { id: '16pt-matte', name: '16pt Matte', price: 5 }, { id: '14pt-uncoated', name: '14pt Uncoated', price: 2 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'matte', name: 'Matte Coating', price: 5 }, { id: 'gloss', name: 'Gloss Coating', price: 5 }, { id: 'uv-front', name: 'UV Front Only', price: 8 } ], quantityTiers: [ { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' }, { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' }, { min: 2501, max: 5000, multiplier: 0.38, label: '2.5K-5K' }, { min: 5001, max: 10000, multiplier: 0.28, label: '5K-10K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 12 }, { id: 'express', name: 'Express (1-2 days)', price: 28 } ], defaultQuantity: 500 }, 'letterhead': { name: 'Letterhead', icon: 'file', image: 'http://static.photos/office/320x240/44', basePrice: 40.00, sizes: [ { id: '8.5x11', name: '8.5" x 11"', price: 0 }, { id: '8.5x14', name: '8.5" x 14"', price: 5 } ], paperTypes: [ { id: '24lb-bond', name: '24lb Bond', price: 0 }, { id: '28lb-bond', name: '28lb Bond', price: 5 }, { id: '32lb-bond', name: '32lb Bond', price: 10 }, { id: 'linen', name: 'Linen Finish', price: 15 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'watermark', name: 'Watermark', price: 8 } ], quantityTiers: [ { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, { min: 501, max: 1000, multiplier: 0.70, label: '501-1K' }, { min: 1001, max: 2500, multiplier: 0.48, label: '1K-2.5K' }, { min: 2501, max: 5000, multiplier: 0.35, label: '2.5K-5K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 18 }, { id: 'express', name: 'Express (1-2 days)', price: 38 } ], defaultQuantity: 500 }, 'envelopes': { name: 'Envelopes', icon: 'mail-open', image: 'http://static.photos/office/320x240/56', basePrice: 32.00, sizes: [ { id: '10-standard', name: '#10 Standard', price: 0 }, { id: 'a7', name: 'A7 (5.25x7.25)', price: 3 }, { id: 'a2', name: 'A2 (4.375x5.75)', price: 0 } ], paperTypes: [ { id: '24lb-white', name: '24lb White Wove', price: 0 }, { id: '28lb-white', name: '28lb White Wove', price: 4 }, { id: 'kraft', name: 'Kraft Brown', price: 3 } ], finishing: [ { id: 'none', name: 'None', price: 0 }, { id: 'window', name: 'Window', price: 5 }, { id: 'peel-seal', name: 'Peel & Seal', price: 4 } ], quantityTiers: [ { min: 100, max: 500, multiplier: 1.0, label: '100-500' }, { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' }, { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' }, { min: 2501, max: 5000, multiplier: 0.36, label: '2.5K-5K' } ], turnaround: [ { id: 'standard', name: 'Standard (5-7 days)', price: 0 }, { id: 'rush', name: 'Rush (3-4 days)', price: 15 }, { id: 'express', name: 'Express (1-2 days)', price: 32 } ], defaultQuantity: 250 } } }; // --- STATE --- let pricing = null; let editingProductKey = null; let editingData = null; // --- AUTH --- const ADMIN_CREDENTIALS = { username: 'admin', password: 'admin123' }; let isLoggedIn = sessionStorage.getItem('printpro_admin_loggedin') === 'true'; // --- DOM --- const loginScreen = document.getElementById('loginScreen'); const dashboard = document.getElementById('dashboard'); const loginForm = document.getElementById('loginForm'); const loginError = document.getElementById('loginError'); const editModal = document.getElementById('editModal'); const modalProductName = document.getElementById('modalProductName'); const modalContent = document.getElementById('modalContent'); const productCards = document.getElementById('productCards'); const toast = document.getElementById('toast'); const toastMsg = document.getElementById('toastMsg'); // --- INIT --- function getPricing() { const stored = localStorage.getItem('printpro_pricing'); if (stored) { try { return JSON.parse(stored); } catch (e) { return JSON.parse(JSON.stringify(DEFAULT_PRICING)); } } const def = JSON.parse(JSON.stringify(DEFAULT_PRICING)); localStorage.setItem('printpro_pricing', JSON.stringify(def)); return def; } function savePricingToStorage(data) { localStorage.setItem('printpro_pricing', JSON.stringify(data)); // Dispatch events to notify the frontend window.dispatchEvent(new Event('pricingUpdated')); // Also try to notify other tabs via storage event localStorage.setItem('printpro_pricing_updated', Date.now().toString()); updateLastSaved(); } function updateLastSaved() { const now = new Date(); const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); document.getElementById('statLastSaved').textContent = timeStr; localStorage.setItem('printpro_last_saved', timeStr); } // --- LOGIN --- loginForm.addEventListener('submit', (e) => { e.preventDefault(); const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value.trim(); if (username === ADMIN_CREDENTIALS.username && password === ADMIN_CREDENTIALS.password) { isLoggedIn = true; sessionStorage.setItem('printpro_admin_loggedin', 'true'); loginScreen.classList.add('hidden'); dashboard.classList.remove('hidden'); initDashboard(); showToast('Welcome back, Admin! 👋'); } else { loginError.textContent = 'Invalid username or password.'; loginError.classList.remove('hidden'); document.getElementById('password').classList.add('border-red-500'); setTimeout(() => { document.getElementById('password').classList.remove('border-red-500'); loginError.classList.add('hidden'); }, 2500); } }); function logout() { isLoggedIn = false; sessionStorage.removeItem('printpro_admin_loggedin'); dashboard.classList.add('hidden'); loginScreen.classList.remove('hidden'); document.getElementById('password').value = ''; loginError.classList.add('hidden'); } // --- TOAST --- function showToast(message) { toastMsg.textContent = message; toast.classList.remove('hidden'); void toast.offsetWidth; toast.classList.add('toast'); setTimeout(() => { toast.classList.add('hidden'); toast.classList.remove('toast'); }, 3000); } // --- DASHBOARD --- function initDashboard() { pricing = getPricing(); updateStats(); renderProductCards(); lucide.createIcons(); } function updateStats() { const products = pricing.products; const keys = Object.keys(products); const totalBase = keys.reduce((sum, k) => sum + products[k].basePrice, 0); const avgPrice = totalBase / keys.length; const totalTiers = keys.reduce((sum, k) => sum + products[k].quantityTiers.length, 0); document.getElementById('statProducts').textContent = keys.length; document.getElementById('statAvgPrice').textContent = '$' + avgPrice.toFixed(2); document.getElementById('statTiers').textContent = totalTiers; document.getElementById('statLastSaved').textContent = localStorage.getItem('printpro_last_saved') || 'Never'; } function renderProductCards() { const products = pricing.products; productCards.innerHTML = Object.entries(products).map(([key, product]) => `
${product.name}

${product.name}

Product Key: ${key}

$${product.basePrice.toFixed(2)}

Sizes

${product.sizes.length} options

Paper Types

${product.paperTypes.length} options

Finishing

${product.finishing.length} options

Tiers

${product.quantityTiers.length} tiers

`).join(''); lucide.createIcons(); } // --- EDIT MODAL --- function openEditModal(productKey) { editingProductKey = productKey; // Deep clone the product data for editing editingData = JSON.parse(JSON.stringify(pricing.products[productKey])); modalProductName.textContent = editingData.name; renderModalContent(); editModal.classList.remove('hidden'); editModal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } function closeModal() { editModal.classList.add('hidden'); editModal.style.display = 'none'; document.body.style.overflow = ''; editingProductKey = null; editingData = null; } function renderModalContent() { const d = editingData; modalContent.innerHTML = `
$

The starting price before any options or quantity discounts.

${d.sizes.map((s, i) => `
$
`).join('')}
${d.paperTypes.map((p, i) => `
$
`).join('')}
${d.finishing.map((f, i) => `
$
`).join('')}
${d.quantityTiers.map((t, i) => `
#${i+1}
`).join('')}
${d.turnaround.map((t, i) => `
$
`).join('')}
`; lucide.createIcons(); } // --- MODAL: ADD/REMOVE OPTIONS --- function addSizeOption() { editingData.sizes.push({ id: 'new-' + Date.now(), name: 'New Size', price: 0 }); renderModalContent(); } function removeSizeOption(i) { if (editingData.sizes.length > 1) { editingData.sizes.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one size option ⚠️'); } } function addPaperOption() { editingData.paperTypes.push({ id: 'new-' + Date.now(), name: 'New Paper', price: 0 }); renderModalContent(); } function removePaperOption(i) { if (editingData.paperTypes.length > 1) { editingData.paperTypes.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one paper option ⚠️'); } } function addFinishingOption() { editingData.finishing.push({ id: 'new-' + Date.now(), name: 'New Finish', price: 0 }); renderModalContent(); } function removeFinishingOption(i) { if (editingData.finishing.length > 0) { editingData.finishing.splice(i, 1); renderModalContent(); } } function addTier() { const tiers = editingData.quantityTiers; const lastTier = tiers[tiers.length - 1]; const newMin = lastTier ? lastTier.max + 1 : 1; tiers.push({ min: newMin, max: newMin + 500, multiplier: 0.50, label: `${newMin}-${newMin+500}` }); renderModalContent(); } function removeTier(i) { if (editingData.quantityTiers.length > 1) { editingData.quantityTiers.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one tier ⚠️'); } } function addTurnaroundOption() { editingData.turnaround.push({ id: 'new-' + Date.now(), name: 'New Turnaround', price: 0 }); renderModalContent(); } function removeTurnaroundOption(i) { if (editingData.turnaround.length > 1) { editingData.turnaround.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one turnaround option ⚠️'); } } // --- SAVE MODAL CHANGES --- function saveModalChanges() { if (!editingProductKey || !editingData) return; // Gather base price const basePriceEl = document.getElementById('editBasePrice'); editingData.basePrice = parseFloat(basePriceEl.value) || 0; // Gather sizes const sizeNames = modalContent.querySelectorAll('[data-size-name]'); const sizePrices = modalContent.querySelectorAll('[data-size-price]'); editingData.sizes = editingData.sizes.map((s, i) => ({ ...s, name: sizeNames[i]?.value || s.name, price: parseFloat(sizePrices[i]?.value) || 0 })); // Gather paper types const paperNames = modalContent.querySelectorAll('[data-paper-name]'); const paperPrices = modalContent.querySelectorAll('[data-paper-price]'); editingData.paperTypes = editingData.paperTypes.map((p, i) => ({ ...p, name: paperNames[i]?.value || p.name, price: parseFloat(paperPrices[i]?.value) || 0 })); // Gather finishing const finishingNames = modalContent.querySelectorAll('[data-finishing-name]'); const finishingPrices = modalContent.querySelectorAll('[data-finishing-price]'); editingData.finishing = editingData.finishing.map((f, i) => ({ ...f, name: finishingNames[i]?.value || f.name, price: parseFloat(finishingPrices[i]?.value) || 0 })); // Gather tiers const tierMins = modalContent.querySelectorAll('[data-tier-min]'); const tierMaxs = modalContent.querySelectorAll('[data-tier-max]'); const tierMults = modalContent.querySelectorAll('[data-tier-mult]'); editingData.quantityTiers = editingData.quantityTiers.map((t, i) => ({ ...t, min: parseInt(tierMins[i]?.value) || t.min, max: parseInt(tierMaxs[i]?.value) || t.max, multiplier: parseFloat(tierMults[i]?.value) || t.multiplier, label: `${parseInt(tierMins[i]?.value) || t.min}-${parseInt(tierMaxs[i]?.value) || t.max}` })); // Gather turnaround const turnaroundNames = modalContent.querySelectorAll('[data-turnaround-name]'); const turnaroundPrices = modalContent.querySelectorAll('[data-turnaround-price]'); editingData.turnaround = editingData.turnaround.map((t, i) => ({ ...t, name: turnaroundNames[i]?.value || t.name, price: parseFloat(turnaroundPrices[i]?.value) || 0 })); // Save to pricing pricing.products[editingProductKey] = editingData; savePricingToStorage(pricing); closeModal(); updateStats(); renderProductCards(); showToast('Product pricing updated successfully! ✅'); } // --- SAVE ALL --- function saveAllPricing() { savePricingToStorage(pricing); showToast('All pricing saved successfully! 💾'); } // --- RESET --- function resetToDefault() { if (confirm('Are you sure you want to reset ALL pricing to default values? This cannot be undone.')) { pricing = JSON.parse(JSON.stringify(DEFAULT_PRICING)); savePricingToStorage(pricing); updateStats(); renderProductCards(); showToast('Pricing reset to defaults! 🔄'); } } // --- CLOSE MODAL ON OVERLAY CLICK --- editModal.addEventListener('click', (e) => { if (e.target === editModal) { closeModal(); } }); // --- KEYBOARD SHORTCUTS --- document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !editModal.classList.contains('hidden')) { closeModal(); } }); // --- INIT ON LOAD --- function init() { if (isLoggedIn) { loginScreen.classList.add('hidden'); dashboard.classList.remove('hidden'); initDashboard(); } else { loginScreen.classList.remove('hidden'); dashboard.classList.add('hidden'); } // Update last saved display document.getElementById('statLastSaved').textContent = localStorage.getItem('printpro_last_saved') || 'Never'; } document.addEventListener('DOMContentLoaded', init); // Expose functions to global scope window.openEditModal = openEditModal; window.closeModal = closeModal; window.saveModalChanges = saveModalChanges; window.addSizeOption = addSizeOption; window.removeSizeOption = removeSizeOption; window.addPaperOption = addPaperOption; window.removePaperOption = removePaperOption; window.addFinishingOption = addFinishingOption; window.removeFinishingOption = removeFinishingOption; window.addTier = addTier; window.removeTier = removeTier; window.addTurnaroundOption = addTurnaroundOption; window.removeTurnaroundOption = removeTurnaroundOption; window.saveAllPricing = saveAllPricing; window.resetToDefault = resetToDefault; window.logout = logout;