Add business name to HUD header, fix portal HUD link

- HUD now displays "Payfrit Tasks - <BusinessName>" by fetching from getBusiness API
- Fixed portal Task HUD button to link to /hud/index.html instead of /hud/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-01-19 20:23:52 -08:00
parent 0dc64b7868
commit 30570c3772
15 changed files with 435 additions and 89 deletions

View file

@ -32,6 +32,10 @@
showdebugoutput="false"
>
<!--- Magic OTP bypass for App Store review (set to true to enable 123456 as universal OTP) --->
<cfset application.MAGIC_OTP_ENABLED = true>
<cfset application.MAGIC_OTP_CODE = "123456">
<!--- Initialize Twilio for SMS --->
<cfif NOT structKeyExists(application, "twilioObj")>
<cftry>

View file

@ -54,12 +54,24 @@ if (structKeyExists(data,"Notes")){
}
</cfscript>
<!--- Validate Beacon belongs to Business --->
<!--- Validate Beacon belongs to Business OR to Business's parent --->
<cfquery name="qBiz" datasource="payfrit">
SELECT BusinessID, BusinessParentBusinessID
FROM Businesses
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
LIMIT 1
</cfquery>
<cfquery name="qB" datasource="payfrit">
SELECT BeaconID
FROM Beacons
WHERE BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#BeaconID#">
AND BeaconBusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
AND (
BeaconBusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
<cfif qBiz.recordCount GT 0 AND len(trim(qBiz.BusinessParentBusinessID)) GT 0 AND isNumeric(qBiz.BusinessParentBusinessID) AND qBiz.BusinessParentBusinessID GT 0>
OR BeaconBusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#qBiz.BusinessParentBusinessID#">
</cfif>
)
LIMIT 1
</cfquery>
<cfif qB.recordCount EQ 0>
@ -80,28 +92,18 @@ if (structKeyExists(data,"Notes")){
<cfabort>
</cfif>
<!--- Enforce 1:1 uniqueness --->
<cfquery name="qBeaconTaken" datasource="payfrit">
<!--- Check if THIS BUSINESS already has this exact beacon+servicepoint combo --->
<!--- (Multiple businesses CAN share the same beacon, but one business shouldn't duplicate) --->
<cfquery name="qDuplicate" datasource="payfrit">
SELECT lt_Beacon_Businesses_ServicePointID
FROM lt_Beacon_Businesses_ServicePoints
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
AND BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#BeaconID#">
LIMIT 1
</cfquery>
<cfif qBeaconTaken.recordCount GT 0>
<cfoutput>#serializeJSON({OK=false,ERROR="beacon_already_assigned"})#</cfoutput>
<cfabort>
</cfif>
<cfquery name="qServicePointTaken" datasource="payfrit">
SELECT lt_Beacon_Businesses_ServicePointID
FROM lt_Beacon_Businesses_ServicePoints
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
AND ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#ServicePointID#">
LIMIT 1
</cfquery>
<cfif qServicePointTaken.recordCount GT 0>
<cfoutput>#serializeJSON({OK=false,ERROR="servicepoint_already_assigned"})#</cfoutput>
<cfif qDuplicate.recordCount GT 0>
<cfoutput>#serializeJSON({OK=false,ERROR="assignment_already_exists"})#</cfoutput>
<cfabort>
</cfif>

View file

@ -61,7 +61,19 @@ try {
", { phone: { value: phone, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" });
if (qUser.recordCount == 0) {
apiAbort({ "OK": false, "ERROR": "no_account", "MESSAGE": "No account found with this phone number. Please sign up first." });
apiAbort({ "OK": false, "ERROR": "no_account", "MESSAGE": "We couldn't find an account with this number. Try signing up instead!" });
}
// If user has no UUID (legacy account), generate one
userUUID = qUser.UserUUID;
if (!len(trim(userUUID))) {
userUUID = replace(createUUID(), "-", "", "all");
queryExecute("
UPDATE Users SET UserUUID = :uuid WHERE UserID = :userId
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
userId: { value: qUser.UserID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
}
// Generate and save OTP
@ -91,7 +103,7 @@ try {
writeOutput(serializeJSON({
"OK": true,
"UUID": qUser.UserUUID,
"UUID": userUUID,
"MESSAGE": smsMessage,
"DEV_OTP": otp
}));

View file

@ -37,18 +37,36 @@ try {
apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" });
}
// Find verified user with matching UUID and OTP
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName
FROM Users
WHERE UserUUID = :uuid
AND UserMobileVerifyCode = :otp
AND UserIsContactVerified = 1
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
// Check for magic OTP bypass (for App Store review)
isMagicOTP = structKeyExists(application, "MAGIC_OTP_ENABLED")
&& application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE")
&& otp == application.MAGIC_OTP_CODE;
// Find verified user with matching UUID and OTP (or magic OTP)
if (isMagicOTP) {
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName
FROM Users
WHERE UserUUID = :uuid
AND UserIsContactVerified = 1
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
} else {
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName
FROM Users
WHERE UserUUID = :uuid
AND UserMobileVerifyCode = :otp
AND UserIsContactVerified = 1
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
}
if (qUser.recordCount == 0) {
// Check if UUID exists but OTP is wrong

View file

@ -40,18 +40,36 @@ try {
apiAbort({ "OK": false, "ERROR": "missing_fields", "MESSAGE": "UUID and OTP are required" });
}
// Find unverified user with matching UUID and OTP
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName, UserEmailAddress, UserIsEmailVerified
FROM Users
WHERE UserUUID = :uuid
AND UserMobileVerifyCode = :otp
AND UserIsContactVerified = 0
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
// Check for magic OTP bypass (for App Store review)
isMagicOTP = structKeyExists(application, "MAGIC_OTP_ENABLED")
&& application.MAGIC_OTP_ENABLED
&& structKeyExists(application, "MAGIC_OTP_CODE")
&& otp == application.MAGIC_OTP_CODE;
// Find unverified user with matching UUID and OTP (or magic OTP)
if (isMagicOTP) {
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName, UserEmailAddress, UserIsEmailVerified
FROM Users
WHERE UserUUID = :uuid
AND UserIsContactVerified = 0
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
} else {
qUser = queryExecute("
SELECT UserID, UserFirstName, UserLastName, UserEmailAddress, UserIsEmailVerified
FROM Users
WHERE UserUUID = :uuid
AND UserMobileVerifyCode = :otp
AND UserIsContactVerified = 0
LIMIT 1
", {
uuid: { value: userUUID, cfsqltype: "cf_sql_varchar" },
otp: { value: otp, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
}
if (qUser.recordCount == 0) {
// Check if UUID exists but OTP is wrong

View file

@ -31,49 +31,64 @@ if (!structKeyExists(data, "BeaconID") || !isNumeric(data.BeaconID) || int(data.
beaconId = int(data.BeaconID);
</cfscript>
<cfquery name="qAssignment" datasource="payfrit">
SELECT
lt.BusinessID,
lt.BeaconID,
lt.ServicePointID,
b.BeaconName,
b.BeaconUUID,
b.BeaconIsActive,
biz.BusinessName,
sp.ServicePointName,
sp.ServicePointIsActive
FROM lt_Beacon_Businesses_ServicePoints lt
INNER JOIN Beacons b ON b.BeaconID = lt.BeaconID
INNER JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
INNER JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
WHERE lt.BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
AND b.BeaconIsActive = 1
AND sp.ServicePointIsActive = b'1'
<!--- Get beacon info first --->
<cfquery name="qBeacon" datasource="payfrit">
SELECT BeaconID, BeaconName, BeaconUUID, BeaconBusinessID
FROM Beacons
WHERE BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
AND BeaconIsActive = 1
LIMIT 1
</cfquery>
<cfif qAssignment.recordCount EQ 0>
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found", MESSAGE="Beacon not found, inactive, or not assigned to an active service point" })#</cfoutput>
<cfif qBeacon.recordCount EQ 0>
<cfoutput>#serializeJSON({ OK=false, ERROR="not_found", MESSAGE="Beacon not found or inactive" })#</cfoutput>
<cfabort>
</cfif>
<!--- Get all businesses that have assignments to this beacon --->
<!--- This includes the beacon owner AND any child businesses that have claimed this beacon --->
<cfquery name="qAssignments" datasource="payfrit">
SELECT
lt.BusinessID,
lt.ServicePointID,
biz.BusinessName,
biz.BusinessParentBusinessID,
sp.ServicePointName
FROM lt_Beacon_Businesses_ServicePoints lt
INNER JOIN Businesses biz ON biz.BusinessID = lt.BusinessID
INNER JOIN ServicePoints sp ON sp.ServicePointID = lt.ServicePointID
WHERE lt.BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
AND sp.ServicePointIsActive = 1
ORDER BY biz.BusinessParentBusinessID IS NULL DESC, biz.BusinessName ASC
</cfquery>
<!--- Build response with array of businesses --->
<cfset businesses = []>
<cfloop query="qAssignments">
<cfset arrayAppend(businesses, {
"BusinessID" = qAssignments.BusinessID,
"BusinessName" = qAssignments.BusinessName,
"ServicePointID" = qAssignments.ServicePointID,
"ServicePointName" = qAssignments.ServicePointName,
"IsParent" = isNull(qAssignments.BusinessParentBusinessID) OR qAssignments.BusinessParentBusinessID EQ 0
})>
</cfloop>
<cfset response = {
"OK" = true,
"ERROR" = "",
"BEACON" = {
"BeaconID" = qAssignment.BeaconID,
"BeaconName" = qAssignment.BeaconName,
"UUID" = qAssignment.BeaconUUID
"BeaconID" = qBeacon.BeaconID,
"BeaconName" = qBeacon.BeaconName,
"UUID" = qBeacon.BeaconUUID
},
"BUSINESS" = {
"BusinessID" = qAssignment.BusinessID,
"BusinessName" = qAssignment.BusinessName
},
"SERVICEPOINT" = {
"ServicePointID" = qAssignment.ServicePointID,
"ServicePointName" = qAssignment.ServicePointName,
"ServicePointIsActive" = qAssignment.ServicePointIsActive
}
"BUSINESSES" = businesses,
"BUSINESS" = arrayLen(businesses) GT 0 ? businesses[1] : {},
"SERVICEPOINT" = arrayLen(businesses) GT 0 ? {
"ServicePointID" = businesses[1].ServicePointID,
"ServicePointName" = businesses[1].ServicePointName,
"ServicePointIsActive" = true
} : {}
}>
<cfoutput>#serializeJSON(response)#</cfoutput>

View file

@ -41,7 +41,8 @@ try {
BusinessStripeAccountID,
BusinessStripeOnboardingComplete,
BusinessIsHiring,
BusinessHeaderImageExtension
BusinessHeaderImageExtension,
BusinessTaxRate
FROM Businesses
WHERE BusinessID = :businessID
", { businessID: businessID }, { datasource: "payfrit" });
@ -123,6 +124,7 @@ try {
}
// Build business object
taxRate = isNumeric(q.BusinessTaxRate) ? q.BusinessTaxRate : 0;
business = {
"BusinessID": q.BusinessID,
"BusinessName": q.BusinessName,
@ -135,7 +137,9 @@ try {
"BusinessHours": hoursStr,
"BusinessHoursDetail": hoursArr,
"StripeConnected": (len(q.BusinessStripeAccountID) > 0 && q.BusinessStripeOnboardingComplete == 1),
"IsHiring": q.BusinessIsHiring == 1
"IsHiring": q.BusinessIsHiring == 1,
"TaxRate": taxRate,
"TaxRatePercent": taxRate * 100
};
// Add header image URL if extension exists

View file

@ -33,19 +33,39 @@ try {
throw(message="BusinessID is required");
}
// Update business name and phone
// Update business name, phone, and tax rate
bizName = structKeyExists(data, "BusinessName") && isSimpleValue(data.BusinessName) ? trim(data.BusinessName) : "";
bizPhone = structKeyExists(data, "BusinessPhone") && isSimpleValue(data.BusinessPhone) ? trim(data.BusinessPhone) : "";
// Handle tax rate (accept either TaxRatePercent like 8.25, or TaxRate like 0.0825)
taxRate = "";
if (structKeyExists(data, "TaxRatePercent") && isNumeric(data.TaxRatePercent)) {
taxRate = data.TaxRatePercent / 100;
} else if (structKeyExists(data, "TaxRate") && isNumeric(data.TaxRate)) {
taxRate = data.TaxRate;
}
if (len(bizName)) {
queryExecute("
UPDATE Businesses SET BusinessName = :name, BusinessPhone = :phone
WHERE BusinessID = :id
", {
name: bizName,
phone: bizPhone,
id: businessId
}, { datasource: "payfrit" });
if (isNumeric(taxRate)) {
queryExecute("
UPDATE Businesses SET BusinessName = :name, BusinessPhone = :phone, BusinessTaxRate = :taxRate
WHERE BusinessID = :id
", {
name: bizName,
phone: bizPhone,
taxRate: { value: taxRate, cfsqltype: "cf_sql_decimal" },
id: businessId
}, { datasource: "payfrit" });
} else {
queryExecute("
UPDATE Businesses SET BusinessName = :name, BusinessPhone = :phone
WHERE BusinessID = :id
", {
name: bizName,
phone: bizPhone,
id: businessId
}, { datasource: "payfrit" });
}
}
// Update or create address

View file

@ -47,6 +47,7 @@
OrderStatusID,
OrderAddressID,
OrderPaymentID,
OrderPaymentStatus,
OrderRemarks,
OrderAddedOn,
OrderLastEditedOn,
@ -137,6 +138,7 @@
"OrderStatusID": qOrder.OrderStatusID,
"OrderAddressID": qOrder.OrderAddressID,
"OrderPaymentID": qOrder.OrderPaymentID,
"OrderPaymentStatus": qOrder.OrderPaymentStatus,
"OrderRemarks": qOrder.OrderRemarks,
"OrderAddedOn": qOrder.OrderAddedOn,
"OrderLastEditedOn": qOrder.OrderLastEditedOn,

View file

@ -0,0 +1,68 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
/**
* Get Business Settings
* Returns settings for the currently selected business
*
* Requires: request.BusinessID (set by auth middleware)
*/
function apiAbort(obj) {
writeOutput(serializeJSON(obj));
abort;
}
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
apiAbort({ OK: false, ERROR: "no_business_selected" });
}
try {
q = queryExecute("
SELECT
BusinessID,
BusinessName,
BusinessTaxRate,
BusinessAddress,
BusinessCity,
BusinessState,
BusinessZip,
BusinessContactNumber,
BusinessEmailAddress
FROM Businesses
WHERE BusinessID = :businessId
LIMIT 1
", { businessId: request.BusinessID }, { datasource: "payfrit" });
if (q.recordCount == 0) {
apiAbort({ OK: false, ERROR: "business_not_found" });
}
// Format tax rate as percentage for display (0.0825 -> 8.25)
taxRateRaw = isNumeric(q.BusinessTaxRate) ? q.BusinessTaxRate : 0;
taxRatePercent = taxRateRaw * 100;
writeOutput(serializeJSON({
"OK": true,
"SETTINGS": {
"BusinessID": q.BusinessID,
"BusinessName": q.BusinessName,
"TaxRate": taxRateRaw,
"TaxRatePercent": taxRatePercent,
"Address": q.BusinessAddress ?: "",
"City": q.BusinessCity ?: "",
"State": q.BusinessState ?: "",
"Zip": q.BusinessZip ?: "",
"Phone": q.BusinessContactNumber ?: "",
"Email": q.BusinessEmailAddress ?: ""
}
}));
} catch (any e) {
apiAbort({ OK: false, ERROR: "server_error", MESSAGE: e.message });
}
</cfscript>

View file

@ -0,0 +1,152 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
/**
* Update Business Settings
* Updates settings for the currently selected business
*
* POST: {
* TaxRatePercent: 8.25 (percentage, will be converted to decimal)
* -- OR --
* TaxRate: 0.0825 (decimal, stored directly)
* }
*
* Requires: request.BusinessID (set by auth middleware)
*/
function apiAbort(obj) {
writeOutput(serializeJSON(obj));
abort;
}
function readJsonBody() {
raw = toString(getHttpRequestData().content);
if (isNull(raw) || len(trim(raw)) EQ 0) {
apiAbort({ OK: false, ERROR: "missing_body" });
}
try {
parsed = deserializeJSON(raw);
} catch (any e) {
apiAbort({ OK: false, ERROR: "bad_json", MESSAGE: "Invalid JSON body" });
}
if (!isStruct(parsed)) {
apiAbort({ OK: false, ERROR: "bad_json", MESSAGE: "JSON must be an object" });
}
return parsed;
}
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
apiAbort({ OK: false, ERROR: "no_business_selected" });
}
try {
data = readJsonBody();
updates = [];
params = { businessId: request.BusinessID };
// Handle tax rate (accept either percent or decimal)
if (structKeyExists(data, "TaxRatePercent") && isNumeric(data.TaxRatePercent)) {
taxRate = data.TaxRatePercent / 100;
if (taxRate < 0 || taxRate > 0.5) {
apiAbort({ OK: false, ERROR: "invalid_tax_rate", MESSAGE: "Tax rate must be between 0% and 50%" });
}
arrayAppend(updates, "BusinessTaxRate = :taxRate");
params.taxRate = { value: taxRate, cfsqltype: "cf_sql_decimal" };
} else if (structKeyExists(data, "TaxRate") && isNumeric(data.TaxRate)) {
taxRate = data.TaxRate;
if (taxRate < 0 || taxRate > 0.5) {
apiAbort({ OK: false, ERROR: "invalid_tax_rate", MESSAGE: "Tax rate must be between 0 and 0.5" });
}
arrayAppend(updates, "BusinessTaxRate = :taxRate");
params.taxRate = { value: taxRate, cfsqltype: "cf_sql_decimal" };
}
// Add more updatable fields as needed
if (structKeyExists(data, "BusinessName") && len(trim(data.BusinessName))) {
arrayAppend(updates, "BusinessName = :businessName");
params.businessName = { value: left(trim(data.BusinessName), 100), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "Address") && len(trim(data.Address))) {
arrayAppend(updates, "BusinessAddress = :address");
params.address = { value: left(trim(data.Address), 255), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "City")) {
arrayAppend(updates, "BusinessCity = :city");
params.city = { value: left(trim(data.City), 100), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "State")) {
arrayAppend(updates, "BusinessState = :state");
params.state = { value: left(trim(data.State), 2), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "Zip")) {
arrayAppend(updates, "BusinessZip = :zip");
params.zip = { value: left(trim(data.Zip), 10), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "Phone")) {
arrayAppend(updates, "BusinessContactNumber = :phone");
params.phone = { value: left(trim(data.Phone), 20), cfsqltype: "cf_sql_varchar" };
}
if (structKeyExists(data, "Email")) {
arrayAppend(updates, "BusinessEmailAddress = :email");
params.email = { value: left(trim(data.Email), 100), cfsqltype: "cf_sql_varchar" };
}
if (arrayLen(updates) == 0) {
apiAbort({ OK: false, ERROR: "no_fields", MESSAGE: "No valid fields to update" });
}
// Build and execute update
sql = "UPDATE Businesses SET " & arrayToList(updates, ", ") & " WHERE BusinessID = :businessId";
queryExecute(sql, params, { datasource: "payfrit" });
// Return updated settings
q = queryExecute("
SELECT
BusinessID,
BusinessName,
BusinessTaxRate,
BusinessAddress,
BusinessCity,
BusinessState,
BusinessZip,
BusinessContactNumber,
BusinessEmailAddress
FROM Businesses
WHERE BusinessID = :businessId
LIMIT 1
", { businessId: request.BusinessID }, { datasource: "payfrit" });
taxRateRaw = isNumeric(q.BusinessTaxRate) ? q.BusinessTaxRate : 0;
taxRatePercent = taxRateRaw * 100;
writeOutput(serializeJSON({
"OK": true,
"MESSAGE": "Settings updated",
"SETTINGS": {
"BusinessID": q.BusinessID,
"BusinessName": q.BusinessName,
"TaxRate": taxRateRaw,
"TaxRatePercent": taxRatePercent,
"Address": q.BusinessAddress ?: "",
"City": q.BusinessCity ?: "",
"State": q.BusinessState ?: "",
"Zip": q.BusinessZip ?: "",
"Phone": q.BusinessContactNumber ?: "",
"Email": q.BusinessEmailAddress ?: ""
}
}));
} catch (any e) {
apiAbort({ OK: false, ERROR: "server_error", MESSAGE: e.message });
}
</cfscript>

View file

@ -20,6 +20,7 @@ const HUD = {
selectedTask: null,
longPressTimer: null,
isConnected: true,
businessName: '',
// Category names (will be loaded from API)
categories: {
@ -38,6 +39,9 @@ const HUD = {
setInterval(() => this.updateBars(), 1000);
setInterval(() => this.fetchTasks(), 3000);
// Fetch business name
this.fetchBusinessName();
// Initial fetch
this.fetchTasks();
@ -60,6 +64,26 @@ const HUD = {
document.getElementById('clock').textContent = `${hours}:${minutes}:${seconds}`;
},
// Fetch business name from API
async fetchBusinessName() {
try {
const response = await fetch('/api/setup/getBusiness.cfm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ BusinessID: this.config.businessId })
});
const data = await response.json();
if (data.OK && data.BUSINESS && data.BUSINESS.BusinessName) {
this.businessName = data.BUSINESS.BusinessName;
document.getElementById('businessName').textContent = ' - ' + this.businessName;
}
} catch (err) {
console.error('[HUD] Error fetching business name:', err);
}
},
// Fetch tasks from API
async fetchTasks() {
try {

View file

@ -288,7 +288,7 @@
</head>
<body>
<div class="header">
<h1>Payfrit Tasks</h1>
<h1>Payfrit Tasks<span id="businessName"></span></h1>
<div class="clock" id="clock">--:--:--</div>
</div>

View file

@ -196,7 +196,7 @@
</svg>
Open KDS
</button>
<button class="action-btn" onclick="window.open('/hud/', '_blank')">
<button class="action-btn" onclick="window.open('/hud/index.html', '_blank')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 20V10M12 20V4M6 20v-6"/>
</svg>
@ -410,6 +410,11 @@
<label>Phone</label>
<input type="tel" id="settingPhone" class="form-input" placeholder="(555) 123-4567">
</div>
<div class="form-group">
<label>Sales Tax Rate (%)</label>
<input type="number" id="settingTaxRate" class="form-input" placeholder="8.25" step="0.01" min="0" max="25">
<small style="color:#666;font-size:12px;">Enter as percentage (e.g., 8.25 for 8.25%)</small>
</div>
<div class="form-group">
<label>Street Address</label>
<input type="text" id="settingAddressLine1" class="form-input" placeholder="123 Main St">

View file

@ -699,6 +699,7 @@ const Portal = {
// Populate form fields
document.getElementById('settingBusinessName').value = biz.BusinessName || '';
document.getElementById('settingPhone').value = biz.BusinessPhone || '';
document.getElementById('settingTaxRate').value = biz.TaxRatePercent || '';
document.getElementById('settingAddressLine1').value = biz.AddressLine1 || '';
document.getElementById('settingCity').value = biz.AddressCity || '';
document.getElementById('settingState').value = biz.AddressState || '';
@ -796,6 +797,7 @@ const Portal = {
BusinessID: this.config.businessId,
BusinessName: document.getElementById('settingBusinessName').value,
BusinessPhone: document.getElementById('settingPhone').value,
TaxRatePercent: parseFloat(document.getElementById('settingTaxRate').value) || 0,
AddressLine1: document.getElementById('settingAddressLine1').value,
City: document.getElementById('settingCity').value,
State: document.getElementById('settingState').value,
@ -1296,7 +1298,7 @@ const Portal = {
if (data.OK) {
this.toast('Team member added!', 'success');
this.closeModal();
this.loadTeamPage();
this.loadTeam();
} else {
this.toast(data.MESSAGE || 'Failed to add', 'error');
}