Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Add custom ad banner #1202

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion configs/app/features/adsBanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const provider: AdBannerProviders = (() => {
const title = 'Banner ads';

type AdsBannerFeaturePayload = {
provider: Exclude<AdBannerProviders, 'adbutler' | 'none'>;
provider: Exclude<AdBannerProviders, 'adbutler' | 'custom' | 'none'>;
} | {
provider: 'adbutler';
adButler: {
Expand All @@ -23,6 +23,9 @@ type AdsBannerFeaturePayload = {
mobile: AdButlerConfig;
};
};
} | {
provider: 'custom';
configUrl: string;
}

const config: Feature<AdsBannerFeaturePayload> = (() => {
Expand All @@ -43,6 +46,17 @@ const config: Feature<AdsBannerFeaturePayload> = (() => {
},
});
}
} else if (provider === 'custom') {
const configUrl = getEnvValue(process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL);

if (configUrl) {
return Object.freeze({
title,
isEnabled: true,
provider,
configUrl,
});
}
} else if (provider !== 'none') {
return Object.freeze({
title,
Expand Down
9 changes: 9 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ const adsBannerSchema = yup
NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string<AdBannerProviders>().oneOf(SUPPORTED_AD_BANNER_PROVIDERS),
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: adButlerConfigSchema,
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema,
NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL: yup
.string()
.when('NEXT_PUBLIC_AD_BANNER_PROVIDER', {
is: (value: AdBannerProviders) => value === 'custom',
then: (schema) => schema.url().required(),
otherwise: (schema) =>
schema.max(-1, 'NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL cannot not be used without NEXT_PUBLIC_AD_BANNER_PROVIDER being set to "custom", ' +
'and it must be a valid URL'),
}),
});

const sentrySchema = yup
Expand Down
12 changes: 11 additions & 1 deletion docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,19 @@ This feature is **enabled by default** with the `slise` ads provider. To switch

| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `none` | Ads provider | - | `slise` | `coinzilla` |
| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `custom` \| `none` | Ads provider | - | `slise` | `coinzilla` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` |
| NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL | `string` | URL of configuration file (.json format only) which contains list of custom banners that will be shown in the network menu. See below list of available properties for particular banner | - | - | `https://example.com/ad_custom_config.json` |

#### Custom banners configuration properties

| Variable | Type | Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| text | `string` | Tooltip text displayed when the mouse is moved over the banner. | Required | - | `Anyblock` |
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
| url | `string` | Link that opens when clicking on the banner. | Required | - | `https://example.com` |
| desktop | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is greater than 62em (usually 99px). | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` |
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
| mobile | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is less than 62em (usually 99px). | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` |

&nbsp;

Expand Down
2 changes: 1 addition & 1 deletion nextjs/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ declare module "nextjs-routes" {
| StaticRoute<"/api/media-type">
| StaticRoute<"/api/proxy">
| StaticRoute<"/api-docs">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
| DynamicRoute<"/apps/[id]", { "id": string }>
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
| StaticRoute<"/auth/auth0">
| StaticRoute<"/auth/profile">
| StaticRoute<"/auth/unverified-email">
Expand Down
6 changes: 6 additions & 0 deletions types/client/adCustomConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type AdCustomConfig = {
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
text: string;
url: string;
desktop: string;
mobile: string;
}
2 changes: 1 addition & 1 deletion types/client/adProviders.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ArrayElement } from 'types/utils';

export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'none' ] as const;
export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'custom', 'none' ] as const;
export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>;

export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const;
Expand Down
3 changes: 3 additions & 0 deletions ui/shared/ad/AdBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as cookies from 'lib/cookies';

import AdbutlerBanner from './AdbutlerBanner';
import CoinzillaBanner from './CoinzillaBanner';
import CustomAdBanner from './CustomAdBanner';
import SliseBanner from './SliseBanner';

const feature = config.features.adsBanner;
Expand All @@ -24,6 +25,8 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo
return <AdbutlerBanner/>;
case 'coinzilla':
return <CoinzillaBanner/>;
case 'custom':
return <CustomAdBanner/>;
case 'slise':
return <SliseBanner/>;
}
Expand Down
48 changes: 48 additions & 0 deletions ui/shared/ad/CustomAdBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Flex, chakra, Tooltip, Image } from '@chakra-ui/react';
import React, { useState, useEffect } from 'react';

import useCustomBanners from 'ui/shared/ad/useCustomBanners';

const CustomAdBanner = ({ className }: { className?: string }) => {
const customBanners = useCustomBanners().data || [];
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
const [ currentBannerIndex, setCurrentBannerIndex ] = useState(0);
jasonzysun marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (customBanners.length === 0) {
return;
}
const timer = setInterval(() => {
setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % customBanners.length);
}, 60000);
tom2drum marked this conversation as resolved.
Show resolved Hide resolved

return () => {
clearInterval(timer);
};
}, [ customBanners.length ]);

if (customBanners.length === 0) {
return null;
}

const currentBanner = customBanners[currentBannerIndex];

return (
<>
<Flex className={ className } h="90px" display={{ base: 'none', lg: 'flex' }}>
<Tooltip label={ currentBanner.text } aria-label={ currentBanner.text }>
<a href={ currentBanner.url } target="_blank" rel="noopener noreferrer">
<Image src={ currentBanner.desktop } alt={ currentBanner.text } height="100%" width="auto" borderRadius="10px"/>
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
</a>
</Tooltip>
</Flex>
<Flex className={ className } h="90px" display={{ base: 'flex', lg: 'none' }}>
<Tooltip label={ currentBanner.text } aria-label={ currentBanner.text }>
<a href={ currentBanner.url } target="_blank" rel="noopener noreferrer">
<Image src={ currentBanner.mobile } alt={ currentBanner.text } height="100%" width="auto" borderRadius="10px"/>
</a>
</Tooltip>
</Flex>
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
</>
);
};

export default chakra(CustomAdBanner);
26 changes: 26 additions & 0 deletions ui/shared/ad/useCustomBanners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import type { AdCustomConfig } from 'types/client/adCustomConfig';

import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/hooks/useFetch';

const feature = config.features.adsBanner;
const configUrl = (feature.isEnabled && feature.provider === 'custom') ? feature.configUrl : '';
export default function useCustomBanners() {
const apiFetch = useApiFetch();
const { isLoading, data } = useQuery<unknown, ResourceError<unknown>, Array<AdCustomConfig>>(
[ 'custom-configs' ],
async() => apiFetch(configUrl),
{
enabled: feature.isEnabled && feature.provider === 'custom',
staleTime: Infinity,
});

return React.useMemo(() => ({
tom2drum marked this conversation as resolved.
Show resolved Hide resolved
data,
isLoading,
}), [ data, isLoading ]);
}