diff --git a/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx b/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx index 225aafdca5..e6cb986d3b 100644 --- a/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx +++ b/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx @@ -4,6 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import { SvgAlertsInformative32, SvgLogoChangenow } from '@/assets/icons' import { FailedStep } from '@/components/ChangeNowModal/steps/FailedStep' import { SwapExpired } from '@/components/ChangeNowModal/steps/SwapExpired' +import { TransactionTimeout } from '@/components/ChangeNowModal/steps/TransactionTimeout' import { Spinner } from '@/components/_loaders/Spinner' import { DialogButtonProps } from '@/components/_overlays/Dialog' import { DialogModal } from '@/components/_overlays/DialogModal' @@ -61,7 +62,14 @@ export const ChangeNowModal = ({ type, onClose }: ChangeNowModalProps) => { }, [step, type]) const secondaryButton = useMemo(() => { - if ([ChangeNowModalStep.INFO, ChangeNowModalStep.SWAP_EXPIRED, ChangeNowModalStep.FAILED].includes(step)) { + if ( + [ + ChangeNowModalStep.INFO, + ChangeNowModalStep.SWAP_EXPIRED, + ChangeNowModalStep.TIMEOUT, + ChangeNowModalStep.FAILED, + ].includes(step) + ) { return { text: 'Cancel', onClick: () => onClose(), @@ -110,8 +118,10 @@ export const ChangeNowModal = ({ type, onClose }: ChangeNowModalProps) => { + [ChangeNowModalStep.SWAP_EXPIRED, ChangeNowModalStep.TIMEOUT, ChangeNowModalStep.FAILED].includes(step) ? ( + ) : type === 'sell' ? ( 'Cashout JOY' ) : ( @@ -119,7 +129,14 @@ export const ChangeNowModal = ({ type, onClose }: ChangeNowModalProps) => { ) } show - dividers={![ChangeNowModalStep.INFO, ChangeNowModalStep.SWAP_EXPIRED, ChangeNowModalStep.FAILED].includes(step)} + dividers={ + ![ + ChangeNowModalStep.INFO, + ChangeNowModalStep.SWAP_EXPIRED, + ChangeNowModalStep.FAILED, + ChangeNowModalStep.TIMEOUT, + ].includes(step) + } onExitClick={step === ChangeNowModalStep.SWAP_EXPIRED ? undefined : () => onClose()} primaryButton={ primaryButtonProps @@ -142,6 +159,9 @@ export const ChangeNowModal = ({ type, onClose }: ChangeNowModalProps) => { )} {step === ChangeNowModalStep.SWAP_EXPIRED && } + {step === ChangeNowModalStep.TIMEOUT && transactionData.current && ( + + )} {step === ChangeNowModalStep.FAILED && transactionData.current && ( )} diff --git a/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx b/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx index a077e78ca6..84ea80a374 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx +++ b/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import QRCode from 'qrcode.react' -import { useMemo, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import { useQuery } from 'react-query' import { SvgActionChevronR, SvgAlertsSuccess24 } from '@/assets/icons' @@ -64,6 +64,7 @@ export const ProgressStep = ({ const steps = isSellingJoy ? sellSteps : buySteps const { trackChangenowTokenSold, trackChangenowTokenBought } = useSegmentAnalytics() const { memberId } = useUser() + const mountTimestamp = useRef(Date.now()) const { data } = useQuery( ['getTransactionStatus', transactionData.id], () => changeNowService.getTransactionStatus(transactionData.id).then((res) => res.data), @@ -73,6 +74,12 @@ export const ProgressStep = ({ if (data.status === 'failed') { goToStep(ChangeNowModalStep.FAILED) } + + // if transaction doesn't fail or succeed after 26 min + // the issue might be on API side, we should not waste more time waiting + if (retry && Date.now() - mountTimestamp.current > 26 * 60 * 1000) { + goToStep(ChangeNowModalStep.TIMEOUT) + } }, } ) @@ -160,6 +167,7 @@ export const ProgressStep = ({ hideStepNumberText={currentStep !== idx} title={currentStep === idx ? stepText : ''} variant={currentStep < idx ? 'future' : currentStep === idx ? 'current' : 'completed'} + showOtherStepsOnMobile /> ))} @@ -172,10 +180,7 @@ export const ProgressStep = ({ Exchange ID: - window.open(`https://changenow.io/exchange/txs/${transactionData.id}`, '_blank')} - > + {transactionData.id} diff --git a/packages/atlas/src/components/ChangeNowModal/steps/TransactionTimeout.tsx b/packages/atlas/src/components/ChangeNowModal/steps/TransactionTimeout.tsx new file mode 100644 index 0000000000..f1441908bc --- /dev/null +++ b/packages/atlas/src/components/ChangeNowModal/steps/TransactionTimeout.tsx @@ -0,0 +1,22 @@ +import { FlexBox } from '@/components/FlexBox' +import { Text } from '@/components/Text' +import { TextButton } from '@/components/_buttons/Button' +import { atlasConfig } from '@/config' + +type TransactionTimeoutProps = { + transactionId: string +} +export const TransactionTimeout = ({ transactionId }: TransactionTimeoutProps) => { + return ( + + + Transaction timed out + + + {atlasConfig.general.appName} did not receive any transaction status update from ChangeNOW for 25 minutes. + Transaction might have succeeded and we just don't know about it. Double check transaction status on ChangeNOW + site by clicking this button. + + + ) +} diff --git a/packages/atlas/src/components/ChangeNowModal/steps/types.ts b/packages/atlas/src/components/ChangeNowModal/steps/types.ts index 21c3a16226..7e7c63e954 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/types.ts +++ b/packages/atlas/src/components/ChangeNowModal/steps/types.ts @@ -6,6 +6,7 @@ export enum ChangeNowModalStep { SUMMARY, PROGRESS, SWAP_EXPIRED, + TIMEOUT, FAILED, }