Skip to content

Commit

Permalink
Add product promos
Browse files Browse the repository at this point in the history
  • Loading branch information
lesleyjanenorton committed Mar 6, 2020
1 parent 684d684 commit 82e8b9d
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 41 deletions.
99 changes: 93 additions & 6 deletions public/css/product-promos.css
Original file line number Diff line number Diff line change
@@ -1,35 +1,90 @@
.product-promo {
position: relative;
overflow: hidden;
border-radius: 16px;
padding: 32px 42px;
color: rgba(255, 255, 255, 1);
text-align: left;
background-position: 104%;
background-size: auto 104%;
background-repeat: no-repeat;
}

.promo-copy {
padding-right: 13%;
}

.promo-monitor {
background-color: #393473;
background-image: url("/img/svg/promos/promo-bg-monitor.svg");
}

.promo-mobile {
background-color: #123474;
background-image: url("/img/svg/promos/promo-bg-mobile.svg");
}

/* QR Code */
.product-promo.promo-mobile::after {
background: #fff;
border-radius: 4px;
background-image: url("/img/svg/promos/mobile-qr-gradient.svg");
content: "";
width: 60px;
height: 60px;
position: absolute;
right: 42px;
bottom: 32px;
background-size: 90%;
background-repeat: no-repeat;
background-position: center center;
}

.promo-private-network,
.promo-ecosystem,
.promo-lockwise {
background-image: url("/img/svg/promos/promo-bg-lockwise.svg");
}

.promo-fpn {
background-image: url("/img/svg/promos/promo-bg-fpn.svg");
}

.promo-ecosystem {
background-image: url("/img/svg/promos/promo-bg-ecosystem.svg");
}

.promo-lockwise,
.promo-fpn,
.promo-ecosystem {
background-color: #1e1338;
}

.promo-monitor > .promo-content::before {
background-image: url("/img/svg/fxa-tout-yellow-env.svg");
}

.promo-mobile > .promo-content::before {
background-image: url("/img/svg/logos/fx-logo.svg");
}

.promo-lockwise > .promo-content::before {
background-image: url("/img/svg/logos/fx-lockwise.svg");
}

.promo-fpn > .promo-content::before {
background-image: url("/img/svg/logos/fpn-logo.svg");
}

.promo-ecosystem > .promo-content::before {
background-image: url("/img/svg/logos/fx-master-logo.svg");
}

.promo-content::before {
margin-right: 36px;
min-width: 88px;
min-width: 66px;
min-height: 66px;
background-size: contain;
background-repeat: no-repeat;
background-position: top center;
content: "";
display: inline-block;
}
Expand All @@ -50,29 +105,61 @@
border-color: rgba(255, 255, 255, 1);
border-radius: 8px;
min-height: 40px;
color: rgba(255, 255, 255, 1);
color: rgb(255, 255, 255, 1);
font-size: 16px;
margin: auto auto auto 0;
padding: 12px 32px;
}

@media screen and (max-width: 600px) {
.promo-wrapper {
margin-top: 44px;
margin-bottom: 20px;
padding: 0;
}

@media screen and (max-width: 800px) {
.product-promo.drop-shadow.flx.al-cntr.jst-cntr.promo-mobile::after {
display: none;
}

.promo-wrapper {
padding: 0 24px;
}

.product-promo {
background-image: none !important;
padding: 42px;
}

.promo-content {
flex-direction: column;
text-align: center;
align-items: center;
}

.promo-body {
max-width: 400px;
}

.promo-copy {
padding-right: 0;
}

.promo-content::before {
margin: auto auto 16px auto;
}

.promo-headline {
font-size: 27px;
font-size: 32px;
}
}

@media screen and (max-width: 500px) {
.product-promo {
padding: 42px 24px;
}

.promo-headline {
font-size: 28px;
}
}
1 change: 1 addition & 0 deletions public/img/svg/promos/mobile-qr-gradient.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions public/js/fxa-analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,8 @@ function sendRecommendationPings(ctaSelector) {
const eventTriggers = [
"#scan-user-email",
"#add-another-email-form",
".open-oauth",
".open-oauth:not(.product-promo-wrapper)", // The promo entrypoint events are handled elsewhere.
];

// Send number of foundBreaches on Scan, Full Report, and User Dashboard pageviews
if (pageLocation === ("Scan Results")) {
const breaches = document.querySelectorAll(".breach-card");
Expand Down
54 changes: 39 additions & 15 deletions public/js/resolve-breaches.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,50 +29,74 @@
return;
}


function sendResolutionPing(eventAction, eventLabel) {
function sendBreachDetailAnalyticsPing(eventCategory, eventAction, eventLabel) {
if (typeof(ga) !== "undefined") {
ga("send", "event", "Breach Resolution", eventAction, eventLabel);
ga("send", "event", eventCategory, eventAction, eventLabel);
}
}


// set up IntersectionObserver to watch for resolution event triggers
// set up IntersectionObserver to watch for event triggers on breach-details pages
// and send "View" ping when they become visible in the viewport
const availableIntersectionObserver = ("IntersectionObserver" in window);
const resolveBtns = document.querySelectorAll(".resolve-button, a.what-to-do-next");
const productPromos = document.querySelectorAll(".product-promo-wrapper");
const gaEventTriggers = [...productPromos, ...resolveBtns];

const availableIntersectionObserver = ("IntersectionObserver" in window);
const gaAvailable = typeof(ga) !== undefined;

if (availableIntersectionObserver && resolveBtns && gaAvailable) {
const onResolveButtonsComingIntoView = (entries, observer) => {
// TODO: Store this in the dataset of breach resolution event triggers
const resolutionEventCategory = "Breach Resolution";

if (availableIntersectionObserver && gaEventTriggers && gaAvailable) {
const onEventTriggersComingIntoView = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
sendResolutionPing("View", entry.target.dataset.analyticsLabel);
const entryData = entry.target.dataset;
const analyticsLabel = entryData.analyticsLabel;
if (entry.target.classList.contains("product-promo-wrapper")) {
sendBreachDetailAnalyticsPing(entryData.eventCategory, "View", analyticsLabel);
observer.unobserve(entry.target);
return;
}
sendBreachDetailAnalyticsPing(resolutionEventCategory, "View", analyticsLabel);
observer.unobserve(entry.target);
return;
}
});
};

const observer = new IntersectionObserver(onResolveButtonsComingIntoView, { rootMargin: "0px"});
resolveBtns.forEach(btn => {
observer.observe(btn);
const observer = new IntersectionObserver(onEventTriggersComingIntoView, { rootMargin: "0px"});
gaEventTriggers.forEach(el => {
observer.observe(el);
});
}

// Fallback for older browsers without IntersectionObserver:
// Send "View - No IntersectionObserver" pings on page load, regardless
// of whether or not the triggers are actually visible.
if (!availableIntersectionObserver && resolveBtns && gaAvailable) {
resolveBtns.forEach(btn => {
sendResolutionPing("View - No IntersectionObserver", btn.dataset.analyticsLabel);
if (!availableIntersectionObserver && gaEventTriggers && gaAvailable) {
gaEventTriggers.forEach(el => {
const elemData = el.dataset;
if (el.classList.contains("product-promo-wrappper")) {
return sendBreachDetailAnalyticsPing(elemData.eventCategory, "View - No IntersectionObserver", elemData.analyticsLabel);
}
sendBreachDetailAnalyticsPing(resolutionEventCategory, "View - No IntersectionObserver", elemData.analyticsLabel);
});
}

productPromos.forEach(promo => {
promo.addEventListener("click", () => {
const promoData = promo.dataset;
sendBreachDetailAnalyticsPing(promoData.eventCategory, "Engage", promoData.analyticsLabel);
});
});


resolveBtns.forEach(btn => {
btn.addEventListener("click", async(e) => {
if (gaAvailable) {
sendResolutionPing("Engage", btn.dataset.analyticsLabel);
sendBreachDetailAnalyticsPing(resolutionEventCategory, "Engage", btn.dataset.analyticsLabel);
}

// If "What to do next" link is clicked, scroll to the list of recommendations.
Expand Down
2 changes: 2 additions & 0 deletions template-helpers/breach-detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ function getBreachDetail(args) {
};
}

// Determine which product promo to show
breachDetail.promo = getPromoStrings(args);

const BREACH_RESOLUTION_ENABLED = (AppConstants.BREACH_RESOLUTION_ENABLED === "1");
if (BREACH_RESOLUTION_ENABLED && args.data.root.affectedEmails) {
Expand Down
93 changes: 93 additions & 0 deletions template-helpers/product-promos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use strict";

const AppConstants = require("./../app-constants");

const { localize } = require("./hbs-helpers");

function productPromos(locales, promoUtms, promoKey) {
const productPromos = {
"monitor": {
promoHeadline: localize(locales, "monitor-promo-headline"),
promoBody: localize(locales, "monitor-promo-body"),
promoCta: localize(locales, "sign-up-for-alerts"),
promoId: "promo-monitor",
promoUrl: `${AppConstants.SERVER_URL}/oauth/init` + promoUtms,
fxaEntrypoint: true,
},
"fx-mobile": {
promoHeadline: localize(locales, "mobile-promo-headline"),
promoBody: localize(locales, "mobile-promo-body"),
promoCta: localize(locales, "mobile-promo-cta"),
promoId: "promo-mobile",
promoUrl: "http://mozilla.org/firefox/mobile" + promoUtms,
},
"lockwise": {
promoHeadline: localize(locales, "promo-lockwise-headline"),
promoBody: localize(locales, "lockwise-promo-body"),
promoCta: localize(locales, "promo-lockwise-cta"),
promoId: "promo-lockwise",
promoUrl: "https://bhqf.adj.st/?adjust_t=6tteyjo&adj_deeplink=lockwise%3A%2F%2F&adj_fallback=https%3A%2F%2Fwww.mozilla.org%2Fen-US%2Ffirefox%2Flockwise" + promoUtms,
},
"fpn": {
promoHeadline: localize(locales, "fpn-promo-headline"),
promoBody: localize(locales, "promo-fpn-body"),
promoCta: localize(locales, "promo-fpn-cta"),
promoId: "promo-fpn",
promoUrl: "https://fpn.firefox.com" + promoUtms,
},
"fx-ecosystem": {
promoHeadline: localize(locales, "ecosystem-promo-headline"),
promoBody: localize(locales, "ecosystem-promo-body"),
promoCta: localize(locales, "promo-ecosystem-cta"),
promoId: "promo-ecosystem",
promoUrl: "https://www.mozilla.org/firefox" + promoUtms,
},
};

if (productPromos[promoKey]) {
return productPromos[promoKey];
}
productPromos["fx-ecosystem"];
}


function getPromoStrings(args) {
const templateData = args.data.root;
const locales = templateData.req.supportedLocales;
const breach = templateData.featuredBreach;
const promoUtms = "?utm_source=fx-monitor&utm_medium=referral&utm_campaign=promo-banner&utm_content=desktop";

// show Monitor sign up promo if there is no signed in user
if (!templateData.req.session.user) {
return productPromos(locales, promoUtms, "monitor");
}

const userAgent = templateData.req.headers["user-agent"];
const browserIsMobileFirefox = (
/Mobile; rv:61.0/i.test(userAgent) ||
/FxiOS/i.test(userAgent)
);
const PRODUCT_PROMOS_ENABLED = (AppConstants.PRODUCT_PROMOS_ENABLED === "1");
if (PRODUCT_PROMOS_ENABLED) {

// show promo for mobile unless the user is on Firefox Mobile
if (!browserIsMobileFirefox) {
return productPromos(locales, promoUtms, "fx-mobile");
}

// show promo for FPN if IP addresses were exposed
if (breach.DataClasses.includes("ip-addresses") && locales[0] === "en") {
return productPromos(locales, promoUtms, "fpn");
}

// Don't show Lockwise banner until Monitor is whitelisted and UITour is implemented
// if (breach.DataClasses.includes("passwords")) {
// return productPromos(locales, promoUtms, "lockwise");
// }
}
return productPromos(locales, promoUtms, "fx-ecosystem");
}

module.exports = {
getPromoStrings,
};
Loading

0 comments on commit 82e8b9d

Please sign in to comment.