From 2f0d6086177518cebdf8b3efb981b73873a40d50 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Wed, 20 Sep 2023 18:43:51 +0800 Subject: [PATCH 01/13] Add custom ad banner --- configs/app/features/adsBanner.ts | 18 ++++++++-- deploy/scripts/download_assets.sh | 1 + deploy/tools/envs-validator/index.ts | 4 +++ deploy/tools/envs-validator/schema.ts | 9 +++++ docs/ENVS.md | 12 ++++++- nextjs/nextjs-routes.d.ts | 2 +- types/client/adCustomConfig.ts | 6 ++++ types/client/adProviders.ts | 2 +- ui/shared/ad/AdBanner.tsx | 3 ++ ui/shared/ad/CustomAdBanner.tsx | 50 +++++++++++++++++++++++++++ ui/shared/ad/useCustomBanners.tsx | 26 ++++++++++++++ 11 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 types/client/adCustomConfig.ts create mode 100644 ui/shared/ad/CustomAdBanner.tsx create mode 100644 ui/shared/ad/useCustomBanners.tsx diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index 2408fbb173..d735f94499 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -3,7 +3,7 @@ import type { AdButlerConfig } from 'types/client/adButlerConfig'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; import type { AdBannerProviders } from 'types/client/adProviders'; -import { getEnvValue, parseEnvJson } from '../utils'; +import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from '../utils'; const provider: AdBannerProviders = (() => { const envValue = getEnvValue(process.env.NEXT_PUBLIC_AD_BANNER_PROVIDER) as AdBannerProviders; @@ -14,7 +14,7 @@ const provider: AdBannerProviders = (() => { const title = 'Banner ads'; type AdsBannerFeaturePayload = { - provider: Exclude; + provider: Exclude; } | { provider: 'adbutler'; adButler: { @@ -23,6 +23,9 @@ type AdsBannerFeaturePayload = { mobile: AdButlerConfig; }; }; +} | { + provider: 'custom'; + configUrl: string; } const config: Feature = (() => { @@ -43,6 +46,17 @@ const config: Feature = (() => { }, }); } + } else if (provider === 'custom') { + const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', 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, diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index 179062e2d5..826a0856ce 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -21,6 +21,7 @@ ASSETS_ENVS=( "NEXT_PUBLIC_NETWORK_LOGO_DARK" "NEXT_PUBLIC_NETWORK_ICON" "NEXT_PUBLIC_NETWORK_ICON_DARK" + "NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL" ) # Create the assets directory if it doesn't exist diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 16f7dcf1e4..7fc8a161ad 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -42,6 +42,10 @@ async function validateEnvs(appEnvs: Record) { './public/assets/footer_links.json', appEnvs.NEXT_PUBLIC_FOOTER_LINKS, ) || '[]'; + appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL = await getExternalJsonContent( + './public/assets/ad_custom_config.json', + appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL, + ) || '[]'; await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); console.log('👍 All good!'); diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 856ba853a9..e16616175b 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -109,6 +109,15 @@ const adsBannerSchema = yup NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string().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 diff --git a/docs/ENVS.md b/docs/ENVS.md index 55f37c186e..a9fc3acd54 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -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` | +| 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` | +| 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` |   diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index cc38af5ad4..acfff365b8 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -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 }> | StaticRoute<"/auth/auth0"> | StaticRoute<"/auth/profile"> | StaticRoute<"/auth/unverified-email"> diff --git a/types/client/adCustomConfig.ts b/types/client/adCustomConfig.ts new file mode 100644 index 0000000000..8f23307d78 --- /dev/null +++ b/types/client/adCustomConfig.ts @@ -0,0 +1,6 @@ +export type AdCustomConfig = { + text: string; + url: string; + desktop: string; + mobile: string; +} diff --git a/types/client/adProviders.ts b/types/client/adProviders.ts index 0ae135eff9..61bb5da5a6 100644 --- a/types/client/adProviders.ts +++ b/types/client/adProviders.ts @@ -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; export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const; diff --git a/ui/shared/ad/AdBanner.tsx b/ui/shared/ad/AdBanner.tsx index dd948cdb77..a3eaec1f93 100644 --- a/ui/shared/ad/AdBanner.tsx +++ b/ui/shared/ad/AdBanner.tsx @@ -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; @@ -24,6 +25,8 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo return ; case 'coinzilla': return ; + case 'custom': + return ; case 'slise': return ; } diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx new file mode 100644 index 0000000000..5259d137df --- /dev/null +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -0,0 +1,50 @@ +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 || []; + const [ currentBannerIndex, setCurrentBannerIndex ] = useState(0); + // eslint-disable-next-line no-console + console.log(customBanners); + useEffect(() => { + if (customBanners.length === 0) { + return; + } + const timer = setInterval(() => { + setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % customBanners.length); + }, 60000); + + return () => { + clearInterval(timer); + }; + }, [ customBanners.length ]); + + if (customBanners.length === 0) { + return null; + } + + const currentBanner = customBanners[currentBannerIndex]; + + return ( + <> + + + + { + + + + + + + { + + + + + ); +}; + +export default chakra(CustomAdBanner); diff --git a/ui/shared/ad/useCustomBanners.tsx b/ui/shared/ad/useCustomBanners.tsx new file mode 100644 index 0000000000..3e1f63a06e --- /dev/null +++ b/ui/shared/ad/useCustomBanners.tsx @@ -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, Array>( + [ 'custom-configs' ], + async() => apiFetch(configUrl), + { + enabled: feature.isEnabled && feature.provider === 'custom', + staleTime: Infinity, + }); + + return React.useMemo(() => ({ + data, + isLoading, + }), [ data, isLoading ]); +} From 2e9c7f470a5510c63e805cd7169723f9039af2b9 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Thu, 21 Sep 2023 16:07:23 +0800 Subject: [PATCH 02/13] Modify the usage of NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL Let useCustomBanners() always obtains the latest data instead of downloading it at runtime --- configs/app/features/adsBanner.ts | 4 ++-- deploy/scripts/download_assets.sh | 1 - deploy/tools/envs-validator/index.ts | 4 ---- ui/shared/ad/CustomAdBanner.tsx | 2 -- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index d735f94499..ef4391ec52 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -3,7 +3,7 @@ import type { AdButlerConfig } from 'types/client/adButlerConfig'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; import type { AdBannerProviders } from 'types/client/adProviders'; -import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from '../utils'; +import { getEnvValue, parseEnvJson } from '../utils'; const provider: AdBannerProviders = (() => { const envValue = getEnvValue(process.env.NEXT_PUBLIC_AD_BANNER_PROVIDER) as AdBannerProviders; @@ -47,7 +47,7 @@ const config: Feature = (() => { }); } } else if (provider === 'custom') { - const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); + const configUrl = getEnvValue(process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); if (configUrl) { return Object.freeze({ diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index 826a0856ce..179062e2d5 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -21,7 +21,6 @@ ASSETS_ENVS=( "NEXT_PUBLIC_NETWORK_LOGO_DARK" "NEXT_PUBLIC_NETWORK_ICON" "NEXT_PUBLIC_NETWORK_ICON_DARK" - "NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL" ) # Create the assets directory if it doesn't exist diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 7fc8a161ad..16f7dcf1e4 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -42,10 +42,6 @@ async function validateEnvs(appEnvs: Record) { './public/assets/footer_links.json', appEnvs.NEXT_PUBLIC_FOOTER_LINKS, ) || '[]'; - appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL = await getExternalJsonContent( - './public/assets/ad_custom_config.json', - appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL, - ) || '[]'; await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); console.log('👍 All good!'); diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index 5259d137df..268f5f4eaf 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -6,8 +6,6 @@ import useCustomBanners from 'ui/shared/ad/useCustomBanners'; const CustomAdBanner = ({ className }: { className?: string }) => { const customBanners = useCustomBanners().data || []; const [ currentBannerIndex, setCurrentBannerIndex ] = useState(0); - // eslint-disable-next-line no-console - console.log(customBanners); useEffect(() => { if (customBanners.length === 0) { return; From a266d1a73a144bc785f7da0616bea6c1e91bdf3d Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Fri, 22 Sep 2023 14:02:26 +0800 Subject: [PATCH 03/13] 1. Move all ad types in one file. 2. Putting interval in the custom ad config and update the schema. 3. Add the variable of the config URL to the list of downloaded assets and use helper getExternalAssetFilePath in the app config file to get the public path to the file in application code. 4. Fix some variables' name and docs. --- configs/app/features/adsBanner.ts | 7 ++- configs/app/features/adsText.ts | 4 +- deploy/scripts/download_assets.sh | 1 + deploy/tools/envs-validator/index.ts | 4 ++ deploy/tools/envs-validator/schema.ts | 42 +++++++++++++----- docs/ENVS.md | 22 +++++++--- types/client/{adProviders.ts => ad.ts} | 17 +++++++ types/client/adButlerConfig.ts | 5 --- types/client/adCustomConfig.ts | 6 --- ui/shared/ad/CustomAdBanner.tsx | 61 ++++++++++++++++---------- ui/shared/ad/useCustomBanners.tsx | 2 +- 11 files changed, 110 insertions(+), 61 deletions(-) rename types/client/{adProviders.ts => ad.ts} (58%) delete mode 100644 types/client/adButlerConfig.ts delete mode 100644 types/client/adCustomConfig.ts diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index ef4391ec52..ba340d4c21 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -1,7 +1,6 @@ import type { Feature } from './types'; -import type { AdButlerConfig } from 'types/client/adButlerConfig'; -import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; -import type { AdBannerProviders } from 'types/client/adProviders'; +import type { AdButlerConfig, AdBannerProviders } from 'types/client/ad'; +import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/ad'; import { getEnvValue, parseEnvJson } from '../utils'; @@ -47,8 +46,8 @@ const config: Feature = (() => { }); } } else if (provider === 'custom') { + // const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); const configUrl = getEnvValue(process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); - if (configUrl) { return Object.freeze({ title, diff --git a/configs/app/features/adsText.ts b/configs/app/features/adsText.ts index 297378e76b..f5f2ed5b92 100644 --- a/configs/app/features/adsText.ts +++ b/configs/app/features/adsText.ts @@ -1,6 +1,6 @@ import type { Feature } from './types'; -import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; -import type { AdTextProviders } from 'types/client/adProviders'; +import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/ad'; +import type { AdTextProviders } from 'types/client/ad'; import { getEnvValue } from '../utils'; diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index 179062e2d5..826a0856ce 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -21,6 +21,7 @@ ASSETS_ENVS=( "NEXT_PUBLIC_NETWORK_LOGO_DARK" "NEXT_PUBLIC_NETWORK_ICON" "NEXT_PUBLIC_NETWORK_ICON_DARK" + "NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL" ) # Create the assets directory if it doesn't exist diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 16f7dcf1e4..7fc8a161ad 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -42,6 +42,10 @@ async function validateEnvs(appEnvs: Record) { './public/assets/footer_links.json', appEnvs.NEXT_PUBLIC_FOOTER_LINKS, ) || '[]'; + appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL = await getExternalJsonContent( + './public/assets/ad_custom_config.json', + appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL, + ) || '[]'; await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); console.log('👍 All good!'); diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 38ef40d579..2f49a8022e 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -1,8 +1,7 @@ import * as yup from 'yup'; -import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; -import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; -import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; +import type { AdButlerConfig, AdTextProviders, AdBannerProviders } from '../../../types/client/ad'; +import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/ad'; import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; import type { NavItemExternal } from '../../../types/client/navigation-items'; import type { WalletType } from '../../../types/client/wallets'; @@ -103,21 +102,40 @@ const adButlerConfigSchema = yup .required(), }); +const adCustomConfigSchema = yup + .object() + .shape({ + banners: yup + .array() + .of( + yup.object().shape({ + text: yup.string(), + url: yup.string().url().required(), + desktop: yup.string().url().required(), + mobile: yup.string().url().required(), + }), + ) + .required(), + interval: yup.number(), + }) + .when('NEXT_PUBLIC_AD_BANNER_PROVIDER', { + is: (value: AdBannerProviders) => value === 'custom', + then: (schema) => schema, + otherwise: (schema) => + schema.test( + 'custom-validation', + 'NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL cannot not be used without NEXT_PUBLIC_AD_BANNER_PROVIDER being set to "custom"', + () => false, + ), + }); + const adsBannerSchema = yup .object() .shape({ NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string().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'), - }), + NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL: adCustomConfigSchema, }); const sentrySchema = yup diff --git a/docs/ENVS.md b/docs/ENVS.md index a9fc3acd54..641479c6ce 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -228,19 +228,27 @@ 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` \| `custom` \| `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` | +| NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL | `string` | URL of configuration file (.json format only) which contains settings and list of custom banners that will be shown in the home page and token detail page. See below list of available properties for particular banner | - | - | `https://example.com/ad_custom_config.json` | + +#### Configuration properties +| Variable | Type | Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| banners | `array` | List of banners with their properties. Refer to the "Custom banners configuration properties" section below. | Required | - | See below | +| interval | `number` | Duration (in milliseconds) for how long each banner will be displayed. | - | 60000 | `6000` | + +  #### Custom banners configuration properties -| Variable | Type | Description | Compulsoriness | Default value | Example value | +| Variable | Type | Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| text | `string` | Tooltip text displayed when the mouse is moved over the banner. | Required | - | `Anyblock` | +| text | `string` | Tooltip text displayed when the mouse is moved over the banner. | - | - | - | | 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` | -| 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` | +| desktop | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is greater than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` | +| mobile | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is less than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` |   @@ -325,7 +333,7 @@ This feature is **always enabled**, but you can configure its behavior by passin #### Marketplace app configuration properties -| Property | Type | Description | Compulsoriness | Example value +| Property | Type | Description | Compulsoriness | Example value | | --- | --- | --- | --- | --- | | id | `string` | Used as slug for the app. Must be unique in the app list. | Required | `'app'` | | external | `boolean` | `true` means that the application opens in a new window, but not in an iframe. | - | `true` | diff --git a/types/client/adProviders.ts b/types/client/ad.ts similarity index 58% rename from types/client/adProviders.ts rename to types/client/ad.ts index 61bb5da5a6..0eb10f1f4d 100644 --- a/types/client/adProviders.ts +++ b/types/client/ad.ts @@ -5,3 +5,20 @@ export type AdBannerProviders = ArrayElement; + +export type AdButlerConfig = { + id: string; + width: string; + height: string; +} +export type AdCustomBannerConfig = { + text: string; + url: string; + desktop: string; + mobile: string; +} + +export type AdCustomConfig = { + banners: Array; + interval: number; +} diff --git a/types/client/adButlerConfig.ts b/types/client/adButlerConfig.ts deleted file mode 100644 index 39dcb385b7..0000000000 --- a/types/client/adButlerConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type AdButlerConfig = { - id: string; - width: string; - height: string; -} diff --git a/types/client/adCustomConfig.ts b/types/client/adCustomConfig.ts deleted file mode 100644 index 8f23307d78..0000000000 --- a/types/client/adCustomConfig.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type AdCustomConfig = { - text: string; - url: string; - desktop: string; - mobile: string; -} diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index 268f5f4eaf..d11a637bd7 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -1,47 +1,60 @@ import { Flex, chakra, Tooltip, Image } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; import React, { useState, useEffect } from 'react'; -import useCustomBanners from 'ui/shared/ad/useCustomBanners'; +import type { AdCustomConfig } from 'types/client/ad'; + +import config from 'configs/app'; +import type { ResourceError } from 'lib/api/resources'; +import useFetch from 'lib/hooks/useFetch'; + +import useIsMobile from '../../../lib/hooks/useIsMobile'; const CustomAdBanner = ({ className }: { className?: string }) => { - const customBanners = useCustomBanners().data || []; + const isMobile = useIsMobile(); + + const feature = config.features.adsBanner; + const configUrl = (feature.isEnabled && feature.provider === 'custom') ? feature.configUrl : ''; + + const apiFetch = useFetch(); + const { data: adConfig } = useQuery, AdCustomConfig>( + [ 'custom-configs' ], + async() => apiFetch(configUrl), + { + enabled: feature.isEnabled && feature.provider === 'custom', + staleTime: Infinity, + }); + const interval = adConfig?.interval || 60000; + const banners = adConfig?.banners || []; + const [ currentBannerIndex, setCurrentBannerIndex ] = useState(0); useEffect(() => { - if (customBanners.length === 0) { + if (banners.length === 0) { return; } const timer = setInterval(() => { - setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % customBanners.length); - }, 60000); + setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % banners.length); + }, interval); return () => { clearInterval(timer); }; - }, [ customBanners.length ]); + }, [ interval, banners.length ]); - if (customBanners.length === 0) { + if (banners.length === 0) { return null; } - const currentBanner = customBanners[currentBannerIndex]; + const currentBanner = banners[currentBannerIndex]; return ( - <> - - - - { - - - - - - - { - - - - + + + + { + + + ); }; diff --git a/ui/shared/ad/useCustomBanners.tsx b/ui/shared/ad/useCustomBanners.tsx index 3e1f63a06e..a7ca0e8fb2 100644 --- a/ui/shared/ad/useCustomBanners.tsx +++ b/ui/shared/ad/useCustomBanners.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import React from 'react'; -import type { AdCustomConfig } from 'types/client/adCustomConfig'; +import type { AdCustomConfig } from 'types/client/ad'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; From e0fac6c1d46e623b95c454d2889dbdf2f8ed2bdb Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Fri, 22 Sep 2023 14:24:44 +0800 Subject: [PATCH 04/13] fix some error --- configs/app/features/adsBanner.ts | 5 ++--- ui/shared/ad/useCustomBanners.tsx | 26 -------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 ui/shared/ad/useCustomBanners.tsx diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index ba340d4c21..44753ef191 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -2,7 +2,7 @@ import type { Feature } from './types'; import type { AdButlerConfig, AdBannerProviders } from 'types/client/ad'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/ad'; -import { getEnvValue, parseEnvJson } from '../utils'; +import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from '../utils'; const provider: AdBannerProviders = (() => { const envValue = getEnvValue(process.env.NEXT_PUBLIC_AD_BANNER_PROVIDER) as AdBannerProviders; @@ -46,8 +46,7 @@ const config: Feature = (() => { }); } } else if (provider === 'custom') { - // const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); - const configUrl = getEnvValue(process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); + const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); if (configUrl) { return Object.freeze({ title, diff --git a/ui/shared/ad/useCustomBanners.tsx b/ui/shared/ad/useCustomBanners.tsx deleted file mode 100644 index a7ca0e8fb2..0000000000 --- a/ui/shared/ad/useCustomBanners.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import React from 'react'; - -import type { AdCustomConfig } from 'types/client/ad'; - -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, Array>( - [ 'custom-configs' ], - async() => apiFetch(configUrl), - { - enabled: feature.isEnabled && feature.provider === 'custom', - staleTime: Infinity, - }); - - return React.useMemo(() => ({ - data, - isLoading, - }), [ data, isLoading ]); -} From 20b7b8fffd79d88be4c9e70fbd6b5c42d994df01 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Fri, 22 Sep 2023 14:32:26 +0800 Subject: [PATCH 05/13] fix some error --- deploy/tools/envs-validator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 7fc8a161ad..d28efc69a2 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -45,7 +45,7 @@ async function validateEnvs(appEnvs: Record) { appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL = await getExternalJsonContent( './public/assets/ad_custom_config.json', appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL, - ) || '[]'; + ) || '{}'; await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); console.log('👍 All good!'); From 3ee3677d80ba43a5ed0ff04b2b44a1b6d056a7a0 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Fri, 22 Sep 2023 14:33:01 +0800 Subject: [PATCH 06/13] fix some error --- deploy/tools/envs-validator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index d28efc69a2..5fe2f9ccf4 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -45,7 +45,7 @@ async function validateEnvs(appEnvs: Record) { appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL = await getExternalJsonContent( './public/assets/ad_custom_config.json', appEnvs.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL, - ) || '{}'; + ) || '{ "banners": []}'; await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); console.log('👍 All good!'); From 286911863fbecc610f3bd30dab242d97c5d8ae5c Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Sun, 24 Sep 2023 17:12:51 +0800 Subject: [PATCH 07/13] 1. Add randomStart and randomNextAd into config to provide a optional play order setting; 2. Update schema and doc; 3. Modify some variable names to improve code readability --- deploy/tools/envs-validator/schema.ts | 25 +++++++++++++--------- docs/ENVS.md | 8 ++++--- types/client/ad.ts | 12 ++++++----- ui/shared/ad/CustomAdBanner.tsx | 30 +++++++++++++++++++-------- 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 2f49a8022e..e94771e4a8 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -1,6 +1,6 @@ import * as yup from 'yup'; -import type { AdButlerConfig, AdTextProviders, AdBannerProviders } from '../../../types/client/ad'; +import type { AdButlerConfig, AdTextProviders, AdBannerProviders, AdCustomBannerConfig } from '../../../types/client/ad'; import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/ad'; import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; import type { NavItemExternal } from '../../../types/client/navigation-items'; @@ -102,21 +102,26 @@ const adButlerConfigSchema = yup .required(), }); +const adCustomBannerConfigSchema: yup.ObjectSchema = yup + .object() + .shape({ + text: yup.string(), + url: yup.string().url(), + desktopImageUrl: yup.string().url().required(), + mobileImageUrl: yup.string().url().required(), + }); + const adCustomConfigSchema = yup .object() .shape({ banners: yup .array() - .of( - yup.object().shape({ - text: yup.string(), - url: yup.string().url().required(), - desktop: yup.string().url().required(), - mobile: yup.string().url().required(), - }), - ) + .of(adCustomBannerConfigSchema) + .min(1, 'Banners array cannot be empty') .required(), - interval: yup.number(), + interval: yup.number().positive(), + randomStart: yup.boolean(), + randomNextAd: yup.boolean(), }) .when('NEXT_PUBLIC_AD_BANNER_PROVIDER', { is: (value: AdBannerProviders) => value === 'custom', diff --git a/docs/ENVS.md b/docs/ENVS.md index 641479c6ce..317b1a0bea 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -238,6 +238,8 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | --- | --- | --- | --- | --- | --- | | banners | `array` | List of banners with their properties. Refer to the "Custom banners configuration properties" section below. | Required | - | See below | | interval | `number` | Duration (in milliseconds) for how long each banner will be displayed. | - | 60000 | `6000` | +| randomStart | `boolean` | Set to true to randomly start playing advertisements from any position in the array | - | `false` | `true` | +| randomNextAd | `boolean` | Set to ture to randomly play advertisements | - | `false` | `true` |   @@ -246,9 +248,9 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | Variable | Type | Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | | text | `string` | Tooltip text displayed when the mouse is moved over the banner. | - | - | - | -| 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 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` | -| mobile | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is less than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` | +| url | `string` | Link that opens when clicking on the banner. | - | - | `https://example.com` | +| desktopImageUrl | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is greater than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` | +| mobileImageUrl | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is less than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` |   diff --git a/types/client/ad.ts b/types/client/ad.ts index 0eb10f1f4d..136f44dffb 100644 --- a/types/client/ad.ts +++ b/types/client/ad.ts @@ -12,13 +12,15 @@ export type AdButlerConfig = { height: string; } export type AdCustomBannerConfig = { - text: string; - url: string; - desktop: string; - mobile: string; + text?: string; + url?: string; + desktopImageUrl: string; + mobileImageUrl: string; } export type AdCustomConfig = { banners: Array; - interval: number; + interval?: number; + randomStart?: boolean; + randomNextAd?: boolean; } diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index d11a637bd7..5b596ace96 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -6,9 +6,9 @@ import type { AdCustomConfig } from 'types/client/ad'; import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; +import { MINUTE } from 'lib/consts'; import useFetch from 'lib/hooks/useFetch'; - -import useIsMobile from '../../../lib/hooks/useIsMobile'; +import useIsMobile from 'lib/hooks/useIsMobile'; const CustomAdBanner = ({ className }: { className?: string }) => { const isMobile = useIsMobile(); @@ -18,31 +18,42 @@ const CustomAdBanner = ({ className }: { className?: string }) => { const apiFetch = useFetch(); const { data: adConfig } = useQuery, AdCustomConfig>( - [ 'custom-configs' ], + [ 'ad-banner-custom-config' ], async() => apiFetch(configUrl), { enabled: feature.isEnabled && feature.provider === 'custom', staleTime: Infinity, }); - const interval = adConfig?.interval || 60000; + const interval = adConfig?.interval || MINUTE; const banners = adConfig?.banners || []; + const randomStart = adConfig?.randomStart || false; + const randomNextAd = adConfig?.randomNextAd || false; - const [ currentBannerIndex, setCurrentBannerIndex ] = useState(0); + const [ currentBannerIndex, setCurrentBannerIndex ] = useState( + randomStart ? Math.floor(Math.random() * banners.length) : 0, + ); useEffect(() => { if (banners.length === 0) { return; } const timer = setInterval(() => { - setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % banners.length); + if (randomNextAd) { + setCurrentBannerIndex(Math.floor(Math.random() * banners.length)); + } else { + setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % banners.length); + } }, interval); return () => { clearInterval(timer); }; - }, [ interval, banners.length ]); + }, [ interval, banners.length, randomNextAd ]); if (banners.length === 0) { - return null; + return ( + + + ); } const currentBanner = banners[currentBannerIndex]; @@ -51,7 +62,8 @@ const CustomAdBanner = ({ className }: { className?: string }) => { - { + { From 5b41dc1b40173326cd787faf916c3a733bba9060 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Wed, 27 Sep 2023 11:00:58 +0800 Subject: [PATCH 08/13] 1. Manage the loading and error states of the component. 2. Specify a skeleton as a fallback prop for the Image --- ui/shared/ad/CustomAdBanner.tsx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index 5b596ace96..8f7b9832a3 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -1,4 +1,4 @@ -import { Flex, chakra, Tooltip, Image } from '@chakra-ui/react'; +import { Flex, chakra, Tooltip, Image, Skeleton } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; import React, { useState, useEffect } from 'react'; @@ -17,13 +17,14 @@ const CustomAdBanner = ({ className }: { className?: string }) => { const configUrl = (feature.isEnabled && feature.provider === 'custom') ? feature.configUrl : ''; const apiFetch = useFetch(); - const { data: adConfig } = useQuery, AdCustomConfig>( + const { data: adConfig, isLoading, isError } = useQuery, AdCustomConfig>( [ 'ad-banner-custom-config' ], async() => apiFetch(configUrl), { enabled: feature.isEnabled && feature.provider === 'custom', staleTime: Infinity, }); + const interval = adConfig?.interval || MINUTE; const banners = adConfig?.banners || []; const randomStart = adConfig?.randomStart || false; @@ -49,6 +50,17 @@ const CustomAdBanner = ({ className }: { className?: string }) => { }; }, [ interval, banners.length, randomNextAd ]); + if (isLoading) { + return ; + } + + if (isError || !adConfig) { + return ( + + + ); + } + if (banners.length === 0) { return ( @@ -63,7 +75,9 @@ const CustomAdBanner = ({ className }: { className?: string }) => { { + alt={ currentBanner.text } height="100%" width="auto" borderRadius="md" + fallback={ } + /> From 88b86d166830ca39b4584d982145cdaf070820a0 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Sun, 8 Oct 2023 21:03:54 +0800 Subject: [PATCH 09/13] fix width of Skeleton --- ui/shared/ad/CustomAdBanner.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index 8f7b9832a3..c19e6f631f 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -49,9 +49,8 @@ const CustomAdBanner = ({ className }: { className?: string }) => { clearInterval(timer); }; }, [ interval, banners.length, randomNextAd ]); - if (isLoading) { - return ; + return ; } if (isError || !adConfig) { @@ -76,7 +75,7 @@ const CustomAdBanner = ({ className }: { className?: string }) => { { } + fallback={ } /> From 449699dffec04a5e10e75b6110cfb21a2904e664 Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Sun, 8 Oct 2023 21:18:01 +0800 Subject: [PATCH 10/13] 1.fix 'ture' to 'true' 2.use 'test(urlTest)' instead of 'url()' --- deploy/tools/envs-validator/schema.ts | 6 +++--- docs/ENVS.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 80e361c0d5..4afc2686af 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -135,9 +135,9 @@ const adCustomBannerConfigSchema: yup.ObjectSchema = yup .object() .shape({ text: yup.string(), - url: yup.string().url(), - desktopImageUrl: yup.string().url().required(), - mobileImageUrl: yup.string().url().required(), + url: yup.string().test(urlTest), + desktopImageUrl: yup.string().test(urlTest).required(), + mobileImageUrl: yup.string().test(urlTest).required(), }); const adCustomConfigSchema = yup diff --git a/docs/ENVS.md b/docs/ENVS.md index f66088f499..27c4c6d141 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -261,7 +261,7 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | banners | `array` | List of banners with their properties. Refer to the "Custom banners configuration properties" section below. | Required | - | See below | | interval | `number` | Duration (in milliseconds) for how long each banner will be displayed. | - | 60000 | `6000` | | randomStart | `boolean` | Set to true to randomly start playing advertisements from any position in the array | - | `false` | `true` | -| randomNextAd | `boolean` | Set to ture to randomly play advertisements | - | `false` | `true` | +| randomNextAd | `boolean` | Set to true to randomly play advertisements | - | `false` | `true` |   From 815b67d4536fda370e52ef0dae613f02e855dcbc Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Sun, 8 Oct 2023 21:42:47 +0800 Subject: [PATCH 11/13] Randomly playing by shuffling the array order --- ui/shared/ad/CustomAdBanner.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/shared/ad/CustomAdBanner.tsx b/ui/shared/ad/CustomAdBanner.tsx index c19e6f631f..affb3c97e7 100644 --- a/ui/shared/ad/CustomAdBanner.tsx +++ b/ui/shared/ad/CustomAdBanner.tsx @@ -1,5 +1,6 @@ import { Flex, chakra, Tooltip, Image, Skeleton } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; +import shuffle from 'lodash/shuffle'; import React, { useState, useEffect } from 'react'; import type { AdCustomConfig } from 'types/client/ad'; @@ -26,9 +27,10 @@ const CustomAdBanner = ({ className }: { className?: string }) => { }); const interval = adConfig?.interval || MINUTE; - const banners = adConfig?.banners || []; + const baseBanners = adConfig?.banners || []; const randomStart = adConfig?.randomStart || false; const randomNextAd = adConfig?.randomNextAd || false; + const banners = randomNextAd ? shuffle(baseBanners) : baseBanners; const [ currentBannerIndex, setCurrentBannerIndex ] = useState( randomStart ? Math.floor(Math.random() * banners.length) : 0, @@ -38,11 +40,7 @@ const CustomAdBanner = ({ className }: { className?: string }) => { return; } const timer = setInterval(() => { - if (randomNextAd) { - setCurrentBannerIndex(Math.floor(Math.random() * banners.length)); - } else { - setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % banners.length); - } + setCurrentBannerIndex((prevIndex) => (prevIndex + 1) % banners.length); }, interval); return () => { From 4131076609d84a243e3cdec185d46d8cb38bdacc Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Sun, 8 Oct 2023 23:32:52 +0800 Subject: [PATCH 12/13] remove 'process.env.' --- configs/app/features/adsBanner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index dfc2aa5f2d..2587107682 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -46,7 +46,7 @@ const config: Feature = (() => { }); } } else if (provider === 'custom') { - const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL', process.env.NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL); + const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_AD_CUSTOM_CONFIG_URL'); if (configUrl) { return Object.freeze({ title, From a0c59facdff2a131a33914638c557e68e56bee2b Mon Sep 17 00:00:00 2001 From: jasonzysun Date: Mon, 9 Oct 2023 12:26:40 +0800 Subject: [PATCH 13/13] fix doc --- docs/ENVS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ENVS.md b/docs/ENVS.md index 27c4c6d141..0b6d3e7ef9 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -271,8 +271,8 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | --- | --- | --- | --- | --- | --- | | text | `string` | Tooltip text displayed when the mouse is moved over the banner. | - | - | - | | url | `string` | Link that opens when clicking on the banner. | - | - | `https://example.com` | -| desktopImageUrl | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is greater than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` | -| mobileImageUrl | `string` | Banner image (both .png, .jpg, and .gif are acceptable) used when the screen width is less than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` | +| desktopImageUrl | `string` | Banner image (.png, .jpg, and .gif are all acceptable) used when the screen width is greater than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/desktop/example.gif` | +| mobileImageUrl | `string` | Banner image (.png, .jpg, and .gif are all acceptable) used when the screen width is less than 1000px. | Required | - | `https://example.com/configs/ad-custom-banners/mobile/example.gif` |