From 800d1f12469c810183b2467d492416eae7bceda4 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Thu, 12 Feb 2026 14:20:35 -0800 Subject: [PATCH] Add brand color extraction and auto-header detection to setup wizard --- api/setup/analyzeMenuImages.cfm | 16 ++++++-- api/setup/saveWizard.cfm | 15 ++++++-- portal/setup-wizard.html | 67 +++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/api/setup/analyzeMenuImages.cfm b/api/setup/analyzeMenuImages.cfm index 5fe1b4d..8e7c58e 100644 --- a/api/setup/analyzeMenuImages.cfm +++ b/api/setup/analyzeMenuImages.cfm @@ -94,7 +94,7 @@ - + @@ -190,9 +190,18 @@ + + + + + + + + + - + @@ -398,10 +407,11 @@ + - + diff --git a/api/setup/saveWizard.cfm b/api/setup/saveWizard.cfm index 54b3148..f5ea127 100644 --- a/api/setup/saveWizard.cfm +++ b/api/setup/saveWizard.cfm @@ -57,6 +57,14 @@ try { // Extract tax rate (stored as decimal, e.g. 8.25% -> 0.0825) bizTaxRate = structKeyExists(biz, "taxRatePercent") && isSimpleValue(biz.taxRatePercent) ? val(biz.taxRatePercent) / 100 : 0; + // Extract brand color (6-digit hex without #) + bizBrandColor = structKeyExists(biz, "brandColor") && isSimpleValue(biz.brandColor) ? trim(biz.brandColor) : ""; + // Ensure it's a valid 6-digit hex (remove # if present) + bizBrandColor = reReplace(bizBrandColor, "^##?", ""); + if (!reFind("^[0-9A-Fa-f]{6}$", bizBrandColor)) { + bizBrandColor = ""; // Invalid format, skip it + } + // Create address record first (use extracted address fields) - safely extract as simple values addressLine1 = structKeyExists(biz, "addressLine1") && isSimpleValue(biz.addressLine1) ? trim(biz.addressLine1) : ""; city = structKeyExists(biz, "city") && isSimpleValue(biz.city) ? trim(biz.city) : ""; @@ -103,8 +111,8 @@ try { // Create new business with address link and phone queryTimed(" - INSERT INTO Businesses (Name, Phone, UserID, AddressID, DeliveryZIPCodes, CommunityMealType, TaxRate, AddedOn) - VALUES (:name, :phone, :userId, :addressId, :deliveryZips, :communityMealType, :taxRate, NOW()) + INSERT INTO Businesses (Name, Phone, UserID, AddressID, DeliveryZIPCodes, CommunityMealType, TaxRate, BrandColor, AddedOn) + VALUES (:name, :phone, :userId, :addressId, :deliveryZips, :communityMealType, :taxRate, :brandColor, NOW()) ", { name: bizName, phone: bizPhone, @@ -112,7 +120,8 @@ try { addressId: addressId, deliveryZips: len(zip) ? zip : "", communityMealType: communityMealType, - taxRate: { value: bizTaxRate, cfsqltype: "cf_sql_decimal" } + taxRate: { value: bizTaxRate, cfsqltype: "cf_sql_decimal" }, + brandColor: { value: len(bizBrandColor) ? bizBrandColor : javaCast("null", ""), cfsqltype: "cf_sql_varchar", null: !len(bizBrandColor) } }, { datasource: "payfrit" }); qNewBiz = queryTimed("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" }); diff --git a/portal/setup-wizard.html b/portal/setup-wizard.html index c3be3eb..81a6beb 100644 --- a/portal/setup-wizard.html +++ b/portal/setup-wizard.html @@ -1263,6 +1263,20 @@ console.log('Merged DATA:', result.DATA); console.log('========================'); + // Auto-select header image from candidate indices if available + const headerCandidates = result.DATA.headerCandidateIndices || []; + if (headerCandidates.length > 0 && config.uploadedFiles.length > 0) { + const candidateIndex = headerCandidates[0]; + if (candidateIndex >= 0 && candidateIndex < config.uploadedFiles.length) { + const candidateFile = config.uploadedFiles[candidateIndex]; + // Only use image files as headers (not PDFs) + if (candidateFile.type.match('image.*')) { + config.headerImageFile = candidateFile; + console.log('Auto-selected header image:', candidateFile.name, 'from index', candidateIndex); + } + } + } + // Remove loading message and start conversation flow document.getElementById('conversation').innerHTML = ''; @@ -1450,6 +1464,20 @@ } } + // Helper to sync brand color inputs + function syncBrandColor(hexInput) { + let hex = hexInput.value.replace(/[^0-9A-Fa-f]/g, '').toUpperCase(); + if (hex.length === 6) { + document.getElementById('bizBrandColor').value = '#' + hex; + } + } + + // Sync color picker to hex input + function onBrandColorPick() { + const colorVal = document.getElementById('bizBrandColor').value; + document.getElementById('bizBrandColorHex').value = colorVal.replace('#', '').toUpperCase(); + } + // Step 1: Business Info async function showBusinessInfoStep() { updateProgress(2); @@ -1556,6 +1584,14 @@ e.g. 8.25 for 8.25% +
+ +
+ + + Used for menu accents +
+
@@ -1626,6 +1662,10 @@ } // Update stored data with any edits + // Get brand color from hex input (without #) + let brandColor = document.getElementById('bizBrandColorHex').value.replace(/^#/, '').toUpperCase(); + if (!/^[0-9A-F]{6}$/.test(brandColor)) brandColor = 'E74C3C'; // Default if invalid + config.extractedData.business = { name: document.getElementById('bizName').value, addressLine1: document.getElementById('bizAddressLine1').value, @@ -1634,6 +1674,7 @@ zip: document.getElementById('bizZip').value, phone: document.getElementById('bizPhone').value, taxRatePercent: parseFloat(document.getElementById('bizTaxRate').value) || 0, + brandColor: brandColor, hoursSchedule: hoursSchedule // Send the structured schedule instead of the raw hours string }; @@ -1643,9 +1684,14 @@ // Header Image step - between business info and categories function showHeaderImageStep() { + const hasAutoHeader = config.headerImageFile != null; + const headerText = hasAutoHeader + ? `

I found an image that would work great as your menu header!

` + : `

This image appears at the top of your menu in the Payfrit app. It's the first thing customers see!

`; + addMessage('ai', `

Header Image

-

This image appears at the top of your menu in the Payfrit app. It's the first thing customers see!

+ ${headerText}

Recommended specs:

    @@ -1654,14 +1700,14 @@
  • Content: Your restaurant, food, or branding
- +
@@ -1669,11 +1715,24 @@ - Continue + ${hasAutoHeader ? 'Use This Image' : 'Continue'}
`); + + // If we have an auto-detected header, show its preview + if (hasAutoHeader) { + const reader = new FileReader(); + reader.onload = function(e) { + const preview = document.getElementById('headerUploadPreview'); + if (preview) { + preview.style.backgroundImage = `url(${e.target.result})`; + preview.style.display = 'block'; + } + }; + reader.readAsDataURL(config.headerImageFile); + } } function previewWizardHeader(input) {