promo-button, by Tincre.dev
- promo-button, by Tincre
.dev
- Contents
- Making ads easy again
- Promo Sync - Enhanced Promo Ad Data
- Promo Pay - Turnkey Payments Solution Included
- Support
- License
- Development
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.
Use your favorite package manager to rock installation of the promo button.
yarn add @tincre/promo-button # -D if you want this as a dev dep
npm install @tincre/promo-button # --save-dev if you want it as a dev dep
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
- Create the frontend component
- Add backend functionality
- Update the
backend
property in your frontend from 1 - Add an environment file, e.g.
.env.local
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.
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);
}
}
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
/>
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.
PROMO_API_KEY=
PROMO_CLIENT_ID=
PROMO_APP_ID=
PROMO_CLIENT_SECRET=
There are styling, content, and component customizations possible, in addition to some optional state props exposed.
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.
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
, andviolet
CSS class and id selectors are also available. They are promo-button-word-{1|2|3}
(choose 1, 2, or 3).
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.
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}
/>
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.
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!
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`
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>
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.
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',
},
},
},
};
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
.promo-button-dialog-outer
.promo-button-dialog-inner
-
.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
.promo-button-dialog-title-text
.promo-button-dialog-subtitle-text
.promo-button-ad-title-input-label
.promo-button-ad-title-input-label-inner
.promo-button-ad-title-input
.promo-button-ad-title-input-container
.promo-button-target-link-input
.promo-button-target-link-input-container
.promo-button-target-link-label
.promo-button-range-input-label
.promo-button-range-input
.promo-button-range-input-container
.promo-button-upload-button
.promo-button-upload-button-label
.promo-button-dialog-submission-button
.promo-button-dialog-submission-button-disabled
.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.
We have two fully working example applications herein to get you started. See the example directory for code.
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.
See the Next.js example for a basic Next.js application with a built-in backend.
For the frontend, edit pages/index.tsx.
For the backend, edit pages/api/promo.ts.
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.
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.
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
.
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 -->
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!
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.
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.
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 <></>
}
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.
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.
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.
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.
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.
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
- Google:
page_view
- Meta:
ViewContent
- Google:
add_payment_info
- Meta:
AddPaymentInfo
- Google:
add_to_cart
- Meta:
AddToCart
- Google:
sign_up
- Meta:
CompleteRegistration
- Google:
purchase
- Meta:
Donate
- Google:
begin_checkout
- Meta:
InitiateCheckout
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.
- Google:
purchase
- Meta:
Purchase
- Google:
search
- Meta:
Search
- Google:
purchase
- Meta:
StartTrial
- Google:
generate_lead
- Meta:
SubmitApplication
- Google:
generate_lead
- Meta:
Subscribe
- Google:
select_content
- Meta:
ViewContent
- Google: https://support.google.com/analytics/answer/9267735
- Meta: https://developers.facebook.com/docs/meta-pixel/reference#standard-events
Promo Pay allows Tincre.dev developers built-in and customizable payments, automatically part of the button herein.
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.
- Documentation: tincre.dev/docs
- Guides and how-tos: tincre.dev/docs/guides
- Reference docs: tincre.dev/docs/reference
- Community: community.tincre.dev
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.
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.
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.
To do a proper release, ensure you're in the base repo directory and run
npm publish . --access public --dry-run
.
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!