# 📋 Lead Notes Button — GHL Chrome Extension Script

A **GoHighLevel Chrome Extension script** that injects a **"Lead Notes"** button into the GHL conversation and contact detail pages. When clicked, it opens a structured modal displaying the current contact's key details alongside a categorized grid of lead disposition buttons — allowing agents to quickly log a lead's status, tag them, and add notes without leaving the conversation view.

---

## 📁 Project Structure

```
extension/
└── lead_notes_general.js    # Self-contained IIFE-style script injected into GHL
```

> This script runs as a content script inside the GoHighLevel web app via a Chrome Extension. It hooks into GHL's SPA navigation events and injects UI elements directly into the live DOM.

---

## ⚙️ How It Works — Overview

```
Page Load / Route Change
         │
         ▼
appendLeadNotesButtongenereal()
         │
         ├─ Check if URL is conversations or contacts/detail
         ├─ Wait for #composer-textarea to appear in DOM (polls every 300ms)
         ├─ Check Location ID
         │   Blocked:  Yjkpt82b8Vqjm10Ir9tD → skip entirely
         │   Skipped:  ynCWf3Y4AEJ7hfQ4lVWE → button not injected
         │   All others → inject "Lead Notes" button above the composer
         │
         ▼
Agent clicks "Lead Notes"
         │
         ▼
modal_genereal_endchat()
         │
         ├─ Detect context (conversation page or contact detail page)
         ├─ Extract contactId from URL
         ├─ Fetch in parallel:
         │   ├─ Custom Fields schema (to find milestone + setter fields)
         │   ├─ Contact details (name, tags, assignedTo, customFields)
         │   └─ Assigned user details (name)
         │
         ├─ Build sidebar: name, created date, assigned user, source,
         │                  milestone stage, setter contact status, tags
         │
         └─ Build 2×2 disposition grid:
             Lead | Scheduled
             Conversation | Application
             (with location-specific extras merged in)
```

---

## 🧩 Features

### 1. Context-Aware Button Injection

The script injects a **"Lead Notes"** link element directly above the GHL conversation composer. It:
- Only runs on `/conversations/conversations` and `/contacts/detail/` pages
- Polls every 300ms for `#composer-textarea` to appear (handles GHL's lazy DOM rendering)
- Checks the Location ID and **blocks injection** for excluded locations
- Deduplicates — if `#endc-tab` already exists, it does not inject again
- Listens to GHL's `routeChangeEvent` (fired on SPA navigation) and re-injects after a 500ms delay
- Also runs once on initial page load with a 1-second delay

**Location exclusions:**

| Location ID | Behavior |
|---|---|
| `Yjkpt82b8Vqjm10Ir9tD` | Completely excluded — button never shown, modal never opened |
| `ynCWf3Y4AEJ7hfQ4lVWE` | Button not injected (Media Simplified's own account) |

---

### 2. Lead Notes Modal

When the agent clicks "Lead Notes", `modal_genereal_endchat()` runs and builds a two-column layout inside the existing `.modal_endchat` element:

**Left sidebar (190px)** — Contact snapshot:
- Avatar (initials, blue circle)
- Full name with a direct link to the contact detail page
- Created date (formatted as "January 3rd 2025")
- Assigned user name
- Lead source / attribution medium
- Current milestone stage (read from a custom field named `"Green lead note button action"`)
- Setter contact status (read from custom field `"Should the setters/ rovers be contacting this lead..."`)
- All current tags (comma-separated)

**Right section** — 2×2 disposition grid with four categories:

| Category | Purpose |
|---|---|
| **Lead** | Initial lead qualification outcomes |
| **Scheduled** | Appointment booking and no-show tracking |
| **Conversation** | Post-call status updates |
| **Application** | Loan application lifecycle stages |

Each button in the grid:
- Has a color class (e.g., `green-white`, `dark-grey-red`) for visual status coding
- Carries one or more **GHL tags** in `data-tags` for automation triggers
- Shows a **tooltip help icon** (blue circle with `?`) explaining when to use that disposition

---

### 3. Disposition Button Sets

#### Lead
| Button | Tags Applied | When to Use |
|---|---|---|
| Not Interested | `not-interested, ni` | Lead declined moving forward |
| Bad Phone | `bad number, DND` | Invalid or unreachable number |
| Hot | `hot` | Engaged, high-priority lead |
| Not Ready Yet | `future-followup` | Interested but not applying now |

#### Scheduled
| Button | Tags Applied | When to Use |
|---|---|---|
| Book Appointment | `scheduled` | Appointment successfully set |
| Appointment No-show | `no-show` | Lead missed scheduled call |
| Cancel Appointment | `appt_canceled` | Appointment canceled by either party |

#### Conversation
| Button | Tags Applied | When to Use |
|---|---|---|
| Spoke With: Waiting on App | `client-handling` | Conversation done, waiting for application |
| Review / Update Note | `Review` | Adding context after a call |
| No App Yet - Lost Contact | `not-include-tag` | Lead went unresponsive, no application |
| Add to 'Rate-Watch' | `Rate-watch` | Lead wants to monitor rates first |
| Does Not Qualify | `does-not-qualify` | Lead fails program requirements |

#### Application
| Button | Tags Applied | When to Use |
|---|---|---|
| App taken: Waiting on Docs | `official-app-complete, waiting-on-docs` | App active, docs outstanding |
| App taken - Lost Contact | `official-app-complete, lost-contact` | App started, lead went cold |
| Pre/Approved | `official-app-complete, pre-approved` | Conditionally approved |
| Closed Deal | `closed` | Loan funded and closed |
| App Declined / Closed App | `official-app-complete, does-not-qualify` | Application rejected or closed |

---

### 4. Location-Specific Customizations

#### `ILDYz632fBNyX2gaAvpy` — Provisor (extra Application buttons added)
| Button | Tags Applied |
|---|---|
| Phone Application Taken | `phone-app-taken` |
| Send Credit Report Authorization | `phone-app-taken` |

#### `0auCGbGL9boekEe2iaYu` — Custom label overrides
| Original Label | Replaced With |
|---|---|
| "Spoke With: Waiting on App" | "Waiting on application" |
| "App taken: Waiting on Docs" | "Waiting on docs" |

Customization is applied at render time — the base `allClientFaqData` object is mutated for the active location before the grid is built.

---

### 5. Additional Disposition Note

Below the grid, an **"Additional Disposition"** section is always shown. Agents can type a freeform note and submit it — the note is posted to the contact's note feed via `POST /contacts/{contactId}/notes/` prefixed with `-->Additional Disposition -`.

Empty lines are stripped before submission. Clicking Cancel closes the modal.

---

## 🔌 GHL API Calls

All API calls use the GHL internal token obtained from `window.getToken()` and target `services.leadconnectorhq.com`.

| Function | Endpoint | Purpose |
|---|---|---|
| `getContactDetails(contactId)` | `GET /contacts/{id}` | Full contact record including custom fields and tags |
| `getConversationDetails(conversationId)` | `GET /conversations/{id}` | Extracts `contactId` from a conversation URL |
| `getCustomFields()` | `GET /locations/{locationId}/customFields` | Fetches field schema to locate milestone and setter field IDs |
| `getAssignedUser(userId)` | `GET /users/{id}` | Gets assigned rep's name |
| `getPipeline()` | `GET /opportunities/pipelines?locationId=...` | Pipeline lookup (available, currently unused in render) |
| `getCustomValues()` | `GET /locations/{locationId}/customValues/search` | Custom value lookup (available for extension) |
| `updateCustomField(contactId, fieldId, value)` | `PUT /contacts/{id}` | Updates a single custom field on the contact |
| `get_data_api(uri, method, data)` | Generic GHL wrapper | Used for posting notes and other generic API calls |
| `getuserdetails()` | `GET /locations/{locationId}` | Fetches location metadata |

---

## 🧰 Utility Functions

| Function | Description |
|---|---|
| `formatDate(dateStr)` | Converts ISO date string to human-readable format ("January 3rd 2025") with correct ordinal suffixes |
| `resetReasonList()` | Clears the `.reason_list` container and removes inline styles before rebuilding |
| `createOpportunityOrStage(uri, method, data)` | Creates or updates a GHL opportunity — returns `null` on failure instead of throwing |
| `submitSendContractForm(userData, captchaToken, formId)` | Submits a GHL form with reCAPTCHA Enterprise v3 token (15-second timeout) |
| `generateCaptchaTokenForSendContract(siteKey)` | Generates a fresh reCAPTCHA Enterprise token using `grecaptcha.enterprise.execute()` |

---

## 🗂️ Key Data Structure — `allClientFaqData`

```javascript
const allClientFaqData = {
  "Lead": [
    {
      text: "Button Label",          // Displayed text on the button
      color: "green-white",          // CSS class for button color styling
      tags: ["tag1", "tag2"],        // GHL tags applied when this button is clicked
      help: "Tooltip description"    // Shown in the help icon tooltip
    },
    // ...
  ],
  "Scheduled": [ /* ... */ ],
  "Conversation": [ /* ... */ ],
  "Application": [ /* ... */ ],
};
```

Location-specific overrides are merged into this object before rendering using `Object.assign` / spread — the base data is never permanently mutated across modal opens.

---

## 📍 Contact ID Resolution

The modal resolves the contact ID differently depending on which GHL page the agent is on:

```
URL contains /contacts/detail/
    → Extract contactId directly from URL path via regex

URL contains /conversations/
    → Extract conversationId from URL path
    → Call GET /conversations/{conversationId}
    → Read contactId from response
```

---

## 🛡️ Error Handling & Safety

- All GHL API calls use `.catch(() => {})` — failures are silent to prevent the modal from crashing
- `createOpportunityOrStage` returns `null` on any error rather than throwing
- `formatDate` returns an empty string for invalid or missing date strings
- `modal_genereal_endchat` wraps its entire body in a `try/catch` and logs errors to console
- `appendLeadNotesButtongenereal` exits early if the target DOM element is not found and retries via `setTimeout`
- The `#additional-disposition-submit` click handler is re-bound with `$("body").off(...).on(...)` on each modal open to prevent duplicate event listeners accumulating across opens

---

## ⚡ Initialization Sequence

```javascript
// 1. Immediate call on script load (with 1s delay for DOM readiness)
setTimeout(() => {
  if (locationid !== "Yjkpt82b8Vqjm10Ir9tD") {
    appendLeadNotesButtongenereal();
  }
}, 1000);

// 2. Re-inject on every GHL SPA route change (with 500ms delay)
window.addEventListener("routeChangeEvent", function (e) {
  if (locationid !== "Yjkpt82b8Vqjm10Ir9tD") {
    setTimeout(appendLeadNotesButtongenereal, 500);
  }
});