Skip to content

Commit

Permalink
Stop using loader functions for swap page (#1316)
Browse files Browse the repository at this point in the history
* Fix typo

* Use ZQuery for the Swap slice

* Add a fade transition

* Add docs for FadeIn

* Fix tests

* Remove unnecessary class change

* Fix tests

* Render errors, if any

* Make getAssetId optional
  • Loading branch information
jessepinho authored Jun 20, 2024
1 parent c555df7 commit 2f0a8e4
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 127 deletions.
3 changes: 1 addition & 2 deletions apps/minifront/src/components/root-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { SendForm } from './send/send-form';
import { Receive } from './send/receive';
import { ErrorBoundary } from './shared/error-boundary';
import { SwapLayout } from './swap/layout';
import { SwapLoader } from './swap/swap-loader';
import { StakingLayout } from './staking/layout';
import { IbcLoader } from './ibc/ibc-loader';
import { IbcLayout } from './ibc/layout';
Expand Down Expand Up @@ -56,7 +55,7 @@ export const rootRouter: Router = createHashRouter([
},
{
path: PagePath.SWAP,
loader: SwapLoader,
loader: abortLoader,
element: <SwapLayout />,
},
{
Expand Down
4 changes: 2 additions & 2 deletions apps/minifront/src/components/send/send-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const SendForm = () => {
txInProgress,
} = useStore(sendSelector);
// State to manage privacy warning display
const [showNonNativeFeeWarning, setshowNonNativeFeeWarning] = useState(false);
const [showNonNativeFeeWarning, setShowNonNativeFeeWarning] = useState(false);

// Check if the user has native staking tokens
const stakingToken = hasStakingToken(
Expand Down Expand Up @@ -81,7 +81,7 @@ export const SendForm = () => {
if (Number(amount) < 0) return;
setAmount(amount);
// Conditionally prompt a privacy warning about non-native fee tokens
setshowNonNativeFeeWarning(Number(amount) > 0 && !stakingToken);
setShowNonNativeFeeWarning(Number(amount) > 0 && !stakingToken);
}}
validations={[
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ const zeroBalance = new ValueView({

const validatorInfo = new ValidatorInfo({ validator: {} });

let MOCK_STAKING_TOKENS_AND_FILTER:
| {
unstakedTokensByAccount: Map<number, ValueView | undefined>;
accountSwitcherFilter: number[];
}
| undefined;
let MOCK_STAKING_TOKENS_AND_FILTER: {
unstakedTokensByAccount: Map<number, ValueView | undefined>;
accountSwitcherFilter: number[];
} | null = vi.hoisted(() => null);

vi.mock('../../../../../state/staking', async () => ({
...(await vi.importActual('../../../../../state/staking')),
Expand All @@ -53,7 +51,7 @@ vi.mock('../../../../../utils/use-store-shallow', async () => ({

describe('<StakingActions />', () => {
beforeEach(() => {
MOCK_STAKING_TOKENS_AND_FILTER = undefined;
MOCK_STAKING_TOKENS_AND_FILTER = null;
});

it('renders an enabled Delegate button there is a non-zero balance of unstaked tokens', () => {
Expand Down Expand Up @@ -91,7 +89,7 @@ describe('<StakingActions />', () => {
});

it('renders a disabled Delegate button when unstaked tokens are undefined', () => {
MOCK_STAKING_TOKENS_AND_FILTER = undefined;
MOCK_STAKING_TOKENS_AND_FILTER = null;

const { getByText } = render(
<StakingActions delegationTokens={nonZeroBalance} validatorInfo={validatorInfo} />,
Expand Down
98 changes: 60 additions & 38 deletions apps/minifront/src/components/swap/swap-form/token-swap-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import BalanceSelector from '../../shared/balance-selector';
import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb';
import { useStatus } from '../../../state/status';
import { hasStakingToken } from '../../../fetchers/staking-token';
import { useStakingTokenMetadata } from '../../../state/shared';
import { useBalancesResponses, useSwappableAssets } from '../../../state/swap';
import { FadeIn } from '@repo/ui/components/ui/fade-in';

const isValidAmount = (amount: string, assetIn?: BalancesResponse) =>
Number(amount) >= 0 && (!assetIn || !amountMoreThanBalance(assetIn, amount));
Expand All @@ -39,7 +42,11 @@ const getKnownZeroValueView = (metadata?: Metadata) => {
});
};

const assetOutBalanceSelector = ({ swap: { balancesResponses, assetIn, assetOut } }: AllSlices) => {
const getAssetOutBalance = (
balancesResponses: BalancesResponse[] = [],
assetIn?: BalancesResponse,
assetOut?: Metadata,
) => {
if (!assetIn || !assetOut) return getKnownZeroValueView();

const match = balancesResponses.find(balance => {
Expand All @@ -54,17 +61,13 @@ const assetOutBalanceSelector = ({ swap: { balancesResponses, assetIn, assetOut
};

const tokenSwapInputSelector = (state: AllSlices) => ({
swappableAssets: state.swap.swappableAssets,
assetIn: state.swap.assetIn,
setAssetIn: state.swap.setAssetIn,
assetOut: state.swap.assetOut,
setAssetOut: state.swap.setAssetOut,
amount: state.swap.amount,
setAmount: state.swap.setAmount,
balancesResponses: state.swap.balancesResponses,
priceHistory: state.swap.priceHistory,
assetOutBalance: assetOutBalanceSelector(state),
hasStakingTokenMeta: state.swap.stakingAssetMetadata,
});

/**
Expand All @@ -76,24 +79,16 @@ const tokenSwapInputSelector = (state: AllSlices) => ({
export const TokenSwapInput = () => {
const status = useStatus();
const latestKnownBlockHeight = status.data?.latestKnownBlockHeight ?? 0n;
const {
swappableAssets,
amount,
setAmount,
assetIn,
setAssetIn,
assetOut,
setAssetOut,
balancesResponses,
priceHistory,
assetOutBalance,
hasStakingTokenMeta,
} = useStoreShallow(tokenSwapInputSelector);
const stakingTokenMetadata = useStakingTokenMetadata();
const balancesResponses = useBalancesResponses();
const swappableAssets = useSwappableAssets();
const { amount, setAmount, assetIn, setAssetIn, assetOut, setAssetOut, priceHistory } =
useStoreShallow(tokenSwapInputSelector);
// State to manage privacy warning display
const [showNonNativeFeeWarning, setshowNonNativeFeeWarning] = useState(false);
const [showNonNativeFeeWarning, setShowNonNativeFeeWarning] = useState(false);
const assetOutBalance = getAssetOutBalance(balancesResponses.data, assetIn, assetOut);

// Check if the user has native staking tokens
const stakingToken = hasStakingToken(balancesResponses, hasStakingTokenMeta);
const userHasStakingToken = hasStakingToken(balancesResponses.data, stakingTokenMetadata.data);

useEffect(() => {
if (!assetIn || !assetOut) return;
Expand Down Expand Up @@ -131,10 +126,10 @@ export const TokenSwapInput = () => {
onChange={e => {
if (!isValidAmount(e.target.value, assetIn)) return;
setAmount(e.target.value);
setshowNonNativeFeeWarning(Number(e.target.value) > 0 && !stakingToken);
setShowNonNativeFeeWarning(Number(e.target.value) > 0 && !userHasStakingToken);
}}
/>
<div className='flex gap-4 sm:contents'>
<div className='flex gap-4'>
{assetIn && (
<div className='ml-auto hidden h-full flex-col justify-end self-end sm:flex'>
<span className='mr-2 block whitespace-nowrap text-xs text-muted-foreground'>
Expand All @@ -143,21 +138,48 @@ export const TokenSwapInput = () => {
</div>
)}

<div className='flex h-full flex-col gap-2'>
<BalanceSelector value={assetIn} onChange={setAssetIn} balances={balancesResponses} />
{assetIn?.balanceView && (
<BalanceValueView valueView={assetIn.balanceView} onClick={setInputToBalanceMax} />
)}
</div>

<div className='flex flex-col gap-2 pt-2'>
<ArrowRight size={16} className='text-muted-foreground' />
</div>

<div className='flex h-full flex-col gap-2'>
<AssetSelector assets={swappableAssets} value={assetOut} onChange={setAssetOut} />
{assetOut && <BalanceValueView valueView={assetOutBalance} />}
</div>
<FadeIn condition={!!balancesResponses.error || !!swappableAssets.error}>
<div className='flex gap-4 text-red'>
{balancesResponses.error instanceof Error && balancesResponses.error.toString()}
{swappableAssets.error instanceof Error && swappableAssets.error.toString()}
</div>
</FadeIn>

<FadeIn condition={!!balancesResponses.data && !!swappableAssets.data}>
<div className='flex gap-4'>
<div className='flex h-full flex-col gap-2'>
{balancesResponses.data && (
<BalanceSelector
value={assetIn}
onChange={setAssetIn}
balances={balancesResponses.data}
/>
)}
{assetIn?.balanceView && (
<BalanceValueView
valueView={assetIn.balanceView}
onClick={setInputToBalanceMax}
/>
)}
</div>

<div className='size-4 pt-2'>
<ArrowRight size={16} className='text-muted-foreground' />
</div>

<div className='flex h-full flex-col gap-2'>
{swappableAssets.data && (
<AssetSelector
assets={swappableAssets.data}
value={assetOut}
onChange={setAssetOut}
/>
)}

{assetOut && <BalanceValueView valueView={assetOutBalance} />}
</div>
</div>
</FadeIn>
</div>
{priceHistory.startMetadata && priceHistory.endMetadata && priceHistory.candles.length ? (
<CandlestickPlot
Expand Down
42 changes: 0 additions & 42 deletions apps/minifront/src/components/swap/swap-loader.ts

This file was deleted.

4 changes: 2 additions & 2 deletions apps/minifront/src/state/ibc-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export const filterBalancesPerChain = (
registryAssets: Metadata[],
stakingTokenMetadata?: Metadata,
): BalancesResponse[] => {
const penumbraAssetId = getAssetId(stakingTokenMetadata);
const penumbraAssetId = getAssetId.optional()(stakingTokenMetadata);
const assetsWithMatchingChannel = registryAssets
.filter(a => {
const match = assetPatterns.ibc.capture(a.base);
Expand All @@ -244,6 +244,6 @@ export const filterBalancesPerChain = (
const assetIdsToCheck = [penumbraAssetId, ...assetsWithMatchingChannel];

return allBalances.filter(({ balanceView }) => {
return assetIdsToCheck.some(assetId => assetId.equals(getAssetIdFromValueView(balanceView)));
return assetIdsToCheck.some(assetId => assetId?.equals(getAssetIdFromValueView(balanceView)));
});
};
16 changes: 10 additions & 6 deletions apps/minifront/src/state/swap/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ describe('Swap Slice', () => {
expect(useStore.getState().swap.amount).toBe('');
expect(useStore.getState().swap.assetIn).toBeUndefined();
expect(useStore.getState().swap.assetOut).toBeUndefined();
expect(useStore.getState().swap.swappableAssets).toEqual([]);
expect(useStore.getState().swap.balancesResponses).toEqual([]);
expect(useStore.getState().swap.swappableAssets).toEqual(
expect.objectContaining({ data: undefined, error: undefined, loading: false }),
);
expect(useStore.getState().swap.balancesResponses).toEqual(
expect.objectContaining({ data: undefined, error: undefined, loading: false }),
);
expect(useStore.getState().swap.duration).toBe('instant');
expect(useStore.getState().swap.txInProgress).toBe(false);
});
Expand Down Expand Up @@ -102,8 +106,8 @@ describe('Swap Slice', () => {
});

useStore.setState(state => {
state.swap.swappableAssets = [metadata1, metadata2];
state.swap.balancesResponses = [
state.swap.swappableAssets.data = [metadata1, metadata2];
state.swap.balancesResponses.data = [
balancesResponseWithMetadata1,
balancesResponseWithMetadata2,
];
Expand Down Expand Up @@ -149,8 +153,8 @@ describe('Swap Slice', () => {
});

useStore.setState(state => {
state.swap.swappableAssets = [metadata1, metadata2];
state.swap.balancesResponses = [
state.swap.swappableAssets.data = [metadata1, metadata2];
state.swap.balancesResponses.data = [
balancesResponseWithMetadata1,
balancesResponseWithMetadata2,
];
Expand Down
Loading

0 comments on commit 2f0a8e4

Please sign in to comment.