From 70fe96edd191e0ef8241bafed1634432f97f2c37 Mon Sep 17 00:00:00 2001 From: Punam Dahiya Date: Thu, 27 Feb 2020 18:52:46 +0000 Subject: [PATCH] Bug 1616370 - Simplified welcome page r=k88hudson Differential Revision: https://phabricator.services.mozilla.com/D63406 --HG-- extra : moz-landing-system : lando --- .eslintignore | 1 + browser/components/newtab/.eslintrc.js | 1 + .../components/newtab/AboutNewTabService.jsm | 26 + .../content/aboutwelcome.bundle.js | 448 ++++++++++++++++++ .../aboutwelcome/content/aboutwelcome.css | 22 + .../aboutwelcome/content/aboutwelcome.html | 26 + .../content-src/aboutwelcome/aboutwelcome.jsx | 31 ++ .../aboutwelcome/aboutwelcome.scss | 32 ++ .../aboutwelcome/components/FxCards.jsx | 46 ++ .../aboutwelcome/components/HeroText.jsx | 19 + .../content-src/lib/aboutwelcome-utils.js | 104 ++++ browser/components/newtab/jar.mn | 1 + browser/components/newtab/package.json | 4 +- .../newtab/webpack.aboutwelcome.config.js | 24 + browser/components/newtab/yamscripts.yml | 4 +- 15 files changed, 787 insertions(+), 2 deletions(-) create mode 100644 browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js create mode 100644 browser/components/newtab/aboutwelcome/content/aboutwelcome.css create mode 100644 browser/components/newtab/aboutwelcome/content/aboutwelcome.html create mode 100644 browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx create mode 100644 browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss create mode 100644 browser/components/newtab/content-src/aboutwelcome/components/FxCards.jsx create mode 100644 browser/components/newtab/content-src/aboutwelcome/components/HeroText.jsx create mode 100644 browser/components/newtab/content-src/lib/aboutwelcome-utils.js create mode 100644 browser/components/newtab/webpack.aboutwelcome.config.js diff --git a/.eslintignore b/.eslintignore index 1124ca189ba3..257875983b43 100644 --- a/.eslintignore +++ b/.eslintignore @@ -47,6 +47,7 @@ browser/extensions/pdfjs/content/web browser/components/pocket/content/panels/js/tmpl.js # Ignore newtab files +browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js browser/components/newtab/data/ browser/components/newtab/logs/ diff --git a/browser/components/newtab/.eslintrc.js b/browser/components/newtab/.eslintrc.js index 7a658dfefa84..5da69079a88f 100644 --- a/browser/components/newtab/.eslintrc.js +++ b/browser/components/newtab/.eslintrc.js @@ -35,6 +35,7 @@ module.exports = { { // These files use fluent-dom to insert content "files": [ + "content-src/aboutwelcome/components/HeroText.jsx", "content-src/asrouter/templates/OnboardingMessage/**", "content-src/asrouter/templates/FirstRun/**", "content-src/asrouter/templates/Trailhead/**", diff --git a/browser/components/newtab/AboutNewTabService.jsm b/browser/components/newtab/AboutNewTabService.jsm index 2310a03afabe..4f254e079747 100644 --- a/browser/components/newtab/AboutNewTabService.jsm +++ b/browser/components/newtab/AboutNewTabService.jsm @@ -7,6 +7,10 @@ "use strict"; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); @@ -20,6 +24,17 @@ ChromeUtils.defineModuleGetter( "resource:///modules/AboutNewTab.jsm" ); +const PREF_SEPARATE_ABOUT_WELCOME = "browser.aboutwelcome.enabled"; +const SEPARATE_ABOUT_WELCOME_URL = + "resource://activity-stream/aboutwelcome/aboutwelcome.html"; + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "isSeparateAboutWelcome", + PREF_SEPARATE_ABOUT_WELCOME, + false +); + const TOPIC_APP_QUIT = "quit-application-granted"; const TOPIC_CONTENT_DOCUMENT_INTERACTIVE = "content-document-interactive"; @@ -142,6 +157,14 @@ AboutNewTabService.prototype = { break; } + // Bail out early for separate about:welcome URL + if ( + isSeparateAboutWelcome && + win.location.pathname.includes("welcome") + ) { + break; + } + const onLoaded = () => { const debugString = this._activityStreamDebug ? "-dev" : ""; @@ -254,6 +277,9 @@ AboutNewTabService.prototype = { * This is calculated in the same way the default URL is. */ get welcomeURL() { + if (isSeparateAboutWelcome) { + return SEPARATE_ABOUT_WELCOME_URL; + } return this.defaultURL; }, diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js new file mode 100644 index 000000000000..601deacad17d --- /dev/null +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -0,0 +1,448 @@ +/*! + * + * NOTE: This file is generated by webpack from aboutwelcome.jsx + * using the npm bundle task. + * + */ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _components_HeroText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); +/* harmony import */ var _components_FxCards__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); +/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + + + +class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent { + sendTelemetry(ping) {// TBD: Handle telemetry messages + } + + render() { + const { + props + } = this; + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: "trailheadCards" + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: "trailheadCardsInner" + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_HeroText__WEBPACK_IMPORTED_MODULE_2__["HeroText"], { + title: props.title, + subtitle: props.subtitle + }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_FxCards__WEBPACK_IMPORTED_MODULE_3__["FxCards"], { + cards: props.cards, + sendTelemetry: this.sendTelemetry + }))); + } + +} + +AboutWelcome.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_4__["DEFAULT_WELCOME_CONTENT"]; +react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(AboutWelcome, null), document.getElementById("root")); + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + +module.exports = React; + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +module.exports = ReactDOM; + +/***/ }), +/* 3 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HeroText", function() { return HeroText; }); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const HeroText = props => { + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", { + className: "welcome-title", + "data-l10n-id": props.title.string_id + }), props.subtitle && props.subtitle.string_id && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", { + className: "welcome-subtitle", + "data-l10n-id": props.subtitle.string_id + })); +}; + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FxCards", function() { return FxCards; }); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); +/* harmony import */ var _asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6); +/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7); +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + +class FxCards extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent { + onCardAction(action) { + let actionUpdates = {}; + let UTMTerm = "utm_term_separate_welcome"; + + if (action.type === "OPEN_URL") { + let url = new URL(action.data.args); + Object(_asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__["addUtmParams"])(url, UTMTerm); + actionUpdates = { + data: { ...action.data, + args: url.toString() + } + }; + } + + _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].handleUserAction({ + data: { ...action, + ...actionUpdates + } + }); + } + + render() { + const { + props + } = this; + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: `trailheadCardGrid show` + }, props.cards.map(card => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__["OnboardingCard"], _extends({ + key: card.id, + message: card, + className: "trailheadCard", + sendUserActionTelemetry: props.sendTelemetry, + onAction: this.onCardAction, + UISurface: "ABOUT_WELCOME" + }, card))))); + } + +} + +/***/ }), +/* 5 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addUtmParams", function() { return addUtmParams; }); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * BASE_PARAMS keys/values can be modified from outside this file + */ +const BASE_PARAMS = { + utm_source: "activity-stream", + utm_campaign: "firstrun", + utm_medium: "referral" +}; +/** + * Takes in a url as a string or URL object and returns a URL object with the + * utm_* parameters added to it. If a URL object is passed in, the paraemeters + * are added to it (the return value can be ignored in that case as it's the + * same object). + */ + +function addUtmParams(url, utmTerm) { + let returnUrl = url; + + if (typeof returnUrl === "string") { + returnUrl = new URL(url); + } + + Object.keys(BASE_PARAMS).forEach(key => { + returnUrl.searchParams.append(key, BASE_PARAMS[key]); + }); + returnUrl.searchParams.append("utm_term", utmTerm); + return returnUrl; +} + +/***/ }), +/* 6 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingCard", function() { return OnboardingCard; }); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + } + + onClick() { + const { + props + } = this; + const ping = { + event: "CLICK_BUTTON", + message_id: props.id, + id: props.UISurface + }; + props.sendUserActionTelemetry(ping); + props.onAction(props.content.primary_button.action, props.message); + } + + render() { + const { + content + } = this.props; + const className = this.props.className || "onboardingMessage"; + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: className + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: `onboardingMessageImage ${content.icon}` + }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: "onboardingContent" + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", { + className: "onboardingTitle", + "data-l10n-id": content.title.string_id + }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", { + className: "onboardingText", + "data-l10n-id": content.text.string_id + })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", { + className: "onboardingButtonContainer" + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", { + "data-l10n-id": content.primary_button.label.string_id, + className: "button onboardingButton", + onClick: this.onClick + })))); + } + +} + +/***/ }), +/* 7 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AboutWelcomeUtils", function() { return AboutWelcomeUtils; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_WELCOME_CONTENT", function() { return DEFAULT_WELCOME_CONTENT; }); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +const AboutWelcomeUtils = { + handleUserAction({ + data: action + }) { + switch (action.type) { + case "OPEN_URL": + window.open(action.data.args); + break; + } + } + +}; +const DEFAULT_WELCOME_CONTENT = { + title: { + string_id: "onboarding-welcome-header" + }, + subtitle: { + string_id: "onboarding-fullpage-welcome-subheader" + }, + cards: [{ + content: { + title: { + string_id: "onboarding-data-sync-title" + }, + text: { + string_id: "onboarding-data-sync-text2" + }, + icon: "devices", + primary_button: { + label: { + string_id: "onboarding-data-sync-button2" + }, + action: { + type: "OPEN_URL", + addFlowParams: true, + data: { + args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", + where: "tabshifted" + } + } + } + }, + id: "TRAILHEAD_CARD_2", + order: 1, + blockOnClick: false + }, { + content: { + title: { + string_id: "onboarding-firefox-monitor-title" + }, + text: { + string_id: "onboarding-firefox-monitor-text2" + }, + icon: "ffmonitor", + primary_button: { + label: { + string_id: "onboarding-firefox-monitor-button" + }, + action: { + type: "OPEN_URL", + data: { + args: "https://monitor.firefox.com/", + where: "tabshifted" + } + } + } + }, + id: "TRAILHEAD_CARD_3", + order: 2, + blockOnClick: false + }, { + content: { + title: { + string_id: "onboarding-mobile-phone-title" + }, + text: { + string_id: "onboarding-mobile-phone-text" + }, + icon: "mobile", + primary_button: { + label: { + string_id: "onboarding-mobile-phone-button" + }, + action: { + type: "OPEN_URL", + data: { + args: "https://www.mozilla.org/firefox/mobile/", + where: "tabshifted" + } + } + } + }, + id: "TRAILHEAD_CARD_6", + order: 6, + blockOnClick: false + }] +}; + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css new file mode 100644 index 000000000000..4c4eb0380887 --- /dev/null +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +body { + --grey-subtitle: $grey-60; } + +.welcome-container { + background: var(--trailhead-cards-background-color); } + +.trailheadCardGrid { + margin-top: 32px; } + +.trailheadCards .welcome-title { + margin-bottom: 5px; + line-height: 52px; } + +.trailheadCards .welcome-subtitle { + font-size: 28px; + font-weight: 200; + margin: 6px 0 0; + color: var(--grey-subtitle); + line-height: 42px; } diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.html b/browser/components/newtab/aboutwelcome/content/aboutwelcome.html new file mode 100644 index 000000000000..1289551b2acc --- /dev/null +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx new file mode 100644 index 000000000000..c10be74a1ab8 --- /dev/null +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import ReactDOM from "react-dom"; +import { HeroText } from "./components/HeroText"; +import { FxCards } from "./components/FxCards"; +import { DEFAULT_WELCOME_CONTENT } from "../lib/aboutwelcome-utils"; + +class AboutWelcome extends React.PureComponent { + sendTelemetry(ping) { + // TBD: Handle telemetry messages + } + + render() { + const { props } = this; + return ( +
+
+ + +
+
+ ); + } +} + +AboutWelcome.defaultProps = DEFAULT_WELCOME_CONTENT; + +ReactDOM.render(, document.getElementById("root")); diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss new file mode 100644 index 000000000000..cd2fcddfbf9f --- /dev/null +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss @@ -0,0 +1,32 @@ +// sass-lint:disable no-css-comments +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +$grey-60: #4A4A4F; + +body { + --grey-subtitle: $grey-60; +} + +.welcome-container { + background: var(--trailhead-cards-background-color); +} + +.trailheadCardGrid { + margin-top: 32px; +} + +.trailheadCards { + .welcome-title { + margin-bottom: 5px; + line-height: 52px; + } + + .welcome-subtitle { + font-size: 28px; + font-weight: 200; + margin: 6px 0 0; + color: var(--grey-subtitle); + line-height: 42px; + } +} diff --git a/browser/components/newtab/content-src/aboutwelcome/components/FxCards.jsx b/browser/components/newtab/content-src/aboutwelcome/components/FxCards.jsx new file mode 100644 index 000000000000..ce02436ed89a --- /dev/null +++ b/browser/components/newtab/content-src/aboutwelcome/components/FxCards.jsx @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import { addUtmParams } from "../../asrouter/templates/FirstRun/addUtmParams"; +import { OnboardingCard } from "../../asrouter/templates/OnboardingMessage/OnboardingMessage"; +import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils"; + +export class FxCards extends React.PureComponent { + onCardAction(action) { + let actionUpdates = {}; + let UTMTerm = "utm_term_separate_welcome"; + + if (action.type === "OPEN_URL") { + let url = new URL(action.data.args); + addUtmParams(url, UTMTerm); + actionUpdates = { data: { ...action.data, args: url.toString() } }; + } + + AboutWelcomeUtils.handleUserAction({ + data: { ...action, ...actionUpdates }, + }); + } + + render() { + const { props } = this; + return ( + +
+ {props.cards.map(card => ( + + ))} +
+
+ ); + } +} diff --git a/browser/components/newtab/content-src/aboutwelcome/components/HeroText.jsx b/browser/components/newtab/content-src/aboutwelcome/components/HeroText.jsx new file mode 100644 index 000000000000..9131efdd78c4 --- /dev/null +++ b/browser/components/newtab/content-src/aboutwelcome/components/HeroText.jsx @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; + +export const HeroText = props => { + return ( + +

+ {props.subtitle && props.subtitle.string_id && ( +

+ )} + + ); +}; diff --git a/browser/components/newtab/content-src/lib/aboutwelcome-utils.js b/browser/components/newtab/content-src/lib/aboutwelcome-utils.js new file mode 100644 index 000000000000..0e3235d74fa1 --- /dev/null +++ b/browser/components/newtab/content-src/lib/aboutwelcome-utils.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export const AboutWelcomeUtils = { + handleUserAction({ data: action }) { + switch (action.type) { + case "OPEN_URL": + window.open(action.data.args); + break; + } + }, +}; + +export const DEFAULT_WELCOME_CONTENT = { + title: { + string_id: "onboarding-welcome-header", + }, + subtitle: { + string_id: "onboarding-fullpage-welcome-subheader", + }, + cards: [ + { + content: { + title: { + string_id: "onboarding-data-sync-title", + }, + text: { + string_id: "onboarding-data-sync-text2", + }, + icon: "devices", + primary_button: { + label: { + string_id: "onboarding-data-sync-button2", + }, + action: { + type: "OPEN_URL", + addFlowParams: true, + data: { + args: + "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", + where: "tabshifted", + }, + }, + }, + }, + id: "TRAILHEAD_CARD_2", + order: 1, + blockOnClick: false, + }, + { + content: { + title: { + string_id: "onboarding-firefox-monitor-title", + }, + text: { + string_id: "onboarding-firefox-monitor-text2", + }, + icon: "ffmonitor", + primary_button: { + label: { + string_id: "onboarding-firefox-monitor-button", + }, + action: { + type: "OPEN_URL", + data: { + args: "https://monitor.firefox.com/", + where: "tabshifted", + }, + }, + }, + }, + id: "TRAILHEAD_CARD_3", + order: 2, + blockOnClick: false, + }, + { + content: { + title: { + string_id: "onboarding-mobile-phone-title", + }, + text: { + string_id: "onboarding-mobile-phone-text", + }, + icon: "mobile", + primary_button: { + label: { + string_id: "onboarding-mobile-phone-button", + }, + action: { + type: "OPEN_URL", + data: { + args: "https://www.mozilla.org/firefox/mobile/", + where: "tabshifted", + }, + }, + }, + }, + id: "TRAILHEAD_CARD_6", + order: 6, + blockOnClick: false, + }, + ], +}; diff --git a/browser/components/newtab/jar.mn b/browser/components/newtab/jar.mn index d49d843f7656..1b22f084185b 100644 --- a/browser/components/newtab/jar.mn +++ b/browser/components/newtab/jar.mn @@ -6,6 +6,7 @@ browser.jar: % resource activity-stream %res/activity-stream/ contentaccessible=yes res/activity-stream/lib/ (./lib/*) res/activity-stream/common/ (./common/*) + res/activity-stream/aboutwelcome/ (./aboutwelcome/content/*) res/activity-stream/vendor/Redux.jsm (./vendor/Redux.jsm) res/activity-stream/vendor/react.js (./vendor/react.js) res/activity-stream/vendor/react-dom.js (./vendor/react-dom.js) diff --git a/browser/components/newtab/package.json b/browser/components/newtab/package.json index 2d0e6757412b..0443b60db695 100644 --- a/browser/components/newtab/package.json +++ b/browser/components/newtab/package.json @@ -85,7 +85,8 @@ "mochitest-debug": "(cd $npm_package_config_mc_dir && ./mach mochitest --jsdebugger browser/components/newtab/test/browser)", "bundle": "npm-run-all bundle:*", "bundle:webpack": "webpack --config webpack.system-addon.config.js", - "bundle:css": "node-sass content-src/styles -o css", + "bundle:welcomeBundle": "webpack --config webpack.aboutwelcome.config.js", + "bundle:css": "node-sass content-src/styles -o css && node-sass content-src/aboutwelcome -o aboutwelcome/content", "bundle:html": "rimraf prerendered && node ./bin/render-activity-stream-html.js", "buildmc": "npm-run-all buildmc:*", "prebuildmc": "rimraf $npm_package_config_mc_dir/browser/components/newtab/", @@ -108,6 +109,7 @@ "startmc:watch": "npm run watchmc", "watchmc": "npm-run-all --parallel watchmc:*", "watchmc:webpack": "npm run bundle:webpack -- --env.development -w", + "watchmc:welcomeBundle": "npm run bundle:welcomeBundle -- --env.development -w", "watchmc:css": "npm run bundle:css && npm run bundle:css -- --source-map-embed --source-map-contents -w", "importmc": "npm-run-all importmc:*", "importmc:src": "rsync --exclude-from .mcignore -a $npm_package_config_mc_dir/browser/components/newtab/ .", diff --git a/browser/components/newtab/webpack.aboutwelcome.config.js b/browser/components/newtab/webpack.aboutwelcome.config.js new file mode 100644 index 000000000000..5cc32487a6c6 --- /dev/null +++ b/browser/components/newtab/webpack.aboutwelcome.config.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const path = require("path"); +const config = require("./webpack.system-addon.config.js"); +const webpack = require("webpack"); +const absolute = relPath => path.join(__dirname, relPath); +const banner = ` +NOTE: This file is generated by webpack from aboutwelcome.jsx +using the npm bundle task. +`; +module.exports = Object.assign({}, config(), { + entry: absolute("content-src/aboutwelcome/aboutwelcome.jsx"), + output: { + path: absolute("aboutwelcome/content"), + filename: "aboutwelcome.bundle.js", + }, + externals: { + react: "React", + "react-dom": "ReactDOM", + }, + plugins: [new webpack.BannerPlugin(banner)], +}); diff --git a/browser/components/newtab/yamscripts.yml b/browser/components/newtab/yamscripts.yml index 53d1b664e75b..85a5f8c6ad30 100644 --- a/browser/components/newtab/yamscripts.yml +++ b/browser/components/newtab/yamscripts.yml @@ -15,7 +15,8 @@ scripts: # bundle: Build all assets for activity stream bundle: webpack: webpack --config webpack.system-addon.config.js - css: node-sass content-src/styles -o css + css: node-sass content-src/styles -o css && node-sass content-src/aboutwelcome -o aboutwelcome/content + welcomeBundle: webpack --config webpack.aboutwelcome.config.js html: rimraf prerendered && node ./bin/render-activity-stream-html.js # buildmc: Export code to mozilla central @@ -51,6 +52,7 @@ scripts: watchmc: _parallel: true webpack: =>bundle:webpack -- --env.development -w + welcomeBundle: =>bundle:welcomeBundle -- --env.development -w css: =>bundle:css && =>bundle:css -- --source-map-embed --source-map-contents -w # importmc: Import changes from mc to github repo