specify ad provider (#3005)
* specify ad provider * update publisher key * upgrade specify sdk * use profile address for ads * unifiy banner size, remove hype * upgrade specify sdk * review fixes
@@ -12,10 +12,11 @@ const provider: AdBannerProviders = (() => {
|
||||
})();
|
||||
|
||||
const additionalProvider = getEnvValue('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER') as AdBannerAdditionalProviders;
|
||||
const isSpecifyEnabled = getEnvValue('NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY') === 'true';
|
||||
|
||||
const title = 'Banner ads';
|
||||
|
||||
type AdsBannerFeaturePayload = {
|
||||
type AdsBannerFeatureProviderPayload = {
|
||||
provider: Exclude<AdBannerProviders, 'adbutler' | 'none'>;
|
||||
} | {
|
||||
provider: 'adbutler';
|
||||
@@ -36,6 +37,10 @@ type AdsBannerFeaturePayload = {
|
||||
};
|
||||
};
|
||||
|
||||
type AdsBannerFeaturePayload = AdsBannerFeatureProviderPayload & {
|
||||
isSpecifyEnabled: boolean;
|
||||
};
|
||||
|
||||
const config: Feature<AdsBannerFeaturePayload> = (() => {
|
||||
if (provider === 'adbutler') {
|
||||
const desktopConfig = parseEnvJson<AdButlerConfig>(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP'));
|
||||
@@ -52,6 +57,7 @@ const config: Feature<AdsBannerFeaturePayload> = (() => {
|
||||
mobile: mobileConfig,
|
||||
},
|
||||
},
|
||||
isSpecifyEnabled,
|
||||
});
|
||||
}
|
||||
} else if (provider !== 'none') {
|
||||
@@ -71,12 +77,14 @@ const config: Feature<AdsBannerFeaturePayload> = (() => {
|
||||
mobile: mobileConfig,
|
||||
},
|
||||
},
|
||||
isSpecifyEnabled,
|
||||
});
|
||||
}
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
provider,
|
||||
isSpecifyEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -72,3 +72,4 @@ NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
|
||||
NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS=['solidity','vyper','yul','geas']
|
||||
NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY=true
|
||||
|
||||
@@ -10,7 +10,7 @@ NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
|
||||
# Instance ENVs
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=hype
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=slise
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_HOST=www.shibariumscan.io
|
||||
|
||||
@@ -459,6 +459,7 @@ const adsBannerSchema = yup
|
||||
NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: yup.string<AdBannerAdditionalProviders>().oneOf(SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS),
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: adButlerConfigSchema,
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema,
|
||||
NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY: yup.boolean(),
|
||||
});
|
||||
|
||||
const accountSchema = yup
|
||||
|
||||
@@ -456,10 +456,11 @@ Ads are enabled by default on all self-hosted instances. If you would like to di
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `hype` \| `none` | Ads provider | - | `slise` | `coinzilla` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `none` | Ads provider | - | `slise` | `coinzilla` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER | `adbutler` | Additional ads provider to mix with the main one | - | - | `adbutler` | v1.28.0+ |
|
||||
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` | v1.3.0+ |
|
||||
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` | v1.3.0+ |
|
||||
| NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY | `boolean` | Enables Specify ads in addition to the main ad banner provider | - | - | `true` | upcoming |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ const TEST_URLS: Record<AdBannerProviders, string> = {
|
||||
slise: 'https://v1.slise.xyz/serve',
|
||||
coinzilla: 'https://request-global.czilladx.com',
|
||||
adbutler: 'https://servedbyadbutler.com/app.js',
|
||||
hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js',
|
||||
none: DEFAULT_URL,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import sha256 from 'crypto-js/sha256';
|
||||
import type CspDev from 'csp-dev';
|
||||
|
||||
import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript';
|
||||
import { hypeInit } from 'ui/shared/ad/hypeBannerScript';
|
||||
|
||||
export function ad(): CspDev.DirectiveDescriptor {
|
||||
return {
|
||||
@@ -19,10 +18,8 @@ export function ad(): CspDev.DirectiveDescriptor {
|
||||
// slise
|
||||
'*.slise.xyz',
|
||||
|
||||
// hype
|
||||
'api.hypelab.com',
|
||||
'*.ixncdn.com',
|
||||
'*.cloudfront.net',
|
||||
// specify
|
||||
'app.specify.sh',
|
||||
],
|
||||
'frame-src': [
|
||||
// coinzilla
|
||||
@@ -40,11 +37,6 @@ export function ad(): CspDev.DirectiveDescriptor {
|
||||
|
||||
// slise
|
||||
'*.slise.xyz',
|
||||
|
||||
//hype
|
||||
`'sha256-${ Base64.stringify(sha256(hypeInit ?? '')) }'`,
|
||||
'https://api.hypelab.com',
|
||||
'd1q98dzwj6s2rb.cloudfront.net',
|
||||
],
|
||||
'img-src': [
|
||||
// coinzilla
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"@emotion/react": "11.14.0",
|
||||
"@growthbook/growthbook-react": "0.21.0",
|
||||
"@helia/verified-fetch": "2.6.12",
|
||||
"@hypelab/sdk-react": "^1.0.0",
|
||||
"@metamask/post-message-stream": "^7.0.0",
|
||||
"@metamask/providers": "^10.2.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
@@ -74,6 +73,7 @@
|
||||
"@rollbar/react": "0.12.1",
|
||||
"@scure/base": "1.1.9",
|
||||
"@slise/embed-react": "^2.2.0",
|
||||
"@specify-sh/sdk": "0.3.0",
|
||||
"@tanstack/react-query": "5.55.4",
|
||||
"@tanstack/react-query-devtools": "5.55.4",
|
||||
"@types/papaparse": "^5.3.5",
|
||||
|
||||
@@ -94,6 +94,9 @@ const config: PlaywrightTestConfig = defineConfig({
|
||||
|
||||
// Mock for @helia/verified-fetch to avoid build issues in tests
|
||||
{ find: '@helia/verified-fetch', replacement: './playwright/mocks/modules/@helia/verified-fetch.js' },
|
||||
|
||||
// Mock for @specify-sh/sdk to avoid build issues in tests
|
||||
{ find: '@specify-sh/sdk', replacement: './playwright/mocks/modules/@specify-sh/sdk.js' },
|
||||
],
|
||||
},
|
||||
define: {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Mock for @specify-sh/sdk to avoid build issues in Playwright component tests
|
||||
|
||||
export const ImageFormat = {
|
||||
SHORT_BANNER: 'short_banner',
|
||||
LONG_BANNER: 'long_banner',
|
||||
};
|
||||
|
||||
export default class Specify {
|
||||
constructor(options) {
|
||||
this.publisherKey = options.publisherKey;
|
||||
}
|
||||
|
||||
async serve() {
|
||||
// Mock implementation that returns null to simulate no ad
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const SpecifyAd = {
|
||||
imageUrl: '',
|
||||
headline: '',
|
||||
ctaUrl: '',
|
||||
};
|
||||
@@ -4,7 +4,6 @@ export const SUPPORTED_AD_BANNER_PROVIDERS = [
|
||||
'slise',
|
||||
'adbutler',
|
||||
'coinzilla',
|
||||
'hype',
|
||||
'none',
|
||||
] as const;
|
||||
export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>;
|
||||
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -5,14 +5,18 @@ import type { BannerPlatform } from './types';
|
||||
import type { AdBannerProviders } from 'types/client/adProviders';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
|
||||
|
||||
import AdbutlerBanner from './AdbutlerBanner';
|
||||
import CoinzillaBanner from './CoinzillaBanner';
|
||||
import HypeBanner from './HypeBanner';
|
||||
import { DESKTOP_BANNER_WIDTH, MOBILE_BANNER_WIDTH } from './consts';
|
||||
import SliseBanner from './SliseBanner';
|
||||
import SpecifyBanner from './SpecifyBanner';
|
||||
|
||||
const feature = config.features.adsBanner;
|
||||
const isSpecifyEnabled = feature.isEnabled && feature.isSpecifyEnabled;
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -22,14 +26,26 @@ interface Props {
|
||||
}
|
||||
|
||||
const AdBannerContent = ({ className, isLoading, provider, platform }: Props) => {
|
||||
const { address: addressWC, isConnecting } = useAccount();
|
||||
const profileQuery = useProfileQuery();
|
||||
const [ showSpecify, setShowSpecify ] = React.useState(isSpecifyEnabled);
|
||||
|
||||
const handleEmptySpecify = React.useCallback(() => {
|
||||
setShowSpecify(false);
|
||||
}, []);
|
||||
|
||||
const address = addressWC || profileQuery.data?.address_hash as `0x${ string }` | undefined;
|
||||
|
||||
const content = (() => {
|
||||
if (showSpecify && (address || profileQuery.isLoading || isConnecting)) {
|
||||
const isLoading = address ? false : profileQuery.isLoading || isConnecting;
|
||||
return <SpecifyBanner platform={ platform } address={ address } onEmpty={ handleEmptySpecify } isLoading={ isLoading }/>;
|
||||
}
|
||||
switch (provider) {
|
||||
case 'adbutler':
|
||||
return <AdbutlerBanner platform={ platform }/>;
|
||||
case 'coinzilla':
|
||||
return <CoinzillaBanner platform={ platform }/>;
|
||||
case 'hype':
|
||||
return <HypeBanner platform={ platform }/>;
|
||||
case 'slise':
|
||||
return <SliseBanner platform={ platform }/>;
|
||||
}
|
||||
@@ -40,7 +56,7 @@ const AdBannerContent = ({ className, isLoading, provider, platform }: Props) =>
|
||||
className={ className }
|
||||
loading={ isLoading }
|
||||
borderRadius="none"
|
||||
maxW={ ('adButler' in feature && feature.adButler) ? feature.adButler.config.desktop.width : '728px' }
|
||||
maxW={{ base: `${ MOBILE_BANNER_WIDTH }px`, lg: `${ DESKTOP_BANNER_WIDTH }px` }}
|
||||
w="100%"
|
||||
>
|
||||
{ content }
|
||||
|
||||
@@ -10,6 +10,13 @@ import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import { isBrowser } from 'toolkit/utils/isBrowser';
|
||||
import { connectAdbutler, placeAd, ADBUTLER_ACCOUNT } from 'ui/shared/ad/adbutlerScript';
|
||||
|
||||
import {
|
||||
DESKTOP_BANNER_WIDTH,
|
||||
DESKTOP_BANNER_HEIGHT,
|
||||
MOBILE_BANNER_WIDTH,
|
||||
MOBILE_BANNER_HEIGHT,
|
||||
} from './consts';
|
||||
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
const AdbutlerBanner = ({ className, platform }: BannerProps) => {
|
||||
@@ -66,30 +73,15 @@ const AdbutlerBanner = ({ className, platform }: BannerProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { width, height } = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop':
|
||||
return { width: `${ feature.adButler.config.desktop.width }px`, height: `${ feature.adButler.config.desktop.height }px` };
|
||||
case 'mobile':
|
||||
return { width: `${ feature.adButler.config.mobile.width }px`, height: `${ feature.adButler.config.mobile.height }px` };
|
||||
default:
|
||||
return {
|
||||
width: {
|
||||
base: `${ feature.adButler.config.mobile.width }px`,
|
||||
lg: `${ feature.adButler.config.desktop.width }px`,
|
||||
},
|
||||
height: {
|
||||
base: `${ feature.adButler.config.mobile.height }px`,
|
||||
lg: `${ feature.adButler.config.desktop.height }px`,
|
||||
},
|
||||
};
|
||||
}
|
||||
})() ?? { width: '0', height: '0' };
|
||||
|
||||
const getElementId = (id: string) => id + (platform ? `_${ platform }` : '');
|
||||
|
||||
return (
|
||||
<Flex className={ className } id={ getElementId('adBanner') } h={ height } w={ width }>
|
||||
<Flex
|
||||
className={ className }
|
||||
id={ getElementId('adBanner') }
|
||||
h={ platform === 'mobile' ? `${ MOBILE_BANNER_HEIGHT }px` : `${ DESKTOP_BANNER_HEIGHT }px` }
|
||||
w={ platform === 'mobile' ? `${ MOBILE_BANNER_WIDTH }px` : `${ DESKTOP_BANNER_WIDTH }px` }
|
||||
>
|
||||
{ !isHidden && (
|
||||
<>
|
||||
<Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script>
|
||||
|
||||
@@ -7,6 +7,13 @@ import type { BannerProps } from './types';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import { isBrowser } from 'toolkit/utils/isBrowser';
|
||||
|
||||
import {
|
||||
DESKTOP_BANNER_WIDTH,
|
||||
MOBILE_BANNER_WIDTH,
|
||||
DESKTOP_BANNER_HEIGHT,
|
||||
MOBILE_BANNER_HEIGHT,
|
||||
} from './consts';
|
||||
|
||||
const CoinzillaBanner = ({ className, platform }: BannerProps) => {
|
||||
const isInBrowser = isBrowser();
|
||||
const isMobileViewport = useIsMobile();
|
||||
@@ -22,9 +29,9 @@ const CoinzillaBanner = ({ className, platform }: BannerProps) => {
|
||||
const { width, height } = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop':
|
||||
return { width: 728, height: 90 };
|
||||
return { width: DESKTOP_BANNER_WIDTH, height: DESKTOP_BANNER_HEIGHT };
|
||||
case 'mobile':
|
||||
return { width: 320, height: 100 };
|
||||
return { width: MOBILE_BANNER_WIDTH, height: MOBILE_BANNER_HEIGHT };
|
||||
default:
|
||||
return { width: undefined, height: undefined };
|
||||
}
|
||||
@@ -36,18 +43,18 @@ const CoinzillaBanner = ({ className, platform }: BannerProps) => {
|
||||
window.coinzilla_display = window.coinzilla_display || [];
|
||||
const cDisplayPreferences = {
|
||||
zone: '26660bf627543e46851',
|
||||
width: width ? String(width) : '728',
|
||||
height: height ? String(height) : '90',
|
||||
width: width ? String(width) : DESKTOP_BANNER_WIDTH.toString(),
|
||||
height: height ? String(height) : DESKTOP_BANNER_HEIGHT.toString(),
|
||||
};
|
||||
window.coinzilla_display.push(cDisplayPreferences);
|
||||
}
|
||||
}, [ height, isInBrowser, isHidden, width ]);
|
||||
}, [ isInBrowser, isHidden, platform, width, height ]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={ className }
|
||||
id={ 'adBanner' + (platform ? `_${ platform }` : '') }
|
||||
h={ height ? `${ height }px` : { base: '100px', lg: '90px' } }
|
||||
h={ height ? `${ height }px` : { base: `${ MOBILE_BANNER_HEIGHT }px`, lg: `${ DESKTOP_BANNER_HEIGHT }px` } }
|
||||
w={ width ? `${ width }px` : undefined }
|
||||
>
|
||||
{ !isHidden && (
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Flex, chakra } from '@chakra-ui/react';
|
||||
import { Banner, setWalletAddresses } from '@hypelab/sdk-react';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
|
||||
import { hypeInit } from './hypeBannerScript';
|
||||
|
||||
const DESKTOP_BANNER_SLUG = 'b1559fc3e7';
|
||||
const MOBILE_BANNER_SLUG = '668ed80a9e';
|
||||
|
||||
const HypeBanner = ({ className, platform }: BannerProps) => {
|
||||
const { address } = useAccount();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (address) {
|
||||
setWalletAddresses([ address ]);
|
||||
}
|
||||
}, [ address ]);
|
||||
|
||||
const banner = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop': {
|
||||
return (
|
||||
<Flex className={ className } w="728px" h="90px">
|
||||
<Banner placement={ DESKTOP_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
case 'mobile': {
|
||||
return (
|
||||
<Flex className={ className } w="320px" h="50px">
|
||||
<Banner placement={ MOBILE_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<>
|
||||
<Flex className={ className } w="728px" h="90px" display={{ base: 'none', lg: 'flex' }}>
|
||||
<Banner placement={ DESKTOP_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
<Flex className={ className } w="320px" h="50px" display={{ base: 'flex', lg: 'none' }}>
|
||||
<Banner placement={ MOBILE_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
id="hypelab"
|
||||
strategy="afterInteractive"
|
||||
>
|
||||
{ hypeInit }
|
||||
</Script>
|
||||
{ banner }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(HypeBanner);
|
||||
@@ -6,47 +6,66 @@ import type { BannerProps } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import {
|
||||
DESKTOP_BANNER_HEIGHT,
|
||||
DESKTOP_BANNER_WIDTH,
|
||||
MOBILE_BANNER_HEIGHT,
|
||||
MOBILE_BANNER_WIDTH,
|
||||
} from './consts';
|
||||
|
||||
const SliseBanner = ({ className, platform }: BannerProps) => {
|
||||
|
||||
if (platform === 'desktop') {
|
||||
return (
|
||||
<Flex className={ className } h="90px">
|
||||
<Flex className={ className } h={ `${ DESKTOP_BANNER_HEIGHT }px` }>
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="728x90"
|
||||
style={{ width: '728px', height: '90px' }}/>
|
||||
style={{ width: `${ DESKTOP_BANNER_WIDTH }px`, height: `${ DESKTOP_BANNER_HEIGHT }px` }}/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'mobile') {
|
||||
// slise provides 270x90 banner on mobile, but we use 320x100 format, so we scale it a little bit
|
||||
return (
|
||||
<Flex className={ className } h="90px">
|
||||
<Flex
|
||||
className={ className }
|
||||
h={ `${ MOBILE_BANNER_HEIGHT }px` }
|
||||
w={ `${ MOBILE_BANNER_WIDTH }px` }
|
||||
justifyContent="center"
|
||||
>
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="270x90"
|
||||
style={{ width: '270px', height: '90px' }}/>
|
||||
style={{ width: `300px`, height: `${ MOBILE_BANNER_HEIGHT }px` }}/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex className={ className } h="90px" display={{ base: 'none', lg: 'flex' }}>
|
||||
<Flex className={ className } h={ `${ DESKTOP_BANNER_HEIGHT }px` } display={{ base: 'none', lg: 'flex' }}>
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="728x90"
|
||||
style={{ width: '728px', height: '90px' }}/>
|
||||
style={{ width: `${ DESKTOP_BANNER_WIDTH }px`, height: `${ DESKTOP_BANNER_HEIGHT }px` }}/>
|
||||
</Flex>
|
||||
<Flex className={ className } h="90px" display={{ base: 'flex', lg: 'none' }}>
|
||||
<Flex
|
||||
className={ className }
|
||||
h={ `${ MOBILE_BANNER_HEIGHT }px` }
|
||||
w={ `${ MOBILE_BANNER_WIDTH }px` }
|
||||
justifyContent="center"
|
||||
display={{ base: 'flex', lg: 'none' }}
|
||||
>
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="270x90"
|
||||
style={{ width: '270px', height: '90px' }}/>
|
||||
style={{ width: `300px`, height: `${ MOBILE_BANNER_HEIGHT }px` }}/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Box, chakra } from '@chakra-ui/react';
|
||||
import type { SpecifyAd } from '@specify-sh/sdk';
|
||||
import Specify, { ImageFormat } from '@specify-sh/sdk';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import { Image } from 'toolkit/chakra/image';
|
||||
|
||||
import {
|
||||
DESKTOP_BANNER_HEIGHT,
|
||||
DESKTOP_BANNER_WIDTH,
|
||||
MOBILE_BANNER_WIDTH,
|
||||
MOBILE_BANNER_HEIGHT,
|
||||
} from './consts';
|
||||
|
||||
const PUBLISHER_KEY = 'spk_uq51124ciii28vt00f8za4m5hibpuh';
|
||||
|
||||
type Props = BannerProps & {
|
||||
address: `0x${ string }` | undefined;
|
||||
onEmpty: () => void;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const SpecifyBanner = ({ className, platform, address, onEmpty, isLoading }: Props) => {
|
||||
const isMobileViewport = useIsMobile();
|
||||
const isMobile = platform === 'mobile' || isMobileViewport;
|
||||
const [ ad, setAd ] = React.useState<SpecifyAd | null>(null);
|
||||
const [ isFetching, setIsFetching ] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (address && !isLoading) {
|
||||
const specify = new Specify({
|
||||
publisherKey: PUBLISHER_KEY,
|
||||
// sdk provides cacheAddressesInLocalSession option, but we decided to launch without it for now
|
||||
});
|
||||
const fetchContent = async() => {
|
||||
try {
|
||||
const content = await specify.serve(
|
||||
[ address as `0x${ string }` ],
|
||||
{ imageFormat: isMobile ? ImageFormat.SHORT_BANNER : ImageFormat.LONG_BANNER },
|
||||
);
|
||||
if (content?.imageUrl) {
|
||||
setAd(content);
|
||||
} else {
|
||||
onEmpty();
|
||||
}
|
||||
} catch (error) {
|
||||
onEmpty();
|
||||
} finally {
|
||||
setIsFetching(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchContent();
|
||||
}
|
||||
}, [ address, isMobile, onEmpty, isLoading ]);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
window.open(ad?.ctaUrl, '_blank');
|
||||
}, [ ad?.ctaUrl ]);
|
||||
|
||||
const { width, height } = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop':
|
||||
return { width: DESKTOP_BANNER_WIDTH, height: DESKTOP_BANNER_HEIGHT };
|
||||
case 'mobile':
|
||||
return { width: MOBILE_BANNER_WIDTH, height: MOBILE_BANNER_HEIGHT };
|
||||
default:
|
||||
return { width: undefined, height: undefined };
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
if (isLoading || isFetching) {
|
||||
return (
|
||||
<Box className={ className }
|
||||
h={ height ? `${ height }px` : { base: `${ MOBILE_BANNER_HEIGHT }px`, lg: `${ DESKTOP_BANNER_HEIGHT }px` } }
|
||||
w={ width ? `${ width }px` : undefined }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!ad) return null;
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={ ad.imageUrl }
|
||||
alt={ ad.headline }
|
||||
cursor="pointer"
|
||||
onClick={ handleClick }
|
||||
className={ className }
|
||||
w={ width ? `${ width }px` : { base: `${ MOBILE_BANNER_WIDTH }px`, lg: `${ DESKTOP_BANNER_WIDTH }px` } }
|
||||
h={ height ? `${ height }px` : { base: `${ MOBILE_BANNER_HEIGHT }px`, lg: `${ DESKTOP_BANNER_HEIGHT }px` } }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(SpecifyBanner);
|
||||
@@ -0,0 +1,4 @@
|
||||
export const DESKTOP_BANNER_WIDTH = 728;
|
||||
export const DESKTOP_BANNER_HEIGHT = 90;
|
||||
export const MOBILE_BANNER_WIDTH = 320;
|
||||
export const MOBILE_BANNER_HEIGHT = 100;
|
||||
@@ -1,21 +0,0 @@
|
||||
import config from 'configs/app';
|
||||
|
||||
const PRODUCTION_PROPERTY_SLUG = '127fddd522';
|
||||
const HYPE_API_URL = 'https://api.hypelab.com';
|
||||
|
||||
export const hypeInit = (() => {
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
if (!feature.isEnabled || feature.provider !== 'hype') {
|
||||
return;
|
||||
}
|
||||
|
||||
return `!(function (h, y, p, e, l, a, b) {
|
||||
((l = document.createElement(h)).async = !0),
|
||||
(l.src = y),
|
||||
(l.onload = function () {
|
||||
(a = { URL: p, propertySlug: e, environment: 'production' }), HypeLab.initialize(a);
|
||||
}),
|
||||
(b = document.getElementsByTagName(h)[0]).parentNode.insertBefore(l, b);
|
||||
})('script', 'https://api.hypelab.com/v1/scripts/hp-sdk.js?v=0', '${ HYPE_API_URL }', '${ PRODUCTION_PROPERTY_SLUG }');`;
|
||||
})();
|
||||
@@ -1,3 +1,4 @@
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import * as cookies from 'lib/cookies';
|
||||
|
||||
@@ -5,7 +6,7 @@ export default function useProfileQuery() {
|
||||
return useApiQuery('general:user_info', {
|
||||
queryOptions: {
|
||||
refetchOnMount: false,
|
||||
enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
|
||||
enabled: config.features.account.isEnabled && Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@@ -2899,11 +2899,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b"
|
||||
integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==
|
||||
|
||||
"@hypelab/sdk-react@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@hypelab/sdk-react/-/sdk-react-1.0.0.tgz#399058576422a7a715edfd6c55b946950a26ba39"
|
||||
integrity sha512-d8eJOqgocXvfMae/JbFXZdK/Q79oYYXScto1wJ7uTDHxrPZTIpgVNhuJM7UKmVJAKRSHnniRcZCjh2+pZCW5ww==
|
||||
|
||||
"@img/[email protected]":
|
||||
version "0.34.3"
|
||||
resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz#4850c8ace3c1dc13607fa07d43377b1f9aa774da"
|
||||
@@ -6427,6 +6422,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
"@specify-sh/[email protected]":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@specify-sh/sdk/-/sdk-0.3.0.tgz#997dcb864607e5e93801672d2b6197667e7e9c3a"
|
||||
integrity sha512-xTX19bQp0R36bee4QW4r6A21d2WQ2za9vps4DUI87uKRNXhQ3m3DUxfmWO9TCQ0l2MVyWRdIGsZXe4FWnHjKnw==
|
||||
dependencies:
|
||||
cross-fetch "4.0.0"
|
||||
|
||||
"@stablelib/aead@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3"
|
||||
@@ -10896,6 +10898,13 @@ create-require@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||
|
||||
[email protected], cross-fetch@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
|
||||
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
|
||||
dependencies:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-fetch@^3.0.4:
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.6.tgz#bae05aa31a4da760969756318feeee6e70f15d6c"
|
||||
@@ -10910,13 +10919,6 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5:
|
||||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-fetch@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
|
||||
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
|
||||
dependencies:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
|
||||