npm package: @liburionsoftware/adserve-react
Description: React components for AdServe — drop-in ad display with built-in viewability tracking
Latest version: 1.0.1

# @liburionsoftware/adserve-react

React components for [AdServe](https://github.com/liburionsoftware/advertise) — drop-in ad zones with built-in viewability tracking, click attribution, and support for image and HTML5 creatives.

**Requirements:** React 18+, an AdServe account with at least one site and ad zone.

---

## Installation

```bash
npm install @liburionsoftware/adserve-react
```

---

## Integrating ad zones in your website

### 1. Wrap your app with `AdProvider`

Set the **base URL** of your AdServe server (the same origin that hosts `/api/serve`). Put this once, e.g. in your root layout or `App.tsx`.

```tsx
import { AdProvider } from "@liburionsoftware/adserve-react";

export default function RootLayout({ children }) {
  return (
    <AdProvider baseUrl="https://ads.yoursite.com">
      {children}
    </AdProvider>
  );
}
```

Use your real AdServe URL. Examples:

- Production: `https://ads.yoursite.com`
- Local: `http://localhost:3000`

### 2. Place ads with `AdBanner`

Use your **zone ID** from the AdServe dashboard (Publisher → My Sites → [your site] → Ad Zones). Each zone has a unique ID on its card.

```tsx
import { AdBanner } from "@liburionsoftware/adserve-react";

export default function ArticlePage() {
  return (
    <article>
      <h1>My Article</h1>

      {/* Leaderboard above content */}
      <AdBanner zone="YOUR_ZONE_ID" />

      <p>Your content here...</p>

      {/* Sidebar ad with optional keywords for targeting */}
      <AdBanner
        zone="ANOTHER_ZONE_ID"
        keywords={["tech", "react"]}
        fallback={<div className="h-[250px] w-[300px] bg-muted animate-pulse rounded" />}
      />
    </article>
  );
}
```

**Props:**

| Prop        | Type           | Description |
|------------|----------------|-------------|
| `zone`     | `string`       | **Required.** Zone ID from the dashboard. |
| `keywords` | `string[]`     | Optional. Context keywords for better ad matching. |
| `fallback` | `ReactNode`    | Optional. Shown while loading or when no ad is returned. |
| `className`| `string`       | Optional. Applied to the ad container. |
| `style`    | `CSSProperties`| Optional. Merged with the container styles. |

### 3. (Optional) Custom rendering with `useAd`

For full control over layout and markup, use the `useAd` hook. You must still attach `containerRef` to the element you use for viewability tracking.

```tsx
import { useAd } from "@liburionsoftware/adserve-react";

function CustomAd() {
  const { ad, loading, error, containerRef } = useAd({
    zone: "YOUR_ZONE_ID",
    keywords: ["sports"],
  });

  if (loading) return <Skeleton className="w-[300px] h-[250px]" />;
  if (error) return null;
  if (!ad) return null;

  return (
    <div ref={containerRef}>
      <a href={ad.clickTracker} target="_blank" rel="noopener sponsored">
        {ad.contentUrl ? (
          <img src={ad.contentUrl} width={ad.width} height={ad.height} alt="Ad" />
        ) : (
          <div dangerouslySetInnerHTML={{ __html: ad.htmlContent ?? "" }} />
        )}
      </a>
    </div>
  );
}
```

---

## Full example (Next.js App Router)

```tsx
// app/layout.tsx
import { AdProvider } from "@liburionsoftware/adserve-react";

export default function Layout({ children }) {
  return (
    <html>
      <body>
        <AdProvider baseUrl={process.env.NEXT_PUBLIC_AD_SERVER_URL!}>
          {children}
        </AdProvider>
      </body>
    </html>
  );
}
```

```tsx
// app/page.tsx
import { AdBanner } from "@liburionsoftware/adserve-react";

export default function Home() {
  return (
    <main>
      <AdBanner zone="clx9abc123def456" />
      <h1>Welcome</h1>
      <p>Content...</p>
    </main>
  );
}
```

Set `NEXT_PUBLIC_AD_SERVER_URL` in `.env.local` (e.g. `https://ads.yoursite.com`).

---

## Behavior

- **Request:** The library calls `GET {baseUrl}/api/serve?zone=...&url=...&ref=...` (and optional `ctx` for keywords).
- **Rendering:** Image ads and HTML5/INTERACTIVE creatives are rendered automatically; size comes from the server.
- **Viewability:** An intersection observer tracks when the ad is ≥50% visible for 1 second, then sends an impression to `POST {baseUrl}/api/track/impression`.
- **Clicks:** Clicks use the server-provided `clickTracker` URL (redirect + tracking).

---

## Publishing updates to npm

Maintainers can publish new versions from this repo as follows.

### 1. Make your changes

Edit source in `packages/react-sdk/src/`. Run the build and typecheck locally:

```bash
cd packages/react-sdk
npm install
npm run build
npm run typecheck
```

### 2. Bump the version

Use [semver](https://semver.org/): **major** for breaking changes, **minor** for new features (backward compatible), **patch** for fixes.

```bash
npm version patch   # 1.0.0 → 1.0.1
# or
npm version minor   # 1.0.0 → 1.1.0
# or
npm version major   # 1.0.0 → 2.0.0
```

This updates `package.json` and creates a git tag (e.g. `v1.0.1`). Commit and push the tag if you use it for releases.

### 3. Publish

From `packages/react-sdk`:

```bash
npm publish --access public
```

Scoped packages (`@liburionsoftware/...`) default to restricted access; `--access public` makes the package installable by anyone.

### 4. (Optional) Automate with CI

To publish on tag push (e.g. GitHub Actions):

- Add a secret `NPM_TOKEN` (npm granular token with “Publish” permission for the scope).
- On push of tag `v*`, run: `npm ci`, `npm run build` in the package, then `npm publish --access public` with `//registry.npmjs.org/:_authToken=${NPM_TOKEN}`.

---

## License

MIT