# OpsHeaven Widget Suite — Developer Integration Guide

> **Version:** 2.0 — Last updated March 2026
>
> This is the **complete reference** for integrating the OpsHeaven widget suite into any web project. Follow the steps in order. If something is unclear, the Troubleshooting section at the end covers every common issue.

---

## Table of Contents

1. [What You Get](#1-what-you-get)
2. [Prerequisites](#2-prerequisites)
3. [Quick Start (Recommended — single script tag)](#3-quick-start)
4. [Configuration Reference](#4-configuration-reference)
5. [Authentication & Role Detection](#5-authentication--role-detection)
6. [End-User "Report a Problem" Widget](#6-end-user-report-a-problem-widget)
7. [Preventing Button Flash (SPA Best Practice)](#7-preventing-button-flash)
8. [Alternative Integration Methods](#8-alternative-integration-methods)
9. [Backend Requirements](#9-backend-requirements)
10. [Testing Checklist](#10-testing-checklist)
11. [Troubleshooting](#11-troubleshooting)
12. [Quick Reference Card](#12-quick-reference-card)

---

## 1. What You Get

The widget suite injects the following into your app — no npm install, no build step, just script tags:

| Widget | Who sees it | What it does |
|--------|-------------|--------------|
| **Single Request** (`singleRequest.js`) | Admin users | "Report an Issue" button + modal form that submits to the API |
| **End-User Report** (`endUserDiv.js`) | Non-admin users | "Report a problem" card + modal (see [Section 6](#6-end-user-report-a-problem-widget)) |
| **Cloud FAB** (`cloudFAB.js`) | Admin users | Floating Action Button (bottom-right circle → cloud menu with "Submit an issue") |

> **Removed / disabled widgets:**
> - ~~**Client Dashboard** (`client-dashboard.js`)~~ — migrated to the OTM platform as a native React page. No longer part of injection.
> - ~~**Multi Requests** (`multiRequests.js`)~~ — disabled; feature not currently in use. Code is commented out in the file and the loader skips it.

**How roles work:**
- **Admin** → sees the FAB (cloud button), which opens Single Request.
- **Non-admin** (any authenticated user whose role is not "admin") → sees the "Report a problem" card (if the mount point is present).
- **Not logged in** → nothing is shown.

---

## 2. Prerequisites

Before integrating, confirm these are in place:

### 2.1 You need from us

| Item | Value |
|------|-------|
| **Script server URL** | `https://injection.ohtplayground.com/` |
| **API key** | `otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728` |

> Replace these if your environment uses different values.

### 2.2 Your app must

1. **Store a JWT** after login in one of these locations:
   - `localStorage.token`
   - `localStorage.access_token`
   - `sessionStorage.token`

2. **Have user email available** via one of:
   - The JWT payload (field: `email`)
   - A stored user object in `localStorage` or `sessionStorage` under any of these keys: `user_data`, `user`, `authUser`, `auth`, `profile`, `currentUser`, `userData`, `auth_user`, `me` — with an `email` field.

3. **Have user role available** (for FAB visibility) via one of:
   - The JWT payload (field: `role`)
   - A stored user object with `role`, `user.role`, `data.role`, or `role_name`
   - A stored user object with `role_id` (where `role_id === 1` = admin)

> **No backend or JWT changes are required.** The widgets read whatever your app already stores.

---

## 3. Quick Start

**Recommended approach:** One script tag using `client.js`. Add this just before `</body>` in your HTML:

```html
<script src="https://injection.ohtplayground.com/client.js?v=250225"
        data-prefix="MYAPP"
        singleRequest="true"
        endUserDiv="true"
        cloudFAB="true"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728">
</script>
```

**Replace `MYAPP`** with your project's short name (e.g. `HT`, `BAITAK`, `CRM`). This prefixes all injected IDs and classes so they don't clash with your CSS.

That's it. On next page load:
- **Admin users** see the blue + FAB (bottom-right).
- **Non-admin users** see the "Report a problem" card (if you add the mount point — see [Section 6](#6-end-user-report-a-problem-widget)).
- **Logged-out users** see nothing.

---

## 4. Configuration Reference

### 4.1 client.js Attributes

| Attribute | Type | Required | Description |
|-----------|------|----------|-------------|
| `api_key` | string | **Yes** | Your API key for the integration backend |
| `singleRequest` | `"true"` / `"false"` | No | Load the "Report an Issue" modal (admin) |
| `endUserDiv` | `"true"` / `"false"` | No | Load the "Report a problem" card (non-admin; requires mount point) |
| `cloudFAB` | `"true"` / `"false"` | No | Load the Floating Action Button (admin) |
| `data-prefix` | string | No | Namespace prefix for all injected IDs/classes (e.g. `MYAPP`) |

> **Deprecated attributes (ignored):** `client-dashboard`, `dashboard_url`, `multiRequests` — these are no longer active. They can be safely removed from existing script tags.

### 4.2 Which widgets to enable

**Typical setup (all active features):**
```
singleRequest="true" endUserDiv="true" cloudFAB="true"
```

**Admin-only (no end-user report):**
```
singleRequest="true" endUserDiv="false" cloudFAB="true"
```

**End-user report only (no admin FAB):**
```
singleRequest="false" endUserDiv="true" cloudFAB="false"
```

### 4.3 Namespacing (data-prefix)

When `data-prefix="MYAPP"` is set, every injected element gets prefixed IDs and classes:

| Without prefix | With `data-prefix="MYAPP"` |
|----------------|----------------------------|
| `#fw-btn` | `#MYAPP-fw-btn` |
| `.cd-open-btn` | `.MYAPP-cd-open-btn` |
| `#multi-overlay` | `#MYAPP-multi-overlay` |
| `.oht-fab` | `.MYAPP-oht-fab` |

**Always use a prefix** to avoid CSS conflicts with your app.

---

## 5. Authentication & Role Detection

### 5.1 How the widgets find the user

The widgets check these sources **in order** (first match wins):

**Token:**
1. `localStorage.token`
2. `localStorage.access_token`
3. `sessionStorage.token`

**Email and name** (for request submission):
1. JWT payload fields: `email`, `name`, `full_name`, `username`
2. Stored user object fields: `email`, `name`, `full_name`, `first_name` + `last_name`, `username`

**Role** (admin vs non-admin):
1. Stored user object: `role`, `user.role`, `data.role`, `role_name`
2. Stored user object: `role_id` (value `1` = admin)
3. JWT payload: `role`, `user.role`

**Stored user object keys scanned:** `user_data`, `user`, `authUser`, `auth`, `profile`, `currentUser`, `userData`, `auth_user`, `me`

### 5.2 What your app needs to do

**On login:** Store the JWT and (optionally) a user object. Example:
```javascript
localStorage.setItem('token', response.access_token);
localStorage.setItem('user_data', JSON.stringify(response.user));
```

**On logout:** Clear them. Example:
```javascript
localStorage.removeItem('token');
localStorage.removeItem('user_data');
```

The widgets poll every 250–500ms and react to `storage` events, so they update automatically when auth changes.

> **Tip:** If your app dispatches a custom event on logout (e.g. `window.dispatchEvent(new Event('user_data_updated'))`), cloudFAB listens for it and tears down immediately.

---

## 6. End-User "Report a Problem" Widget

The **endUserDiv** widget is different from the others: it does **not** create its own floating button. Instead, it needs a **mount point** — an empty div in your HTML where it will inject the "Report a problem" card and modal.

### 6.1 How to add it

Place this div **wherever you want the "Report a problem" card** to appear:

```html
<div id="client-feedback-widget"></div>
```

> The id is always `client-feedback-widget` (no prefix). The widget looks for this exact id.

### 6.2 Who sees it

- **Non-admin** (any authenticated user whose role is not "admin") → the card appears.
- **Admin** → the card is hidden (admins use the FAB instead).
- **Not logged in** → the card is hidden.

### 6.3 Examples

**In a static HTML page:**
```html
<div class="sidebar">
  <div id="client-feedback-widget"></div>
</div>
```

**In a React component:**
```jsx
function SettingsPage() {
  return (
    <div className="settings-container">
      <div id="client-feedback-widget" className="mb-6" />
      {/* ... rest of settings ... */}
    </div>
  );
}
```

**Conditionally (only for non-admins in React):**
```jsx
function SettingsPage() {
  const { currentUser } = useAuth();
  const isAdmin = currentUser?.role === 'admin';

  return (
    <div>
      {!isAdmin && <div id="client-feedback-widget" className="mb-6" />}
      {/* ... */}
    </div>
  );
}
```

### 6.4 If you DON'T want the end-user widget

Set `endUserDiv="false"` in the client.js attributes and don't add the mount div. Nothing will be injected.

---

## 7. Preventing Button Flash

When `cloudFAB` is enabled, there can be a brief flash of legacy buttons before the FAB replaces them. To prevent this, add this **inline script BEFORE** the client.js tag:

```html
<!-- Prevents flash of legacy widget buttons before FAB loads -->
<script>
  (function () {
    var id = 'oht-legacy-hide';
    if (document.getElementById(id)) return;
    var pre = 'MYAPP-';  // ← Must match your data-prefix + hyphen
    var s = document.createElement('style');
    s.id = id;
    s.textContent = '#' + pre + 'fw-btn, .' + pre + 'cd-open-btn, #' + pre + 'openPopupBtn, .' + pre + 'floating-btn { display:none!important; visibility:hidden!important; }';
    (document.head || document.documentElement).appendChild(s);
  })();
</script>
```

> **Replace `MYAPP-`** with your actual prefix + hyphen. If you don't use a prefix, use the unprefixed selectors: `#fw-btn, .cd-open-btn, #openPopupBtn, .floating-btn`.

### Complete HTML example (with flash prevention):

```html
<!-- 1. Flash prevention (inline, runs immediately) -->
<script>
  (function () {
    var id = 'oht-legacy-hide';
    if (document.getElementById(id)) return;
    var pre = 'MYAPP-';
    var s = document.createElement('style');
    s.id = id;
    s.textContent = '#' + pre + 'fw-btn, .' + pre + 'cd-open-btn, #' + pre + 'openPopupBtn, .' + pre + 'floating-btn { display:none!important; visibility:hidden!important; }';
    (document.head || document.documentElement).appendChild(s);
  })();
</script>

<!-- 2. Widget loader -->
<script src="https://injection.ohtplayground.com/client.js?v=250225"
        data-prefix="MYAPP"
        singleRequest="true"
        client-dashboard="true"
        multiRequests="true"
        endUserDiv="true"
        cloudFAB="true"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"
        dashboard_url="https://injection.ohtplayground.com/client-dashboard.html">
</script>
```

---

## 8. Alternative Integration Methods

### Method A: Five separate script tags

If you prefer explicit control, add these in **exact order** before `</body>`:

```html
<script src="https://injection.ohtplayground.com/singleRequest.js?v=250225"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"></script>

<script src="https://injection.ohtplayground.com/client-dashboard.js?v=250225"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"
        dashboard_url="https://injection.ohtplayground.com/client-dashboard.html"></script>

<script src="https://injection.ohtplayground.com/multiRequests.js?v=250225"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"></script>

<script src="https://injection.ohtplayground.com/endUserDiv.js?v=250225"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"></script>

<script src="https://injection.ohtplayground.com/cloudFAB.js?v=250225"></script>
```

> **Note:** This method does NOT support `data-prefix` namespacing. Use the client.js method (Section 3) if you need namespacing.

### Method B: Dynamic loader

Host `loader.js` on your server or use inline:

```html
<script src="https://injection.ohtplayground.com/loader.js?v=250225"></script>
```

See the loader.js source for customization. This also does NOT support `data-prefix`.

---

## 9. Backend Requirements

The widget scripts submit requests to a backend API. Ensure these are configured:

### 9.1 Integration API

| Setting | Value |
|---------|-------|
| **Endpoint** | `POST /api/integration/requests?api_key=<API_KEY>` |
| **Auth header** | `Authorization: Bearer <JWT>` (sent by widget from stored token) |
| **Content-Type** | `multipart/form-data` (FormData with optional file uploads) |

**Request body fields:**

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `requester_type` | string | Yes | `"client"` or `"end_user"` |
| `requester_email` | string | Yes | From JWT or stored user |
| `requester_name` | string | Yes | From JWT or stored user |
| `title` | string | Yes | Issue title |
| `description` | string | Yes | Issue description |
| `request_type` | string | Yes | e.g. `"bug"` |
| `urgency` | string | Yes | `"low"`, `"medium"`, `"high"` |
| `page_name` | string/array | Yes | Selected page(s) |
| `page_url` | string | **No** | Current page URL. **Must be optional** so localhost works |
| `files` / `attachments` | file(s) | No | Optional file upload |

**Response format:**
```json
{ "success": true, "data": { "request_id": "REQ-1234" } }
```
or on error:
```json
{ "success": false, "message": "Validation failed", "errors": [...] }
```

### 9.2 Batch API (multiRequests)

| Setting | Value |
|---------|-------|
| **Endpoint** | `POST /api/integration/requests/batch?api_key=<API_KEY>` |
| **Body** | FormData with `requests` (JSON array) + `attachments[i]` (files) |

### 9.3 CORS

The API server must allow:
- **Origins:** The host page origin (e.g. `https://yourapp.com`) and the script server origin
- **Headers:** `Authorization`, `X-API-Key`, `Content-Type`
- **Methods:** `GET`, `POST`, `OPTIONS`

### 9.4 Script server

The server at `https://injection.ohtplayground.com/` must serve these files with CORS `Access-Control-Allow-Origin: *` for GET:

- `client.js`
- `singleRequest.js`
- `client-dashboard.js`
- `multiRequests.js`
- `endUserDiv.js`
- `cloudFAB.js`
- `client-dashboard.html`

---

## 10. Testing Checklist

After integration, verify each scenario:

### Setup
- [ ] Script tag added before `</body>`
- [ ] `api_key` is correct
- [ ] `data-prefix` is set to your project name
- [ ] `dashboard_url` is reachable
- [ ] Your app stores JWT in `localStorage.token` (or `access_token` / `sessionStorage.token`)
- [ ] Your app stores user email (in JWT or user object)

### Admin user
- [ ] Login as admin → blue + FAB appears (bottom-right)
- [ ] Hover FAB → cloud expands with "Submit an issue" and "Requests Dashboard"
- [ ] Click "Submit an issue" → modal opens, fill form, submit → success popup
- [ ] Click "Requests Dashboard" → dashboard overlay opens, shows submitted requests
- [ ] No legacy black request button visible at any point
- [ ] Logout → FAB disappears, nothing shows on login page

### Non-admin user
- [ ] Login as non-admin → FAB does NOT appear
- [ ] If `endUserDiv="true"` and mount point exists → "Report a problem" card appears
- [ ] Click "+ Report a problem" → modal opens, submit → success
- [ ] Logout → card disappears

### Not logged in
- [ ] On login page → nothing shows (no FAB, no buttons, no cards)
- [ ] No console errors from widget scripts

### Edge cases
- [ ] Hard refresh (Ctrl+Shift+R) → no flash of old buttons
- [ ] Open in incognito → nothing shows (no token)
- [ ] Navigate between pages (SPA) → widgets update correctly

---

## 11. Troubleshooting

### "Nothing appears at all"

1. **Check the browser console** for 404 errors on script URLs. If `client.js` returns 404, the script server doesn't have it deployed.
2. **Check that the JWT is stored.** In browser console: `localStorage.getItem('token')` — should return a JWT string.
3. **Check role detection.** In console: `localStorage.getItem('user_data')` — the parsed object should have a `role` field.

### "FAB doesn't appear for admin"

The widget considers a user "admin" if `role` equals or contains `"admin"`, or if `role_id === 1`. Check that your stored user has one of these.

### "End-user widget doesn't appear"

1. Is `endUserDiv="true"` in the script tag?
2. Is `<div id="client-feedback-widget"></div>` in the DOM? (Check with browser DevTools.)
3. Is the user non-admin? The widget hides for admin users.
4. Is the user authenticated? The widget hides if no token is found.

### "Flash of black button before FAB"

Add the inline hide-style script **before** client.js (see [Section 7](#7-preventing-button-flash)).

### "FAB stays visible after logout"

Ensure your app clears `localStorage.token` and `localStorage.user_data` on logout. If your app uses `window.location.href = '/login'`, the full page reload will re-run the scripts and the FAB will be removed.

### "CORS error on form submission"

The integration API (`/api/integration/requests`) must allow:
- Your page origin in `Access-Control-Allow-Origin`
- `Authorization` in `Access-Control-Allow-Headers`

### "page_url validation error on localhost"

The backend must accept requests **without** `page_url`. The widget omits it on localhost to avoid SSRF issues.

---

## 12. Quick Reference Card

**Copy-paste this into your HTML (before `</body>`):**

```html
<!-- ═══ OpsHeaven Widget Integration ═══ -->

<!-- Step 1: Prevent button flash (optional but recommended) -->
<script>
  (function () {
    var id = 'oht-legacy-hide', pre = 'MYAPP-';
    if (document.getElementById(id)) return;
    var s = document.createElement('style');
    s.id = id;
    s.textContent = '#' + pre + 'fw-btn, .' + pre + 'cd-open-btn, #' + pre + 'openPopupBtn, .' + pre + 'floating-btn { display:none!important; visibility:hidden!important; }';
    (document.head || document.documentElement).appendChild(s);
  })();
</script>

<!-- Step 2: Load widgets -->
<script src="https://injection.ohtplayground.com/client.js?v=250225"
        data-prefix="MYAPP"
        singleRequest="true"
        client-dashboard="true"
        multiRequests="true"
        endUserDiv="true"
        cloudFAB="true"
        api_key="otm_sk_072e8dc86163f2f8730c88375e21a8a81758cbbd847386f74d161a08aa293728"
        dashboard_url="https://injection.ohtplayground.com/client-dashboard.html">
</script>
```

**Then replace:**
1. `MYAPP` → your project's short name (both in the inline script and `data-prefix`)
2. `api_key` → your API key (if different)
3. URLs → your script server / dashboard URLs (if different)

**For end-user "Report a problem":** add `<div id="client-feedback-widget"></div>` where you want the card to appear (only for non-admin users).

---

*This guide covers everything needed to integrate. If you have questions, check the Troubleshooting section or contact the OpsHeaven team.*
