Commit 27e4c09d authored by Max Alekseenko's avatar Max Alekseenko

use separate list of categories and improve apps sorting

parent b352e18e
...@@ -10,6 +10,7 @@ export { default as googleAnalytics } from './googleAnalytics'; ...@@ -10,6 +10,7 @@ export { default as googleAnalytics } from './googleAnalytics';
export { default as graphqlApiDocs } from './graphqlApiDocs'; export { default as graphqlApiDocs } from './graphqlApiDocs';
export { default as growthBook } from './growthBook'; export { default as growthBook } from './growthBook';
export { default as marketplace } from './marketplace'; export { default as marketplace } from './marketplace';
export { default as marketplaceCategories } from './marketplaceCategories';
export { default as mixpanel } from './mixpanel'; export { default as mixpanel } from './mixpanel';
export { default as nameService } from './nameService'; export { default as nameService } from './nameService';
export { default as restApiDocs } from './restApiDocs'; export { default as restApiDocs } from './restApiDocs';
......
import type { Feature } from './types';
import { getExternalAssetFilePath } from '../utils';
// config file will be downloaded at run-time and saved in the public folder
const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL');
const title = 'Marketplace categories';
const config: Feature<{ configUrl: string }> = (() => {
if (configUrl) {
return Object.freeze({
title,
isEnabled: true,
configUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -46,6 +46,7 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true ...@@ -46,6 +46,7 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com 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
......
...@@ -15,6 +15,7 @@ ASSETS_DIR="$1" ...@@ -15,6 +15,7 @@ ASSETS_DIR="$1"
# Define a list of environment variables containing URLs of external assets # Define a list of environment variables containing URLs of external assets
ASSETS_ENVS=( ASSETS_ENVS=(
"NEXT_PUBLIC_MARKETPLACE_CONFIG_URL" "NEXT_PUBLIC_MARKETPLACE_CONFIG_URL"
"NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL"
"NEXT_PUBLIC_FEATURED_NETWORKS" "NEXT_PUBLIC_FEATURED_NETWORKS"
"NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_FOOTER_LINKS"
"NEXT_PUBLIC_NETWORK_LOGO" "NEXT_PUBLIC_NETWORK_LOGO"
......
...@@ -36,6 +36,7 @@ async function validateEnvs(appEnvs: Record<string, string>) { ...@@ -36,6 +36,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
const envsWithJsonConfig = [ const envsWithJsonConfig = [
'NEXT_PUBLIC_FEATURED_NETWORKS', 'NEXT_PUBLIC_FEATURED_NETWORKS',
'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL',
'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL',
'NEXT_PUBLIC_FOOTER_LINKS', 'NEXT_PUBLIC_FOOTER_LINKS',
]; ];
......
...@@ -71,6 +71,7 @@ const marketplaceAppSchema: yup.ObjectSchema<MarketplaceAppOverview> = yup ...@@ -71,6 +71,7 @@ const marketplaceAppSchema: yup.ObjectSchema<MarketplaceAppOverview> = yup
telegram: yup.string().test(urlTest), telegram: yup.string().test(urlTest),
github: yup.string().test(urlTest), github: yup.string().test(urlTest),
internalWallet: yup.boolean(), internalWallet: yup.boolean(),
priority: yup.number(),
}); });
const marketplaceSchema = yup const marketplaceSchema = yup
...@@ -80,6 +81,9 @@ const marketplaceSchema = yup ...@@ -80,6 +81,9 @@ const marketplaceSchema = yup
.array() .array()
.json() .json()
.of(marketplaceAppSchema), .of(marketplaceAppSchema),
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: yup
.array()
.json(),
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup
.string() .string()
.when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', { .when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', {
......
...@@ -22,6 +22,7 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true ...@@ -22,6 +22,7 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='<a href="#">Hello</a>' NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='<a href="#">Hello</a>'
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
......
...@@ -182,6 +182,7 @@ frontend: ...@@ -182,6 +182,7 @@ frontend:
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)" NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)"
NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io
NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']" NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']"
......
...@@ -158,6 +158,7 @@ frontend: ...@@ -158,6 +158,7 @@ frontend:
NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
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'}]"
......
...@@ -56,6 +56,7 @@ frontend: ...@@ -56,6 +56,7 @@ frontend:
NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST: https://stats-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_STATS_API_HOST: https://stats-optimism-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)" NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)"
NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io
NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']" NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']"
......
...@@ -64,6 +64,7 @@ frontend: ...@@ -64,6 +64,7 @@ frontend:
NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]"
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar
......
...@@ -421,6 +421,7 @@ This feature is **always enabled**, but you can configure its behavior by passin ...@@ -421,6 +421,7 @@ This feature is **always enabled**, but you can configure its behavior by passin
| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | Required | - | `https://example.com/marketplace_config.json` | | NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | Required | - | `https://example.com/marketplace_config.json` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` | | NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | | NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` |
| NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL | `string` | URL of configuration file (`.json` format only) which the list of categories to be displayed on the markeplace page in the specified order | - | - | `https://example.com/marketplace_categories.json` |
#### Marketplace app configuration properties #### Marketplace app configuration properties
......
...@@ -8,6 +8,7 @@ export type MarketplaceAppPreview = { ...@@ -8,6 +8,7 @@ export type MarketplaceAppPreview = {
categories: Array<string>; categories: Array<string>;
url: string; url: string;
internalWallet?: boolean; internalWallet?: boolean;
priority?: number;
} }
export type MarketplaceAppOverview = MarketplaceAppPreview & { export type MarketplaceAppOverview = MarketplaceAppPreview & {
......
...@@ -5,11 +5,13 @@ import React from 'react'; ...@@ -5,11 +5,13 @@ import React from 'react';
import { MarketplaceCategory } from 'types/client/marketplace'; import { MarketplaceCategory } from 'types/client/marketplace';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import useMarketplaceApps from './useMarketplaceApps'; import useMarketplaceApps from './useMarketplaceApps';
import useMarketplaceCategories from './useMarketplaceCategories';
const favoriteAppsLocalStorageKey = 'favoriteApps'; const favoriteAppsLocalStorageKey = 'favoriteApps';
...@@ -73,14 +75,23 @@ export default function useMarketplace() { ...@@ -73,14 +75,23 @@ export default function useMarketplace() {
const { isPlaceholderData, isError, error, data, displayedApps } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps); const { isPlaceholderData, isError, error, data, displayedApps } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps);
const { value: isExperiment } = useFeatureValue('marketplace_exp', false);
const { isPlaceholderData: isCategoriesPlaceholderData, data: categoriesData } = useMarketplaceCategories();
const categories = React.useMemo(() => { const categories = React.useMemo(() => {
let categoryNames: Array<string> = [];
const grouped = _groudBy(data, app => app.categories); const grouped = _groudBy(data, app => app.categories);
return Object.keys(grouped).map(category => ({
name: category, if (categoriesData?.length && isExperiment) {
count: grouped[category].length, categoryNames = categoriesData;
})); } else {
// return _unique(data?.map(app => app.categories).flat()) || []; categoryNames = Object.keys(grouped);
}, [ data ]); }
return categoryNames
.map(category => ({ name: category, count: grouped[category]?.length || 0 }))
.filter(c => c.count > 0);
}, [ data, categoriesData, isExperiment ]);
React.useEffect(() => { React.useEffect(() => {
setFavoriteApps(getFavoriteApps()); setFavoriteApps(getFavoriteApps());
...@@ -134,6 +145,7 @@ export default function useMarketplace() { ...@@ -134,6 +145,7 @@ export default function useMarketplace() {
isDisclaimerModalOpen, isDisclaimerModalOpen,
showDisclaimer, showDisclaimer,
appsTotal: data?.length || 0, appsTotal: data?.length || 0,
isCategoriesPlaceholderData,
}), [ }), [
selectedCategoryId, selectedCategoryId,
categories, categories,
...@@ -152,5 +164,6 @@ export default function useMarketplace() { ...@@ -152,5 +164,6 @@ export default function useMarketplace() {
isDisclaimerModalOpen, isDisclaimerModalOpen,
showDisclaimer, showDisclaimer,
data?.length, data?.length,
isCategoriesPlaceholderData,
]); ]);
} }
...@@ -6,6 +6,7 @@ import { MarketplaceCategory } from 'types/client/marketplace'; ...@@ -6,6 +6,7 @@ import { MarketplaceCategory } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useApiFetch from 'lib/hooks/useFetch'; import useApiFetch from 'lib/hooks/useFetch';
import { MARKETPLACE_APP } from 'stubs/marketplace'; import { MARKETPLACE_APP } from 'stubs/marketplace';
...@@ -22,12 +23,38 @@ function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, fav ...@@ -22,12 +23,38 @@ function isAppCategoryMatches(category: string, app: MarketplaceAppOverview, fav
app.categories.includes(category); app.categories.includes(category);
} }
function sortApps(apps: Array<MarketplaceAppOverview>, isExperiment: boolean) {
if (!isExperiment) {
return apps.sort((a, b) => a.title.localeCompare(b.title));
}
return apps.sort((a, b) => {
const priorityA = a.priority || 0;
const priorityB = b.priority || 0;
// First, sort by priority (descending)
if (priorityB !== priorityA) {
return priorityB - priorityA;
}
// If priority is the same, sort by internalWallet (true first)
if (a.internalWallet !== b.internalWallet) {
return a.internalWallet ? -1 : 1;
}
// If internalWallet is also the same, sort by external (false first)
if (a.external !== b.external) {
return a.external ? 1 : -1;
}
// If all criteria are the same, keep original order (stable sort)
return 0;
});
}
export default function useMarketplaceApps(filter: string, selectedCategoryId: string = MarketplaceCategory.ALL, favoriteApps: Array<string> = []) { export default function useMarketplaceApps(filter: string, selectedCategoryId: string = MarketplaceCategory.ALL, favoriteApps: Array<string> = []) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const { value: isExperiment } = useFeatureValue('marketplace_exp', false);
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>({ const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>({
queryKey: [ 'marketplace-apps' ], queryKey: [ 'marketplace-apps' ],
queryFn: async() => apiFetch(configUrl, undefined, { resource: 'marketplace-apps' }), queryFn: async() => apiFetch(configUrl, undefined, { resource: 'marketplace-apps' }),
select: (data) => (data as Array<MarketplaceAppOverview>).sort((a, b) => a.title.localeCompare(b.title)), select: (data) => sortApps(data as Array<MarketplaceAppOverview>, isExperiment),
placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined, placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined,
staleTime: Infinity, staleTime: Infinity,
enabled: feature.isEnabled, enabled: feature.isEnabled,
......
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/hooks/useFetch';
const feature = config.features.marketplaceCategories;
const configUrl = feature.isEnabled ? feature.configUrl : '';
export default function useMarketplaceCategories() {
const apiFetch = useApiFetch();
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<string>>({
queryKey: [ 'marketplace-categories' ],
queryFn: async() => apiFetch(configUrl, undefined, { resource: 'marketplace-categories' }),
select: (data) => (data as Array<string>),
placeholderData: feature.isEnabled ? Array(9).fill('Bridge').map((c, i) => c + i) : undefined,
staleTime: Infinity,
enabled: feature.isEnabled,
});
return React.useMemo(() => ({
data,
error,
isError,
isPlaceholderData,
}), [
data,
error,
isError,
isPlaceholderData,
]);
}
...@@ -38,6 +38,7 @@ const Marketplace = () => { ...@@ -38,6 +38,7 @@ const Marketplace = () => {
isDisclaimerModalOpen, isDisclaimerModalOpen,
showDisclaimer, showDisclaimer,
appsTotal, appsTotal,
isCategoriesPlaceholderData,
} = useMarketplace(); } = useMarketplace();
const { value: isExperiment } = useFeatureValue('marketplace_exp', false); const { value: isExperiment } = useFeatureValue('marketplace_exp', false);
...@@ -90,7 +91,7 @@ const Marketplace = () => { ...@@ -90,7 +91,7 @@ const Marketplace = () => {
<> <>
{ isExperiment && ( { isExperiment && (
<Box marginTop={{ base: 0, lg: 8 }}> <Box marginTop={{ base: 0, lg: 8 }}>
{ isPlaceholderData ? ( { (isPlaceholderData || isCategoriesPlaceholderData) ? (
<TabsSkeleton tabs={ categoryTabs }/> <TabsSkeleton tabs={ categoryTabs }/>
) : ( ) : (
<TabsWithScroll <TabsWithScroll
......
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