From b9f5eb834f7661ff855527efe6797d7f9ae83dcc Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Sun, 11 Jan 2026 17:13:59 -0800 Subject: [PATCH] Add hiring toggle functionality to portal Team page - Add setHiring.cfm API endpoint to update BusinessIsHiring - Add endpoint to Application.cfm allowlist - Update portal.js to load and sync hiring toggle state - Wire toggle to call API and show feedback toasts - Bump portal.js version for cache busting Co-Authored-By: Claude Opus 4.5 --- api/Application.cfm | 1 + api/businesses/setHiring.cfm | 68 +++++++++++++++++++++++++++++ portal/index.html | 2 +- portal/portal.js | 85 ++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 api/businesses/setHiring.cfm diff --git a/api/Application.cfm b/api/Application.cfm index 5de06bf..607f2e6 100644 --- a/api/Application.cfm +++ b/api/Application.cfm @@ -128,6 +128,7 @@ if (len(request._api_path)) { if (findNoCase("/api/portal/stats.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/portal/myBusinesses.cfm", request._api_path)) request._api_isPublic = true; if (findNoCase("/api/portal/team.cfm", request._api_path)) request._api_isPublic = true; + if (findNoCase("/api/businesses/setHiring.cfm", request._api_path)) request._api_isPublic = true; // Order history (auth handled in endpoint) if (findNoCase("/api/orders/history.cfm", request._api_path)) request._api_isPublic = true; diff --git a/api/businesses/setHiring.cfm b/api/businesses/setHiring.cfm new file mode 100644 index 0000000..b204cb6 --- /dev/null +++ b/api/businesses/setHiring.cfm @@ -0,0 +1,68 @@ + + + + + + +/* + PATH: /api/businesses/setHiring.cfm + + INPUT (JSON): + { "BusinessID": 17, "IsHiring": true } + + OUTPUT (JSON): + { OK: true, IsHiring: true } or { OK: false, ERROR: string } +*/ + +function apiAbort(required struct payload) { + writeOutput(serializeJSON(payload)); + abort; +} + +function readJsonBody() { + var raw = getHttpRequestData().content; + if (isNull(raw)) raw = ""; + if (!len(trim(raw))) return {}; + try { + var data = deserializeJSON(raw); + if (isStruct(data)) return data; + } catch (any e) {} + return {}; +} + +data = readJsonBody(); +businessId = structKeyExists(data, "BusinessID") ? val(data.BusinessID) : 0; +isHiring = structKeyExists(data, "IsHiring") ? (data.IsHiring == true ? 1 : 0) : -1; + +if (businessId <= 0) { + apiAbort({ "OK": false, "ERROR": "missing_business_id" }); +} + +if (isHiring == -1) { + apiAbort({ "OK": false, "ERROR": "missing_is_hiring" }); +} + +try { + queryExecute(" + UPDATE Businesses + SET BusinessIsHiring = ? + WHERE BusinessID = ? + ", [ + { value: isHiring, cfsqltype: "cf_sql_tinyint" }, + { value: businessId, cfsqltype: "cf_sql_integer" } + ], { datasource: "payfrit" }); + + writeOutput(serializeJSON({ + "OK": true, + "IsHiring": isHiring == 1 + })); + abort; + +} catch (any e) { + apiAbort({ + "OK": false, + "ERROR": "server_error", + "MESSAGE": e.message + }); +} + diff --git a/portal/index.html b/portal/index.html index cd3f1c1..dd31e5f 100644 --- a/portal/index.html +++ b/portal/index.html @@ -424,6 +424,6 @@
- + diff --git a/portal/portal.js b/portal/portal.js index 84a4499..1edb5e5 100644 --- a/portal/portal.js +++ b/portal/portal.js @@ -111,10 +111,12 @@ const Portal = { if (data.OK && data.BUSINESS) { const biz = data.BUSINESS; + this.businessData = biz; // Store for later use document.getElementById('businessName').textContent = biz.BusinessName || 'Business'; document.getElementById('businessAvatar').textContent = (biz.BusinessName || 'B').charAt(0).toUpperCase(); document.getElementById('userAvatar').textContent = 'U'; } else { + this.businessData = null; document.getElementById('businessName').textContent = 'Business #' + this.config.businessId; document.getElementById('businessAvatar').textContent = 'B'; document.getElementById('userAvatar').textContent = 'U'; @@ -497,6 +499,9 @@ const Portal = { const tbody = document.getElementById('teamTableBody'); tbody.innerHTML = 'Loading team...'; + // Load hiring toggle state from business data + this.loadHiringToggle(); + try { const url = `${this.config.apiBaseUrl}/portal/team.cfm`; console.log('[Portal] Fetching team from:', url); @@ -543,6 +548,86 @@ const Portal = { } }, + // Load hiring toggle from business data + loadHiringToggle() { + const toggle = document.getElementById('hiringToggle'); + if (!toggle) return; + + // Remove existing listener if any + toggle.onchange = null; + + // Set initial state from business data + if (this.businessData && typeof this.businessData.IsHiring !== 'undefined') { + toggle.checked = this.businessData.IsHiring; + console.log('[Portal] Hiring toggle set to:', this.businessData.IsHiring); + } else { + console.log('[Portal] No business data for hiring toggle, fetching...'); + // Fetch fresh business data if not available + this.refreshBusinessData(); + } + + // Wire up the toggle + toggle.onchange = () => this.toggleHiring(toggle.checked); + }, + + // Refresh business data + async refreshBusinessData() { + try { + const response = await fetch(`${this.config.apiBaseUrl}/businesses/get.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) { + this.businessData = data.BUSINESS; + const toggle = document.getElementById('hiringToggle'); + if (toggle) { + toggle.checked = this.businessData.IsHiring; + console.log('[Portal] Hiring toggle refreshed to:', this.businessData.IsHiring); + } + } + } catch (err) { + console.error('[Portal] Error refreshing business data:', err); + } + }, + + // Toggle hiring status + async toggleHiring(isHiring) { + console.log('[Portal] Setting hiring to:', isHiring); + try { + const response = await fetch(`${this.config.apiBaseUrl}/businesses/setHiring.cfm`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + BusinessID: this.config.businessId, + IsHiring: isHiring + }) + }); + const data = await response.json(); + console.log('[Portal] setHiring response:', data); + + if (data.OK) { + // Update local state + if (this.businessData) { + this.businessData.IsHiring = data.IsHiring; + } + this.toast(isHiring ? 'Now accepting applications' : 'Applications disabled', 'success'); + } else { + // Revert toggle + const toggle = document.getElementById('hiringToggle'); + if (toggle) toggle.checked = !isHiring; + this.toast(data.ERROR || 'Failed to update hiring status', 'error'); + } + } catch (err) { + console.error('[Portal] Error toggling hiring:', err); + // Revert toggle + const toggle = document.getElementById('hiringToggle'); + if (toggle) toggle.checked = !isHiring; + this.toast('Error updating hiring status', 'error'); + } + }, + // Edit team member (placeholder) editTeamMember(employeeId) { this.showToast('Team member editing coming soon', 'info');