Skip to content

Commit

Permalink
Rework sell amm modal
Browse files Browse the repository at this point in the history
  • Loading branch information
WRadoslaw committed Nov 2, 2023
1 parent 2769d62 commit 515a1a1
Showing 1 changed file with 122 additions and 114 deletions.
236 changes: 122 additions & 114 deletions packages/atlas/src/components/_crt/SellTokenModal/SellTokenModal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import BN from 'bn.js'
import { useCallback, useMemo } from 'react'
import { Controller, useForm } from 'react-hook-form'
import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'

import { useGetFullCreatorTokenQuery } from '@/api/queries/__generated__/creatorTokens.generated'
import { FlexBox } from '@/components/FlexBox/FlexBox'
import { JoyTokenIcon } from '@/components/JoyTokenIcon'
import { NumberFormat } from '@/components/NumberFormat'
import { Text } from '@/components/Text'
import { TextButton } from '@/components/_buttons/Button'
import { FormField } from '@/components/_inputs/FormField'
import { TokenInput } from '@/components/_inputs/TokenInput'
import { DetailsContent } from '@/components/_nft/NftTile'
import { AmmModalFormTemplate } from '@/components/_crt/AmmModalTemplates'
import { AmmModalSummaryTemplate } from '@/components/_crt/AmmModalTemplates/AmmModalSummaryTemplate'
import { DialogModal } from '@/components/_overlays/DialogModal'
import { atlasConfig } from '@/config'
import { useMediaMatch } from '@/hooks/useMediaMatch'
import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
import { useFee, useJoystream, useTokenPrice } from '@/providers/joystream'
import { hapiBnToTokenNumber, tokenNumberToHapiBn } from '@/joystream-lib/utils'
import { useFee, useJoystream } from '@/providers/joystream'
import { useSnackbar } from '@/providers/snackbars'
import { useTransaction } from '@/providers/transactions/transactions.hooks'
import { useUser } from '@/providers/user/user.hooks'
Expand All @@ -28,9 +22,8 @@ export type SellTokenModalProps = {
}

export const SellTokenModal = ({ tokenId, onClose, show }: SellTokenModalProps) => {
const [step, setStep] = useState<'form' | 'summary'>('form')
const { control, watch, handleSubmit, formState } = useForm<{ tokens: number }>()
const { convertTokensToUSD } = useTokenPrice()
const smMatch = useMediaMatch('sm')
const { memberId } = useUser()
const tokens = watch('tokens') || 0
const { joystream, proxyCallback } = useJoystream()
Expand All @@ -42,9 +35,11 @@ export const SellTokenModal = ({ tokenId, onClose, show }: SellTokenModalProps)
id: tokenId,
},
})

const currentAmm = data?.creatorTokenById?.ammCurves.find((amm) => !amm.finalized)
const title = data?.creatorTokenById?.symbol ?? 'N/A'
const userTokenBalance = 0 // todo: this will come from orion

const calculateSlippageAmount = useCallback(
(amount: number) => {
const currentAmm = data?.creatorTokenById?.ammCurves.find((amm) => !amm.finalized)
Expand All @@ -63,148 +58,161 @@ export const SellTokenModal = ({ tokenId, onClose, show }: SellTokenModalProps)
}, [tokens, calculateSlippageAmount])
const pricePerUnit = priceForAllToken / (tokens || 1)

const details = useMemo(
const formDetails = useMemo(
() => [
{
title: 'You will get',
title: 'Available balance',
content: (
<NumberFormat value={priceForAllToken} as="p" variant="t200-strong" withToken withDenomination="before" />
<NumberFormat
value={userTokenBalance}
as="p"
variant="t200"
withToken
withDenomination="before"
customTicker={`$${title}`}
denominationMultiplier={pricePerUnit}
/>
),
tooltipText: 'Lorem ipsum',
},
{
title: 'Fee',
content: <NumberFormat value={fullFee} as="p" variant="t200" withDenomination="before" withToken />,
title: 'You will receive',
content: (
<NumberFormat
value={tokens > 0 ? priceForAllToken : 0}
as="p"
variant="t200-strong"
withToken
withDenomination="before"
/>
),
tooltipText: 'Lorem ipsum',
},
],
[priceForAllToken, pricePerUnit, title, tokens]
)

const summaryDetails = useMemo(
() => [
{
title: 'You will spend',
title: 'Selling',
content: (
<NumberFormat
value={tokens}
as="p"
variant="t200"
withToken
withDenomination="before"
customTicker={`$${title}`}
denominationMultiplier={pricePerUnit}
/>
),
tooltipText: 'Lorem ipsum',
},
{
title: 'Price per unit',
content: (
<NumberFormat
value={tokenNumberToHapiBn(pricePerUnit)}
as="p"
variant="t200"
withToken
withDenomination="before"
/>
),
tooltipText: 'Lorem ipsum',
},
{
title: 'Fee',
content: <NumberFormat value={fullFee} as="p" variant="t200" withToken withDenomination="before" />,
tooltipText: 'Lorem ipsum',
},
{
title: 'You will receive',
content: (
<NumberFormat value={priceForAllToken} as="p" variant="t200-strong" withToken withDenomination="before" />
),
tooltipText: 'Lorem ipsum',
},
],
[fullFee, priceForAllToken, pricePerUnit, title, tokens]
)

const onSubmit = () =>
handleSubmit((data) => {
const slippageTolerance = calculateSlippageAmount(data.tokens)
if (!joystream || !memberId || !slippageTolerance) {
return
}
handleTransaction({
txFactory: async (updateStatus) =>
(await joystream.extrinsics).sellTokenOnMarket(
tokenId,
memberId,
String(data.tokens),
slippageTolerance.toString(),
proxyCallback(updateStatus)
),
onError: () => {
displaySnackbar({
title: 'Something went wrong',
iconType: 'error',
})
},
onTxSync: async () => {
displaySnackbar({
title: `${(tokens * priceForAllToken) / data.tokens} ${atlasConfig.joystream.tokenTicker} received`,
description: `${tokens} $${title} sold`,
})
onClose()
},
})
const onFormSubmit = () =>
handleSubmit(() => {
setStep('summary')
})()

const onTransactionSubmit = async () => {
const slippageTolerance = calculateSlippageAmount(tokens)

if (!joystream || !memberId || !slippageTolerance) {
return
}
handleTransaction({
txFactory: async (updateStatus) =>
(await joystream.extrinsics).sellTokenOnMarket(
tokenId,
memberId,
String(tokens),
slippageTolerance.toString(),
proxyCallback(updateStatus)
),
onError: () => {
displaySnackbar({
title: 'Something went wrong',
iconType: 'error',
})
},
onTxSync: async () => {
displaySnackbar({
title: `${(tokens * priceForAllToken) / tokens} ${atlasConfig.joystream.tokenTicker} received`,
description: `${tokens} $${title} sold`,
})
onClose()
},
})
}

if (!currentAmm && show) {
throw new Error('BuyAmmModal invoked on token without active amm')
}
console.log(currentAmm?.mintedByAmm)

const isFormStep = step === 'form'

return (
<DialogModal
title={`Sell $${title}`}
show={show}
onExitClick={onClose}
secondaryButton={{
text: isFormStep ? 'Cancel' : 'Back',
onClick: isFormStep ? onClose : () => setStep('summary'),
}}
primaryButton={{
text: 'Sell tokens',
onClick: onSubmit,
text: isFormStep ? 'Continue' : 'Sell tokens',
onClick: isFormStep ? onFormSubmit : onTransactionSubmit,
}}
>
<FlexBox flow="column" gap={8}>
<FlexBox gap={6} equalChildren>
<DetailsContent
avoidIconStyling
tileSize={smMatch ? 'big' : 'bigSmall'}
caption="PRICE PER UNIT"
content={pricePerUnit}
icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
withDenomination
/>
<DetailsContent
avoidIconStyling
tileSize={smMatch ? 'big' : 'bigSmall'}
caption={`YOUR $${title} BALANCE`}
content={userTokenBalance}
icon={<JoyTokenIcon size={smMatch ? 24 : 16} variant="silver" />}
withDenomination
denominationMultiplier={pricePerUnit}
/>
</FlexBox>
<Controller
name="tokens"
{step === 'form' ? (
<AmmModalFormTemplate
control={control}
rules={{
validate: {
valid: (value) => {
if (!value || value < 1) {
return 'You need to sell at least one token'
}
if (value > +(currentAmm?.mintedByAmm ?? 0)) return 'You cannot sell more tokens than minted'
if (value > userTokenBalance) return 'Amount exceeds your account balance'
return true
},
},
error={formState.errors.tokens?.message}
pricePerUnit={pricePerUnit}
maxValue={userTokenBalance}
details={formDetails}
validation={(value) => {
if (!value || value < 1) {
return 'You need to sell at least one token'
}
if (value > +(currentAmm?.mintedByAmm ?? 0)) return 'You cannot sell more tokens than minted'
if (value > userTokenBalance) return 'Amount exceeds your account balance'
return true
}}
render={({ field }) => (
<FormField label="Tokens to spend" error={formState.errors.tokens?.message}>
<TokenInput
value={field.value}
onChange={field.onChange}
placeholder="0"
nodeEnd={
<FlexBox gap={2} alignItems="baseline">
<Text variant="t300" as="p" color="colorTextMuted">
${convertTokensToUSD((field.value || 0) * pricePerUnit)?.toFixed(2)}
</Text>
<TextButton onClick={() => field.onChange(userTokenBalance)}>Max</TextButton>
</FlexBox>
}
/>
</FormField>
)}
/>

<FlexBox flow="column" gap={2}>
{details.map((row, i) => (
<FlexBox key={row.title} alignItems="center" justifyContent="space-between">
<Text variant={i + 1 === details.length ? 't200-strong' : 't200'} as="p" color="colorText">
{row.title}
</Text>
{row.content}
</FlexBox>
))}
</FlexBox>
</FlexBox>
) : (
<AmmModalSummaryTemplate details={summaryDetails} />
)}
</DialogModal>
)
}

0 comments on commit 515a1a1

Please sign in to comment.