diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9f34a79b1..363f5ddf8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1993,6 +1993,173 @@ "enhanced_anti_tracking": { "message": "Enhanced Anti-Tracking" }, + + + "panel_rewards_view__reward": { + "message": "Reward" + }, + "panel_rewards_view__rewards": { + "message": "Rewards" + }, + "OFFERS_BEGIN": { + "message": "" + }, + "myoffrz_affiliate_link": { + "message": "affiliate link" + }, + "myoffrz_copy_and_go": { + "message": "copy & go" + }, + "myoffrz_feedback_option1": { + "message": "I already used the offer" + }, + "myoffrz_feedback_option2": { + "message": "It's not a good deal" + }, + "myoffrz_feedback_option3": { + "message": "It's not relevant to me" + }, + "myoffrz_feedback_option4": { + "message": "Other reasons" + }, + "myoffrz_feedback_title": { + "message": "Feedback" + }, + "myoffrz_get_code": { + "message": "Get code" + }, + "myoffrz_offer_removed": { + "message": "This offer was removed." + }, + "myoffrz_optional": { + "message": "Optional" + }, + "myoffrz_powered_by_offrz": { + "message": "POWERED BY MyOffrz" + }, + "myoffrz_show_code": { + "message": "Show code" + }, + "myoffrz_show_more": { + "message": "Show more" + }, + "myoffrz_skip": { + "message": "Skip" + }, + "myoffrz_welcome_link": { + "message": "How It Works" + }, + "myoffrz_welcome_text": { + "message": "As you browse the Internet, we are searching for attractive offers based on your interests." + }, + "myoffrz_welcome_title": { + "message": "Hello!" + }, + "myoffrz_why_offers_text": { + "message": "No personal data leaves your device. Your browser automatically identifies what you might be interested in based on your previous use. " + }, + "myoffrz_why_see_these_offers": { + "message": "Why do I see these offers?" + }, + "myoffrz_why_see_this": { + "message": "Why do I see this?" + }, + "myoffrz_badge_text_new": { + "message": "NEW" + }, + "myoffrz_conditions": { + "message": "Conditions" + }, + "myoffrz_turnoff_rewards": { + "message": "Turn off Ghostery Rewards" + }, + + + "OFFERS_ALREADY_TRANSLATED": { + "message": "" + }, + "myoffrz_help": { + "message": "Help" + }, + "myoffrz_learn_more": { + "message": "Learn more" + }, + "myoffrz_send": { + "message": "Send" + }, + "myoffrz_settings": { + "message": "Settings" + }, + "myoffrz_tooltip_new_offer": { + "message": "New Reward discovered!" + }, + "myoffrz_turnoff_notification": { + "message": "Ghostery Rewards is now off. You can always turn it on in your Settings or the Rewards dashboard." + }, + "myoffrz_optin_description": { + "message": "Would you like to receive offers and discounts from trusted Ghostery partners?" + }, + "myoffrz_optin_title": { + "message": "New Reward discovered!" + }, + "myoffrz_no": { + "message": "no" + }, + "myoffrz_yes": { + "message": "yes" + }, + "myoffrz_rewards_new": { + "message": "New Reward discovered!" + }, + "myoffrz_apply_code": { + "message": "Apply code" + }, + "myoffrz_cancel": { + "message": "Cancel" + }, + "myoffrz_copied": { + "message": "Copied" + }, + "myoffrz_copy_code": { + "message": "Copy code" + }, + "myoffrz_expires_in_day": { + "message": "Available for 1 day" + }, + "myoffrz_expires_in_days": { + "message": "Available for $COUNT$ days", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "myoffrz_expires_in_hour": { + "message": "Available for 1 hour" + }, + "myoffrz_expires_in_hours": { + "message": "Available for $COUNT$ hours", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "myoffrz_expires_in_minute": { + "message": "Available for 1 minute" + }, + "myoffrz_expires_in_minutes": { + "message": "Available for $COUNT$ minutes", + "placeholders": { + "count": { + "content": "$1" + } + } + }, + "OFFERS_ENDS": { + "message": "" + }, + "unknown": { "message": "Unknown" }, diff --git a/app/content-scripts/rewards/HotDog.jsx b/app/content-scripts/rewards/HotDog.jsx deleted file mode 100644 index 365462653..000000000 --- a/app/content-scripts/rewards/HotDog.jsx +++ /dev/null @@ -1,84 +0,0 @@ -/** - * HotDog Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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, { Component } from 'react'; -import { withRouter } from 'react-router-dom'; - -/** - * @class Create the Rewards "HotDog", aka UI element - * @memberOf RewardsContentScript - */ -class HotDog extends Component { - constructor(props) { - super(props); - this.state = { - closed: false - }; - this.shownSignal = false; - this.iframeEl = window.parent.document.getElementById('ghostery-iframe-container'); - - if (this.iframeEl) { - this.iframeEl.classList = ''; - this.iframeEl.classList.add('hot-dog'); - } - - this.ghostyStar = `url(${chrome.extension.getURL('app/images/rewards/ghosty-star.svg')})`; - this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/light-x.svg')})`; - this.close = this.close.bind(this); - this.navigate = this.navigate.bind(this); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - if (nextProps.reward && nextProps.reward !== null && !this.shownSignal) { - this.props.actions.sendSignal('offer_notification_hotdog'); - this.shownSignal = true; - } - } - - navigate() { - this.props.actions.sendSignal('offer_click_hotdog'); - if (this.iframeEl) { - this.iframeEl.classList.add('offer-card'); - } - this.props.history.push('/offercard'); - } - - close() { - this.props.actions.sendSignal('offer_closed_hotdog'); - if (this.iframeEl) { - this.iframeEl.classList = ''; - } - this.setState({ - closed: true - }); - } - - render() { - return ( -
- { this.state.closed !== true && ( -
-
-
- {t('rewards_new_text')} -
-
-
-
- )} -
- ); - } -} - -export default withRouter(HotDog); diff --git a/app/content-scripts/rewards/Notification.jsx b/app/content-scripts/rewards/Notification.jsx deleted file mode 100644 index 9aaed0ffe..000000000 --- a/app/content-scripts/rewards/Notification.jsx +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Notification Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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, { Component } from 'react'; - -/** - * @class Handles notifications for Rewards - * @memberOf RewardsContentScript - */ -class Notification extends Component { - constructor(props) { - super(props); - this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/white-x.svg')})`; - this.state = { - closed: false - }; - - this.closeNotification = this.closeNotification.bind(this); - } - - closeNotification(confirm) { - if (typeof this.props.data.closeCallback === 'function') { - this.props.data.closeCallback(confirm); - } - this.setState({ - closed: true - }); - } - - renderOptoutImage() { - return ( -
- -
- ); - } - - renderOptoutLink() { - return ( - this.props.data.textLink.callback()} - > - {this.props.data.textLink.text} - - ); - } - - renderHeadline() { - return ( -
- {t('rewards_first_prompt_headline')} -
- ); - } - - renderLabels() { - return ( -
- - {t('rewards_exclusive')} - - {t('rewards_best_offer')} -
- ); - } - - renderClose() { - return ( -
{ this.closeNotification(); }} - style={{ backgroundImage: this.closeIcon }} - /> - ); - } - - render() { - return ( -
- {!this.state.closed && ( -
-
-
-
- {this.props.data.type === 'first-prompt' && this.renderOptoutImage()} - {this.props.data.type !== 'first-prompt' && this.renderClose()} -
- {this.props.data.type === 'first-prompt' && this.renderLabels()} - {this.props.data.type === 'first-prompt' && this.renderHeadline()} - {this.props.data.message} - {' '} - {this.props.data.type === 'first-prompt' && this.renderOptoutLink()} -
- {this.props.data.buttons && ( -
- - -
- )} - {this.props.data.textLink && this.props.data.type !== 'first-prompt' - && ( - { - if (this.props.data.textLink.callback) { - this.props.data.textLink.callback(); - } - }} - > - {this.props.data.textLink.text} - - ) - } -
-
-
- )} -
- ); - } -} - -export default Notification; diff --git a/app/content-scripts/rewards/OfferCard.jsx b/app/content-scripts/rewards/OfferCard.jsx deleted file mode 100644 index c97e906e9..000000000 --- a/app/content-scripts/rewards/OfferCard.jsx +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Offer Card Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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, { Component } from 'react'; -import msgModule from '../utils/msg'; -import { computeTimeDelta } from '../../panel/utils/utils'; -import Notification from './Notification'; -import Settings from './Settings'; -import ClickOutside from '../../panel/components/BuildingBlocks/ClickOutside'; -import Tooltip from '../../panel/components/Tooltip'; - -const msg = msgModule('rewards'); -const { sendMessage } = msg; - -/** - * @class Generate Rewards offer card - * @memberOf RewardsContentScript - */ -class OfferCard extends Component { - constructor(props) { - super(props); - const { - attrs: { isCodeHidden } = {}, - offer_data: offerData = {}, - } = props.reward || {}; - const { ui_info: { template_data: templateData = {} } = {} } = offerData; - this.state = { - code: isCodeHidden ? '*****' : templateData.code, - closed: false, - copyText: t('rewards_copy_code'), - showPrompt: this.props.conf.rewardsPromptAccepted ? false : 1, - showSettings: false, - rewardUI: templateData, - shouldShowCross: this.props.conf.rewardsPromptAccepted, - }; - - this.iframeEl = window.parent.document.getElementById('ghostery-iframe-container'); - if (this.iframeEl) { - this.iframeContentDocument = this.iframeEl.contentDocument; - this.iframeEl.classList = ''; - this.iframeEl.classList.add('offer-card'); - } - this.rewardPictureEl = null; - - this.betaLogo = `url(${chrome.extension.getURL('app/images/rewards/ghostery-rewards-beta.png')})`; - this.closeIcon = `url(${chrome.extension.getURL('app/images/drawer/x.svg')})`; - this.ghostyGrey = `url(${chrome.extension.getURL('app/images/rewards/ghosty-grey.svg')})`; - this.kebabIcon = `url(${chrome.extension.getURL('app/images/rewards/settings-kebab.svg')})`; - this.poweredByMyoffrz = `url(${chrome.extension.getURL('app/images/rewards/powered-by-myoffrz.svg')})`; - - this.closeOfferCard = this.closeOfferCard.bind(this); - this.copyCode = this.copyCode.bind(this); - this.disableRewards = this.disableRewards.bind(this); - this.disableRewardsNotification = this.disableRewardsNotification.bind(this); - this.toggleSettings = this.toggleSettings.bind(this); - this.handleImageLoaded = this.handleImageLoaded.bind(this); - this.handlePrompt = this.handlePrompt.bind(this); - this.redeem = this.redeem.bind(this); - - this.notifications = [ - { - type: 'first-prompt', - buttons: true, - message: t('rewards_first_prompt_extended'), - textLink: { - href: 'https://www.ghostery.com/faqs/what-is-ghostery-rewards/', - text: t('learn_more'), - callback: () => { - this.props.actions.sendSignal('offer_first_learn'); - sendMessage('ping', 'rewards_first_learn_more'); - }, - }, - closeCallback: (option) => { this.handlePrompt(1, option); }, - }, - { - type: 'second-prompt', - buttons: true, - message: t('rewards_second_prompt'), - textLink: {}, - closeCallback: (option) => { this.handlePrompt(2, option); }, - }, - { - type: 'disabled-message', - buttons: false, - message: t('rewards_disable_notification'), - textLink: { - text: t('rewards_disable_confirm'), - callback: this.closeOfferCard, - }, - closeCallback: this.closeOfferCard, - }, - ]; - - const { reward } = props; - this.props.actions.messageBackground('rewardSeen', { - offerId: reward.offer_id - }); - this.props.actions.sendSignal('offer_shown'); - this.props.actions.sendSignal('offer_dsp_session'); - } - - componentDidMount() { - this.props.actions.addRewardSeenListener(); - if (this.state.rewardUI.picture_url) { - const bgImg = new Image(); - bgImg.onload = () => { - this.rewardPictureEl.style.backgroundImage = `url(${bgImg.src})`; - }; - bgImg.src = this.state.rewardUI.picture_url; - } - } - - copyCode() { - this.props.actions.sendSignal('code_copied'); - - // 'copied' feedback for user - this.setState({ - copyText: `${t('rewards_code_copied')}!`, - code: this.state.rewardUI.code, - }, () => { - this.offerCardRef.querySelector('.reward-code-input').select(); - document.execCommand('copy'); - }); - - // prevent multiple clicks - clearTimeout(this.timeout); - this.timeout = setTimeout(() => { - this.setState({ - copyText: t('rewards_copy_code') - }); - }, 3000); - } - - toggleSettings() { - if (!this.state.showSettings) { - this.props.actions.sendSignal('offer_settings'); - } - this.setState({ - showSettings: !this.state.showSettings - }); - } - - disableRewards() { - const signal = { - actionId: 'rewards_off', - origin: 'rewards-hotdog-card', - type: 'action-signal', - }; - sendMessage('setPanelData', { enable_offers: false, signal }); - // TODO catch - sendMessage('ping', 'rewards_off'); - } - - disableRewardsNotification() { - this.disableRewards(); - this.setState({ - showPrompt: 3 - }); - } - - handlePrompt(promptNumber, option) { - const reject = () => { - this.props.actions.sendSignal('offer_first_optout'); - sendMessage('ping', 'rewards_first_reject_optout'); - this.disableRewards(); - this.closeOfferCard(); - }; - if (promptNumber === 1) { - if (!option) { - reject(); - return; - } - this.props.actions.messageBackground('rewardsPromptOptedIn'); - this.props.actions.sendSignal('offer_first_optin'); - sendMessage('ping', 'rewards_first_accept'); - } else if (promptNumber === 2) { - if (option) { - reject(); - return; - } - this.props.actions.sendSignal('offer_first_optlater'); - sendMessage('ping', 'rewards_first_reject_optin'); - this.closeOfferCard(); - } - this.props.actions.messageBackground('rewardsPromptAccepted'); - this.setState({ - showPrompt: false, - shouldShowCross: true, - }); - } - - closeOfferCard() { - this.props.actions.removeFocusListener(); - if (this.iframeEl) { - this.iframeEl.classList = ''; - } - this.setState({ - closed: true - }); - } - - redeem() { - this.setState({ code: this.state.rewardUI.code }); - this.props.actions.sendSignal('offer_ca_action'); - } - - handleImageLoaded(e) { - e.target.classList.remove('hide'); - } - - renderNotification(type) { - const notificationProps = this.notifications[type]; - return ( - - ); - } - - renderExpiresText() { - const { expirationMs } = this.props.reward.offer_data; - const expireDays = Math.round((new Date()).setDate(new Date().getDate() + expirationMs / 1000 / 60 / 60 / 24)); - const delta = computeTimeDelta(new Date(expireDays), new Date()); - const { count, type } = delta; - if (count === 1) { - return t(`rewards_expires_in_${type.slice(0, -1)}`); - } - return t(`rewards_expires_in_${type}`, [count]); - } - - renderCross() { - return ( -
{ this.props.actions.sendSignal('offer_closed_card'); this.closeOfferCard(); }} - style={{ backgroundImage: this.closeIcon }} - /> - ); - } - - render() { - return ( - // @TODO condition for hide class -
{ this.offerCardRef = ref; }} className="ghostery-rewards-component"> - { this.state.closed !== true && ( -
-
-
-
- {this.state.shouldShowCross && this.renderCross()} -
-
-
-
-
- -
-
{ this.kebabRef = node; }} - /> - { this.state.showSettings && ( -
- - { this.props.actions.sendSignal('about_ghostery_rewards', false); }} disable={this.disableRewardsNotification} /> - -
- )} -
- { this.state.rewardUI.picture_url && ( -
-
-
{ this.rewardPictureEl = node; }} /> -
-
- )} -
- {/*
*/} -
- { this.state.rewardUI.benefit } -
- - { this.state.rewardUI.headline } - - - { this.state.rewardUI.desc } - -
-
- { this.state.rewardUI.code && ( -
-
- {this.state.code} - -
- {this.state.copyText} -
- )} -
- - { this.renderExpiresText() } - - {this.state.rewardUI.conditions && ( -
- { t('rewards_terms_conditions') } - -
- )} -
- - {this.state.rewardUI.call_to_action.text} - -
-
-
-
- {this.props.conf.rewardsPromptAccepted && - {t('rewards_disable')} - } -
-
- -
- -
-
- { this.state.showPrompt === 1 && - this.renderNotification(0) - } - { this.state.showPrompt === 2 && - this.renderNotification(1) - } - { this.state.showPrompt === 3 && - this.renderNotification(2) - } -
- )} -
- ); - } -} - -export default OfferCard; diff --git a/app/content-scripts/rewards/Settings.jsx b/app/content-scripts/rewards/Settings.jsx deleted file mode 100644 index 65c9524a6..000000000 --- a/app/content-scripts/rewards/Settings.jsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Settings Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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, { Component } from 'react'; - -/** - * @class Handles settings for Rewards - * @memberOf RewardsContentScript - */ -class Settings extends Component { - constructor(props) { - super(props); - this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/white-x.svg')})`; - this.state = { - closed: false - }; - - this.close = this.close.bind(this); - } - - close() { - this.setState({ - closed: true - }); - if (typeof this.props.closeCallback === 'function') { - this.props.closeCallback(); - } - } - - render() { - return ( -
- {!this.state.closed && ( -
-
-
- {t('rewards_about')} -
{ this.close(); this.props.disable(); }}> - {t('rewards_disable')} -
-
- {/*
{t('rewards_settings')}
-
{t('rewards_delete')}
*/} -
-
- )} -
- ); - } -} - -export default Settings; diff --git a/app/content-scripts/rewards/index.jsx b/app/content-scripts/rewards/index.jsx deleted file mode 100644 index 0befce2bb..000000000 --- a/app/content-scripts/rewards/index.jsx +++ /dev/null @@ -1,249 +0,0 @@ -/** - * Ghostery Rewards - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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 - */ - -/** - * @namespace RewardsContentScript - */ - -/* eslint no-use-before-define: 0 */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Router, Route } from 'react-router-dom'; -import retargetEvents from 'react-shadow-dom-retarget-events'; -import HotDog from './HotDog'; -import OfferCard from './OfferCard'; -import msgModule from '../utils/msg'; -import history from '../../panel/utils/history'; -import globals from '../../../src/classes/Globals'; - -const msg = msgModule('rewards'); -const { sendMessage } = msg; -const { onMessage } = globals; -const channelsSupported = (typeof chrome.runtime.connect === 'function'); - -/** - * @class Injects Ghostery Rewards components - * @memberOf RewardsContentScript - */ -class RewardsApp { - constructor() { - this.reward = null; - this.conf = null; - this.rewardsContainer = document.createElement('div'); - this.rewardsApp = document.createElement('div'); - this.rewardsIframe = null; - this.iframeStyle = null; - this.port = null; - this.mainView = null; - this.rewardsApp.id = 'ghostery-rewards-app'; - this.rewardsApp.className = 'show'; - - this.handleMessages = this.handleMessages.bind(this); - this.sendSignal = this.sendSignal.bind(this); - this.messageBackground = this.messageBackground.bind(this); - this.removeFocusListener = this.removeFocusListener.bind(this); - this.focusListener = this.focusListener.bind(this); - this.addRewardSeenListener = this.addRewardSeenListener.bind(this); - - this.actions = { - sendSignal: this.sendSignal, - messageBackground: this.messageBackground, - removeFocusListener: this.removeFocusListener, - addRewardSeenListener: this.addRewardSeenListener - }; - } - - init() { - if (document.readyState === 'complete') { - this.start(); - } else { - document.onreadystatechange = () => { - if (document.readyState === 'complete') { - this.start(); - } - }; - } - } - - start() { - if (document.head.createShadowRoot || document.head.attachShadow) { - this.renderShadow(); - } else { - // use iframe to encapsulate CSS - fallback for everything else besides chrome - this.renderIframe(); - } - } - - renderReact() { - const MainView = this.mainView; - ReactDOM.render(, this.rewardsApp); - } - - renderShadow() { - // Use shadowDOM to encapsulate CSS - document.body.appendChild(this.rewardsContainer); - const shadowRoot = this.rewardsContainer.attachShadow({ mode: 'open' }); // Get the shadow root - shadowRoot.appendChild(this.rewardsApp); // Append React root to shadow root - retargetEvents(shadowRoot); // Reattach React click events - - this.mainView = props => ( - - - - } - /> - - } - /> - - } - /> - - ); - this.initListener(); - } - - renderIframe() { - this.rewardsIframe = document.createElement('iframe'); - this.rewardsIframe.id = 'ghostery-iframe-container'; - this.rewardsIframe.onload = () => { - this.iframeStyle = document.createElement('link'); - this.iframeStyle.rel = 'stylesheet'; - this.iframeStyle.type = 'text/css'; - this.iframeStyle.href = chrome.extension.getURL('dist/css/rewards_styles.css'); - - this.rewardsIframe.contentWindow.document.head.appendChild(this.iframeStyle); - this.rewardsContainer = this.rewardsIframe.contentWindow.document.body; - - this.rewardsApp.classList.add('iframe-child'); - this.rewardsContainer.appendChild(this.rewardsApp); - this.mainView = props => ( - -
- - } - /> - - } - /> - - } - /> -
-
- ); - this.initListener(); - }; - document.body.appendChild(this.rewardsIframe); - } - - initListener() { - if (channelsSupported) { - this.port = chrome.runtime.connect({ name: 'rewardsPort' }); - if (this.port) { - this.port.onMessage.addListener(this.handleMessages); - this.port.postMessage({ name: 'rewardsLoaded' }); - } - } else { - // TODO listen for this in background.js - sendMessage('rewardsLoaded'); - onMessage.addListener(this.handleMessages); - } - } - - handleMessages(request) { - if (request.name === 'showOffer' || request.name === 'showHotDog') { - this.reward = request.reward; - this.conf = request.conf; - } - - if (request.name === 'showOffer') { - history.push('/offercard'); - } - - // in FF 61 react sees some elements in iframes as non interactive - // react overwrites the event handlers for these elements with `function noop() {}` - // https://github.com/facebook/react/blob/16.4.2-dev/packages/react-dom/src/client/ReactDOMFiberComponent.js#L235 - // using setTimeout to render react is a temporary workaround - setTimeout(() => { - this.renderReact(); - }); - } - - focusListener() { - this.sendSignal('offer_shown'); - } - - addRewardSeenListener() { - window.addEventListener('focus', this.focusListener); - } - - removeFocusListener() { - window.removeEventListener('focus', this.focusListener); - } - - messageBackground(name, message, signal = false) { - if ( - signal && - this.port && - (message.actionId !== 'rewards_off') && - (message.actionId !== 'rewards_on') - ) { - this.port.postMessage({ - name, - message - }); - } else { - sendMessage(name, message); - } - } - - sendSignal(actionId, offerSignal = true) { - // Cliqz metrics - const offerId = offerSignal ? this.reward.offer_id : null; - const message = { - offerId, - actionId, - origin: 'rewards-hotdog-card', - type: !offerSignal ? 'action-signal' : 'offer-action-signal', - }; - this.messageBackground('rewardSignal', message, true); - } -} - -const rewardsApp = new RewardsApp(); -rewardsApp.init(); diff --git a/app/images/rewards/best-offer.svg b/app/images/rewards/best-offer.svg deleted file mode 100644 index 13a0f4d8d..000000000 --- a/app/images/rewards/best-offer.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - best-offer-icon13x12 - Created with Sketch. - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/images/rewards/exclusive.svg b/app/images/rewards/exclusive.svg deleted file mode 100644 index 61bb751d5..000000000 --- a/app/images/rewards/exclusive.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - exclusive-icon13x12 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/images/rewards/ghostery-rewards-beta.png b/app/images/rewards/ghostery-rewards-beta.png deleted file mode 100644 index 929d71fc8..000000000 Binary files a/app/images/rewards/ghostery-rewards-beta.png and /dev/null differ diff --git a/app/images/rewards/ghostery_O.png b/app/images/rewards/ghostery_O.png deleted file mode 100644 index 40bb85692..000000000 Binary files a/app/images/rewards/ghostery_O.png and /dev/null differ diff --git a/app/images/rewards/light-x.svg b/app/images/rewards/light-x.svg deleted file mode 100644 index c7006483f..000000000 --- a/app/images/rewards/light-x.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/rewards/powered-by-myoffrz.svg b/app/images/rewards/powered-by-myoffrz.svg deleted file mode 100644 index 26f202dd8..000000000 --- a/app/images/rewards/powered-by-myoffrz.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - powered_by - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/images/rewards/settings-kebab.svg b/app/images/rewards/settings-kebab.svg deleted file mode 100644 index bc9b11daa..000000000 --- a/app/images/rewards/settings-kebab.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/images/rewards/white-x.svg b/app/images/rewards/white-x.svg deleted file mode 100644 index 9dd5cd1a4..000000000 --- a/app/images/rewards/white-x.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/panel/actions/RewardsActions.js b/app/panel/actions/RewardsActions.js index 1c75bac48..a8d61933d 100644 --- a/app/panel/actions/RewardsActions.js +++ b/app/panel/actions/RewardsActions.js @@ -14,8 +14,6 @@ import { UPDATE_REWARDS_DATA, TOGGLE_OFFERS_ENABLED, - REMOVE_OFFER, - SET_OFFER_READ, SEND_SIGNAL } from '../constants/constants'; @@ -43,29 +41,6 @@ export function toggleOffersEnabled(enabled) { }; } -/** - * Removes a reward from the rewards list - * @param {String} id The ID of the reward we want to remove. - * @return {Object} - */ -export function removeOffer(id) { - return { - type: REMOVE_OFFER, - data: { id } - }; -} - -/** - * Sets the unread status of an offer to false - * @param {String} id the ID of the reward we want to update. - * @return {Object} - */ -export function setOfferRead(id) { - return { - type: SET_OFFER_READ, - data: { id } - }; -} // TODO the reducer calls getRewardMessage // determine whether it would be better to simply call getRewardMessage directly where sendSignal is called diff --git a/app/panel/actions/__tests__/RewardsActions.js b/app/panel/actions/__tests__/RewardsActions.js index b182350a3..eccf4fff9 100644 --- a/app/panel/actions/__tests__/RewardsActions.js +++ b/app/panel/actions/__tests__/RewardsActions.js @@ -18,8 +18,6 @@ import * as rewardsActions from '../RewardsActions'; import { UPDATE_REWARDS_DATA, TOGGLE_OFFERS_ENABLED, - REMOVE_OFFER, - SET_OFFER_READ, SEND_SIGNAL } from '../../constants/constants'; @@ -63,30 +61,6 @@ describe('app/panel/actions/RewardsActions.js', () => { expect(actions).toEqual([expectedPayload]); }); - test('removeOffer action should return correctly', () => { - const initialState = {}; - const store = mockStore(initialState); - - const id = 'test_reward_id'; - const expectedPayload = { data: { id }, type: REMOVE_OFFER }; - store.dispatch(rewardsActions.removeOffer(id)); - - const actions = store.getActions(); - expect(actions).toEqual([expectedPayload]); - }); - - test('setOfferRead action should return correctly', () => { - const initialState = {}; - const store = mockStore(initialState); - - const id = 'test_reward_id'; - const expectedPayload = { data: { id }, type: SET_OFFER_READ }; - store.dispatch(rewardsActions.setOfferRead(id)); - - const actions = store.getActions(); - expect(actions).toEqual([expectedPayload]); - }); - test('sendSignal offer-action-signal should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/panel/components/BuildingBlocks/RewardDetail.jsx b/app/panel/components/BuildingBlocks/RewardDetail.jsx deleted file mode 100644 index ec9e9b3a6..000000000 --- a/app/panel/components/BuildingBlocks/RewardDetail.jsx +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Reward Detail Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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 { computeTimeDelta } from '../../utils/utils'; -import { sendMessage } from '../../utils/msg'; -import Tooltip from '../Tooltip'; - -/** - * @class Implements the details for a single reward for for the Rewards Panel - * @memberof PanelBuildingBlocks - */ -class RewardDetail extends React.Component { - constructor(props) { - super(props); - this.state = { - copyText: t('rewards_copy_code'), - code: props.isCodeHidden ? '*****' : props.code, - }; - - // Event Bindings - this.handleCopyClick = this.handleCopyClick.bind(this); - this.handleRedeemClick = this.handleRedeemClick.bind(this); - } - - /** - * Lifecycle event - */ - UNSAFE_componentWillMount() { - this.props.actions.setOfferRead(this.props.id); - } - - /** - * Handles clicking the copy button - */ - handleCopyClick() { - // Show a toast notification - this.props.actions.showNotification({ - text: t('rewards_code_copied_toast_notification'), - classes: 'purple', - }); - - // Update and reset Copy Code text - this.setState({ - code: this.props.code, - copyText: t('rewards_code_copied'), - }, () => { - // Copy the reward code - this.copyNode.querySelector('input').select(); - document.execCommand('copy'); - }); - setTimeout(() => { - this.setState({ copyText: t('rewards_copy_code') }); - }, 3000); - - // Send a signal to the offers black box - this.props.actions.sendSignal('code_copied', this.props.id); - } - - /** - * Handles clicking the redeem button - * @param {Object} event the event object - */ - handleRedeemClick(event) { - event.preventDefault(); - this.props.actions.sendSignal('offer_ca_action', this.props.id); - sendMessage('openNewTab', { - url: this.props.redeemUrl, - become_active: true, - }); - window.close(); - } - - /** - * Helper render function for the expires text. - * @return {JSX} JSX for the Rewards Detail - */ - renderExpiresText() { - const { expires } = this.props; - const delta = computeTimeDelta(new Date(expires), new Date()); - const { count, type } = delta; - if (count === 1) { - return t(`rewards_expires_in_${type.slice(0, -1)}`); - } - return t(`rewards_expires_in_${type}`, [count]); - } - - /** - * React's required render function. Returns JSX - * @return {JSX} JSX for rendering the details for a Reward - */ - render() { - const { - code, - text, - description, - benefit, - conditions, - pictureUrl, - redeemText - } = this.props; - - return ( -
-
-
-
- {benefit && ( -
- { benefit } -
- )} - {text && ( -
- { text } -
- )} - {description && ( -
- { description } -
- )} - {code && ( -
- { this.copyNode = node; }}> - {this.state.code} - - - - {this.state.copyText} - -
- )} -
-
- { this.renderExpiresText() } -
- {conditions && ( -
- - { t('rewards_terms_conditions') } - - -
- )} -
-
- { redeemText } -
-
- ); - } -} - -export default RewardDetail; diff --git a/app/panel/components/BuildingBlocks/RewardListItem.jsx b/app/panel/components/BuildingBlocks/RewardListItem.jsx deleted file mode 100644 index 398100889..000000000 --- a/app/panel/components/BuildingBlocks/RewardListItem.jsx +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Reward List Item Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * 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 { Link } from 'react-router-dom'; -import ClassNames from 'classnames'; -import { computeTimeDelta } from '../../utils/utils'; - -/** - * @class Implements a single reward in a list for the Rewards Panel - * @memberof PanelBuildingBlocks - */ -class RewardListItem extends React.Component { - constructor(props) { - super(props); - this.state = {}; - - // Event Bindings - this.handleClick = this.handleClick.bind(this); - this.clickCloseButton = this.clickCloseButton.bind(this); - - // Send Signal - this.props.actions.sendSignal('offer_shown', this.props.id); - } - - /** - * Handle the click event: everything normal unless it is disabled - * @param {Object} event the click event - */ - handleClick(event) { - const { disabled, unread, id } = this.props; - if (disabled) { - event.preventDefault(); - } else { - if (unread) { - this.props.actions.sendSignal('offer_click_specific_new', id); - } - this.props.actions.sendSignal('offer_click_specific', id); - this.props.actions.sendSignal('offer_dsp_session', id); - } - } - - /** - * Handle clicking on the close button - * @param {Object} event the click event - */ - clickCloseButton(event) { - // Prevent the event from propagating and linking to the Reward Detail - event.preventDefault(); - event.stopPropagation(); - - const { id } = this.props; - this.props.actions.removeOffer(id); - this.props.actions.sendSignal('remove_offer_link', id); - } - - /** - * Helper render function for the expires text. - * @return {JSX} JSX for the Rewards Items List - */ - renderExpiresText() { - const { expires } = this.props; - const delta = computeTimeDelta(new Date(expires), new Date()); - const { count, type } = delta; - if (count === 1) { - return t(`rewards_expires_in_${type.slice(0, -1)}`); - } - return t(`rewards_expires_in_${type}`, [count]); - } - - /** - * React's required render function. Returns JSX - * @return {JSX} JSX for rendering a Reward within the Rewards List - */ - render() { - const { - id, - isLong, - text, - benefit, - unread, - disabled, - logoUrl, - } = this.props; - const itemClassName = ClassNames('RewardListItem', 'row', { - 'RewardListItem--greyscale': disabled, - 'RewardListItem--unread': unread, - 'RewardListItem--elongated': isLong, - 'not-clickable': disabled, - clickable: !disabled, - }); - - return ( - -
-
- -
-
-
{ benefit }
-
{ text }
-
{ this.renderExpiresText() }
-
-
-
- - - -
-
- - - -
-
-
- - ); - } -} - -export default RewardListItem; diff --git a/app/panel/components/BuildingBlocks/index.js b/app/panel/components/BuildingBlocks/index.js index 3cb86a8ca..edc925cbc 100644 --- a/app/panel/components/BuildingBlocks/index.js +++ b/app/panel/components/BuildingBlocks/index.js @@ -21,8 +21,6 @@ import GhosteryFeature from './GhosteryFeature'; import NotScanned from './NotScanned'; import PauseButton from './PauseButton'; import ToggleSlider from './ToggleSlider'; -import RewardDetail from './RewardDetail'; -import RewardListItem from './RewardListItem'; import ModalExitButton from './ModalExitButton'; export { @@ -33,7 +31,5 @@ export { NotScanned, PauseButton, ToggleSlider, - RewardDetail, - RewardListItem, ModalExitButton }; diff --git a/app/panel/components/Detail.jsx b/app/panel/components/Detail.jsx index 662cead77..dfb9bf494 100644 --- a/app/panel/components/Detail.jsx +++ b/app/panel/components/Detail.jsx @@ -63,20 +63,23 @@ class Detail extends React.Component { const condensedToggleClassNames = ClassNames('condensed-toggle', { condensed: this.props.is_expanded, }); - const { enable_offers, unread_offer_ids } = this.props; const activeTab = this.props.history.location.pathname.includes('rewards') ? 'rewards' : 'blocking'; + const contentDetailsClassNames = ClassNames({ + expanded: this.props.is_expanded, + rewardsView: activeTab === 'rewards', + }); return (
-
+
0} + hasReward={false} subscriptionsPlus={this.props.user && this.props.user.subscriptionsPlus} activeTab={activeTab} /> diff --git a/app/panel/components/Panel.jsx b/app/panel/components/Panel.jsx index 45f5de0e4..575027e6f 100644 --- a/app/panel/components/Panel.jsx +++ b/app/panel/components/Panel.jsx @@ -150,10 +150,6 @@ class Panel extends React.Component { reload: true }); } - - if (panel.enable_offers && panel.unread_offer_ids.length > 0) { - sendMessage('ping', 'engaged_offer'); - } } /** diff --git a/app/panel/components/Rewards.jsx b/app/panel/components/Rewards.jsx index 8ba483f27..3a36932bf 100644 --- a/app/panel/components/Rewards.jsx +++ b/app/panel/components/Rewards.jsx @@ -13,11 +13,12 @@ import React from 'react'; import ClassNames from 'classnames'; -import { Link, Route } from 'react-router-dom'; -import { ToggleSlider, RewardListItem, RewardDetail } from './BuildingBlocks'; +import { Route } from 'react-router-dom'; +import { ToggleSlider } from './BuildingBlocks'; import { DynamicUIPortContext } from '../contexts/DynamicUIPortContext'; import { sendMessage } from '../utils/msg'; import globals from '../../../src/classes/Globals'; +import { log } from '../../../src/utils/common'; const IS_CLIQZ = (globals.BROWSER_INFO.name === 'cliqz'); @@ -33,7 +34,10 @@ class Rewards extends React.Component { constructor(props) { super(props); this.state = { - rewardsArray: null, + iframeWidth: 0, + iframeHeight: 0, + shouldHideRewards: false, + rewardsCount: 0, }; // event bindings @@ -41,10 +45,13 @@ class Rewards extends React.Component { // helper render functions this.renderRewardListComponent = this.renderRewardListComponent.bind(this); - this.renderRewardDetailComponent = this.renderRewardDetailComponent.bind(this); - this.handleBackClick = this.handleBackClick.bind(this); this.handleFaqClick = this.handleFaqClick.bind(this); this.handlePortMessage = this.handlePortMessage.bind(this); + + + // myoffrz + this.iframe = React.createRef(); + this.handleMyoffrzMessage = this.handleMyoffrzMessage.bind(this); } /** @@ -53,66 +60,79 @@ class Rewards extends React.Component { componentDidMount() { this._dynamicUIPort = this.context; this._dynamicUIPort.onMessage.addListener(this.handlePortMessage); - this._dynamicUIPort.postMessage({ name: 'RewardsComponentDidMount' }); + window.addEventListener('message', this.handleMyoffrzMessage); + this._dynamicUIPort.postMessage({ name: 'RewardsComponentDidMount' }); this.props.actions.sendSignal('hub_open'); } - /** - * Lifecycle event - */ - UNSAFE_componentWillReceiveProps(nextProps) { - let rewardsArray = null; - if (nextProps.rewards) { - rewardsArray = Object.keys(nextProps.rewards).map((key) => { - const reward = nextProps.rewards[key].offer_data; - const { isCodeHidden } = nextProps.rewards[key].attrs || {}; - const createdTS = nextProps.rewards[key].createdTs; - return { - id: reward.offer_id, - unread: nextProps.unread_offer_ids.indexOf(reward.offer_id) !== -1, - code: reward.ui_info.template_data.code, - isCodeHidden, - text: reward.ui_info.template_data.title, - description: reward.ui_info.template_data.desc, - benefit: reward.ui_info.template_data.benefit, - conditions: reward.ui_info.template_data.conditions, - logo_url: reward.ui_info.template_data.logo_url, - picture_url: reward.ui_info.template_data.picture_url, - redeem_url: reward.ui_info.template_data.call_to_action.url, - redeem_text: reward.ui_info.template_data.call_to_action.text, - expires: new Date(createdTS + reward.expirationMs), - }; - }).filter(reward => reward.expires > Date.now()); - } - this.setState({ rewardsArray }); - } - /** * Lifecycle event */ componentWillUnmount() { /* @TODO send message to background to remove port onDisconnect event */ this.props.actions.sendSignal('hub_closed'); - this._dynamicUIPort.postMessage({ name: 'RewardsComponentWillUnmount' }); this._dynamicUIPort.onMessage.removeListener(this.handlePortMessage); + window.removeEventListener('message', this.handleMyoffrzMessage); } /** - * Handles message from the dynamic UI port to background - */ + * Handles message from the dynamic UI port to background + */ handlePortMessage(msg) { if (msg.to !== 'rewards' || !msg.body) { return; } + // msg.body can contain enable_offers prop this.props.actions.updateRewardsData(msg.body); } - /** - * Handles clicking the back button - */ - handleBackClick(offerId) { - this.props.actions.sendSignal('offer_return_hub', offerId); + iframeResize(data = {}) { + const { width = 0, height = 0 } = data; + this.setState({ iframeWidth: width, iframeHeight: height }); + } + + sendToIframe(message) { + this.iframe.current.contentWindow.postMessage(JSON.stringify({ + target: 'cliqz-offers-cc', + origin: 'window', + message, + }), '*'); + } + + myoffrzSendRuntimeMessage({ message, target }) { + chrome.runtime.sendMessage({ message, target }, (result = {}) => { + if (chrome.runtime.lastError) { + log('myoffrzSendRuntimeMessage, runtime.lastError', chrome.runtime.lastError); + return; + } + if (result.action !== 'pushData' || !this.iframe.current) { return; } + const { data: { vouchers = [] } = {} } = result; + const rewardsCount = vouchers.length; + this.setState({ shouldHideRewards: rewardsCount === 0, rewardsCount }); + this.iframe.current.frameBorder = 0; + this.sendToIframe(result); + }); + } + + handleMyoffrzMessage(msg = {}) { + let target; + let message; + try { + const parsedData = JSON.parse(msg.data || '{}'); + target = parsedData.target; + message = parsedData.message || {}; + } catch (e) { + // just silent return + return; + } + + if (target !== 'cliqz-offers-cc') { return; } + if (message.action === 'resize') { + this.iframeResize(message.data); + } else { + this.myoffrzSendRuntimeMessage({ message, target }); + } } /** @@ -150,33 +170,15 @@ class Rewards extends React.Component { * Helper render function for the Rewards Header * @return {JSX} JSX for the Rewards Header */ - renderRewardsHeader = (routeProps) => { - let reward; - const { id } = routeProps.match.params; - if (id && this.state.rewardsArray) { - reward = this.state.rewardsArray.find(el => el.id === id); - } - const { enable_offers, location } = this.props; - const showBack = location.pathname.indexOf('/detail/rewards/detail') !== -1; - const showToggle = location.pathname === '/detail/rewards/list'; - const headerClassNames = ClassNames('RewardsPanel__header', 'flex-container', 'align-middle', { - 'align-justify': !showBack, - }); - const headerTitleClassNames = ClassNames('RewardsPanel__title', { - 'RewardsPanel--left-padding': showBack, - }); + renderRewardsHeader = () => { + const { enable_offers } = this.props; + const headerClassNames = ClassNames('RewardsPanel__header', 'flex-container', 'align-middle', 'align-justify'); + const headerTitleClassNames = ClassNames('RewardsPanel__title'); return (
- {showBack && ( - { this.handleBackClick(id); }}> - - - - - )} { t('ghostery_rewards') } - {showToggle && !IS_CLIQZ && ( + {!IS_CLIQZ && ( {enable_offers ? t('rewards_on') : t('rewards_off')} @@ -188,9 +190,6 @@ class Rewards extends React.Component { /> )} - {!showToggle && reward && - - }
); } @@ -209,92 +208,76 @@ class Rewards extends React.Component { ); } + renderCLIQZtext() { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_cliqz_text') }
+
+
+ { t('panel_detail_learn_more') } +
+
+ ); + } + + renderRewardsTurnoffText() { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_off') }
+
+ ); + } + + renderRewardsNoneFoundText() { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_none_found') }
+
+ ); + } + /** * Helper render function for the list of Rewards Items * @return {JSX} JSX for the Rewards Items List */ renderRewardListComponent() { - const { actions, enable_offers, is_expanded } = this.props; - const { rewardsArray } = this.state; + if (IS_CLIQZ) { return this.renderCLIQZtext(); } + const { enable_offers, is_expanded } = this.props; + if (!enable_offers) { return this.renderRewardsTurnoffText(); } - if (IS_CLIQZ) { - return ( -
- { this.renderRewardSvg() } -
{ t('panel_detail_rewards_cliqz_text') }
-
-
- { t('learn_more') } -
-
- ); - } - if (enable_offers && !rewardsArray) { - return ( -
- { this.renderRewardSvg() } -
{ t('panel_detail_rewards_loading') }
-
- ); - } - if (enable_offers && rewardsArray.length === 0) { - return ( -
- { this.renderRewardSvg() } -
{ t('panel_detail_rewards_none_found') }
-
- ); - } - if (!enable_offers && (!rewardsArray || rewardsArray.length === 0)) { - return ( -
- { this.renderRewardSvg() } -
{ t('panel_detail_rewards_off') }
-
- ); - } - - const rewardsList = rewardsArray.map((reward, index) => ( - - )); - return
{ rewardsList }
; - } + const { + shouldHideRewards, + iframeWidth, + iframeHeight, + rewardsCount, + } = this.state; + if (shouldHideRewards) { return this.renderRewardsNoneFoundText(); } - /** - * Helper render function for an individual Reward Item - * @return {JSX} JSX for the Rewards Detail Item - */ - renderRewardDetailComponent(routeProps) { - const { id } = routeProps.match.params; - const reward = this.state.rewardsArray.find(el => el.id === id); + const src = chrome.runtime.getURL('cliqz/offers-cc/index.html?cross-origin'); + const text = t(`panel_rewards_view__reward${rewardsCount === 1 ? '' : 's'}`); return ( - + <> + {is_expanded && ( +
+
{rewardsCount}
+
{text}
+
+ )} +