From 1b58708698905c6d693c5e24c8e0277075409102 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 19 Sep 2024 17:47:08 +0100 Subject: [PATCH] Update trending apps sorting, add app stats, add most liked apps tab --- .gitignore | 1 + packages/core/src/types/index.ts | 3 + skale-network | 2 +- src/App.scss | 11 +- src/LikedAppsContext.tsx | 14 +- src/Router.tsx | 19 ++- src/components/Chip.tsx | 4 + src/components/HomeComponents.tsx | 4 +- src/components/ecosystem/AppCardV2.tsx | 10 +- src/components/ecosystem/Socials.tsx | 4 +- src/components/ecosystem/TrendingApps.tsx | 73 ---------- .../ecosystem/{ => tabs}/AllApps.tsx | 25 ++-- .../ecosystem/{ => tabs}/FavoriteApps.tsx | 21 +-- src/components/ecosystem/tabs/MostLiked.tsx | 99 ++++++++++++++ .../ecosystem/{ => tabs}/NewApps.tsx | 16 ++- .../ecosystem/tabs/TrendingApps.tsx | 102 ++++++++++++++ src/core/ecosystem/utils.ts | 8 ++ src/core/explorer.ts | 8 +- src/pages/App.tsx | 129 ++++++++++++------ src/pages/Chain.tsx | 2 +- src/pages/Ecosystem.tsx | 84 ++++++++---- src/pages/Home.tsx | 26 +++- src/useApps.tsx | 36 +++-- 23 files changed, 507 insertions(+), 194 deletions(-) delete mode 100644 src/components/ecosystem/TrendingApps.tsx rename src/components/ecosystem/{ => tabs}/AllApps.tsx (76%) rename src/components/ecosystem/{ => tabs}/FavoriteApps.tsx (83%) create mode 100644 src/components/ecosystem/tabs/MostLiked.tsx rename src/components/ecosystem/{ => tabs}/NewApps.tsx (81%) create mode 100644 src/components/ecosystem/tabs/TrendingApps.tsx diff --git a/.gitignore b/.gitignore index a589be2..f82eb5a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ src/data/metaportConfig*.ts src/meta/ src/assets/validators/index.ts src/metadata.json +src/metrics.json src/metadata/chainsData.json src/metadata/faucet.json diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 791bf40..d54f194 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -135,6 +135,9 @@ export interface IAddressCounters { token_transfers_count: string transactions_count: string validations_count: string + transactions_last_day: number + transactions_last_7_days: number + transactions_last_30_days: number } export interface IStats { diff --git a/skale-network b/skale-network index d41f6fb..9dc6add 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit d41f6fb3728adb318bdd869e3a2f87672cf10f2c +Subproject commit 9dc6add3b8e7e21576a96956df011f413c3e3c59 diff --git a/src/App.scss b/src/App.scss index 01b9e1f..a5e4088 100644 --- a/src/App.scss +++ b/src/App.scss @@ -762,6 +762,15 @@ input[type=number] { } } +.chipMostLiked { + background: linear-gradient(180deg, #e8a25b, #e58e36) !important; + + + p { + color: black !important + } +} + .chipNewApp { background: linear-gradient(180deg, #65a974, #508d5e) !important; @@ -800,7 +809,7 @@ input[type=number] { .chipXs { border-radius: 15px; - padding: 4px 8px; + padding: 4px 6px; svg { width: 12px; diff --git a/src/LikedAppsContext.tsx b/src/LikedAppsContext.tsx index 926f719..fc8596f 100644 --- a/src/LikedAppsContext.tsx +++ b/src/LikedAppsContext.tsx @@ -38,8 +38,8 @@ interface LikedAppsContextType { refreshLikedApps: () => Promise getAppId: (chainName: string, appName: string) => string getAppInfoById: (appId: string) => types.IAppId - getTrendingApps: () => string[] - getTrendingRank: (trendingAppIds: string[], appId: string) => number | undefined + getMostLikedApps: () => string[] + getMostLikedRank: (mostLikedAppIds: string[], appId: string) => number | undefined } const LikedAppsContext = createContext(undefined) @@ -133,15 +133,15 @@ export const LikedAppsProvider: React.FC<{ children: React.ReactNode }> = ({ chi return { chain, app } } - const getTrendingApps = useCallback(() => { + const getMostLikedApps = useCallback(() => { return Object.entries(appLikes) .sort(([, likesA], [, likesB]) => likesB - likesA) .slice(0, MAX_APPS_DEFAULT) .map(([appId]) => appId) }, [appLikes]) - const getTrendingRank = (trendingAppIds: string[], appId: string): number | undefined => { - const idx = trendingAppIds.indexOf(appId) + const getMostLikedRank = (mostLikedAppIds: string[], appId: string): number | undefined => { + const idx = mostLikedAppIds.indexOf(appId) return idx === -1 ? undefined : idx + 1 } @@ -156,8 +156,8 @@ export const LikedAppsProvider: React.FC<{ children: React.ReactNode }> = ({ chi refreshLikedApps: fetchLikedApps, getAppId, getAppInfoById, - getTrendingApps, - getTrendingRank + getMostLikedApps, + getMostLikedRank }} > {children} diff --git a/src/Router.tsx b/src/Router.tsx index 4719c80..61d0a8e 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -257,7 +257,14 @@ export default function Router() { } + element={ + + } /> } /> @@ -299,7 +306,15 @@ export default function Router() { /> } + element={ + + } /> = ({ trending }) => { ) } +export const ChipMostLiked: React.FC<{}> = ({}) => { + return +} + export const ChipNew: React.FC<{}> = ({}) => { return } diff --git a/src/components/HomeComponents.tsx b/src/components/HomeComponents.tsx index 318f32f..89b02ee 100644 --- a/src/components/HomeComponents.tsx +++ b/src/components/HomeComponents.tsx @@ -29,7 +29,7 @@ import TrendingUpRoundedIcon from '@mui/icons-material/TrendingUpRounded' import LinkRoundedIcon from '@mui/icons-material/LinkRounded' import PieChartOutlineRoundedIcon from '@mui/icons-material/PieChartOutlineRounded' import OutboundRoundedIcon from '@mui/icons-material/OutboundRounded' -import InterestsRoundedIcon from '@mui/icons-material/InterestsRounded' +import PeopleRoundedIcon from '@mui/icons-material/PeopleRounded' interface SectionIcons { [key: string]: JSX.Element @@ -40,7 +40,7 @@ export const SECTION_ICONS: SectionIcons = { favorites: , new: , trending: , - userFavorites: , + mostLiked: , categories: } diff --git a/src/components/ecosystem/AppCardV2.tsx b/src/components/ecosystem/AppCardV2.tsx index a434e3a..c26f972 100644 --- a/src/components/ecosystem/AppCardV2.tsx +++ b/src/components/ecosystem/AppCardV2.tsx @@ -33,7 +33,7 @@ import { chainBg, getChainAlias } from '../../core/metadata' import CollapsibleDescription from '../CollapsibleDescription' import CategoriesChips from './CategoriesChips' import SocialButtons from './Socials' -import { ChipTrending, ChipNew, ChipPreTge } from '../Chip' +import { ChipMostLiked, ChipNew, ChipPreTge, ChipTrending } from '../Chip' export default function AppCard(props: { skaleNetwork: types.SkaleNetwork @@ -43,7 +43,8 @@ export default function AppCard(props: { transactions?: number newApps?: types.AppWithChainAndName[] isNew?: boolean - trending?: number + mostLiked?: number + trending?: boolean }) { const shortAlias = getChainShortAlias(props.chainsMeta, props.schainName) const url = `/ecosystem/${shortAlias}/${props.appName}` @@ -78,10 +79,11 @@ export default function AppCard(props: { )}
-

+

{getChainAlias(props.chainsMeta, props.schainName, props.appName)}

- {props.trending !== undefined && } + {props.mostLiked !== undefined && } + {props.trending && } {props.isNew && } {appMeta.categories && Object.keys(appMeta.categories).includes('pretge') && ( diff --git a/src/components/ecosystem/Socials.tsx b/src/components/ecosystem/Socials.tsx index 2151b69..8454c58 100644 --- a/src/components/ecosystem/Socials.tsx +++ b/src/components/ecosystem/Socials.tsx @@ -41,6 +41,7 @@ interface SocialButtonsProps { appName?: string className?: string size?: 'sm' | 'md' + all?: boolean } const MAX_SOCIALS_SM = 6 @@ -50,6 +51,7 @@ const SocialButtons: React.FC = ({ chainName, appName, className, + all = false, size = 'sm' }) => { const isMd = size === 'md' @@ -113,7 +115,7 @@ const SocialButtons: React.FC = ({ } ] - const visibleLinks = isMd ? socialLinks : socialLinks.slice(0, MAX_SOCIALS_SM) + const visibleLinks = isMd || all ? socialLinks : socialLinks.slice(0, MAX_SOCIALS_SM) return (
diff --git a/src/components/ecosystem/TrendingApps.tsx b/src/components/ecosystem/TrendingApps.tsx deleted file mode 100644 index 0536076..0000000 --- a/src/components/ecosystem/TrendingApps.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useMemo } from 'react' -import { type types } from '@/core' -import AppCard from './AppCardV2' -import { Box, Grid } from '@mui/material' -import { cls, cmn, SkPaper } from '@skalenetwork/metaport' -import Carousel from '../Carousel' -import { isNewApp } from '../../core/ecosystem/utils' -import { getAppMeta } from '../../core/ecosystem/apps' - -interface TrendingAppsProps { - skaleNetwork: types.SkaleNetwork - chainsMeta: types.ChainsMetadataMap - useCarousel?: boolean - newApps: types.AppWithChainAndName[] - trendingApps: types.AppWithChainAndName[] -} - -const TrendingApps: React.FC = ({ - skaleNetwork, - chainsMeta, - useCarousel, - newApps, - trendingApps -}) => { - const filteredApps = useMemo( - () => trendingApps.filter((app) => getAppMeta(chainsMeta, app.chain, app.appName)), - [trendingApps, chainsMeta] - ) - - const renderAppCard = (app: types.AppWithChainAndName) => { - const isNew = isNewApp({ chain: app.chain, app: app.appName }, newApps) - return ( - - - - ) - } - - if (filteredApps.length === 0) { - return ( - -
-

- No trending apps match your current filters -

-
-
- ) - } - - if (useCarousel) { - return {filteredApps.map(renderAppCard)} - } - - return ( - - {filteredApps.map((app) => ( - - {renderAppCard(app)} - - ))} - - ) -} - -export default React.memo(TrendingApps) diff --git a/src/components/ecosystem/AllApps.tsx b/src/components/ecosystem/tabs/AllApps.tsx similarity index 76% rename from src/components/ecosystem/AllApps.tsx rename to src/components/ecosystem/tabs/AllApps.tsx index 68dcf11..eb42cb1 100644 --- a/src/components/ecosystem/AllApps.tsx +++ b/src/components/ecosystem/tabs/AllApps.tsx @@ -24,11 +24,11 @@ import React, { useMemo } from 'react' import { cls, cmn, SkPaper } from '@skalenetwork/metaport' import { type types } from '@/core' -import { useLikedApps } from '../../LikedAppsContext' -import AppCardV2 from './AppCardV2' +import { useLikedApps } from '../../../LikedAppsContext' +import AppCardV2 from '../AppCardV2' import { Grid } from '@mui/material' -import { isNewApp } from '../../core/ecosystem/utils' -import Loader from '../Loader' +import { isNewApp, isTrending } from '../../../core/ecosystem/utils' +import Loader from '../../Loader' interface AllAppsProps { skaleNetwork: types.SkaleNetwork @@ -36,12 +36,20 @@ interface AllAppsProps { apps: types.AppWithChainAndName[] newApps: types.AppWithChainAndName[] loaded: boolean + trendingApps: types.AppWithChainAndName[] } -const AllApps: React.FC = ({ skaleNetwork, chainsMeta, apps, newApps, loaded }) => { - const { getTrendingApps, getAppId, getTrendingRank } = useLikedApps() +const AllApps: React.FC = ({ + skaleNetwork, + chainsMeta, + apps, + newApps, + loaded, + trendingApps +}) => { + const { getMostLikedApps, getAppId, getMostLikedRank } = useLikedApps() - const trendingAppIds = useMemo(() => getTrendingApps(), [getTrendingApps]) + const mostLikedAppIds = useMemo(() => getMostLikedApps(), [getMostLikedApps]) if (!loaded) return if (apps.length === 0) @@ -67,7 +75,8 @@ const AllApps: React.FC = ({ skaleNetwork, chainsMeta, apps, newAp schainName={app.chain} appName={app.appName} chainsMeta={chainsMeta} - trending={getTrendingRank(trendingAppIds, appId)} + mostLiked={getMostLikedRank(mostLikedAppIds, appId)} + trending={isTrending(trendingApps, app.chain, app.appName)} isNew={isNew} /> diff --git a/src/components/ecosystem/FavoriteApps.tsx b/src/components/ecosystem/tabs/FavoriteApps.tsx similarity index 83% rename from src/components/ecosystem/FavoriteApps.tsx rename to src/components/ecosystem/tabs/FavoriteApps.tsx index 54e9001..165b9ce 100644 --- a/src/components/ecosystem/FavoriteApps.tsx +++ b/src/components/ecosystem/tabs/FavoriteApps.tsx @@ -20,17 +20,18 @@ * @file FavoriteApps.tsx * @copyright SKALE Labs 2024-Present */ + import { useMemo } from 'react' import { types } from '@/core' -import { useLikedApps } from '../../LikedAppsContext' -import AppCard from './AppCardV2' +import { useLikedApps } from '../../../LikedAppsContext' +import AppCard from '../AppCardV2' import { Button, Grid } from '@mui/material' import GridViewRoundedIcon from '@mui/icons-material/GridViewRounded' import { cls, cmn, SkPaper } from '@skalenetwork/metaport' -import Carousel from '../Carousel' -import ConnectWallet from '../ConnectWallet' +import Carousel from '../../Carousel' +import ConnectWallet from '../../ConnectWallet' import { Link } from 'react-router-dom' -import { isNewApp } from '../../core/ecosystem/utils' +import { isNewApp, isTrending } from '../../../core/ecosystem/utils' export default function FavoriteApps(props: { skaleNetwork: types.SkaleNetwork @@ -38,17 +39,18 @@ export default function FavoriteApps(props: { useCarousel?: boolean newApps: types.AppWithChainAndName[] filteredApps: types.AppWithChainAndName[] + trendingApps: types.AppWithChainAndName[] isSignedIn: boolean error: string | null }) { - const { getTrendingApps, getAppId, getTrendingRank } = useLikedApps() - const trendingAppIds = useMemo(() => getTrendingApps(), [getTrendingApps]) + const { getMostLikedApps, getAppId, getMostLikedRank } = useLikedApps() + const mostLikedAppIds = useMemo(() => getMostLikedApps(), [getMostLikedApps]) if (!props.isSignedIn) return if (props.error) return
Error: {props.error}
const appCards = props.filteredApps.map((app) => { - const trendingRank = getTrendingRank(trendingAppIds, getAppId(app.chain, app.appName)) + const mostLikedRank = getMostLikedRank(mostLikedAppIds, getAppId(app.chain, app.appName)) const isNew = isNewApp({ chain: app.chain, app: app.appName }, props.newApps) return ( ) diff --git a/src/components/ecosystem/tabs/MostLiked.tsx b/src/components/ecosystem/tabs/MostLiked.tsx new file mode 100644 index 0000000..07b8dc3 --- /dev/null +++ b/src/components/ecosystem/tabs/MostLiked.tsx @@ -0,0 +1,99 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file MostLikedApps.tsx + * @copyright SKALE Labs 2024-Present + */ + +import React, { useMemo } from 'react' +import { type types } from '@/core' +import AppCard from '../AppCardV2' +import { Box, Grid } from '@mui/material' +import { cls, cmn, SkPaper } from '@skalenetwork/metaport' +import Carousel from '../../Carousel' +import { isNewApp, isTrending } from '../../../core/ecosystem/utils' +import { getAppMeta } from '../../../core/ecosystem/apps' + +interface MostLikedAppsProps { + skaleNetwork: types.SkaleNetwork + chainsMeta: types.ChainsMetadataMap + useCarousel?: boolean + newApps: types.AppWithChainAndName[] + filteredApps: types.AppWithChainAndName[] + trendingApps: types.AppWithChainAndName[] +} + +const MostLikedApps: React.FC = ({ + skaleNetwork, + chainsMeta, + useCarousel, + newApps, + filteredApps, + trendingApps +}) => { + const apps = useMemo( + () => filteredApps.filter((app) => getAppMeta(chainsMeta, app.chain, app.appName)), + [filteredApps, chainsMeta] + ) + + const renderAppCard = (app: types.AppWithChainAndName) => { + const isNew = isNewApp({ chain: app.chain, app: app.appName }, newApps) + return ( + + + + ) + } + + if (apps.length === 0) { + return ( + +
+

+ No trending apps match your current filters +

+
+
+ ) + } + + if (useCarousel) { + return {apps.map(renderAppCard)} + } + + return ( + + {apps.map((app) => ( + + {renderAppCard(app)} + + ))} + + ) +} + +export default React.memo(MostLikedApps) diff --git a/src/components/ecosystem/NewApps.tsx b/src/components/ecosystem/tabs/NewApps.tsx similarity index 81% rename from src/components/ecosystem/NewApps.tsx rename to src/components/ecosystem/tabs/NewApps.tsx index d04e14c..b4a3d2a 100644 --- a/src/components/ecosystem/NewApps.tsx +++ b/src/components/ecosystem/tabs/NewApps.tsx @@ -23,15 +23,17 @@ import React, { useMemo } from 'react' import { Grid, Box } from '@mui/material' import { cls, cmn, SkPaper } from '@skalenetwork/metaport' -import AppCard from './AppCardV2' -import Carousel from '../Carousel' +import AppCard from '../AppCardV2' +import Carousel from '../../Carousel' import { type types } from '@/core' -import { useLikedApps } from '../../LikedAppsContext' +import { useLikedApps } from '../../../LikedAppsContext' +import { isTrending } from '../../../core/ecosystem/utils' interface NewAppsProps { newApps: types.AppWithChainAndName[] skaleNetwork: types.SkaleNetwork chainsMeta: types.ChainsMetadataMap + trendingApps: types.AppWithChainAndName[] useCarousel?: boolean } @@ -39,10 +41,11 @@ const NewApps: React.FC = ({ newApps, skaleNetwork, chainsMeta, + trendingApps, useCarousel = false }) => { - const { getTrendingApps, getAppId, getTrendingRank } = useLikedApps() - const trendingAppIds = useMemo(() => getTrendingApps(), [getTrendingApps]) + const { getMostLikedApps, getAppId, getMostLikedRank } = useLikedApps() + const trendingAppIds = useMemo(() => getMostLikedApps(), [getMostLikedApps]) const renderAppCard = (app: types.AppWithChainAndName) => { const appId = getAppId(app.chain, app.appName) @@ -53,7 +56,8 @@ const NewApps: React.FC = ({ schainName={app.chain} appName={app.appName} chainsMeta={chainsMeta} - trending={getTrendingRank(trendingAppIds, appId)} + mostLiked={getMostLikedRank(trendingAppIds, appId)} + trending={isTrending(trendingApps, app.chain, app.appName)} isNew={true} /> ) diff --git a/src/components/ecosystem/tabs/TrendingApps.tsx b/src/components/ecosystem/tabs/TrendingApps.tsx new file mode 100644 index 0000000..9a70de1 --- /dev/null +++ b/src/components/ecosystem/tabs/TrendingApps.tsx @@ -0,0 +1,102 @@ +/** + * @license + * SKALE portal + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file TrendingApps.tsx + * @copyright SKALE Labs 2024-Present + */ + +import React, { useMemo } from 'react' +import { type types } from '@/core' +import AppCard from '../AppCardV2' +import { Box, Grid } from '@mui/material' +import { cls, cmn, SkPaper } from '@skalenetwork/metaport' +import Carousel from '../../Carousel' +import { isNewApp } from '../../../core/ecosystem/utils' +import { getAppMeta } from '../../../core/ecosystem/apps' +import { useLikedApps } from '../../../LikedAppsContext' + +interface TrendingAppsProps { + skaleNetwork: types.SkaleNetwork + chainsMeta: types.ChainsMetadataMap + useCarousel?: boolean + newApps: types.AppWithChainAndName[] + filteredApps: types.AppWithChainAndName[] +} + +const TrendingApps: React.FC = ({ + skaleNetwork, + chainsMeta, + useCarousel, + newApps, + filteredApps +}) => { + const apps = useMemo( + () => filteredApps.filter((app) => getAppMeta(chainsMeta, app.chain, app.appName)), + [filteredApps, chainsMeta] + ) + const { getMostLikedApps, getMostLikedRank, getAppId } = useLikedApps() + + const mostLikedAppIds = useMemo(() => getMostLikedApps(), [getMostLikedApps]) + + const renderAppCard = (app: types.AppWithChainAndName) => { + const isNew = isNewApp({ chain: app.chain, app: app.appName }, newApps) + const appId = getAppId(app.chain, app.appName) + return ( + + + + ) + } + + if (apps.length === 0) { + return ( + +
+

+ No trending apps match your current filters +

+
+
+ ) + } + + if (useCarousel) { + return {apps.map(renderAppCard)} + } + + return ( + + {filteredApps.map((app) => ( + + {renderAppCard(app)} + + ))} + + ) +} + +export default React.memo(TrendingApps) diff --git a/src/core/ecosystem/utils.ts b/src/core/ecosystem/utils.ts index c63f232..f8975b3 100644 --- a/src/core/ecosystem/utils.ts +++ b/src/core/ecosystem/utils.ts @@ -88,3 +88,11 @@ export const isNewApp = ( ): boolean => { return newApps.some((newApp) => newApp.chain === app.chain && newApp.appName === app.app) } + +export const isTrending = ( + apps: types.AppWithChainAndName[], + chainName: string, + appName: string +): boolean => { + return apps.some((a) => a.appName === appName && a.chain === chainName) +} diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 3acb773..ead0465 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -41,7 +41,10 @@ export function getTotalAppCounters( gas_usage_count: '0', token_transfers_count: '0', transactions_count: '0', - validations_count: '0' + validations_count: '0', + transactions_last_day: 0, + transactions_last_7_days: 0, + transactions_last_30_days: 0 } for (const address in countersArray) { if (countersArray.hasOwnProperty(address)) { @@ -60,6 +63,9 @@ export function getTotalAppCounters( totalCounters.validations_count = ( parseInt(totalCounters.validations_count) + parseInt(addressCounters.validations_count) ).toString() + totalCounters.transactions_last_day += addressCounters.transactions_last_day + totalCounters.transactions_last_7_days += addressCounters.transactions_last_7_days + totalCounters.transactions_last_30_days += addressCounters.transactions_last_30_days } } return totalCounters diff --git a/src/pages/App.tsx b/src/pages/App.tsx index e225f75..efbe7e4 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -48,9 +48,11 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' import HubRoundedIcon from '@mui/icons-material/HubRounded' import FavoriteRoundedIcon from '@mui/icons-material/FavoriteRounded' import FavoriteBorderOutlinedIcon from '@mui/icons-material/FavoriteBorderOutlined' +import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded' +import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded' +import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded' import ChainLogo from '../components/ChainLogo' -import SkStack from '../components/SkStack' import Tile from '../components/Tile' import LinkSurface from '../components/LinkSurface' import Breadcrumbs from '../components/Breadcrumbs' @@ -69,8 +71,9 @@ import CategoriesChips from '../components/ecosystem/CategoriesChips' import { useLikedApps } from '../LikedAppsContext' import { useAuth } from '../AuthContext' import ErrorTile from '../components/ErrorTile' -import { ChipNew, ChipPreTge, ChipTrending } from '../components/Chip' -import { getRecentApps, isNewApp } from '../core/ecosystem/utils' +import { ChipNew, ChipPreTge, ChipTrending, ChipMostLiked } from '../components/Chip' +import { getRecentApps, isNewApp, isTrending } from '../core/ecosystem/utils' +import { useApps } from '../useApps' export default function App(props: { mpc: MetaportCore @@ -85,9 +88,9 @@ export default function App(props: { appLikes, toggleLikedApp, getAppId, - getTrendingApps, + getMostLikedApps, refreshLikedApps, - getTrendingRank + getMostLikedRank } = useLikedApps() const { isSignedIn, handleSignIn } = useAuth() @@ -126,13 +129,16 @@ export default function App(props: { const appDescription = appMeta.description ?? 'No description' + const { trendingApps } = useApps(props.chainsMeta, props.metrics) + const appId = getAppId(chain, app) const isLiked = likedApps.includes(appId) const likesCount = appLikes[appId] || 0 - const trendingAppIds = useMemo(() => getTrendingApps(), [getTrendingApps]) - const trendingIndex = getTrendingRank(trendingAppIds, appId) + const mostLiked = useMemo(() => getMostLikedApps(), [getMostLikedApps]) + const mostLikedIndex = getMostLikedRank(mostLiked, appId) const isNew = isNewApp({ chain, app }, newApps) + const trending = isTrending(trendingApps, chain, app) const handleToggleLike = async () => { if (!address) { @@ -242,7 +248,8 @@ export default function App(props: {

{appAlias}

- {trendingIndex !== undefined && } + {mostLikedIndex !== undefined && } + {trending && } {isNew && } {appMeta.categories && Object.keys(appMeta.categories).includes('pretge') && ( @@ -257,40 +264,84 @@ export default function App(props: {
- - {appMeta.contracts ? ( - } - /> - ) : null} - {appMeta.contracts ? ( + + {appMeta.contracts && ( + + } + /> + + )} + {appMeta.contracts && ( + + + ) : undefined + } + tooltip={ + props.metrics && counters + ? `Given gas price ${props.metrics.gas} Gwei. ${counters.gas_usage_count} of gas used.` + : undefined + } + value={props.metrics && counters ? `${formatGas()} ETH` : undefined} + icon={} + /> + + )} + - ) : undefined - } - tooltip={ - props.metrics && counters - ? `Given gas price ${props.metrics.gas} Gwei. ${counters.gas_usage_count} of gas used.` - : undefined - } - value={props.metrics && counters ? `${formatGas()} ETH` : undefined} - icon={} + text="Favorites" + value={likesCount.toString()} + icon={} /> - ) : null} - } - /> - + + {appMeta.contracts && ( + + } + /> + + )} + {appMeta.contracts && ( + + } + /> + + )} + {appMeta.contracts && ( + + } + /> + + )} + {chain !== OFFCHAIN_APP && ( diff --git a/src/pages/Chain.tsx b/src/pages/Chain.tsx index f0ed90a..1a8bf6d 100644 --- a/src/pages/Chain.tsx +++ b/src/pages/Chain.tsx @@ -34,7 +34,7 @@ import { type types } from '@/core' import { findChainName } from '../core/chain' export default function Chain(props: { - loadData: any + loadData: () => Promise schains: types.ISChain[] stats: types.IStats | null metrics: types.IMetrics | null diff --git a/src/pages/Ecosystem.tsx b/src/pages/Ecosystem.tsx index d58194b..706de04 100644 --- a/src/pages/Ecosystem.tsx +++ b/src/pages/Ecosystem.tsx @@ -28,6 +28,7 @@ import FavoriteRoundedIcon from '@mui/icons-material/FavoriteRounded' import TrendingUpRoundedIcon from '@mui/icons-material/TrendingUpRounded' import StarRoundedIcon from '@mui/icons-material/StarRounded' import AddCircleOutlineRoundedIcon from '@mui/icons-material/AddCircleOutlineRounded' +import PeopleRoundedIcon from '@mui/icons-material/PeopleRounded' import { type types } from '@/core' import { cmn, cls, type MetaportCore } from '@skalenetwork/metaport' @@ -41,20 +42,26 @@ import CategoryDisplay from '../components/ecosystem/Categories' import SearchComponent from '../components/ecosystem/AppSearch' import SelectedCategories from '../components/ecosystem/SelectedCategories' import SkStack from '../components/SkStack' -import AllApps from '../components/ecosystem/AllApps' -import NewApps from '../components/ecosystem/NewApps' -import FavoriteApps from '../components/ecosystem/FavoriteApps' -import TrendingApps from '../components/ecosystem/TrendingApps' +import AllApps from '../components/ecosystem/tabs/AllApps' +import NewApps from '../components/ecosystem/tabs/NewApps' +import FavoriteApps from '../components/ecosystem/tabs/FavoriteApps' +import MostLiked from '../components/ecosystem/tabs/MostLiked' +import TrendingApps from '../components/ecosystem/tabs/TrendingApps' import SocialButtons from '../components/ecosystem/Socials' export default function Ecosystem(props: { mpc: MetaportCore chainsMeta: types.ChainsMetadataMap + metrics: types.IMetrics | null isXs: boolean + loadData: () => Promise }) { const { getCheckedItemsFromUrl, setCheckedItemsInUrl, getTabIndexFromUrl, setTabIndexInUrl } = useUrlParams() - const { allApps, newApps, trendingApps, favoriteApps, isSignedIn } = useApps(props.chainsMeta) + const { allApps, newApps, mostLikedApps, trendingApps, favoriteApps, isSignedIn } = useApps( + props.chainsMeta, + props.metrics + ) const [checkedItems, setCheckedItems] = useState([]) const [filteredApps, setFilteredApps] = useState([]) @@ -63,6 +70,7 @@ export default function Ecosystem(props: { const [loaded, setLoaded] = useState(false) useEffect(() => { + props.loadData() const initialCheckedItems = getCheckedItemsFromUrl() setCheckedItems(initialCheckedItems) const initialTabIndex = getTabIndexFromUrl() @@ -102,6 +110,22 @@ export default function Ecosystem(props: { ], // New Apps [ 2, + trendingApps.filter((app) => + filteredApps.some( + (filteredApp) => filteredApp.chain === app.chain && filteredApp.appName === app.appName + ) + ) + ], // Trending Apps + [ + 3, + mostLikedApps.filter((app) => + filteredApps.some( + (filteredApp) => filteredApp.chain === app.chain && filteredApp.appName === app.appName + ) + ) + ], // Most liked Apps + [ + 4, isSignedIn ? favoriteApps.filter((app) => filteredApps.some( @@ -110,19 +134,11 @@ export default function Ecosystem(props: { ) ) : [] - ], // Favorite Apps - [ - 3, - trendingApps.filter((app) => - filteredApps.some( - (filteredApp) => filteredApp.chain === app.chain && filteredApp.appName === app.appName - ) - ) - ] // Trending Apps + ] // Favorite Apps ]) return (tabIndex: number) => filterMap.get(tabIndex) || filteredApps - }, [filteredApps, newApps, favoriteApps, trendingApps, isSignedIn]) + }, [filteredApps, newApps, trendingApps, mostLikedApps, favoriteApps, isSignedIn]) const currentFilteredApps = getFilteredAppsByTab(activeTab) @@ -143,7 +159,7 @@ export default function Ecosystem(props: {

- +
@@ -189,14 +205,20 @@ export default function Ecosystem(props: { className={cls('btn', 'btnSm', cmn.mri5, cmn.mleft5, 'tab', 'fwmobile')} /> } + label="Trending" + icon={} iconPosition="start" className={cls('btn', 'btnSm', cmn.mri5, cmn.mleft5, 'tab', 'fwmobile')} /> } + label="Most Liked" + icon={} + iconPosition="start" + className={cls('btn', 'btnSm', cmn.mri5, cmn.mleft5, 'tab', 'fwmobile')} + /> + } iconPosition="start" className={cls('btn', 'btnSm', cmn.mri5, cmn.mleft5, 'tab', 'fwmobile')} /> @@ -209,6 +231,7 @@ export default function Ecosystem(props: { chainsMeta={props.chainsMeta} newApps={newApps} loaded={loaded} + trendingApps={trendingApps} /> )} {activeTab === 1 && ( @@ -216,24 +239,35 @@ export default function Ecosystem(props: { newApps={currentFilteredApps} skaleNetwork={props.mpc.config.skaleNetwork} chainsMeta={props.chainsMeta} + trendingApps={trendingApps} /> )} {activeTab === 2 && ( - )} {activeTab === 3 && ( - + )} + {activeTab === 4 && ( + )} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index cfc8490..8efc31b 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -31,21 +31,33 @@ import { useApps } from '../useApps' import Headline from '../components/Headline' import PageCard from '../components/PageCard' import CategoryCardsGrid from '../components/ecosystem/CategoryCardsGrid' -import NewApps from '../components/ecosystem/NewApps' -import FavoriteApps from '../components/ecosystem/FavoriteApps' -import TrendingApps from '../components/ecosystem/TrendingApps' +import NewApps from '../components/ecosystem/tabs/NewApps' +import FavoriteApps from '../components/ecosystem/tabs/FavoriteApps' +import TrendingApps from '../components/ecosystem/tabs/TrendingApps' import { SECTION_ICONS, EXPLORE_CARDS } from '../components/HomeComponents' import SocialButtons from '../components/ecosystem/Socials' import { SKALE_SOCIAL_LINKS } from '../core/constants' +import { useEffect } from 'react' interface HomeProps { skaleNetwork: types.SkaleNetwork chainsMeta: types.ChainsMetadataMap + metrics: types.IMetrics | null + loadData: () => Promise } -export default function Home({ skaleNetwork, chainsMeta }: HomeProps): JSX.Element { - const { newApps, trendingApps, favoriteApps, isSignedIn } = useApps(chainsMeta) +export default function Home({ + skaleNetwork, + chainsMeta, + metrics, + loadData +}: HomeProps): JSX.Element { + const { newApps, trendingApps, favoriteApps, isSignedIn } = useApps(chainsMeta, metrics) + + useEffect(() => { + loadData() + }, []) return ( @@ -68,6 +80,7 @@ export default function Home({ skaleNetwork, chainsMeta }: HomeProps): JSX.Eleme useCarousel={true} newApps={newApps} filteredApps={favoriteApps} + trendingApps={trendingApps} isSignedIn={isSignedIn} error={null} /> @@ -83,6 +96,7 @@ export default function Home({ skaleNetwork, chainsMeta }: HomeProps): JSX.Eleme skaleNetwork={skaleNetwork} chainsMeta={chainsMeta} useCarousel={true} + trendingApps={trendingApps} /> } /> @@ -95,7 +109,7 @@ export default function Home({ skaleNetwork, chainsMeta }: HomeProps): JSX.Eleme chainsMeta={chainsMeta} skaleNetwork={skaleNetwork} newApps={newApps} - trendingApps={trendingApps} + filteredApps={trendingApps} useCarousel /> } diff --git a/src/useApps.tsx b/src/useApps.tsx index 96e4bb1..01eced8 100644 --- a/src/useApps.tsx +++ b/src/useApps.tsx @@ -23,12 +23,13 @@ import { useMemo } from 'react' import { type types } from '@/core' import { getRecentApps } from './core/ecosystem/utils' +import { getTotalAppCounters } from './core/explorer' import { MAX_APPS_DEFAULT } from './core/constants' import { useLikedApps } from './LikedAppsContext' import { useAuth } from './AuthContext' -export function useApps(chainsMeta: types.ChainsMetadataMap) { - const { getTrendingApps, likedApps, getAppId } = useLikedApps() +export function useApps(chainsMeta: types.ChainsMetadataMap, metrics: types.IMetrics | null) { + const { getMostLikedApps, likedApps, getAppId } = useLikedApps() const { isSignedIn } = useAuth() const allApps = useMemo(() => { @@ -47,14 +48,13 @@ export function useApps(chainsMeta: types.ChainsMetadataMap) { return apps.sort((a, b) => (b.added || 0) - (a.added || 0)) }, [chainsMeta]) - const trendingAppIds = getTrendingApps() - - const trendingApps = useMemo(() => { + const mostLikedAppIds = getMostLikedApps() + const mostLikedApps = useMemo(() => { const appMap = new Map(allApps.map((app) => [getAppId(app.chain, app.appName), app])) - return trendingAppIds + return mostLikedAppIds .map((id) => appMap.get(id)) .filter((app): app is types.AppWithChainAndName => app !== undefined) - }, [allApps, trendingAppIds, getAppId]) + }, [allApps, mostLikedAppIds, getAppId]) const favoriteApps = useMemo(() => { if (!isSignedIn) return [] @@ -62,5 +62,25 @@ export function useApps(chainsMeta: types.ChainsMetadataMap) { return apps.sort((a, b) => a.alias.localeCompare(b.alias)) }, [allApps, likedApps, isSignedIn, getAppId]) - return { allApps, newApps, trendingApps, favoriteApps, isSignedIn } + const trendingApps = useMemo(() => { + if (!metrics) return [] + + const appsWithTransactions = allApps.map((app) => { + const chainMetrics = metrics.metrics[app.chain] + if (!chainMetrics || !chainMetrics.apps_counters[app.appName]) { + return { ...app, transactions_last_7_days: 0 } + } + const totalCounters = getTotalAppCounters(chainMetrics.apps_counters[app.appName]) + return { + ...app, + transactions_last_7_days: totalCounters ? totalCounters.transactions_last_7_days : 0 + } + }) + + return appsWithTransactions + .sort((a, b) => b.transactions_last_7_days - a.transactions_last_7_days) + .slice(0, MAX_APPS_DEFAULT) + }, [allApps, metrics]) + + return { allApps, newApps, mostLikedApps, favoriteApps, trendingApps, isSignedIn } }