/promo-button

One button to rule them all. Gain marketing supa powers with a few lines of code.

Primary LanguageTypeScriptMozilla Public License 2.0MPL-2.0

promo-button, by Tincre.dev

The Promo promo-button button example one, by Tincre.dev The Promo promo-button button example two, by Tincre.dev.
One button to rule them all. At least all the ads ones. Seriously, don't do ads any other way; the rest just suck.

Contents

Making ads easy again

The promo-button is a React library to quickly add the button that allows your apps, sites, and web properties to offer users a turn-key interface to Promo.

Payments, user and developer notifications, data management, reporting, ad campaign management, media management and more are built-in and included by default.

Installation

Use your favorite package manager to rock installation of the promo button.

Yarn

yarn add @tincre/promo-button # -D if you want this as a dev dep

Npm

npm install @tincre/promo-button # --save-dev if you want it as a dev dep

⚠️ Release 0.5.0 breaking changes ⚠️

Tincre released promo-node to handle utility and other functionality. As such this lib will no longer export getToken or generateAccessToken.

To get those imports back, simply install the new dependency:

yarn add @tincre/promo-node@latest

Usage

  1. Create the frontend component
  2. Add backend functionality
  3. Update the backend property in your frontend from 1
  4. Add an environment file, e.g. .env.local

Frontend

To get going try throwing the below into your index.jsx or index.tsx file.

import { PromoButton } from '@tincre/promo-button';

<PromoButton
  logoSrc="path-to-your-logo"
  words={['Real', 'Easy', 'Ads']} // Change these to your fav!
  shape="square"
  email="client-email" // should be updated by your authentication/user session object
  backend="my-backend-route" // express route or other HTTP backend
/>;

ℹ️ Though this is a client-side component library you will need to leverage some type of backend that proxies with the Promo API.

You can customize styling, too. See below.

Now add the Cloudinary Upload Widget as a script within your page load that will render the <PromoButton component. For example, in your Next.js app, use the <Script src="https://upload-widget.cloudinary.com/global/all.js"/> component with that source.

Backend

This following example will work out of the box if you use Next.js.

We provide convenience helper methods generateAccessToken and getToken to help you securely authenticate requests to the Promo API.

// Promo API route support: https://tincre.dev/docs/reference
import { generateAccessToken, getToken } from '@tincre/promo-node';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  const clientSecret: string = process.env.PROMO_CLIENT_SECRET || '';
  const appId: string = process.env.PROMO_APP_ID || '';
  const clientId: string = process.env.PROMO_CLIENT_ID || '';
  let accessTokenSigned: string = generateAccessToken(
    'http://localhost:3000', // update w/hostname + base route
    clientId,
    appId,
    clientSecret
  );
  let resultToken: string = await getToken(accessTokenSigned);
  const promoApiUrl = 'https://promo.api.tincre.dev/campaigns';
  // get data from client
  const data = req.body;
  // build request options
  const promoApiRequestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${resultToken}`,
    },
    body: data,
  };
  // send request to tincre.dev Promo API
  // forward the status value
  const promoApiResponse = await fetch(promoApiUrl, promoApiRequestOptions);
  if (promoApiResponse.status === 200) {
    res.status(200);
  } else {
    res.status(promoApiResponse.status);
  }
}

Populate the backend prop

Depending on the route and your application populate the initial backend prop. You should populate this with the route pointing to the above function, inside your client PromoButton.

For example,

<PromoButton
  ...
  backend="/api/promo" // express route or other backend
/>

Environment variables

You'll need the following environment variables available in Node.js:

  • PROMO_CLIENT_ID
  • PROMO_CLIENT_SECRET
  • PROMO_APP_ID
  • PROMO_API_KEY (optional)

These values can be found in the Tincre.dev Dashboard after you're logged in and have created at least one app.

Example .env.local
PROMO_API_KEY=
PROMO_CLIENT_ID=
PROMO_APP_ID=
PROMO_CLIENT_SECRET=

Customizations

There are styling, content, and component customizations possible, in addition to some optional state props exposed.

Controlling open/close state

It's simple to control the button's open close state by passing in a boolean prop isOpen and a React useState boolean setter named setIsOpen.

  • Open: isOpen={true}
  • Closed: isOpen={false}

This can be handy in case you need to control the button's state from an external interface. If included, the Promo Button will copy its state to your provided isOpen and setIsOpen state variables.

Customize words and colors

The button's main text defaults to Real Easy Ads in Tincre colors, violet, blue, and yellow, respectively.

Both of these can be easily changed using the words and wordColors string array properties. Simply specify word colors when rendering the PromoButton as a prop, e.g.:

<PromoButton
  words={['Super', 'Easy', 'Ads']}
  wordColors={['red', 'slate', 'blue']}
  ...
/>

Current color choices include

  • gray,
  • slate,
  • red,
  • indigo,
  • blue,
  • green,
  • yellow,
  • orange, and
  • violet

CSS class and id selectors are also available. They are promo-button-word-{1|2|3} (choose 1, 2, or 3).

Add options

Since 0.2.4 the button can be given options to customize its functionality.

Available options include the post-payment "How It Works" page via the options prop howItWorksContent, the cloudinary options prop to customize cloudinary values, the inputValues prop to immediately fill the button form inputs with your chosen values, and the inputPlaceholders to customize the HTML <form> input placeholder values. Use inputConfig to configure various input element defaults, such as the budget slider minimum allowed ad spend.

Example

Users can pass in an options object to do so:

const options = {
  howItWorksContent: {
    steps: [
      { title: string; subtitle: string },
      { title: string; subtitle: string },
      { title: string; subtitle: string }
    ];
    title: string;
    subtitle: string;
    submittedSubtitle: string;
    submittedTitle: string;
    footerCloseMessage?: string;
  },
  cloudinary: {
    cloudName: string;
    uploadPreset: string;
    folder: string;
    multiple: boolean;
  },
  inputPlaceholders: {
    adTitle: 'The Promo Button, by Tincre.dev',
    budget: 250,
    target: 'https://tincre.dev/promo',
    adCopy: '🔘 One button to rule them all. 🔘',
    adCallToAction: 'Upgrade your apps today.',
    buttonText: 'Try it',
  },
  inputValues: {
    adTitle: 'The Promo Button, by Tincre.dev',
    budget: 250,
    target: 'https://tincre.dev/promo',
    adCopy: '🔘 One button to rule them all. 🔘',
    adCallToAction: 'Upgrade your apps today.',
    buttonText: 'Try it',
    isFlat: true,
  },
  inputConfig: {
    minSpend: 500,
    maxSpend: 50000,
    rangeStep: 100,
  },
  paymentType: 'all', // 'inclusive' or 'additive'
  targetLinkIcons: {
    'Spotify': undefined, // remove the spotify icon from the button rendering
  },
  adDisplayImageCropMessageText: "Hey! This crop is automagic and will differ between platforms. Your stuff will look great!"
  isShowingAdvancedAttributes?: true
};

Then use it like so:

<PromoButton
  ...
  options={options}
/>

Managing state from PromoButton parent

In addition to the above options properties, button users can optionally access and manage state via passing a React Dispatch<setStateAction<InputValues>> | Dispatch<setStateAction<null>> function type.

setPromoData

For example,

import { useState } from 'react';

const [promoData, setPromoData] = useState(null);

<PromoButton
  options={{
    setPromoData: setPromoData,
  }}
/>;

// Do something with `promoData`

🌶️ Check out the types.ts file for specific typing or hover over the button in your editor!

setIsSubmittingPayment

Button clients can also pass through submission status to track the state of payment submissions with setIsSubmittingPayment.

For example,

import { useState } from 'react';

export default function App() {
  const [isSubmittingPayment, setIsSubmittingPayment] = useState(false);

  return <div>
    <h1>{isSubmittingPayment}</h1>
    <PromoButton
      ...
      setIsSubmittingPayment={setIsSubmittingPayment}
    />
  </div>
}
// Do something cooler with `isSubmittingPayment`

Custom ad display component

As of release 0.3.4 users can customize the ad display preview component within the button.

For example,

<CustomAdDisplay
  fileImage={fileImage}
  setFileImage={setFileImage}
  scale={65}
  adTitle={adTitle || 'Tincre.dev Promo Button'}
  adCopy={
    adCopy ||
    'One button to rule them all. Gain marketing supa powers with a few lines of code.'
  }
  target={targetLink || 'https://tincre.dev/promo'}
  callToAction={callToAction || 'Get Started today!'}
  buttonText={buttonText || 'Sign up'}
/>

fileImage is an array of objects with at least secure_url in them, created with React's useState hook.

ℹ️ Internally this happens within the PromoButton function body, i.e. const [fileImage, setFileImage] = useState();.

ℹ️ Populating the fileImage array is done automatically within the library.

You may then pass the customAdDisplay component to the PromoButton as a prop:

import PromoButton from '@tincre/promo-button';
import CustomAdDisplay from './CustomAdDisplay';

<div>
  <PromoButton
    adDisplayComponent={CustomAdDisplay}
    ...
  />
</div>

Disabling UI-payments (email only)

Promo payments are automatically emailed when generated. To generate payment links, your backend needs to call the Promo API /payments endpoint.

📚️ See the docs on /payments here.

We include code to get you started in the Next.js Promo Button example directory. In particular, see the file promo.ts.

If you'd like to disable payment links entirely, you simply need to include disablePromoPay={true} in your button rendering.

For example:

<PromoButton disablePromoPay={true} />

🌶️ This only disables the frontend aspects of the PromoPay component. Because Promo handles support, billing, and campaign managent in total, you'll need to use the Promo Pay solution, customized to your liking via API.

📚️ Click or press to read reference documentation for the Promo API /payments endpoint.

Custom css

The Promo Button styling can be easily customized. Each major section has a css class which you can override.

If using TailwindCSS you may adjust or override any of the classes below via adding to your global css file:

@tailwind base;
@tailwind components;

@tailwind utilities;

@layer components {
  .promo-button-my-custom-outer-button-class {
    @apply px-36 py-2 // whatever tailwind you want;
  }
}

Furthermore, the color brand may be customized to provide a one-shot button color. In your tailwind configuration file:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: '#4F46E5',
      },
    },
  },
};
Main button container

The following are modifiable by the shape prop via the PromoButton component. For example, if shape is "plain" then the class selected will be promo-button-plain.

As such, you may add your own promo-button-<my-name> class and pass "my-name" to the shape prop.

  • .promo-button-main
  • .promo-button-hero
  • .promo-button-circle
  • .promo-button-plain
  • .promo-button-square
Main dialog containers
  • .promo-button-dialog-outer
  • .promo-button-dialog-inner
Main styling
  • .promo-button-text

  • .promo-button-form-container

  • .promo-button-modal-logo

  • .promo-button-image-upload-error

  • .promo-button-target-link-error

  • .promo-button-budget-max-error

  • .promo-button-close-icon-outer

  • .promo-button-close-icon-inner

  • .promo-button-close-icon-size

  • #promo-button-advanced-attributes-collapse

Dialog section styling
Title and subtitle
  • .promo-button-dialog-title-text
  • .promo-button-dialog-subtitle-text
Ad title input
  • .promo-button-ad-title-input-label
  • .promo-button-ad-title-input-label-inner
  • .promo-button-ad-title-input
  • .promo-button-ad-title-input-container
Target link input
  • .promo-button-target-link-input
  • .promo-button-target-link-input-container
  • .promo-button-target-link-label
Spend range input
  • .promo-button-range-input-label
  • .promo-button-range-input
  • .promo-button-range-input-container
Asset upload button
  • .promo-button-upload-button
  • .promo-button-upload-button-label
Submit campaign button
  • .promo-button-dialog-submission-button
  • .promo-button-dialog-submission-button-disabled
Ad preview
  • .promo-button-ad-display-link-button
  • .promo-button-ad-display-cta-emoji
  • .promo-button-ad-display-cta
  • .promo-button-ad-display-target-link
  • .promo-button-ad-display-second-half-container
  • .promo-button-ad-display-video
  • .promo-button-ad-display-image
  • .promo-button-ad-display-ad-title
  • .promo-button-ad-display-title-and-copy-container
  • .promo-button-ad-display-first-half-container
  • #promo-button-ad-display-image-crop-message-text

Note the # for the HTML id versus . for class selector.

Example applications

We have two fully working example applications herein to get you started. See the example directory for code.

React

See the React example for code to get you started if you're rocking a traditional react application.

Edit index.tsx and index.html to get started.

⚠️ Note that the react application demo does not have a backend, currently. See Express documentation for a way to do this in Node.js w/your react application.

Next.js

See the Next.js example for a basic Next.js application with a built-in backend.

Frontend /pages/

For the frontend, edit pages/index.tsx.

Backend /pages/api/

For the backend, edit pages/api/promo.ts.

Promo Sync - Enhanced Promo Ad Data

Use our open source libraries to connect your ad targets with Promo ads and underlying ad platforms like Google, Facebook, Instagram, and YouTube.

Use alongside of or standalone with Google Tag Manager, the Meta Conversions API, and/or your Meta Pixel.

Quick start

You'll need two steps to get going with Enhanced Promo Ads. Promo Sync supercharges ad campaign target links automatically, as a core Promo API feature.

Adding the steps below will further supercharge your campaigns with conversions data but is not required.

Add the Tincre GTag

Follow the installation method below to install the Tincre Sync Gtag. This works alongside or without a current Google Tag Manager or Analytics installation.

Our tag is GTM-57QS65R.

<head>

Into the <head> of each page:

<!-- Google Tag Manager -->
<script>
  (function (w, d, s, l, i) {
    w[l] = w[l] || [];
    w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
    var f = d.getElementsByTagName(s)[0],
      j = d.createElement(s),
      dl = l != 'dataLayer' ? '&l=' + l : '';
    j.async = true;
    j.src = 'https://sync.tincre.dev/gtm.js?id=' + i + dl;
    f.parentNode.insertBefore(j, f);
  })(window, document, 'script', 'dataLayer', 'GTM-57QS65R');
</script>
<!-- End Google Tag Manager -->
<body>

And then in the <body>:

<!-- Google Tag Manager (noscript) -->
<noscript
  ><iframe
    src="https://sync.tincre.dev/ns.html?id=GTM-57QS65R"
    height="0"
    width="0"
    style="display:none;visibility:hidden"
  ></iframe
></noscript>
<!-- End Google Tag Manager (noscript) -->

That's it!

Alongside a current Gtag installation (custom)

Just call the gtag function again, i.e.

gtag('config', 'GTM-57QS65R', {
  transport_url: 'https://sync.tincre.dev',
  first_party_collection: true,
});

See the Google Tag Manager documentation for additional details.

Deduplicating Ids

You should deduplicate ids if you are installing alongside a current implementation that you'd like your Promo events to merge with prior setups, rather than add to.

Pass your event below a transaction_id value and it'll match up throughout your data from Promo and Google Tag Manager.

See this Google Ads answer for more details.

Add an event

To use an event simply import it and call the function, e.g.

import { promoEventPageView } from '@tincre/promo-sync'
import { useEffect } from 'react';

export default function Index({...}) {
    useEffect(() => { // run once on load
        promoEventPageView()
    }, [])
    return <></>
}

Understanding Promo Sync

Promo Sync connects Promo campaigns with Meta Ad platforms like Facebook and Instagram, Google Tag Manager, and Google Ads to bring users detailed, high-frequency ad campaign data.

Not only does it connect your target links with Promo, Sync enhances ad campaign interaction performance and typically leads to lower cost conversions.

Connect to ad platforms

Using link inference from other and past ads Tincre Sync is able to combine custom Google Tag Manager events and ship those in coordinated fashion to campaign ad platforms.

Better ad performance

Ad platforms work with better and more plentiful data sources. Your target properties allow for ample additional information to be delivered and reported back to ad platforms.

Everything from campaign image, video, and text copy assets, to platform placement (e.g. Facebook Feed versus Instagram Story) are typically improved with additional Sync data.

Cheaper conversions

Conversion events like cart checkouts, page views, and button clicks are invaluable pricing tools for Promo campaigns and their underlying ad platforms like Facebook and Google Search.

Detailed Promo Data

With Promo Sync your Promo ad campaign data are supercharged in your dashboard, the API, and in the campaigns themselves. View enhanced data in detailed timeseries, cost and result breakdowns, and in data exports.

Event types

Below outlines what types of events should be used for various common conversion tracking purposes.

Promo Events, which are prefixed by PromoEvent, map to internal Google Tags, GA4 Events, and Meta Pixel Events along with the Conversions API.

https://support.google.com/analytics/answer/9267735 > https://developers.facebook.com/docs/meta-pixel/reference#standard-events

PromoEventPageView
  • Google: page_view
  • Meta: ViewContent
PromoEventAddPaymentInfo
  • Google: add_payment_info
  • Meta: AddPaymentInfo
PromoEventAddToCart
  • Google: add_to_cart
  • Meta: AddToCart
PromoEventCompleteRegistration
  • Google: sign_up
  • Meta: CompleteRegistration
PromoEventDonate
  • Google: purchase
  • Meta: Donate
PromoEventInitiateCheckout
  • Google: begin_checkout
  • Meta: InitiateCheckout
PromoEventLead

Some action taken by a user that indicates a step towards an eventual purchase or action conversion.

  • Google: generate_lead
  • Meta: Lead

Examples:

  • A person clicks on a "listen now" link on a musical artist's website.
  • A person navigates to and clicks on the "Pricing" tab on a business's website.
PromoEventPurchase
  • Google: purchase
  • Meta: Purchase
PromoEventSearch
  • Google: search
  • Meta: Search
PromoEventStartTrial
  • Google: purchase
  • Meta: StartTrial
PromoEventSubmitApplication
  • Google: generate_lead
  • Meta: SubmitApplication
PromoEventSubscribe
  • Google: generate_lead
  • Meta: Subscribe
PromoEventViewContent
  • Google: select_content
  • Meta: ViewContent

Platform references

Promo Pay - Turnkey Payments Solution Included

Promo Pay allows Tincre.dev developers built-in and customizable payments, automatically part of the button herein.

Customizing Promo Pay

If you'd like to customize your use of Promo Pay read the documentation here to learn more about the API endpoint.

You can add your own stripe IDs and branding as of November 2022.

Support

License

This code is free to use for your commercial or personal projects. It is open-source licensed under the Mozilla Public License 2.0.

You will see various headers throughout the codebase and can reference the license directly via LICENSE herein.

Development

Releases

We use npm for releases. In particular, we use npm --publish to get the job done.

Currently, only @thinkjrs has the ability to release. The following section is written for memory.

Release prep

Prior to using npm --publish a release tag needs to be created for the library using our standard tagging practices.

Ensure that tests ✅ pass during this process prior to releasing via npm.

Test release

To do a proper release, ensure you're in the base repo directory and run npm publish . --access public --dry-run.

Release latest tag

To complete a full release to the latest npm dist-tag, ensure you're in the base repo directory and run npm publish . --access public.

🎉 That's it!