Merge pull request #1925 from blockscout/defi-dropdown
Add a DeFi dropdown and pass custom query params to the iframe
@@ -0,0 +1,21 @@
|
||||
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';
|
||||
export { default as blockchainInteraction } from './blockchainInteraction';
|
||||
export { default as csvExport } from './csvExport';
|
||||
export { default as dataAvailability } from './dataAvailability';
|
||||
export { default as deFiDropdown } from './deFiDropdown';
|
||||
export { default as faultProofSystem } from './faultProofSystem';
|
||||
export { default as gasTracker } from './gasTracker';
|
||||
export { default as googleAnalytics } from './googleAnalytics';
|
||||
@@ -26,7 +27,6 @@ export { default as sentry } from './sentry';
|
||||
export { default as sol2uml } from './sol2uml';
|
||||
export { default as stats } from './stats';
|
||||
export { default as suave } from './suave';
|
||||
export { default as swapButton } from './swapButton';
|
||||
export { default as txInterpretation } from './txInterpretation';
|
||||
export { default as userOps } from './userOps';
|
||||
export { default as validators } from './validators';
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
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_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL=aerodrome
|
||||
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_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
|
||||
|
||||
@@ -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_FEATURED_APP=gearbox-protocol
|
||||
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'}
|
||||
|
||||
#meta
|
||||
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_SEO_ENHANCED_DATA_ENABLED=true
|
||||
|
||||
|
||||
@@ -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_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_SWAP_BUTTON_URL=uniswap
|
||||
|
||||
@@ -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_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
|
||||
NEXT_PUBLIC_HAS_USER_OPS=true
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
# rollup
|
||||
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
|
||||
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/
|
||||
|
||||
@@ -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_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
|
||||
NEXT_PUBLIC_HAS_USER_OPS=true
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
|
||||
|
||||
@@ -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 type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders';
|
||||
import type { ContractCodeIde } from '../../../types/client/contract';
|
||||
import type { DeFiDropdownItem } from '../../../types/client/deFiDropdown';
|
||||
import { GAS_UNITS } from '../../../types/client/gasTracker';
|
||||
import type { GasUnit } from '../../../types/client/gasTracker';
|
||||
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
|
||||
|
||||
import { replaceQuotes } from '../../../configs/app/utils';
|
||||
import * as regexp from '../../../lib/regexp';
|
||||
import type { IconName } from '../../../ui/shared/IconSvg';
|
||||
|
||||
const protocols = [ 'http', 'https' ];
|
||||
|
||||
@@ -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
|
||||
.object()
|
||||
.noUnknown(true, (params) => {
|
||||
@@ -611,7 +624,6 @@ const schema = yup
|
||||
NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(),
|
||||
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
|
||||
NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(),
|
||||
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup
|
||||
.mixed()
|
||||
.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
|
||||
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_DATA_AVAILABILITY_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: yup
|
||||
.array()
|
||||
.transform(replaceQuotes)
|
||||
.json()
|
||||
.of(deFiDropdownItemSchema),
|
||||
NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean()
|
||||
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
|
||||
is: 'optimistic',
|
||||
|
||||
@@ -73,7 +73,6 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com
|
||||
NEXT_PUBLIC_VISUALIZE_API_BASE_PATH=https://example.com
|
||||
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
|
||||
NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket']
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
|
||||
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'}
|
||||
|
||||
|
||||
@@ -197,7 +197,6 @@ frontend:
|
||||
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_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
|
||||
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_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:
|
||||
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_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:
|
||||
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
|
||||
|
||||
@@ -74,7 +74,6 @@ frontend:
|
||||
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
|
||||
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
|
||||
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
|
||||
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:
|
||||
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_TRANSACTION_INTERPRETATION_PROVIDER: blockscout
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
|
||||
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER: getit
|
||||
NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler
|
||||
|
||||
@@ -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_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_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 |
|
||||
| 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=
|
||||
|
||||
|
||||
|
||||
### 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 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 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'}]` |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 12 12" 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"/>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -128,7 +128,7 @@ Type extends EventTypes.FILTERS ? {
|
||||
'Filter name': string;
|
||||
} :
|
||||
Type extends EventTypes.BUTTON_CLICK ? {
|
||||
'Content': 'Swap button' | 'Multichain';
|
||||
'Content': string;
|
||||
'Source': string;
|
||||
} :
|
||||
Type extends EventTypes.PROMO_BANNER ? {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { NextRouter } from 'next/router';
|
||||
|
||||
export default function removeQueryParam(router: NextRouter, param: string) {
|
||||
const { pathname, query } = router;
|
||||
const { pathname, query, asPath } = router;
|
||||
const newQuery = { ...query };
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { NextRouter } from 'next/router';
|
||||
|
||||
export default function updateQueryParam(router: NextRouter, param: string, newValue: string) {
|
||||
const { pathname, query } = router;
|
||||
const { pathname, query, asPath } = router;
|
||||
const newQuery = { ...query };
|
||||
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 @@
|
||||
| "networks/logo-placeholder"
|
||||
| "nft_shield"
|
||||
| "output_roots"
|
||||
| "payment_link"
|
||||
| "plus"
|
||||
| "privattags"
|
||||
| "profile"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { IconName } from 'ui/shared/IconSvg';
|
||||
|
||||
export type DeFiDropdownItem = {
|
||||
text: string;
|
||||
icon: IconName;
|
||||
} & (
|
||||
{ dappId: string; url?: never } |
|
||||
{ url: string; dappId?: never }
|
||||
);
|
||||
@@ -0,0 +1,58 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
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';
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import * as metadata from 'lib/metadata';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import removeQueryParam from 'lib/router/removeQueryParam';
|
||||
import ContentLoader from 'ui/shared/ContentLoader';
|
||||
|
||||
import MarketplaceAppTopBar from '../marketplace/MarketplaceAppTopBar';
|
||||
import useAutoConnectWallet from '../marketplace/useAutoConnectWallet';
|
||||
import useMarketplaceWallet from '../marketplace/useMarketplaceWallet';
|
||||
import useSecurityReports from '../marketplace/useSecurityReports';
|
||||
import { getAppUrl } from '../marketplace/utils';
|
||||
|
||||
const feature = config.features.marketplace;
|
||||
|
||||
@@ -134,25 +134,7 @@ const MarketplaceApp = () => {
|
||||
const { data, isPending } = query;
|
||||
const { setIsAutoConnectDisabled } = useMarketplaceContext();
|
||||
|
||||
const appUrl = useMemo(() => {
|
||||
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 ]);
|
||||
const appUrl = useMemo(() => getAppUrl(data?.url, router), [ data?.url, router ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
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));
|
||||
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
@@ -1,50 +0,0 @@
|
||||
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';
|
||||
|
||||
test.beforeEach(async({ 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' ],
|
||||
]);
|
||||
});
|
||||
@@ -29,3 +29,18 @@ test('with secondary coin price +@mobile', async({ render, mockApiResponse }) =>
|
||||
const component = await render(<TopBar/>);
|
||||
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';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import DeFiDropdown from './DeFiDropdown';
|
||||
import Settings from './settings/Settings';
|
||||
import SwapButton from './SwapButton';
|
||||
import TopBarStats from './TopBarStats';
|
||||
|
||||
const feature = config.features.swapButton;
|
||||
const feature = config.features.deFiDropdown;
|
||||
|
||||
const TopBar = () => {
|
||||
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
|
||||
@@ -24,7 +24,7 @@ const TopBar = () => {
|
||||
<Flex alignItems="center">
|
||||
{ feature.isEnabled && (
|
||||
<>
|
||||
<SwapButton/>
|
||||
<DeFiDropdown/>
|
||||
<Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
|
||||
</>
|
||||
) }
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |