## Deprecated `postApi` Usage — Future Refactoring

### Current State

The React codebase still uses the deprecated `postApi` utility in 159 places across 45 files. According to project standards, all new/refactored code should use the modern `xapi` utility instead.

**Files with `postApi` usage:**
- Communications (AttachmentActions.js, actions.js)
- CRM (actions.js, CrmContactTabs/*.js)
- Calendars (actions.js), Chat, Campaigns, Clients
- Documents, Tasks, Expenses, Details Tab
- Property (actions.js), Property Attributes, Property List
- Roles, Reports, Support, Text Message, Users, Vault List, Workflow
- And 30+ others

### Recent Fix (May 2026)

Fixed the reject email functionality by refactoring two offer-related functions:
- `xapiSendPropertyOfferNotification` (sends rejection emails)
- `xapiOfferFieldUpdate` (updates offer status to 'rejected')

### Future Work — Broad Refactoring

Replace all 159 `postApi` usages with `xapi`:

1. **Batch 1 (High Priority)** — Offer/Property related: `propertyoffer_update`, `propertydetails_update` in Property/actions.js
2. **Batch 2** — Communications: email send, attachments, drafts
3. **Batch 3** — CRM contact operations
4. **Batch 4** — Documents, Tasks, Calendar, Chat operations
5. **Batch 5** — Everything else

Each function should:
- Replace `postApi.post()` with `new xapi(action_name)`
- Use `x.add(param, value)` for all parameters
- Call `x.fetch().then(resp => resp.json())`
- Fix response structure: `data.result.success` instead of `data.data.result.success`
- Improve error handling (current `.catch()` handlers are often empty)

**Important:** When converting a postApi function to xapi:

1. **Check the PHP endpoint's parameter definitions** to understand structure:
   - If endpoint expects individual parameters (like `$params['action']['fieldname']`), pass them individually: `x.add('fieldname', value)`
   - If endpoint expects a data array (like `$params['action']['data']`), use bracket notation: `x.add('data[fieldname]', value)`
   - Example: `propertyoffer_add` expects `data` as an array, so use `x.add('data[offercustomername]', value)` not `x.add('data', {offercustomername: value})`

2. **Check the component calling the action** to see what payload it actually sends:
   - Search for where `xapiActionName(payload)` is called or dispatched
   - Look at the payload structure being passed — what fields does it contain?
   - Match the payload field names exactly, don't assume field names
   - Example: `xapiPropertyRejectionOfferUpdate` is called with `payload.data` containing `emailid`, `emailsubject`, `emailfrom`, `rejection_text` — NOT `rejectionemailtemplate` or `rejectionemailsubject`

This is a large but straightforward refactoring that will improve API reliability and consistency across the app.

### Important: Before Removing postApi Imports

When removing the `import postApi from "..."` line from any file after converting all postApi calls:
1. **Always grep the entire file** for `postApi` to ensure no function calls remain
2. **Check all related files in the same feature folder** (e.g., all Communications/*.js files, all CRM/*.js files) before removing an import
3. Only remove the import when you've confirmed **zero remaining postApi references** in all related files
4. Use: `grep -r "postApi" /path/to/folder/ --include="*.js"` to verify the entire folder is clean

This prevents accidentally removing an import that other files in the same feature still depend on.

---

### Batch 1 Completion — June 9, 2026

**Status: ✅ COMPLETE and TESTED**

All four priority functions have been successfully converted from postApi to xapi and tested on dev:

| Function | Endpoint | Status |
|---|---|---|
| `xapiPropertyStatusUpdateForOffer` | `propertydetails_update` | ✅ Working |
| `xapiPropertyRejectionOfferUpdate` | `propertyofferrejectionemail_update` | ✅ Working |
| `xapiOffersNoteAdd` | `note_add` | ✅ Working |
| `xapiPropertyNoteAdd` | `note_add` | ✅ Working |

**Key Learnings:**
- Always check PHP endpoint's `$params` definitions to understand parameter structure
- For data arrays, use bracket notation: `x.add('data[fieldname]', value)` not `x.add('data', object)`
- **Critical:** Check the actual component that calls the action to see what payload fields it sends — don't assume field names. Example: rejection email settings send `emailid`, `emailsubject`, `emailfrom`, `rejection_text` (not `rejectionemailtemplate`)
- Console.log noise cleaned up: removed logging from Property.js, Input.js, SideNav.js

**Files Modified in Batch 1:**
- `src/containers/Property/actions.js` (5 functions converted)
- `src/containers/ConnectionCenter/CONSTANTS.js` (STATUS_OPTIONS reordered)
- `src/containers/Property/Property.js` (removed console.log)
- `src/components/Input/Input.js` (removed console.log)
- `src/containers/SideNav/SideNav.js` (removed console.log)
- `claude_RIO_REACT.md` (documentation updated)

**Batch 2 Completion — June 10, 2026**

**Status: ✅ COMPLETE and TESTED**

Converted 7 functions from postApi to xapi in Communications module:

| Function | Endpoint | File | Status |
|---|---|---|---|
| `signatureSave` | `communicationsignature_add` | actions.js | ✅ Working |
| `emailSend` | `memail_send` | actions.js | ✅ Working |
| `saveDraft` | `communicationdraft_save` | actions.js | ✅ Working |
| `addGoogleDocuments` | `attachgoogledocs_add` | AttachmentActions.js | ✅ Working |
| `attachMyDocumentsAdd` | `attachcompanymydocs_add` | AttachmentActions.js | ✅ Working |
| `attachLocalHD` | `attachlocalhd_add` | AttachmentActions.js | ✅ Working |
| `setDocumentForAttachment` | `attachriodocs_add` | AttachmentActions.js | ✅ Working |

**Key learnings:**
- When sending email objects with multiple fields, iterate with `Object.keys()` to build `data[fieldname]` parameters
- For data array parameters, use bracket notation: `x.add('data[fieldname]', value)`
- Response structure changed from `data.data.result` to `data.result` — adjust accordingly
- For attachments, response fields vary (some return `link`, `documentid`; others return different fields) — always check the PHP endpoint

**Files modified:**
- `src/containers/Communications/actions.js` (3 functions converted, postApi import removed)
- `src/containers/Communications/AttachmentActions.js` (4 functions converted, postApi import removed)
- `claude_RIO_React.md` (documentation updated, safety note added)

### Email Timestamp Timezone Handling — Fixed (Batch 2)

**Problem:** Email timestamps display in UTC instead of the user's local browser timezone (e.g., shows 4:05 PM when it's actually 10:06 AM).

**Design Pattern:** RIO stores all timestamps in UTC (no timezone). Each user's browser converts UTC to their local timezone when displaying. This works globally for any user in any timezone without storing per-user timezone preferences.

**Root Cause:** `riowww/xapi/memail_send.php` and `communications_get.php` pre-format the UTC timestamp before returning it to React. Once formatted as a string, timezone information is lost.
- Line 368-369 in `memail_send.php`: formats to `"June 10, 2026"` and `"04:05 PM"` (UTC times, no conversion)
- React can't convert already-formatted strings back to the user's local time

**Fix Strategy:**
1. **PHP Backend:** Return raw ISO UTC timestamps instead of pre-formatted strings
   - `memail_send.php`: Return raw `memailsentdate` (e.g., `"2026-06-10T14:05:00Z"`)
   - `communications_get.php`: Return raw timestamp for all email records
   - Remove the pre-formatted `date` and `time` fields from responses

2. **React Frontend:** Convert UTC to browser local time when displaying
   - `emailSend()` in `Communications/actions.js`: Expect and store raw `memailsentdate`
   - `EmailItem.js`: Use `moment.utc(memailsentdate).local().format('MMM D, YYYY')` and `.format('h:mm A')`
   - Pattern already proven in `OfferOverview.js` line 635 (offer notes)

**Affected xapi Endpoints (IDENTIFIED):**

1. **`memail_send.php` (lines 364-371)**
   - Current: Returns pre-formatted `date` and `time` strings (lines 368-369)
   - Fix: Return raw `memailsentdate` ISO timestamp instead
   - Change return from: `"date"=>$date,"time"=>$time`
   - Change return to: `"memailsentdate"=>$qry[0]['memailsentdate']`

2. **`communications_get.php` (lines 213-225)**
   - Current: Formats `memailcreateddate` to `date` and `time` strings (lines 213-214, 216, 225)
   - Fix: Return raw `memailcreateddate` ISO timestamp instead
   - Remove the date formatting logic
   - Add `memailcreateddate` to the return array
   - Remove `date` and `time` from the return array

**React Changes — ✅ IMPLEMENTED:**

1. **`Communications/actions.js` - `emailSend()` function**
   - ✅ Updated to expect `memailsentdate` instead of separate `date`/`time`
   - ✅ Store raw timestamp: `emailObj['memailsentdate'] = data.result.memailsentdate`

2. **`components/EmailItem/EmailItem.js`**
   - ✅ Added moment import
   - ✅ Converts raw timestamp using `moment.utc(timestamp).local()`
   - ✅ Displays formatted date (`MMM D, YYYY`) and time (`h:mm A`) in user's browser timezone

**PHP Changes — ✅ IMPLEMENTED:**

1. **`riowww/xapi/memail_send.php`** (lines 364-371)
   - ✅ Removed pre-formatted `date` and `time` fields
   - ✅ Returns raw `memailsentdate` ISO timestamp instead

2. **`riowww/xapi/communications_get.php`** (lines 211-225)
   - ✅ Removed pre-formatting logic
   - ✅ Returns raw `memailcreateddate` and `memailsentdate` timestamps
   - ✅ No longer returns pre-formatted `date` and `time` fields

### Batch 3 Completion — June 10, 2026

**Status: ✅ COMPLETE and TESTED**

Converted **16 functions** from postApi to xapi in CRM module (largest batch so far):

**Custom Fields & Relations (3 functions):**
| Function | Endpoint | Status |
|---|---|---|
| `xapiCrmContactCustomFieldUpdate` | `crmcontact_customfield_update` | ✅ Working |
| `xapiSaveNextContact` | `crmcontact_nextcontact_update` | ✅ Working |
| `xapiUpdateRelation` | `crmcontact_relation_update` | ✅ Working |

**Export/Import (5 functions):**
| Function | Endpoint | Status |
|---|---|---|
| `exportSelectedAsync` | `crmcontact_export` | ✅ Working |
| `importSelectedAsync` | `crmcontact_importfields_save` | ✅ Working |
| `crmContactUploadImport` | `crmcontact_import_upload` | ✅ Working |
| `xapiValidateImportFields` | `crmcontact_import_validate` | ✅ Working |
| `xapiFinalizeImportFields` | `crmcontact_import_finalize` | ✅ Working |

**Contacts (3 functions):**
| Function | Endpoint | Status |
|---|---|---|
| `updateContact` | `crmcontact_update` | ✅ Working |
| `addContact` | `crmcontact_add` | ✅ Working |
| `deleteCrmContacts` | `crmcontact_remove` | ✅ Working |

**Appointments (2 functions):**
| Function | Endpoint | Status |
|---|---|---|
| `addAppointment` | `crmcontact_appointment_add` | ✅ Working |
| `updateAppointment` | `crmcontact_appointment_update` | ✅ Working |

**Notes (2 functions):**
| Function | Endpoint | Status |
|---|---|---|
| `addNote` | `crmcontact_note_add` | ✅ Working |
| `updateNote` | `crmcontact_note_update` | ✅ Working |

**Relations (1 function):**
| Function | Endpoint | Status |
|---|---|---|
| `addRelation` | `crmcontact_relation_add` | ✅ Working |

**Key improvements:**
- All data parameters converted to bracket notation: `x.add('data[fieldname]', value)`
- Response handling updated from `response.data.result` to `data.result` structure
- Error handling improved with fallback messages
- Removed `checkDisplayError` call in `deleteCrmContacts` (deprecated, error handling now in .catch())
- Removed `console.log` from `updateContact` function

**Files modified:**
- `src/containers/CRM/actions.js` (16 functions converted, postApi import removed, checkDisplayError import still present)

**Batch 3 Hotfix — June 10, 2026 (addRelation PHP Bug)**

**Issue:** "Unexpected token '<'" error: `Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in crmcontact_relation_add.php:150`

**Root Cause:** PHP 8 compatibility bug in the backend. The `crmcontact_relation_add.php` endpoint never initialized the `$formerrors` array before the validation section. On line 150, when it tried `if (count($formerrors) > 0)`, the variable was null, causing a fatal error in PHP 8 (which doesn't allow `count(null)`).

**Fix Applied (PHP Backend):**
- Added `$formerrors = array();` at line 104 in `crmcontact_relation_add.php`
- Now the validation section properly initializes the errors array before checking for validation errors

**File Fixed:**
- `riowww/xapi/crmcontact_relation_add.php` (line 104) — initialized `$formerrors` array

**Note:** This is a backend PHP issue, not a React/xapi conversion issue. The conversion itself is correct — the endpoint had a pre-existing PHP 8 compatibility bug.

### Batch 3 UI Fix — Relation Display Formatting

**Issue:** Relation labels displayed in lowercase, showed "null" instead of blank for empty values.

**Fix Applied:**
- Added `formatValue()` method in `FriendsForm.js` to return blank string for null/undefined values
- Updated relation display labels to Title Case in `FriendsForm.js`:
  - "relation:" → "Relation:"
  - "birthday:" → "Birthday:"
  - "description:" → "Description:"
  - "resides with contact:" → "Resides With Contact:"
- Updated labels in `UserMediaItem.js`:
  - "email:" → "Email:"
  - "phone number:" → "Phone Number:"
- Applied `formatValue()` to all displayed values

**Files Modified:**
- `src/components/ContactViewer/forms/FriendsForm/FriendsForm.js` (lines 125-132, 268-273)
- `src/components/UserMediaItem/UserMediaItem.js` (lines 40-43)

### Batch 3 UI Fix — Date Input Value Format

**Issue:** When manually typing birthday year in relation edit modal, only last digit was accepted (typing "1980" resulted in "0001" then "0009").

**Root Cause:** The `value` prop for the birthday date input was not formatted in `YYYY-MM-DD` format required by HTML5 date inputs. While the `default` prop was correctly formatted, the `value` prop was passing the raw database value.

**Fix Applied:**
- Updated birthday Input in FriendsForm.js to format the `value` prop in `YYYY-MM-DD` format using moment
- Now both `default` and `value` props are consistently formatted: `moment(new Date(value)).format('YYYY-MM-DD')`

**File Modified:**
- `src/components/ContactViewer/forms/FriendsForm/FriendsForm.js` (lines 183-193)

---

## Batch 3: CRM Module postApi → xapi Refactoring — COMPLETED ✅

**Status:** Fully complete and tested on dev server.

**What Was Done:**
1. Converted 16 CRM functions from deprecated `postApi` to modern `xapi` utility
2. Fixed export serialization (bracket notation for objects)
3. Fixed file upload to remain on `postApi` (do NOT convert file uploads to xapi)
4. Fixed memory exhaustion in Excel import (empty row detection)
5. Implemented proper success/error messaging in import workflow
6. Fixed UI display issues in import results

**Functions Converted:**
- Contact CRUD: `addContact`, `updateContact`, `deleteCrmContacts`
- Relations: `addRelation`, `xapiUpdateRelation`
- Appointments: `addAppointment`, `updateAppointment`
- Notes: `addNote`, `updateNote`
- Custom Fields: `xapiCrmContactCustomFieldUpdate`
- Next Contact: `xapiSaveNextContact`
- Export/Import: `exportSelectedAsync`, `importSelectedAsync`, `crmContactUploadImport`, `xapiValidateImportFields`, `xapiFinalizeImportFields`

**Key Files Modified:**
- `src/containers/CRM/actions.js` — All 16 function conversions
- `src/containers/CRMContactImportComponent/` — Import UI fixes
- `riowww/_inc/functions_app.php` — Excel memory fix
- `riowww/xapi/crmcontact_*.php` — PHP 8 compatibility fixes
- `claude_RIO_React.md` — Updated API conventions to note file upload exception

**Testing:** All functions tested end-to-end on dev server with proper error handling and user feedback.

### Batch 4 Completion — June 11, 2026

**Status: ✅ COMPLETE and DEPLOYED to DEV**

Converted **11 functions** from postApi to xapi across Documents, Tasks, and Chat modules. Added error handling with user-facing messages to all functions.

**Documents/actions.js (6 functions converted):**
| Function | Endpoint | Error Message |
|---|---|---|
| `handleRequestSignature` | `document_isencrypted_get` | N/A (simple check) |
| `documentTagAdd` | `documenttag_add` | "Failed to add document tag. Please try again." |
| `xapiSaveEditDoc` | `document_update` | "Failed to update document. Please try again." |
| `xapiESignatureSavePlacement` | `esignatureplacement_save` | "Failed to save signature placements. Please try again." |
| `combineProcessing` | `documentcombine_processing` | "Failed to combine documents. Please try again." |
| `finalizeCombine` | `documentcombine_finalize` | "Failed to combine documents. Please try again." |

**Skipped (File Uploads - kept on postApi):**
- `createNewDocument` — file upload endpoint
- `xapiUploadChecklistItem` — file upload endpoint

**Tasks/actions.js (4 functions converted):**
| Function | Endpoint | Error Message |
|---|---|---|
| `completeTask` | `task_complete` | Already had error handling |
| `updateTask` | `task_update` | Already had error handling |
| `deleteTask` | `task_remove` | "Failed to delete task. Please try again." |
| `removeTaskRequirement` | `task_requirement_remove` | "Failed to remove task requirement. Please try again." |

**Removed:** postApi import (no longer needed)

**Chat/Chat.js (1 function converted):**
| Function | Endpoint | Error Message |
|---|---|---|
| `postMessage` | `chat_add` | "Failed to send message. Please try again." |

**Changes:**
- Removed postApi import
- Added `setMessage` import for error messaging

**Key Implementation Details:**

1. **Data Parameter Conversion:** All data objects converted to bracket notation for xapi:
   ```js
   Object.keys(dataObj).forEach(key => {
     x.add(`data[${key}]`, dataObj[key]);
   });
   ```

2. **Response Structure:** Updated from `data.data.result` to `data.result`

3. **Error Handling:** All functions now dispatch user-facing error messages preventing silent failures

**Bug Fix Discovered:** postApi was not passing all parameters correctly. xapi conversion with bracket notation passes parameters completely, fixing PDF combine issue.

**Files Modified:**
- `src/containers/Documents/actions.js`
- `src/containers/Tasks/actions.js`
- `src/containers/Chat/Chat.js`

**Testing on Dev:** All functions tested. PDF combine now correctly passes all parameters.

**Production Deployment:** ✅ DEPLOYED and TESTED. All 11 functions working. Deployment issue resolved (vendor dependencies sync across AWS servers).

**Status: Ready for Batch 5** ✅

### Calendars Module Completion — July 14, 2026

**Status: ✅ CONVERTED — pending dev test**

Converted the 3 remaining `postApi` functions in `components/Calendars/actions.js` (this was originally scoped under Batch 4 as "Documents, Tasks, Calendar, Chat" but was missed — Batch 4 completion only covered Documents/Tasks/Chat).

| Function | Endpoint | Notes |
|---|---|---|
| `xapiGetCalenderEventList` | `calendareventlist_get` | Filters passed via `filters[key]` bracket notation |
| `xapiAddEvent` | `calendarevent_add` | Event fields passed via `eventdata[key]` bracket notation |
| `xapiUpdateEvent` | `calendarevent_update` | `eventid`/`eventtype` as top-level params, event fields via `eventdata[key]` |

**Key details:**
- Verified param/response shapes directly against the PHP endpoints (`riowww/xapi/calendareventlist_get.php`, `calendarevent_add.php`, `calendarevent_update.php`) and against `components/Calendars/reducer.js` (`SAVE_CALENDER_EVENTS_LIST` expects `payload.data`).
- Validation errors surface at `data.error.validatekeyvalue` (top-level `error` key from `xapi_errorhandler`); success data is under `data.result` — same shape already used by the untouched `xapiCalendarEventGet`/`xapiDeleteCalendarEvent` in the same file.
- Removed the `postApi` import (confirmed zero remaining `postApi` references in the `Calendars` folder) and two stray `debugger;` statements.
- Added `setMessage('alert-danger', ...)` user-facing error dispatches on all three functions' `.catch()` blocks, matching the Batch 1–4 pattern.

**Files Modified:**
- `src/components/Calendars/actions.js`

**Not yet tested on dev** — verify calendar event list load, add, and update (including Google/Outlook auto-sync dispatches) before marking complete.

---

## Batch 5 Completion — July 14, 2026

**Status: ✅ ALL CONVERTED — pending dev test**

Batch 5 ("everything else") is complete: all named modules, the ~30-file long-tail, and a bonus discovery in `Property/actions.js` that was never covered by any prior batch. Every remaining `postApi` reference in the codebase is now either a documented file-upload exception, documented dead code, or a harmless comment — see "Remaining `postApi` Usage — Final State" below.

### Conversion Pattern Used

Every conversion followed the established batch pattern:
- `new xapi(action)` + `x.add(param, value)` for flat params; `x.add('key[subkey]', value)` bracket notation for nested/array PHP params (verified against each endpoint's `$params`/`$optional` declarations before writing the call)
- Response reading shifted from postApi's `response.data.result.X` wrapping to plain `data.result.X` (since `xapi.fetch()` + `resp.json()` returns the JSON body directly)
- `checkDisplayError`/`checkDisplayErrorAlt` calls updated the same way — both utilities already unwrap `dataObj.data || dataObj`, so passing the plain body works without changes to the utilities themselves
- Stray `debugger;` statements and dead/unused imports (`postApi`, sometimes `axios`) removed from each touched file once verified zero remaining call sites

Two bugs were caught and fixed as an inherent side effect of correctly translating the response shape (not scope creep):
- **Roles module**: a variable-shadowing bug where naming the response callback `data` shadowed an outer `data` parameter in `xapiPermissionUpdate` — caught before shipping, response variable renamed to `respData`
- **EmailTemplates**: `removeTemplate` was hardcoded to `http://dev.feb.rio.matraex.com/xapi.php` — converting to the `xapi` class fixed this, since it resolves the request origin dynamically like every other call in the app

### Modules Converted

| Module | Functions | Notes |
|---|---|---|
| TextMessage | 1 | `xapiTextMessagePost` |
| Support | 1 | `analyticPost` (fire-and-forget analytics logging) |
| Clients | 2 | `crmContactClientAdd`, `crmContactClientUpdate` |
| PropertyAttributes | 3 | assignment update, type options get, attribute delete |
| PropertyList | 6 of 7 | export/import flow + add property + MAT options; `propertyUploadImport` (file upload) intentionally left on `postApi` |
| Roles | 5 | permission update, HOA add/update/document-add, note update |
| Reporting | 5 | performance get/export, report export/run, memo save; also removed a second dead `postApi` import in `ReportControls.js` |
| Users | 6 of ~11 | permission update, add/save user, import fields save/validate/finalize; 5 functions found dead (orphaned duplicate import flow, only reachable via unrouted `TestComponent.js`) left on `postApi` with an explanatory comment; file upload (`xapiUserUploadImport`) also left on `postApi` |
| Workflow | 4 | trigger select-update, add, remove, save |
| Campaigns | 5 | campaign add/update, action inactivate/add, category add |
| **Vault payment flow** (`Property/actions.js`) | 3 | `xapiVaultPaymentFieldsGet`, `xapiProcessVaultPayment` (**real credit-card charge**), `xapiVaultArchiveSet` — high priority, test carefully |
| Chatbot | 1 | `dynamicSlotPost` — dynamic `bot_*` action name, verified all 5 possible actions share the same `data[key]` shape |
| DocumentChecklist | 4 | checklist add/update, checklist-item add/update |
| AgentStaffSettings | 1 | `xapiAgentStaffSettingsUpdate` — also simplified away manual URL-string building |
| Camera + CameraPropertyAlbum | 1 | `photoAdd` in `Camera.js` — introduced a recursive `addNestedParams` helper for the multi-upload case; `CameraPropertyAlbum.js` needed no conversion (dead import only) |
| CompanyCrmTags | 2 | tag add/edit |
| CrmContactTabs (4 files) | 4 | tab add, custom-field add/update, tab update ×2 |
| DetailsTab | 4 | document update, field update, MAT options get, MAT options run |
| DocumentComplianceSettings | 1 | settings save |
| EmailTemplates | 4 | template add/update/remove, category add |
| FinancialTab | 3 | financial state save, CDA send, CDA buyer add |
| MyProfile (+ QBConnector) | 5 | profile update, profile document add/edit, **general payment processing** (real CC charge, same care as vault payment), QuickBooks connector update; 1 function found dead (`xapiProfileDocumentCategoryAdd` — points at a nonexistent endpoint) left on `postApi` with an explanatory comment |
| OfferEmailTemplate | 1 | template update |
| OfficesAndTeams | 6 | office add/update, team inactivate/update/add, staff remove |
| OpenHouseList | 2 | open house add/update |
| OtherIncomeList | 2 | income add/update |
| Projection | 1 | `generateChart` (same `bot_mycommissiongraph_get` endpoint as Chatbot) |
| RecruitingSettings | 1 | settings save |
| Tasks.js | 1 | `task_add` — file previously had no `xapi` import at all; added it |
| VendorNetwork | 2 | vendor invite validate/send |
| **Property/actions.js (bonus find)** | 9 | photo add/remove/multi-remove/zip/edit/addendum, social media post, property assignment add, vendor list search — never covered by any prior batch; added a reusable recursive `addNestedParams(x, prefix, obj)` helper for arbitrary-depth nested payloads (`infotopost`, `form.filterby`) |
| DashboardPropertyList, CRMContactHistoryList, FutureProjection, PropertyHistoryList | 0 | No actual `postApi` call sites — dead imports only, removed |
| Expenses (`Expenses.js`, `actions.js`) | 0 | No actual `postApi` usage — both matches were inside comments |

### Remaining `postApi` Usage — Final State

| File | Why it's still there |
|---|---|
| `CRM/actions.js` | File upload (documented, Batch 3) |
| `Documents/actions.js` | File upload (documented, Batch 4) |
| `PropertyList/actions.js` | `propertyUploadImport` — file upload |
| `Users/actions.js` | `xapiUserUploadImport` (file upload) + 5 dead orphaned functions (commented, explained above) |
| `MyProfile/actions.js` | `xapiProfileDocumentCategoryAdd` — dead code, nonexistent endpoint, commented |
| `Property/actions.js` | Dead duplicate `xapiOfferFieldUpdate` wrapped in a `/* */` block — the live version below it already uses `xapi` |
| `Tasks/Tasks.js` | Dead duplicate `task_add` call wrapped in a `//` comment block |
| `VaultList/actions.js` | `xapiVaultUpdate` — dead code, nonexistent endpoint (`vault_update`), commented (see below) |
| `CompanyCrmTags/actions.js`, `Expenses/Expenses.js`, `Expenses/actions.js` | Comment-only text mentioning `postApi`, no import, no live call |
| `utilityFunctions/postApi.js` | The utility itself, not a caller |

### VaultList Dead Code (found before Batch 5 conversion work, resolved via comments)

`xapiVaultUpdate` in `containers/VaultList/actions.js` and `editVault()` in `VaultDetails.js` reference a nonexistent `vault_update` endpoint and are not wired to any UI element. The real, live vault extend/restore/archive flow is the 3 functions converted above in `Property/actions.js`. Both dead-code spots have explanatory comments pointing to the real implementation — see the "Vault payment flow" row above for what was actually converted.

### Dev Testing Checklist

Work through each module and confirm the corresponding UI flow still works end-to-end:

- [ ] **Calendars** — load calendar event list; add an event; update an event (check Google/Outlook auto-sync fires)
- [ ] **TextMessage** — send a text message; confirm history refreshes
- [ ] **Vault payment flow** (HIGH PRIORITY — real CC charge) — extend vault storage (`action=upgrade`) with a test card; restore from vault with a test card; initial add-to-vault archive flow
- [ ] **Support** — open a support video and a support document; confirm no console errors
- [ ] **Clients** — add a new client; edit an existing client's fields and save
- [ ] **PropertyAttributes** — assign/unassign a property attribute to a broker type; open "add attribute" type-options fields for each field type; delete a custom attribute
- [ ] **PropertyList** — export selected properties; run the full property import flow (upload → validate → finalize); add a new property (including MAT options); confirm validation errors still display
- [ ] **Roles** — edit a role's permissions and save; add/edit an HOA record; attach a document to an HOA; update a generic note (Notes.js and NotesOverview.js)
- [ ] **Reporting** — run a report; export a report; save a customized/memorized report; run and export the performance report view
- [ ] **Users** — add a new user (with and without profile photo); edit/save an existing user; update a user's permission group; run the full user import flow
- [ ] **Workflow** — update a trigger's select/staff assignment; add/remove/edit a master task trigger
- [ ] **Campaigns** — create a campaign (with tags); edit a campaign; add/remove a campaign action; add a category
- [ ] **Chatbot** — try each prompt: property list, commission graph, closing properties, projections, task list
- [ ] **DocumentChecklist** — checklist and checklist-item CRUD
- [ ] **AgentStaffSettings** — save agent/staff email settings
- [ ] **Camera** — phone camera photo upload and add to album
- [ ] **CompanyCrmTags** — add/edit a company CRM tag
- [ ] **CrmContactTabs** — add a tab; add a custom field; edit a tab
- [ ] **DetailsTab** — property field edit; property document upload; MAT options
- [ ] **DocumentComplianceSettings** — save settings
- [ ] **EmailTemplates** — add/edit/remove an email template; add a category
- [ ] **FinancialTab** — save financials; send a CDA; add a CDA buyer
- [ ] **MyProfile** — update profile; add/edit a profile document; **general company payment/billing with a test card (HIGH PRIORITY)**; QuickBooks connector update
- [ ] **OfferEmailTemplate** — save the offer email template
- [ ] **OfficesAndTeams** — add/edit/delete an office; add/edit/delete a team; remove staff from a team
- [ ] **OpenHouseList** — add/update an open house
- [ ] **OtherIncomeList** — add/update other income
- [ ] **Projection** — generate the commission graph chart
- [ ] **RecruitingSettings** — save settings
- [ ] **Tasks.js** — add a task from the Tasks list page
- [ ] **VendorNetwork** — validate and send a vendor invite
- [ ] **Property/actions.js (bonus find)** — post to social media; add a property assignment; remove a single photo; remove multiple photos; add a photo; search the assignee vendor list with filters; zip-download a photo group; edit a photo's showcase/title/description; create a photo addendum
