CONFIGURATOR CONTROL

Truck Guy Upfitters — Bed Cap Master Admin

Manufacturer Wholesale Costs, Dynamic Margins & Customer MSRP Totalizer

Changes saved successfully.
Showing 0 of 0 Covers
Brand Model Name & Identifier Style / Category Wholesale Cost ($) Markup % Margin % MSRP Retail ($) Gross Profit ($) Status Actions
Component Options & Feature Overrides
Customize retail pricing, customer-facing titles, and spec descriptions for windows, doors, roof racks, toolboxes, and lighting accessories.
Organization Profile & Storefront Branding
When other dealers, upfitters, or clients download this package from GitHub, these settings customize the customer-facing storefront.
Venturous Hosted URL & Storefront Routing Strategy
Choose whether your customer builds their cap on your dedicated Venturous address (e.g. buildyour.venturous.top/@/truck_guy_upfitters/) or directly inside your website content window.
Live Customer Configurator Address Open Live Link ↗
https://buildyour.venturous.top/@/truck_guy_upfitters/

Share this link directly on social media, in Google ads, customer email quotes, or link to it from your store navigation.

Venturous Dealer Account Provisioning & Email Invitations VENTUROUS HQ ONLY
Venturous staff provision new authorized upfitters by sending an invitation link. Two distinct emails are strictly required for the dealer to complete registration to ensure fail-safe account recovery.

Invite New Installing Dealer

Active Dealer Provisioning Directory

Dealer Name Handle Slug Primary Owner Email Recovery Email Status Actions
Truck Guy Upfitters /@/truck_guy_upfitters/ admin@example.com recovery@example.com ● Active
Shop Labor, Taxes & Global Pricing Defaults
Configure shop installation rates, sales tax percentages, and default target markup / margin formulas applied to new covers.
Fail-Safe Administrator Access (Mandatory 2-Email Security) LOCKOUT PREVENTION
To prevent instance lockout when staff leave or credentials expire, two distinct email addresses are strictly required to save administrative settings.
Customer Login & Dealer OAuth Initialization
Configure how customers and sales staff authenticate when saving or submitting quotes. Enable guest mode or integrate Google OAuth / Dealer PINs.
Legal Trademarks & OEM Relationship Disclaimer
Displayed across all customer quotes, estimates, and storefront footers to ensure full compliance with automotive manufacturer trademark fair use.
`; } else { embedBox.value = `\nConfigure & Price Your Bed Cap Online`; } } } function onDeploymentModeChange(val) { const slug = document.getElementById('settingCompanySlug')?.value || 'truck_guy_upfitters'; updateLiveSlugPreview(slug); markDirty(); } function copyEmbedCode() { const embedBox = document.getElementById('settingEmbedCode'); if (embedBox) { embedBox.select(); navigator.clipboard.writeText(embedBox.value); showBanner('Embed code copied to clipboard!', 'success'); } } // Tab Controller function switchAdminTab(tabKey) { document.querySelectorAll('.admin-tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.admin-tab-content').forEach(content => content.classList.remove('active')); const targetTab = document.getElementById('tab' + tabKey.charAt(0).toUpperCase() + tabKey.slice(1)); if (targetTab) targetTab.classList.add('active'); const activeBtn = Array.from(document.querySelectorAll('.admin-tab-btn')).find(b => b.getAttribute('onclick') && b.getAttribute('onclick').includes(tabKey)); if (activeBtn) activeBtn.classList.add('active'); if (tabKey === 'options') { renderOptionsEditor(); } } function syncColorInput(inputId, val) { document.getElementById(inputId).value = val; markDirty(); } function syncColorPicker(pickerId, val) { if (/^#[0-9A-Fa-f]{6}$/.test(val)) { document.getElementById(pickerId).value = val; } markDirty(); } function onAuthProviderChange(val) { const gBlock = document.getElementById('googleOauthSettingsBlock'); const pinBlock = document.getElementById('dealerPinSettingsBlock'); if (gBlock) gBlock.style.display = val === 'google' ? 'grid' : 'none'; if (pinBlock) pinBlock.style.display = val === 'dealer_pin' ? 'block' : 'none'; markDirty(); } // Render Options Catalog Editor function renderOptionsEditor() { const container = document.getElementById('optionsCategoriesContainer'); if (!container) return; container.innerHTML = ''; const optCat = currentCatalog.options_catalog || {}; const catKeys = Object.keys(optCat); document.getElementById('tabBadgeOptions').innerText = catKeys.reduce((acc, k) => acc + (optCat[k] || []).length, 0); catKeys.forEach(catKey => { const items = optCat[catKey] || []; const card = document.createElement('div'); card.className = 'option-cat-card'; let formattedCatName = catKey.replace(/_/g, ' ').toUpperCase(); card.innerHTML = `
${formattedCatName} (${items.length} Options)
`; container.appendChild(card); const list = card.querySelector(`#optList_${catKey}`); items.forEach((item, idx) => { const row = document.createElement('div'); row.className = 'option-item-row'; row.innerHTML = `
${item.id}
`; list.appendChild(row); }); }); } function updateOptionItem(catKey, itemIdx, field, val) { if (currentCatalog.options_catalog && currentCatalog.options_catalog[catKey]) { currentCatalog.options_catalog[catKey][itemIdx][field] = val; markDirty(); } } // Global Catalog State let currentCatalog = { metadata: {}, pricing_rules: {}, models: [], options_catalog: {} }; let catalogHistory = []; let isDirty = false; let perspectiveMode = 'retail'; // 'retail' or 'mfr' let verifiedPrimaryEmail = ""; let verifiedRecoveryEmail = ""; let pendingVerification = null; // { email, emailType } // Math Helpers function markupToMargin(markup) { if (markup <= -100) return 0; return (markup / (100 + markup)) * 100; } function marginToMarkup(margin) { if (margin >= 100) return 999; return (margin / (100 - margin)) * 100; } function calcRetail(cost, markup) { return cost * (1 + (markup / 100)); } function calcCost(retail, markup) { if (markup <= -100) return retail; return retail / (1 + (markup / 100)); } // Environment & Portal Detection const isDealerPortal = window.location.hostname.startsWith('dealer.'); const isAdminPortal = window.location.hostname.startsWith('admin.'); function applyPortalCustomizations() { if (isDealerPortal) { // Hide dealer account invitation & corporate oversight tab const dealersTabBtn = Array.from(document.querySelectorAll('.admin-tab-btn')).find(b => b.getAttribute('onclick') && b.getAttribute('onclick').includes('dealers')); if (dealersTabBtn) dealersTabBtn.style.display = 'none'; // Update header & badges const logoBadge = document.querySelector('.brand-logo-badge'); if (logoBadge) logoBadge.innerText = 'AUTHORIZED DEALER PORTAL'; const subtitle = document.querySelector('.header-title-group p'); if (subtitle) subtitle.innerText = 'Dealer Storefront Settings, Markups, Labor Rates & Local Quote Management'; // Disable/Hide wholesale manufacturer perspective switch const mfrBtn = document.getElementById('btnModeMfr'); if (mfrBtn) mfrBtn.style.display = 'none'; // Hide manufacturer cost inputs in modal & table const costTh = Array.from(document.querySelectorAll('.data-table thead th')).find(th => th.innerText.toLowerCase().includes('base cost')); if (costTh) costTh.style.display = 'none'; const addCoverBtn = document.querySelector('.header-actions .btn-blue'); if (addCoverBtn) addCoverBtn.style.display = 'none'; } else { const logoBadge = document.querySelector('.brand-logo-badge'); if (logoBadge) logoBadge.innerText = 'VENTUROUS EXECUTIVE DESK'; } } // Initialize on Load window.addEventListener('DOMContentLoaded', async () => { applyPortalCustomizations(); await fetchCatalog(); await fetchHistory(); }); async function fetchCatalog() { try { const res = await fetch('/api/configurator/admin/catalog'); if (res.ok) { currentCatalog = await res.json(); populateSettings(); renderModelsTable(); } } catch (err) { showBanner('Error loading catalog: ' + err.message, 'error'); } } async function fetchHistory() { try { const res = await fetch('/api/configurator/admin/history'); if (res.ok) { catalogHistory = await res.json(); document.getElementById('historyCountBadge').innerText = catalogHistory.length; } } catch (err) { console.error('History fetch error:', err); } } function populateSettings() { const meta = currentCatalog.metadata || {}; const pricing = currentCatalog.pricing_rules || {}; const auth = meta.auth_config || {}; document.getElementById('headerCompanyTitle').innerText = (meta.company_name || 'Venturous') + ' — Bed Cap Master Admin'; document.getElementById('settingCompanyName').value = meta.company_name || 'Truck Guy Upfitters'; document.getElementById('settingCompanySlug').value = meta.company_slug || slugify(meta.company_name || 'truck_guy_upfitters'); document.getElementById('settingDeploymentMode').value = meta.deployment_mode || 'hosted_handle'; document.getElementById('settingParentHost').value = meta.parent_host || 'buildyour.venturous.top'; document.getElementById('settingRetailerDomain').value = meta.custom_retailer_domain || ''; updateLiveSlugPreview(document.getElementById('settingCompanySlug').value); document.getElementById('settingTagline').value = meta.tagline || 'Authorized Vehicle Upfitting & Overland Platform'; document.getElementById('settingSupportPhone').value = meta.support_phone || '864.79.TRUCK'; document.getElementById('settingSupportPhoneTel').value = meta.support_phone_tel || '18647987825'; document.getElementById('settingSupportEmail').value = meta.support_email || 'support@example.com'; document.getElementById('settingLogoUrl').value = meta.logo_url || ''; const pColor = meta.primary_color || '#00E5FF'; const sColor = meta.secondary_color || '#F59E0B'; document.getElementById('settingPrimaryColor').value = pColor; document.getElementById('settingPrimaryColorPicker').value = pColor; document.getElementById('settingSecondaryColor').value = sColor; document.getElementById('settingSecondaryColorPicker').value = sColor; document.getElementById('settingDefaultBrand').value = meta.default_brand || 'Venturous'; document.getElementById('settingDefaultMarkup').value = meta.default_markup_pct || 40.0; document.getElementById('settingDefaultMargin').value = (meta.default_margin_pct || markupToMargin(meta.default_markup_pct || 40.0)).toFixed(2); document.getElementById('settingTaxRate').value = ((meta.tax_rate || 0.06) * 100).toFixed(1); document.getElementById('settingCurrency').value = meta.currency || 'USD'; document.getElementById('settingLaborBase').value = pricing.labor_rates?.base_installation || 200.0; document.getElementById('settingLaborKeyless').value = pricing.labor_rates?.keyless_wiring || 120.0; document.getElementById('settingLaborRack').value = pricing.labor_rates?.rack_installation || 95.0; // Dual Admin Emails verifiedPrimaryEmail = (meta.admin_primary_email || auth.admin_primary_email || 'admin@example.com').toLowerCase().trim(); verifiedRecoveryEmail = (meta.admin_recovery_email || auth.admin_recovery_email || 'recovery@example.com').toLowerCase().trim(); document.getElementById('settingAdminPrimaryEmail').value = verifiedPrimaryEmail; document.getElementById('settingAdminRecoveryEmail').value = verifiedRecoveryEmail; // Auth Config const authProvider = auth.provider || (auth.enabled ? 'google' : 'disabled'); document.getElementById('settingAuthProvider').value = authProvider; document.getElementById('settingAuthAllowGuest').value = auth.allow_guest_submission !== false ? 'true' : 'false'; document.getElementById('settingGoogleClientId').value = auth.google_client_id || ''; document.getElementById('settingGoogleClientSecret').value = auth.google_client_secret || ''; document.getElementById('settingDealerPin').value = auth.dealer_pin || '1234'; onAuthProviderChange(authProvider); // Legal document.getElementById('settingLegalDisclaimer').value = meta.legal_disclaimer || 'Ford, Chevrolet, GMC, Ram, Toyota, Nissan, and Jeep are registered trademarks of their respective original equipment manufacturers. Venturous and {company_name} are independent aftermarket upfitters and are not affiliated with, sponsored by, or endorsed by any OEM.'; document.getElementById('footerVersion').innerText = meta.version || '1.0.0'; document.getElementById('tabBadgeCovers').innerText = (currentCatalog.models || []).length; } function markDirty() { isDirty = true; } function onGlobalMarkupChange(val) { const markup = parseFloat(val) || 0; const margin = markupToMargin(markup); document.getElementById('settingDefaultMargin').value = margin.toFixed(2); markDirty(); } function onGlobalMarginChange(val) { const margin = parseFloat(val) || 0; const markup = marginToMarkup(margin); document.getElementById('settingDefaultMarkup').value = markup.toFixed(2); markDirty(); } function setPerspectiveMode(mode) { perspectiveMode = mode; document.getElementById('btnModeRetail').classList.toggle('active', mode === 'retail'); document.getElementById('btnModeMfr').classList.toggle('active', mode === 'mfr'); renderModelsTable(); } function renderModelsTable() { const tbody = document.getElementById('modelsTableBody'); tbody.innerHTML = ''; const searchQ = document.getElementById('filterSearchInput').value.toLowerCase().trim(); const brandFilter = document.getElementById('filterBrandSelect').value; const styleFilter = document.getElementById('filterStyleSelect').value; const statusFilter = document.getElementById('filterStatusSelect').value; let models = currentCatalog.models || []; let visibleCount = 0; models.forEach((m, idx) => { // Filter checks if (brandFilter !== 'ALL' && m.brand !== brandFilter) return; if (styleFilter !== 'ALL' && m.style !== styleFilter) return; if (statusFilter === 'active' && m.is_active === false) return; if (statusFilter === 'archived' && m.is_active !== false) return; if (searchQ) { const haystack = `${m.name} ${m.id} ${m.brand} ${m.style} ${m.category} ${m.tagline || ''}`.toLowerCase(); if (!haystack.includes(searchQ)) return; } visibleCount++; const cost = parseFloat(m.cost || 0); const markup = parseFloat(m.markup_pct || 40.0); const margin = parseFloat(m.margin_pct || markupToMargin(markup)); const retail = parseFloat(m.base_retail_price || 0); const profit = retail - cost; const isArchived = m.is_active === false; const isOverridden = !!m.manual_price_override; const brandBadgeClass = `badge-brand-${(m.brand || 'custom').toLowerCase()}`; const tr = document.createElement('tr'); if (isArchived) tr.classList.add('row-archived'); tr.innerHTML = ` ${m.brand || 'Venturous'}
${m.name}
${m.id}
${(m.style || 'cab_high').replace(/_/g, ' ')}
${m.category || 'fiberglass'}
${isDealerPortal ? `` : `
$
`}
%
%
$
${isOverridden ? `LOCKED` : ''}
+$${profit.toFixed(2)} ${isArchived ? 'ARCHIVED' : 'ACTIVE'}
`; tbody.appendChild(tr); }); document.getElementById('tableRecordCount').innerText = `Showing ${visibleCount} of ${models.length} Covers`; } // Table Inline Math Updates function onTableCostChange(idx, val) { const cost = parseFloat(val) || 0; const m = currentCatalog.models[idx]; m.cost = cost; if (m.manual_price_override && m.base_retail_price > 0 && cost > 0) { m.markup_pct = ((m.base_retail_price - cost) / cost) * 100; m.margin_pct = ((m.base_retail_price - cost) / m.base_retail_price) * 100; } else { const markup = parseFloat(m.markup_pct || 40.0); m.base_retail_price = calcRetail(cost, markup); m.margin_pct = markupToMargin(markup); } markDirty(); renderModelsTable(); } function onTableMarkupChange(idx, val) { const markup = parseFloat(val) || 0; const m = currentCatalog.models[idx]; m.markup_pct = markup; m.margin_pct = markupToMargin(markup); m.manual_price_override = false; m.base_retail_price = calcRetail(parseFloat(m.cost || 0), markup); markDirty(); renderModelsTable(); } function onTableMarginChange(idx, val) { const margin = parseFloat(val) || 0; const markup = marginToMarkup(margin); const m = currentCatalog.models[idx]; m.margin_pct = margin; m.markup_pct = markup; m.manual_price_override = false; m.base_retail_price = calcRetail(parseFloat(m.cost || 0), markup); markDirty(); renderModelsTable(); } function onTableRetailChange(idx, val) { const retail = parseFloat(val) || 0; const m = currentCatalog.models[idx]; const cost = parseFloat(m.cost || 0); m.base_retail_price = retail; m.manual_price_override = true; if (cost > 0) { m.markup_pct = ((retail - cost) / cost) * 100; m.margin_pct = ((retail - cost) / retail) * 100; } markDirty(); renderModelsTable(); } function unlockModelRetail(idx) { const m = currentCatalog.models[idx]; m.manual_price_override = false; const markup = parseFloat(m.markup_pct || 40.0); m.base_retail_price = calcRetail(parseFloat(m.cost || 0), markup); markDirty(); renderModelsTable(); showBanner(`Unlocked ${m.name} to auto-calculate MSRP at ${markup.toFixed(1)}% markup.`, 'success'); } function toggleArchiveModel(idx) { const m = currentCatalog.models[idx]; m.is_active = (m.is_active === false) ? true : false; markDirty(); renderModelsTable(); showBanner(`${m.is_active ? 'Restored' : 'Archived'} cover: ${m.name}`, 'success'); } // Modal Operations: Add / Edit let activeEditModelIdx = null; function openAddModelModal() { activeEditModelIdx = null; document.getElementById('modalModelTitle').innerText = 'Add New Truck Cap Model'; document.getElementById('editModelName').value = ''; document.getElementById('editModelId').value = ''; document.getElementById('editModelId').disabled = false; document.getElementById('editModelBrand').value = document.getElementById('settingDefaultBrand').value; document.getElementById('editModelStyle').value = 'cab_high'; document.getElementById('editModelCategory').value = 'fiberglass'; document.getElementById('editModelWarranty').value = 'Lifetime Structural & Paint Warranty'; document.getElementById('editModelTagline').value = ''; document.getElementById('editModelDesc').value = ''; document.getElementById('editModelFeatures').value = ''; const defMarkup = parseFloat(document.getElementById('settingDefaultMarkup').value) || 40.0; document.getElementById('editModelCost').value = '2000.00'; document.getElementById('editModelMarkup').value = defMarkup.toFixed(1); document.getElementById('editModelMargin').value = markupToMargin(defMarkup).toFixed(1); document.getElementById('editModelRetail').value = calcRetail(2000.0, defMarkup).toFixed(2); document.getElementById('editModelOverride').checked = false; updateModalProfitPreview(); document.getElementById('modelEditModal').classList.add('active'); } function openEditModelModal(idx) { activeEditModelIdx = idx; const m = currentCatalog.models[idx]; document.getElementById('modalModelTitle').innerText = `Edit Model: ${m.name}`; document.getElementById('editModelName').value = m.name || ''; document.getElementById('editModelId').value = m.id || ''; document.getElementById('editModelId').disabled = true; // prevent primary key mutation document.getElementById('editModelBrand').value = m.brand || 'Venturous'; document.getElementById('editModelStyle').value = m.style || 'cab_high'; document.getElementById('editModelCategory').value = m.category || 'fiberglass'; document.getElementById('editModelWarranty').value = m.warranty || ''; document.getElementById('editModelTagline').value = m.tagline || ''; document.getElementById('editModelDesc').value = m.description || ''; document.getElementById('editModelFeatures').value = (m.standard_features || []).join('\n'); document.getElementById('editModelCost').value = parseFloat(m.cost || 0).toFixed(2); document.getElementById('editModelMarkup').value = parseFloat(m.markup_pct || 40.0).toFixed(1); document.getElementById('editModelMargin').value = parseFloat(m.margin_pct || markupToMargin(m.markup_pct || 40.0)).toFixed(1); document.getElementById('editModelRetail').value = parseFloat(m.base_retail_price || 0).toFixed(2); document.getElementById('editModelOverride').checked = !!m.manual_price_override; updateModalProfitPreview(); document.getElementById('modelEditModal').classList.add('active'); } function closeModelModal() { document.getElementById('modelEditModal').classList.remove('active'); } function onModelNameInput(val) { if (activeEditModelIdx === null) { const slug = val.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); document.getElementById('editModelId').value = slug; } } function onModalCostInput(val) { const cost = parseFloat(val) || 0; const override = document.getElementById('editModelOverride').checked; if (override) { const retail = parseFloat(document.getElementById('editModelRetail').value) || 0; if (cost > 0 && retail > 0) { document.getElementById('editModelMarkup').value = (((retail - cost) / cost) * 100).toFixed(1); document.getElementById('editModelMargin').value = (((retail - cost) / retail) * 100).toFixed(1); } } else { const markup = parseFloat(document.getElementById('editModelMarkup').value) || 40.0; document.getElementById('editModelRetail').value = calcRetail(cost, markup).toFixed(2); } updateModalProfitPreview(); } function onModalMarkupInput(val) { const markup = parseFloat(val) || 0; const margin = markupToMargin(markup); document.getElementById('editModelMargin').value = margin.toFixed(1); document.getElementById('editModelOverride').checked = false; const cost = parseFloat(document.getElementById('editModelCost').value) || 0; document.getElementById('editModelRetail').value = calcRetail(cost, markup).toFixed(2); updateModalProfitPreview(); } function onModalMarginInput(val) { const margin = parseFloat(val) || 0; const markup = marginToMarkup(margin); document.getElementById('editModelMarkup').value = markup.toFixed(1); document.getElementById('editModelOverride').checked = false; const cost = parseFloat(document.getElementById('editModelCost').value) || 0; document.getElementById('editModelRetail').value = calcRetail(cost, markup).toFixed(2); updateModalProfitPreview(); } function onModalRetailInput(val) { const retail = parseFloat(val) || 0; const cost = parseFloat(document.getElementById('editModelCost').value) || 0; document.getElementById('editModelOverride').checked = true; if (cost > 0) { document.getElementById('editModelMarkup').value = (((retail - cost) / cost) * 100).toFixed(1); document.getElementById('editModelMargin').value = (((retail - cost) / retail) * 100).toFixed(1); } updateModalProfitPreview(); } function updateModalProfitPreview() { const cost = parseFloat(document.getElementById('editModelCost').value) || 0; const retail = parseFloat(document.getElementById('editModelRetail').value) || 0; const profit = retail - cost; document.getElementById('modalProfitPreview').innerText = `Profit: +$${profit.toFixed(2)}`; } function saveModalModel() { const name = document.getElementById('editModelName').value.trim(); const id = document.getElementById('editModelId').value.trim(); if (!name || !id) { alert('Please enter a valid Model Name and ID.'); return; } const features = document.getElementById('editModelFeatures').value .split('\n') .map(s => s.trim()) .filter(Boolean); const modelData = { id: id, name: name, brand: document.getElementById('editModelBrand').value, style: document.getElementById('editModelStyle').value, category: document.getElementById('editModelCategory').value, cost: parseFloat(document.getElementById('editModelCost').value) || 0, markup_pct: parseFloat(document.getElementById('editModelMarkup').value) || 40.0, margin_pct: parseFloat(document.getElementById('editModelMargin').value) || 28.57, base_retail_price: parseFloat(document.getElementById('editModelRetail').value) || 0, manual_price_override: document.getElementById('editModelOverride').checked, tagline: document.getElementById('editModelTagline').value.trim(), description: document.getElementById('editModelDesc').value.trim(), warranty: document.getElementById('editModelWarranty').value.trim(), standard_features: features, compatible_options: activeEditModelIdx !== null ? (currentCatalog.models[activeEditModelIdx].compatible_options || []) : [], is_active: activeEditModelIdx !== null ? (currentCatalog.models[activeEditModelIdx].is_active !== false) : true }; if (activeEditModelIdx !== null) { currentCatalog.models[activeEditModelIdx] = modelData; } else { currentCatalog.models.push(modelData); } markDirty(); closeModelModal(); renderModelsTable(); showBanner(`Saved cover model: ${name}`, 'success'); } // Bulk Markup Modal function openBulkMarkupModal() { document.getElementById('bulkMarkupModal').classList.add('active'); } function closeBulkMarkupModal() { document.getElementById('bulkMarkupModal').classList.remove('active'); } function applyBulkMarkup() { const targetBrand = document.getElementById('bulkBrandTarget').value; const newMarkup = parseFloat(document.getElementById('bulkMarkupValue').value) || 40.0; let count = 0; currentCatalog.models.forEach(m => { if (targetBrand === 'ALL' || m.brand === targetBrand) { m.markup_pct = newMarkup; m.margin_pct = markupToMargin(newMarkup); m.manual_price_override = false; m.base_retail_price = calcRetail(parseFloat(m.cost || 0), newMarkup); count++; } }); markDirty(); closeBulkMarkupModal(); renderModelsTable(); showBanner(`Applied ${newMarkup}% markup across ${count} covers.`, 'success'); } // History Modal function openHistoryModal() { const container = document.getElementById('historyListContainer'); container.innerHTML = ''; if (!catalogHistory.length) { container.innerHTML = '

No historical snapshots recorded yet.

'; } else { catalogHistory.forEach(entry => { const div = document.createElement('div'); div.className = 'history-item'; const dt = new Date(entry.timestamp); const dateStr = dt.toLocaleString(); div.innerHTML = `
${entry.version_id} ${entry.summary || 'Catalog update'} Author: ${entry.author || 'Admin'} • ${dateStr} • ${entry.active_models_count || entry.models_count} Active Covers
`; container.appendChild(div); }); } document.getElementById('historyModal').classList.add('active'); } function closeHistoryModal() { document.getElementById('historyModal').classList.remove('active'); } async function restoreHistoricalVersion(versionId) { if (!confirm(`Are you sure you want to restore catalog version ${versionId}? Current unsaved changes will be replaced.`)) { return; } try { const res = await fetch('/api/configurator/admin/history/restore', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ version_id: versionId, author: 'Admin Web' }) }); const data = await res.json(); if (data.success) { closeHistoryModal(); await fetchCatalog(); await fetchHistory(); showBanner(`Successfully restored catalog to version ${versionId}`, 'success'); } else { showBanner('Restore failed: ' + data.detail, 'error'); } } catch (err) { showBanner('Restore error: ' + err.message, 'error'); } } // Save All Changes function openEmailVerifyModal(email, emailType) { pendingVerification = { email, emailType }; document.getElementById('emailVerifyModalTitle').innerText = `Verify ${emailType === 'primary' ? 'Primary Admin' : 'Recovery'} Email`; document.getElementById('emailVerifyTargetDisplay').innerText = email; document.getElementById('emailVerifyCodeInput').value = ''; document.getElementById('emailVerifyError').style.display = 'none'; document.getElementById('emailVerifyModal').classList.add('active'); document.getElementById('emailVerifyCodeInput').focus(); } function closeEmailVerifyModal() { document.getElementById('emailVerifyModal').classList.remove('active'); } async function resendVerificationCode() { if (!pendingVerification) return; try { const res = await fetch('/api/configurator/admin/email/send-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: pendingVerification.email, email_type: pendingVerification.emailType }) }); const data = await res.json(); if (data.success) { showBanner(data.message, 'success'); } else { document.getElementById('emailVerifyError').innerText = data.detail || 'Failed to resend code'; document.getElementById('emailVerifyError').style.display = 'block'; } } catch (err) { document.getElementById('emailVerifyError').innerText = 'Network error: ' + err.message; document.getElementById('emailVerifyError').style.display = 'block'; } } async function confirmEmailVerificationCode() { if (!pendingVerification) return; const code = document.getElementById('emailVerifyCodeInput').value.trim(); if (!code || code.length !== 6) { document.getElementById('emailVerifyError').innerText = 'Please enter a valid 6-digit verification code.'; document.getElementById('emailVerifyError').style.display = 'block'; return; } try { const res = await fetch('/api/configurator/admin/email/verify-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: pendingVerification.email, code: code, email_type: pendingVerification.emailType }) }); const data = await res.json(); if (data.success && data.verified) { if (pendingVerification.emailType === 'primary') { verifiedPrimaryEmail = pendingVerification.email; } else if (pendingVerification.emailType === 'recovery') { verifiedRecoveryEmail = pendingVerification.email; } closeEmailVerifyModal(); showBanner(`Email verified successfully! Resuming save...`, 'success'); setTimeout(saveAllCatalogChanges, 300); } else { document.getElementById('emailVerifyError').innerText = data.detail || 'Invalid verification code.'; document.getElementById('emailVerifyError').style.display = 'block'; } } catch (err) { document.getElementById('emailVerifyError').innerText = 'Verification error: ' + err.message; document.getElementById('emailVerifyError').style.display = 'block'; } } async function saveAllCatalogChanges() { // Validate mandatory dual admin emails before proceeding const pEmail = (document.getElementById('settingAdminPrimaryEmail')?.value || '').trim().toLowerCase(); const rEmail = (document.getElementById('settingAdminRecoveryEmail')?.value || '').trim().toLowerCase(); const emailErrEl = document.getElementById('adminEmailError'); if (!pEmail || !rEmail || pEmail === rEmail) { if (emailErrEl) emailErrEl.style.display = 'block'; switchAdminTab('auth'); showBanner('Error: You must provide TWO DIFFERENT email addresses (Primary Admin & Secondary Recovery) to prevent instance lockout.', 'error'); return; } if (emailErrEl) emailErrEl.style.display = 'none'; // Check if Primary Email changed and needs OTP verification if (verifiedPrimaryEmail && pEmail !== verifiedPrimaryEmail) { switchAdminTab('auth'); showBanner(`Security Verification Required: A confirmation code is being sent to ${pEmail}`, 'info'); try { const sRes = await fetch('/api/configurator/admin/email/send-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: pEmail, email_type: 'primary' }) }); const sData = await sRes.json(); if (sRes.ok) { openEmailVerifyModal(pEmail, 'primary'); } else { showBanner(sData.detail || 'Failed to dispatch verification code.', 'error'); } } catch (err) { showBanner('Error dispatching verification code: ' + err.message, 'error'); } return; } // Check if Recovery Email changed and needs OTP verification if (verifiedRecoveryEmail && rEmail !== verifiedRecoveryEmail) { switchAdminTab('auth'); showBanner(`Security Verification Required: A confirmation code is being sent to ${rEmail}`, 'info'); try { const sRes = await fetch('/api/configurator/admin/email/send-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: rEmail, email_type: 'recovery' }) }); const sData = await sRes.json(); if (sRes.ok) { openEmailVerifyModal(rEmail, 'recovery'); } else { showBanner(sData.detail || 'Failed to dispatch verification code.', 'error'); } } catch (err) { showBanner('Error dispatching verification code: ' + err.message, 'error'); } return; } // Sync global organization & white-label settings to metadata currentCatalog.metadata.company_name = document.getElementById('settingCompanyName').value; currentCatalog.metadata.company_slug = slugify(document.getElementById('settingCompanySlug').value || document.getElementById('settingCompanyName').value); currentCatalog.metadata.deployment_mode = document.getElementById('settingDeploymentMode').value; currentCatalog.metadata.parent_host = document.getElementById('settingParentHost').value; currentCatalog.metadata.custom_retailer_domain = document.getElementById('settingRetailerDomain').value; currentCatalog.metadata.tagline = document.getElementById('settingTagline').value; currentCatalog.metadata.support_phone = document.getElementById('settingSupportPhone').value; currentCatalog.metadata.support_phone_tel = document.getElementById('settingSupportPhoneTel').value; currentCatalog.metadata.support_email = document.getElementById('settingSupportEmail').value; currentCatalog.metadata.logo_url = document.getElementById('settingLogoUrl').value; currentCatalog.metadata.primary_color = document.getElementById('settingPrimaryColor').value; currentCatalog.metadata.secondary_color = document.getElementById('settingSecondaryColor').value; currentCatalog.metadata.default_brand = document.getElementById('settingDefaultBrand').value; currentCatalog.metadata.default_markup_pct = parseFloat(document.getElementById('settingDefaultMarkup').value) || 40.0; currentCatalog.metadata.default_margin_pct = parseFloat(document.getElementById('settingDefaultMargin').value) || 28.57; currentCatalog.metadata.tax_rate = (parseFloat(document.getElementById('settingTaxRate').value) || 6.0) / 100.0; currentCatalog.metadata.currency = document.getElementById('settingCurrency').value; currentCatalog.metadata.legal_disclaimer = document.getElementById('settingLegalDisclaimer').value; currentCatalog.metadata.admin_primary_email = pEmail; currentCatalog.metadata.admin_recovery_email = rEmail; // Auth Config const authProvider = document.getElementById('settingAuthProvider').value; currentCatalog.metadata.auth_config = { enabled: authProvider !== 'disabled', provider: authProvider, admin_primary_email: pEmail, admin_recovery_email: rEmail, google_client_id: document.getElementById('settingGoogleClientId').value, google_client_secret: document.getElementById('settingGoogleClientSecret').value, dealer_pin: document.getElementById('settingDealerPin').value, allow_guest_submission: document.getElementById('settingAuthAllowGuest').value === 'true' }; if (!currentCatalog.pricing_rules) currentCatalog.pricing_rules = {}; if (!currentCatalog.pricing_rules.labor_rates) currentCatalog.pricing_rules.labor_rates = {}; currentCatalog.pricing_rules.labor_rates.base_installation = parseFloat(document.getElementById('settingLaborBase').value) || 200.0; currentCatalog.pricing_rules.labor_rates.keyless_wiring = parseFloat(document.getElementById('settingLaborKeyless').value) || 120.0; currentCatalog.pricing_rules.labor_rates.rack_installation = parseFloat(document.getElementById('settingLaborRack').value) || 95.0; try { const res = await fetch('/api/configurator/admin/catalog', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ metadata: currentCatalog.metadata, pricing_rules: currentCatalog.pricing_rules, models: currentCatalog.models, options_catalog: currentCatalog.options_catalog, author: 'Admin Portal', change_summary: `Saved configuration across ${currentCatalog.models.length} covers` }) }); const data = await res.json(); if (data.success) { isDirty = false; await fetchCatalog(); await fetchHistory(); showBanner(`All changes successfully saved. Version: ${data.version_id}`, 'success'); } else { showBanner('Failed to save catalog: ' + data.detail, 'error'); } } catch (err) { showBanner('Network error saving catalog: ' + err.message, 'error'); } } function showBanner(text, type = 'success') { const b = document.getElementById('statusBanner'); b.className = type; document.getElementById('statusBannerText').innerText = text; b.style.display = 'flex'; setTimeout(() => { b.style.display = 'none'; }, 6000); } function hideBanner() { document.getElementById('statusBanner').style.display = 'none'; }