Skip to content

Commit

Permalink
fix: swap options settings added
Browse files Browse the repository at this point in the history
  • Loading branch information
siandreev committed May 27, 2024
1 parent d665aff commit c21a9da
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 25 deletions.
3 changes: 2 additions & 1 deletion packages/core/src/Keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export enum AppKey {
PRO_BACKUP = 'pro_backup',

SIGNER_MESSAGE = 'signer_message',
SWAP_CUSTOM_ASSETS = 'swap_custom_assets'
SWAP_CUSTOM_ASSETS = 'swap_custom_assets',
SWAP_OPTIONS = 'swap_options'
}
43 changes: 43 additions & 0 deletions packages/uikit/src/components/shared/RadioFlatInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { styled } from 'styled-components';
import { BorderSmallResponsive } from './Styles';
import { Body2Class } from '../Text';
import React, { forwardRef, useId } from 'react';

const LabelStyled = styled.label`
cursor: pointer;
${BorderSmallResponsive};
${Body2Class};
box-sizing: border-box;
text-align: center;
padding: 8px 12px;
background: ${p => p.theme.fieldBackground};
border: 1px solid transparent;
transition: border-color 0.15s ease-in-out;
`;
const InputStyled = styled.input`
display: none;
&:checked + ${LabelStyled} {
border: 1px solid ${p => p.theme.accentBlue};
}
`;

export const RadioFlatInput = forwardRef<HTMLInputElement, React.JSX.IntrinsicElements['input']>(
(props, ref) => {
const fallbackId = useId();
const id = props.id || fallbackId;
const { className, children, ...rest } = props;

return (
<>
<InputStyled type="radio" ref={ref} id={id} {...rest} />
<LabelStyled className={className} htmlFor={id}>
{children}
</LabelStyled>
</>
);
}
);
6 changes: 6 additions & 0 deletions packages/uikit/src/components/shared/Styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { css } from 'styled-components';

export const BorderSmallResponsive = css`
border-radius: ${p =>
p.theme.displayType === 'full-width' ? p.theme.corner2xSmall : p.theme.cornerSmall};
`;
8 changes: 4 additions & 4 deletions packages/uikit/src/components/swap/SwapButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
useSelectedSwap,
useSwapFromAmount,
useSwapFromAsset,
useSwapOptions,
useSwapPriceImpact
} from '../../state/swap/useSwapForm';
import { useCalculatedSwap } from '../../state/swap/useCalculatedSwap';
import { FC } from 'react';
import { useIsActiveWalletLedger } from '../../state/ledger';
import { useSwapOptions } from '../../state/swap/useSwapOptions';

export const SwapButton: FC<{ onClick: () => void; isEncodingProcess: boolean }> = ({
onClick,
Expand All @@ -24,7 +24,7 @@ export const SwapButton: FC<{ onClick: () => void; isEncodingProcess: boolean }>
const [selectedSwap] = useSelectedSwap();

const priceImpact = useSwapPriceImpact();
const [{ maxPriceImpact }] = useSwapOptions();
const { data: swapOptions } = useSwapOptions();

const isNotCompleted = useIsSwapFormNotCompleted();
const activeIsLedger = useIsActiveWalletLedger();
Expand Down Expand Up @@ -53,7 +53,7 @@ export const SwapButton: FC<{ onClick: () => void; isEncodingProcess: boolean }>
);
}

if ((isFetching && !selectedSwap?.trade) || !max || priceImpact === undefined) {
if ((isFetching && !selectedSwap?.trade) || !max || priceImpact === undefined || !swapOptions) {
return (
<Button size="large" secondary loading={true}>
Continue
Expand All @@ -79,7 +79,7 @@ export const SwapButton: FC<{ onClick: () => void; isEncodingProcess: boolean }>
);
}

const priceImpactTooHigh = priceImpact?.gt(maxPriceImpact);
const priceImpactTooHigh = priceImpact?.gt(swapOptions.maxPriceImpact);
if (priceImpactTooHigh) {
return (
<Button size="large" secondary disabled>
Expand Down
203 changes: 203 additions & 0 deletions packages/uikit/src/components/swap/SwapSettingsNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import React, { FC, useLayoutEffect, useState } from 'react';
import { styled } from 'styled-components';
import { Body2, Body3, Label2 } from '../Text';
import { RadioFlatInput } from '../shared/RadioFlatInput';
import { Notification } from '../Notification';
import { InputBlock, InputField } from '../fields/Input';
import { BorderSmallResponsive } from '../shared/Styles';
import { Button } from '../fields/Button';
import { useMutateSwapOptions, useSwapOptions } from '../../state/swap/useSwapOptions';
import { SpinnerIcon } from '../Icon';

export const SwapSettingsNotification: FC<{
isOpen: boolean;
onClose: (confirmed?: boolean) => void;
}> = ({ isOpen, onClose }) => {
return (
<>
<Notification isOpen={isOpen} handleClose={onClose} title="Settings">
{() => <SwapSettingsNotificationContent onClose={onClose} />}
</Notification>
</>
);
};

const SlippageToleranceTextWrapper = styled.div`
padding-bottom: 10px;
> * {
display: block;
}
> ${Body3} {
color: ${p => p.theme.textSecondary};
}
`;

const SlippageOptionsContainer = styled.div`
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
`;

const RadioFlatInputStyled = styled(RadioFlatInput)`
width: 80px;
height: 36px;
flex-shrink: 0;
`;

const ButtonsContainer = styled.div`
display: flex;
gap: 0.5rem;
> * {
flex: 1;
}
`;

const slippagePercentValues = [0.5, 1, 3];

const InputBlockStyled = styled(InputBlock)`
min-height: unset;
height: fit-content;
padding: 0 12px;
${BorderSmallResponsive};
display: flex;
align-items: center;
`;

const InputFieldStyled = styled(InputField)`
width: 100%;
padding: 8px 0;
height: 20px;
box-sizing: content-box;
`;

const LoadingContainer = styled.div`
height: 200px;
display: flex;
align-items: center;
justify-content: center;
`;

const SwapSettingsNotificationContent: FC<{ onClose: () => void }> = ({ onClose }) => {
const { data: swapOptions } = useSwapOptions();
const [isTouched, setIsTouched] = useState(false);
const { mutate } = useMutateSwapOptions();
const [input, setInput] = useState('');
const [inputFocus, setInputFocus] = useState(false);
const [checkedRadioValue, setCheckedRadioValue] = useState<
(typeof slippagePercentValues)[number] | undefined
>();

let isValid = !isTouched;
if (isFinite(Number(input))) {
const inputNum = Number(input);
if (inputNum >= 0 && inputNum <= 100 && inputNum > 0.1) {
isValid = true;
}
}

if (checkedRadioValue) {
isValid = true;
}

useLayoutEffect(() => {
if (swapOptions?.slippagePercent) {
if (slippagePercentValues.includes(swapOptions?.slippagePercent)) {
setCheckedRadioValue(swapOptions?.slippagePercent);
} else {
setInput(swapOptions?.slippagePercent.toString());
setInputFocus(true);
}
}
}, [swapOptions?.slippagePercent]);

useLayoutEffect(() => {
if (checkedRadioValue) {
setInputFocus(false);
}
}, [checkedRadioValue]);

if (!swapOptions) {
return (
<LoadingContainer>
<SpinnerIcon />
</LoadingContainer>
);
}

const onFocus = () => {
setCheckedRadioValue(undefined);
setInputFocus(true);
};

const onBlur = () => {
setIsTouched(true);
if (!isValid) {
setInputFocus(false);
}
};

const formValue = checkedRadioValue ?? Number(input);

const onSave = () => {
mutate({ slippagePercent: formValue });
onClose?.();
};

return (
<>
<SlippageToleranceTextWrapper>
<Label2>Slippage Tolerance</Label2>
<Body3>The amount the price can change unfavorably before the trade reverts.</Body3>
</SlippageToleranceTextWrapper>
<SlippageOptionsContainer>
{slippagePercentValues.map(value => (
<RadioFlatInputStyled
key={value}
name="slippage-percent"
value={value}
checked={checkedRadioValue === value}
onChange={() => setCheckedRadioValue(value)}
>
{value}%
</RadioFlatInputStyled>
))}
<InputBlockStyled valid={isValid} focus={inputFocus}>
<InputFieldStyled
onChange={e => {
if (
/^[0-9]{1,2}([.,][0-9]{0,2})?$/.test(e.target.value) ||
!e.target.value
) {
setIsTouched(true);
setInput(e.target.value.replace(',', '.'));
}
}}
onFocus={onFocus}
onBlur={onBlur}
value={input}
placeholder="Custom"
inputMode="decimal"
/>
<Body2>%</Body2>
</InputBlockStyled>
</SlippageOptionsContainer>
<ButtonsContainer>
<Button secondary onClick={onClose}>
Cancel
</Button>
<Button
primary
disabled={
(!isValid && !checkedRadioValue) ||
formValue === swapOptions.slippagePercent
}
onClick={onSave}
>
Save
</Button>
</ButtonsContainer>
</>
);
};
14 changes: 9 additions & 5 deletions packages/uikit/src/components/swap/SwapTransactionInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
priceImpactStatus,
useIsSwapFormNotCompleted,
useSelectedSwap,
useSwapOptions,
useSwapPriceImpact
} from '../../state/swap/useSwapForm';
import { useCalculatedSwap } from '../../state/swap/useCalculatedSwap';
import { getDecimalSeparator } from '@tonkeeper/core/dist/utils/formatting';
import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount';
import { useSwapOptions } from '../../state/swap/useSwapOptions';

const TxInfoContainer = styled.div``;

Expand Down Expand Up @@ -96,7 +96,7 @@ export const SwapTransactionInfo = () => {
const { isFetching } = useCalculatedSwap();
const [swap] = useSelectedSwap();
const priceImpact = useSwapPriceImpact();
const [{ slippagePercent }] = useSwapOptions();
const { data: swapOptions } = useSwapOptions();
const isNotCompleted = useIsSwapFormNotCompleted();

const priceImpactId = useId();
Expand Down Expand Up @@ -157,15 +157,15 @@ export const SwapTransactionInfo = () => {
</div>
<Tooltip id={minimumReceivedId}>TODO</Tooltip>
<InfoRowRight>
{!trade ? (
{!trade || !swapOptions ? (
<InfoSkeleton />
) : (
<Body3>
≈&nbsp;
{
new AssetAmount({
weiAmount: trade!.to.weiAmount
.multipliedBy(100 - slippagePercent)
.multipliedBy(100 - swapOptions.slippagePercent)
.div(100),
asset: trade!.to.asset
}).stringAssetRelativeAmount
Expand All @@ -181,7 +181,11 @@ export const SwapTransactionInfo = () => {
</div>
<Tooltip id={slippageId}>TODO</Tooltip>
<InfoRowRight>
{!trade ? <InfoSkeleton /> : <Body3>{slippagePercent}%</Body3>}
{!trade || !swapOptions ? (
<InfoSkeleton />
) : (
<Body3>{swapOptions.slippagePercent}%</Body3>
)}
</InfoRowRight>
</InfoRow>
<InfoRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IconButton } from '../../fields/IconButton';
import { SlidersIcon } from '../../Icon';
import { styled } from 'styled-components';
import { SwapSettingsNotification } from '../SwapSettingsNotification';
import { useState } from 'react';

const IconButtonStyled = styled(IconButton)`
padding: 10px;
Expand All @@ -12,9 +14,14 @@ const IconButtonStyled = styled(IconButton)`
`;

export const SwapSettingsButton = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<IconButtonStyled transparent>
<SlidersIcon />
</IconButtonStyled>
<>
<IconButtonStyled transparent onClick={() => setIsOpen(true)}>
<SlidersIcon />
</IconButtonStyled>
<SwapSettingsNotification isOpen={isOpen} onClose={() => setIsOpen(false)} />
</>
);
};
Loading

0 comments on commit c21a9da

Please sign in to comment.