Commit 89be9fd8 authored by Max Alekseenko's avatar Max Alekseenko Committed by GitHub

Merge pull request #1925 from blockscout/defi-dropdown

Add a DeFi dropdown and pass custom query params to the iframe
parents 3230bc92 988f7001
import type { Feature } from './types';
import type { DeFiDropdownItem } from 'types/client/deFiDropdown';
import { getEnvValue, parseEnvJson } from '../utils';
const items = parseEnvJson<Array<DeFiDropdownItem>>(getEnvValue('NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS')) || [];
const title = 'DeFi dropdown';
const config: Feature<{ items: Array<DeFiDropdownItem> }> = items.length > 0 ?
Object.freeze({
title,
isEnabled: true,
items,
}) :
Object.freeze({
title,
isEnabled: false,
});
export default config;
...@@ -8,6 +8,7 @@ export { default as bridgedTokens } from './bridgedTokens'; ...@@ -8,6 +8,7 @@ export { default as bridgedTokens } from './bridgedTokens';
export { default as blockchainInteraction } from './blockchainInteraction'; export { default as blockchainInteraction } from './blockchainInteraction';
export { default as csvExport } from './csvExport'; export { default as csvExport } from './csvExport';
export { default as dataAvailability } from './dataAvailability'; export { default as dataAvailability } from './dataAvailability';
export { default as deFiDropdown } from './deFiDropdown';
export { default as faultProofSystem } from './faultProofSystem'; export { default as faultProofSystem } from './faultProofSystem';
export { default as gasTracker } from './gasTracker'; export { default as gasTracker } from './gasTracker';
export { default as googleAnalytics } from './googleAnalytics'; export { default as googleAnalytics } from './googleAnalytics';
...@@ -26,7 +27,6 @@ export { default as sentry } from './sentry'; ...@@ -26,7 +27,6 @@ export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml'; export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats'; export { default as stats } from './stats';
export { default as suave } from './suave'; export { default as suave } from './suave';
export { default as swapButton } from './swapButton';
export { default as txInterpretation } from './txInterpretation'; export { default as txInterpretation } from './txInterpretation';
export { default as userOps } from './userOps'; export { default as userOps } from './userOps';
export { default as validators } from './validators'; export { default as validators } from './validators';
......
import type { Feature } from './types';
import { getEnvValue } from '../utils';
import marketplace from './marketplace';
const value = getEnvValue('NEXT_PUBLIC_SWAP_BUTTON_URL');
const title = 'Swap button';
function isValidUrl(string: string) {
try {
new URL(string);
return true;
} catch (error) {
return false;
}
}
const config: Feature<{ dappId: string } | { url: string }> = (() => {
if (value) {
if (isValidUrl(value)) {
return Object.freeze({
title,
isEnabled: true,
url: value,
});
} else if (marketplace.isEnabled) {
return Object.freeze({
title,
isEnabled: true,
dappId: value,
});
}
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -56,7 +56,6 @@ NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'728301','width':'728','height':'90 ...@@ -56,7 +56,6 @@ NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'728301','width':'728','height':'90
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'728301','width':'320','height':'100'} NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'728301','width':'320','height':'100'}
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_SWAP_BUTTON_URL=aerodrome
NEXT_PUBLIC_MARKETPLACE_ENABLED=true NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
......
...@@ -54,10 +54,10 @@ NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKj ...@@ -54,10 +54,10 @@ NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKj
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
#meta #meta
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true
...@@ -58,4 +58,3 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com ...@@ -58,4 +58,3 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
...@@ -57,10 +57,9 @@ NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blocksc ...@@ -57,10 +57,9 @@ NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blocksc
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
# rollup # rollup
NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/
\ No newline at end of file
...@@ -53,7 +53,6 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com ...@@ -53,7 +53,6 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_MARKETPLACE_ENABLED=false NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
......
...@@ -12,6 +12,7 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; ...@@ -12,6 +12,7 @@ import type { AdButlerConfig } from '../../../types/client/adButlerConfig';
import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS, SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS } from '../../../types/client/adProviders'; import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS, SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS } from '../../../types/client/adProviders';
import type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders'; import type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders';
import type { ContractCodeIde } from '../../../types/client/contract'; import type { ContractCodeIde } from '../../../types/client/contract';
import type { DeFiDropdownItem } from '../../../types/client/deFiDropdown';
import { GAS_UNITS } from '../../../types/client/gasTracker'; import { GAS_UNITS } from '../../../types/client/gasTracker';
import type { GasUnit } from '../../../types/client/gasTracker'; import type { GasUnit } from '../../../types/client/gasTracker';
import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace';
...@@ -40,6 +41,7 @@ import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx ...@@ -40,6 +41,7 @@ import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx
import { replaceQuotes } from '../../../configs/app/utils'; import { replaceQuotes } from '../../../configs/app/utils';
import * as regexp from '../../../lib/regexp'; import * as regexp from '../../../lib/regexp';
import type { IconName } from '../../../ui/shared/IconSvg';
const protocols = [ 'http', 'https' ]; const protocols = [ 'http', 'https' ];
...@@ -450,6 +452,17 @@ const bridgedTokensSchema = yup ...@@ -450,6 +452,17 @@ const bridgedTokensSchema = yup
}), }),
}); });
const deFiDropdownItemSchema: yup.ObjectSchema<DeFiDropdownItem> = yup
.object({
text: yup.string().required(),
icon: yup.string<IconName>().required(),
dappId: yup.string(),
url: yup.string().test(urlTest),
})
.test('oneOfRequired', 'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: Either dappId or url is required', function(value) {
return Boolean(value.dappId) || Boolean(value.url);
}) as yup.ObjectSchema<DeFiDropdownItem>;
const schema = yup const schema = yup
.object() .object()
.noUnknown(true, (params) => { .noUnknown(true, (params) => {
...@@ -611,7 +624,6 @@ const schema = yup ...@@ -611,7 +624,6 @@ const schema = yup
NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(), NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(),
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(), NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(),
NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(),
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup
.mixed() .mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG, it should have name and url template', (data) => { .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG, it should have name and url template', (data) => {
...@@ -629,6 +641,11 @@ const schema = yup ...@@ -629,6 +641,11 @@ const schema = yup
NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(), NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(),
NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string<GasUnit>().oneOf(GAS_UNITS)), NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string<GasUnit>().oneOf(GAS_UNITS)),
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(), NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(),
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: yup
.array()
.transform(replaceQuotes)
.json()
.of(deFiDropdownItemSchema),
NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean() NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean()
.when('NEXT_PUBLIC_ROLLUP_TYPE', { .when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: 'optimistic', is: 'optimistic',
......
...@@ -73,7 +73,6 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com ...@@ -73,7 +73,6 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com
NEXT_PUBLIC_VISUALIZE_API_BASE_PATH=https://example.com NEXT_PUBLIC_VISUALIZE_API_BASE_PATH=https://example.com
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket'] NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket']
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
...@@ -197,7 +197,6 @@ frontend: ...@@ -197,7 +197,6 @@ frontend:
NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth-goerli.blockscout.com/ NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth-goerli.blockscout.com/
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
envFromSecret: envFromSecret:
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?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/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
......
...@@ -165,7 +165,6 @@ frontend: ...@@ -165,7 +165,6 @@ frontend:
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
#NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" #NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-sepolia.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-sepolia.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -74,7 +74,6 @@ frontend: ...@@ -74,7 +74,6 @@ frontend:
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
......
...@@ -86,7 +86,6 @@ frontend: ...@@ -86,7 +86,6 @@ frontend:
NEXT_PUBLIC_HAS_USER_OPS: true NEXT_PUBLIC_HAS_USER_OPS: true
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
NEXT_PUBLIC_AD_BANNER_PROVIDER: getit NEXT_PUBLIC_AD_BANNER_PROVIDER: getit
NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler
......
...@@ -7,4 +7,5 @@ ...@@ -7,4 +7,5 @@
| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | | NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL |
| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L1_BASE_URL | | NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L1_BASE_URL |
| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED | | NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED |
| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | | NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL |
\ No newline at end of file | NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS |
...@@ -683,13 +683,13 @@ OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED= ...@@ -683,13 +683,13 @@ OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=
&nbsp; &nbsp;
### Swap button ### DeFi dropdown
If the feature is enabled, a Swap button will be displayed at the top of the explorer page, which will take you to the specified application in the marketplace or to an external site. If the feature is enabled, a single button or a dropdown (if more than 1 item is provided) will be displayed at the top of the explorer page, which will take a user to the specified application in the marketplace or to an external site.
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | | NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon: string; dappId?: string, url?: string }]` | An array of dropdown items containing the button text, icon name and dappId in DAppscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` |
&nbsp; &nbsp;
......
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.603 2.425A5.072 5.072 0 0 1 19.2 6.034a5.08 5.08 0 0 1-1.423 3.571l-.012.012-2.202 2.204c.21-.479.325-1.047.325-1.718 0-.474-.057-.896-.165-1.272l.62-.62A3.08 3.08 0 0 0 14.154 3a3.072 3.072 0 0 0-2.156.859l-1.396 1.389a1 1 0 0 1-1.41-1.418l1.4-1.394.01-.01Zm-5.96 5.95-2.207 2.209-.012.012A5.079 5.079 0 0 0 6.03 19.2c1.33.011 2.612-.5 3.569-1.425l.012-.013 1.393-1.394a1 1 0 0 0-1.414-1.414l-1.387 1.388A3.072 3.072 0 0 1 3 14.15c-.007-.805.301-1.58.858-2.16l.62-.62a4.617 4.617 0 0 1-.163-1.268c0-.676.115-1.247.328-1.727Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.308 12.49v-1.173h2.863c.069 0 .135-.031.184-.086a.313.313 0 0 0 .076-.207.313.313 0 0 0-.076-.207.247.247 0 0 0-.184-.086H9.089c-.346 0-.677-.155-.92-.43a1.564 1.564 0 0 1-.382-1.036c0-.388.137-.76.381-1.036.244-.275.575-.429.92-.429h.521V6.628h1.041V7.8h1.302v1.172H9.088a.246.246 0 0 0-.184.086.313.313 0 0 0-.076.207c0 .078.028.153.076.208a.246.246 0 0 0 .184.085h2.083c.345 0 .676.155.92.43.244.274.381.647.381 1.036 0 .388-.137.761-.381 1.036s-.575.43-.92.43h-.52v1.171H9.608V12.49H8.308Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.98 5.36c-.387 0-.645.258-.645.645a4.485 4.485 0 0 1-4.512 4.512h-.451c-.13 0-.194 0-.322-.064-.13 0-.194-.065-.323-.065-.064 0-.129-.064-.193-.064-.451-.129-.903-.387-1.29-.645-.064-.064-.128-.064-.128-.129-.065-.064-.13-.129-.194-.129-.129-.064-.258-.193-.322-.257l-.129-.13 1.16-.193a.669.669 0 0 0 .516-.773.669.669 0 0 0-.773-.516l-2.128.387-.515.129a.435.435 0 0 0-.387.258 1.195 1.195 0 0 0-.13.45l.517 2.708c.064.323.322.516.644.516h.13a.669.669 0 0 0 .515-.774l-.194-1.095c.13.128.323.257.452.386h.064c.065.065.129.13.193.13.13.128.258.193.452.257.129.258.322.322.515.451h.065c.064.065.193.065.322.13.258.064.516.128.71.193.128 0 .257.064.386.064h.774a5.778 5.778 0 0 0 5.801-5.801c.065-.323-.258-.58-.58-.58ZM.73 6.65c.387 0 .645-.258.645-.645a4.485 4.485 0 0 1 4.513-4.512h.45c.13 0 .194 0 .323.064.13 0 .194.065.323.065.064 0 .128.064.193.064.451.129.902.322 1.29.645.064.064.128.064.193.129.064.064.129.064.193.128.064.065.129.194.258.258l.129.13-1.16.193a.669.669 0 0 0-.516.773c.064.322.322.516.644.516h.13l2.707-.516a.669.669 0 0 0 .515-.773L11.045.526A.669.669 0 0 0 10.27.01a.669.669 0 0 0-.516.773L9.95 1.88c-.13-.129-.322-.322-.451-.386h-.065c-.064-.065-.129-.13-.193-.13-.13-.128-.258-.193-.451-.322C8.596.913 8.403.849 8.21.72h-.065C8.079.655 7.95.655 7.822.59a10.574 10.574 0 0 1-.71-.193c-.128 0-.257-.065-.386-.065h-.773A5.778 5.778 0 0 0 .15 6.134c-.064.258.194.516.58.516Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M2.944 10.007c0 .58-.406.967-1.015.967-.609 0-1.015-.386-.913-.773 0-4.835 4.06-8.703 9.136-8.703h1.218c.102 0 .204.025.305.049s.203.048.304.048c.305.097.711.193 1.117.29.09.043.178.066.259.088.103.027.192.051.249.106h.101c.153.096.305.169.457.241.152.073.305.145.457.242.082.052.156.097.225.138.188.114.337.204.486.345.101 0 .203.097.304.194h.102c.203.097.507.387.71.58l-.304-1.644a1.007 1.007 0 0 1 .812-1.16c.61-.097 1.117.29 1.218.773l.812 3.965a1.007 1.007 0 0 1-.812 1.16l-4.264.774h-.203c-.507 0-.913-.29-1.015-.774a1.006 1.006 0 0 1 .812-1.16l1.828-.29-.203-.194a.913.913 0 0 1-.311-.277c-.034-.042-.065-.08-.096-.11-.05-.048-.101-.072-.152-.096-.05-.024-.101-.048-.152-.097a.534.534 0 0 0-.152-.096.534.534 0 0 1-.153-.097c-.609-.484-1.32-.774-2.03-.967a.373.373 0 0 1-.152-.048c-.051-.025-.102-.049-.152-.049a.783.783 0 0 1-.254-.048.783.783 0 0 0-.254-.048c-.203-.097-.305-.097-.508-.097h-.71c-3.96 0-7.107 2.997-7.107 6.768Zm14.112 0c0-.58.406-.966 1.015-.966.508 0 1.015.386.914.87 0 4.835-4.06 8.702-9.137 8.702H8.63c-.102 0-.203-.024-.305-.048-.101-.024-.203-.048-.304-.048-.305-.097-.71-.194-1.117-.29a1.458 1.458 0 0 0-.258-.088c-.103-.028-.192-.052-.25-.106h-.101a4.445 4.445 0 0 0-.198-.116c-.234-.133-.455-.258-.614-.56-.305-.097-.508-.194-.71-.387-.102 0-.204-.097-.305-.194h-.102a4.366 4.366 0 0 0-.355-.29 4.347 4.347 0 0 1-.355-.29l.304 1.644A1.007 1.007 0 0 1 3.148 19h-.203c-.508 0-.914-.29-1.015-.773l-.812-4.062c0-.193.101-.483.203-.676a.686.686 0 0 1 .609-.387l.812-.194 3.35-.58c.61-.096 1.117.29 1.218.774a1.007 1.007 0 0 1-.812 1.16l-1.827.29.203.194c.101.096.304.29.507.386.102 0 .203.097.305.194 0 .06.039.082.092.114.033.02.072.042.11.08.61.386 1.32.773 2.031.966.051 0 .102.024.153.048.05.025.101.049.152.049.101 0 .178.024.254.048a.783.783 0 0 0 .253.048c.203.097.305.097.508.097h.71c3.96 0 7.107-2.997 7.107-6.768Z" fill="currentColor"/>
</svg> </svg>
...@@ -128,7 +128,7 @@ Type extends EventTypes.FILTERS ? { ...@@ -128,7 +128,7 @@ Type extends EventTypes.FILTERS ? {
'Filter name': string; 'Filter name': string;
} : } :
Type extends EventTypes.BUTTON_CLICK ? { Type extends EventTypes.BUTTON_CLICK ? {
'Content': 'Swap button' | 'Multichain'; 'Content': string;
'Source': string; 'Source': string;
} : } :
Type extends EventTypes.PROMO_BANNER ? { Type extends EventTypes.PROMO_BANNER ? {
......
import type { NextRouter } from 'next/router'; import type { NextRouter } from 'next/router';
export default function removeQueryParam(router: NextRouter, param: string) { export default function removeQueryParam(router: NextRouter, param: string) {
const { pathname, query } = router; const { pathname, query, asPath } = router;
const newQuery = { ...query }; const newQuery = { ...query };
delete newQuery[param]; delete newQuery[param];
router.replace({ pathname, query: newQuery }, undefined, { shallow: true });
const hashIndex = asPath.indexOf('#');
const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : '';
router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true });
} }
import type { NextRouter } from 'next/router'; import type { NextRouter } from 'next/router';
export default function updateQueryParam(router: NextRouter, param: string, newValue: string) { export default function updateQueryParam(router: NextRouter, param: string, newValue: string) {
const { pathname, query } = router; const { pathname, query, asPath } = router;
const newQuery = { ...query }; const newQuery = { ...query };
newQuery[param] = newValue; newQuery[param] = newValue;
router.replace({ pathname, query: newQuery }, undefined, { shallow: true });
const hashIndex = asPath.indexOf('#');
const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : '';
router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true });
} }
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
| "networks/logo-placeholder" | "networks/logo-placeholder"
| "nft_shield" | "nft_shield"
| "output_roots" | "output_roots"
| "payment_link"
| "plus" | "plus"
| "privattags" | "privattags"
| "profile" | "profile"
......
import type { IconName } from 'ui/shared/IconSvg';
export type DeFiDropdownItem = {
text: string;
icon: IconName;
} & (
{ dappId: string; url?: never } |
{ url: string; dappId?: never }
);
import type { NextRouter } from 'next/router';
import { getAppUrl } from './utils';
describe('getAppUrl', () => {
let router: NextRouter;
beforeEach(() => {
router = {
pathname: '/current/path',
asPath: '/current/path?someParam=value',
query: {},
replace: jest.fn(),
} as unknown as NextRouter;
});
it('should return undefined if url is undefined', () => {
const result = getAppUrl(undefined, router);
expect(result).toBeUndefined();
});
it('should return the custom url if origins match', () => {
router.query.url = 'https://example.com/custom-path?query=value';
const result = getAppUrl('https://example.com/app', router);
expect(result).toBe('https://example.com/custom-path?query=value');
});
it('should remove the query param and return original url if origins do not match', () => {
router.query.url = 'https://different.com/custom-path?query=value';
const result = getAppUrl('https://example.com/app', router);
expect(result).toBe('https://example.com/app?someParam=value');
expect(router.replace).toHaveBeenCalledWith({ pathname: '/current/path', query: {}, hash: '' }, undefined, { shallow: true });
});
it('should construct the new url with custom params and hash', () => {
router.asPath = '/current/path?path=newPath&newParam=newValue#section';
const result = getAppUrl('https://example.com/app?existingParam=1', router);
expect(result).toBe('https://example.com/newPath?existingParam=1&newParam=newValue#section');
});
it('should handle url without query and hash', () => {
router.asPath = '/current/path';
const result = getAppUrl('https://example.com/app', router);
expect(result).toBe('https://example.com/app');
});
it('should handle error in custom url parsing', () => {
router.query.url = 'invalid-url';
const result = getAppUrl('https://example.com/app', router);
expect(result).toBe('https://example.com/app?someParam=value');
});
it('should handle error in target url parsing', () => {
router.asPath = '/current/path?invalidQuery#section';
const result = getAppUrl('invalid-url', router);
expect(result).toBe('invalid-url');
});
});
import type { NextRouter } from 'next/router';
import getQueryParamString from 'lib/router/getQueryParamString';
import removeQueryParam from 'lib/router/removeQueryParam';
export function getAppUrl(url: string | undefined, router: NextRouter) {
if (!url) {
return;
}
try {
// get the custom url from the query
const customUrl = getQueryParamString(router.query.url);
if (customUrl) {
const customOrigin = new URL(customUrl).origin;
const appOrigin = new URL(url).origin;
if (customOrigin === appOrigin) {
return customUrl;
} else {
removeQueryParam(router, 'url');
}
}
} catch (err) {}
try {
// get hash and params (using asPath to avoid conflicts with dynamic route params)
const [ , queryAndHash ] = router.asPath.split('?');
const [ queryString, hash ] = queryAndHash ? queryAndHash.split('#') : [ '', '' ];
const customHash = hash ? `#${ hash }` : '';
const customParams = new URLSearchParams(queryString);
// remove reserved params
[ 'url', 'action' ].forEach((param) => customParams.delete(param));
if (customParams.toString() || customHash) {
const targetUrl = new URL(url);
const targetParams = new URLSearchParams(targetUrl.search);
let customPath = customParams.get('path');
if (customPath) {
customPath = customPath.startsWith('/') ? customPath : `/${ customPath }`;
targetUrl.pathname = customPath;
customParams.delete('path');
}
customParams.forEach((value, key) => {
targetParams.append(key, value);
});
targetUrl.search = targetParams.toString();
targetUrl.hash = customHash;
return targetUrl.toString();
}
} catch (err) {}
return url;
}
...@@ -16,13 +16,13 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; ...@@ -16,13 +16,13 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import * as metadata from 'lib/metadata'; import * as metadata from 'lib/metadata';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import removeQueryParam from 'lib/router/removeQueryParam';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import MarketplaceAppTopBar from '../marketplace/MarketplaceAppTopBar'; import MarketplaceAppTopBar from '../marketplace/MarketplaceAppTopBar';
import useAutoConnectWallet from '../marketplace/useAutoConnectWallet'; import useAutoConnectWallet from '../marketplace/useAutoConnectWallet';
import useMarketplaceWallet from '../marketplace/useMarketplaceWallet'; import useMarketplaceWallet from '../marketplace/useMarketplaceWallet';
import useSecurityReports from '../marketplace/useSecurityReports'; import useSecurityReports from '../marketplace/useSecurityReports';
import { getAppUrl } from '../marketplace/utils';
const feature = config.features.marketplace; const feature = config.features.marketplace;
...@@ -134,25 +134,7 @@ const MarketplaceApp = () => { ...@@ -134,25 +134,7 @@ const MarketplaceApp = () => {
const { data, isPending } = query; const { data, isPending } = query;
const { setIsAutoConnectDisabled } = useMarketplaceContext(); const { setIsAutoConnectDisabled } = useMarketplaceContext();
const appUrl = useMemo(() => { const appUrl = useMemo(() => getAppUrl(data?.url, router), [ data?.url, router ]);
if (!data?.url) {
return;
}
try {
const customUrl = getQueryParamString(router.query.url);
const customOrigin = new URL(customUrl).origin;
const appOrigin = new URL(data.url).origin;
if (customOrigin === appOrigin) {
return customUrl;
} else {
removeQueryParam(router, 'url');
}
} catch (err) {}
return data.url;
}, [ data?.url, router ]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
......
import { Button, Box, Flex, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import DeFiDropdownItem from './DeFiDropdownItem';
const feature = config.features.deFiDropdown;
const DeFiDropdown = () => {
const router = useRouter();
const source = getPageType(router.pathname);
const { isOpen, onToggle, onClose } = useDisclosure();
const handleClick = React.useCallback((content: string) => {
mixpanel.logEvent(mixpanel.EventTypes.BUTTON_CLICK, { Content: content, Source: source });
}, [ source ]);
if (!feature.isEnabled) {
return null;
}
const buttonStyles = {
variant: 'solid',
size: 'xs',
borderRadius: 'sm',
height: 5,
px: 1.5,
fontWeight: '500',
};
const items = feature.items.map((item) => ({
...item,
onClick: () => handleClick(item.text),
}));
return items.length > 1 ? (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<Button
onClick={ onToggle }
bgColor={ isOpen ? 'blue.400' : undefined }
{ ...buttonStyles }
>
<chakra.span display={{ base: 'none', lg: 'inline' }} mr={ 1 }>
Blockscout
</chakra.span>
DeFi
<IconSvg name="arrows/east-mini" boxSize={ 4 } ml={ 1 } transform="rotate(-90deg)"/>
</Button>
</PopoverTrigger>
<PopoverContent w="auto">
<PopoverBody >
<Flex flexDirection="column" gap={ 1 }>
{ items.map((item, index) => (
<DeFiDropdownItem key={ index } item={ item }/>
)) }
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
) : (
<Button
as="a"
href={ items[0].dappId ? route({ pathname: '/apps/[id]', query: { id: items[0].dappId, action: 'connect' } }) : items[0].url }
target={ items[0].dappId ? '_self' : '_blank' }
onClick={ items[0].onClick }
{ ...buttonStyles }
>
<IconSvg name={ items[0].icon } boxSize={ 3 } mr={{ base: 0, sm: 1 }}/>
<Box display={{ base: 'none', sm: 'inline' }}>
{ items[0].text }
</Box>
</Button>
);
};
export default chakra(React.memo(DeFiDropdown));
import { Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { DeFiDropdownItem as TDeFiDropdownItem } from 'types/client/deFiDropdown';
import { route } from 'nextjs-routes';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
import LinkInternal from 'ui/shared/links/LinkInternal';
type Props = {
item: TDeFiDropdownItem & { onClick: () => void };
}
const DeFiDropdownItem = ({ item }: Props) => {
const styles = {
width: '100%',
height: '34px',
display: 'inline-flex',
alignItems: 'center',
color: useColorModeValue('blackAlpha.800', 'gray.400'),
_hover: {
textDecoration: 'none',
'& *': {
color: 'link_hovered',
},
},
};
const content = (
<>
<IconSvg name={ item.icon } boxSize={ 5 } mr={ 2 }/>
<Text as="span" fontSize="sm">{ item.text }</Text>
</>
);
return item.dappId ? (
<LinkInternal
href={ route({ pathname: '/apps/[id]', query: { id: item.dappId, action: 'connect' } }) }
target="_self"
{ ...styles }
>
{ content }
</LinkInternal>
) : (
<LinkExternal href={ item.url } { ...styles }>
{ content }
</LinkExternal>
);
};
export default React.memo(DeFiDropdownItem);
import { Button, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
const feature = config.features.swapButton;
const SwapButton = () => {
const router = useRouter();
const source = getPageType(router.pathname);
const handleClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.BUTTON_CLICK, { Content: 'Swap button', Source: source });
}, [ source ]);
if (!feature.isEnabled) {
return null;
}
const href = 'url' in feature ?
feature.url :
route({ pathname: '/apps/[id]', query: { id: feature.dappId, action: 'connect' } });
return (
<Button
as="a"
href={ href }
target={ 'url' in feature ? '_blank' : '_self' }
variant="solid"
size="xs"
borderRadius="sm"
height={ 5 }
px={ 1.5 }
onClick={ handleClick }
>
<IconSvg name="swap" boxSize={ 3 } mr={{ base: 0, sm: 1 }}/>
<Box display={{ base: 'none', sm: 'inline' }}>
Swap
</Box>
</Button>
);
};
export default React.memo(SwapButton);
...@@ -7,7 +7,7 @@ import TopBar from './TopBar'; ...@@ -7,7 +7,7 @@ import TopBar from './TopBar';
test.beforeEach(async({ mockEnvs }) => { test.beforeEach(async({ mockEnvs }) => {
await mockEnvs([ await mockEnvs([
[ 'NEXT_PUBLIC_SWAP_BUTTON_URL', 'uniswap' ], [ 'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS', '[{"text":"Swap","icon":"swap","dappId":"uniswap"}]' ],
[ 'NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL', 'DUCK' ], [ 'NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL', 'DUCK' ],
]); ]);
}); });
...@@ -29,3 +29,18 @@ test('with secondary coin price +@mobile', async({ render, mockApiResponse }) => ...@@ -29,3 +29,18 @@ test('with secondary coin price +@mobile', async({ render, mockApiResponse }) =>
const component = await render(<TopBar/>); const component = await render(<TopBar/>);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('with DeFi dropdown +@dark-mode +@mobile', async({ render, page, mockApiResponse, mockEnvs }) => {
await mockEnvs([
[
'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS',
'[{"text":"Swap","icon":"swap","dappId":"uniswap"},{"text":"Payment link","icon":"payment_link","url":"https://example.com"}]',
],
]);
await mockApiResponse('stats', statsMock.base);
const component = await render(<TopBar/>);
await component.getByText(/DeFi/i).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } });
});
...@@ -3,11 +3,11 @@ import React from 'react'; ...@@ -3,11 +3,11 @@ import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import DeFiDropdown from './DeFiDropdown';
import Settings from './settings/Settings'; import Settings from './settings/Settings';
import SwapButton from './SwapButton';
import TopBarStats from './TopBarStats'; import TopBarStats from './TopBarStats';
const feature = config.features.swapButton; const feature = config.features.deFiDropdown;
const TopBar = () => { const TopBar = () => {
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100'); const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
...@@ -24,7 +24,7 @@ const TopBar = () => { ...@@ -24,7 +24,7 @@ const TopBar = () => {
<Flex alignItems="center"> <Flex alignItems="center">
{ feature.isEnabled && ( { feature.isEnabled && (
<> <>
<SwapButton/> <DeFiDropdown/>
<Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/> <Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
</> </>
) } ) }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment