Add menu wizard flow: redirect to setup wizard after creating a new menu

When a new menu is created in Manage Menus, the user is now redirected
to the setup wizard with businessId and menuId params. The wizard skips
business info/header steps and goes straight to photo upload + category
extraction. Backend uses the provided menuId instead of creating a new
menu. Also removes temp debug from menus.cfm.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-01-28 17:08:31 -08:00
parent 65cf855ade
commit 02a19f52be
4 changed files with 126 additions and 98 deletions

View file

@ -242,7 +242,7 @@ try {
response["ERROR"] = "server_error";
response["MESSAGE"] = e.message;
response["DETAIL"] = e.detail ?: "";
response["TAGCONTEXT"] = e.tagContext[1].template & ":" & e.tagContext[1].line;
}
writeOutput(serializeJSON(response));

View file

@ -34,6 +34,7 @@ try {
businessId = structKeyExists(data, "businessId") ? val(data.businessId) : 0;
userId = structKeyExists(data, "userId") ? val(data.userId) : 0;
providedMenuId = structKeyExists(data, "menuId") ? val(data.menuId) : 0;
wizardData = structKeyExists(data, "data") ? data.data : {};
biz = structKeyExists(wizardData, "business") ? wizardData.business : {};
@ -290,81 +291,87 @@ try {
}
}
// Create a Menu record for this business (or get existing menu with same name)
menuName = structKeyExists(wizardData, "menuName") && isSimpleValue(wizardData.menuName) && len(trim(wizardData.menuName))
? trim(wizardData.menuName)
: "Main Menu";
// Get menu time range (optional)
menuStartTime = structKeyExists(wizardData, "menuStartTime") && isSimpleValue(wizardData.menuStartTime) && len(trim(wizardData.menuStartTime))
? trim(wizardData.menuStartTime)
: "";
menuEndTime = structKeyExists(wizardData, "menuEndTime") && isSimpleValue(wizardData.menuEndTime) && len(trim(wizardData.menuEndTime))
? trim(wizardData.menuEndTime)
: "";
// Convert HH:MM to HH:MM:SS if needed
if (len(menuStartTime) == 5) menuStartTime = menuStartTime & ":00";
if (len(menuEndTime) == 5) menuEndTime = menuEndTime & ":00";
// Validate menu hours fall within business operating hours
if (len(menuStartTime) && len(menuEndTime)) {
qHours = queryExecute("
SELECT MIN(HoursOpenTime) as earliestOpen, MAX(HoursClosingTime) as latestClose
FROM Hours
WHERE HoursBusinessID = :bizID
", { bizID: businessId }, { datasource: "payfrit" });
if (qHours.recordCount > 0 && !isNull(qHours.earliestOpen) && !isNull(qHours.latestClose)) {
earliestOpen = timeFormat(qHours.earliestOpen, "HH:mm:ss");
latestClose = timeFormat(qHours.latestClose, "HH:mm:ss");
if (menuStartTime < earliestOpen || menuEndTime > latestClose) {
throw(message="Menu hours (" & menuStartTime & " - " & menuEndTime & ") must be within business operating hours (" & earliestOpen & " - " & latestClose & ")");
}
response.steps.append("Validated menu hours against business hours (" & earliestOpen & " - " & latestClose & ")");
}
}
qMenu = queryExecute("
SELECT MenuID FROM Menus
WHERE MenuBusinessID = :bizID AND MenuName = :name AND MenuIsActive = 1
", { bizID: businessId, name: menuName }, { datasource: "payfrit" });
if (qMenu.recordCount > 0) {
menuID = qMenu.MenuID;
// Update existing menu with new time range if provided
if (len(menuStartTime) && len(menuEndTime)) {
queryExecute("
UPDATE Menus SET MenuStartTime = :startTime, MenuEndTime = :endTime
WHERE MenuID = :menuID
", {
menuID: menuID,
startTime: menuStartTime,
endTime: menuEndTime
}, { datasource: "payfrit" });
response.steps.append("Updated existing menu: " & menuName & " (ID: " & menuID & ") with hours " & menuStartTime & " - " & menuEndTime);
} else {
response.steps.append("Using existing menu: " & menuName & " (ID: " & menuID & ")");
}
// Use provided menuId (add-menu mode) or create/find menu
if (providedMenuId > 0) {
menuID = providedMenuId;
response.steps.append("Using provided menu ID: " & menuID);
} else {
queryExecute("
INSERT INTO Menus (
MenuBusinessID, MenuName, MenuDaysActive, MenuStartTime, MenuEndTime, MenuSortOrder, MenuIsActive, MenuAddedOn
) VALUES (
:bizID, :name, 127, :startTime, :endTime, 0, 1, NOW()
)
", {
bizID: businessId,
name: menuName,
startTime: len(menuStartTime) ? menuStartTime : javaCast("null", ""),
endTime: len(menuEndTime) ? menuEndTime : javaCast("null", "")
}, { datasource: "payfrit" });
// Create a Menu record for this business (or get existing menu with same name)
menuName = structKeyExists(wizardData, "menuName") && isSimpleValue(wizardData.menuName) && len(trim(wizardData.menuName))
? trim(wizardData.menuName)
: "Main Menu";
qNewMenu = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
menuID = qNewMenu.id;
timeInfo = len(menuStartTime) && len(menuEndTime) ? " (" & menuStartTime & " - " & menuEndTime & ")" : " (all day)";
response.steps.append("Created menu: " & menuName & timeInfo & " (ID: " & menuID & ")");
// Get menu time range (optional)
menuStartTime = structKeyExists(wizardData, "menuStartTime") && isSimpleValue(wizardData.menuStartTime) && len(trim(wizardData.menuStartTime))
? trim(wizardData.menuStartTime)
: "";
menuEndTime = structKeyExists(wizardData, "menuEndTime") && isSimpleValue(wizardData.menuEndTime) && len(trim(wizardData.menuEndTime))
? trim(wizardData.menuEndTime)
: "";
// Convert HH:MM to HH:MM:SS if needed
if (len(menuStartTime) == 5) menuStartTime = menuStartTime & ":00";
if (len(menuEndTime) == 5) menuEndTime = menuEndTime & ":00";
// Validate menu hours fall within business operating hours
if (len(menuStartTime) && len(menuEndTime)) {
qHours = queryExecute("
SELECT MIN(HoursOpenTime) as earliestOpen, MAX(HoursClosingTime) as latestClose
FROM Hours
WHERE HoursBusinessID = :bizID
", { bizID: businessId }, { datasource: "payfrit" });
if (qHours.recordCount > 0 && !isNull(qHours.earliestOpen) && !isNull(qHours.latestClose)) {
earliestOpen = timeFormat(qHours.earliestOpen, "HH:mm:ss");
latestClose = timeFormat(qHours.latestClose, "HH:mm:ss");
if (menuStartTime < earliestOpen || menuEndTime > latestClose) {
throw(message="Menu hours (" & menuStartTime & " - " & menuEndTime & ") must be within business operating hours (" & earliestOpen & " - " & latestClose & ")");
}
response.steps.append("Validated menu hours against business hours (" & earliestOpen & " - " & latestClose & ")");
}
}
qMenu = queryExecute("
SELECT MenuID FROM Menus
WHERE MenuBusinessID = :bizID AND MenuName = :name AND MenuIsActive = 1
", { bizID: businessId, name: menuName }, { datasource: "payfrit" });
if (qMenu.recordCount > 0) {
menuID = qMenu.MenuID;
// Update existing menu with new time range if provided
if (len(menuStartTime) && len(menuEndTime)) {
queryExecute("
UPDATE Menus SET MenuStartTime = :startTime, MenuEndTime = :endTime
WHERE MenuID = :menuID
", {
menuID: menuID,
startTime: menuStartTime,
endTime: menuEndTime
}, { datasource: "payfrit" });
response.steps.append("Updated existing menu: " & menuName & " (ID: " & menuID & ") with hours " & menuStartTime & " - " & menuEndTime);
} else {
response.steps.append("Using existing menu: " & menuName & " (ID: " & menuID & ")");
}
} else {
queryExecute("
INSERT INTO Menus (
MenuBusinessID, MenuName, MenuDaysActive, MenuStartTime, MenuEndTime, MenuSortOrder, MenuIsActive, MenuAddedOn
) VALUES (
:bizID, :name, 127, :startTime, :endTime, 0, 1, NOW()
)
", {
bizID: businessId,
name: menuName,
startTime: len(menuStartTime) ? menuStartTime : javaCast("null", ""),
endTime: len(menuEndTime) ? menuEndTime : javaCast("null", "")
}, { datasource: "payfrit" });
qNewMenu = queryExecute("SELECT LAST_INSERT_ID() as id", {}, { datasource: "payfrit" });
menuID = qNewMenu.id;
timeInfo = len(menuStartTime) && len(menuEndTime) ? " (" & menuStartTime & " - " & menuEndTime & ")" : " (all day)";
response.steps.append("Created menu: " & menuName & timeInfo & " (ID: " & menuID & ")");
}
}
// Build category map

View file

@ -3503,6 +3503,11 @@
});
const data = await response.json();
if (data.OK) {
if (data.ACTION === 'created') {
// New menu — redirect to wizard to populate it
window.location.href = `${BASE_PATH}/portal/setup-wizard.html?businessId=${this.config.businessId}&menuId=${data.MENUID}`;
return;
}
this.toast(`Menu ${data.ACTION}!`, 'success');
this.closeModal();
await this.loadMenu();

View file

@ -1048,8 +1048,13 @@
});
function initializeConfig() {
// Wizard is for creating NEW businesses - businessId starts as null
config.businessId = null;
// Parse URL parameters for add-menu mode
const params = new URLSearchParams(window.location.search);
const paramBusinessId = params.get('businessId');
const paramMenuId = params.get('menuId');
config.businessId = paramBusinessId ? parseInt(paramBusinessId) : null;
config.menuId = paramMenuId ? parseInt(paramMenuId) : null;
// Determine API base URL
const basePath = window.location.pathname.includes('/biz.payfrit.com/')
@ -1065,8 +1070,7 @@
}
config.userId = userId;
// BusinessId is optional - will be created if not provided
console.log('Wizard initialized. BusinessId:', config.businessId, 'UserId:', config.userId);
console.log('Wizard initialized. BusinessId:', config.businessId, 'MenuId:', config.menuId, 'UserId:', config.userId);
}
function setupUploadZone() {
@ -1250,8 +1254,12 @@
// Remove loading message and start conversation flow
document.getElementById('conversation').innerHTML = '';
// Start with business info
showBusinessInfoStep();
// In add-menu mode, skip business info and header — go straight to categories
if (config.businessId && config.menuId) {
showCategoriesStep();
} else {
showBusinessInfoStep();
}
} catch (error) {
console.error('Analysis error:', error);
@ -2157,22 +2165,28 @@
document.getElementById('summaryModifiers').textContent = modifiers.length;
document.getElementById('summaryItems').textContent = items.length;
// Set default menu hours based on business hours (earliest open, latest close)
const hoursSchedule = business.hoursSchedule || [];
if (hoursSchedule.length > 0) {
let earliestOpen = '23:59';
let latestClose = '00:00';
hoursSchedule.forEach(day => {
if (day.open && day.open < earliestOpen) earliestOpen = day.open;
if (day.close && day.close > latestClose) latestClose = day.close;
});
document.getElementById('menuStartTime').value = earliestOpen;
document.getElementById('menuEndTime').value = latestClose;
// In add-menu mode, hide menu name/hours/community meal (already set during menu creation)
if (config.menuId) {
document.getElementById('menuNameInput').parentElement.style.display = 'none';
document.getElementById('menuStartTime').closest('.summary-stat').style.display = 'none';
} else {
// Set default menu hours based on business hours (earliest open, latest close)
const hoursSchedule = business.hoursSchedule || [];
if (hoursSchedule.length > 0) {
let earliestOpen = '23:59';
let latestClose = '00:00';
hoursSchedule.forEach(day => {
if (day.open && day.open < earliestOpen) earliestOpen = day.open;
if (day.close && day.close > latestClose) latestClose = day.close;
});
document.getElementById('menuStartTime').value = earliestOpen;
document.getElementById('menuEndTime').value = latestClose;
}
}
addMessage('ai', `
<p>Your menu is ready to save!</p>
<p><strong>${business.name || 'Your Restaurant'}</strong></p>
${config.menuId ? '' : `<p><strong>${business.name || 'Your Restaurant'}</strong></p>`}
<ul style="margin: 12px 0; padding-left: 20px; color: var(--gray-600);">
<li>${categories.length} categories</li>
<li>${modifiers.length} modifier templates</li>
@ -2189,10 +2203,11 @@
console.log('=== SAVE MENU CALLED ===');
console.log('Data to save:', config.extractedData);
// Get menu name and time range from inputs
const menuName = document.getElementById('menuNameInput').value.trim() || 'Main Menu';
const menuStartTime = document.getElementById('menuStartTime').value || '';
const menuEndTime = document.getElementById('menuEndTime').value || '';
// In add-menu mode, menu name/time were already set — skip these fields
const menuNameEl = document.getElementById('menuNameInput');
const menuName = menuNameEl ? menuNameEl.value.trim() || 'Main Menu' : 'Main Menu';
const menuStartTime = document.getElementById('menuStartTime')?.value || '';
const menuEndTime = document.getElementById('menuEndTime')?.value || '';
// Validate menu hours fall within business operating hours
if (menuStartTime && menuEndTime) {
@ -2233,6 +2248,7 @@
},
body: JSON.stringify({
businessId: config.businessId || 0,
menuId: config.menuId || 0,
userId: config.userId,
data: config.extractedData
})
@ -2302,9 +2318,9 @@
}
}
// Redirect to visual menu builder after a moment
// Redirect back after a moment
setTimeout(() => {
window.location.href = 'index.html#menu';
window.location.href = config.menuId ? 'menu-builder.html' : 'index.html#menu';
}, 1500);
} catch (error) {