Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

implement offramp summary popup #394

Open
wants to merge 7 commits into
base: polygon-prototype-staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/components/FeeCollapse/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FC } from 'preact/compat';
import Big from 'big.js';
import { roundDownToTwoDecimals } from '../../helpers/parseNumbers';
import { OutputTokenDetails } from '../../constants/tokenConfig';
import { useEventsContext } from '../../contexts/events';
import { useOfframpFees } from '../../hooks/useOfframpFees';

export function calculateTotalReceive(toAmount: Big, outputToken: OutputTokenDetails): string {
const feeBasisPoints = outputToken.offrampFeesBasisPoints;
Expand Down Expand Up @@ -32,10 +32,7 @@ export const FeeCollapse: FC<CollapseProps> = ({ toAmount = Big(0), toToken, exc
trackEvent({ event: 'click_details' });
};

const toAmountFixed = roundDownToTwoDecimals(toAmount);
const totalReceive = calculateTotalReceive(toAmount, toToken);
const totalReceiveFormatted = roundDownToTwoDecimals(Big(totalReceive));
const feesCost = roundDownToTwoDecimals(Big(toAmountFixed || 0).sub(totalReceive));
const { toAmountFixed, totalReceiveFormatted, feesCost } = useOfframpFees(toAmount, toToken);

return (
<div className="border border-blue-700 collapse-arrow collapse" onClick={trackFeeCollapseOpen}>
Expand Down
140 changes: 140 additions & 0 deletions src/components/OfframpSummaryDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { ArrowDownIcon } from '@heroicons/react/20/solid';
import { FC } from 'preact/compat';
import Big from 'big.js';

import { InputTokenDetails, OutputTokenDetails } from '../../constants/tokenConfig';
import { UseTokenOutAmountResult } from '../../hooks/nabla/useTokenAmountOut';
import { useGetAssetIcon } from '../../hooks/useGetAssetIcon';
import { useOfframpFees } from '../../hooks/useOfframpFees';
import { useNetwork } from '../../contexts/network';
import { Networks } from '../../helpers/networks';

import { ExchangeRate } from '../ExchangeRate';
import { NetworkIcon } from '../NetworkIcon';
import { Dialog } from '../Dialog';

interface AssetDisplayProps {
amount: string;
symbol: string;
iconSrc: string;
iconAlt: string;
}

const AssetDisplay = ({ amount, symbol, iconSrc, iconAlt }: AssetDisplayProps) => (
<div className="flex items-center justify-between w-full">
<span className="text-lg font-bold">
{amount} {symbol}
</span>
<img src={iconSrc} alt={iconAlt} className="w-8 h-8" />
</div>
);

interface FeeDetailsProps {
network: Networks;
feesCost: string;
fiatSymbol: string;
tokenOutAmount: UseTokenOutAmountResult;
fromToken: InputTokenDetails;
anchorUrl?: string;
}

const FeeDetails = ({ network, feesCost, fiatSymbol, tokenOutAmount, fromToken, anchorUrl }: FeeDetailsProps) => (
<section className="mt-6">
<div className="flex justify-between mb-2">
<p>Offramp fees</p>
Copy link
Member

@ebma ebma Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add in the brackets how this offramp fee is comprised. (The code I suggested is not enough, we still need to transform the values to something easy to read for the user)

Suggested change
<p>Offramp fees</p>
<p>Offramp fee ({`${outputToken.offrampFeeBasisPoints}%` + outputToken.offrampFeesFixedComponent > 0 ? ` + {outputToken.offrampFeesFixedComponent}` : ""})</p>

<p className="flex items-center gap-2">
<NetworkIcon network={network} className="w-4 h-4" />
<strong>
{feesCost} {fiatSymbol}
</strong>
</p>
</div>
<div className="flex justify-between mb-2">
<p>Quote</p>
<p>
<strong>
<ExchangeRate tokenOutData={tokenOutAmount} fromToken={fromToken} toTokenSymbol={fiatSymbol} />
($1.00)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it's in the reference image, I think we should remove this. As far as I understand, the idea of this '($1.00)' would be to indicate the dollar value of the resulting quote. This means, in order to make this value make sense, we would need to fetch the ARS or EUR to USD exchange rate from somewhere else and compare our output token amount to that. I think this is too complex and we should rather remove it for now. If this becomes a hard requirement later, we can still add it in the future.

Suggested change
($1.00)

</strong>
</p>
</div>
<div className="flex justify-between">
<p>Partner - {anchorUrl ? new URL(anchorUrl).origin : ''}</p>
</div>
</section>
);

interface OfframpSummaryDialogProps {
fromToken: InputTokenDetails;
toToken: OutputTokenDetails;
formToAmount: string;
fromAmountString: string;
visible: boolean;
tokenOutAmount: UseTokenOutAmountResult;
anchorUrl?: string;
onSubmit: () => void;
onClose: () => void;
}

export const OfframpSummaryDialog: FC<OfframpSummaryDialogProps> = ({
fromToken,
toToken,
visible,
formToAmount,
fromAmountString,
tokenOutAmount,
anchorUrl,
onClose,
onSubmit,
}) => {
const { selectedNetwork } = useNetwork();
const fromIcon = useGetAssetIcon(fromToken.networkAssetIcon);
const toIcon = useGetAssetIcon(toToken.fiat.assetIcon);

const { feesCost } = useOfframpFees(tokenOutAmount.data?.roundedDownQuotedAmountOut || Big(0), toToken);

if (!visible) return null;
if (!anchorUrl) return null;

const content = (
<div className="flex flex-col justify-center">
<AssetDisplay
iconAlt={fromToken.networkAssetIcon}
symbol={fromToken.assetSymbol}
amount={fromAmountString}
iconSrc={fromIcon}
/>
<ArrowDownIcon className="w-4 h-4 my-2" />
<AssetDisplay amount={formToAmount} symbol={toToken.fiat.symbol} iconSrc={toIcon} iconAlt={toToken.fiat.symbol} />
<FeeDetails
fiatSymbol={toToken.fiat.symbol}
fromToken={fromToken}
tokenOutAmount={tokenOutAmount}
network={selectedNetwork}
feesCost={feesCost}
anchorUrl={anchorUrl}
/>
</div>
);
const actions = (
// eslint-disable-next-line react/jsx-no-target-blank
<a
href={anchorUrl}
target="_blank"
rel="opener" //noopener forbids the use of postMessages.
className="btn-vortex-primary btn rounded-xl"
style={{ flex: '1 1 calc(50% - 0.75rem/2)' }}
onClick={(e) => {
e.preventDefault();
onSubmit();
window.open(anchorUrl, '_blank');
}}
>
Continue with Partner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add the 'external' icon that indicates that clicking this button/link will open a new tab.

</a>
);

return (
<Dialog content={content} visible={visible} actions={actions} headerText="You're offramping" onClose={onClose} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that we should change this title since we don't want to call it 'offramp' but use the term 'sell' everywhere.

Suggested change
<Dialog content={content} visible={visible} actions={actions} headerText="You're offramping" onClose={onClose} />
<Dialog content={content} visible={visible} actions={actions} headerText="You're selling" onClose={onClose} />

);
};
18 changes: 18 additions & 0 deletions src/hooks/useOfframpFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Big from 'big.js';
import { calculateTotalReceive } from '../components/FeeCollapse';
import { roundDownToTwoDecimals } from '../helpers/parseNumbers';
import { OutputTokenDetails } from '../constants/tokenConfig';

export const useOfframpFees = (toAmount: Big, toToken: OutputTokenDetails) => {
const toAmountFixed = roundDownToTwoDecimals(toAmount);
const totalReceive = calculateTotalReceive(toAmount, toToken);
const totalReceiveFormatted = roundDownToTwoDecimals(Big(totalReceive));
const feesCost = roundDownToTwoDecimals(Big(toAmountFixed || 0).sub(totalReceive));

return {
toAmountFixed,
totalReceive,
totalReceiveFormatted,
feesCost,
};
};
57 changes: 36 additions & 21 deletions src/pages/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { swapConfirm } from './helpers/swapConfirm';
import { TrustedBy } from '../../components/TrustedBy';
import { WhyVortex } from '../../components/WhyVortex';
import { usePolkadotWalletState } from '../../contexts/polkadotWallet';
import { OfframpSummaryDialog } from '../../components/OfframpSummaryDialog';

export const SwapPage = () => {
const formRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -70,6 +71,8 @@ export const SwapPage = () => {
const [apiInitializeFailed, setApiInitializeFailed] = useState(false);
const [_, setIsReady] = useState(false);
const [showCompareFees, setShowCompareFees] = useState(false);
const [isOfframpSummaryDialogVisible, setIsOfframpSummaryDialogVisible] = useState(false);
const [isOfframpRedirectDone, setIsOfframpRedirectDone] = useState(false);
const [cachedId, setCachedId] = useState<string | undefined>(undefined);
const { trackEvent } = useEventsContext();
const { selectedNetwork, setNetworkSelectorDisabled } = useNetwork();
Expand Down Expand Up @@ -345,6 +348,11 @@ export const SwapPage = () => {
const onSwapConfirm = (e: Event) => {
e.preventDefault();

if (offrampStarted) {
setIsOfframpSummaryDialogVisible(true);
return;
}

if (!termsAccepted && !termsChecked) {
setTermsError(true);

Expand All @@ -369,10 +377,27 @@ export const SwapPage = () => {
handleOnSubmit,
setTermsAccepted,
});

setIsOfframpSummaryDialogVisible(true);
};

const main = (
<main ref={formRef}>
<OfframpSummaryDialog
fromToken={fromToken}
fromAmountString={fromAmountString}
toToken={toToken}
formToAmount={formToAmount}
tokenOutAmount={tokenOutAmount}
visible={isOfframpSummaryDialogVisible}
anchorUrl={firstSep24ResponseState?.url}
onSubmit={() => {
handleOnAnchorWindowOpen();
setIsOfframpSummaryDialogVisible(false);
setIsOfframpRedirectDone(true);
}}
onClose={() => setIsOfframpSummaryDialogVisible(false)}
/>
<SignInModal signingPending={signingPending} closeModal={handleCancel} handleSignIn={handleSign} />
<SigningBox step={offrampSigningPhase} />
<form className="px-4 py-4 mx-4 my-8 rounded-lg shadow-custom md:mx-auto md:w-96" onSubmit={onSwapConfirm}>
Expand Down Expand Up @@ -426,27 +451,17 @@ export const SwapPage = () => {
>
Compare fees
</button>

{firstSep24ResponseState?.url !== undefined ? (
// eslint-disable-next-line react/jsx-no-target-blank
<a
href={firstSep24ResponseState.url}
target="_blank"
rel="opener" //noopener forbids the use of postMessages.
className="btn-vortex-primary btn rounded-xl"
style={{ flex: '1 1 calc(50% - 0.75rem/2)' }}
onClick={handleOnAnchorWindowOpen}
// open in a tinier window
>
Continue with Partner
</a>
) : (
<SwapSubmitButton
text={offrampInitiating ? 'Confirming' : offrampStarted ? 'Processing Details' : 'Confirm'}
disabled={Boolean(getCurrentErrorMessage()) || !inputAmountIsStable || !!initializeFailedMessage} // !!initializeFailedMessage we disable when the initialize failed message is not null
pending={offrampInitiating || offrampStarted || offrampState !== undefined}
/>
)}
<SwapSubmitButton
text={
offrampInitiating
? 'Confirming'
: offrampStarted && isOfframpSummaryDialogVisible
? 'Processing'
: 'Confirm'
}
disabled={Boolean(getCurrentErrorMessage()) || !inputAmountIsStable || !!initializeFailedMessage} // !!initializeFailedMessage we disable when the initialize failed message is not null
pending={offrampInitiating || (offrampStarted && isOfframpRedirectDone) || offrampState !== undefined}
/>
</div>
<hr className="mt-6 mb-3" />
<PoweredBy />
Expand Down
Loading