# EMBED_GUIDE.md — putting the widget on your website

Show your event's **approved** updates on your own site. Everything is read-only and authenticated
with an **API key** — safe to ship in client-side HTML because the key only grants read access to
your own organization's approved updates.

There are three ways to embed, from easiest to most custom.

---

## Before you start

1. In the admin portal, open **API keys → + New key**. Copy the secret **immediately** — it is
   shown only once.
2. Make sure your event is **live** and has at least one **approved** update.
3. Grab your event code (e.g. `NGR2027`) from the **Events** page.

The portal's **Embed & widget** page will build any of the snippets below for you — pick the event,
paste your key, choose a format, and copy.

---

## Option 1 — JavaScript widget (recommended)

Drop a target element and the script tag. The widget fetches approved updates, renders a clean
default UI, and can auto-refresh.

```html
<div id="update-feed"></div>

<script src="https://YOUR-DOMAIN/update/api/public/events/NGR2027/widget.js"
        data-key="upd_your_api_key_here"
        data-target="#update-feed"
        data-limit="20"
        data-poll="30"></script>
```

**Script attributes**

| Attribute | Meaning | Default |
|---|---|---|
| `data-key` | Your API key (required). | — |
| `data-target` | CSS selector of the container to render into. If omitted, the widget renders right after the script tag. | — |
| `data-limit` | How many updates to show. | `20` |
| `data-poll` | Auto-refresh interval in seconds (`0` = off, minimum 10). | `0` |
| `data-likes` | Show like buttons (`"false"` to hide). | on |

The widget renders text, **images and video inline**, a **📍 location link** for geotagged posts,
and a **like button** (♥) per update. Likes are anonymous and deduped per browser via a token the
widget stores in `localStorage` — no reader login required.

**Styling.** The widget injects minimal default styles (questions are highlighted in teal). Every
element uses `uw-*` classes under `.update-widget`, so you can override freely:

```css
.update-widget .uw-item { border-radius: 16px; }
.update-widget .uw-item.uw-question { border-color: #6d28d9; background: #f5f3ff; }
.update-widget .uw-badge { background: #6d28d9; }
```

All text is HTML-escaped by the widget before insertion (updates are already sanitized server-side
too — defense in depth).

---

## Option 2 — Iframe

Simplest possible embed; no JavaScript on your page. (Renders the raw JSON feed; use Option 1 for a
styled list.)

```html
<iframe src="https://YOUR-DOMAIN/update/api/public/events/NGR2027/updates?key=upd_your_api_key_here"
        style="width:100%;height:520px;border:1px solid #e5e7eb;border-radius:10px"></iframe>
```

---

## Option 3 — Raw JSON API (full control)

Build your own UI against the JSON endpoint.

```js
fetch("https://YOUR-DOMAIN/update/api/public/events/NGR2027/updates?limit=20", {
  headers: { "X-API-Key": "upd_your_api_key_here" }
})
  .then(r => r.json())
  .then(data => {
    // data.event  -> { code, title, status }
    // data.updates -> [{ id, text, type, media_url, channel, author, submitted_at }]
    // data.paging  -> { limit, next_before, count }
    console.log(data.updates);
  });
```

**Pagination** is keyset-based: pass `before=<id>` (use `paging.next_before` from the previous
response) to fetch older updates.

**Caching / resilience.** Responses include `Cache-Control: public, max-age=30` and an `ETag`.
Send `If-None-Match` to get a `304 Not Modified` when nothing changed — your page can cache the
last good response and keep showing it if the platform is briefly unreachable.

---

## Update object shape

```json
{
  "id": 42,
  "text": "Doors are open, crowd is building.",
  "type": "statement",             // or "question" (ends with ?)
  "media_type": "image",           // none | image | video | audio | document | link | location
  "media_url": "https://…/uploads/…jpg",
  "location": { "lat": 6.5244, "lng": 3.3792, "map_url": "https://www.google.com/maps?q=6.5244,3.3792" },
  "likes": 3,
  "channel": "whatsapp",           // whatsapp | telegram | sms | email | web
  "author": "Ada",                 // display name, may be null
  "submitted_at": "2026-07-14T14:41:31+00:00"
}
```

### Likes & follow (building your own UI)

If you're rendering your own feed (Option 3), you can drive engagement directly:

```js
// Like an update (dedupe with a token you persist per visitor)
fetch(`${API}/public/updates/${id}/like`, {
  method: "POST",
  headers: { "X-API-Key": key, "Content-Type": "application/json" },
  body: JSON.stringify({ voter_token: myVisitorToken })
});

// Follow the event
fetch(`${API}/public/events/NGR2027/follow`, {
  method: "POST",
  headers: { "X-API-Key": key, "Content-Type": "application/json" },
  body: JSON.stringify({ email: readerEmail })
});

// "Top moments" (most-liked)
fetch(`${API}/public/events/NGR2027/top?limit=5`, { headers: { "X-API-Key": key } });
```

---

## CORS

Public endpoints send CORS headers. By default (`CORS_ALLOWED_ORIGINS=*`) any site may read with a
valid key. For production, set `CORS_ALLOWED_ORIGINS` in `.env` to your org domains, e.g.:

```
CORS_ALLOWED_ORIGINS=https://htcafrica.org,https://www.htcafrica.org
```

---

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `401 unauthorized` | Missing/invalid/revoked key. Regenerate in the portal. |
| `403 forbidden` | The key belongs to a different org than the event. Use a key from the event's own org. |
| Empty list | No **approved** updates yet, or the event isn't **live**. |
| Nothing renders (Option 1) | Check the browser console; confirm `data-key` is set and the `src` host is correct. |

A ready-to-open sample page is included at [`examples/embed-demo.html`](examples/embed-demo.html) —
paste your key into it and open it in a browser.
