Merge pull request #1797 from mozilla/1789-join-the-party-experiment
Growth Experiment #5 - "Join the Party" Share Link
This commit is contained in:
Коммит
978d4e1e3b
|
@ -2,8 +2,11 @@
|
|||
|
||||
const HIBP = require("../hibp");
|
||||
const DB = require("../db/DB");
|
||||
const AppConstants = require("../app-constants");
|
||||
const { changePWLinks } = require("../lib/changePWLinks");
|
||||
const { getAllEmailsAndBreaches } = require("./user");
|
||||
const { getExperimentFlags } = require("./utils");
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
|
||||
async function getBreachDetail(req, res) {
|
||||
const allBreaches = req.app.locals.breaches;
|
||||
|
@ -35,11 +38,15 @@ async function getBreachDetail(req, res) {
|
|||
}
|
||||
|
||||
const changePWLink = getChangePWLink(featuredBreach);
|
||||
|
||||
const experimentFlags = getExperimentFlags(req, EXPERIMENTS_ENABLED);
|
||||
|
||||
res.render("breach-detail", {
|
||||
title: req.fluentFormat("home-title"),
|
||||
featuredBreach,
|
||||
changePWLink,
|
||||
affectedEmails,
|
||||
experimentFlags,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@ const AppConstants = require("../app-constants");
|
|||
const DB = require("../db/DB");
|
||||
const HIBP = require("../hibp");
|
||||
const { scanResult } = require("../scan-results");
|
||||
const { generatePageToken, getExperimentFlags } = require("./utils");
|
||||
const {
|
||||
generatePageToken,
|
||||
getExperimentBranch,
|
||||
getExperimentFlags,
|
||||
getUTMContents,
|
||||
} = require("./utils");
|
||||
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
|
||||
|
@ -17,7 +22,6 @@ function _getFeaturedBreach(allBreaches, breachQueryValue) {
|
|||
}
|
||||
|
||||
async function home(req, res) {
|
||||
|
||||
const formTokens = {
|
||||
pageToken: AppConstants.PAGE_TOKEN_TIMER > 0 ? generatePageToken(req) : "",
|
||||
csrfToken: req.csrfToken(),
|
||||
|
@ -30,9 +34,31 @@ async function home(req, res) {
|
|||
return res.redirect("/user/dashboard");
|
||||
}
|
||||
|
||||
// Rewrites the /share/{COLOR} links to /
|
||||
if (req.session.redirectHome) {
|
||||
req.session.redirectHome = false;
|
||||
return res.redirect("/");
|
||||
}
|
||||
|
||||
// Note - If utmOverrides get set, they are unenrolled from the experiment
|
||||
const utmOverrides = getUTMContents(req);
|
||||
const experimentFlags = getExperimentFlags(req, EXPERIMENTS_ENABLED);
|
||||
|
||||
// Growth Experiment
|
||||
if (EXPERIMENTS_ENABLED) {
|
||||
getExperimentBranch(req, false, ["en"], {
|
||||
"va": 50,
|
||||
"vb": 50,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (req.params && req.params.breach) {
|
||||
req.query.breach = req.params.breach;
|
||||
}
|
||||
|
||||
if (req.query.breach) {
|
||||
|
||||
featuredBreach = _getFeaturedBreach(req.app.locals.breaches, req.query.breach);
|
||||
|
||||
if (!featuredBreach) {
|
||||
|
@ -53,6 +79,7 @@ async function home(req, res) {
|
|||
pageToken: formTokens.pageToken,
|
||||
csrfToken: formTokens.csrfToken,
|
||||
experimentFlags,
|
||||
utmOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,6 +90,7 @@ async function home(req, res) {
|
|||
pageToken: formTokens.pageToken,
|
||||
csrfToken: formTokens.csrfToken,
|
||||
experimentFlags,
|
||||
utmOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,13 @@ const sha1 = require("../sha1-utils");
|
|||
|
||||
const log = mozlog("controllers.oauth");
|
||||
|
||||
// Growth Experiment
|
||||
const utmArray = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
||||
|
||||
function getUTMNames() {
|
||||
return utmArray;
|
||||
}
|
||||
|
||||
function init(req, res, next, client = FxAOAuthClient) {
|
||||
// Set a random state string in a cookie so that we can verify
|
||||
// the user when they're redirected back to us after auth.
|
||||
|
@ -28,6 +35,10 @@ function init(req, res, next, client = FxAOAuthClient) {
|
|||
url.searchParams.append("action", "email");
|
||||
|
||||
for (const param of fxaParams.searchParams.keys()) {
|
||||
// Growth Experiment
|
||||
if (utmArray.includes(param)) {
|
||||
req.session.utmContents[param] = fxaParams.searchParams.get(param);
|
||||
}
|
||||
url.searchParams.append(param, fxaParams.searchParams.get(param));
|
||||
}
|
||||
|
||||
|
@ -57,6 +68,15 @@ async function confirmed(req, res, next, client = FxAOAuthClient) {
|
|||
|
||||
const returnURL = new URL("/user/dashboard", AppConstants.SERVER_URL);
|
||||
|
||||
// Growth Experiment
|
||||
if (req.session.utmContents) {
|
||||
getUTMNames().forEach(param => {
|
||||
if (req.session.utmContents[param]) {
|
||||
returnURL.searchParams.append(param, req.session.utmContents[param]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is signing up or signing in,
|
||||
// then add new users to db and send email.
|
||||
if (!existingUser || existingUser.fxa_refresh_token === null) {
|
||||
|
|
|
@ -12,7 +12,7 @@ const { resultsSummary } = require("../scan-results");
|
|||
const sha1 = require("../sha1-utils");
|
||||
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
const { getExperimentFlags } = require("./utils");
|
||||
const { getExperimentFlags, getUTMContents } = require("./utils");
|
||||
|
||||
const FXA_MONITOR_SCOPE = "https://identity.mozilla.com/apps/monitor";
|
||||
|
||||
|
@ -229,7 +229,9 @@ async function getDashboard(req, res) {
|
|||
const user = req.user;
|
||||
const allBreaches = req.app.locals.breaches;
|
||||
const { verifiedEmails, unverifiedEmails } = await getAllEmailsAndBreaches(user, allBreaches);
|
||||
const utmOverrides = getUTMContents(req);
|
||||
|
||||
// Growth Experiment
|
||||
const experimentFlags = getExperimentFlags(req, EXPERIMENTS_ENABLED);
|
||||
|
||||
let lastAddedEmail = null;
|
||||
|
@ -248,6 +250,7 @@ async function getDashboard(req, res) {
|
|||
unverifiedEmails,
|
||||
whichPartial: "dashboards/breaches-dash",
|
||||
experimentFlags,
|
||||
utmOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -561,6 +564,18 @@ async function getBreachStats(req, res) {
|
|||
|
||||
|
||||
function logout(req, res) {
|
||||
// Growth Experiment
|
||||
if (EXPERIMENTS_ENABLED && req.session.experimentFlags) {
|
||||
// Persist experimentBranch across session reset
|
||||
const sessionExperimentFlags = req.session.experimentFlags;
|
||||
req.session.reset();
|
||||
req.session.experimentFlags = sessionExperimentFlags;
|
||||
|
||||
// Return
|
||||
res.redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.reset();
|
||||
res.redirect("/");
|
||||
}
|
||||
|
|
|
@ -182,9 +182,23 @@ function getExperimentBranch(req, sorterNum = false, language = false, variation
|
|||
|
||||
}
|
||||
|
||||
function getUTMContents(req) {
|
||||
if (!req) {
|
||||
throw new Error("No request available");
|
||||
}
|
||||
|
||||
// If UTMs are set previously, set them again.
|
||||
if (req.session.utmOverrides) {
|
||||
return req.session.utmOverrides;
|
||||
}
|
||||
|
||||
req.session.utmOverrides = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getExperimentFlags(req, EXPERIMENTS_ENABLED) {
|
||||
if (!req) {
|
||||
throw new Error("No request availabe");
|
||||
throw new Error("No request available");
|
||||
}
|
||||
|
||||
if (req.session.experimentFlags && EXPERIMENTS_ENABLED) {
|
||||
|
@ -208,4 +222,5 @@ module.exports = {
|
|||
hasUserSignedUpForRelay,
|
||||
getExperimentBranch,
|
||||
getExperimentFlags,
|
||||
getUTMContents,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
_Last updated: Aug 8, 2020_
|
||||
|
||||
# Firefox Monitor Experiment Deployment Process
|
||||
|
||||
This is the experiment process plan for Firefox Monitor. It documents how to deploy experiments and take them down.
|
||||
|
||||
## ENV Variable
|
||||
|
||||
Use the variable `EXPERIMENT_ACTIVE` to enable experiments in your environment.
|
||||
|
||||
- If set to `1` - Experiment code is visible and active.
|
||||
- If NOT set `1` – Experiment code is hidden and not active.
|
||||
|
||||
## Tagging System
|
||||
|
||||
To deploy with the ENV variable set to `1`, add `-exp` to your version tag.
|
||||
|
||||
Example: `v12.14.2-exp`
|
||||
|
||||
Any tag without the `-exp` will disable experiments at an ENV level.
|
|
@ -15,6 +15,7 @@ const { FXA } = require("./lib/fxa");
|
|||
const { FluentError } = require("./locale-utils");
|
||||
const mozlog = require("./log");
|
||||
|
||||
const HIBP = require("./hibp");
|
||||
|
||||
const log = mozlog("middleware");
|
||||
|
||||
|
@ -173,6 +174,63 @@ async function requireSessionUser(req, res, next) {
|
|||
next();
|
||||
}
|
||||
|
||||
function getShareUTMs(req, res, next) {
|
||||
// Step 1: See if the user needs to be redirected to the homepage or to a breach-detail page.
|
||||
const generalShareUrls = [
|
||||
"/share/orange", //Header
|
||||
"/share/purple", // Footer
|
||||
"/share/blue", // user/dashboard
|
||||
"/share/",
|
||||
];
|
||||
|
||||
if (generalShareUrls.includes(req.url)) {
|
||||
// If not breach specific, redirect to "/"
|
||||
req.session.redirectHome = true;
|
||||
}
|
||||
|
||||
const inNotInActiveExperiment = (!req.session.experimentFlags);
|
||||
|
||||
// Excluse user from experiment if they don't have any experimentFlags set already.
|
||||
if (inNotInActiveExperiment) {
|
||||
|
||||
// Step 2: Determine if user needs to have share-link UTMs set
|
||||
const colors = [
|
||||
"orange", //Header
|
||||
"purple", // Footer
|
||||
"blue", // user/dashboard
|
||||
];
|
||||
|
||||
const urlArray = req.url.split("/");
|
||||
const color = urlArray.slice(-1)[0];
|
||||
|
||||
req.session.utmOverrides = {
|
||||
campaignName: "shareLinkTraffic",
|
||||
campaignTerm: "default",
|
||||
};
|
||||
|
||||
// Set Color Var in UTM
|
||||
if (color.length && colors.includes(color)) {
|
||||
req.session.utmOverrides.campaignTerm = color;
|
||||
}
|
||||
|
||||
if (color.length && !colors.includes(color)) {
|
||||
const allBreaches = req.app.locals.breaches;
|
||||
const breachName = color;
|
||||
const featuredBreach = HIBP.getBreachByName(allBreaches, breachName);
|
||||
|
||||
if (featuredBreach) {
|
||||
req.session.utmOverrides.campaignTerm = featuredBreach.Name;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude share users
|
||||
req.session.experimentFlags = {
|
||||
excludeFromExperiment: true,
|
||||
};
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
@ -185,4 +243,5 @@ module.exports = {
|
|||
clientErrorHandler,
|
||||
errorHandler,
|
||||
requireSessionUser,
|
||||
getShareUTMs,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
.email-cards.experiment .share-monitor--dashboard {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.email-cards.experiment .share-monitor--dashboard ~ .share-monitor--dashboard {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard {
|
||||
margin: 0 0 3rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard ~ .share-monitor--dashboard {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard img {
|
||||
display: block;
|
||||
margin: 0 auto 1rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard a {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.share-monitor--breach {
|
||||
margin: calc(var(--padding) * 2.5) auto 0;
|
||||
border-radius: 0.7rem;
|
||||
background: var(--monitorGradient);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.share-monitor--breach img {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.share-monitor--breach .inset {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 0.575rem;
|
||||
padding: calc(var(--padding) * 1.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.share-monitor--breach h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.share-monitor--breach a {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.share-monitor--modal {
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.share-monitor--modal-content {
|
||||
background-color: var(--inkDark);
|
||||
max-width: 800px;
|
||||
width: 80vw;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 4px;
|
||||
padding: 2rem;
|
||||
color: #ededf0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-image: url("/img/modal-bg-left.png"), url("/img/modal-bg-right.png");
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
background-position: bottom left, bottom right;
|
||||
|
||||
/* 6:10 ratio */
|
||||
background-size: 120px auto, 200px auto;
|
||||
}
|
||||
|
||||
.share-monitor--modal-text {
|
||||
max-width: 560px;
|
||||
width: 100%;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.share-monitor--modal-text .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-monitor--modal-text h2 {
|
||||
color: #ffffff;
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.share-monitor--modal-text p {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button-modal-close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border: none;
|
||||
background-image: url("/img/svg/x-close-white.svg");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.copy-form {
|
||||
display: flex;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
margin: 1em auto 0.5em;
|
||||
}
|
||||
|
||||
.copy-form-btn {
|
||||
background: var(--blue3);
|
||||
color: white;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
padding: 0 1rem;
|
||||
font-size: 16px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: block;
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.copy-form-btn span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.copy-form-input {
|
||||
background: #ffffff;
|
||||
padding: 0 0 0 1em;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
display: block;
|
||||
width: calc(100% - 80px);
|
||||
max-width: 320px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.share-monitor--modal-content .disclaimer {
|
||||
font-size: 0.75rem;
|
||||
max-width: 340px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.share-monitor--dashboard img {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.share-monitor--dashboard a {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.share-monitor--breach .inset {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.share-monitor--breach img {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
.share-monitor--modal-content {
|
||||
width: 90vw;
|
||||
background-image: none;
|
||||
}
|
||||
.copy-form {
|
||||
display: block;
|
||||
}
|
||||
.copy-form-input {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 1em;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
max-width: 100%;
|
||||
}
|
||||
.copy-form-btn {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
padding: 1em 12px;
|
||||
}
|
||||
.copy-form-btn span {
|
||||
display: inline;
|
||||
}
|
||||
.share-monitor--modal-text h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.1 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 4.7 KiB |
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="64px" height="64px" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>present</title>
|
||||
<g id="Primary-Flow" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Resolving-a-Breach---Return-Visit" transform="translate(-652.000000, -1337.000000)" fill-rule="nonzero">
|
||||
<g id="Email-Addresses" transform="translate(273.000000, 753.000000)">
|
||||
<g id="Breach-List" transform="translate(0.000000, 76.000000)">
|
||||
<g id="Promo-/-Condensed-/-Lockwise" transform="translate(99.000000, 508.000000)">
|
||||
<g id="Group" transform="translate(107.000000, 0.000000)">
|
||||
<g id="firefox_illustrations-fullcolor_recommendation-64" transform="translate(173.000000, 0.000000)">
|
||||
<path d="M64,20.5111111 C64,19.4310876 63.124468,18.5555556 62.0444444,18.5555556 L1.95555556,18.5555556 C0.879554894,18.5652064 0.00965081065,19.4351104 0,20.5111111 L0,32.7777778 L64,32.7777778 L64,20.5111111 Z" id="Path" fill="#FFEA80"></path>
|
||||
<path d="M5.33332985,32.7777778 L5.33332985,61.3288889 C5.3323905,61.7912107 5.52301906,62.2332651 5.85988024,62.5499146 C6.19674143,62.8665641 6.64973114,63.0295109 7.11111111,63 L56.9955556,63 C57.9184847,63 58.6666667,62.2518181 58.6666667,61.3288889 L58.6666667,32.7777778 L5.33332985,32.7777778 Z" id="Path" fill="#FFD567"></path>
|
||||
<polygon id="Path" fill="#FF8A50" points="23.1111111 18.5555556 40.8888889 18.5555556 40.8888889 32.7777778 23.1111111 32.7777778"></polygon>
|
||||
<polygon id="Path" fill="#FF7139" points="23.1111111 32.7777778 40.8888889 32.7777778 40.8888889 63 23.1111111 63"></polygon>
|
||||
<polygon id="Path" fill="#E25920" points="23.1111111 32.7777778 40.8888889 32.7777778 40.8888889 38.1111111 23.1111111 38.1111111"></polygon>
|
||||
<polygon id="Path" fill="#FFBD4F" points="5.33333333 32.7777778 23.1111111 32.7777778 23.1111111 38.1111111 5.33333333 38.1111111"></polygon>
|
||||
<polygon id="Path" fill="#FFBD4F" points="40.8888889 32.7777778 58.6666667 32.7777778 58.6666667 38.1111111 40.8888889 38.1111111"></polygon>
|
||||
<path d="M33.1733333,17.1155556 C21.8666667,11.5866667 19.3955556,6.32444444 20.32,2.52 C16.96,6.07555556 16.3911111,12.9911111 31.3955556,20.2977778 C39.52,16.3155556 43.0755556,12.4933333 44.1066667,9.24 C41.0736406,12.6238459 37.3436905,15.310623 33.1733333,17.1155556 Z" id="Path" fill="#E31587"></path>
|
||||
<path d="M33.1733333,17.1155556 C37.3403654,15.3209947 41.0700462,12.6465407 44.1066667,9.27555556 C44.9555031,6.60772154 44.080198,3.69239078 41.9022222,1.93333333 C38.0622222,-1.51555556 33.1911111,1.61333333 31.4311111,4.97333333 C29.6533333,1.61333333 24.7822222,-1.51555556 20.9422222,1.93333333 C20.7335743,2.11534289 20.5375651,2.31135203 20.3555556,2.52 C19.3955556,6.32444444 21.8666667,11.5866667 33.1733333,17.1155556 L33.1733333,17.1155556 Z" id="Path" fill="#FF298A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 3.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="x-close" width="16" height="16" viewBox="0 0 16 16"><path fill="#ffffff" d="M9.061 8l3.47-3.47a.75.75 0 0 0-1.061-1.06L8 6.939 4.53 3.47a.75.75 0 1 0-1.06 1.06L6.939 8 3.47 11.47a.75.75 0 1 0 1.06 1.06L8 9.061l3.47 3.47a.75.75 0 0 0 1.06-1.061z"></path></svg>
|
После Ширина: | Высота: | Размер: 307 B |
|
@ -0,0 +1,101 @@
|
|||
"use strict";
|
||||
|
||||
/* global ga */
|
||||
|
||||
function selectURL(e, skipAnalyticsPing = false) {
|
||||
const shareModalInput = document.getElementById("shareModalInput");
|
||||
|
||||
// If an a user-init'd focus, send analytics ping
|
||||
if (!skipAnalyticsPing) {
|
||||
sendShareModalPing(shareModalInput);
|
||||
}
|
||||
|
||||
shareModalInput.select();
|
||||
shareModalInput.setSelectionRange(0, 99999);
|
||||
}
|
||||
|
||||
|
||||
function copyURL(e) {
|
||||
sendShareModalPing(e.target);
|
||||
const shareModalInput = document.getElementById("shareModalInput");
|
||||
// Remove select listener after this event is fired. (Dup engagement)
|
||||
shareModalInput.removeEventListener("focus", selectURL);
|
||||
selectURL(null, true);
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function keyPress(e) {
|
||||
if(e.key === "Escape") {
|
||||
closeShareModal();
|
||||
document.removeEventListener("keydown", keyPress);
|
||||
}
|
||||
}
|
||||
|
||||
function closeShareModal() {
|
||||
const shareModal = document.getElementById("shareModal");
|
||||
shareModal.style.display = "none";
|
||||
}
|
||||
|
||||
function sendShareModalPing(el) {
|
||||
if (typeof(ga) !== "undefined") {
|
||||
const eventCategory = "[v2] exp5-share-modal";
|
||||
const eventAction = el.dataset.eventAction;
|
||||
const eventLabel = `Link ID: ${el.dataset.eventLabel}`;
|
||||
const options = {};
|
||||
return ga("send", "event", eventCategory, eventAction, eventLabel, options);
|
||||
}
|
||||
}
|
||||
|
||||
function initShareModal(target, breachText) {
|
||||
const shareModal = document.getElementById("shareModal");
|
||||
const shareModalInput = document.getElementById("shareModalInput");
|
||||
const shareModalCopy = document.getElementById("shareModalCopy");
|
||||
const btnCloseShareModal = document.getElementById("closeShareModal");
|
||||
const shareTextBreach = document.getElementById("shareTextBreach");
|
||||
const shareTextGeneral = document.getElementById("shareTextGeneral");
|
||||
|
||||
const shareModalLocation = target.dataset.eventLabel;
|
||||
|
||||
shareModalCopy.dataset.eventLabel = shareModalLocation;
|
||||
shareModalInput.dataset.eventLabel = shareModalLocation;
|
||||
|
||||
// Set input to correct URL value;
|
||||
shareModalInput.value = target.href;
|
||||
|
||||
// Make modal visible
|
||||
shareModal.style.display = "block";
|
||||
|
||||
if (breachText) {
|
||||
shareTextGeneral.classList.add("hidden");
|
||||
shareTextBreach.classList.remove("hidden");
|
||||
} else {
|
||||
shareTextGeneral.classList.remove("hidden");
|
||||
shareTextBreach.classList.add("hidden");
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", keyPress);
|
||||
shareModalInput.addEventListener("focus", selectURL);
|
||||
shareModalCopy.addEventListener("click", copyURL);
|
||||
btnCloseShareModal.addEventListener("click", closeShareModal);
|
||||
|
||||
shareModal.addEventListener("click", (e)=>{
|
||||
// If the click is INSIDE the modal, ignore it.
|
||||
if (e.target !== shareModal) { return; }
|
||||
closeShareModal();
|
||||
});
|
||||
}
|
||||
|
||||
function openShareModal(target, breach = false) {
|
||||
initShareModal(target, breach);
|
||||
}
|
||||
|
||||
const btnOpenShareModal = document.querySelectorAll(".js-share-modal");
|
||||
|
||||
if (btnOpenShareModal) {
|
||||
btnOpenShareModal.forEach( el => {
|
||||
el.addEventListener("click", (e)=> {
|
||||
e.preventDefault();
|
||||
openShareModal(e.target, e.target.dataset.breach);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -155,6 +155,29 @@ function setGAListeners(){
|
|||
});
|
||||
});
|
||||
|
||||
// Growth Experiment
|
||||
if (document.body.dataset.experiment) {
|
||||
document.querySelectorAll(".ga-growth-ping").forEach((el) => {
|
||||
el.addEventListener("click", async(e) => {
|
||||
// Overwrite current event category for active OAuth buttons
|
||||
if (el.dataset.eventCategory !== "fxa-oauth") {
|
||||
el.dataset.eventCategory = "fxa-oauth";
|
||||
}
|
||||
await sendPing(el, "Click", el.dataset.eventLabel, {transport: "beacon"});
|
||||
});
|
||||
});
|
||||
|
||||
// Set Share Modal GA Params on individual elements
|
||||
document.querySelectorAll(".js-share-modal, .js-share-modal-breach").forEach((el) => {
|
||||
if (el.dataset.label) {
|
||||
el.dataset.eventLabel = el.dataset.label;
|
||||
}
|
||||
|
||||
if (el.dataset.eventCategory !== "exp5-share-modal") {
|
||||
el.dataset.eventCategory = "exp5-share-modal";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem("gaInit", true);
|
||||
|
@ -201,6 +224,29 @@ function setGAListeners(){
|
|||
ga("set", "anonymizeIp", true);
|
||||
ga("set", "dimension6", `${document.body.dataset.signedInUser}`);
|
||||
|
||||
|
||||
// Set Share URL UTMs
|
||||
if ( document.body.dataset.utm_campaign && !document.body.dataset.experiment ) {
|
||||
// campaignName
|
||||
ga("set", "campaignName", `${document.body.dataset.utm_campaign}`);
|
||||
ga("set", "dimension9", `${document.body.dataset.utm_campaign}`);
|
||||
// campaignKeyword / term
|
||||
ga("set", "campaignKeyword", `${document.body.dataset.utm_term}`);
|
||||
ga("set", "dimension8", `${document.body.dataset.utm_term}`);
|
||||
}
|
||||
|
||||
// Growth Experiment
|
||||
if (document.body.dataset.experiment) {
|
||||
// If an experiment is active, set the "Growth Experiment Version"
|
||||
// Custom Dimension to whichever branch is active.
|
||||
ga("set", "dimension7", `${document.body.dataset.experiment}`);
|
||||
ga("set", "dimension8", `${document.body.dataset.experiment}`);
|
||||
ga("set", "dimension9", `${document.body.dataset.utm_campaign}`);
|
||||
ga("set", "campaignName", `${document.body.dataset.utm_campaign}`);
|
||||
ga("set", "campaignKeyword", `${document.body.dataset.utm_term}`);
|
||||
}
|
||||
|
||||
|
||||
ga("send", "pageview", {
|
||||
hitCallback: function() {
|
||||
removeUtmsFromUrl();
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
/* global sendRecommendationPings */
|
||||
/* global ga */
|
||||
|
||||
const utmParams = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content" ];
|
||||
|
||||
if (typeof TextEncoder === "undefined") {
|
||||
const cryptoScript = document.createElement("script");
|
||||
const scripts = document.getElementsByTagName("script")[0];
|
||||
|
@ -62,6 +64,35 @@ function doOauth(el, {emailWatch = false} = {}) {
|
|||
}
|
||||
});
|
||||
|
||||
// Growth Experiment: OAuth Entry Point IDs are unique to the experiment.
|
||||
const oAuthEntryPointIds = [
|
||||
"fx-monitor-check-for-breaches-blue-btn",
|
||||
"fx-monitor-find-out-blue-btn",
|
||||
"fx-monitor-alert-me-blue-btn-top",
|
||||
"fx-monitor-alert-me-blue-btn-bottom",
|
||||
];
|
||||
|
||||
if (oAuthEntryPointIds.includes(el.dataset.entrypoint)) {
|
||||
// Growth Experiment: Reset UTMs from in-line body tag data elements.
|
||||
utmParams.forEach(key => {
|
||||
if (document.body.dataset[key]) {
|
||||
url.searchParams.delete(key);
|
||||
url.searchParams.append(key, document.body.dataset[key]);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof(ga) !== "undefined" && document.body.dataset.experiment) {
|
||||
ga("send", {
|
||||
hitType: "event",
|
||||
eventCategory: document.body.dataset.utm_campaign,
|
||||
eventAction: document.body.dataset.experiment,
|
||||
eventLabel: el.dataset.entrypoint,
|
||||
transport: "beacon",
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!sessionStorage) {
|
||||
window.location.assign(url);
|
||||
return;
|
||||
|
@ -141,6 +172,26 @@ function handleFormSubmits(formEvent) {
|
|||
|
||||
const formClassList = thisForm.classList;
|
||||
|
||||
// Growth Experiment
|
||||
if (formClassList.contains("skip")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Growth Experiment
|
||||
if (document.body.dataset.experiment) {
|
||||
const scanFormActionURL = new URL(thisForm.action);
|
||||
|
||||
utmParams.forEach(key => {
|
||||
if (document.body.dataset[key]) {
|
||||
scanFormActionURL.searchParams.append(key, document.body.dataset[key]);
|
||||
}
|
||||
});
|
||||
|
||||
const revisedActionURL = scanFormActionURL.pathname + scanFormActionURL.search;
|
||||
|
||||
thisForm.action = revisedActionURL.toString();
|
||||
}
|
||||
|
||||
if (thisForm.email && !isValidEmail(email)) {
|
||||
sendPing(thisForm, "Failure");
|
||||
formClassList.add("invalid");
|
||||
|
|
|
@ -5,10 +5,17 @@ const csrf = require("csurf");
|
|||
|
||||
const {home, getAboutPage, getAllBreaches, getBentoStrings, getSecurityTips, notFound} = require("../controllers/home");
|
||||
|
||||
const { getShareUTMs } = require("../middleware");
|
||||
|
||||
const router = express.Router();
|
||||
const csrfProtection = csrf();
|
||||
|
||||
router.get("/", csrfProtection, home);
|
||||
router.get("/share/orange", csrfProtection, getShareUTMs, home);
|
||||
router.get("/share/purple", csrfProtection, getShareUTMs, home);
|
||||
router.get("/share/blue", csrfProtection, getShareUTMs, home);
|
||||
router.get("/share/:breach", csrfProtection, getShareUTMs, home);
|
||||
router.get("/share/", csrfProtection, getShareUTMs, home);
|
||||
router.get("/about", getAboutPage);
|
||||
router.get("/breaches", getAllBreaches);
|
||||
router.get("/security-tips", getSecurityTips);
|
||||
|
|
|
@ -5,6 +5,7 @@ const { URL } = require("url");
|
|||
const HIBP = require("./hibp");
|
||||
const sha1 = require("./sha1-utils");
|
||||
|
||||
// Growth Experiment
|
||||
const AppConstants = require("./app-constants");
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
const { getExperimentFlags } = require("./controllers/utils");
|
||||
|
@ -14,6 +15,7 @@ const scanResult = async(req, selfScan=false) => {
|
|||
const allBreaches = req.app.locals.breaches;
|
||||
let scannedEmail = null;
|
||||
|
||||
// Growth Experiment
|
||||
const experimentFlags = getExperimentFlags(req, EXPERIMENTS_ENABLED);
|
||||
|
||||
const title = req.fluentFormat("scan-title");
|
||||
|
|
|
@ -62,9 +62,12 @@ function getBreachCategory(breach) {
|
|||
function getSortedDataClasses(locales, breach, isUserBrowserFirefox=false, isUserLocaleEnUs=false, changePWLink=false) {
|
||||
const priorityDataClasses = getAllPriorityDataClasses(isUserBrowserFirefox, isUserLocaleEnUs, changePWLink);
|
||||
|
||||
const experimentFlags = breach.experimentFlags;
|
||||
|
||||
const sortedDataClasses = {
|
||||
priority: [],
|
||||
lowerPriority: [],
|
||||
experimentFlags: experimentFlags,
|
||||
};
|
||||
|
||||
const dataClasses = breach.DataClasses;
|
||||
|
@ -108,6 +111,7 @@ function getGenericFillerRecs(locales, numberOfRecsNeeded) {
|
|||
}
|
||||
|
||||
function getBreachDetail(args) {
|
||||
const experimentFlags = args.data.root.experimentFlags;
|
||||
const { locales, breach, changePWLink, isUserBrowserFirefox } = getVars(args);
|
||||
const { sortedDataClasses, recommendations } = getSortedDataClassesAndRecs(locales, breach, isUserBrowserFirefox, changePWLink);
|
||||
const breachCategory = getBreachCategory(breach);
|
||||
|
@ -141,6 +145,8 @@ function getBreachDetail(args) {
|
|||
copy: breachExposedPasswords ? localize(locales, "rec-section-subhead") : localize(locales, "rec-section-subhead-no-pw"),
|
||||
recommendationsList: recommendations,
|
||||
},
|
||||
|
||||
experimentFlags: experimentFlags,
|
||||
};
|
||||
|
||||
// Add correct "What is a ... breach" copy.
|
||||
|
|
|
@ -13,6 +13,7 @@ function userIsOnRelayWaitList(args) {
|
|||
}
|
||||
|
||||
function getBreachesDashboard(args) {
|
||||
const experimentFlags = args.data.root.experimentFlags;
|
||||
const verifiedEmails = args.data.root.verifiedEmails;
|
||||
const locales = args.data.root.req.supportedLocales;
|
||||
let breachesFound = false;
|
||||
|
@ -72,6 +73,7 @@ function getBreachesDashboard(args) {
|
|||
const emailCards = {
|
||||
verifiedEmails: verifiedEmails,
|
||||
breachesFound: breachesFound,
|
||||
experimentFlags: experimentFlags,
|
||||
};
|
||||
|
||||
return args.fn(emailCards);
|
||||
|
|
|
@ -3,8 +3,13 @@
|
|||
const { getStrings } = require("./hbs-helpers");
|
||||
const { LocaleUtils } = require("./../locale-utils");
|
||||
|
||||
const AppConstants = require("../app-constants");
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
const { getExperimentFlags } = require("../controllers/utils");
|
||||
|
||||
function getFooterLinks(args) {
|
||||
const locales = args.data.root.req.supportedLocales;
|
||||
|
||||
const footerLinks = [
|
||||
{
|
||||
title: "About Firefox Monitor",
|
||||
|
@ -28,6 +33,21 @@ function getFooterLinks(args) {
|
|||
},
|
||||
];
|
||||
|
||||
// Growth Experiment: Only add the footer share line if user is on VB branch
|
||||
const experimentFlags = getExperimentFlags(args.data.root.req, EXPERIMENTS_ENABLED);
|
||||
if (EXPERIMENTS_ENABLED && experimentFlags.treatmentBranch) {
|
||||
const shareMonitorFooter = {
|
||||
title: "Share Monitor",
|
||||
stringId: "share-monitor",
|
||||
href: "/share/purple",
|
||||
experiment: "js-share-modal",
|
||||
label: "share-modal--footer",
|
||||
};
|
||||
|
||||
footerLinks.splice(2, 0, shareMonitorFooter);
|
||||
}
|
||||
|
||||
|
||||
return getStrings(footerLinks, locales);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,12 @@ function getString (id, args) {
|
|||
function getStrings(stringArr, locales) {
|
||||
stringArr.forEach(string => {
|
||||
const stringId = string.stringId;
|
||||
string.stringId =LocaleUtils.fluentFormat(locales, stringId);
|
||||
// Growth Experiment: Catch EN* Experimental Strings
|
||||
if (stringId === "share-monitor") {
|
||||
string.stringId = "Share Monitor";
|
||||
} else {
|
||||
string.stringId =LocaleUtils.fluentFormat(locales, stringId);
|
||||
}
|
||||
});
|
||||
return stringArr;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
const { getStrings, getFxaUrl } = require("./hbs-helpers");
|
||||
const { LocaleUtils } = require("./../locale-utils");
|
||||
|
||||
const AppConstants = require("../app-constants");
|
||||
const EXPERIMENTS_ENABLED = (AppConstants.EXPERIMENT_ACTIVE === "1");
|
||||
|
||||
function getSignedInAs(args) {
|
||||
const locales = args.data.root.req.supportedLocales;
|
||||
const userEmail = args.data.root.req.session.user.primary_email;
|
||||
|
@ -59,6 +62,19 @@ function fxaMenuLinks(args) {
|
|||
},
|
||||
];
|
||||
|
||||
// Growth Experiment: Only add the avatar share link if user is on VB branch
|
||||
if (EXPERIMENTS_ENABLED && args.data.root.req.session.experimentFlags.treatmentBranch) {
|
||||
const shareMonitorFooter = {
|
||||
title: "Share Monitor",
|
||||
stringId: "share-monitor",
|
||||
href: "/share/orange",
|
||||
experiment: "js-share-modal",
|
||||
label: "share-modal--header",
|
||||
};
|
||||
|
||||
fxaLinks.unshift(shareMonitorFooter);
|
||||
}
|
||||
|
||||
return getStrings(fxaLinks, locales);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<!--Exposed Data Classes -->
|
||||
<section class="detail-section bg-white jst-cntr flx flx-col">
|
||||
{{> breach-detail-content-group dataClasses sectionId="dataClasses"}}
|
||||
{{> breach-detail-content-group dataClasses sectionId="dataClasses" name=./breach.Name treatmentBranch=./experimentFlags.treatmentBranch}}
|
||||
</section>
|
||||
|
||||
<!-- What to do tips -->
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
<link rel="icon" href="/img/favicons/favicon-128.png" sizes="128x128" />
|
||||
<link rel="icon" href="/img/favicons/favicon-256.png" sizes="256x256" />
|
||||
</head>
|
||||
<body {{> analytics/default_dataset }} data-bento-app-id="fx-monitor">
|
||||
<body {{> analytics/default_dataset }} data-bento-app-id="fx-monitor"
|
||||
{{#if experimentFlags.experimentBranch }} {{> analytics/experiment }} {{/if}}
|
||||
{{#if utmOverrides.campaignName }} {{> analytics/share }} {{/if}} >
|
||||
{{> header/header }}
|
||||
{{{ body }}}
|
||||
{{> footer }}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
data-utm_term={{experimentFlags.experimentBranch}}
|
||||
data-experiment={{experimentFlags.experimentBranch}}
|
||||
data-utm_campaign="growthuserflow5"
|
|
@ -0,0 +1,2 @@
|
|||
data-utm_term="{{utmOverrides.campaignTerm}}"
|
||||
data-utm_campaign="{{utmOverrides.campaignName}}"
|
|
@ -48,5 +48,8 @@
|
|||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if treatmentBranch}}
|
||||
{{> experiment--share-promo-breach breachName=./breach }}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -38,9 +38,10 @@
|
|||
<h2 class="pref-headline">{{ getString "email-addresses-title" }}</h2>
|
||||
{{> dashboards/manage-email-link variableClass="hide-mobile"}}
|
||||
</div>
|
||||
<div class="email-cards flx flx-col jst-cntr">
|
||||
<div class="email-cards flx flx-col jst-cntr {{#if experimentFlags.treatmentBranch}}experiment{{/if}}">
|
||||
{{#each this.verifiedEmails }}
|
||||
{{> email-card }}
|
||||
{{> experiment--share-promo-dashboard}}
|
||||
{{/each}}
|
||||
{{#if breachesFound }}
|
||||
{{> hibp-attribution variableClasses="dash-attribution txt-cntr"}}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<div id="shareModal" class="share-monitor--modal">
|
||||
<div class="share-monitor--modal-content txt-cntr">
|
||||
<button class="button-modal-close" id="closeShareModal" type="button" name="button"></button>
|
||||
<div class="share-monitor--modal-text">
|
||||
<h2>Share Monitor</h2>
|
||||
<p id="shareTextGeneral" class="share-monitor--modal-promo">Copy this link to share Monitor with people you care about. You can paste the link into email, text messages, social media - wherever you want.</p>
|
||||
<p id="shareTextBreach" class="hidden share-monitor--modal-promo">Share this link with someone you know who may have been involved in this breach. You can paste the link into email, text messages, social media - wherever you want.</p>
|
||||
<label class="copy-form" for="shareModalCopy">
|
||||
<input class="copy-form-input" data-event-action="Focus/Select on URL Input" id="shareModalInput" type="text" name="" value="" readonly="readonly">
|
||||
<button class="copy-form-btn" data-event-action="Click Copy URL Button" id="shareModalCopy" type="button" name="button">Copy <span>Link</span> </button>
|
||||
</label>
|
||||
<p class="disclaimer">Monitor won’t track whether or not you share this link. They can check their results for free. No account required.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
<!-- {{ name }} -->
|
||||
<div class="share-monitor--breach">
|
||||
<div class="inset">
|
||||
<img src="/img/svg/present.svg" alt="Gift present with a bow">
|
||||
<div class="text">
|
||||
<h3 class="txt-purple7">Know someone who may have been affected by this breach?</h3>
|
||||
<a class="blue-link js-share-modal" data-ga-link="" data-breach="true" data-event-category="exp5-share-modal" data-event-label="share-modal--breach-detail" href="/share/{{ name }}">Send them an invitation to check their email address</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
<div class="share-monitor--dashboard txt-cntr">
|
||||
<img class="" src="/img/svg/present.svg" alt="Gift present with a bow">
|
||||
<h3 class="section-headline txt-purple7">Help protect your family and friends</h3>
|
||||
<a class="blue-link js-share-modal" data-ga-link="" data-event-category="exp5-share-modal" data-event-label="share-modal--dashboard" href="/share/blue">Send them an invitation to check their email address</a>
|
||||
</div>
|
|
@ -7,10 +7,12 @@
|
|||
</li>
|
||||
{{#each (getFooterLinks)}}
|
||||
<li class="footer-link-wrapper">
|
||||
<a class="footer-link" href="{{ this.href }}" {{> analytics/outbound-link eventLabel=this.title }} {{#if openNewWindow}} target="_blank" {{/if}} rel="noopener">
|
||||
<a class="footer-link {{this.experiment}}" href="{{ this.href }}" {{> analytics/outbound-link eventLabel=this.title }} {{#if this.label}} data-label="{{ this.label }}" {{/if}} {{#if openNewWindow}} target="_blank" {{/if}} rel="noopener">
|
||||
{{ this.stringId }}
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
{{> experiment--share-modal}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="fxa-menu-wrapper">
|
||||
<div id="avatar-wrapper" class="avatar-wrapper {{ addClass }}" tabindex="0" aria-label="{{ getString "open-fxa-menu" }}">
|
||||
<div id="avatar-wrapper" data-ga-link="" data-event-category="avatar-menu" data-event-label="open-fxa-menu" class="avatar-wrapper {{ addClass }}" tabindex="0" aria-label="{{ getString "open-fxa-menu" }}">
|
||||
<img alt="{{ req.session.user.primary_email }}" class="avatar" src="{{ req.session.user.fxa_profile_json.avatar }}"/>
|
||||
</div>
|
||||
<div id="fxa-menu" class="fxa-menu">
|
||||
|
@ -8,7 +8,7 @@
|
|||
<span class="signed-in-as">{{{ getSignedInAs }}}</span>
|
||||
</div>
|
||||
{{#each (fxaMenuLinks)}}
|
||||
<a class="fxa-menu-link" {{> analytics/internal-link eventLabel=this.title }} href="{{ this.href }}">{{ this.stringId }}</a>
|
||||
<a class="fxa-menu-link {{this.experiment}}" {{> analytics/internal-link eventLabel=this.title }} {{#if this.label}} data-label="{{ this.label }}" {{/if}} href="{{ this.href }}">{{ this.stringId }}</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<link rel="stylesheet" href="/css/security-tips.css">
|
||||
<link rel="stylesheet" href="/css/sign-up-banner.css">
|
||||
<link rel="stylesheet" href="/css/subpage.css">
|
||||
<link rel="stylesheet" href="/css/share.css">
|
||||
|
||||
<script type="text/javascript" src="/js/all-breaches/all-breaches.js"></script>
|
||||
<script type="text/javascript" src="/js/analytics_dnt-helper.js" defer></script>
|
||||
|
@ -36,6 +37,7 @@
|
|||
<script type="text/javascript" src="/js/privacy-defender.js" defer></script>
|
||||
<script type="text/javascript" src="/js/scan-email.js" defer></script>
|
||||
<script type="text/javascript" src="/js/scan-results.js" defer></script>
|
||||
<script type="text/javascript" src="/js/experiment.js" defer></script>
|
||||
|
||||
{{else}}
|
||||
<link rel="stylesheet" href="/dist/app.min.css">
|
||||
|
|
Загрузка…
Ссылка в новой задаче