Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add banners #19658

Merged
merged 5 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions apps/core/src/hooks/useFormatCoin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useFeatureValue } from '@growthbook/growthbook-react';
import { useSuiClient } from '@mysten/dapp-kit';
import { CoinMetadata } from '@mysten/sui/client';
import { SUI_TYPE_ARG } from '@mysten/sui/utils';
Expand Down Expand Up @@ -44,8 +45,21 @@ const ELLIPSIS = '\u{2026}';
const SYMBOL_TRUNCATE_LENGTH = 5;
const NAME_TRUNCATE_LENGTH = 10;

type CoinMetadataOverrides = {
[coinType: string]: {
name?: string;
iconUrl?: string;
symbol?: string;
};
};

export function useCoinMetadata(coinType?: string | null) {
const client = useSuiClient();
const tokenMetadataOverrides = useFeatureValue<CoinMetadataOverrides>(
'token-metadata-overrides',
{},
);

return useQuery({
queryKey: ['coin-metadata', coinType],
queryFn: async () => {
Expand All @@ -72,16 +86,29 @@ export function useCoinMetadata(coinType?: string | null) {
select(data) {
if (!data) return null;

const symbol =
coinType && tokenMetadataOverrides[coinType]?.symbol
? tokenMetadataOverrides[coinType].symbol
: data.symbol;
const name =
coinType && tokenMetadataOverrides[coinType]?.name
? tokenMetadataOverrides[coinType].name
: data.name;

return {
...data,
iconUrl:
coinType && tokenMetadataOverrides[coinType]?.iconUrl
? tokenMetadataOverrides[coinType].iconUrl
: data.iconUrl,
symbol:
data.symbol.length > SYMBOL_TRUNCATE_LENGTH
? data.symbol.slice(0, SYMBOL_TRUNCATE_LENGTH) + ELLIPSIS
: data.symbol,
symbol.length > SYMBOL_TRUNCATE_LENGTH
? symbol.slice(0, SYMBOL_TRUNCATE_LENGTH) + ELLIPSIS
: symbol,
name:
data.name.length > NAME_TRUNCATE_LENGTH
? data.name.slice(0, NAME_TRUNCATE_LENGTH) + ELLIPSIS
: data.name,
name.length > NAME_TRUNCATE_LENGTH
? name.slice(0, NAME_TRUNCATE_LENGTH) + ELLIPSIS
: name,
};
},
retry: false,
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/components/buynlarge/HomePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export function BuyNLargeHomePanel() {

return (
<>
{bnl.map((item) => {
{bnl.map((item, index) => {
if (!item || !item.enabled || !item.asset || seen.includes(item?.objectType)) return null;

return (
<div>
<div key={index}>
<div
role="button"
onClick={() => {
Expand Down
8 changes: 6 additions & 2 deletions apps/wallet/src/ui/app/components/coin-icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { ImageIcon } from '_app/shared/image-icon';
import { useCoinMetadata } from '@mysten/core';
import { Sui, Unstaked } from '@mysten/icons';
import { SUI_TYPE_ARG } from '@mysten/sui/utils';
import { normalizeStructTag, SUI_TYPE_ARG } from '@mysten/sui/utils';
import { cva, type VariantProps } from 'class-variance-authority';

import { useCoinMetadataOverrides } from '../../hooks/useCoinMetadataOverride';
Expand Down Expand Up @@ -63,9 +63,13 @@ export interface CoinIconProps extends VariantProps<typeof imageStyle> {
}

export function CoinIcon({ coinType, ...styleProps }: CoinIconProps) {
const isSui = coinType
? normalizeStructTag(coinType) === normalizeStructTag(SUI_TYPE_ARG)
: false;

return (
<div className={imageStyle(styleProps)}>
{coinType === SUI_TYPE_ARG ? <SuiCoin /> : <NonSuiCoin coinType={coinType} />}
{isSui ? <SuiCoin /> : <NonSuiCoin coinType={coinType} />}
</div>
);
}
18 changes: 18 additions & 0 deletions apps/wallet/src/ui/app/hooks/useGetAllBalances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useCoinsReFetchingConfig } from '_app/hooks/useCoinsReFetchingConfig';
import { useSuiClientQuery } from '@mysten/dapp-kit';

export function useGetAllBalances(owner: string) {
const { staleTime, refetchInterval } = useCoinsReFetchingConfig();

return useSuiClientQuery(
'getAllBalances',
{ owner: owner! },
{
enabled: !!owner,
refetchInterval,
staleTime,
},
);
}
11 changes: 7 additions & 4 deletions apps/wallet/src/ui/app/pages/home/tokens/TokensDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
useCoinsReFetchingConfig,
useSortedCoinsByCategories,
} from '_hooks';
import { UsdcPromoBanner } from '_pages/home/usdc-promo/UsdcPromoBanner';
import {
DELEGATED_STAKES_QUERY_REFETCH_INTERVAL,
DELEGATED_STAKES_QUERY_STALE_TIME,
Expand Down Expand Up @@ -177,7 +178,7 @@ export function TokenRow({
)}
</div>
) : (
<div className="flex gap-1 items-center">
<div className="flex gap-1 items-start">
<Text variant="subtitleSmall" weight="semibold" color="gray-90">
{symbol}
</Text>
Expand All @@ -191,19 +192,19 @@ export function TokenRow({

<div className="ml-auto flex flex-col items-end gap-1">
{balance > 0n && (
<Text variant="body" color="gray-90" weight="medium">
<Text variant="body" color="gray-90" weight="medium" className="text-end">
{formatted} {symbol}
</Text>
)}

{balanceInUsd && balanceInUsd > 0 && (
{balanceInUsd && balanceInUsd > 0 ? (
<Text variant="subtitle" color="steel-dark" weight="medium">
{Number(balanceInUsd).toLocaleString('en', {
style: 'currency',
currency: 'USD',
})}
</Text>
)}
) : null}
</div>
</Tag>
);
Expand Down Expand Up @@ -429,6 +430,8 @@ function TokenDetails({ coinType }: TokenDetailsProps) {
>
<AccountsList />
<BuyNLargeHomePanel />
<UsdcPromoBanner />

<div className="flex flex-col w-full">
<PortfolioName
name={activeAccount.nickname ?? domainName ?? formatAddress(activeAccountAddress)}
Expand Down
48 changes: 48 additions & 0 deletions apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { Button } from '_app/shared/ButtonUI';
import { Heading } from '_app/shared/heading';
import PageTitle from '_app/shared/PageTitle';
import { Text } from '_app/shared/text';
import { useUsdcPromo } from '_pages/home/usdc-promo/useUsdcPromo';
import { USDC_TYPE_ARG } from '_pages/swap/utils';
import { useNavigate, useSearchParams } from 'react-router-dom';

export function UsdcPromo() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const fromCoinType = searchParams.get('type');
const presetAmount = searchParams.get('presetAmount');
const { promoBannerSheetTitle, promoBannerSheetContent } = useUsdcPromo();

return (
<div className="flex flex-col items-center gap-6">
<PageTitle back />
<img
src="https://fe-assets.mystenlabs.com/wallet_next/usdc_icon.png"
alt="USDC"
className="h-16 w-16"
/>
<div className="flex flex-col gap-2 text-center">
<Heading as="h1" variant="heading2" weight="semibold">
{promoBannerSheetTitle}
</Heading>
<Text variant="pBody" weight="medium" color="gray-90">
{promoBannerSheetContent}
</Text>
</div>
<Button
text="Swap wUSDC"
onClick={() => {
navigate(
`/swap?${new URLSearchParams({
type: fromCoinType || '',
toType: USDC_TYPE_ARG,
presetAmount: presetAmount || '',
})}`,
);
}}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useGetAllBalances } from '_app/hooks/useGetAllBalances';
import { useActiveAddress } from '_hooks';
import { useUsdcPromo } from '_pages/home/usdc-promo/useUsdcPromo';
import { useCoinMetadata } from '@mysten/core';
import { type CoinBalance } from '@mysten/sui/client';
import BigNumber from 'bignumber.js';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';

function BannerImage({ balance }: { balance: CoinBalance }) {
const navigate = useNavigate();
const { promoBannerImage } = useUsdcPromo();
const { data: metadata } = useCoinMetadata(balance.coinType);

const maxBalance = useMemo(() => {
const decimals = metadata?.decimals ?? 0;
return new BigNumber(balance?.totalBalance || 0)
.shiftedBy(-decimals)
.decimalPlaces(decimals)
.toString();
}, [balance, metadata]);

return (
<img
role="button"
className="w-full cursor-pointer"
alt="USDC Promo"
src={promoBannerImage}
onClick={() => {
navigate(
`/usdc-promo?${new URLSearchParams({
type: balance.coinType,
presetAmount: maxBalance,
})}`,
);
}}
/>
);
}

export function UsdcPromoBanner() {
const activeAccountAddress = useActiveAddress();
const { enabled, wrappedUsdcList } = useUsdcPromo();

const { data: coinBalances } = useGetAllBalances(activeAccountAddress || '');

const usdcInUsersBalance = coinBalances
? coinBalances.filter(
(coin) => wrappedUsdcList.includes(coin.coinType) && Number(coin.totalBalance) > 0,
)
: [];

const firstUsdcInUsersBalance = usdcInUsersBalance[0];

if (!enabled || !firstUsdcInUsersBalance) {
return null;
}

return <BannerImage balance={firstUsdcInUsersBalance} />;
}
25 changes: 25 additions & 0 deletions apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react';

type WalletUsdcPromo = {
promoBannerImage: string;
promoBannerSheetTitle: string;
promoBannerSheetContent: string;
wrappedUsdcList: string[];
};

export function useUsdcPromo() {
const enabled = useFeatureIsOn('wallet-usdc-promo-enabled');
const dynamicConfigs = useFeatureValue<WalletUsdcPromo>('wallet-usdc-promo', {
promoBannerImage: '',
promoBannerSheetTitle: '',
promoBannerSheetContent: '',
wrappedUsdcList: [],
});

return {
...dynamicConfigs,
enabled,
};
}
5 changes: 5 additions & 0 deletions apps/wallet/src/ui/app/pages/swap/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { type DeepBookClient } from '@mysten/deepbook';
import { type BalanceChange } from '@mysten/sui/client';
import BigNumber from 'bignumber.js';

export const W_USDC_TYPE_ARG =
'0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN';
export const USDC_TYPE_ARG =
'0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';

export function useSwapData({
baseCoinType,
quoteCoinType,
Expand Down
5 changes: 3 additions & 2 deletions apps/wallet/src/ui/app/shared/text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ const textStyles = cva([], {
export interface TextProps extends VariantProps<typeof textStyles> {
children: ReactNode;
title?: string;
className?: string;
}

export function Text({ children, title, ...styleProps }: TextProps) {
export function Text({ children, title, className, ...styleProps }: TextProps) {
return (
<div title={title} className={textStyles(styleProps)}>
<div title={title} className={textStyles({ ...styleProps, className })}>
{children}
</div>
);
Expand Down
Loading