Skip to main content

Advertising Campaigns

The advertising system cross-promotes your products across every app using drop-in ad components, ten display variants, placement targeting, frequency capping, and automatic impression/click analytics. Campaigns and products are authored in the admin panel; apps just choose a placement.

The pieces

  • Products (zaions_products) — what you promote: name, tagline, URL, icon, color, features.
  • Campaigns (zaions_campaigns) — which product shows where: placement, variant, targeting, custom copy, frequency cap.
  • Impressions (zaions_impressions) — analytics the apps write back.

Drop-in usage

import { AdPanel, AdSlider } from 'shared-features';

<AdPanel placement="sidebar_panel" />
<AdSlider placement="footer_slider" />

Each component fetches an eligible campaign for the placement, records an impression on first display, and renders the configured variant. No campaign or not initialized → renders null.

Ad components

ComponentUse it for
AdPanelA single compact panel (sidebar/footer/settings). Honors a variant prop.
AdSliderA small auto-rotating promotional panel.
AdBannerA persistent banner with rotation + progress indicators.
AdCarouselA multi-campaign carousel.
TopbarAdBannerA slim banner pinned to the top of the page.
AdModalA one-time welcome modal (first visit).
AdUpdateModalA carousel modal shown when the app version changes.

AdPanel with an explicit variant

<AdPanel placement="sidebar_panel" variant="small_panel_2" />

When you pass variant, that layout wins. When you omit it, AdPanel honors the campaign's own admin-configured variant, falling back to small_panel_2 — the same resolution AdSlider uses, so explicit and admin-driven variants stay consistent.

One-time and update modals

import { AdModal, useOneTimeAdModal, AdUpdateModal, useUpdateAdModal } from 'shared-features';

function App() {
const welcome = useOneTimeAdModal();
const update = useUpdateAdModal(); // pass the current version to detect changes

return (
<>
{welcome.shouldShow && <AdModal onClose={welcome.markAsShown} />}
{update.shouldShow && <AdUpdateModal onClose={update.markAsShown} />}
</>
);
}

Placements

PlacementWhere it rendersRecommended variants
popup_sliderExtension popupsmall
options_panelExtension options pagesmall
onetime_modalFirst-visit welcome modallarge
update_modalVersion-update modallarge
notificationPush notification
footer_sliderWeb app footersmall
sidebar_panelWeb app sidebarsmall
home_bannerHome page herolarge

Display variants

Small (compact spaces): small_panel_1 Minimal · small_panel_2 Tagline · small_panel_3 Features · small_panel_4 Gradient · small_panel_5 Card.

Large (feature areas): large_slider_1 Hero · large_slider_2 Feature Grid · large_slider_3 Testimonial · large_slider_4 Comparison · large_slider_5 Video.

The admin sets a campaign's variant; AdSlider/AdUpdateModal/AdPanel resolve it to the matching component via getSmallPanelVariant / getLargePanelVariant (both exported if you want to render variants yourself).

Build your own ad UI

If the built-in components do not fit, drive everything from useCampaigns:

import { useCampaigns } from 'shared-features';

function Footer() {
const { campaigns, loading, recordImpression, recordClick } = useCampaigns({
placement: 'footer_slider',
maxCampaigns: 5,
});

if (loading || campaigns.length === 0) return null;

return (
<div>
{campaigns.map((c) => (
<div
key={c.id}
onMouseEnter={() => recordImpression(c)}
onClick={() => {
recordClick(c);
window.open(c.product.url, '_blank', 'noopener,noreferrer');
}}
>
<h3>{c.product.name}</h3>
<p>{c.product.tagline}</p>
</div>
))}
</div>
);
}

useCampaign({ placement }) is the single-campaign variant used by AdPanel/AdSlider.

Frequency capping

Ads are capped per campaign (default ~20 days between impressions for the same user). Eligibility is tracked locally via @capacitor/preferences on native and localStorage on web, so a user is not shown the same campaign repeatedly. The eligibility helpers (isEligibleForCampaign, getEligibleCampaignIds, getCampaignHistory) are exported from the services layer.

FAQ

Are these third-party ads? No. This is first-party cross-promotion of your own products — there is no third-party ad network, no tracking pixels, and no programmatic bidding.

Do I have to use the components? No. useCampaigns/useCampaign give you the data and the analytics callbacks; render it however you like.

What if there are no active campaigns? Every component renders null and useCampaigns returns an empty array, so it is always safe to leave ad slots mounted.

See the components reference and hooks reference for full prop and return types.