Adnimation #1447
This commit is contained in:
commit
4d7f1c1d42
12 changed files with 323 additions and 84 deletions
|
@ -8,13 +8,7 @@ import {
|
|||
} from 'redux/selectors/sync';
|
||||
import { doUserSetReferrer } from 'redux/actions/user';
|
||||
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||
import {
|
||||
selectOdyseeMembershipIsPremiumPlus,
|
||||
selectUser,
|
||||
selectUserLocale,
|
||||
selectUserVerifiedEmail,
|
||||
selectHomepageFetched,
|
||||
} from 'redux/selectors/user';
|
||||
import { selectUser, selectUserLocale, selectUserVerifiedEmail, selectHomepageFetched } from 'redux/selectors/user';
|
||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||
import { doFetchChannelListMine, doFetchCollectionListMine } from 'redux/actions/claims';
|
||||
import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
||||
|
@ -49,7 +43,6 @@ const select = (state) => ({
|
|||
syncFatalError: selectSyncFatalError(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
myChannelClaimIds: selectMyChannelClaimIds(state),
|
||||
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||
homepageFetched: selectHomepageFetched(state),
|
||||
defaultChannelClaim: selectDefaultChannelClaim(state),
|
||||
});
|
||||
|
|
|
@ -14,7 +14,6 @@ import useKonamiListener from 'util/enhanced-layout';
|
|||
import Yrbl from 'component/yrbl';
|
||||
import FileRenderFloating from 'component/fileRenderFloating';
|
||||
import { withRouter } from 'react-router';
|
||||
import useAdOutbrain from 'effects/use-ad-outbrain';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import Nag from 'component/common/nag';
|
||||
import REWARDS from 'rewards';
|
||||
|
@ -83,7 +82,6 @@ type Props = {
|
|||
syncFatalError: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
myChannelClaimIds: ?Array<string>,
|
||||
hasPremiumPlus: ?boolean,
|
||||
setIncognito: (boolean) => void,
|
||||
fetchModBlockedList: () => void,
|
||||
fetchModAmIList: () => void,
|
||||
|
@ -122,7 +120,6 @@ function App(props: Props) {
|
|||
activeChannelClaim,
|
||||
setIncognito,
|
||||
fetchModBlockedList,
|
||||
hasPremiumPlus,
|
||||
fetchModAmIList,
|
||||
homepageFetched,
|
||||
defaultChannelClaim,
|
||||
|
@ -509,8 +506,6 @@ function App(props: Props) {
|
|||
|
||||
useDegradedPerformance(setLbryTvApiStatus, user);
|
||||
|
||||
useAdOutbrain(Boolean(hasPremiumPlus), isAuthenticated, history?.location?.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
// When language is changed or translations are fetched, we render.
|
||||
setLangRenderKey(Date.now());
|
||||
|
|
|
@ -166,7 +166,13 @@ function ChannelContent(props: Props) {
|
|||
infiniteScroll={defaultInfiniteScroll}
|
||||
injectedItem={
|
||||
!hasPremiumPlus && {
|
||||
node: <Ads small type="video" tileLayout />,
|
||||
node: (index, lastVisibleIndex, pageSize) => {
|
||||
if (pageSize && index < pageSize) {
|
||||
return index === lastVisibleIndex ? <Ads type="video" tileLayout={tileLayout} small /> : null;
|
||||
} else {
|
||||
return index % (pageSize * 2) === 0 ? <Ads type="video" tileLayout={tileLayout} small /> : null;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
meta={
|
||||
|
|
|
@ -17,25 +17,25 @@ const OUTBRAIN_CONTAINER_KEY = 'outbrainSizeDiv';
|
|||
let script;
|
||||
|
||||
/**
|
||||
* @param hasPremiumPlus
|
||||
* @param isAuthenticated
|
||||
* @param hasPremiumPlus `undefined` if not yet fetched, boolean otherwise.
|
||||
* @param isAuthenticated `undefined` if email is not fetched, boolean
|
||||
* otherwise.
|
||||
* @param pathname Reminder: the component using this effect must be listening
|
||||
* to path changes (e.g. useHistory, etc.). This value must not
|
||||
* come from window.location.pathname, which doesn't spark an
|
||||
* update.
|
||||
*/
|
||||
export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated: boolean, pathname: string) {
|
||||
// Only look at authentication for now. Eventually, we'll only use 'hasPremiumPlus'.
|
||||
// Authenticated will return undefined if not yet populated, so wait and only show
|
||||
// when returned as false
|
||||
const isNotAuthenticated = isAuthenticated === false;
|
||||
export default function useAdOutbrain(hasPremiumPlus: ?boolean, isAuthenticated: ?boolean, pathname: string) {
|
||||
// Still need to look at `isAuthenticated` because `hasPremiumPlus` remains
|
||||
// in unfetched (`undefined) state in Incognito.
|
||||
const loadScript = isAuthenticated === false || hasPremiumPlus === false;
|
||||
|
||||
const propRef = React.useRef({ isAuthenticated, pathname });
|
||||
propRef.current = { isAuthenticated, pathname };
|
||||
const propRef = React.useRef({ hasPremiumPlus, pathname });
|
||||
propRef.current = { hasPremiumPlus, pathname };
|
||||
|
||||
function resolveVisibility() {
|
||||
if (window[OUTBRAIN_CONTAINER_KEY]) {
|
||||
if (propRef.current.isAuthenticated) {
|
||||
if (propRef.current.hasPremiumPlus) {
|
||||
window[OUTBRAIN_CONTAINER_KEY].style.display = 'none';
|
||||
} else {
|
||||
window[OUTBRAIN_CONTAINER_KEY].style.display = EXCLUDED_PATHS.includes(propRef.current.pathname) ? 'none' : '';
|
||||
|
@ -44,7 +44,7 @@ export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated:
|
|||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!inIFrame() && isNotAuthenticated && !script) {
|
||||
if (!inIFrame() && loadScript && !script) {
|
||||
const loadTimer = setTimeout(() => {
|
||||
script = document.createElement('script');
|
||||
script.src = 'https://adncdnend.azureedge.net/adtags/odysee.adn.js';
|
||||
|
@ -57,10 +57,9 @@ export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated:
|
|||
|
||||
return () => clearTimeout(loadTimer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isNotAuthenticated]);
|
||||
}, [loadScript]);
|
||||
|
||||
React.useEffect(() => {
|
||||
resolveVisibility();
|
||||
}, [isAuthenticated, pathname]);
|
||||
}, [hasPremiumPlus, pathname]);
|
||||
}
|
||||
|
|
50
ui/effects/use-should-show-ads.js
Normal file
50
ui/effects/use-should-show-ads.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { SHOW_ADS } from 'config';
|
||||
|
||||
const NO_COUNTRY_CHECK = true;
|
||||
|
||||
const GOOGLE_AD_URL = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
|
||||
let ad_blocker_detected;
|
||||
|
||||
export default function useShouldShowAds(
|
||||
hasPremiumPlus: boolean,
|
||||
userCountry: string,
|
||||
doSetAdBlockerFound: (boolean) => void
|
||||
) {
|
||||
const [shouldShowAds, setShouldShowAds] = React.useState(resolveAdVisibility());
|
||||
|
||||
function resolveAdVisibility() {
|
||||
// 'ad_blocker_detected' will be undefined at startup. Wait until we are
|
||||
// sure it is not blocked (i.e. === false) before showing the component.
|
||||
return ad_blocker_detected === false && SHOW_ADS && !hasPremiumPlus && (NO_COUNTRY_CHECK || userCountry === 'US');
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (ad_blocker_detected === undefined) {
|
||||
let mounted = true;
|
||||
|
||||
fetch(GOOGLE_AD_URL)
|
||||
.then((response) => {
|
||||
const detected = response.redirected === true;
|
||||
ad_blocker_detected = detected;
|
||||
doSetAdBlockerFound(detected); // Also stash in redux for components to listen to.
|
||||
})
|
||||
.catch(() => {
|
||||
ad_blocker_detected = true;
|
||||
doSetAdBlockerFound(true);
|
||||
})
|
||||
.finally(() => {
|
||||
if (mounted) {
|
||||
setShouldShowAds(resolveAdVisibility());
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return shouldShowAds;
|
||||
}
|
|
@ -223,7 +223,18 @@ function DiscoverPage(props: Props) {
|
|||
tags={tags}
|
||||
hiddenNsfwMessage={<HiddenNsfw type="page" />}
|
||||
repostedClaimId={repostedClaim ? repostedClaim.claim_id : null}
|
||||
injectedItem={!isWildWest && !hasPremiumPlus && { node: <Ads small type="video" tileLayout /> }}
|
||||
injectedItem={
|
||||
!isWildWest &&
|
||||
!hasPremiumPlus && {
|
||||
node: (index, lastVisibleIndex, pageSize) => {
|
||||
if (pageSize && index < pageSize) {
|
||||
return index === lastVisibleIndex ? <Ads small type="video" tileLayout={tileLayout} /> : null;
|
||||
} else {
|
||||
return index % (pageSize * 2) === 0 ? <Ads small type="video" tileLayout={tileLayout} /> : null;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
// TODO: find a better way to determine discover / wild west vs other modes release times
|
||||
// for now including && !tags so that
|
||||
releaseTime={releaseTime || undefined}
|
||||
|
|
|
@ -34,6 +34,7 @@ const select = (state) => ({
|
|||
homepageOrder: selectClientSetting(state, SETTINGS.HOMEPAGE_ORDER),
|
||||
hasMembership: selectHasOdyseeMembership(state),
|
||||
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||
currentTheme: selectClientSetting(state, SETTINGS.THEME),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -15,12 +15,13 @@ import Icon from 'component/common/icon';
|
|||
import WaitUntilOnPage from 'component/common/wait-until-on-page';
|
||||
import RecommendedPersonal from 'component/recommendedPersonal';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import { useIsLargeScreen } from 'effects/use-screensize';
|
||||
import { useIsLargeScreen, useIsMobile } from 'effects/use-screensize';
|
||||
import { GetLinksData } from 'util/buildHomepage';
|
||||
import { getLivestreamUris } from 'util/livestream';
|
||||
import ScheduledStreams from 'component/scheduledStreams';
|
||||
import { splitBySeparator } from 'util/lbryURI';
|
||||
import Ads from 'web/component/ads';
|
||||
import AdsBanner from 'web/component/adsBanner';
|
||||
import Meme from 'web/component/meme';
|
||||
|
||||
const CATEGORY_LIVESTREAM_LIMIT = 3;
|
||||
|
@ -43,6 +44,7 @@ type Props = {
|
|||
doOpenModal: (id: string, ?{}) => void,
|
||||
hasMembership: ?boolean,
|
||||
hasPremiumPlus: boolean,
|
||||
currentTheme: string,
|
||||
};
|
||||
|
||||
function HomePage(props: Props) {
|
||||
|
@ -62,12 +64,14 @@ function HomePage(props: Props) {
|
|||
doOpenModal,
|
||||
hasMembership,
|
||||
hasPremiumPlus,
|
||||
currentTheme,
|
||||
} = props;
|
||||
|
||||
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
|
||||
const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0;
|
||||
const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
const isMobileScreen = useIsMobile();
|
||||
const subscriptionChannelIds = subscribedChannels.map((sub) => splitBySeparator(sub.uri)[1]);
|
||||
|
||||
const rowData: Array<RowDataItem> = GetLinksData(
|
||||
|
@ -182,6 +186,8 @@ function HomePage(props: Props) {
|
|||
label={__('View More')}
|
||||
/>
|
||||
)}
|
||||
{isMobileScreen && <AdsBanner key={`${currentTheme}:${title}`} />}
|
||||
{!isMobileScreen && (index === 0 || index % 2 === 0) && <AdsBanner key={`${currentTheme}:${title}`} />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -319,6 +319,7 @@
|
|||
height: 27px !important;
|
||||
}
|
||||
|
||||
/*
|
||||
.ob-widget-footer {
|
||||
position: absolute !important;
|
||||
right: 32px;
|
||||
|
@ -339,6 +340,7 @@
|
|||
display: none !important;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
.ob-widget-items-container {
|
||||
padding-left: var(--spacing-xs);
|
||||
|
@ -366,3 +368,81 @@
|
|||
#av-container #av-inner #gui #timeline #timeline-progress {
|
||||
background: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// Banner
|
||||
// ****************************************************************************
|
||||
|
||||
$BANNER_BORDER: 1px;
|
||||
|
||||
.banner-ad {
|
||||
margin-top: var(--spacing-l);
|
||||
background-color: var(--color-ads-background);
|
||||
border: $BANNER_BORDER solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.banner-ad__container {
|
||||
min-height: 250px;
|
||||
|
||||
padding: var(--spacing-xxs) var(--spacing-m);
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-xxs) var(--spacing-xxs);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-xlarge) {
|
||||
max-width: calc(#{$breakpoint-xlarge} * 3 / 4);
|
||||
}
|
||||
}
|
||||
|
||||
.banner-ad__driver {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-width: 50%;
|
||||
|
||||
border-left: $BANNER_BORDER solid var(--color-border);
|
||||
border-bottom: $BANNER_BORDER solid var(--color-border);
|
||||
border-radius: 0 var(--border-radius) 0 var(--border-radius);
|
||||
float: right;
|
||||
|
||||
font-size: var(--font-xsmall);
|
||||
@media (max-width: $breakpoint-small) {
|
||||
font-size: var(--font-xxsmall);
|
||||
}
|
||||
|
||||
> * {
|
||||
padding: 0 var(--spacing-xxxs);
|
||||
}
|
||||
}
|
||||
|
||||
.banner-ad__driver-label {
|
||||
color: var(--color-text-subtitle);
|
||||
}
|
||||
|
||||
.banner-ad__driver-value {
|
||||
color: var(--color-text-subtitle);
|
||||
border-left: $BANNER_BORDER solid var(--color-border);
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@supports (-webkit-line-clamp: 3) {
|
||||
white-space: initial;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
line-height: 1.2;
|
||||
padding: calc(var(--spacing-xxxs) / 2) var(--spacing-xxxs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
// @flow
|
||||
import { SHOW_ADS } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useEffect } from 'react';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Button from 'component/button';
|
||||
import PremiumPlusTile from 'component/premiumPlusTile';
|
||||
import classnames from 'classnames';
|
||||
import useShouldShowAds from 'effects/use-should-show-ads';
|
||||
import { platform } from 'util/platform';
|
||||
import Icon from 'component/common/icon';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { LocalStorage, LS } from 'util/storage';
|
||||
|
||||
const USE_ADNIMATION = true;
|
||||
|
||||
// prettier-ignore
|
||||
const AD_CONFIGS = Object.freeze({
|
||||
DEFAULT: {
|
||||
|
@ -25,10 +27,30 @@ const AD_CONFIGS = Object.freeze({
|
|||
url: 'https://tg1.vidcrunch.com/api/adserver/spt?AV_TAGID=61dff05c599f1e20b01085d4&AV_PUBLISHERID=6182c8993c8ae776bd5635e9',
|
||||
tag: 'AV61dff05c599f1e20b01085d4',
|
||||
},
|
||||
ADNIMATION: {
|
||||
url: 'https://tg1.aniview.com/api/adserver/spt?AV_TAGID=6252bb6f28951333ec10a7a6&AV_PUBLISHERID=601d9a7f2e688a79e17c1265',
|
||||
tag: 'AV6252bb6f28951333ec10a7a6',
|
||||
},
|
||||
});
|
||||
|
||||
// Internal use only. One-time update flag.
|
||||
let ad_blocker_detected;
|
||||
// ****************************************************************************
|
||||
// Helpers
|
||||
// ****************************************************************************
|
||||
|
||||
function removeIfExists(querySelector) {
|
||||
const element = document.querySelector(querySelector);
|
||||
if (element) element.remove();
|
||||
}
|
||||
|
||||
function resolveVidcrunchConfig() {
|
||||
const mobileAds = platform.isAndroid() || platform.isIOS();
|
||||
const isInEu = LocalStorage.getItem(LS.GDPR_REQUIRED) === 'true';
|
||||
return isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// Ads
|
||||
// ****************************************************************************
|
||||
|
||||
type Props = {
|
||||
type: string,
|
||||
|
@ -37,18 +59,11 @@ type Props = {
|
|||
className?: string,
|
||||
noFallback?: boolean,
|
||||
// --- redux ---
|
||||
claim: Claim,
|
||||
isMature: boolean,
|
||||
userHasPremiumPlus: boolean,
|
||||
userCountry: string,
|
||||
doSetAdBlockerFound: (boolean) => void,
|
||||
};
|
||||
|
||||
function removeIfExists(querySelector) {
|
||||
const element = document.querySelector(querySelector);
|
||||
if (element) element.remove();
|
||||
}
|
||||
|
||||
function Ads(props: Props) {
|
||||
const {
|
||||
type = 'video',
|
||||
|
@ -61,46 +76,8 @@ function Ads(props: Props) {
|
|||
doSetAdBlockerFound,
|
||||
} = props;
|
||||
|
||||
const [shouldShowAds, setShouldShowAds] = React.useState(resolveAdVisibility());
|
||||
const mobileAds = platform.isAndroid() || platform.isIOS();
|
||||
|
||||
// this is populated from app based on location
|
||||
const isInEu = LocalStorage.getItem(LS.GDPR_REQUIRED) === 'true';
|
||||
const adConfig = isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
||||
|
||||
function resolveAdVisibility() {
|
||||
// 'ad_blocker_detected' will be undefined at startup. Wait until we are
|
||||
// sure it is not blocked (i.e. === false) before showing the component.
|
||||
return ad_blocker_detected === false && SHOW_ADS && !userHasPremiumPlus && userCountry === 'US';
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ad_blocker_detected === undefined) {
|
||||
let mounted = true;
|
||||
const GOOGLE_AD_URL = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
|
||||
|
||||
fetch(GOOGLE_AD_URL)
|
||||
.then((response) => {
|
||||
const detected = response.redirected === true;
|
||||
window.odysee_ad_blocker_detected = detected;
|
||||
ad_blocker_detected = detected;
|
||||
doSetAdBlockerFound(detected);
|
||||
})
|
||||
.catch(() => {
|
||||
ad_blocker_detected = true;
|
||||
doSetAdBlockerFound(true);
|
||||
})
|
||||
.finally(() => {
|
||||
if (mounted) {
|
||||
setShouldShowAds(resolveAdVisibility());
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
const shouldShowAds = useShouldShowAds(userHasPremiumPlus, userCountry, doSetAdBlockerFound);
|
||||
const adConfig = USE_ADNIMATION ? AD_CONFIGS.ADNIMATION : resolveVidcrunchConfig();
|
||||
|
||||
// add script to DOM
|
||||
useEffect(() => {
|
||||
|
@ -122,11 +99,20 @@ function Ads(props: Props) {
|
|||
delete window.__VIDCRUNCH_CONFIG_618bb4d28aac298191eec411__;
|
||||
delete window.__player_618bb4d28aac298191eec411__;
|
||||
|
||||
const styles = document.querySelectorAll('body > style');
|
||||
styles.forEach((s) => {
|
||||
// We are asking Adnimation to supply us with a specific ID or
|
||||
// pattern so that our query wouldn't break when they change their
|
||||
// script. For now, this is the "best effort".
|
||||
if (s.innerText && s.innerText.startsWith('#outbrain')) {
|
||||
s.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// clean DOM elements from ad related elements
|
||||
removeIfExists('[src^="https://cdn.vidcrunch.com/618bb4d28aac298191eec411.js"]');
|
||||
removeIfExists('[src^="https://player.aniview.com/script/6.1/aniview.js"]');
|
||||
removeIfExists('[id^="AVLoaderaniplayer_vidcrunch"]');
|
||||
removeIfExists('#av_css_id');
|
||||
removeIfExists('[src^="https://player.avplayer.com"]');
|
||||
removeIfExists('[src^="https://gum.criteo.com"]');
|
||||
removeIfExists('[id^="AVLoaderaniview_slot"]');
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
|
|
18
web/component/adsBanner/index.js
Normal file
18
web/component/adsBanner/index.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { doSetAdBlockerFound } from 'redux/actions/app';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectOdyseeMembershipIsPremiumPlus, selectUserCountry } from 'redux/selectors/user';
|
||||
import AdsBanner from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
userHasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||
userCountry: selectUserCountry(state),
|
||||
currentTheme: selectClientSetting(state, SETTINGS.THEME),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
doSetAdBlockerFound,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(AdsBanner);
|
94
web/component/adsBanner/view.jsx
Normal file
94
web/component/adsBanner/view.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import useShouldShowAds from 'effects/use-should-show-ads';
|
||||
|
||||
const AD_SCRIPT_URL = 'https://widgets.outbrain.com/outbrain.js';
|
||||
|
||||
const AD_CONFIG = {
|
||||
AR_18: 'AR_18', // 5 tiles.
|
||||
AR_60: 'AR_60', // 6 tiles. Doesn't work well on mobile (6 tiles compresses to 1, text only).
|
||||
AR_3: 'AR_3', // 4 tiles on desktop, dynamic count on mobile.
|
||||
};
|
||||
|
||||
// ****************************************************************************
|
||||
// ****************************************************************************
|
||||
|
||||
const adsSignInDriver = (
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
sign_up_for_premium: (
|
||||
<Button button="link" label={__('Get Odysee Premium+')} navigate={`/$/${PAGES.ODYSEE_MEMBERSHIP}`} />
|
||||
),
|
||||
}}
|
||||
>
|
||||
%sign_up_for_premium% for an ad free experience.
|
||||
</I18nMessage>
|
||||
);
|
||||
|
||||
// ****************************************************************************
|
||||
// AdsBanner
|
||||
// ****************************************************************************
|
||||
|
||||
let gReferenceCounter = 0;
|
||||
|
||||
type Props = {
|
||||
userHasPremiumPlus: boolean,
|
||||
userCountry: string,
|
||||
currentTheme: string,
|
||||
doSetAdBlockerFound: (boolean) => void,
|
||||
};
|
||||
|
||||
export default function AdsBanner(props: Props) {
|
||||
const { userHasPremiumPlus, userCountry, currentTheme, doSetAdBlockerFound } = props;
|
||||
const shouldShowAds = useShouldShowAds(userHasPremiumPlus, userCountry, doSetAdBlockerFound);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldShowAds) {
|
||||
try {
|
||||
const script = document.createElement('script');
|
||||
script.src = AD_SCRIPT_URL;
|
||||
script.async = true;
|
||||
script.onload = () => {
|
||||
++gReferenceCounter;
|
||||
};
|
||||
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
return () => {
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(script);
|
||||
|
||||
if (--gReferenceCounter <= 0) {
|
||||
// Note: This method has the bad requirement of the parent having to
|
||||
// mount and dismount all banners in the same cycle.
|
||||
delete window.OBR;
|
||||
// TODO: clear styles after the team adds an ID or class for us to query.
|
||||
}
|
||||
};
|
||||
} catch (e) {}
|
||||
}
|
||||
}, [shouldShowAds]);
|
||||
|
||||
if (!shouldShowAds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="banner-ad">
|
||||
<div className="banner-ad__driver">
|
||||
<div className="banner-ad__driver-label">Ad</div>
|
||||
<div className="banner-ad__driver-value">{adsSignInDriver}</div>
|
||||
</div>
|
||||
<div
|
||||
className="banner-ad__container OUTBRAIN"
|
||||
data-ob-contenturl="DROP_PERMALINK_HERE"
|
||||
data-widget-id={AD_CONFIG.AR_18}
|
||||
data-ob-installation-key="ADNIMKAJDGAG4GAO6AGG6H5KP"
|
||||
data-dark-mode={currentTheme === 'dark' ? 'true' : 'false'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue