diff --git a/configs/app/ui/views/index.ts b/configs/app/ui/views/index.ts index 4f1d77073b..ef4b874dd0 100644 --- a/configs/app/ui/views/index.ts +++ b/configs/app/ui/views/index.ts @@ -1,4 +1,5 @@ export { default as address } from './address'; export { default as block } from './block'; export { default as nft } from './nft'; +export { default as token } from './token'; export { default as tx } from './tx'; diff --git a/configs/app/ui/views/token.ts b/configs/app/ui/views/token.ts new file mode 100644 index 0000000000..05052ce913 --- /dev/null +++ b/configs/app/ui/views/token.ts @@ -0,0 +1,7 @@ +import { getEnvValue } from 'configs/app/utils'; + +const config = Object.freeze({ + hideScamTokensEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED') === 'true', +}); + +export default config; diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 0b0b075299..f528eaad0d 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -10,6 +10,8 @@ NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_INSTANCE=rubber_duck NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true + # Instance ENVs NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index bed33149c7..f1a82269ca 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -755,6 +755,7 @@ const schema = yup .transform(replaceQuotes) .json() .of(nftMarketplaceSchema), + NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED: yup.boolean(), NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED: yup.boolean(), // e. misc diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index 6bd0759fa1..ecc6d34aca 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -78,6 +78,7 @@ frontend: NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" PROMETHEUS_METRICS_ENABLED: true NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true + NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED: true envFromSecret: NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID diff --git a/docs/ENVS.md b/docs/ENVS.md index a2272bbf2a..f56e9fb81a 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -280,6 +280,13 @@ Settings for meta tags, OG tags and SEO   +#### Token views +| Variable | Type | Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED | `boolean` | Show the "Hide scam tokens" toggle in the site settings dropdown. This option controls the visibility of tokens with a poor reputation in the search results. | - | - | `'["value","tx_fee"]'` | v1.38.0+ | + +  + #### NFT views | Variable | Type | Description | Compulsoriness | Default value | Example value | Version | diff --git a/lib/cookies.ts b/lib/cookies.ts index fc4f8cc144..518b50fbf7 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -17,6 +17,7 @@ export enum NAMES { MIXPANEL_DEBUG = '_mixpanel_debug', ADDRESS_NFT_DISPLAY_TYPE = 'address_nft_display_type', UUID = 'uuid', + SHOW_SCAM_TOKENS = 'show_scam_tokens', } export function get(name?: NAMES | undefined | null, serverCookie?: string) { diff --git a/middleware.ts b/middleware.ts index cd820f475e..8443292593 100644 --- a/middleware.ts +++ b/middleware.ts @@ -23,6 +23,7 @@ export function middleware(req: NextRequest) { middlewares.colorTheme(req, res); middlewares.addressFormat(req, res); + middlewares.scamTokens(req, res); const end = Date.now(); diff --git a/nextjs/middlewares/index.ts b/nextjs/middlewares/index.ts index 4e4fdbef4e..763e81a7fe 100644 --- a/nextjs/middlewares/index.ts +++ b/nextjs/middlewares/index.ts @@ -1,3 +1,4 @@ export { account } from './account'; export { default as colorTheme } from './colorTheme'; export { default as addressFormat } from './addressFormat'; +export { default as scamTokens } from './scamTokens'; diff --git a/nextjs/middlewares/scamTokens.ts b/nextjs/middlewares/scamTokens.ts new file mode 100644 index 0000000000..d28f2d6cd9 --- /dev/null +++ b/nextjs/middlewares/scamTokens.ts @@ -0,0 +1,14 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import config from 'configs/app'; +import * as cookiesLib from 'lib/cookies'; + +export default function scamTokensMiddleware(req: NextRequest, res: NextResponse) { + if (config.UI.views.token.hideScamTokensEnabled) { + const showScamTokensCookie = req.cookies.get(cookiesLib.NAMES.SHOW_SCAM_TOKENS); + + if (!showScamTokensCookie) { + res.cookies.set(cookiesLib.NAMES.SHOW_SCAM_TOKENS, 'false', { path: '/' }); + } + } +} diff --git a/nextjs/utils/fetchProxy.ts b/nextjs/utils/fetchProxy.ts index 0fd5b19b57..ee1d4be138 100644 --- a/nextjs/utils/fetchProxy.ts +++ b/nextjs/utils/fetchProxy.ts @@ -7,20 +7,20 @@ import nodeFetch from 'node-fetch'; import { httpLogger } from 'nextjs/utils/logger'; -import * as cookies from 'lib/cookies'; - export default function fetchFactory( _req: NextApiRequest | (IncomingMessage & { cookies: NextApiRequestCookies }), ) { // first arg can be only a string // FIXME migrate to RequestInfo later if needed return function fetch(url: string, init?: RequestInit): Promise { - const apiToken = _req.cookies[cookies.NAMES.API_TOKEN]; + const cookie = Object.entries(_req.cookies) + .map(([ key, value ]) => `${ key }=${ value }`) + .join('; '); const headers = { accept: _req.headers['accept'] || 'application/json', 'content-type': _req.headers['content-type'] || 'application/json', - cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '', + cookie, ...pick(_req.headers, [ 'x-csrf-token', 'Authorization', // the old value, just in case diff --git a/ui/snippets/topBar/TopBar.pw.tsx b/ui/snippets/topBar/TopBar.pw.tsx index ac33468115..068d1856c9 100644 --- a/ui/snippets/topBar/TopBar.pw.tsx +++ b/ui/snippets/topBar/TopBar.pw.tsx @@ -10,6 +10,7 @@ test.beforeEach(async({ mockEnvs }) => { await mockEnvs([ [ 'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS', '[{"text":"Swap","icon":"swap","dappId":"uniswap"}]' ], [ 'NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL', 'DUCK' ], + [ 'NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED', 'true' ], ]); }); diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png index 8a4112da0c..8256853688 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_dark-color-mode_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png index 9a90077cf9..3663a3c635 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png index 2989109bd6..11f8d011cd 100644 Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-2.png differ diff --git a/ui/snippets/topBar/settings/Settings.tsx b/ui/snippets/topBar/settings/Settings.tsx index 065a992230..300e2e1824 100644 --- a/ui/snippets/topBar/settings/Settings.tsx +++ b/ui/snippets/topBar/settings/Settings.tsx @@ -7,6 +7,7 @@ import IconSvg from 'ui/shared/IconSvg'; import SettingsAddressFormat from './SettingsAddressFormat'; import SettingsColorTheme from './SettingsColorTheme'; import SettingsIdentIcon from './SettingsIdentIcon'; +import SettingsScamTokens from './SettingsScamTokens'; const Settings = () => { const { isOpen, onToggle, onClose } = useDisclosure(); @@ -27,9 +28,10 @@ const Settings = () => { - + + diff --git a/ui/snippets/topBar/settings/SettingsScamTokens.tsx b/ui/snippets/topBar/settings/SettingsScamTokens.tsx new file mode 100644 index 0000000000..3e647c1c80 --- /dev/null +++ b/ui/snippets/topBar/settings/SettingsScamTokens.tsx @@ -0,0 +1,41 @@ +import { FormLabel, FormControl, Switch, Box } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import { useAppContext } from 'lib/contexts/app'; +import * as cookies from 'lib/cookies'; + +const SettingsScamTokens = () => { + const { cookies: appCookies } = useAppContext(); + + const initialValue = cookies.get(cookies.NAMES.SHOW_SCAM_TOKENS, appCookies); + + const [ isChecked, setIsChecked ] = React.useState(initialValue !== 'true'); + + const handleChange = React.useCallback(() => { + setIsChecked(prev => { + const nextValue = !prev; + cookies.set(cookies.NAMES.SHOW_SCAM_TOKENS, nextValue ? 'false' : 'true'); + return nextValue; + }); + window.location.reload(); + }, []); + + if (!config.UI.views.token.hideScamTokensEnabled) { + return null; + } + + return ( + <> + + + + Hide scam tokens + + + + + ); +}; + +export default React.memo(SettingsScamTokens);