diff --git a/app/hub/Views/HomeView/HomeViewContainer.jsx b/app/hub/Views/HomeView/HomeViewContainer.jsx index c98e55b78..81caf36c6 100644 --- a/app/hub/Views/HomeView/HomeViewContainer.jsx +++ b/app/hub/Views/HomeView/HomeViewContainer.jsx @@ -103,21 +103,11 @@ class HomeViewContainer extends Component { */ _handleTryMidnightClick = () => { this._handlePremiumPromoModalClick('premium'); } - /** - * @returns {bool} - * @private - * Is the user a Premium subscriber? - */ - _premiumSubscriber = () => { - const { loggedIn, user } = this.props; - - return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:premium')); - } - _render() { const { justInstalled } = this.state; const { home, user } = this.props; - const isPlus = user && user.subscriptionsPlus || false; + const isPlus = user && user.plusAccess || false; + const isPremium = user && user.premiumAccess || false; const { premium_promo_modal_shown, setup_complete, @@ -134,7 +124,7 @@ class HomeViewContainer extends Component { isPlus, }; - const showPromoModal = !premium_promo_modal_shown && !this._premiumSubscriber(); + const showPromoModal = !premium_promo_modal_shown && !isPremium; return (
@@ -174,7 +164,7 @@ HomeViewContainer.propTypes = { }), user: PropTypes.shape({ email: PropTypes.string, - subscriptionsPlus: PropTypes.bool, + plusAccess: PropTypes.bool, }), actions: PropTypes.shape({ getHomeProps: PropTypes.func.isRequired, @@ -194,7 +184,7 @@ HomeViewContainer.defaultProps = { }, user: { email: '', - subscriptionsPlus: false, + plusAccess: false, }, }; diff --git a/app/hub/Views/PlusView/PlusViewContainer.jsx b/app/hub/Views/PlusView/PlusViewContainer.jsx index 5d8332ddc..2737380ec 100644 --- a/app/hub/Views/PlusView/PlusViewContainer.jsx +++ b/app/hub/Views/PlusView/PlusViewContainer.jsx @@ -43,8 +43,9 @@ class PlusViewContainer extends Component { * @return {JSX} JSX for rendering the Plus View of the Hub app */ render() { + const { user } = this.props; const childProps = { - isPlus: this.props.user && this.props.user.subscriptionsPlus || false, + isPlus: user && user.plusAccess || false, onPlusClick: this._sendPlusPing, }; @@ -56,7 +57,7 @@ class PlusViewContainer extends Component { PlusViewContainer.propTypes = { user: PropTypes.shape({ email: PropTypes.string, - subscriptionsPlus: PropTypes.bool, + plusAccess: PropTypes.bool, }), actions: PropTypes.shape({ sendPing: PropTypes.func.isRequired, @@ -68,7 +69,7 @@ PlusViewContainer.propTypes = { PlusViewContainer.defaultProps = { user: { email: false, - subscriptionsPlus: false, + plusAccess: false, }, }; diff --git a/app/images/panel/gold-plus-icon-expanded-view.svg b/app/images/panel/plus-badge-icon-expanded-view.svg similarity index 100% rename from app/images/panel/gold-plus-icon-expanded-view.svg rename to app/images/panel/plus-badge-icon-expanded-view.svg diff --git a/app/images/panel/gold-plus-icon.svg b/app/images/panel/plus-badge-icon.svg similarity index 100% rename from app/images/panel/gold-plus-icon.svg rename to app/images/panel/plus-badge-icon.svg diff --git a/app/images/panel/premium-badge-icon-expanded-view.svg b/app/images/panel/premium-badge-icon-expanded-view.svg new file mode 100644 index 000000000..1f6390dc0 --- /dev/null +++ b/app/images/panel/premium-badge-icon-expanded-view.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/panel/premium-badge-icon.svg b/app/images/panel/premium-badge-icon.svg new file mode 100644 index 000000000..292b7a5e1 --- /dev/null +++ b/app/images/panel/premium-badge-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/panel/components/Detail.jsx b/app/panel/components/Detail.jsx index dfb9bf494..3846924d7 100644 --- a/app/panel/components/Detail.jsx +++ b/app/panel/components/Detail.jsx @@ -60,13 +60,14 @@ class Detail extends React.Component { * @return {ReactComponent} ReactComponent instance */ render() { + const { is_expanded, user, history } = this.props; const condensedToggleClassNames = ClassNames('condensed-toggle', { - condensed: this.props.is_expanded, + condensed: is_expanded, }); - const activeTab = this.props.history.location.pathname.includes('rewards') ? 'rewards' : 'blocking'; + const activeTab = history.location.pathname.includes('rewards') ? 'rewards' : 'blocking'; const contentDetailsClassNames = ClassNames({ - expanded: this.props.is_expanded, + expanded: is_expanded, rewardsView: activeTab === 'rewards', }); @@ -80,7 +81,7 @@ class Detail extends React.Component {
diff --git a/app/panel/components/Header.jsx b/app/panel/components/Header.jsx index 5f31d701a..4a2ab5fd7 100644 --- a/app/panel/components/Header.jsx +++ b/app/panel/components/Header.jsx @@ -145,12 +145,13 @@ class Header extends React.Component { return this.props.is_expert ? '/detail/blocking' : '/'; } - clickUpgradeBannerOrGoldPlusIcon = () => { + clickUpgradeBannerOrSubscriberBadgeIcon = () => { // TODO check whether this is the message we want to be sending now sendMessage('ping', 'plus_panel_from_badge'); const { user } = this.props; - const subscriber = user && user.subscriptionsPlus; - this.props.history.push(subscriber ? '/subscription/info' : `/subscribe/${!!user}`); + const hasPlusAccess = user && user.plusAccess; + const hasPremiumAccess = user && user.premiumAccess; + this.props.history.push(hasPlusAccess || hasPremiumAccess ? '/subscription/info' : `/subscribe/${!!user}`); } /** @@ -176,11 +177,12 @@ class Header extends React.Component { const tabDetailedClassNames = ClassNames('header-tab', { active: is_expert, }); - const subscriber = user && user.subscriptionsPlus; + const hasPlusAccess = user && user.plusAccess; + const hasPremiumAccess = user && user.premiumAccess; const accountLogolink = this.generateAccountLogo(); const badgeClasses = ClassNames('columns', 'shrink', { - 'non-subscriber-badge': !subscriber, - 'gold-subscriber-badge': subscriber + 'non-subscriber-badge': !(hasPlusAccess || hasPremiumAccess), + 'subscriber-badge': hasPlusAccess || hasPremiumAccess }); const simpleTab = ( @@ -216,10 +218,11 @@ class Header extends React.Component { ); const plusUpgradeBannerOrSubscriberBadgeLogolink = ( -
+
{ - (subscriber && ) || - + ((hasPremiumAccess) && ) + || ((hasPlusAccess) && ) + || }
); @@ -237,7 +240,8 @@ class Header extends React.Component { const headerMenu = ( { + const { hasPremiumAccess, hasPlusAccess } = this.props; sendMessage('ping', 'plus_panel_from_menu'); this.props.toggleDropdown(); - this.props.history.push(this.props.subscriber ? '/subscription/info' : `/subscribe/${this.props.loggedIn}`); + this.props.history.push(hasPlusAccess || hasPremiumAccess ? '/subscription/info' : '/subscribe/false'); } /** @@ -162,9 +163,21 @@ class HeaderMenu extends React.Component { * @return {ReactComponent} ReactComponent instance */ render() { - const { loggedIn, email } = this.props; - const optionClasses = ClassNames({ 'menu-option': this.props.subscriber, 'menu-option-non-subscriber': !this.props.subscriber }); - const iconClasses = ClassNames('menu-icon-container', { subscriber: this.props.subscriber }, { 'non-subscriber': !this.props.subscriber }); + const { + loggedIn, + email, + hasPremiumAccess, + hasPlusAccess + } = this.props; + const optionClasses = ClassNames({ + 'menu-option': hasPlusAccess || hasPremiumAccess, + 'menu-option-non-subscriber': !(hasPlusAccess || hasPremiumAccess) + }); + const iconClasses = ClassNames('menu-icon-container', { + premium: hasPremiumAccess, + plus: !hasPremiumAccess && hasPlusAccess, + 'non-subscriber': !(hasPremiumAccess || hasPlusAccess) + }); return (
@@ -230,13 +243,27 @@ class HeaderMenu extends React.Component {
  • - - - - - - - { t('ghostery_plus') } + {/* Show premium icon to premium users and plus icon to basic and plus users */} + {hasPremiumAccess && ( + + + + + + + + + + )} + {!hasPremiumAccess && ( + + + + + + + )} + {hasPremiumAccess ? t('panel_detail_premium_title') : t('ghostery_plus')}
  • diff --git a/app/panel/components/Panel.jsx b/app/panel/components/Panel.jsx index 77055f519..1334abe3f 100644 --- a/app/panel/components/Panel.jsx +++ b/app/panel/components/Panel.jsx @@ -214,10 +214,10 @@ class Panel extends React.Component { * @private * Is the user a Premium subscriber? */ - _premiumSubscriber = () => { + _hasPremiumAccess = () => { const { loggedIn, user } = this.props; - return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:premium')); + return loggedIn && (user && user.premiumAccess); } /** @@ -225,10 +225,10 @@ class Panel extends React.Component { * @private * Is the user a Plus subscriber? */ - _plusSubscriber = () => { + _hasPlusAccess = () => { const { loggedIn, user } = this.props; - return loggedIn && (user && user.subscriptionsPlus); + return loggedIn && (user && user.plusAccess); } /** @@ -237,17 +237,15 @@ class Panel extends React.Component { * Renders the Premium promo modal */ _renderPremiumPromoModal = () => { - if (this._premiumSubscriber()) return null; + if (this._hasPremiumAccess()) return null; sendMessage('promoModals.sawPremiumPromo', {}); - const isPlus = this._plusSubscriber(); - return ( ); @@ -278,7 +276,7 @@ class Panel extends React.Component { * Renders the Plus promo modal if the user is not already an subscriber */ _renderPlusPromoModal = () => { - if (this._plusSubscriber() || this._premiumSubscriber()) { return null; } + if (this._hasPlusAccess() || this._hasPremiumAccess()) { return null; } sendMessage('promoModals.sawPlusPromo', {}); diff --git a/app/panel/components/Stats.jsx b/app/panel/components/Stats.jsx index 8ec7ac7c7..bfab2f720 100644 --- a/app/panel/components/Stats.jsx +++ b/app/panel/components/Stats.jsx @@ -31,7 +31,7 @@ class Stats extends React.Component { */ componentDidMount() { sendMessage('ping', 'hist_stats_panel'); - if (!this._isPlus(this.props)) { + if (!this._hasPlusAccess(this.props)) { // eslint-disable-next-line this.setState(this._reset(true)); return; @@ -43,8 +43,8 @@ class Stats extends React.Component { * Lifecycle event */ UNSAFE_componentWillReceiveProps(nextProps) { - const nextPlus = this._isPlus(nextProps); - const thisPlus = this._isPlus(this.props); + const nextPlus = this._hasPlusAccess(nextProps); + const thisPlus = this._hasPlusAccess(this.props); if (nextPlus !== thisPlus) { if (nextPlus) { this._init(); @@ -153,7 +153,7 @@ class Stats extends React.Component { * @param {Object} event click event */ selectView = (event) => { - if (!this._isPlus(this.props)) { + if (!this._hasPlusAccess(this.props)) { return; } const state = Object.assign({}, this.state); @@ -177,7 +177,7 @@ class Stats extends React.Component { * @param {Object} event click event */ selectType = (event) => { - if (!this._isPlus(this.props)) { + if (!this._hasPlusAccess(this.props)) { return; } const state = Object.assign({}, this.state); @@ -227,7 +227,7 @@ class Stats extends React.Component { * @param {Object} event click event */ selectTimeframe = (e) => { - if (!this._isPlus(this.props)) { + if (!this._hasPlusAccess(this.props)) { return; } const state = Object.assign({}, this.state); @@ -256,7 +256,7 @@ class Stats extends React.Component { } resetStats = () => { - if (!this._isPlus(this.props)) { + if (!this._hasPlusAccess(this.props)) { return; } this.setState({ showResetModal: true }); @@ -291,6 +291,7 @@ class Stats extends React.Component { } _reset = (demo) => { + const { user } = this.props; const demoData = [ { date: '2018-12-28', amount: 300, index: 0 }, { date: '2018-12-29', amount: 450, index: 1 }, @@ -329,7 +330,7 @@ class Stats extends React.Component { monthlyAverageData: clearData, dailyAverageData: clearData, showResetModal: false, - showPitchModal: (!this.props.user || !this.props.user.subscriptionsPlus), + showPitchModal: (!user || !user.plusAccess), }; return clearOrDemoState; } @@ -517,7 +518,7 @@ class Stats extends React.Component { return selectionData; } - _isPlus = props => props.user && props.user.subscriptionsPlus; + _hasPlusAccess = props => props.user && props.user.plusAccess; /** * Render the the Stats View @@ -529,7 +530,7 @@ class Stats extends React.Component { return ( { const { - active, plan_amount, plan_interval, charge_date, plan_ends, loading + productName, active, plan_amount, plan_interval, charge_date, plan_ends, loading } = props.subscriptionData; const subscriptionExpiration = (plan_ends > 1) ? t('subscription_days_left', plan_ends.toString()) : t('subscription_one_day_left'); return (
    -

    { t('ghostery_plus') }

    + {productName === 'Ghostery Premium' && ( +

    { t('panel_detail_premium_title') }

    + )} + {productName === 'Ghostery Plus' && ( +

    { t('ghostery_plus') }

    + )} {loading ? (
    ) : ( diff --git a/app/panel/components/Summary.jsx b/app/panel/components/Summary.jsx index 4b9463999..84da9b6ab 100644 --- a/app/panel/components/Summary.jsx +++ b/app/panel/components/Summary.jsx @@ -215,7 +215,7 @@ class Summary extends React.Component { clickUpgradeBannerOrGoldPlusIcon() { sendMessage('ping', 'plus_panel_from_badge'); - this.props.history.push(this._isPlusSubscriber() ? '/subscription/info' : `/subscribe/${!!this.props.user}`); + this.props.history.push(this._hasPremiumAccess() || this._hasPlusAccess() ? '/subscription/info' : `/subscribe/${!!this.props.user}`); } /** @@ -306,10 +306,16 @@ class Summary extends React.Component { } } - _isPlusSubscriber() { + _hasPlusAccess() { const { user } = this.props; - return user && user.subscriptionsPlus; + return user && user.plusAccess; + } + + _hasPremiumAccess() { + const { user } = this.props; + + return user && user.premiumAccess; } _pageHost() { @@ -746,9 +752,10 @@ class Summary extends React.Component { * @return {JSX} JSX for rendering the plus upgrade banner or subscriber icon */ _renderPlusUpgradeBannerOrSubscriberIcon() { - const { is_expert, current_theme } = this.props; + const { is_expert, current_theme, user } = this.props; - const isPlusSubscriber = this._isPlusSubscriber(); + const hasPremiumAccess = user && user.premiumAccess; + const hasPlusAccess = user && user.plusAccess; const upgradeBannerClassNames = ClassNames('UpgradeBanner', { 'UpgradeBanner--normal': !is_expert, 'UpgradeBanner--small': is_expert, @@ -756,15 +763,23 @@ class Summary extends React.Component { return (
    - {isPlusSubscriber && ( + {hasPremiumAccess && ( +
    +
    + +
    +
    + )} + + {hasPlusAccess && !hasPremiumAccess && (
    - +
    )} - {!isPlusSubscriber && ( + {(!hasPlusAccess && !hasPremiumAccess) && (
    {t('subscription_upgrade_to')} diff --git a/app/scss/partials/_header.scss b/app/scss/partials/_header.scss index 2c29a8281..0a49a6a55 100644 --- a/app/scss/partials/_header.scss +++ b/app/scss/partials/_header.scss @@ -74,7 +74,7 @@ @extend %pointer; display: block; } - .gold-subscriber-badge { + .subscriber-badge { @extend .base-badge; margin-top: 4px; } @@ -148,12 +148,18 @@ .menu-option { @extend .menu-option-base; - .subscriber { + .plus { width: 18px; height: 16px; path {fill: $light-gray;} path.text {fill: #4a4a4a;} } + .premium { + margin-left: -4px; + g > g { + fill: #DEDEDE; + } + } &:hover { span {color: $ghosty-blue} .menu-icon {fill: $ghosty-blue; stroke: none;} @@ -165,6 +171,9 @@ path {stroke: $ghosty-blue;} path.text {fill: $ghosty-blue;} } + .premium > g > g { + fill: $ghosty-blue; + } } span { color: #ffffff; diff --git a/src/background.js b/src/background.js index e4fbc0e53..ae5b3a17b 100644 --- a/src/background.js +++ b/src/background.js @@ -823,20 +823,21 @@ function onMessageHandler(request, sender, callback) { } if (name === 'account.getUserSubscriptionData') { account.getUserSubscriptionData() - .then((customer) => { - // TODO temporary fix to handle multiple subscriptions - let sub = customer.subscriptions; - if (!Array.isArray(sub)) { - sub = [sub]; + .then((subscriptions) => { + // Return highest tier subscription from array + const premiumSubscription = subscriptions.find(subscription => subscription.productName.includes('Ghostery Premium')); + if (premiumSubscription) { + callback({ subscriptionData: premiumSubscription }); + return; } - const subscriptionData = sub.reduce((acc, curr) => { - let a = acc; - if (curr.productName.includes('Plus')) { - a = curr; - } - return a; - }, {}); - callback({ subscriptionData }); + + const plusSubscription = subscriptions.find(subscription => subscription.productName.includes('Ghostery Plus')); + if (plusSubscription) { + callback({ subscriptionData: plusSubscription }); + return; + } + + callback({}); }) .catch((err) => { log('Error getting user subscription data:', err); @@ -880,7 +881,9 @@ function onMessageHandler(request, sender, callback) { account.getUser(message) .then((user) => { if (user) { - user.subscriptionsPlus = account.hasScopesUnverified(['subscriptions:plus']); + user.plusAccess = account.hasScopesUnverified(['subscriptions:plus']) + || account.hasScopesUnverified(['subscriptions:premium']); + user.premiumAccess = account.hasScopesUnverified(['subscriptions:premium']); } callback({ user }); }) diff --git a/src/classes/Account.js b/src/classes/Account.js index 4dfd48a30..59d00f788 100644 --- a/src/classes/Account.js +++ b/src/classes/Account.js @@ -158,25 +158,38 @@ class Account { }) ) + /** + * @return {array} All subscriptions the user has, empty if none + */ getUserSubscriptionData = () => ( this._getUserID() .then(userID => api.get('stripe/customers', userID, 'cards,subscriptions')) .then((res) => { const customer = build(normalize(res), 'customers', res.data.id); + // TODO temporary fix to handle multiple subscriptions let sub = customer.subscriptions; if (!Array.isArray(sub)) { sub = [sub]; } - const subPlus = sub.reduce((acc, curr) => { - let a = acc; - if (curr.productName.includes('Plus')) { - a = curr; + + const subscriptions = []; + + const premiumSubscription = sub.find(subscription => subscription.productName.includes('Ghostery Premium')); + if (premiumSubscription) { + subscriptions.push(premiumSubscription); + this._setSubscriptionData(premiumSubscription); + } + + const plusSubscription = sub.find(subscription => subscription.productName.includes('Ghostery Plus')); + if (plusSubscription) { + subscriptions.push(plusSubscription); + if (!premiumSubscription) { + this._setSubscriptionData(plusSubscription); } - return a; - }, {}); - this._setSubscriptionData(subPlus); - return customer; + } + + return subscriptions; }) ) diff --git a/src/classes/PanelData.js b/src/classes/PanelData.js index 281433511..3a99b8b5e 100644 --- a/src/classes/PanelData.js +++ b/src/classes/PanelData.js @@ -264,7 +264,9 @@ class PanelData { _getCurrentAccount() { const currentAccount = conf.account; if (currentAccount && currentAccount.user) { - currentAccount.user.subscriptionsPlus = account.hasScopesUnverified(['subscriptions:plus']); + currentAccount.user.plusAccess = account.hasScopesUnverified(['subscriptions:plus']) + || account.hasScopesUnverified(['subscriptions:premium']); + currentAccount.user.premiumAccess = account.hasScopesUnverified(['subscriptions:premium']); } return currentAccount; } diff --git a/src/utils/api.js b/src/utils/api.js index f2e3865f5..64671a0fe 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -154,17 +154,17 @@ class Api { get = (type, id, include = '') => { if (!id) { return Promise.reject(new Error('id is missing')); } - return this._sendAuthenticatedRequest('GET', `/api/v2/${type}/${id}?${include ? `include=${include}` : ''}`); + return this._sendAuthenticatedRequest('GET', `/api/v2.1.0/${type}/${id}?${include ? `include=${include}` : ''}`); } - save = (type, data) => this._sendAuthenticatedRequest('POST', `/api/v2/${type}/`, data) + save = (type, data) => this._sendAuthenticatedRequest('POST', `/api/v2.1.0/${type}/`, data) update = (type, data) => { // TODO check for data.id and fail - this._sendAuthenticatedRequest('PATCH', `/api/v2/${type}/${data.id}`, { data }); + this._sendAuthenticatedRequest('PATCH', `/api/v2.1.0/${type}/${data.id}`, { data }); } - remove = (type, id) => this._sendAuthenticatedRequest('DELETE', `/api/v2/${type}/${id}`) + remove = (type, id) => this._sendAuthenticatedRequest('DELETE', `/api/v2.1.0/${type}/${id}`) } export default Api;