Skip to content

Commit 547a721

Browse files
mgrabinafoodaka
andauthored
feat: favourite markets (#2650)
Co-authored-by: mark hinschberger <markhinschberger@gmail.com>
1 parent 6ffcdf2 commit 547a721

13 files changed

Lines changed: 305 additions & 65 deletions

File tree

public/icons/networks/ink.svg

Lines changed: 1 addition & 3 deletions
Loading

src/components/MarketSwitcher.tsx

Lines changed: 126 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { ChevronDownIcon } from '@heroicons/react/outline';
2-
import { ExternalLinkIcon } from '@heroicons/react/solid';
2+
import { ExternalLinkIcon, StarIcon } from '@heroicons/react/solid';
33
import { Trans } from '@lingui/macro';
44
import {
55
Box,
66
BoxProps,
7+
IconButton,
78
ListItemText,
89
MenuItem,
910
SvgIcon,
@@ -111,6 +112,35 @@ enum SelectedMarketVersion {
111112
V3,
112113
}
113114

115+
// Custom market order requested by BD - TODO: move logic to the backend base on TVL
116+
const MARKET_ORDER_BY_TITLE: { [title: string]: number } = {
117+
Core: 0,
118+
Prime: 1,
119+
Base: 2,
120+
Arbitrum: 3,
121+
Avalanche: 4,
122+
Linea: 5,
123+
'Horizon RWA': 6,
124+
Sonic: 7,
125+
OP: 8,
126+
Gnosis: 9,
127+
Aptos: 10,
128+
'BNB Chain': 11,
129+
Polygon: 12,
130+
Scroll: 13,
131+
ZKsync: 14,
132+
Celo: 15,
133+
Metis: 16,
134+
Soneium: 17,
135+
EtherFi: 18,
136+
};
137+
138+
const getMarketOrder = (marketId: CustomMarket): number => {
139+
const { market } = getMarketInfoById(marketId);
140+
const marketTitle = market.marketTitle;
141+
return MARKET_ORDER_BY_TITLE[marketTitle] ?? 999; // Default to end if not found
142+
};
143+
114144
export const MarketSwitcher = () => {
115145
const [selectedMarketVersion, setSelectedMarketVersion] = useState<SelectedMarketVersion>(
116146
SelectedMarketVersion.V3
@@ -121,6 +151,10 @@ export const MarketSwitcher = () => {
121151
const [trackEvent, currentMarket, setCurrentMarket] = useRootStore(
122152
useShallow((store) => [store.trackEvent, store.currentMarket, store.setCurrentMarket])
123153
);
154+
const isFavoriteMarket = useRootStore((store) => store.isFavoriteMarket);
155+
const toggleFavoriteMarket = useRootStore((store) => store.toggleFavoriteMarket);
156+
// Subscribe to favoriteMarkets to trigger re-renders when favorites change
157+
useRootStore((store) => store.favoriteMarkets);
124158

125159
const isV3MarketsAvailable = availableMarkets
126160
.map((marketId: CustomMarket) => {
@@ -143,6 +177,12 @@ export const MarketSwitcher = () => {
143177
setCurrentMarket(selectedMarket);
144178
};
145179

180+
const handleStarClick = (e: React.MouseEvent, marketId: CustomMarket) => {
181+
e.stopPropagation();
182+
e.preventDefault();
183+
toggleFavoriteMarket(marketId);
184+
};
185+
146186
const marketBlurbs: { [key: string]: JSX.Element } = {
147187
proto_mainnet_v3: (
148188
<Trans>Main Ethereum market with the largest selection of assets and yield options</Trans>
@@ -385,53 +425,94 @@ export const MarketSwitcher = () => {
385425
</StyledToggleButtonGroup>
386426
</Box>
387427
)}
388-
{availableMarkets.map((marketId: CustomMarket) => {
389-
const { market, logo } = getMarketInfoById(marketId);
390-
const marketNaming = getMarketHelpData(market.marketTitle);
391-
return (
392-
<MenuItem
393-
key={marketId}
394-
data-cy={`marketSelector_${marketId}`}
395-
value={marketId}
396-
sx={{
397-
'.MuiListItemIcon-root': { minWidth: 'unset' },
398-
display:
399-
(market.v3 && selectedMarketVersion === SelectedMarketVersion.V2) ||
400-
(!market.v3 && selectedMarketVersion === SelectedMarketVersion.V3)
401-
? 'none'
402-
: 'flex',
403-
}}
404-
>
405-
<MarketLogo size={32} logo={logo} testChainName={marketNaming.testChainName} />
406-
<ListItemText sx={{ mr: 0 }}>
407-
{marketNaming.name} {market.isFork ? 'Fork' : ''}
408-
</ListItemText>
409-
<ListItemText
428+
{availableMarkets
429+
.slice() // Create a copy to avoid mutating the original array
430+
.sort((a, b) => {
431+
const aIsFavorite = isFavoriteMarket(a);
432+
const bIsFavorite = isFavoriteMarket(b);
433+
434+
// If both are favorites or both are not favorites, maintain custom order
435+
if (aIsFavorite === bIsFavorite) {
436+
return getMarketOrder(a) - getMarketOrder(b);
437+
}
438+
439+
// Otherwise, favorites come first
440+
return aIsFavorite ? -1 : 1;
441+
})
442+
.map((marketId: CustomMarket) => {
443+
const { market, logo } = getMarketInfoById(marketId);
444+
const marketNaming = getMarketHelpData(market.marketTitle);
445+
const isFavorite = isFavoriteMarket(marketId);
446+
return (
447+
<MenuItem
448+
key={marketId}
449+
data-cy={`marketSelector_${marketId}`}
450+
value={marketId}
410451
sx={{
411-
textAlign: 'right',
412-
display: 'flex',
413-
alignItems: 'center',
414-
flexDirection: 'row-reverse',
415-
gap: 1,
452+
'.MuiListItemIcon-root': { minWidth: 'unset' },
453+
display:
454+
(market.v3 && selectedMarketVersion === SelectedMarketVersion.V2) ||
455+
(!market.v3 && selectedMarketVersion === SelectedMarketVersion.V3)
456+
? 'none'
457+
: 'flex',
416458
}}
417459
>
418-
<Typography color="text.muted" variant="description">
419-
{marketNaming.testChainName}
420-
</Typography>
421-
{market.externalUrl && (
422-
<SvgIcon
423-
sx={{
424-
fontSize: '16px',
425-
color: 'text.muted',
426-
}}
427-
>
428-
<ExternalLinkIcon />
429-
</SvgIcon>
430-
)}
431-
</ListItemText>
432-
</MenuItem>
433-
);
434-
})}
460+
<MarketLogo size={32} logo={logo} testChainName={marketNaming.testChainName} />
461+
<ListItemText sx={{ mr: 0 }}>
462+
{marketNaming.name} {market.isFork ? 'Fork' : ''}
463+
</ListItemText>
464+
<ListItemText
465+
sx={{
466+
textAlign: 'right',
467+
display: 'flex',
468+
alignItems: 'center',
469+
flexDirection: 'row-reverse',
470+
gap: 1,
471+
}}
472+
>
473+
<Typography color="text.muted" variant="description">
474+
{marketNaming.testChainName}
475+
</Typography>
476+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
477+
{market.externalUrl && (
478+
<SvgIcon
479+
sx={{
480+
fontSize: '16px',
481+
color: 'text.muted',
482+
}}
483+
>
484+
<ExternalLinkIcon />
485+
</SvgIcon>
486+
)}
487+
<Tooltip title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}>
488+
<IconButton
489+
size="small"
490+
onClick={(e) => handleStarClick(e, marketId)}
491+
sx={{
492+
padding: '2px',
493+
'&:hover': {
494+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
495+
},
496+
}}
497+
>
498+
<SvgIcon
499+
sx={{
500+
fontSize: '18px',
501+
color: isFavorite ? '#FBCC5F' : 'text.disabled',
502+
'&:hover': {
503+
color: isFavorite ? '#FBCC5F' : 'text.secondary',
504+
},
505+
}}
506+
>
507+
<StarIcon />
508+
</SvgIcon>
509+
</IconButton>
510+
</Tooltip>
511+
</Box>
512+
</ListItemText>
513+
</MenuItem>
514+
);
515+
})}
435516
</TextField>
436517
);
437518
};

src/components/TopInfoPanel/PageTitle.tsx

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { StarIcon } from '@heroicons/react/solid';
12
import { Trans } from '@lingui/macro';
2-
import { Box, Button, Typography, useMediaQuery, useTheme } from '@mui/material';
3+
import { Box, Button, SvgIcon, Typography, useMediaQuery, useTheme } from '@mui/material';
34
import { ReactNode } from 'react';
45

56
import { useRootStore } from '../../store/root';
@@ -13,16 +14,33 @@ export interface PageTitleProps extends Pick<NetworkConfig, 'bridge'> {
1314
pageTitle?: ReactNode;
1415
withMarketSwitcher?: boolean;
1516
withMigrateButton?: boolean;
17+
withFavoriteButton?: boolean;
1618
}
1719

18-
export const PageTitle = ({ pageTitle, withMarketSwitcher, withMigrateButton }: PageTitleProps) => {
20+
export const PageTitle = ({
21+
pageTitle,
22+
withMarketSwitcher,
23+
withMigrateButton,
24+
withFavoriteButton,
25+
}: PageTitleProps) => {
1926
const isMigrateToV3Available = useRootStore((state) => selectIsMigrationAvailable(state));
27+
const currentMarket = useRootStore((state) => state.currentMarket);
28+
const isFavoriteMarket = useRootStore((state) => state.isFavoriteMarket);
29+
const toggleFavoriteMarket = useRootStore((state) => state.toggleFavoriteMarket);
30+
// Subscribe to favoriteMarkets to trigger re-renders when favorites change
31+
useRootStore((state) => state.favoriteMarkets);
2032

2133
const theme = useTheme();
2234
const upToLG = useMediaQuery(theme.breakpoints.up('lg'));
2335
// const upToMD = useMediaQuery(theme.breakpoints.up('md'));
2436
const downToXSM = useMediaQuery(theme.breakpoints.down('xsm'));
2537

38+
const isCurrentMarketFavorite = isFavoriteMarket(currentMarket);
39+
40+
const handleFavoriteClick = () => {
41+
toggleFavoriteMarket(currentMarket);
42+
};
43+
2644
return (
2745
<Box
2846
sx={{
@@ -53,17 +71,51 @@ export const PageTitle = ({ pageTitle, withMarketSwitcher, withMigrateButton }:
5371
alignItems: 'flex-start',
5472
flexWrap: 'wrap',
5573
mb: !pageTitle ? 4 : 0,
74+
justifyContent: 'space-between',
75+
width: '100%',
5676
}}
5777
>
58-
{withMarketSwitcher && <MarketSwitcher />}
59-
{/* <BridgeButton bridge={bridge} variant="surface" withoutIcon={!upToMD} /> */}
60-
{/* NOTE:// Removing for now */}
61-
{isMigrateToV3Available && withMigrateButton && (
62-
<Link href={ROUTES.migrationTool} sx={{ mt: { xs: 2, xsm: 0 } }}>
63-
<Button variant="gradient" size="small">
64-
<Trans>Migrate to V3</Trans>
65-
</Button>
66-
</Link>
78+
<Box sx={{ display: 'flex', alignItems: 'flex-start', flexWrap: 'wrap' }}>
79+
{withMarketSwitcher && <MarketSwitcher />}
80+
{/* <BridgeButton bridge={bridge} variant="surface" withoutIcon={!upToMD} /> */}
81+
{/* NOTE:// Removing for now */}
82+
{isMigrateToV3Available && withMigrateButton && (
83+
<Link href={ROUTES.migrationTool} sx={{ mt: { xs: 2, xsm: 0 } }}>
84+
<Button variant="gradient" size="small">
85+
<Trans>Migrate to V3</Trans>
86+
</Button>
87+
</Link>
88+
)}
89+
</Box>
90+
91+
{withFavoriteButton && (
92+
<Button
93+
onClick={handleFavoriteClick}
94+
variant="surface"
95+
sx={{
96+
display: { xs: 'none', sm: 'flex' }, // Hide on mobile (xs), show on small screens and up
97+
p: '7px 8px',
98+
minWidth: 'unset',
99+
gap: 2,
100+
alignItems: 'center',
101+
}}
102+
aria-label="Favorite tool"
103+
>
104+
<Box sx={{ display: 'flex', alignItems: 'center' }}>
105+
<SvgIcon
106+
sx={{
107+
fontSize: '18px !important',
108+
color: isCurrentMarketFavorite ? '#FBCC5F' : 'rgba(255, 255, 255, 0.3)',
109+
}}
110+
>
111+
<StarIcon />
112+
</SvgIcon>
113+
</Box>
114+
115+
<Typography component="span" typography="subheader1" sx={{ fontWeight: 500 }}>
116+
{isCurrentMarketFavorite ? <Trans>Favourited</Trans> : <Trans>Favourite</Trans>}
117+
</Typography>
118+
</Button>
67119
)}
68120
</Box>
69121
</Box>

src/components/TopInfoPanel/TopInfoPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const TopInfoPanel = ({
1414
titleComponent,
1515
withMarketSwitcher,
1616
withMigrateButton,
17+
withFavoriteButton,
1718
bridge,
1819
children,
1920
containerProps = {},
@@ -34,6 +35,7 @@ export const TopInfoPanel = ({
3435
pageTitle={pageTitle}
3536
withMarketSwitcher={withMarketSwitcher}
3637
withMigrateButton={withMigrateButton}
38+
withFavoriteButton={withFavoriteButton}
3739
bridge={bridge}
3840
/>
3941
)}

src/locales/el/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/locales/en/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/locales/en/messages.po

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ msgstr "Total Deposited"
282282
msgid "{0}{name}"
283283
msgstr "{0}{name}"
284284

285+
#: src/components/TopInfoPanel/PageTitle.tsx
286+
msgid "Favourited"
287+
msgstr "Favourited"
288+
285289
#: src/ui-config/errorMapping.tsx
286290
msgid "Stable borrowing is enabled"
287291
msgstr "Stable borrowing is enabled"
@@ -3000,6 +3004,10 @@ msgstr "Your {name} wallet is empty. Purchase or transfer assets or use <0>{0}</
30003004
msgid "Checking approval"
30013005
msgstr "Checking approval"
30023006

3007+
#: src/components/TopInfoPanel/PageTitle.tsx
3008+
msgid "Favourite"
3009+
msgstr "Favourite"
3010+
30033011
#: src/modules/staking/BuyWithFiat.tsx
30043012
msgid "Buy {cryptoSymbol} with Fiat"
30053013
msgstr "Buy {cryptoSymbol} with Fiat"

src/locales/es/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/locales/fr/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/markets/MarketsTopPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const MarketsTopPanel = () => {
3535
containerProps={marketContainerProps}
3636
pageTitle={<Trans>Markets</Trans>}
3737
withMarketSwitcher
38+
withFavoriteButton
3839
>
3940
<TopInfoPanelItem hideIcon title={<Trans>Total market size</Trans>} loading={loading}>
4041
<FormattedNumber

0 commit comments

Comments
 (0)