Skip to content

Commit

Permalink
Merge pull request #322 from pendulum-chain/feature/terms-and-conditi…
Browse files Browse the repository at this point in the history
…ons-refactor

Don't show Terms And Conditions modal
  • Loading branch information
Sharqiewicz authored Dec 13, 2024
2 parents f563b90 + 1aab84a commit 79f7db5
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 113 deletions.
105 changes: 60 additions & 45 deletions src/components/TermsAndConditions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
import { useState } from 'preact/compat';
import { Button, Checkbox, Link } from 'react-daisyui';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import { Dialog } from '../Dialog';
import { AnimatePresence, motion } from 'framer-motion';
import { StateUpdater } from 'preact/hooks';
import { Checkbox, Link } from 'react-daisyui';

export const TermsAndConditions = () => {
const { set, state } = useLocalStorage<string | undefined>({ key: 'TERMS_AND_CONDITIONS' });
const [checked, setChecked] = useState<boolean>(false);
interface TermsAndConditionsProps {
toggleTermsChecked: () => void;
setTermsError: StateUpdater<boolean>;
termsChecked: boolean;
termsAccepted: boolean;
termsError: boolean;
}

const acceptTerms = () => {
set('accepted');
};

const content = (
<>
<div className="mb-5 text-lg">
<Link
style={{ textDecoration: 'underline' }}
color="accent"
target="_blank"
rel="noreferrer"
href="https://www.vortexfinance.co/terms-conditions"
>
View Terms and Conditions
</Link>
</div>
<div className="flex text-lg">
<Checkbox checked={checked} onClick={() => setChecked(!checked)} color="primary" size="md" />
<span className="pl-2">I have read and accept the terms and conditions</span>
</div>
</>
);
const fadeOutAnimation = {
scale: [1, 1.05, 0],
opacity: [1, 1, 0],
transition: { duration: 0.3 },
};

const actions = (
<Button className="w-full px-12 text-thin" color="primary" onClick={acceptTerms} disabled={!checked}>
Agree
</Button>
);
export const TermsAndConditions = (props: TermsAndConditionsProps) => {
const { termsAccepted } = props;

return (
<Dialog
content={content}
headerText="T&Cs"
visible={!state}
actions={actions}
hideCloseButton={true}
disableNativeEvents={true}
/>
);
return <AnimatePresence mode="wait">{!termsAccepted && <TermsAndConditionsContent {...props} />}</AnimatePresence>;
};

const TermsAndConditionsContent = ({
toggleTermsChecked,
setTermsError,
termsChecked,
termsError,
}: TermsAndConditionsProps) => (
<motion.div key="terms-conditions" exit={fadeOutAnimation}>
<div className="mb-5 text-sm" />
<div className="flex text-sm">
<Checkbox
checked={termsChecked}
onClick={() => {
toggleTermsChecked();
setTermsError(false);
}}
color="primary"
size="sm"
/>
<TermsText error={termsError} />
</div>
</motion.div>
);

const TermsText = ({ error }: { error: boolean }) => (
<motion.span
className={`pl-2 ${error ? 'text-red-600' : ''}`}
animate={{ scale: [1, 1.02, 1], transition: { duration: 0.2 } }}
>
I have read and accept the{' '}
<Link
href="https://www.vortexfinance.co/terms-conditions"
color={error ? 'error' : 'accent'}
className={`transition-all duration-300 ${error ? 'text-red-600 font-bold' : ''}`}
target="_blank"
rel="noreferrer"
style={{ textDecoration: 'underline' }}
>
Terms and Conditions
</Link>
</motion.span>
);
4 changes: 3 additions & 1 deletion src/hooks/nabla/useTokenAmountOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ export interface UseTokenOutAmountResult {
isLoading: boolean;
enabled: boolean;
data: TokenOutData | undefined;
error: string | null;
stableAmountInUnits: string | undefined;
}

interface TokenOutData {
export interface TokenOutData {
preciseQuotedAmountOut: ContractBalance;
roundedDownQuotedAmountOut: Big;
swapFee: ContractBalance;
Expand Down
30 changes: 30 additions & 0 deletions src/hooks/useTermsAndConditions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useState } from 'preact/hooks';
import { useLocalStorage } from './useLocalStorage';

export const useTermsAndConditions = () => {
const { set, state } = useLocalStorage<string | undefined>({ key: 'TERMS_AND_CONDITIONS' });

// Terms and Conditions are accepted only when user has submitted the swap in `confirmSwap.ts`
const [termsAccepted, setTermsAccepted] = useState<boolean>(state === 'accepted');

// termsChecked is used to determine if the Terms and Conditions checkbox is checked and the Swap form can be submitted in `swap/index.tsx`
const [termsChecked, setTermsChecked] = useState<boolean>(false);

const [termsError, setTermsError] = useState<boolean>(false);

const toggleTermsChecked = () => {
setTermsChecked((state) => !state);
};

return {
termsChecked,
toggleTermsChecked,
termsAccepted,
setTermsAccepted: (accepted: boolean) => {
set(accepted ? 'accepted' : undefined);
setTermsAccepted(accepted);
},
termsError,
setTermsError,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Big from 'big.js';

import { ContractBalance, multiplyByPowerOfTen } from '../../../../helpers/contracts';
import { InputTokenDetails, OutputTokenDetails } from '../../../../constants/tokenConfig';
import { SPACEWALK_REDEEM_SAFETY_MARGIN } from '../../../../constants/constants';

export const calculateSwapAmountsWithMargin = (
fromAmount: Big,
preciseQuotedAmountOut: ContractBalance,
inputToken: InputTokenDetails,
outputToken: OutputTokenDetails,
) => {
// Calculate output amount with margin
const outputAmountBigMargin = preciseQuotedAmountOut.preciseBigDecimal
.round(2, 0)
.mul(1 + SPACEWALK_REDEEM_SAFETY_MARGIN);
const expectedRedeemAmountRaw = multiplyByPowerOfTen(outputAmountBigMargin, outputToken.decimals).toFixed();

// Calculate input amount with margin
const inputAmountBig = Big(fromAmount);
const inputAmountBigMargin = inputAmountBig.mul(1 + SPACEWALK_REDEEM_SAFETY_MARGIN);
const inputAmountRaw = multiplyByPowerOfTen(inputAmountBigMargin, inputToken.decimals).toFixed();

return { expectedRedeemAmountRaw, inputAmountRaw };
};
92 changes: 92 additions & 0 deletions src/pages/swap/helpers/swapConfirm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { StateUpdater } from 'preact/hooks';
import { ApiPromise } from '@polkadot/api';
import Big from 'big.js';

import {
getInputTokenDetailsOrDefault,
InputTokenType,
OutputTokenType,
OUTPUT_TOKEN_CONFIG,
} from '../../../../constants/tokenConfig';

import { ExecutionInput } from '../../../../hooks/offramp/useMainProcess';
import { TokenOutData } from '../../../../hooks/nabla/useTokenAmountOut';
import { Networks } from '../../../../contexts/network';

import { calculateSwapAmountsWithMargin } from './calculateSwapAmountsWithMargin';
import { performSwapInitialChecks } from './performSwapInitialChecks';
import { validateSwapInputs } from './validateSwapInputs';

interface SwapConfirmParams {
address: `0x${string}` | undefined;
api: ApiPromise | null;
from: InputTokenType;
fromAmount: Big | undefined;
fromAmountString: string;
handleOnSubmit: (executionInput: ExecutionInput) => void;
inputAmountIsStable: boolean;
selectedNetwork: Networks;
setInitializeFailed: StateUpdater<boolean>;
setIsInitiating: StateUpdater<boolean>;
setTermsAccepted: (accepted: boolean) => void;
to: OutputTokenType;
tokenOutAmount: { data: TokenOutData | undefined };
}

export function swapConfirm(e: Event, params: SwapConfirmParams) {
e.preventDefault();

const {
address,
api,
from,
fromAmount,
fromAmountString,
handleOnSubmit,
inputAmountIsStable,
selectedNetwork,
setInitializeFailed,
setIsInitiating,
setTermsAccepted,
to,
tokenOutAmount,
} = params;

const validInputs = validateSwapInputs(inputAmountIsStable, address, fromAmount, tokenOutAmount.data);
if (!validInputs) {
return;
}

setIsInitiating(true);

const outputToken = OUTPUT_TOKEN_CONFIG[to];
const inputToken = getInputTokenDetailsOrDefault(selectedNetwork, from);

const { expectedRedeemAmountRaw, inputAmountRaw } = calculateSwapAmountsWithMargin(
validInputs.fromAmount,
validInputs.tokenOutAmountData.preciseQuotedAmountOut,
inputToken,
outputToken,
);

performSwapInitialChecks(api!, outputToken, inputToken, expectedRedeemAmountRaw, inputAmountRaw, address!)
.then(() => {
console.log('Initial checks completed. Starting process..');

// here we should set that the user has accepted the terms and conditions in the local storage
setTermsAccepted(true);

handleOnSubmit({
inputTokenType: from,
outputTokenType: to,
amountInUnits: fromAmountString,
offrampAmount: validInputs.tokenOutAmountData.roundedDownQuotedAmountOut,
setInitializeFailed,
});
})
.catch((_error) => {
console.error('Error during swap confirmation:', _error);
setIsInitiating(false);
setInitializeFailed(true);
});
}
24 changes: 24 additions & 0 deletions src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiPromise } from '@polkadot/api';

import { InputTokenDetails, OutputTokenDetails } from '../../../../constants/tokenConfig';
import { getVaultsForCurrency } from '../../../../services/phases/polkadot/spacewalk';
import { testRoute } from '../../../../services/phases/squidrouter/route';

export const performSwapInitialChecks = async (
api: ApiPromise,
outputToken: OutputTokenDetails,
fromToken: InputTokenDetails,
expectedRedeemAmountRaw: string,
inputAmountRaw: string,
address: `0x${string}`,
) => {
await Promise.all([
getVaultsForCurrency(
api,
outputToken.stellarAsset.code.hex,
outputToken.stellarAsset.issuer.hex,
expectedRedeemAmountRaw,
),
testRoute(fromToken, inputAmountRaw, address),
]);
};
28 changes: 28 additions & 0 deletions src/pages/swap/helpers/swapConfirm/validateSwapInputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TokenOutData } from '../../../../hooks/nabla/useTokenAmountOut';

type ValidSwapInputs = {
inputAmountIsStable: true;
address: string;
fromAmount: Big;
tokenOutAmountData: TokenOutData;
};

export const validateSwapInputs = (
inputAmountIsStable: boolean,
address: string | undefined,
fromAmount: Big | undefined,
tokenOutAmountData: TokenOutData | undefined,
): ValidSwapInputs | false => {
if (!inputAmountIsStable) return false;
if (!address) return false;
if (fromAmount === undefined) {
console.log('Input amount is undefined');
return false;
}
if (!tokenOutAmountData) {
console.log('Output amount is undefined');
return false;
}

return { inputAmountIsStable, address, fromAmount, tokenOutAmountData };
};
Loading

0 comments on commit 79f7db5

Please sign in to comment.