diff --git a/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx b/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx index 24d4e9171b..225aafdca5 100644 --- a/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx +++ b/packages/atlas/src/components/ChangeNowModal/ChangeNowModal.tsx @@ -72,6 +72,7 @@ export const ChangeNowModal = ({ type, onClose }: ChangeNowModalProps) => { if (primaryButtonProps) { return { text: 'Go to dashboard', + onClick: () => onClose(), to: absoluteRoutes.viewer.portfolio(), } } diff --git a/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx b/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx index dbf238af9c..98908d9326 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx +++ b/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx @@ -14,6 +14,8 @@ import { FormField } from '@/components/_inputs/FormField' import { Input } from '@/components/_inputs/Input' import { TokenInput, TokenInputProps } from '@/components/_inputs/TokenInput' import { Spinner } from '@/components/_loaders/Spinner' +import { hapiBnToTokenNumber } from '@/joystream-lib/utils' +import { useSubscribeAccountBalance } from '@/providers/joystream' import { useSnackbar } from '@/providers/snackbars' import { square } from '@/styles' import { @@ -23,6 +25,7 @@ import { changeNowService, } from '@/utils/ChangeNowService' import { SentryLogger } from '@/utils/logs' +import { formatSmallDecimal } from '@/utils/number' type CurrencyInputValues = { amount: number @@ -46,10 +49,13 @@ type FormStepProps = { export const FormStep = ({ setPrimaryButtonProps, onSubmit, type, initialValues }: FormStepProps) => { const { displaySnackbar } = useSnackbar() + const { accountBalance } = useSubscribeAccountBalance() const [isLoadingRate, setIsLoadingRate] = useState<'to' | 'from' | null>(null) const debouncedExchangeEstimation = useRef Promise > | null>(null) + const isSellingJoy = type === 'sell' + const { data } = useQuery('changenow-currency', () => changeNowService.getAvailableCurrencies(), { onSuccess: (data) => { const currencyOptions = data.map((curr) => ({ @@ -179,6 +185,12 @@ export const FormStep = ({ setPrimaryButtonProps, onSubmit, type, initialValues if (!value.currency) { return 'Please choose currency' } + + if (isSellingJoy && accountBalance) { + if (value.amount > hapiBnToTokenNumber(accountBalance)) { + return 'Amount exceeds your balance' + } + } }, }} render={({ field: { value, onChange } }) => { @@ -285,7 +297,7 @@ export const FormStep = ({ setPrimaryButtonProps, onSubmit, type, initialValues /> {from.currency && to.currency && ( - Estimated rate: 1 {from.currency.toUpperCase()} ~ {to.amount / from.amount || 'N/A'}{' '} + Estimated rate: 1 {from.currency.toUpperCase()} ~ {formatSmallDecimal(to.amount / from.amount) || 'N/A'}{' '} {to.currency.toUpperCase()} )} diff --git a/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx b/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx index a351bd3642..2c12f0da6e 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx +++ b/packages/atlas/src/components/ChangeNowModal/steps/ProgressStep.tsx @@ -50,7 +50,13 @@ type ProgressStepProps = { transactionData: TransactionData } & CommonProps -export const ProgressStep = ({ transactionData, type, setPrimaryButtonProps, goToStep }: ProgressStepProps) => { +export const ProgressStep = ({ + transactionData, + type, + setPrimaryButtonProps, + goToStep, + onClose, +}: ProgressStepProps) => { const [retry, setRetry] = useState(true) const isSellingJoy = type === 'sell' const steps = isSellingJoy ? sellSteps : buySteps @@ -118,13 +124,13 @@ export const ProgressStep = ({ transactionData, type, setPrimaryButtonProps, goT setRetry(false) setPrimaryButtonProps({ text: 'Close', - onClick: () => undefined, + onClick: () => onClose(), }) step = steps.length extraContent = successText } return [step, steps[step]?.[1], extraContent] - }, [data, isSellingJoy, setPrimaryButtonProps, steps, transactionData.hasAutomaticTransactionSucceeded]) + }, [data, isSellingJoy, onClose, setPrimaryButtonProps, steps, transactionData.hasAutomaticTransactionSucceeded]) return ( diff --git a/packages/atlas/src/components/ChangeNowModal/steps/SummaryStep.tsx b/packages/atlas/src/components/ChangeNowModal/steps/SummaryStep.tsx index b720fd87b7..3518377791 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/SummaryStep.tsx +++ b/packages/atlas/src/components/ChangeNowModal/steps/SummaryStep.tsx @@ -20,6 +20,7 @@ import { cVar, sizes } from '@/styles' import { changeNowService } from '@/utils/ChangeNowService' import { formatJoystreamAddress } from '@/utils/address' import { shortenString } from '@/utils/misc' +import { formatSmallDecimal } from '@/utils/number' import { formatDurationShort, getTimeDiffInSeconds } from '@/utils/time' import { FormData } from './FormStep' @@ -43,6 +44,7 @@ export const SummaryStep = ({ }: SummaryStepProps) => { const [termsAccepted, setTermsAccepted] = useState(false) const [error, setError] = useState('') + const [loading, setLoading] = useState(false) const [timeDiff, setTimeDiff] = useState(undefined) const { activeMembership } = useUser() const { currentUser } = useAuth() @@ -61,6 +63,7 @@ export const SummaryStep = ({ } const isSellingJoy = type === 'sell' const refundAddress = isSellingJoy ? activeMembership.controllerAccount : undefined + setLoading(true) const txData = await changeNowService .createExchangeTransaction({ refundAddress, @@ -74,6 +77,7 @@ export const SummaryStep = ({ }) .then((res) => res.data) .catch(() => { + setLoading(false) displaySnackbar({ title: 'Transaction creation failed', description: 'Please try again, if the problem persists contact support.', @@ -81,6 +85,7 @@ export const SummaryStep = ({ }) if (!txData) { + setLoading(false) return } @@ -110,6 +115,9 @@ export const SummaryStep = ({ }) goToStep(ChangeNowModalStep.PROGRESS) }, + onError: () => { + setLoading(false) + }, }) } else { goToStep(ChangeNowModalStep.PROGRESS) @@ -136,7 +144,8 @@ export const SummaryStep = ({ useEffect(() => { setPrimaryButtonProps({ - text: 'Next', + text: loading ? 'Next' : 'Waiting...', + disabled: loading, onClick: async () => { if (termsAccepted && validUntil) { const timeDiff = getTimeDiffInSeconds(new Date(validUntil)) @@ -150,7 +159,7 @@ export const SummaryStep = ({ } }, }) - }, [goToStep, onTransactionSubmit, setPrimaryButtonProps, termsAccepted, validUntil]) + }, [goToStep, loading, onTransactionSubmit, setPrimaryButtonProps, termsAccepted, validUntil]) useMountEffect(() => { if (!validUntil) { @@ -204,14 +213,16 @@ export const SummaryStep = ({ - - - Estimated Arrival - - - {estimatedArrival ? new Date(estimatedArrival).toDateString() : 'N/A'} - - + {estimatedArrival ? ( + + + Estimated Arrival + + + {new Date(estimatedArrival).toDateString()} + + + ) : null} @@ -227,7 +238,7 @@ export const SummaryStep = ({ Estimated Rate - 1 {fromTicker} ~ {to.amount / from.amount} {toTicker} + 1 {fromTicker} ~ {formatSmallDecimal(to.amount / from.amount)} {toTicker} diff --git a/packages/atlas/src/utils/number.ts b/packages/atlas/src/utils/number.ts index b68eb2ca85..32905fae5b 100644 --- a/packages/atlas/src/utils/number.ts +++ b/packages/atlas/src/utils/number.ts @@ -6,7 +6,7 @@ export const getRandomIntInclusive = (min: number, max: number) => { } const numberFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }) -const smallDecimalFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 6 }) +const smallDecimalFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 10 }) export const formatNumber = (num: number): string => { return numberFormatter.format(num).replaceAll(',', ' ')