{"_id":"@alpine-iq/third-party-loops","_rev":"15-a4bf6f479fd9a33b604bc4ddc83ddc46","name":"@alpine-iq/third-party-loops","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@alpine-iq/third-party-loops","version":"0.1.0","author":{"name":"AIQ"},"license":"ISC","_id":"@alpine-iq/third-party-loops@0.1.0","maintainers":[{"name":"oneofone","email":"oneofone@gmail.com"},{"name":"diegomors_aiq","email":"diego.moraes@aiq.com"},{"name":"typeofcam","email":"cameron.patch@aiq.com"},{"name":"allison-aiq","email":"allison.jacobs@aiq.com"},{"name":"justinhbaker3","email":"justin.baker@aiq.com"},{"name":"officert","email":"timothyofficer@gmail.com"},{"name":"brynnd","email":"brynner.doyle@aiq.com"},{"name":"yeagerhaus","email":"cole.yeager@aiq.com"},{"name":"aiqrlap","email":"robert.lappert@aiq.com"},{"name":"jtranaiq","email":"jimmy.tran@aiq.com"},{"name":"lindsay-breckheimer","email":"lindsay.breckheimer@aiq.com"},{"name":"imaiqphillip","email":"phillip.voydanoff@aiq.com"},{"name":"gabe-morrison","email":"gabriel.morrison@aiq.com"}],"dist":{"shasum":"f14d53dd5b4fa10eb535b97a3cae2fef1ef492f1","tarball":"https://registry.npmjs.org/@alpine-iq/third-party-loops/-/third-party-loops-0.1.0.tgz","fileCount":8,"integrity":"sha512-inQZ1KD5S4SCnr8FZWIfovLGCVAvuz/FQLpGo5ICs5B9ze6EyRep5/xDdwMNcgoRUboN3IfWnlSHq8/buwYeAw==","signatures":[{"sig":"MEUCIQDSt3fPX6xM22VXjZ5sdZgPB4XbuvjvS27KmgPg8QeyTwIgfh/pnzFAxLCRd600i5SU8nEB2dxB6F1bbJxNqTbH/0Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1124518},"main":"index.js","types":"index.d.ts","exports":{".":{"types":"index.d.ts","default":"index.js"},"./styles/*":"./styles/*"},"gitHead":"606bbafe60e189829e104bbdf46df4981fbf9c95","scripts":{},"_npmUser":{"name":"yeagerhaus","email":"cole.yeager@aiq.com"},"_npmVersion":"10.9.3","description":"Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"publishConfig":{"access":"restricted"},"_hasShrinkwrap":false,"devDependencies":{},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","styled-components":"^5.3.8"},"_npmOperationalInternal":{"tmp":"tmp/third-party-loops_0.1.0_1772475668735_0.21442730628499795","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alpine-iq/third-party-loops","version":"0.1.1","author":{"name":"AIQ"},"license":"ISC","_id":"@alpine-iq/third-party-loops@0.1.1","maintainers":[{"name":"oneofone","email":"oneofone@gmail.com"},{"name":"diegomors_aiq","email":"diego.moraes@aiq.com"},{"name":"typeofcam","email":"cameron.patch@aiq.com"},{"name":"allison-aiq","email":"allison.jacobs@aiq.com"},{"name":"justinhbaker3","email":"justin.baker@aiq.com"},{"name":"officert","email":"timothyofficer@gmail.com"},{"name":"brynnd","email":"brynner.doyle@aiq.com"},{"name":"yeagerhaus","email":"cole.yeager@aiq.com"},{"name":"aiqrlap","email":"robert.lappert@aiq.com"},{"name":"jtranaiq","email":"jimmy.tran@aiq.com"},{"name":"lindsay-breckheimer","email":"lindsay.breckheimer@aiq.com"},{"name":"imaiqphillip","email":"phillip.voydanoff@aiq.com"},{"name":"gabe-morrison","email":"gabriel.morrison@aiq.com"}],"dist":{"shasum":"3229223a14ef80e76ca856926d6b71bdaa1bd77e","tarball":"https://registry.npmjs.org/@alpine-iq/third-party-loops/-/third-party-loops-0.1.1.tgz","fileCount":10,"integrity":"sha512-8/F8Z1H+x/EXwf7CU5YzJbJJjaVd57FuuCGSEIefmWyNWn1ukQVVUeN3lvAZAkt4a4HXPae9/xndw9ad7YZDmQ==","signatures":[{"sig":"MEUCIQDyN8Jj9WyoqXTwi2T7m9woadi52DHQ228vP648CxO0FQIgZq7aZq/O/ELJe0H/fhNWfQZHxhc0nJbTBKvgHuzR4mk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3118589},"main":"index.js","types":"index.d.ts","exports":{".":{"types":"index.d.ts","default":"index.js"},"./styles/*":"./styles/*","./third-party-loops.iife.js":"./third-party-loops.iife.js"},"gitHead":"a7338b57da972f7eb5b4e32b88a9fc8ea7024511","scripts":{},"_npmUser":{"name":"yeagerhaus","email":"cole.yeager@aiq.com"},"_npmVersion":"10.9.3","description":"Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","styled-components":"^5.3.8"},"_npmOperationalInternal":{"tmp":"tmp/third-party-loops_0.1.1_1773871783355_0.9595031660712052","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@alpine-iq/third-party-loops","version":"0.1.2","author":{"name":"AIQ"},"license":"ISC","_id":"@alpine-iq/third-party-loops@0.1.2","maintainers":[{"name":"oneofone","email":"oneofone@gmail.com"},{"name":"diegomors_aiq","email":"diego.moraes@aiq.com"},{"name":"typeofcam","email":"cameron.patch@aiq.com"},{"name":"allison-aiq","email":"allison.jacobs@aiq.com"},{"name":"justinhbaker3","email":"justin.baker@aiq.com"},{"name":"officert","email":"timothyofficer@gmail.com"},{"name":"brynnd","email":"brynner.doyle@aiq.com"},{"name":"yeagerhaus","email":"cole.yeager@aiq.com"},{"name":"aiqrlap","email":"robert.lappert@aiq.com"},{"name":"jtranaiq","email":"jimmy.tran@aiq.com"},{"name":"lindsay-breckheimer","email":"lindsay.breckheimer@aiq.com"},{"name":"imaiqphillip","email":"phillip.voydanoff@aiq.com"},{"name":"gabe-morrison","email":"gabriel.morrison@aiq.com"}],"dist":{"shasum":"83165a95a0a6d1fc309ba30f8e7104d9a665b78a","tarball":"https://registry.npmjs.org/@alpine-iq/third-party-loops/-/third-party-loops-0.1.2.tgz","fileCount":11,"integrity":"sha512-/U+6oDScxmvIya9FjNANdNt2jB0ATvbHrGbwIgidKDs5fA8J6G4Inskw6IitxakOQ9PdomhBZfBqObE8kvgGpQ==","signatures":[{"sig":"MEUCIG5lSeQ+UfAx91OZ+dW1GcaOhFkfA0pXC/Z2z3NN7puOAiEA/P5Dg5OCJY2gKywCVeBFMLEzb9oX66dP/HwSwYWl1LM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3369634},"main":"index.js","types":"index.d.ts","exports":{".":{"types":"index.d.ts","default":"index.js"},"./styles/*":"./styles/*","./third-party-loops.iife.js":"./third-party-loops.iife.js"},"gitHead":"43efc92d6ab230c1723a8d4dfa6ce871c2a7f1cc","scripts":{},"_npmUser":{"name":"brynnd","email":"brynner.doyle@aiq.com"},"_npmVersion":"10.9.4","description":"Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.","directories":{},"_nodeVersion":"22.2.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0"},"_npmOperationalInternal":{"tmp":"tmp/third-party-loops_0.1.2_1773958438980_0.7164383991523964","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-03-02T18:21:08.265Z","modified":"2026-09-08T19:08:08.022Z","0.1.0":"2026-03-02T18:21:08.946Z","0.1.1":"2026-03-18T22:09:43.674Z","0.1.2":"2026-03-19T22:13:59.188Z"},"author":{"name":"AIQ"},"license":"ISC","description":"Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.","maintainers":[{"email":"brynner.doyle@aiq.com","name":"brynnd"},{"email":"cole.yeager@aiq.com","name":"yeagerhaus"},{"email":"ahmed@aiq.com","name":"ooo-aiq"},{"email":"gabriel.morrison@aiq.com","name":"gabe-morrison"}],"readme":"# @alpine-iq/third-party-loops\n\nDisplay Alpine IQ sponsored ad placements (\"loops\") in your app or website. This package is intended for third-party integration partners (Jane, Dutchie, DigitalAwesome, etc.) and exposes only what you need — no access to the broader Alpine IQ internal library is required.\n\n## Prerequisites\n\nBefore integrating, you will need the following from your Alpine IQ account manager:\n\n- **API URL** — the base URL for the Alpine IQ API\n- **Vendor name** — your integration identifier (e.g. `jane`, `dutchie`); used in the vendor ads endpoint\n- **Publisher UID** — your organization's unique identifier in the Alpine IQ system\n\nIf you don't have these, reach out to your Alpine IQ contact before proceeding.\n\n---\n\n## Install\n\n```bash\nnpm install @alpine-iq/third-party-loops\n# or\nyarn add @alpine-iq/third-party-loops\n# or\npnpm add @alpine-iq/third-party-loops\n```\n\n### Peer dependencies\n\nThe following must already be installed in your project:\n\n```bash\nnpm install react react-dom\n```\n\n| Package | Version |\n|---|---|\n| `react` | `^18.2.0` |\n| `react-dom` | `^18.2.0` |\n\n### Required styles\n\nThe widget requires Alpine IQ UI styles to render correctly. Import them once in your app's entry point:\n\n```js\nimport '@alpine-iq/third-party-loops/styles/main.css'\nimport '@alpine-iq/third-party-loops/styles/animations.css'\n```\n\n> If you are using a CSS bundler or framework that doesn't support bare CSS imports, copy the contents of these files into your own stylesheet. Contact your Alpine IQ account manager if you need the raw CSS files.\n\n---\n\n## Quick start\n\n### 1. Initialize the API client\n\nCall `initApiClient` **once** at your app's startup, before any widget renders. Calling it multiple times is safe — subsequent calls are ignored. **`apiUrl` and `vendorName` are required**; without `vendorName` the widget cannot fetch ads and will render nothing.\n\n```js\nimport { initApiClient } from '@alpine-iq/third-party-loops'\n\ninitApiClient({\n  apiUrl: 'https://api.alpineiq.com',  // provided by Alpine IQ\n  vendorName: 'your-vendor-name',      // e.g. jane, dutchie — provided by Alpine IQ (required)\n})\n```\n\n### 2. Render the widget\n\n```jsx\nimport { LoopsWidget } from '@alpine-iq/third-party-loops'\n\nfunction ProductPage() {\n  return (\n    <LoopsWidget uid=\"your-publisher-uid\" />\n  )\n}\n```\n\nThat's it. The widget fetches and renders the appropriate ads automatically, and handles impression tracking with no additional setup.\n\n---\n\n## React usage\n\n### Basic example\n\n```jsx\nimport { LoopsWidget } from '@alpine-iq/third-party-loops'\n\nfunction StorePage() {\n  return (\n    <LoopsWidget\n      uid=\"your-publisher-uid\"\n      variant=\"cover-card\"\n      onAdsLoaded={(ads) => console.log(`${ads.length} ad(s) loaded`)}\n    />\n  )\n}\n```\n\n### With a known user (personalized ads)\n\nIf the current user is logged in and you have their Alpine IQ contact ID, pass it via `contactID` to enable personalized ad targeting:\n\n```jsx\n<LoopsWidget\n  uid=\"your-publisher-uid\"\n  contactID={currentUser.aiqContactId}\n/>\n```\n\nWithout `contactID`, ads are served anonymously and are not personalized.\n\n### With a loading state\n\nUse `onLoadingChange` to show a skeleton or placeholder while ads are being fetched:\n\n```jsx\nconst [loading, setLoading] = React.useState(true)\n\n<>\n  {loading && <MySkeletonPlaceholder />}\n  <LoopsWidget\n    uid=\"your-publisher-uid\"\n    onLoadingChange={setLoading}\n  />\n</>\n```\n\n### Props\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `uid` | `string` | Yes | — | Your publisher UID, provided by Alpine IQ. |\n| `variant` | `'cover-card' \\| 'padded-card'` | No | `'cover-card'` | Visual style of the ad card. |\n| `contactID` | `string` | No | — | Alpine IQ contact ID of the current logged-in user. Enables personalized ads. Omit for anonymous/guest users. |\n| `categories` | `string[]` | No | — | Filter ads to specific product categories. Pass a memoized array to avoid unnecessary refetches. |\n| `limit` | `number` | No | — | Maximum number of ads to display. |\n| `customStyles` | `string` | No | — | Raw CSS string injected into the widget's container. Use for minor layout overrides. |\n| `onAdsLoaded` | `(ads: AdTemplateOut[]) => void` | No | — | Called when the ad fetch completes. Receives the loaded ads (empty array if none). |\n| `onLoadingChange` | `(loading: boolean) => void` | No | — | Called with `true` when a fetch starts and `false` when it finishes (or fails). |\n| `onError` | `(error: Error) => void` | No | — | Called when the ad fetch fails. Use to log errors or show fallback content. |\n| `disableImpressionTracking` | `boolean` | No | `false` | Set to `true` to disable view/click tracking. Useful for testing or staging environments. |\n| `useMockAds` | `boolean` | No | `false` | Set to `true` to render built-in sample ads without calling the API. Useful for local development and testing your integration layout before going live. Impression tracking is automatically disabled. |\n\n---\n\n## Non-React / script tag usage\n\nIf your app is not built with React, you can mount the widget using the global `window.__AIQLoopsCore` API. Use the IIFE bundle (which includes React so no extra scripts are required), then call `mountReact`:\n\n```html\n<div id=\"aiq-loops-root\"></div>\n<!-- When installed via npm, the IIFE is at the package root (publish root is dist/) -->\n<script src=\"node_modules/@alpine-iq/third-party-loops/third-party-loops.iife.js\"></script>\n<script>\n  // Initialize the API client first\n  window.__AIQLoopsCore.initApiClient({\n    apiUrl: 'https://api.alpineiq.com',\n    vendorName: 'your-vendor-name',\n  })\n\n  // Mount the widget\n  const instance = window.__AIQLoopsCore.mountReact({\n    element: '#aiq-loops-root',\n    uid: 'your-publisher-uid',\n    variant: 'cover-card',\n  })\n\n  // Optional: react when ads load\n  instance.ads.then((ads) => {\n    console.log(`${ads.length} ad(s) loaded`)\n  })\n</script>\n```\n\nTo unmount (e.g. on page navigation):\n\n```js\nwindow.__AIQLoopsCore.unmountReact(instance)\n```\n\n### `MountOptions`\n\n| Option | Type | Required | Description |\n|---|---|---|---|\n| `element` | `string \\| HTMLElement` | Yes | CSS selector string or DOM element to mount into. |\n| `uid` | `string` | Yes | Your publisher UID. |\n| `variant` | `'cover-card' \\| 'padded-card'` | No | Visual style. Default `'cover-card'`. |\n| `contactID` | `string` | No | Alpine IQ contact ID of the logged-in user. |\n| `categories` | `string[]` | No | Filter ads by category. |\n| `limit` | `number` | No | Max number of ads. |\n| `customStyles` | `string` | No | Raw CSS for the widget container. |\n| `disableImpressionTracking` | `boolean` | No | Disable impression tracking. Default `false`. |\n| `onError` | `(error: Error) => void` | No | Called when the ad fetch fails. |\n| `useMockAds` | `boolean` | No | Render built-in sample ads without calling the API. Default `false`. |\n\n---\n\n## Advanced: calling the API directly\n\nThe plugin exports the underlying API helpers if you need to fetch ads or track impressions outside of the widget component (e.g. server-side rendering, custom UI, prefetching):\n\n```js\nimport {\n  initApiClient,\n  getAdsForUserAndLocation,\n  createAdImpressionResponse,\n  submitAdImpressionType,\n} from '@alpine-iq/third-party-loops'\n\n// Fetch ads manually (vendor placement is implied by initApiClient; no adLocation param)\nconst ads = await getAdsForUserAndLocation(\n  'your-publisher-uid',\n  contactID,    // optional\n  categories,   // optional\n  limit,        // optional\n  refURL        // optional; defaults to current page URL in browser\n)\n\n// Or fetch built-in sample ads for testing (no API call, no initApiClient needed)\nconst mockAds = await getAdsForUserAndLocation(\n  'any-uid',\n  undefined, undefined, undefined, undefined,\n  { useMockAds: true }\n)\n\n// Track that an ad was viewed\nconst impression = await createAdImpressionResponse(ads[0].impressionTracker)\n\n// Track a specific event (e.g. a click)\nawait submitAdImpressionType(impression, 'click')\n```\n\n`getAdsForUserAndLocation` will throw if the API request fails. Wrap in try/catch if you need to handle errors. `createAdImpressionResponse` and `submitAdImpressionType` silently swallow errors — they will never throw.\n\n---\n\n## TypeScript\n\nThe package ships with full type declarations. Useful types you can import:\n\n```ts\nimport type {\n  InitApiClientConfig,\n  LoopsWidgetProps,\n  MountOptions,\n  MountResult,\n} from '@alpine-iq/third-party-loops'\n```\n\n---\n\n## Troubleshooting\n\n**The widget renders nothing.**\n- Check the browser console for any API errors.\n- Confirm `initApiClient` was called before the widget mounted, with the correct `apiUrl` and `vendorName`.\n- Make sure the `uid` is correct — an unrecognized UID will return zero ads.\n\n**Styles look broken or unstyled.**\n- Confirm you've imported both CSS files at your app's entry point (see [Required styles](#required-styles)).\n\n**Ads are not personalized.**\n- Pass the `contactID` prop with the user's Alpine IQ contact ID. Without it, the anonymous endpoint is used.\n\n**I want to test without recording real impressions.**\n- Pass `disableImpressionTracking={true}` to the widget.\n\n**I want to test with sample ads before going live.**\n- Pass `useMockAds={true}` to the widget. This renders built-in sample ads (image + video) without calling the API, so you don't need `initApiClient` or a valid UID. Impression tracking is automatically disabled.\n\n---\n\n## Support\n\nContact your Alpine IQ account manager or integration support team for API URL, vendor name, UIDs, and placement guidance.\n","readmeFilename":"README.md"}