# Page, Offers & Add-Ons

The `useCampaign` hook is the primary way to access **page-level data** in your components. A Page in Limio acts as a container for offers and optional add-ons, and can store custom attributes that drive content, logic, or display behaviour.

```typescript
import { useCampaign } from "@limio/sdk"

const { campaign, offers, addOns, groupValues, tag } = useCampaign()
```

**Returns:**

| Field         | Type             | Description                                           |
| ------------- | ---------------- | ----------------------------------------------------- |
| `campaign`    | `CampaignInfo`   | Page metadata — name, path, and custom attributes     |
| `offers`      | `ElasticOffer[]` | Offers attached to this page                          |
| `addOns`      | `ElasticAddOn[]` | Add-ons attached to this page                         |
| `groupValues` | `OfferGroup[]`   | Group definitions configured at the offer level       |
| `tag`         | `string`         | The tag the user entered the page with (for tracking) |

***

## Displaying offers

The most common pattern is to destructure `offers` from `useCampaign` and render a card for each one. Offers contain pricing, display attributes, product references, and attachments.

```tsx
import React from "react"
import { useCampaign, useBasket } from "@limio/sdk"

const PricingPage = () => {
  const { offers = [] } = useCampaign()
  const { addOfferToBasket } = useBasket()

  return (
    <section>
      {offers.map((offer) => {
        const { display_name__limio, display_price__limio, cta_text__limio } =
          offer.data.attributes

        return (
          <div key={offer.id}>
            <h2>{display_name__limio}</h2>
            <p>{display_price__limio}</p>
            <button onClick={() => addOfferToBasket(offer)}>
              {cta_text__limio || "Subscribe"}
            </button>
          </div>
        )
      })}
    </section>
  )
}
```

<details>

<summary>Example offer object</summary>

```javascript
{
  "id": "a1b2c3d4e5f6...",
  "name": "Leemeeo Premium Monthly",
  "path": "/offers/Leemeeo Pricing/Leemeeo Premium Monthly",
  "parent_path": "/offers/Leemeeo Pricing",
  "type": "item",
  "data": {
    "name": "Leemeeo Premium Monthly",
    "tags": ["/tags/premium"],
    "attributes": {
      "price__limio": [
        {
          "name": "charge_1",
          "label": "Monthly",
          "value": "29.00",
          "currencyCode": "USD",
          "type": "recurring",
          "trigger": "order_date",
          "repeat_interval": 1,
          "repeat_interval_type": "months",
          "repeat_count": 1
        }
      ],
      "display_name__limio": "Leemeeo Premium",
      "display_price__limio": "$29/month",
      "detailed_display_price__limio": "Pay $29.00 a month, cancel anytime.",
      "offer_features__limio": "<ul><li>Unlimited projects</li><li>Priority support</li><li>Advanced analytics</li></ul>",
      "cta_text__limio": "Start Free Trial",
      "group__limio": "monthly",
      "payment_types__limio": ["zuora_card"],
      "checkout__limio": { "checkout_type": "standard" },
      "autoRenew__limio": true
    },
    "attachments": [
      {
        "path": "/assets/Images/Premium",
        "name": "Premium Badge",
        "type": "image/png",
        "url": "/public/premium-badge.png"
      }
    ],
    "products": [
      {
        "name": "Leemeeo Premium",
        "path": "/products/Leemeeo Premium",
        "attributes": {
          "product_code__limio": "SKU-LMEO-001"
        },
        "type": "item"
      }
    ]
  }
}
```

</details>

***

## Grouping offers

Offers can be grouped by their `group__limio` attribute (e.g. "monthly" vs "yearly"). The `groupOffers` utility organises offers into labelled groups for tabbed or sectioned layouts.

```typescript
import { groupOffers } from "@limio/sdk"

const grouped = groupOffers(offers, groupLabels)
```

**Parameters:**

| Param         | Type             | Description                                                         |
| ------------- | ---------------- | ------------------------------------------------------------------- |
| `offers`      | `ElasticOffer[]` | The offers array from `useCampaign`                                 |
| `groupLabels` | `Group[]`        | Group definitions — typically from component props or `groupValues` |

Each `Group` has `{ id: string, label: string, thumbnail?: string }`.

**Returns** an array of `GroupInfo` objects:

| Field       | Type             | Description                                             |
| ----------- | ---------------- | ------------------------------------------------------- |
| `groupId`   | `string`         | The group identifier (matches `group__limio` on offers) |
| `id`        | `string`         | Same as `groupId`                                       |
| `label`     | `string`         | Display label for the group                             |
| `offers`    | `ElasticOffer[]` | Offers belonging to this group                          |
| `thumbnail` | `string`         | Optional thumbnail URL for the group                    |

Offers without a `group__limio` attribute are placed in an "Other" group.

**Example — tabbed pricing page with monthly/yearly toggle:**

```tsx
import React, { useMemo, useState } from "react"
import { useCampaign, groupOffers, useBasket } from "@limio/sdk"

const GroupedPricingPage = ({ groupLabels }) => {
  const { offers } = useCampaign()
  const { addOfferToBasket } = useBasket()

  const offerGroups = useMemo(
    () => groupOffers(offers, groupLabels),
    [offers, groupLabels]
  )

  const [activeGroup, setActiveGroup] = useState(offerGroups[0]?.groupId)

  const currentGroup = offerGroups.find((g) => g.groupId === activeGroup)

  return (
    <section>
      {/* Group tabs */}
      <div className="billing-toggle">
        {offerGroups.map((group) => (
          <button
            key={group.groupId}
            className={group.groupId === activeGroup ? "active" : ""}
            onClick={() => setActiveGroup(group.groupId)}
          >
            {group.label}
          </button>
        ))}
      </div>

      {/* Offer cards for selected group */}
      <div className="offer-cards">
        {currentGroup?.offers.map((offer) => (
          <div key={offer.id} className="offer-card">
            <h3>{offer.data.attributes.display_name__limio}</h3>
            <p>{offer.data.attributes.display_price__limio}</p>
            <button onClick={() => addOfferToBasket(offer)}>
              {offer.data.attributes.cta_text__limio || "Subscribe"}
            </button>
          </div>
        ))}
      </div>
    </section>
  )
}
```

<details>

<summary>Example <code>groupLabels</code> and output</summary>

**Input `groupLabels` (typically passed as a component prop):**

```javascript
[
  { id: "monthly", label: "Monthly", thumbnail: "" },
  { id: "yearly", label: "Yearly", thumbnail: "" }
]
```

**Output from `groupOffers`:**

```javascript
[
  {
    groupId: "monthly",
    id: "monthly",
    label: "Monthly",
    offers: [
      // Leemeeo Premium Monthly ($29/mo)
      // Leemeeo Starter Monthly ($9/mo)
    ],
    thumbnail: ""
  },
  {
    groupId: "yearly",
    id: "yearly",
    label: "Yearly",
    offers: [
      // Leemeeo Premium Yearly ($290/yr)
    ],
    thumbnail: ""
  }
]
```

</details>

***

## Add-ons

Add-ons are optional products attached to a page — typically upsells or extras that complement a base offer. Destructure `addOns` from `useCampaign` to access them. Add-ons share the same structure as offers, with a `record_type` of `"add_on"` and their own `price__limio` charges.

```tsx
import React from "react"
import { useCampaign, useBasket } from "@limio/sdk"

const AddOnList = () => {
  const { offers = [], addOns = [] } = useCampaign()
  const { addOfferToBasket } = useBasket()

  return (
    <section>
      <h2>Optional extras</h2>
      {addOns.map((addOn) => (
        <div key={addOn.id}>
          <span>{addOn.data.attributes.display_name__limio}</span>
          <span>{addOn.data.attributes.description__limio}</span>
          <button onClick={() => addOfferToBasket(addOn)}>
            Add
          </button>
        </div>
      ))}
    </section>
  )
}
```

<details>

<summary>Example add-on object</summary>

```javascript
{
  "path": "/add_ons/leemeeo-priority-support",
  "name": "Leemeeo Priority Support",
  "id": "b83ed9f688d6...",
  "type": "item",
  "data": {
    "name": "Leemeeo Priority Support",
    "record_type": "add_on",
    "attributes": {
      "display_name__limio": "Priority Support",
      "description__limio": "24/7 priority support with a dedicated account manager.",
      "price__limio": [
        {
          "name": "charge_1",
          "label": "Monthly",
          "value": "15.00",
          "currencyCode": "USD",
          "type": "recurring",
          "trigger": "subscription_start",
          "repeat_interval": 1,
          "repeat_interval_type": "month"
        }
      ],
      "label__limio": ["add-on"],
      "compatible_products": ["premium"]
    },
    "products": [
      {
        "path": "/products/Priority Support",
        "attributes": {
          "display_name__limio": "Priority Support",
          "product_code__limio": "SKU-LMEO-PS"
        }
      }
    ]
  }
}
```

</details>

***

## Page attributes

Access custom attributes configured on the page itself through `campaign.attributes`. These drive content, layout logic, or checkout behaviour.

```tsx
import React from "react"
import { useCampaign } from "@limio/sdk"

const PageHeader = () => {
  const { campaign } = useCampaign()

  return (
    <section>
      <h1>{campaign.name}</h1>
      {campaign.attributes.meta_title__limio && (
        <title>{campaign.attributes.meta_title__limio}</title>
      )}
    </section>
  )
}
```

<details>

<summary>Example campaign object</summary>

```javascript
{
  "name": "Leemeeo Pricing",
  "path": "/offers/Leemeeo Pricing",
  "attributes": {
    "meta_title__limio": "Leemeeo — Choose Your Plan",
    "push_to_checkout__limio": true
  }
}
```

</details>

***

## Offer utility functions

The SDK exports additional utilities for working with offers. These are most commonly used in subscription management views rather than acquisition pages.

### `checkActiveOffers()`

Filters an array of offers to return only those that are currently active based on their start and end dates.

```typescript
import { checkActiveOffers } from "@limio/sdk"

const activeOffers = checkActiveOffers(subscription.offers)
const allCurrentAndFuture = checkActiveOffers(subscription.offers, true)
```

| Param           | Type             | Default | Description                 |
| --------------- | ---------------- | ------- | --------------------------- |
| `offers`        | `ElasticOffer[]` | `[]`    | Offers to filter            |
| `includeFuture` | `boolean`        | `false` | Include future-dated offers |

### `useOfferInfo()`

Extracts and normalises common offer attributes into a typed object — useful for rendering offer cards with consistent logic.

```typescript
import { useOfferInfo } from "@limio/sdk"

const {
  displayName,
  hasRecurringCharge,
  isAutoRenew,
  allowMultibuy,
  isDelivery,
  offerImage,
  isGift
} = useOfferInfo(offer)
```

**Returns:**

| Field                | Type         | Description                                    |
| -------------------- | ------------ | ---------------------------------------------- |
| `displayName`        | `string`     | Product display name or group label            |
| `hasRecurringCharge` | `boolean`    | Whether the offer has a recurring price charge |
| `isAutoRenew`        | `boolean`    | Whether auto-renewal is enabled                |
| `allowMultibuy`      | `boolean`    | Whether quantity selection is allowed          |
| `isDelivery`         | `boolean`    | Whether the offer includes physical delivery   |
| `offerImage`         | `Attachment` | First image attachment on the offer            |
| `isGift`             | `boolean`    | Whether this is a gift redemption offer        |
| `usesExternalPrice`  | `boolean`    | Whether pricing is fetched externally          |
| `productNames`       | `string[]`   | Names of products included in the offer        |
| `offerDescription`   | `string`     | MMA description text                           |

***

## See also

* [Basket (Cart)](https://github.com/innovate42/innovate42-service-template/blob/docs/docs/_external/spaces/developer-docs/limio-sdk/basket.md) — Adding offers to the basket and checking out
* [Subscription Update Checkout](https://docs.limio.com/developers/limio-sdk/subscription-update-checkout) — Building plan change flows
* [Pricing](https://github.com/innovate42/innovate42-service-template/blob/docs/docs/_external/spaces/developer-docs/limio-sdk/pricing.md) — Working with price objects and formatting
