diff --git a/api/businesses/get.cfm b/api/businesses/get.cfm index c8a80fc..4b3dd97 100644 --- a/api/businesses/get.cfm +++ b/api/businesses/get.cfm @@ -44,6 +44,7 @@ try { HeaderImageExtension, TaxRate, BrandColor, + BrandColorLight, SessionEnabled, SessionLockMinutes, SessionPaymentStrategy, @@ -150,6 +151,7 @@ try { "TaxRate": taxRate, "TaxRatePercent": taxRate * 100, "BrandColor": len(q.BrandColor) ? (left(q.BrandColor, 1) == chr(35) ? q.BrandColor : chr(35) & q.BrandColor) : "", + "BrandColorLight": len(q.BrandColorLight) ? (left(q.BrandColorLight, 1) == chr(35) ? q.BrandColorLight : chr(35) & q.BrandColorLight) : "", "SessionEnabled": isNumeric(q.SessionEnabled) ? q.SessionEnabled : 0, "SessionLockMinutes": isNumeric(q.SessionLockMinutes) ? q.SessionLockMinutes : 30, "SessionPaymentStrategy": len(q.SessionPaymentStrategy) ? q.SessionPaymentStrategy : "A", diff --git a/api/businesses/saveBrandColor.cfm b/api/businesses/saveBrandColor.cfm index 2e9d6d0..bd10535 100644 --- a/api/businesses/saveBrandColor.cfm +++ b/api/businesses/saveBrandColor.cfm @@ -29,30 +29,27 @@ if (bizId LTE 0) { apiAbort({ "OK": false, "ERROR": "missing_businessid", "MESSAGE": "BusinessID is required" }); } -// Validate color format -brandColor = structKeyExists(data, "BrandColor") && isSimpleValue(data.BrandColor) ? trim(data.BrandColor) : ""; - -// Allow empty to clear, or validate hex format -if (len(brandColor) GT 0) { - // Strip leading # if present - if (left(brandColor, 1) == chr(35)) { - brandColor = right(brandColor, len(brandColor) - 1); - } - // Must be exactly 6 hex chars - if (len(brandColor) != 6 || !reFind("^[0-9A-Fa-f]{6}$", brandColor)) { +// Helper to validate and normalize a hex color +function normalizeHex(raw) { + var c = isSimpleValue(raw) ? trim(raw) : ""; + if (!len(c)) return ""; + if (left(c, 1) == chr(35)) c = right(c, len(c) - 1); + if (len(c) != 6 || !reFind("^[0-9A-Fa-f]{6}$", c)) apiAbort({ "OK": false, "ERROR": "invalid_color", "MESSAGE": "Color must be a valid 6-digit hex color (e.g. 1B4D3E or ##1B4D3E)" }); - } - // Store uppercase, no # prefix - brandColor = uCase(brandColor); + return uCase(c); } +brandColor = normalizeHex(structKeyExists(data, "BrandColor") ? data.BrandColor : ""); +brandColorLight = normalizeHex(structKeyExists(data, "BrandColorLight") ? data.BrandColorLight : ""); + // Update the database queryTimed(" UPDATE Businesses - SET BrandColor = :color + SET BrandColor = :color, BrandColorLight = :colorLight WHERE ID = :bizId ", { color: { value: brandColor, cfsqltype: "cf_sql_varchar" }, + colorLight: { value: brandColorLight, cfsqltype: "cf_sql_varchar" }, bizId: { value: bizId, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); @@ -60,7 +57,8 @@ writeOutput(serializeJSON({ "OK": true, "ERROR": "", "MESSAGE": "Brand color saved", - "BRANDCOLOR": brandColor + "BRANDCOLOR": brandColor, + "BRANDCOLORLIGHT": brandColorLight })); diff --git a/api/menu/getForBuilder.cfm b/api/menu/getForBuilder.cfm index 0579387..ea2d1a6 100644 --- a/api/menu/getForBuilder.cfm +++ b/api/menu/getForBuilder.cfm @@ -461,14 +461,18 @@ try { arrayAppend(templateLibrary, templatesById[templateID]); } - // Get business brand color + // Get business brand colors brandColor = ""; + brandColorLight = ""; try { qBrand = queryTimed(" - SELECT BrandColor AS BusinessBrandColor FROM Businesses WHERE ID = :bizId + SELECT BrandColor, BrandColorLight FROM Businesses WHERE ID = :bizId ", { bizId: businessID }, { datasource: "payfrit" }); - if (qBrand.recordCount > 0 && len(trim(qBrand.BusinessBrandColor))) { - brandColor = left(qBrand.BusinessBrandColor, 1) == chr(35) ? qBrand.BusinessBrandColor : chr(35) & qBrand.BusinessBrandColor; + if (qBrand.recordCount > 0) { + if (len(trim(qBrand.BrandColor))) + brandColor = left(qBrand.BrandColor, 1) == chr(35) ? qBrand.BrandColor : chr(35) & qBrand.BrandColor; + if (len(trim(qBrand.BrandColorLight))) + brandColorLight = left(qBrand.BrandColorLight, 1) == chr(35) ? qBrand.BrandColorLight : chr(35) & qBrand.BrandColorLight; } } catch (any e) { // Column may not exist yet, ignore @@ -481,6 +485,7 @@ try { response["DEFAULT_MENU_ID"] = defaultMenuID; response["TEMPLATES"] = templateLibrary; response["BRANDCOLOR"] = brandColor; + response["BRANDCOLORLIGHT"] = brandColorLight; response["CATEGORY_COUNT"] = arrayLen(categories); response["TEMPLATE_COUNT"] = arrayLen(templateLibrary); response["MENU_COUNT"] = arrayLen(allMenus); diff --git a/portal/menu-builder.html b/portal/menu-builder.html index a0e8648..6561ee9 100644 --- a/portal/menu-builder.html +++ b/portal/menu-builder.html @@ -162,7 +162,7 @@ /* Category Card */ .category-card { - background: var(--gray-50); + background: var(--brand-tint, var(--gray-50)); border-radius: 12px; border: 2px solid var(--gray-200); overflow: hidden; @@ -182,7 +182,7 @@ display: flex; align-items: center; padding: 12px 16px; - background: #fff; + background: var(--brand-tint, #fff); cursor: grab; gap: 12px; } @@ -314,7 +314,7 @@ align-items: center; gap: 12px; padding: 10px 12px; - background: #fff; + background: var(--brand-tint, #fff); border-radius: 8px; border: 1px solid var(--gray-200); cursor: grab; @@ -3622,37 +3622,56 @@ // Show brand color picker modal showBrandColorPicker() { const currentColor = this.brandColor || '#1B4D3E'; - document.getElementById('modalTitle').textContent = 'Brand Color'; + const currentLight = this.brandColorLight || ''; + document.getElementById('modalTitle').textContent = 'Brand Colors'; document.getElementById('modalBody').innerHTML = ` -
- This color is used for the category bar gradients in the customer app. -
-+ Used for category bar gradients in the customer app. +
++ Subtle tint for menu builder cards and backgrounds. Leave empty for white. +
+