mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-04-30 05:35:53 +08:00
[ENG-5004] [ENG-4667] [ENG-3736] [ENG-4654] [ENG-4655] Update the dashboard loader/styles (#612)
* [ENG-5004] Fix clunky loading of dashboard when logging in * Add animation between /login and /home routes * Update the dashboard title styles and indents * Move styles from the CoinDashboard screen to a separate file * Replace and remove the SmallActionButton component * Fix dashboard indents * Update some text style, remove some borders * Improve the dashboard loading UI * Stop hiding the bottombar when loading the explore screen * Remove the old prop use * Fix routing * Fix routing * Fix routing * Add comments for some routes * Improve the dashboard loader * Improve the dashboard loader * Improve the dashboard loader animation * Update the dashboard loader animation * Remove unused import * Add destructurization * Change the token tile skeleton height * Fix e2e tests * Add the auth guard conditions back * Add a try-catch for runes balance fetching, shorten the timeout for the balance e2e test * Update the error handling for hooks used on the Dashboard screen
This commit is contained in:
@@ -30,6 +30,14 @@ const StyledIcon = styled.div`
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const commonToastStyles = {
|
||||
...Theme.typography.body_medium_m,
|
||||
position: 'relative' as const,
|
||||
bottom: Theme.space.xxxxl,
|
||||
borderRadius: Theme.radius(2),
|
||||
padding: Theme.space.s,
|
||||
};
|
||||
|
||||
function App(): React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
@@ -46,7 +54,6 @@ function App(): React.ReactNode {
|
||||
<Toaster
|
||||
max={1}
|
||||
position="bottom-center"
|
||||
containerStyle={{ bottom: 32 }}
|
||||
toastOptions={{
|
||||
duration: 2000,
|
||||
success: {
|
||||
@@ -56,10 +63,8 @@ function App(): React.ReactNode {
|
||||
</StyledIcon>
|
||||
),
|
||||
style: {
|
||||
...Theme.typography.body_medium_m,
|
||||
...commonToastStyles,
|
||||
backgroundColor: Theme.colors.success_medium,
|
||||
borderRadius: Theme.radius(2),
|
||||
padding: Theme.space.s,
|
||||
color: Theme.colors.elevation0,
|
||||
},
|
||||
},
|
||||
@@ -70,19 +75,15 @@ function App(): React.ReactNode {
|
||||
</StyledIcon>
|
||||
),
|
||||
style: {
|
||||
...Theme.typography.body_medium_m,
|
||||
...commonToastStyles,
|
||||
backgroundColor: Theme.colors.danger_dark,
|
||||
borderRadius: Theme.radius(2),
|
||||
padding: Theme.space.s,
|
||||
color: Theme.colors.white_0,
|
||||
},
|
||||
},
|
||||
blank: {
|
||||
style: {
|
||||
...Theme.typography.body_medium_m,
|
||||
...commonToastStyles,
|
||||
backgroundColor: Theme.colors.white_0,
|
||||
borderRadius: Theme.radius(2),
|
||||
padding: Theme.space.s,
|
||||
color: Theme.colors.elevation0,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import AccountRow from '@components/accountRow';
|
||||
import useWalletReducer from '@hooks/useWalletReducer';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import OptionsDialog from '@components/optionsDialog/optionsDialog';
|
||||
@@ -10,14 +10,13 @@ import useSelectedAccount from '@hooks/useSelectedAccount';
|
||||
import { DotsThreeVertical } from '@phosphor-icons/react';
|
||||
import { OPTIONS_DIALOG_WIDTH } from '@utils/constants';
|
||||
|
||||
const SelectedAccountContainer = styled.div<{ $showBorderBottom?: boolean }>((props) => ({
|
||||
const SelectedAccountContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: `${props.theme.space.l} ${props.theme.space.m}`,
|
||||
borderBottom: props.$showBorderBottom ? `0.5px solid ${props.theme.colors.elevation3}` : 'none',
|
||||
}));
|
||||
|
||||
const OptionsButton = styled.button(() => ({
|
||||
@@ -58,15 +57,14 @@ const ButtonRow = styled.button`
|
||||
type Props = {
|
||||
disableMenuOption?: boolean;
|
||||
disableAccountSwitch?: boolean;
|
||||
showBorderBottom?: boolean;
|
||||
};
|
||||
|
||||
function AccountHeaderComponent({
|
||||
disableMenuOption = false,
|
||||
disableAccountSwitch = false,
|
||||
showBorderBottom = true,
|
||||
}: Props) {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const selectedAccount = useSelectedAccount();
|
||||
|
||||
const { t: optionsDialogTranslation } = useTranslation('translation', {
|
||||
@@ -80,7 +78,7 @@ function AccountHeaderComponent({
|
||||
|
||||
const handleAccountSelect = () => {
|
||||
if (!disableAccountSwitch) {
|
||||
navigate('/account-list');
|
||||
navigate('/account-list', { state: { from: pathname } });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -102,7 +100,7 @@ function AccountHeaderComponent({
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectedAccountContainer $showBorderBottom={showBorderBottom}>
|
||||
<SelectedAccountContainer>
|
||||
<AccountRow
|
||||
account={selectedAccount!}
|
||||
isSelected
|
||||
|
||||
33
src/app/components/animatedScreenContainer/index.tsx
Normal file
33
src/app/components/animatedScreenContainer/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import ScreenContainer from '@components/screenContainer';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { ANIMATION_EASING } from '@utils/constants';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
function AnimatedScreenContainer(): JSX.Element {
|
||||
const location = useLocation();
|
||||
const { state } = location;
|
||||
|
||||
const shouldAnimate = state?.from === '/login';
|
||||
|
||||
const styles = useSpring({
|
||||
from: {
|
||||
y: '125%',
|
||||
},
|
||||
to: {
|
||||
y: '0%',
|
||||
},
|
||||
delay: shouldAnimate ? 300 : 0,
|
||||
config: {
|
||||
duration: shouldAnimate ? 450 : 0,
|
||||
easing: ANIMATION_EASING,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<animated.div style={styles}>
|
||||
<ScreenContainer />
|
||||
</animated.div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnimatedScreenContainer;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LoaderSize } from '@utils/constants';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { ANIMATION_EASING, LoaderSize } from '@utils/constants';
|
||||
import ContentLoader from 'react-content-loader';
|
||||
import styled from 'styled-components';
|
||||
import Theme from 'theme';
|
||||
@@ -70,8 +71,9 @@ function BarLoader({ loaderSize }: { loaderSize?: LoaderSize }) {
|
||||
export default BarLoader;
|
||||
|
||||
const StyledContentLoader = styled(ContentLoader)`
|
||||
padding: ${(props) => props.theme.spacing(1)}px;
|
||||
padding: ${(props) => props.theme.space.xxxs}px;
|
||||
`;
|
||||
|
||||
export function BetterBarLoader({
|
||||
width,
|
||||
height,
|
||||
@@ -96,3 +98,29 @@ export function BetterBarLoader({
|
||||
</StyledContentLoader>
|
||||
);
|
||||
}
|
||||
|
||||
export function BestBarLoader({
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
}: {
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
className?: string;
|
||||
}) {
|
||||
const styles = useSpring({
|
||||
from: {
|
||||
backgroundColor: Theme.colors.white_850,
|
||||
},
|
||||
to: {
|
||||
backgroundColor: Theme.colors.white_800,
|
||||
},
|
||||
loop: { reverse: true },
|
||||
config: {
|
||||
duration: 400,
|
||||
easing: ANIMATION_EASING,
|
||||
},
|
||||
});
|
||||
|
||||
return <animated.div style={{ ...styles, width, height }} className={className} />;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function AmountWithInscriptionSatribute({
|
||||
/>
|
||||
</Range>
|
||||
{(inscriptionSatributes.length > 0 || inscriptions.length > index + 1) && (
|
||||
<Divider verticalMargin="s" />
|
||||
<Divider $verticalMargin="s" />
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
@@ -150,7 +150,7 @@ export default function AmountWithInscriptionSatribute({
|
||||
inscriptionSatributes={inscriptionSatributes}
|
||||
/>
|
||||
</Range>
|
||||
{satributesArray.length > 0 && <Divider verticalMargin="s" />}
|
||||
{satributesArray.length > 0 && <Divider $verticalMargin="s" />}
|
||||
</>
|
||||
)}
|
||||
{satributesArray.map(
|
||||
@@ -162,7 +162,7 @@ export default function AmountWithInscriptionSatribute({
|
||||
<Range>
|
||||
<RareSatRow item={item} />
|
||||
</Range>
|
||||
{satributesArray.length > index + 1 && <Divider verticalMargin="s" />}
|
||||
{satributesArray.length > index + 1 && <Divider $verticalMargin="s" />}
|
||||
</>
|
||||
),
|
||||
)}
|
||||
|
||||
@@ -131,12 +131,12 @@ function RareSats({
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<Range>
|
||||
<RareSatRow item={item} />
|
||||
{i < satributes.length - 1 && <Divider verticalMargin="s" />}
|
||||
{i < satributes.length - 1 && <Divider $verticalMargin="s" />}
|
||||
</Range>
|
||||
))}
|
||||
{bundleSize && totalExoticSats !== bundleSize && (
|
||||
<>
|
||||
<Divider verticalMargin="s" />
|
||||
<Divider $verticalMargin="s" />
|
||||
<Range>
|
||||
<RareSatRow
|
||||
item={{
|
||||
|
||||
@@ -3,7 +3,7 @@ import useWalletReducer from '@hooks/useWalletReducer';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import Spinner from '@ui-library/spinner';
|
||||
import { useEffect, type PropsWithChildren } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CenterChildContainer = styled.div`
|
||||
@@ -24,6 +24,7 @@ const isInitialised = {
|
||||
|
||||
function AuthGuard({ children }: PropsWithChildren) {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { encryptedSeed, isUnlocked, accountsList } = useWalletSelector();
|
||||
const { loadWallet, lockWallet } = useWalletReducer();
|
||||
const seedVault = useSeedVault();
|
||||
@@ -40,7 +41,7 @@ function AuthGuard({ children }: PropsWithChildren) {
|
||||
if (encryptedSeed) {
|
||||
// this is a legacy seed store. If it exists, we need to migrate
|
||||
// it to the new seed vault which happens on login
|
||||
navigate('/login');
|
||||
navigate('/login', { state: { from: pathname } });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ function AuthGuard({ children }: PropsWithChildren) {
|
||||
try {
|
||||
await seedVault.getSeed();
|
||||
} catch (error) {
|
||||
navigate('/login');
|
||||
navigate('/login', { state: { from: pathname } });
|
||||
}
|
||||
|
||||
await loadWallet(() => {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface ButtonProps {
|
||||
isOpaque?: boolean;
|
||||
}
|
||||
const Button = styled.div<ButtonProps>((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 16,
|
||||
backgroundColor: props.isOpaque
|
||||
? props.theme.colors.elevation2
|
||||
: props.theme.colors.action.classic,
|
||||
width: 48,
|
||||
height: 48,
|
||||
transition: 'background-color 0.2s ease, opacity 0.2s ease',
|
||||
':hover': {
|
||||
backgroundColor: props.isOpaque
|
||||
? props.theme.colors.elevation2
|
||||
: props.theme.colors.action.classicLight,
|
||||
opacity: props.isOpaque ? 0.85 : 0.6,
|
||||
},
|
||||
}));
|
||||
|
||||
const TransparentButton = styled(Button)`
|
||||
background-color: transparent;
|
||||
border: ${(props) => `1px solid ${props.theme.colors.elevation6}`};
|
||||
`;
|
||||
|
||||
const AnimatedTransparentButton = styled(TransparentButton)`
|
||||
:hover {
|
||||
background: ${(props) => props.theme.colors.elevation6_800};
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonText = styled.h1((props) => ({
|
||||
...props.theme.body_xs,
|
||||
fontWeight: 700,
|
||||
marginTop: props.theme.spacing(4),
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
}));
|
||||
|
||||
const ButtonImage = styled.img({
|
||||
alignSelf: 'center',
|
||||
transform: 'all',
|
||||
transition: 'all 0.2s ease',
|
||||
});
|
||||
|
||||
interface ButtonContainerProps {
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
const ButtonContainer = styled.button<ButtonContainerProps>((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 0,
|
||||
background: 'transparent',
|
||||
opacity: props.isDisabled ? 0.3 : 1,
|
||||
}));
|
||||
|
||||
interface Props {
|
||||
src?: string;
|
||||
text: string;
|
||||
onPress: () => void;
|
||||
isOpaque?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
function SmallActionButton({ src, text, onPress, isOpaque, isDisabled }: Props) {
|
||||
const handleOnPress = () => {
|
||||
if (!isDisabled) onPress();
|
||||
};
|
||||
|
||||
return (
|
||||
<ButtonContainer data-testid="action-button" isDisabled={isDisabled} onClick={handleOnPress}>
|
||||
<Button isOpaque={isOpaque}>{src && <ButtonImage src={src} />}</Button>
|
||||
<ButtonText>{text}</ButtonText>
|
||||
</ButtonContainer>
|
||||
);
|
||||
}
|
||||
export default SmallActionButton;
|
||||
@@ -40,7 +40,8 @@ const Wrapper = styled.div<{ $isTransparent?: boolean; $size?: number }>((props)
|
||||
|
||||
const Title = styled.span((props) => ({
|
||||
...props.theme.typography.body_medium_s,
|
||||
color: props.theme.colors.white_0,
|
||||
lineHeight: '140%',
|
||||
color: props.theme.colors.white_200,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ const RowContainer = styled.div((props) => ({
|
||||
justifyContent: 'space-between',
|
||||
paddingLeft: props.theme.space.xl,
|
||||
paddingRight: props.theme.space.xl,
|
||||
borderTop: `1px solid ${props.theme.colors.elevation3}`,
|
||||
}));
|
||||
|
||||
const MovingDiv = styled(animated.div)((props) => ({
|
||||
|
||||
@@ -2,12 +2,12 @@ import { BetterBarLoader } from '@components/barLoader';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const TilesLoaderContainer = styled.div<{
|
||||
isGalleryOpen?: boolean;
|
||||
$isGalleryOpen?: boolean;
|
||||
}>((props) => ({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
columnGap: props.isGalleryOpen ? props.theme.spacing(16) : props.theme.spacing(8),
|
||||
columnGap: props.$isGalleryOpen ? props.theme.space.xl : props.theme.space.m,
|
||||
}));
|
||||
|
||||
const TileLoaderContainer = styled.div({
|
||||
@@ -16,12 +16,12 @@ const TileLoaderContainer = styled.div({
|
||||
});
|
||||
|
||||
export const StyledBarLoader = styled(BetterBarLoader)<{
|
||||
withMarginBottom?: boolean;
|
||||
isGalleryOpen?: boolean;
|
||||
$withMarginBottom?: boolean;
|
||||
$isGalleryOpen?: boolean;
|
||||
}>((props) => ({
|
||||
padding: 0,
|
||||
borderRadius: props.theme.radius(props.isGalleryOpen ? 3 : 1),
|
||||
marginBottom: props.withMarginBottom ? props.theme.spacing(6) : 0,
|
||||
borderRadius: props.theme.radius(props.$isGalleryOpen ? 3 : 1),
|
||||
marginBottom: props.$withMarginBottom ? props.theme.space.s : 0,
|
||||
}));
|
||||
|
||||
export function TilesSkeletonLoader({
|
||||
@@ -34,23 +34,13 @@ export function TilesSkeletonLoader({
|
||||
isGalleryOpen?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<TilesLoaderContainer className={className} isGalleryOpen={isGalleryOpen}>
|
||||
<TilesLoaderContainer className={className} $isGalleryOpen={isGalleryOpen}>
|
||||
<TileLoaderContainer>
|
||||
<StyledBarLoader
|
||||
width={tileSize}
|
||||
height={tileSize}
|
||||
isGalleryOpen={isGalleryOpen}
|
||||
withMarginBottom
|
||||
/>
|
||||
<StyledBarLoader width={tileSize} height={tileSize} $withMarginBottom />
|
||||
<StyledBarLoader width={107} height={14} />
|
||||
</TileLoaderContainer>
|
||||
<TileLoaderContainer>
|
||||
<StyledBarLoader
|
||||
width={tileSize}
|
||||
height={tileSize}
|
||||
isGalleryOpen={isGalleryOpen}
|
||||
withMarginBottom
|
||||
/>
|
||||
<StyledBarLoader width={tileSize} height={tileSize} $withMarginBottom />
|
||||
<StyledBarLoader width={107} height={14} />
|
||||
</TileLoaderContainer>
|
||||
</TilesLoaderContainer>
|
||||
|
||||
@@ -2,7 +2,7 @@ import IconBitcoin from '@assets/img/dashboard/bitcoin_icon.svg';
|
||||
import IconStacks from '@assets/img/dashboard/stx_icon.svg';
|
||||
import OrdinalIcon from '@assets/img/transactions/ordinal.svg';
|
||||
import RunesIcon from '@assets/img/transactions/runes.svg';
|
||||
import { StyledBarLoader } from '@components/tilesSkeletonLoader';
|
||||
import { StyledBarLoader } from '@components/tokenTile/loader';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import type { FungibleToken } from '@secretkeylabs/xverse-core';
|
||||
import { XVERSE_ORDIVIEW_URL, type CurrencyTypes } from '@utils/constants';
|
||||
@@ -12,9 +12,9 @@ import styled from 'styled-components';
|
||||
|
||||
const DEFAULT_SIZE = 40;
|
||||
|
||||
const TickerImage = styled.img<{ size?: number }>((props) => ({
|
||||
height: props.size ?? DEFAULT_SIZE,
|
||||
width: props.size ?? DEFAULT_SIZE,
|
||||
const TickerImage = styled.img<{ $size?: number }>((props) => ({
|
||||
height: props.$size ?? DEFAULT_SIZE,
|
||||
width: props.$size ?? DEFAULT_SIZE,
|
||||
borderRadius: '50%',
|
||||
}));
|
||||
|
||||
@@ -24,12 +24,12 @@ const LoaderImageContainer = styled.div({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
const TickerIconContainer = styled.div<{ size?: number; round?: boolean }>((props) => ({
|
||||
const TickerIconContainer = styled.div<{ $size?: number; $round?: boolean }>((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: props.size ?? DEFAULT_SIZE,
|
||||
width: props.size ?? DEFAULT_SIZE,
|
||||
height: props.$size ?? DEFAULT_SIZE,
|
||||
width: props.$size ?? DEFAULT_SIZE,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: props.theme.colors.white_850,
|
||||
}));
|
||||
@@ -48,12 +48,12 @@ const TickerProtocolContainer = styled.div`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
const ProtocolIcon = styled.div<{ isSquare?: boolean }>((props) => ({
|
||||
width: props.isSquare ? 18 : 20,
|
||||
height: props.isSquare ? 18 : 20,
|
||||
borderRadius: props.isSquare ? 0 : 20,
|
||||
const ProtocolIcon = styled.div<{ $isSquare?: boolean }>((props) => ({
|
||||
width: props.$isSquare ? 18 : 20,
|
||||
height: props.$isSquare ? 18 : 20,
|
||||
borderRadius: props.$isSquare ? 0 : 20,
|
||||
position: 'absolute',
|
||||
right: props.isSquare ? -9 : -10,
|
||||
right: props.$isSquare ? -9 : -10,
|
||||
bottom: -2,
|
||||
backgroundColor: props.theme.colors.elevation0,
|
||||
padding: 2,
|
||||
@@ -64,7 +64,7 @@ const ProtocolImage = styled.img({
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
interface TokenImageProps {
|
||||
type Props = {
|
||||
currency?: CurrencyTypes;
|
||||
fungibleToken?: FungibleToken;
|
||||
loading?: boolean;
|
||||
@@ -72,7 +72,7 @@ interface TokenImageProps {
|
||||
round?: boolean;
|
||||
showProtocolIcon?: boolean;
|
||||
customProtocolIcon?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function TokenImage({
|
||||
currency,
|
||||
@@ -82,7 +82,7 @@ export default function TokenImage({
|
||||
round,
|
||||
showProtocolIcon = true,
|
||||
customProtocolIcon,
|
||||
}: TokenImageProps) {
|
||||
}: Props) {
|
||||
const { network } = useWalletSelector();
|
||||
const ftProtocol = fungibleToken?.protocol;
|
||||
const [imageError, setImageError] = useState(false);
|
||||
@@ -101,7 +101,7 @@ export default function TokenImage({
|
||||
(fungibleToken?.name ? getTicker(fungibleToken.name) : fungibleToken?.assetName || '');
|
||||
|
||||
const tickerComponent = () => (
|
||||
<TickerIconContainer size={size} round={round}>
|
||||
<TickerIconContainer $size={size} $round={round}>
|
||||
<TickerIconText data-testid="token-image">{ticker.substring(0, 4)}</TickerIconText>
|
||||
</TickerIconContainer>
|
||||
);
|
||||
@@ -129,18 +129,19 @@ export default function TokenImage({
|
||||
return (
|
||||
<TickerImage
|
||||
data-testid="token-image"
|
||||
size={size}
|
||||
$size={size}
|
||||
src={getCurrencyIcon()}
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (fungibleToken.protocol === 'runes') {
|
||||
if (fungibleToken.runeInscriptionId) {
|
||||
return (
|
||||
<TickerImage
|
||||
data-testid="token-image"
|
||||
size={size}
|
||||
$size={size}
|
||||
src={`${XVERSE_ORDIVIEW_URL(network.type)}/thumbnail/${
|
||||
fungibleToken.runeInscriptionId
|
||||
}`}
|
||||
@@ -148,24 +149,27 @@ export default function TokenImage({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (fungibleToken?.runeSymbol) {
|
||||
return (
|
||||
<TickerIconContainer size={size} round={round}>
|
||||
<TickerIconContainer $size={size} $round={round}>
|
||||
<TickerIconText data-testid="token-image">{fungibleToken.runeSymbol}</TickerIconText>
|
||||
</TickerIconContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (fungibleToken?.image) {
|
||||
return (
|
||||
<TickerImage
|
||||
data-testid="token-image"
|
||||
size={size}
|
||||
$size={size}
|
||||
src={fungibleToken.image}
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return tickerComponent();
|
||||
};
|
||||
|
||||
@@ -173,7 +177,11 @@ export default function TokenImage({
|
||||
return (
|
||||
<TickerProtocolContainer>
|
||||
<LoaderImageContainer>
|
||||
<StyledBarLoader width={size ?? DEFAULT_SIZE} height={size ?? DEFAULT_SIZE} />
|
||||
<StyledBarLoader
|
||||
width={size ?? DEFAULT_SIZE}
|
||||
height={size ?? DEFAULT_SIZE}
|
||||
$borderRadius={100}
|
||||
/>
|
||||
</LoaderImageContainer>
|
||||
</TickerProtocolContainer>
|
||||
);
|
||||
@@ -183,7 +191,7 @@ export default function TokenImage({
|
||||
<TickerProtocolContainer>
|
||||
{imageError ? tickerComponent() : renderIcon()}
|
||||
{showProtocolIcon && protocolIcon && (
|
||||
<ProtocolIcon isSquare={ftProtocol === 'runes'}>{protocolIcon}</ProtocolIcon>
|
||||
<ProtocolIcon $isSquare={ftProtocol === 'runes'}>{protocolIcon}</ProtocolIcon>
|
||||
)}
|
||||
</TickerProtocolContainer>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BetterBarLoader } from '@components/barLoader';
|
||||
import { StyledFiatAmountText } from '@components/fiatAmountText';
|
||||
import { BestBarLoader } from '@components/barLoader';
|
||||
import FiatAmountText from '@components/fiatAmountText';
|
||||
import TokenImage from '@components/tokenImage';
|
||||
import useSelectedAccountBtcBalance from '@hooks/queries/useSelectedAccountBtcBalance';
|
||||
import useStxWalletData from '@hooks/queries/useStxWalletData';
|
||||
@@ -22,16 +22,17 @@ const TileContainer = styled.button((props) => ({
|
||||
borderRadius: props.theme.radius(2),
|
||||
}));
|
||||
|
||||
const RowContainer = styled.div({
|
||||
const RowContainer = styled.div((props) => ({
|
||||
flex: '1 0 auto',
|
||||
display: 'flex',
|
||||
});
|
||||
columnGap: props.theme.space.m,
|
||||
}));
|
||||
|
||||
const TextContainer = styled.div((props) => ({
|
||||
marginLeft: props.theme.space.m,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
rowGap: props.theme.space.xxxs,
|
||||
}));
|
||||
|
||||
const AmountContainer = styled.div((props) => ({
|
||||
@@ -40,6 +41,7 @@ const AmountContainer = styled.div((props) => ({
|
||||
marginLeft: props.theme.space.xxs,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-end',
|
||||
rowGap: props.theme.space.xxxs,
|
||||
}));
|
||||
|
||||
const LoaderMainContainer = styled.div({
|
||||
@@ -52,23 +54,29 @@ const LoaderMainContainer = styled.div({
|
||||
const CoinTickerText = styled.p((props) => ({
|
||||
...props.theme.typography.body_bold_m,
|
||||
color: props.theme.colors.white_0,
|
||||
lineHeight: '140%',
|
||||
minHeight: 20,
|
||||
}));
|
||||
|
||||
const SubText = styled.p<{ fullWidth: boolean }>((props) => ({
|
||||
...props.theme.typography.body_medium_s,
|
||||
const SubText = styled.p<{ $fullWidth: boolean }>((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
color: props.theme.colors.white_200,
|
||||
minHeight: 20,
|
||||
lineHeight: '140%',
|
||||
textAlign: 'left',
|
||||
maxWidth: props.fullWidth ? 'unset' : 120,
|
||||
whiteSpace: props.fullWidth ? 'normal' : 'nowrap',
|
||||
maxWidth: props.$fullWidth ? 'unset' : 120,
|
||||
whiteSpace: props.$fullWidth ? 'normal' : 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}));
|
||||
|
||||
const CoinBalanceText = styled.p((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
...props.theme.typography.body_bold_m,
|
||||
color: props.theme.colors.white_0,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
minHeight: 20,
|
||||
lineHeight: '140%',
|
||||
maxWidth: '100%',
|
||||
}));
|
||||
|
||||
@@ -79,14 +87,19 @@ const TokenTitleContainer = styled.div({
|
||||
justifyContent: 'flex-start',
|
||||
});
|
||||
|
||||
const StyledBarLoader = styled(BetterBarLoader)<{
|
||||
withMarginBottom?: boolean;
|
||||
const StyledBarLoader = styled(BestBarLoader)<{
|
||||
$withMarginBottom?: boolean;
|
||||
}>((props) => ({
|
||||
padding: 0,
|
||||
borderRadius: props.theme.radius(1),
|
||||
marginBottom: props.withMarginBottom ? props.theme.space.xxs : 0,
|
||||
marginBottom: props.$withMarginBottom ? props.theme.space.xxxs : 0,
|
||||
}));
|
||||
|
||||
const StyledFiatAmountText = styled(FiatAmountText)`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
line-height: 140%;
|
||||
min-height: 20px;
|
||||
`;
|
||||
|
||||
const CoinBalanceContainer = styled.div`
|
||||
${(props) => props.theme.typography.body_medium_m}
|
||||
color: ${(props) => props.theme.colors.white_0};
|
||||
@@ -100,8 +113,8 @@ const FiatAmountContainer = styled.div`
|
||||
function TokenLoader() {
|
||||
return (
|
||||
<LoaderMainContainer>
|
||||
<StyledBarLoader width={80} height={16} withMarginBottom />
|
||||
<StyledBarLoader width={70} height={14} />
|
||||
<StyledBarLoader width={53} height={20} $withMarginBottom />
|
||||
<StyledBarLoader width={151} height={20} />
|
||||
</LoaderMainContainer>
|
||||
);
|
||||
}
|
||||
@@ -168,7 +181,7 @@ function TokenTile({
|
||||
<TextContainer>
|
||||
<CoinTickerText>{getTickerTitle()}</CoinTickerText>
|
||||
<TokenTitleContainer>
|
||||
<SubText aria-label="Token SubTitle" fullWidth={hideSwapBalance}>
|
||||
<SubText aria-label="Token SubTitle" $fullWidth={hideSwapBalance}>
|
||||
{title}
|
||||
</SubText>
|
||||
</TokenTitleContainer>
|
||||
|
||||
62
src/app/components/tokenTile/loader.tsx
Normal file
62
src/app/components/tokenTile/loader.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { BestBarLoader } from '@components/barLoader';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const LoaderContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${(props) => props.theme.space.s};
|
||||
padding: ${(props) => props.theme.space.m} 0;
|
||||
`;
|
||||
|
||||
export const StyledBarLoader = styled(BestBarLoader)<{
|
||||
$withMarginBottom?: boolean;
|
||||
$borderRadius?: number;
|
||||
}>((props) => ({
|
||||
borderRadius: props.$borderRadius,
|
||||
marginBottom: props.$withMarginBottom ? props.theme.space.s : 0,
|
||||
}));
|
||||
|
||||
const IconContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const LeftColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${(props) => props.theme.space.xxxs};
|
||||
`;
|
||||
|
||||
const RightColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: ${(props) => props.theme.space.xxxs};
|
||||
`;
|
||||
|
||||
function TokenTileLoader() {
|
||||
return (
|
||||
<LoaderContainer>
|
||||
<IconContainer>
|
||||
<StyledBarLoader width={32} height={32} $borderRadius={100} />
|
||||
</IconContainer>
|
||||
<ContentContainer>
|
||||
<LeftColumn>
|
||||
<StyledBarLoader width={56} height={20} />
|
||||
<StyledBarLoader width={70} height={20} />
|
||||
</LeftColumn>
|
||||
<RightColumn>
|
||||
<StyledBarLoader width={53} height={20} />
|
||||
<StyledBarLoader width={151} height={20} />
|
||||
</RightColumn>
|
||||
</ContentContainer>
|
||||
</LoaderContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default TokenTileLoader;
|
||||
@@ -80,6 +80,7 @@ export const useGetBrc20FungibleTokens = (select?: (data: FungibleTokenWithState
|
||||
queryKey: ['brc20-fungible-tokens', ordinalsAddress, network.type, fiatCurrency],
|
||||
queryFn,
|
||||
enabled: Boolean(network && ordinalsAddress),
|
||||
keepPreviousData: true,
|
||||
select: selectWithDerivedState,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,7 +15,10 @@ export const fetchRuneBalances =
|
||||
fiatCurrency: string,
|
||||
): (() => Promise<FungibleToken[]>) =>
|
||||
async () => {
|
||||
const runeBalances = await runesApi.getRuneFungibleTokens(ordinalsAddress, true);
|
||||
const runeBalances: FungibleToken[] = await runesApi.getRuneFungibleTokens(
|
||||
ordinalsAddress,
|
||||
true,
|
||||
);
|
||||
|
||||
if (!Array.isArray(runeBalances) || runeBalances.length === 0) {
|
||||
return [];
|
||||
@@ -66,6 +69,7 @@ export const useRuneFungibleTokensQuery = (
|
||||
select: selectWithDerivedState,
|
||||
refetchOnWindowFocus: !!backgroundRefetch,
|
||||
refetchOnReconnect: !!backgroundRefetch,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export const useGetSip10FungibleTokens = (select?: (data: FungibleTokenWithState
|
||||
queryKey: ['sip10-fungible-tokens', network.type, stxAddress, fiatCurrency],
|
||||
queryFn,
|
||||
enabled: Boolean(network && stxAddress),
|
||||
keepPreviousData: true,
|
||||
select: selectWithDerivedState,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ const useBtcWalletData = () => {
|
||||
queryFn: fetchBtcWalletData,
|
||||
enabled: !!btcAddress,
|
||||
staleTime: 10 * 1000, // 10 secs
|
||||
keepPreviousData: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -8,16 +8,22 @@ export default function useSelectedAccountBtcBalance() {
|
||||
data: nativeBalance,
|
||||
isLoading: nativeLoading,
|
||||
isRefetching: nativeRefetching,
|
||||
failureCount: nativeFailureCount,
|
||||
errorUpdateCount: nativeErrorUpdateCount,
|
||||
} = useBtcAddressBalance(selectedAccount.btcAddresses.native?.address ?? '');
|
||||
const {
|
||||
data: nestedBalance,
|
||||
isLoading: nestedLoading,
|
||||
isRefetching: nestedRefetching,
|
||||
failureCount: nestedFailureCount,
|
||||
errorUpdateCount: nestedErrorUpdateCount,
|
||||
} = useBtcAddressBalance(selectedAccount.btcAddresses.nested?.address ?? '');
|
||||
const {
|
||||
data: taprootBalance,
|
||||
isLoading: taprootLoading,
|
||||
isRefetching: taprootRefetching,
|
||||
failureCount: taprootFailureCount,
|
||||
errorUpdateCount: taprootErrorUpdateCount,
|
||||
} = useBtcAddressBalance(selectedAccount.btcAddresses.taproot.address ?? '');
|
||||
|
||||
if (nativeLoading || nestedLoading || taprootLoading) {
|
||||
@@ -42,5 +48,7 @@ export default function useSelectedAccountBtcBalance() {
|
||||
taprootBalance,
|
||||
isLoading: false,
|
||||
isRefetching: nativeRefetching || nestedRefetching || taprootRefetching,
|
||||
failureCount: nativeFailureCount || nestedFailureCount || taprootFailureCount,
|
||||
errorUpdateCount: nativeErrorUpdateCount || nestedErrorUpdateCount || taprootErrorUpdateCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const useStxWalletData = () => {
|
||||
queryFn: fetchStxWalletData,
|
||||
enabled: !!stxAddress,
|
||||
staleTime: 10 * 1000, // 10 secs
|
||||
keepPreviousData: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -25,5 +25,6 @@ export default function useBtcAddressBalance(address: string) {
|
||||
queryKey: ['btc-address-balance', address],
|
||||
queryFn: fetchBalance,
|
||||
staleTime: 10 * 1000, // 10 secs
|
||||
keepPreviousData: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import RequestsRoutes from '@common/utils/route-urls';
|
||||
import AnimatedScreenContainer from '@components/animatedScreenContainer';
|
||||
import ExtendedScreenContainer from '@components/extendedScreenContainer';
|
||||
import AuthGuard from '@components/guards/auth';
|
||||
import OnboardingGuard from '@components/guards/onboarding';
|
||||
@@ -92,6 +93,21 @@ import { createHashRouter } from 'react-router-dom';
|
||||
import RoutePaths from './paths';
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <AnimatedScreenContainer />,
|
||||
errorElement: <ErrorBoundary />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<Home />
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
element: <ScreenContainer />,
|
||||
@@ -121,14 +137,6 @@ const router = createHashRouter([
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
index: true,
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<Home />
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'legal',
|
||||
element: (
|
||||
@@ -286,7 +294,7 @@ const router = createHashRouter([
|
||||
element: <SpeedUpTransactionScreen />,
|
||||
},
|
||||
{
|
||||
path: 'create-inscription',
|
||||
path: 'create-inscription', // used by our legacy style inscriptions methods for the inscription service
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<CreateInscription />
|
||||
@@ -294,7 +302,7 @@ const router = createHashRouter([
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'create-repeat-inscriptions',
|
||||
path: 'create-repeat-inscriptions', // used by our legacy style inscriptions methods for the inscription service
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<CreateInscription />
|
||||
|
||||
@@ -57,7 +57,7 @@ const Title = styled.div((props) => ({
|
||||
function AccountList(): JSX.Element {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'ACCOUNT_SCREEN' });
|
||||
const navigate = useNavigate();
|
||||
const { search } = useLocation();
|
||||
const { search, state } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const { network, accountsList, ledgerAccountsList } = useWalletSelector();
|
||||
@@ -72,7 +72,7 @@ function AccountList(): JSX.Element {
|
||||
}, [accountsList, ledgerAccountsList, network]);
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
navigate(-1);
|
||||
navigate(state?.from || -1);
|
||||
};
|
||||
|
||||
const handleAccountSelect = async (account: Account, goBack = true) => {
|
||||
|
||||
@@ -3,8 +3,8 @@ import styled from 'styled-components';
|
||||
export const Container = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingLeft: props.theme.spacing(8),
|
||||
paddingRight: props.theme.spacing(8),
|
||||
paddingLeft: props.theme.space.m,
|
||||
paddingRight: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const RowContainer = styled.div({
|
||||
@@ -20,7 +20,7 @@ export const ProtocolText = styled.p((props) => ({
|
||||
height: 15,
|
||||
marginTop: props.theme.spacing(3),
|
||||
textTransform: 'uppercase',
|
||||
marginLeft: props.theme.spacing(2),
|
||||
marginLeft: props.theme.space.xxs,
|
||||
backgroundColor: props.theme.colors.white_400,
|
||||
padding: '1px 6px 1px',
|
||||
color: props.theme.colors.elevation0,
|
||||
@@ -51,7 +51,7 @@ export const FiatAmountText = styled.p((props) => ({
|
||||
...props.theme.headline_category_s,
|
||||
color: props.theme.colors.white_200,
|
||||
fontSize: '0.875rem',
|
||||
marginTop: props.theme.spacing(2),
|
||||
marginTop: props.theme.space.xxs,
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
}));
|
||||
@@ -60,14 +60,14 @@ export const BalanceTitleText = styled.p((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
color: props.theme.colors.white_400,
|
||||
textAlign: 'center',
|
||||
marginTop: props.theme.spacing(4),
|
||||
marginTop: props.theme.space.xs,
|
||||
}));
|
||||
|
||||
export const RowButtonContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: props.theme.spacing(11),
|
||||
marginTop: props.theme.space.l,
|
||||
columnGap: props.theme.space.l,
|
||||
}));
|
||||
|
||||
@@ -75,8 +75,8 @@ export const HeaderSeparator = styled.div((props) => ({
|
||||
border: `0.5px solid ${props.theme.colors.white_400}`,
|
||||
width: '50%',
|
||||
alignSelf: 'center',
|
||||
marginTop: props.theme.spacing(8),
|
||||
marginBottom: props.theme.spacing(8),
|
||||
marginTop: props.theme.space.m,
|
||||
marginBottom: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const StxLockedText = styled.p((props) => ({
|
||||
@@ -100,7 +100,7 @@ export const AvailableStxContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: props.theme.spacing(4),
|
||||
marginTop: props.theme.space.xs,
|
||||
span: {
|
||||
color: props.theme.colors.white_400,
|
||||
marginRight: props.theme.spacing(3),
|
||||
@@ -108,13 +108,13 @@ export const AvailableStxContainer = styled.div((props) => ({
|
||||
}));
|
||||
|
||||
export const VerifyOrViewContainer = styled.div((props) => ({
|
||||
margin: props.theme.spacing(8),
|
||||
marginTop: props.theme.spacing(16),
|
||||
marginBottom: props.theme.spacing(20),
|
||||
margin: props.theme.space.m,
|
||||
marginTop: props.theme.space.xl,
|
||||
marginBottom: props.theme.space.xxl,
|
||||
}));
|
||||
|
||||
export const VerifyButtonContainer = styled.div((props) => ({
|
||||
marginBottom: props.theme.spacing(6),
|
||||
marginBottom: props.theme.space.s,
|
||||
}));
|
||||
|
||||
export const StacksLockedInfoText = styled.span((props) => ({
|
||||
|
||||
@@ -6,7 +6,7 @@ import ArrowSwap from '@assets/img/icons/ArrowSwap.svg';
|
||||
import Lock from '@assets/img/transactions/Lock.svg';
|
||||
import BottomModal from '@components/bottomModal';
|
||||
import ActionButton from '@components/button';
|
||||
import SmallActionButton from '@components/smallActionButton';
|
||||
import SquareButton from '@components/squareButton';
|
||||
import TokenImage from '@components/tokenImage';
|
||||
import useSelectedAccountBtcBalance from '@hooks/queries/useSelectedAccountBtcBalance';
|
||||
import useStxWalletData from '@hooks/queries/useStxWalletData';
|
||||
@@ -283,24 +283,18 @@ export default function CoinHeader({ currency, fungibleToken }: Props) {
|
||||
</BalanceInfoContainer>
|
||||
{renderStackingBalances()}
|
||||
<RowButtonContainer>
|
||||
<SmallActionButton src={ArrowUp} text={t('SEND')} onPress={goToSendScreen} />
|
||||
<SmallActionButton src={ArrowDown} text={t('RECEIVE')} onPress={navigateToReceive} />
|
||||
{showSwaps && (
|
||||
<SmallActionButton src={ArrowSwap} text={t('SWAP')} onPress={navigateToSwaps} />
|
||||
)}
|
||||
<SquareButton src={ArrowUp} text={t('SEND')} onPress={goToSendScreen} />
|
||||
<SquareButton src={ArrowDown} text={t('RECEIVE')} onPress={navigateToReceive} />
|
||||
{showSwaps && <SquareButton src={ArrowSwap} text={t('SWAP')} onPress={navigateToSwaps} />}
|
||||
{showRunesListing && (
|
||||
<SmallActionButton
|
||||
<SquareButton
|
||||
src={List}
|
||||
text={t('LIST')}
|
||||
onPress={() => navigate(`/list-rune/${fungibleToken.principal}`)}
|
||||
/>
|
||||
)}
|
||||
{!fungibleToken && (
|
||||
<SmallActionButton
|
||||
src={Buy}
|
||||
text={t('BUY')}
|
||||
onPress={() => navigate(`/buy/${currency}`)}
|
||||
/>
|
||||
<SquareButton src={Buy} text={t('BUY')} onPress={() => navigate(`/buy/${currency}`)} />
|
||||
)}
|
||||
</RowButtonContainer>
|
||||
<BottomModal
|
||||
|
||||
@@ -105,7 +105,7 @@ function AuthenticationRequest() {
|
||||
const [isTxRejected, setIsTxRejected] = useState(false);
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'AUTH_REQUEST_SCREEN' });
|
||||
const navigate = useNavigate();
|
||||
const { search } = useLocation();
|
||||
const { search, pathname } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const authRequestToken = params.get('authRequest') ?? '';
|
||||
const authRequest = decodeToken(authRequestToken) as unknown as AuthRequest;
|
||||
@@ -267,7 +267,7 @@ function AuthenticationRequest() {
|
||||
};
|
||||
|
||||
const handleSwitchAccount = () => {
|
||||
navigate('/account-list?hideListActions=true');
|
||||
navigate('/account-list?hideListActions=true', { state: { from: pathname } });
|
||||
};
|
||||
|
||||
const handleAddStxLedgerAccount = async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import Spinner from '@ui-library/spinner';
|
||||
import { trackMixPanel } from '@utils/mixpanel';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import AddressPurposeBox from '../addressPurposeBox';
|
||||
import PermissionsList from '../permissionsList';
|
||||
import {
|
||||
@@ -33,6 +33,7 @@ import useBtcAddressRequest from './useBtcAddressRequest';
|
||||
function BtcSelectAddressScreen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'SELECT_BTC_ADDRESS_SCREEN' });
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const { network } = useWalletSelector();
|
||||
@@ -165,7 +166,7 @@ function BtcSelectAddressScreen() {
|
||||
);
|
||||
|
||||
const handleSwitchAccount = () => {
|
||||
navigate('/account-list?hideListActions=true');
|
||||
navigate('/account-list?hideListActions=true', { state: { from: pathname } });
|
||||
};
|
||||
|
||||
if (isLoadingIcon) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StickyHorizontalSplitButtonContainer } from '@ui-library/common.styled'
|
||||
import Spinner from '@ui-library/spinner';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import AddressPurposeBox from '../addressPurposeBox';
|
||||
import {
|
||||
AddressBoxContainer,
|
||||
@@ -29,6 +29,7 @@ import PermissionsList from '../permissionsList';
|
||||
function StxSelectAccountScreen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'SELECT_BTC_ADDRESS_SCREEN' });
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const { network } = useWalletSelector();
|
||||
@@ -85,7 +86,7 @@ function StxSelectAccountScreen() {
|
||||
}, [origin]);
|
||||
|
||||
const handleSwitchAccount = () => {
|
||||
navigate('/account-list?hideListActions=true');
|
||||
navigate('/account-list?hideListActions=true', { state: { from: pathname } });
|
||||
};
|
||||
|
||||
if (isLoadingIcon) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StickyHorizontalSplitButtonContainer } from '@ui-library/common.styled'
|
||||
import Spinner from '@ui-library/spinner';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import AddressPurposeBox from '../addressPurposeBox';
|
||||
import {
|
||||
AddressBoxContainer,
|
||||
@@ -29,10 +29,11 @@ import PermissionsList from '../permissionsList';
|
||||
function StxSelectAddressScreen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'SELECT_BTC_ADDRESS_SCREEN' });
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const { network } = useWalletSelector();
|
||||
const [appIcon, setAppIcon] = useState<string>('');
|
||||
const [appIcon, setAppIcon] = useState('');
|
||||
const [isLoadingIcon, setIsLoadingIcon] = useState(false);
|
||||
const { payload, origin, approveStxAddressRequest, cancelAddressRequest } =
|
||||
useStxAddressRequest();
|
||||
@@ -85,7 +86,7 @@ function StxSelectAddressScreen() {
|
||||
}, [origin]);
|
||||
|
||||
const handleSwitchAccount = () => {
|
||||
navigate('/account-list?hideListActions=true');
|
||||
navigate('/account-list?hideListActions=true', { state: { from: pathname } });
|
||||
};
|
||||
|
||||
if (isLoadingIcon) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import useSelectedAccount from '@hooks/useSelectedAccount';
|
||||
import SelectAccount from '@screens/connect/selectAccount';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export function SelectAccountPrompt() {
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const handleSwitchAccount = () => {
|
||||
navigate('/account-list?hideListActions=true');
|
||||
navigate('/account-list?hideListActions=true', { state: { from: pathname } });
|
||||
};
|
||||
|
||||
return <SelectAccount account={selectedAccount} handlePressAccount={handleSwitchAccount} />;
|
||||
|
||||
@@ -75,26 +75,28 @@ function ExploreScreen() {
|
||||
|
||||
const category = recommended?.filter((r) => r.category === activeTab);
|
||||
|
||||
return isLoading ? (
|
||||
<LoaderContainer>
|
||||
<Spinner color="white" size={30} />
|
||||
</LoaderContainer>
|
||||
) : (
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<StyledHeading typography="headline_l">{t('TITLE')}</StyledHeading>
|
||||
<ExternalLink href={XVERSE_EXPLORE_URL} target="_blank" rel="noreferrer">
|
||||
<ArrowsOut size={16} />
|
||||
{t('EXPAND_VIEW')}
|
||||
</ExternalLink>
|
||||
<Subheader>
|
||||
{t('FEATURED')}
|
||||
<SwiperNavigation />
|
||||
</Subheader>
|
||||
{!!featured?.length && <FeaturedCardCarousel items={featured} />}
|
||||
{tabs && <Tabs tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} />}
|
||||
{!!category?.length && <RecommendedApps items={category} />}
|
||||
</Container>
|
||||
{isLoading ? (
|
||||
<LoaderContainer>
|
||||
<Spinner color="white" size={30} />
|
||||
</LoaderContainer>
|
||||
) : (
|
||||
<Container>
|
||||
<StyledHeading typography="headline_l">{t('TITLE')}</StyledHeading>
|
||||
<ExternalLink href={XVERSE_EXPLORE_URL} target="_blank" rel="noreferrer">
|
||||
<ArrowsOut size={16} />
|
||||
{t('EXPAND_VIEW')}
|
||||
</ExternalLink>
|
||||
<Subheader>
|
||||
{t('FEATURED')}
|
||||
<SwiperNavigation />
|
||||
</Subheader>
|
||||
{!!featured?.length && <FeaturedCardCarousel items={featured} />}
|
||||
{tabs && <Tabs tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} />}
|
||||
{!!category?.length && <RecommendedApps items={category} />}
|
||||
</Container>
|
||||
)}
|
||||
<BottomBar tab="explore" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import BarLoader from '@components/barLoader';
|
||||
import { BestBarLoader } from '@components/barLoader';
|
||||
import { useVisibleBrc20FungibleTokens } from '@hooks/queries/ordinals/useGetBrc20FungibleTokens';
|
||||
import { useVisibleRuneFungibleTokens } from '@hooks/queries/runes/useRuneFungibleTokensQuery';
|
||||
import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens';
|
||||
@@ -8,47 +8,53 @@ import useStxWalletData from '@hooks/queries/useStxWalletData';
|
||||
import useSupportedCoinRates from '@hooks/queries/useSupportedCoinRates';
|
||||
import useSelectedAccount from '@hooks/useSelectedAccount';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import { currencySymbolMap } from '@secretkeylabs/xverse-core';
|
||||
import { setBalanceHiddenToggleAction } from '@stores/wallet/actions/actionCreators';
|
||||
import Spinner from '@ui-library/spinner';
|
||||
import { HIDDEN_BALANCE_LABEL, LoaderSize } from '@utils/constants';
|
||||
import { ANIMATION_EASING, HIDDEN_BALANCE_LABEL } from '@utils/constants';
|
||||
import { calculateTotalBalance, getAccountBalanceKey } from '@utils/helper';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NumericFormat } from 'react-number-format';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
min-height: 62px; // it's the height of RowContainer + BalanceContainer + indent between them
|
||||
margin-top: ${({ theme }) => theme.space.m};
|
||||
`;
|
||||
|
||||
const RowContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: props.theme.spacing(11),
|
||||
marginBottom: props.theme.space.xs,
|
||||
columnGap: props.theme.space.xxs,
|
||||
minHeight: 20,
|
||||
}));
|
||||
|
||||
const BalanceHeadingText = styled.h3((props) => ({
|
||||
...props.theme.headline_category_s,
|
||||
const BalanceHeadingText = styled.p((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
color: props.theme.colors.white_200,
|
||||
textTransform: 'uppercase',
|
||||
opacity: 0.7,
|
||||
lineHeight: '140%',
|
||||
}));
|
||||
|
||||
const CurrencyText = styled.label((props) => ({
|
||||
...props.theme.headline_category_s,
|
||||
...props.theme.typography.body_medium_m,
|
||||
color: props.theme.colors.white_0,
|
||||
fontSize: 13,
|
||||
}));
|
||||
|
||||
const BalanceAmountText = styled.p((props) => ({
|
||||
...props.theme.headline_xl,
|
||||
...props.theme.typography.headline_l,
|
||||
lineHeight: '1',
|
||||
color: props.theme.colors.white_0,
|
||||
}));
|
||||
|
||||
const BarLoaderContainer = styled.div((props) => ({
|
||||
const BarLoaderContainer = styled.div({
|
||||
display: 'flex',
|
||||
maxWidth: 300,
|
||||
marginTop: props.theme.spacing(5),
|
||||
}));
|
||||
});
|
||||
|
||||
const CurrencyCard = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
@@ -56,7 +62,6 @@ const CurrencyCard = styled.div((props) => ({
|
||||
backgroundColor: props.theme.colors.elevation3,
|
||||
width: 45,
|
||||
borderRadius: 30,
|
||||
marginLeft: props.theme.spacing(4),
|
||||
}));
|
||||
|
||||
const BalanceContainer = styled.div((props) => ({
|
||||
@@ -66,15 +71,23 @@ const BalanceContainer = styled.div((props) => ({
|
||||
width: 'fit-content',
|
||||
alignItems: 'center',
|
||||
gap: props.theme.spacing(5),
|
||||
minHeight: 34,
|
||||
cursor: 'pointer',
|
||||
}));
|
||||
|
||||
interface BalanceCardProps {
|
||||
const ContentWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
isLoading: boolean;
|
||||
isRefetching: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
function BalanceCard(props: BalanceCardProps) {
|
||||
function BalanceCard({ isLoading, isRefetching }: Props) {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'DASHBOARD_SCREEN' });
|
||||
const selectedAccount = useSelectedAccount();
|
||||
const dispatch = useDispatch();
|
||||
@@ -84,7 +97,6 @@ function BalanceCard(props: BalanceCardProps) {
|
||||
const { data: stxData } = useStxWalletData();
|
||||
const { btcFiatRate, stxBtcRate } = useSupportedCoinRates();
|
||||
const { setAccountBalance } = useAccountBalance();
|
||||
const { isLoading, isRefetching } = props;
|
||||
// TODO: refactor this into a hook
|
||||
const oldTotalBalance = accountBalances[getAccountBalanceKey(selectedAccount)];
|
||||
const { data: sip10CoinsList } = useVisibleSip10FungibleTokens();
|
||||
@@ -138,46 +150,85 @@ function BalanceCard(props: BalanceCardProps) {
|
||||
if (event.key === 'Enter') onClickBalance();
|
||||
};
|
||||
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
isInitialMount.current = false;
|
||||
}, []);
|
||||
|
||||
const loaderTransitions = useTransition(isLoading, {
|
||||
from: { opacity: isInitialMount.current ? 1 : 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 },
|
||||
config: {
|
||||
duration: 400,
|
||||
easing: ANIMATION_EASING,
|
||||
},
|
||||
exitBeforeEnter: true, // This ensures the leave animation completes before the enter animation starts
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<RowContainer>
|
||||
<BalanceHeadingText>{t('TOTAL_BALANCE')}</BalanceHeadingText>
|
||||
<CurrencyCard>
|
||||
<CurrencyText data-testid="currency-text">{fiatCurrency}</CurrencyText>
|
||||
</CurrencyCard>
|
||||
</RowContainer>
|
||||
{isLoading ? (
|
||||
<BarLoaderContainer>
|
||||
<BarLoader loaderSize={LoaderSize.LARGE} />
|
||||
</BarLoaderContainer>
|
||||
) : (
|
||||
<BalanceContainer onClick={onClickBalance} role="button" tabIndex={0} onKeyDown={onKeyDown}>
|
||||
{balanceHidden && (
|
||||
<BalanceAmountText data-testid="total-balance-value">
|
||||
{HIDDEN_BALANCE_LABEL}
|
||||
</BalanceAmountText>
|
||||
)}
|
||||
{!balanceHidden && (
|
||||
<>
|
||||
<NumericFormat
|
||||
value={balance}
|
||||
displayType="text"
|
||||
prefix={`${currencySymbolMap[fiatCurrency]}`}
|
||||
thousandSeparator
|
||||
renderText={(value: string) => (
|
||||
<BalanceAmountText data-testid="total-balance-value">{value}</BalanceAmountText>
|
||||
)}
|
||||
/>
|
||||
{isRefetching && (
|
||||
<div>
|
||||
<Spinner color="white" size={16} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</BalanceContainer>
|
||||
)}
|
||||
</>
|
||||
<Container>
|
||||
{loaderTransitions((style, loading) => (
|
||||
<ContentWrapper>
|
||||
<animated.div style={style}>
|
||||
{loading ? (
|
||||
<>
|
||||
<RowContainer>
|
||||
<BarLoaderContainer>
|
||||
<BestBarLoader width={76.5} height={20} />
|
||||
</BarLoaderContainer>
|
||||
</RowContainer>
|
||||
<BarLoaderContainer>
|
||||
<BestBarLoader width={244} height={34} />
|
||||
</BarLoaderContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RowContainer>
|
||||
<BalanceHeadingText>{t('TOTAL_BALANCE')}</BalanceHeadingText>
|
||||
<CurrencyCard>
|
||||
<CurrencyText data-testid="currency-text">{fiatCurrency}</CurrencyText>
|
||||
</CurrencyCard>
|
||||
</RowContainer>
|
||||
<BalanceContainer
|
||||
onClick={onClickBalance}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
{balanceHidden && (
|
||||
<BalanceAmountText data-testid="total-balance-value">
|
||||
{HIDDEN_BALANCE_LABEL}
|
||||
</BalanceAmountText>
|
||||
)}
|
||||
{!balanceHidden && (
|
||||
<>
|
||||
<NumericFormat
|
||||
value={balance}
|
||||
displayType="text"
|
||||
prefix={`${currencySymbolMap[fiatCurrency]}`}
|
||||
thousandSeparator
|
||||
renderText={(value: string) => (
|
||||
<BalanceAmountText data-testid="total-balance-value">
|
||||
{value}
|
||||
</BalanceAmountText>
|
||||
)}
|
||||
/>
|
||||
{isRefetching && (
|
||||
<div>
|
||||
<Spinner color="white" size={16} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</BalanceContainer>
|
||||
</>
|
||||
)}
|
||||
</animated.div>
|
||||
</ContentWrapper>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import Banner from './banner';
|
||||
|
||||
const CarouselContainer = styled.div`
|
||||
position: relative;
|
||||
margin-top: ${({ theme }) => theme.space.xxs};
|
||||
margin-bottom: ${({ theme }) => theme.space.xxs};
|
||||
padding-top: ${({ theme }) => theme.space.xxs};
|
||||
padding-bottom: ${({ theme }) => theme.space.xxs};
|
||||
|
||||
.swiper {
|
||||
padding: 0;
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ColumnContainer = styled.div((props) => ({
|
||||
alignItems: 'space-between',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: props.theme.space.xs,
|
||||
marginBottom: props.theme.space.s,
|
||||
marginBottom: props.theme.space.l,
|
||||
}));
|
||||
|
||||
export const ReceiveContainer = styled.div((props) => ({
|
||||
@@ -26,44 +26,40 @@ export const ReceiveContainer = styled.div((props) => ({
|
||||
gap: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const TokenListButtonContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginBottom: props.theme.space.xxxl,
|
||||
}));
|
||||
|
||||
export const TokenListButton = styled.button((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
cursor: props.disabled ? 'not-allowed' : 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
gap: props.theme.space.xs,
|
||||
borderRadius: props.theme.radius(1),
|
||||
backgroundColor: 'transparent',
|
||||
opacity: 0.8,
|
||||
marginTop: props.theme.spacing(5),
|
||||
cursor: props.disabled ? 'not-allowed' : 'pointer',
|
||||
}));
|
||||
|
||||
export const ButtonText = styled.div((props) => ({
|
||||
...props.theme.typography.body_s,
|
||||
fontWeight: 700,
|
||||
color: props.theme.colors.white_0,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
export const ButtonImage = styled.img((props) => ({
|
||||
marginRight: props.theme.spacing(3),
|
||||
alignSelf: 'center',
|
||||
transform: 'all',
|
||||
opacity: 0.8,
|
||||
transition: 'opacity 0.1s ease',
|
||||
'&:hover': {
|
||||
opacity: 1,
|
||||
},
|
||||
'&:active, &:disabled': {
|
||||
opacity: 0.6,
|
||||
},
|
||||
}));
|
||||
|
||||
export const RowButtonContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginTop: props.theme.spacing(11),
|
||||
columnGap: props.theme.spacing(11),
|
||||
}));
|
||||
|
||||
export const TokenListButtonContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: props.theme.space.s,
|
||||
marginBottom: props.theme.spacing(22),
|
||||
marginTop: props.theme.space.l,
|
||||
marginBottom: props.theme.space.xl,
|
||||
columnGap: props.theme.space.l,
|
||||
}));
|
||||
|
||||
export const StyledTokenTile = styled(TokenTile)`
|
||||
@@ -155,7 +151,10 @@ export const IconBackground = styled.div((props) => ({
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const StyledDivider = styled(Divider)<{ $noMarginBottom?: boolean }>`
|
||||
export const StyledDivider = styled(Divider)<{
|
||||
$noMarginBottom?: boolean;
|
||||
$noMarginTop?: boolean;
|
||||
}>`
|
||||
flex: 1 0 auto;
|
||||
width: calc(100% + ${(props) => props.theme.space.xl});
|
||||
margin-left: -${(props) => props.theme.space.m};
|
||||
@@ -166,10 +165,11 @@ export const StyledDivider = styled(Divider)<{ $noMarginBottom?: boolean }>`
|
||||
`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const StyledDividerSingle = styled(StyledDivider)`
|
||||
margin-bottom: 0;
|
||||
${(props) =>
|
||||
props.$noMarginTop &&
|
||||
`
|
||||
margin-top: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const SpacedCallout = styled(Callout)((props) => ({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import dashboardIcon from '@assets/img/dashboard-icon.svg';
|
||||
import ListDashes from '@assets/img/dashboard/list_dashes.svg';
|
||||
import ArrowSwap from '@assets/img/icons/ArrowSwap.svg';
|
||||
import AccountHeaderComponent from '@components/accountHeader';
|
||||
import BottomModal from '@components/bottomModal';
|
||||
import BottomBar from '@components/tabBar';
|
||||
import TokenTileLoader from '@components/tokenTile/loader';
|
||||
import {
|
||||
useGetBrc20FungibleTokens,
|
||||
useVisibleBrc20FungibleTokens,
|
||||
@@ -28,7 +28,7 @@ import useNotificationBanners from '@hooks/useNotificationBanners';
|
||||
import useSelectedAccount from '@hooks/useSelectedAccount';
|
||||
import useTrackMixPanelPageViewed from '@hooks/useTrackMixPanelPageViewed';
|
||||
import useWalletSelector from '@hooks/useWalletSelector';
|
||||
import { ArrowDown, ArrowUp, Plus } from '@phosphor-icons/react';
|
||||
import { ArrowDown, ArrowUp, ListDashes, Plus } from '@phosphor-icons/react';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import CoinSelectModal from '@screens/home/coinSelectModal';
|
||||
import {
|
||||
@@ -46,11 +46,11 @@ import {
|
||||
} from '@stores/wallet/actions/actionCreators';
|
||||
import Button from '@ui-library/button';
|
||||
import SnackBar from '@ui-library/snackBar';
|
||||
import type { CurrencyTypes } from '@utils/constants';
|
||||
import { ANIMATION_EASING, type CurrencyTypes } from '@utils/constants';
|
||||
import { isInOptions, isLedgerAccount } from '@utils/helper';
|
||||
import { optInMixPanel, optOutMixPanel, trackMixPanel } from '@utils/mixpanel';
|
||||
import { sortFtByFiatBalance } from '@utils/tokens';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -61,8 +61,6 @@ import AnnouncementModal from './announcementModal';
|
||||
import BalanceCard from './balanceCard';
|
||||
import BannerCarousel from './bannerCarousel';
|
||||
import {
|
||||
ButtonImage,
|
||||
ButtonText,
|
||||
ColumnContainer,
|
||||
Container,
|
||||
ModalButtonContainer,
|
||||
@@ -73,7 +71,6 @@ import {
|
||||
ModalTitle,
|
||||
RowButtonContainer,
|
||||
StyledDivider,
|
||||
StyledDividerSingle,
|
||||
StyledTokenTile,
|
||||
TokenListButton,
|
||||
TokenListButtonContainer,
|
||||
@@ -93,33 +90,108 @@ function Home() {
|
||||
const [openReceiveModal, setOpenReceiveModal] = useState(false);
|
||||
const [openSendModal, setOpenSendModal] = useState(false);
|
||||
const [openBuyModal, setOpenBuyModal] = useState(false);
|
||||
const { isLoading: loadingBtcWalletData, isRefetching: refetchingBtcWalletData } =
|
||||
useSelectedAccountBtcBalance();
|
||||
const { isInitialLoading: loadingStxWalletData, isRefetching: refetchingStxWalletData } =
|
||||
useStxWalletData();
|
||||
const {
|
||||
isLoading: isInitialLoadingBtc,
|
||||
isRefetching: refetchingBtcWalletData,
|
||||
failureCount: failureCountBtc,
|
||||
errorUpdateCount: errorUpdateCountBtc,
|
||||
} = useSelectedAccountBtcBalance();
|
||||
const { btcFiatRate, stxBtcRate } = useSupportedCoinRates();
|
||||
const { data: notificationBannersArr, isFetching: isFetchingNotificationBannersArr } =
|
||||
useNotificationBanners();
|
||||
|
||||
// Fetching balances
|
||||
const { data: fullSip10CoinsList } = useGetSip10FungibleTokens();
|
||||
const { data: fullBrc20CoinsList } = useGetBrc20FungibleTokens();
|
||||
const { data: fullRunesCoinsList } = useRuneFungibleTokensQuery();
|
||||
const {
|
||||
isInitialLoading: isInitialLoadingStx,
|
||||
isRefetching: refetchingStxWalletData,
|
||||
failureCount: failureCountStx,
|
||||
errorUpdateCount: errorUpdateCountStx,
|
||||
} = useStxWalletData();
|
||||
const {
|
||||
data: sip10CoinsList,
|
||||
isInitialLoading: loadingStxCoinData,
|
||||
isInitialLoading: isInitialLoadingSip10,
|
||||
isRefetching: refetchingStxCoinData,
|
||||
failureCount: failureCountSip10,
|
||||
errorUpdateCount: errorUpdateCountSip10,
|
||||
} = useVisibleSip10FungibleTokens();
|
||||
const {
|
||||
data: brc20CoinsList,
|
||||
isInitialLoading: loadingBrcCoinData,
|
||||
isInitialLoading: isInitialLoadingBrc20,
|
||||
isRefetching: refetchingBrcCoinData,
|
||||
failureCount: failureCountBrc20,
|
||||
errorUpdateCount: errorUpdateCountBrc20,
|
||||
} = useVisibleBrc20FungibleTokens();
|
||||
const {
|
||||
data: runesCoinsList,
|
||||
isInitialLoading: loadingRunesData,
|
||||
isInitialLoading: isInitialLoadingRunes,
|
||||
isRefetching: refetchingRunesData,
|
||||
errorUpdateCount: errorUpdateCountRunes,
|
||||
failureCount: failureCountRunes,
|
||||
} = useVisibleRuneFungibleTokens();
|
||||
|
||||
const isInitialLoadingTokens =
|
||||
(isInitialLoadingSip10 && failureCountSip10 === 0 && errorUpdateCountSip10 === 0) ||
|
||||
(isInitialLoadingBrc20 && failureCountBrc20 === 0 && errorUpdateCountBrc20 === 0) ||
|
||||
(isInitialLoadingRunes && failureCountRunes === 0 && errorUpdateCountRunes === 0);
|
||||
|
||||
const isInitialLoading =
|
||||
(isInitialLoadingBtc && failureCountBtc === 0 && errorUpdateCountBtc === 0) ||
|
||||
(isInitialLoadingStx && failureCountStx === 0 && errorUpdateCountStx === 0) ||
|
||||
isInitialLoadingTokens;
|
||||
|
||||
const isRefetching =
|
||||
refetchingBtcWalletData ||
|
||||
refetchingStxWalletData ||
|
||||
refetchingStxCoinData ||
|
||||
refetchingBrcCoinData ||
|
||||
refetchingRunesData;
|
||||
|
||||
useEffect(() => {
|
||||
const errorChecks = [
|
||||
{
|
||||
condition: failureCountBtc === 1 && errorUpdateCountBtc === 0,
|
||||
message: 'ERRORS.BTC_BALANCE',
|
||||
},
|
||||
{
|
||||
condition: failureCountStx === 1 && errorUpdateCountStx === 0,
|
||||
message: 'ERRORS.STX_BALANCE',
|
||||
},
|
||||
{
|
||||
condition: failureCountSip10 === 1 && errorUpdateCountSip10 === 0,
|
||||
message: 'ERRORS.SIP10_BALANCE',
|
||||
},
|
||||
{
|
||||
condition: failureCountBrc20 === 1 && errorUpdateCountBrc20 === 0,
|
||||
message: 'ERRORS.BRC20_BALANCE',
|
||||
},
|
||||
{
|
||||
condition: failureCountRunes === 1 && errorUpdateCountRunes === 0,
|
||||
message: 'ERRORS.RUNES_BALANCE',
|
||||
},
|
||||
];
|
||||
|
||||
errorChecks.forEach(({ condition, message }) => {
|
||||
if (condition) {
|
||||
toast.error(t(message));
|
||||
}
|
||||
});
|
||||
}, [
|
||||
t,
|
||||
failureCountBtc,
|
||||
errorUpdateCountBtc,
|
||||
failureCountStx,
|
||||
errorUpdateCountStx,
|
||||
failureCountSip10,
|
||||
errorUpdateCountSip10,
|
||||
failureCountBrc20,
|
||||
errorUpdateCountBrc20,
|
||||
failureCountRunes,
|
||||
errorUpdateCountRunes,
|
||||
]);
|
||||
|
||||
useFeeMultipliers();
|
||||
useAppConfig();
|
||||
useAvatarCleanup();
|
||||
@@ -296,38 +368,42 @@ function Home() {
|
||||
dispatch(changeShowDataCollectionAlertAction(false));
|
||||
};
|
||||
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
isInitialMount.current = false;
|
||||
}, []);
|
||||
|
||||
const isCrossChainSwapsEnabled = useHasFeature(FeatureId.CROSS_CHAIN_SWAPS);
|
||||
const showSwaps = isCrossChainSwapsEnabled;
|
||||
|
||||
const transitions = useTransition(showBannerCarousel, {
|
||||
from: { maxHeight: '1000px', opacity: 0.5 },
|
||||
enter: { maxHeight: '1000px', opacity: 1 },
|
||||
const loaderTransitions = useTransition(isInitialLoading, {
|
||||
from: { opacity: isInitialMount.current ? 1 : 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 },
|
||||
config: {
|
||||
duration: 400,
|
||||
easing: ANIMATION_EASING,
|
||||
},
|
||||
exitBeforeEnter: true, // This ensures the leave animation completes before the enter animation starts
|
||||
});
|
||||
|
||||
const bannerTransitions = useTransition(showBannerCarousel && !isInitialLoading, {
|
||||
from: { maxHeight: '102px', opacity: isInitialMount.current ? 1 : 0 },
|
||||
enter: { maxHeight: '102px', opacity: 1 },
|
||||
leave: { maxHeight: '0px', opacity: 0 },
|
||||
config: (item, index, phase) =>
|
||||
phase === 'leave'
|
||||
? {
|
||||
duration: 300,
|
||||
easing: (progress) => 1 - (1 - progress) ** 4,
|
||||
}
|
||||
: {
|
||||
duration: 200,
|
||||
},
|
||||
config: {
|
||||
duration: 400,
|
||||
easing: ANIMATION_EASING,
|
||||
},
|
||||
exitBeforeEnter: true, // This ensures the leave animation completes before the enter animation starts
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AccountHeaderComponent />
|
||||
<Container>
|
||||
<BalanceCard
|
||||
isLoading={loadingStxWalletData || loadingBtcWalletData}
|
||||
isRefetching={
|
||||
refetchingBtcWalletData ||
|
||||
refetchingStxWalletData ||
|
||||
refetchingStxCoinData ||
|
||||
refetchingBrcCoinData ||
|
||||
refetchingRunesData
|
||||
}
|
||||
/>
|
||||
<BalanceCard isLoading={isInitialLoading} isRefetching={isRefetching} />
|
||||
<RowButtonContainer data-testid="transaction-buttons-row">
|
||||
<SquareButton
|
||||
icon={<ArrowUp weight="regular" size="20" />}
|
||||
@@ -347,62 +423,69 @@ function Home() {
|
||||
/>
|
||||
</RowButtonContainer>
|
||||
|
||||
{transitions((style, item) =>
|
||||
{bannerTransitions((style, item) =>
|
||||
item ? (
|
||||
<animated.div style={style}>
|
||||
<br />
|
||||
<StyledDivider color="white_850" verticalMargin="m" />
|
||||
<StyledDivider color="white_850" $verticalMargin="m" $noMarginTop />
|
||||
<BannerCarousel items={filteredNotificationBannersArr} />
|
||||
<StyledDivider
|
||||
color="white_850"
|
||||
verticalMargin="m"
|
||||
$verticalMargin="m"
|
||||
$noMarginBottom={filteredNotificationBannersArr.length === 1}
|
||||
/>
|
||||
</animated.div>
|
||||
) : (
|
||||
<animated.div style={style}>
|
||||
<StyledDividerSingle color="elevation3" verticalMargin="xl" />
|
||||
<StyledDivider color="elevation3" />
|
||||
</animated.div>
|
||||
),
|
||||
)}
|
||||
|
||||
<ColumnContainer>
|
||||
{btcAddress && (
|
||||
<StyledTokenTile
|
||||
title={t('BITCOIN')}
|
||||
currency="BTC"
|
||||
loading={loadingBtcWalletData}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
{loaderTransitions((style, loading) =>
|
||||
loading ? (
|
||||
<animated.div style={style}>
|
||||
<TokenTileLoader />
|
||||
<TokenTileLoader />
|
||||
<TokenTileLoader />
|
||||
<TokenTileLoader />
|
||||
</animated.div>
|
||||
) : (
|
||||
<animated.div style={style}>
|
||||
{btcAddress && (
|
||||
<StyledTokenTile
|
||||
title={t('BITCOIN')}
|
||||
currency="BTC"
|
||||
loading={isInitialLoadingBtc}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
)}
|
||||
{stxAddress && !hideStx && (
|
||||
<StyledTokenTile
|
||||
title={t('STACKS')}
|
||||
currency="STX"
|
||||
loading={isInitialLoadingStx}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
)}
|
||||
{combinedFtList.map((coin) => (
|
||||
<StyledTokenTile
|
||||
key={coin.principal}
|
||||
title={coin.name}
|
||||
currency="FT"
|
||||
loading={isInitialLoadingTokens}
|
||||
fungibleToken={coin}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
))}
|
||||
</animated.div>
|
||||
),
|
||||
)}
|
||||
{stxAddress && !hideStx && (
|
||||
<StyledTokenTile
|
||||
title={t('STACKS')}
|
||||
currency="STX"
|
||||
loading={loadingStxWalletData}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
)}
|
||||
{combinedFtList.map((coin: FungibleTokenWithStates) => {
|
||||
const isLoading = loadingStxCoinData || loadingBrcCoinData || loadingRunesData;
|
||||
return (
|
||||
<StyledTokenTile
|
||||
key={coin.principal}
|
||||
title={coin.name}
|
||||
currency="FT"
|
||||
loading={isLoading}
|
||||
fungibleToken={coin}
|
||||
onPress={handleTokenPressed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ColumnContainer>
|
||||
<TokenListButtonContainer>
|
||||
<TokenListButton onClick={handleManageTokenListOnClick}>
|
||||
<>
|
||||
<ButtonImage src={ListDashes} />
|
||||
<ButtonText>{t('MANAGE_TOKEN')}</ButtonText>
|
||||
</>
|
||||
<ListDashes size={20} />
|
||||
{t('MANAGE_TOKEN')}
|
||||
</TokenListButton>
|
||||
</TokenListButtonContainer>
|
||||
|
||||
@@ -416,7 +499,7 @@ function Home() {
|
||||
visible={openSendModal}
|
||||
coins={combinedFtList}
|
||||
title={t('SEND')}
|
||||
loadingWalletData={loadingStxWalletData || loadingBtcWalletData}
|
||||
loadingWalletData={isInitialLoadingStx || isInitialLoadingBtc}
|
||||
/>
|
||||
<CoinSelectModal
|
||||
onSelectBitcoin={onBuyBtcClick}
|
||||
@@ -426,7 +509,7 @@ function Home() {
|
||||
visible={openBuyModal}
|
||||
coins={[]}
|
||||
title={t('BUY')}
|
||||
loadingWalletData={loadingStxWalletData || loadingBtcWalletData}
|
||||
loadingWalletData={isInitialLoadingStx || isInitialLoadingBtc}
|
||||
/>
|
||||
<AnnouncementModal />
|
||||
</Container>
|
||||
|
||||
187
src/app/screens/landing/index.styled.ts
Normal file
187
src/app/screens/landing/index.styled.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import Button from '@ui-library/button';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
const slideYAndOpacity = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(49px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideY = keyframes`
|
||||
0% {
|
||||
transform: translateY(49px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideOpacity = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const slideRightAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideLeftAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-240px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const AppVersion = styled.p((props) => ({
|
||||
...props.theme.typography.body_s,
|
||||
color: props.theme.colors.white_0,
|
||||
textAlign: 'right',
|
||||
marginRight: props.theme.spacing(9),
|
||||
marginTop: props.theme.spacing(8),
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
export const ArrowContainer = styled.div`
|
||||
position: relative;
|
||||
top: 209px;
|
||||
margin-left: ${(props) => props.theme.space.s};
|
||||
margin-right: ${(props) => props.theme.space.s};
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
animation: ${() => slideOpacity} 0.2s ease-out;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
export const CaretButton = styled.button<{ disabled: boolean }>((props) => ({
|
||||
backgroundColor: 'transparent',
|
||||
cursor: props.disabled ? 'default' : 'pointer',
|
||||
svg: {
|
||||
opacity: props.disabled ? 0.6 : 1,
|
||||
transition: 'opacity 0.1s ease',
|
||||
},
|
||||
'&:hover:enabled, &:focus:enabled': {
|
||||
svg: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export const AnimationContainer = styled.div({
|
||||
marginTop: '60%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const LandingSectionContainer = styled.div({
|
||||
marginTop: '50%',
|
||||
marginBottom: '88px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const InitialTransitionLandingSectionContainer = styled(LandingSectionContainer)`
|
||||
animation: ${() => slideY} 0.2s ease-out;
|
||||
`;
|
||||
|
||||
export const TransitionLandingSectionContainer = styled(LandingSectionContainer)<{
|
||||
$direction: 'left' | 'right';
|
||||
}>((props) => ({
|
||||
animation: `${
|
||||
props.$direction === 'left' ? slideLeftAnimation : slideRightAnimation
|
||||
} 0.3s cubic-bezier(0, 0, 0.58, 1) forwards`,
|
||||
}));
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 135px;
|
||||
height: 25px;
|
||||
`;
|
||||
|
||||
export const LandingTitle = styled.h1`
|
||||
${(props) => props.theme.typography.body_medium_l}
|
||||
margin-top: ${(props) => props.theme.space.m};
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const OnboardingContainer = styled.div((props) => ({
|
||||
marginBottom: props.theme.space.l,
|
||||
}));
|
||||
|
||||
export const TransitionOnboardingContainer = styled(OnboardingContainer)<{
|
||||
$direction: 'left' | 'right';
|
||||
}>((props) => ({
|
||||
animation: `${
|
||||
props.$direction === 'left' ? slideLeftAnimation : slideRightAnimation
|
||||
} 0.3s cubic-bezier(0, 0, 0.58, 1) forwards`,
|
||||
}));
|
||||
|
||||
export const OnBoardingImage = styled.img(() => ({
|
||||
marginTop: -26,
|
||||
alignSelf: 'center',
|
||||
transform: 'all',
|
||||
}));
|
||||
|
||||
export const OnBoardingContent = styled.div((props) => ({
|
||||
marginTop: props.theme.spacing(24),
|
||||
padding: `0 22px`,
|
||||
}));
|
||||
|
||||
export const OnboardingTitle = styled.h1<{ needPadding: boolean }>((props) => ({
|
||||
...props.theme.typography.headline_xs,
|
||||
textAlign: 'center',
|
||||
paddingLeft: props.needPadding ? 20 : 0,
|
||||
paddingRight: props.needPadding ? 20 : 0,
|
||||
}));
|
||||
|
||||
export const BottomContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: ${(props) => props.theme.space.m};
|
||||
margin-right: ${(props) => props.theme.space.m};
|
||||
animation: ${() => slideYAndOpacity} 0.2s ease-out;
|
||||
`;
|
||||
|
||||
export const CreateButton = styled(Button)((props) => ({
|
||||
marginTop: props.theme.space.l,
|
||||
}));
|
||||
|
||||
export const RestoreButton = styled(Button)((props) => ({
|
||||
marginTop: props.theme.space.s,
|
||||
}));
|
||||
@@ -4,203 +4,35 @@ import onboarding1 from '@assets/img/landing/onboarding1.svg';
|
||||
import onboarding2 from '@assets/img/landing/onboarding2.svg';
|
||||
import Dots from '@components/dots';
|
||||
import { CaretLeft, CaretRight } from '@phosphor-icons/react';
|
||||
import Button from '@ui-library/button';
|
||||
import { isInOptions } from '@utils/helper';
|
||||
import { getIsTermsAccepted } from '@utils/localStorage';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Lottie from 'react-lottie';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled, { keyframes, useTheme } from 'styled-components';
|
||||
|
||||
const slideYAndOpacity = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(49px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideY = keyframes`
|
||||
0% {
|
||||
transform: translateY(49px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideOpacity = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const slideRightAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const slideLeftAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-240px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const AppVersion = styled.p((props) => ({
|
||||
...props.theme.typography.body_s,
|
||||
color: props.theme.colors.white_0,
|
||||
textAlign: 'right',
|
||||
marginRight: props.theme.spacing(9),
|
||||
marginTop: props.theme.spacing(8),
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
const ArrowContainer = styled.div`
|
||||
position: relative;
|
||||
top: 209px;
|
||||
margin-left: ${(props) => props.theme.space.s};
|
||||
margin-right: ${(props) => props.theme.space.s};
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
animation: ${() => slideOpacity} 0.2s ease-out;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const CaretButton = styled.button<{ disabled: boolean }>((props) => ({
|
||||
backgroundColor: 'transparent',
|
||||
cursor: props.disabled ? 'default' : 'pointer',
|
||||
svg: {
|
||||
opacity: props.disabled ? 0.6 : 1,
|
||||
transition: 'opacity 0.1s ease',
|
||||
},
|
||||
'&:hover:enabled, &:focus:enabled': {
|
||||
svg: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const AnimationContainer = styled.div({
|
||||
marginTop: '60%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const LandingSectionContainer = styled.div({
|
||||
marginTop: '50%',
|
||||
marginBottom: '88px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const InitialTransitionLandingSectionContainer = styled(LandingSectionContainer)`
|
||||
animation: ${() => slideY} 0.2s ease-out;
|
||||
`;
|
||||
|
||||
const TransitionLandingSectionContainer = styled(LandingSectionContainer)<{
|
||||
$direction: 'left' | 'right';
|
||||
}>((props) => ({
|
||||
animation: `${
|
||||
props.$direction === 'left' ? slideLeftAnimation : slideRightAnimation
|
||||
} 0.3s cubic-bezier(0, 0, 0.58, 1) forwards`,
|
||||
}));
|
||||
|
||||
const Logo = styled.img`
|
||||
width: 135px;
|
||||
height: 25px;
|
||||
`;
|
||||
|
||||
const LandingTitle = styled.h1`
|
||||
${(props) => props.theme.typography.body_medium_l}
|
||||
margin-top: ${(props) => props.theme.space.m};
|
||||
color: ${(props) => props.theme.colors.white_200};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const OnboardingContainer = styled.div((props) => ({
|
||||
marginBottom: props.theme.space.l,
|
||||
}));
|
||||
|
||||
const TransitionOnboardingContainer = styled(OnboardingContainer)<{
|
||||
$direction: 'left' | 'right';
|
||||
}>((props) => ({
|
||||
animation: `${
|
||||
props.$direction === 'left' ? slideLeftAnimation : slideRightAnimation
|
||||
} 0.3s cubic-bezier(0, 0, 0.58, 1) forwards`,
|
||||
}));
|
||||
|
||||
const OnBoardingImage = styled.img(() => ({
|
||||
marginTop: -26,
|
||||
alignSelf: 'center',
|
||||
transform: 'all',
|
||||
}));
|
||||
|
||||
const OnBoardingContent = styled.div((props) => ({
|
||||
marginTop: props.theme.spacing(24),
|
||||
padding: `0 22px`,
|
||||
}));
|
||||
|
||||
const OnboardingTitle = styled.h1<{ needPadding: boolean }>((props) => ({
|
||||
...props.theme.typography.headline_xs,
|
||||
textAlign: 'center',
|
||||
paddingLeft: props.needPadding ? 20 : 0,
|
||||
paddingRight: props.needPadding ? 20 : 0,
|
||||
}));
|
||||
|
||||
const BottomContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: ${(props) => props.theme.space.m};
|
||||
margin-right: ${(props) => props.theme.space.m};
|
||||
animation: ${() => slideYAndOpacity} 0.2s ease-out;
|
||||
`;
|
||||
|
||||
const CreateButton = styled(Button)((props) => ({
|
||||
marginTop: props.theme.space.l,
|
||||
}));
|
||||
|
||||
const RestoreButton = styled(Button)((props) => ({
|
||||
marginTop: props.theme.space.s,
|
||||
}));
|
||||
import { useTheme } from 'styled-components';
|
||||
import {
|
||||
AnimationContainer,
|
||||
AppVersion,
|
||||
ArrowContainer,
|
||||
BottomContainer,
|
||||
CaretButton,
|
||||
Container,
|
||||
CreateButton,
|
||||
InitialTransitionLandingSectionContainer,
|
||||
LandingTitle,
|
||||
Logo,
|
||||
OnBoardingContent,
|
||||
OnBoardingImage,
|
||||
OnboardingTitle,
|
||||
RestoreButton,
|
||||
TransitionLandingSectionContainer,
|
||||
TransitionOnboardingContainer,
|
||||
} from './index.styled';
|
||||
|
||||
function Landing() {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'LANDING_SCREEN' });
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState<number>(0);
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||
const [animationComplete, setAnimationComplete] = useState(
|
||||
process.env.SKIP_ANIMATION_WALLET_STARTUP === 'true',
|
||||
);
|
||||
|
||||
104
src/app/screens/login/index.styled.ts
Normal file
104
src/app/screens/login/index.styled.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { animated } from '@react-spring/web';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Logo = styled.img({
|
||||
width: 57,
|
||||
height: 57,
|
||||
});
|
||||
|
||||
export const IconButton = styled.button({
|
||||
background: 'none',
|
||||
});
|
||||
|
||||
export const ScreenContainer = styled(animated.div)((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: props.theme.spacing(9),
|
||||
paddingRight: props.theme.spacing(9),
|
||||
overflowY: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
export const ContentContainer = styled(animated.div)({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
export const AppVersion = styled.p((props) => ({
|
||||
...props.theme.typography.body_s,
|
||||
color: props.theme.colors.white_0,
|
||||
textAlign: 'right',
|
||||
marginTop: props.theme.space.m,
|
||||
}));
|
||||
|
||||
export const TopSectionContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: props.theme.spacing(60),
|
||||
marginBottom: props.theme.spacing(30),
|
||||
}));
|
||||
|
||||
export const PasswordInputLabel = styled.h2((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
textAlign: 'left',
|
||||
marginTop: props.theme.spacing(15.5),
|
||||
}));
|
||||
|
||||
export const PasswordInputContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
border: `1px solid ${props.theme.colors.elevation3}`,
|
||||
paddingLeft: props.theme.space.m,
|
||||
paddingRight: props.theme.space.m,
|
||||
borderRadius: props.theme.radius(1),
|
||||
marginTop: props.theme.space.xs,
|
||||
}));
|
||||
|
||||
export const PasswordInput = styled.input((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
height: 44,
|
||||
backgroundColor: props.theme.colors.elevation0,
|
||||
color: props.theme.colors.white_0,
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
}));
|
||||
|
||||
export const LandingTitle = styled.h1((props) => ({
|
||||
...props.theme.tile_text,
|
||||
paddingTop: props.theme.spacing(15),
|
||||
paddingLeft: props.theme.spacing(34),
|
||||
paddingRight: props.theme.spacing(34),
|
||||
color: props.theme.colors.white_200,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
export const ButtonContainer = styled.div((props) => ({
|
||||
marginTop: props.theme.space.m,
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
export const ErrorMessage = styled.h2((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
textAlign: 'left',
|
||||
color: props.theme.colors.feedback.error,
|
||||
marginTop: props.theme.space.xs,
|
||||
}));
|
||||
|
||||
export const ForgotPasswordButton = styled.button((props) => ({
|
||||
...props.theme.typography.body_m,
|
||||
textAlign: 'center',
|
||||
marginTop: props.theme.space.l,
|
||||
color: props.theme.colors.white_0,
|
||||
textDecoration: 'underline',
|
||||
backgroundColor: 'transparent',
|
||||
':hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
}));
|
||||
@@ -4,121 +4,34 @@ import logo from '@assets/img/xverse_logo.svg';
|
||||
import useSeedVault from '@hooks/useSeedVault';
|
||||
import useSeedVaultMigration from '@hooks/useSeedVaultMigration';
|
||||
import useWalletReducer from '@hooks/useWalletReducer';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useSpring } from '@react-spring/web';
|
||||
import MigrationConfirmation from '@screens/migrationConfirmation';
|
||||
import { AnalyticsEvents } from '@secretkeylabs/xverse-core';
|
||||
import Button from '@ui-library/button';
|
||||
import { trackMixPanel } from '@utils/mixpanel';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Logo = styled.img({
|
||||
width: 57,
|
||||
height: 57,
|
||||
});
|
||||
|
||||
const IconButton = styled.button({
|
||||
background: 'none',
|
||||
});
|
||||
|
||||
const ScreenContainer = styled(animated.div)((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: props.theme.spacing(9),
|
||||
paddingRight: props.theme.spacing(9),
|
||||
overflowY: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const ContentContainer = styled(animated.div)({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const AppVersion = styled.p((props) => ({
|
||||
...props.theme.typography.body_s,
|
||||
color: props.theme.colors.white_0,
|
||||
textAlign: 'right',
|
||||
marginTop: props.theme.space.m,
|
||||
}));
|
||||
|
||||
const TopSectionContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: props.theme.spacing(60),
|
||||
marginBottom: props.theme.spacing(30),
|
||||
}));
|
||||
|
||||
const PasswordInputLabel = styled.h2((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
textAlign: 'left',
|
||||
marginTop: props.theme.spacing(15.5),
|
||||
}));
|
||||
|
||||
const PasswordInputContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
border: `1px solid ${props.theme.colors.elevation3}`,
|
||||
paddingLeft: props.theme.space.m,
|
||||
paddingRight: props.theme.space.m,
|
||||
borderRadius: props.theme.radius(1),
|
||||
marginTop: props.theme.space.xs,
|
||||
}));
|
||||
|
||||
const PasswordInput = styled.input((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
height: 44,
|
||||
backgroundColor: props.theme.colors.elevation0,
|
||||
color: props.theme.colors.white_0,
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
}));
|
||||
|
||||
const LandingTitle = styled.h1((props) => ({
|
||||
...props.theme.tile_text,
|
||||
paddingTop: props.theme.spacing(15),
|
||||
paddingLeft: props.theme.spacing(34),
|
||||
paddingRight: props.theme.spacing(34),
|
||||
color: props.theme.colors.white_200,
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const ButtonContainer = styled.div((props) => ({
|
||||
marginTop: props.theme.space.m,
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const ErrorMessage = styled.h2((props) => ({
|
||||
...props.theme.typography.body_medium_m,
|
||||
textAlign: 'left',
|
||||
color: props.theme.colors.feedback.error,
|
||||
marginTop: props.theme.space.xs,
|
||||
}));
|
||||
|
||||
const ForgotPasswordButton = styled.button((props) => ({
|
||||
...props.theme.typography.body_m,
|
||||
textAlign: 'center',
|
||||
marginTop: props.theme.space.l,
|
||||
color: props.theme.colors.white_0,
|
||||
textDecoration: 'underline',
|
||||
backgroundColor: 'transparent',
|
||||
':hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
}));
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
AppVersion,
|
||||
ButtonContainer,
|
||||
ContentContainer,
|
||||
ErrorMessage,
|
||||
ForgotPasswordButton,
|
||||
IconButton,
|
||||
LandingTitle,
|
||||
Logo,
|
||||
PasswordInput,
|
||||
PasswordInputContainer,
|
||||
PasswordInputLabel,
|
||||
ScreenContainer,
|
||||
TopSectionContainer,
|
||||
} from './index.styled';
|
||||
|
||||
function Login(): JSX.Element {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'LOGIN_SCREEN' });
|
||||
const navigate = useNavigate();
|
||||
const { state } = useLocation();
|
||||
const { unlockWallet } = useWalletReducer();
|
||||
const { hasSeed } = useSeedVault();
|
||||
const { migrateCachedStorage, isVaultUpdated } = useSeedVaultMigration();
|
||||
@@ -160,6 +73,7 @@ function Login(): JSX.Element {
|
||||
const handleTogglePasswordView = () => {
|
||||
setIsPasswordVisible(!isPasswordVisible);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
if (error) {
|
||||
setError('');
|
||||
@@ -175,11 +89,19 @@ function Login(): JSX.Element {
|
||||
setShowMigration(true);
|
||||
} else {
|
||||
setIsVerifying(false);
|
||||
navigate(-1);
|
||||
if (state?.from) {
|
||||
navigate(state?.from, { state: { from: '/login' } }); // this is needed for AnimatedScreenContainer where we check the state.from
|
||||
} else {
|
||||
navigate(-1);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setIsVerifying(false);
|
||||
navigate(-1);
|
||||
if (state?.from) {
|
||||
navigate(state?.from, { state: { from: '/login' } }); // this is needed for AnimatedScreenContainer where we check the state.from
|
||||
} else {
|
||||
navigate(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ function NftDashboardHidden() {
|
||||
return (
|
||||
<>
|
||||
{isGalleryOpen ? (
|
||||
<AccountHeaderComponent disableMenuOption={isGalleryOpen} showBorderBottom={false} />
|
||||
<AccountHeaderComponent disableMenuOption={isGalleryOpen} />
|
||||
) : (
|
||||
<TopRow
|
||||
onClick={handleBackButtonClick}
|
||||
|
||||
@@ -89,7 +89,7 @@ function NftDashboard() {
|
||||
{isOrdinalReceiveAlertVisible && (
|
||||
<ShowOrdinalReceiveAlert onOrdinalReceiveAlertClose={onOrdinalReceiveAlertClose} />
|
||||
)}
|
||||
<AccountHeaderComponent disableMenuOption={isGalleryOpen} showBorderBottom={false} />
|
||||
<AccountHeaderComponent disableMenuOption={isGalleryOpen} />
|
||||
<Container>
|
||||
<PageHeader>
|
||||
<CollectibleContainer>
|
||||
|
||||
@@ -2,15 +2,15 @@ import styled from 'styled-components';
|
||||
import Theme from 'theme';
|
||||
|
||||
const Divider = styled.div<{
|
||||
verticalMargin: keyof typeof Theme.space;
|
||||
color?: keyof typeof Theme.colors;
|
||||
$verticalMargin?: keyof typeof Theme.space;
|
||||
$color?: keyof typeof Theme.colors;
|
||||
}>((props) => ({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: props.color
|
||||
? String(props.theme.colors[props.color])
|
||||
backgroundColor: props.$color
|
||||
? String(props.theme.colors[props.$color])
|
||||
: props.theme.colors.white_900,
|
||||
margin: `${props.theme.space[props.verticalMargin]} 0`,
|
||||
margin: props.$verticalMargin ? `${props.theme.space[props.$verticalMargin]} 0` : 0,
|
||||
}));
|
||||
export default Divider;
|
||||
|
||||
@@ -66,6 +66,7 @@ export const DEFAULT_TRANSITION_OPTIONS = {
|
||||
|
||||
export const MAX_ACC_NAME_LENGTH = 20;
|
||||
// UI
|
||||
export const ANIMATION_EASING = (progress: number) => 1 - (1 - progress) ** 3; // ease out (0, 0, 0.58, 1)
|
||||
export const EMPTY_LABEL = '--';
|
||||
export const HIDDEN_BALANCE_LABEL = '●●●●●●';
|
||||
export const OPTIONS_DIALOG_WIDTH = 179;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4H13.5" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.00049 8H13.5001" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.00049 12H13.5001" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.5 4H3.5" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.50049 8H3.5001" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.50049 12H3.5001" stroke="white" stroke-opacity="0.8" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 884 B |
@@ -207,7 +207,7 @@
|
||||
"AMOUNT": "Amount"
|
||||
},
|
||||
"DASHBOARD_SCREEN": {
|
||||
"TOTAL_BALANCE": "total balance",
|
||||
"TOTAL_BALANCE": "Total Balance",
|
||||
"BITCOIN": "Bitcoin",
|
||||
"STACKS": "Stacks",
|
||||
"ACCOUNT_NAME": "Account",
|
||||
@@ -235,6 +235,13 @@
|
||||
},
|
||||
"TOKEN_HIDDEN": "Token hidden and reported",
|
||||
"UNDO": "Undo",
|
||||
"ERRORS": {
|
||||
"BTC_BALANCE": "Failed to load Bitcoin balance",
|
||||
"STX_BALANCE": "Failed to load Stacks balance",
|
||||
"SIP10_BALANCE": "Failed to load SIP10 token balances",
|
||||
"BRC20_BALANCE": "Failed to load BRC20 token balances",
|
||||
"RUNES_BALANCE": "Failed to load Rune balances"
|
||||
},
|
||||
"ANNOUNCEMENTS": {
|
||||
"NATIVE_SEGWIT": {
|
||||
"TITLE": "Native SegWit is here!",
|
||||
|
||||
@@ -483,9 +483,9 @@ export default class Wallet {
|
||||
|
||||
this.backToGallery = page.getByTestId('back-to-gallery');
|
||||
this.itemCollection = page.getByTestId('collection-item');
|
||||
this.buttonSend = page.getByRole('button', { name: 'Send' });
|
||||
this.buttonShare = page.getByRole('button', { name: 'Share' });
|
||||
this.buttonReceive = page.getByRole('button', { name: 'Receive', exact: true });
|
||||
this.buttonSend = page.locator('button').filter({ hasText: 'Send' });
|
||||
this.buttonShare = page.locator('button').filter({ hasText: 'Share' });
|
||||
this.buttonReceive = page.locator('button').filter({ hasText: 'Receive' });
|
||||
this.buttonOpenOrdinalViewer = page.getByRole('button', { name: 'Open in Ordinal Viewer' });
|
||||
this.labelBundle = page.locator('h1').filter({ hasText: 'Bundle' });
|
||||
this.labelSatsValue = page.locator('h1').filter({ hasText: 'Sats value' });
|
||||
@@ -620,6 +620,12 @@ export default class Wallet {
|
||||
}
|
||||
|
||||
async checkVisualsStartpage() {
|
||||
// Wait for the balance element to be present in the DOM
|
||||
await this.page.waitForSelector('[data-testid="total-balance-value"]', { state: 'attached' });
|
||||
|
||||
// Wait for a short duration to allow the animation to complete
|
||||
await this.page.waitForTimeout(400);
|
||||
|
||||
await expect(this.balance).toBeVisible();
|
||||
await expect(this.manageTokenButton).toBeVisible();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user