Skip to content

Commit

Permalink
fix(frontend): It is ambiguous on what scale is the withdrawal and de…
Browse files Browse the repository at this point in the history
…posit input (#2817)

* fix(frontend): asset scale consistency in liquidity dialogs.

* Ensure asset scale consistency when displaying and withdrawing liquidity by adding asset info to the liquidity dialog component and updating the input handling in Rafiki Admin UI.
---------

Co-authored-by: Blair Currey <[email protected]>
  • Loading branch information
Emanuel-Palestino and BlairCurrey authored Jul 31, 2024
1 parent af6bae3 commit 9b7fcdc
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 23 deletions.
44 changes: 41 additions & 3 deletions packages/frontend/app/components/LiquidityDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
import { Dialog } from '@headlessui/react'
import { Form } from '@remix-run/react'
import type { ChangeEvent } from 'react'
import { useState } from 'react'
import { XIcon } from '~/components/icons'
import { Button, Input } from '~/components/ui'

type BasicAsset = {
code: string
scale: number
}

type LiquidityDialogProps = {
title: string
onClose: () => void
type: 'Deposit' | 'Withdraw'
asset: BasicAsset
}

export const LiquidityDialog = ({
title,
onClose,
type
type,
asset
}: LiquidityDialogProps) => {
const [actualAmount, setActualAmount] = useState<number>(0)
const [errorMessage, setErrorMessage] = useState<string>('')

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const userInput = e.target.value
const scaledInput = parseFloat(userInput) * Math.pow(10, asset.scale)
const integerScaledInput = Math.floor(scaledInput)
if (scaledInput < 0) {
const error = 'The amount should be a positive value'
setErrorMessage(error)
} else if (scaledInput !== integerScaledInput) {
const error = 'The asset scale cannot accomodate this value'
setErrorMessage(error)
} else {
setErrorMessage('')
}
setActualAmount(integerScaledInput)
}

return (
<Dialog as='div' className='relative z-10' onClose={onClose} open={true}>
<div className='fixed inset-0 bg-tealish/30 bg-opacity-75 transition-opacity' />
Expand All @@ -38,13 +66,23 @@ export const LiquidityDialog = ({
{title}
</Dialog.Title>
<div className='mt-2'>
<Input
required
type='number'
name='displayAmount'
label='Amount'
onChange={handleChange}
addOn={asset.code}
step='any'
error={errorMessage}
/>
<Form method='post' replace preventScrollReset>
<Input
required
min={1}
type='number'
type='hidden'
name='amount'
label='Amount'
value={actualAmount}
/>
<div className='flex justify-end py-3'>
<Button aria-label={`${type} liquidity`} type='submit'>
Expand Down
27 changes: 22 additions & 5 deletions packages/frontend/app/routes/assets.$assetId.deposit-liquidity.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { type ActionFunctionArgs } from '@remix-run/node'
import { useNavigate } from '@remix-run/react'
import { json, type ActionFunctionArgs } from '@remix-run/node'
import { useLoaderData, useNavigate } from '@remix-run/react'
import { v4 } from 'uuid'
import { LiquidityDialog } from '~/components/LiquidityDialog'
import { depositAssetLiquidity } from '~/lib/api/asset.server'
import { depositAssetLiquidity, getAssetInfo } from '~/lib/api/asset.server'
import { messageStorage, setMessageAndRedirect } from '~/lib/message.server'
import { amountSchema } from '~/lib/validate.server'
import { redirectIfUnauthorizedAccess } from '../lib/kratos_checks.server'
import { type LoaderFunctionArgs } from '@remix-run/node'
import { z } from 'zod'

export const loader = async ({ request }: LoaderFunctionArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const cookies = request.headers.get('cookie')
await redirectIfUnauthorizedAccess(request.url, cookies)
return null

const assetId = params.assetId

const result = z.string().uuid().safeParse(assetId)
if (!result.success) {
throw json(null, { status: 400, statusText: 'Invalid asset ID.' })
}

const asset = await getAssetInfo({ id: result.data })

if (!asset) {
throw json(null, { status: 404, statusText: 'Asset not found.' })
}

return json({ asset })
}

export default function AssetDepositLiquidity() {
const navigate = useNavigate()
const dismissDialog = () => navigate('..', { preventScrollReset: true })
const { asset } = useLoaderData<typeof loader>()

return (
<LiquidityDialog
onClose={dismissDialog}
title='Deposit asset liquidity'
type='Deposit'
asset={{ code: asset.code, scale: asset.scale }}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { type ActionFunctionArgs } from '@remix-run/node'
import { useNavigate } from '@remix-run/react'
import { json, type ActionFunctionArgs } from '@remix-run/node'
import { useLoaderData, useNavigate } from '@remix-run/react'
import { v4 } from 'uuid'
import { LiquidityDialog } from '~/components/LiquidityDialog'
import { withdrawAssetLiquidity } from '~/lib/api/asset.server'
import { getAssetInfo, withdrawAssetLiquidity } from '~/lib/api/asset.server'
import { messageStorage, setMessageAndRedirect } from '~/lib/message.server'
import { amountSchema } from '~/lib/validate.server'
import { redirectIfUnauthorizedAccess } from '~/lib/kratos_checks.server'
import { type LoaderFunctionArgs } from '@remix-run/node'
import { z } from 'zod'

export const loader = async ({ request }: LoaderFunctionArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const cookies = request.headers.get('cookie')
await redirectIfUnauthorizedAccess(request.url, cookies)
return null

const assetId = params.assetId

const result = z.string().uuid().safeParse(assetId)
if (!result.success) {
throw json(null, { status: 400, statusText: 'Invalid asset ID.' })
}

const asset = await getAssetInfo({ id: result.data })

if (!asset) {
throw json(null, { status: 404, statusText: 'Asset not found.' })
}

return json({ asset })
}

export default function AssetWithdrawLiquidity() {
const navigate = useNavigate()
const dismissDialog = () => navigate('..', { preventScrollReset: true })
const { asset } = useLoaderData<typeof loader>()

return (
<LiquidityDialog
onClose={dismissDialog}
title='Withdraw asset liquidity'
type='Withdraw'
asset={{ code: asset.code, scale: asset.scale }}
/>
)
}
Expand Down
27 changes: 22 additions & 5 deletions packages/frontend/app/routes/peers.$peerId.deposit-liquidity.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { type ActionFunctionArgs } from '@remix-run/node'
import { useNavigate } from '@remix-run/react'
import { json, type ActionFunctionArgs } from '@remix-run/node'
import { useLoaderData, useNavigate } from '@remix-run/react'
import { v4 } from 'uuid'
import { LiquidityDialog } from '~/components/LiquidityDialog'
import { depositPeerLiquidity } from '~/lib/api/peer.server'
import { depositPeerLiquidity, getPeer } from '~/lib/api/peer.server'
import { messageStorage, setMessageAndRedirect } from '~/lib/message.server'
import { amountSchema } from '~/lib/validate.server'
import { redirectIfUnauthorizedAccess } from '../lib/kratos_checks.server'
import { type LoaderFunctionArgs } from '@remix-run/node'
import { z } from 'zod'

export const loader = async ({ request }: LoaderFunctionArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const cookies = request.headers.get('cookie')
await redirectIfUnauthorizedAccess(request.url, cookies)
return null

const peerId = params.peerId

const result = z.string().uuid().safeParse(peerId)
if (!result.success) {
throw json(null, { status: 400, statusText: 'Invalid peer ID.' })
}

const peer = await getPeer({ id: result.data })

if (!peer) {
throw json(null, { status: 400, statusText: 'Peer not found.' })
}

return json({ asset: peer.asset })
}

export default function PeerDepositLiquidity() {
const navigate = useNavigate()
const dismissDialog = () => navigate('..', { preventScrollReset: true })
const { asset } = useLoaderData<typeof loader>()

return (
<LiquidityDialog
onClose={dismissDialog}
title='Deposit peer liquidity'
type='Deposit'
asset={{ code: asset.code, scale: asset.scale }}
/>
)
}
Expand Down
27 changes: 22 additions & 5 deletions packages/frontend/app/routes/peers.$peerId.withdraw-liquidity.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { type ActionFunctionArgs } from '@remix-run/node'
import { useNavigate } from '@remix-run/react'
import { json, type ActionFunctionArgs } from '@remix-run/node'
import { useLoaderData, useNavigate } from '@remix-run/react'
import { v4 } from 'uuid'
import { LiquidityDialog } from '~/components/LiquidityDialog'
import { withdrawPeerLiquidity } from '~/lib/api/peer.server'
import { getPeer, withdrawPeerLiquidity } from '~/lib/api/peer.server'
import { messageStorage, setMessageAndRedirect } from '~/lib/message.server'
import { amountSchema } from '~/lib/validate.server'
import { redirectIfUnauthorizedAccess } from '../lib/kratos_checks.server'
import { type LoaderFunctionArgs } from '@remix-run/node'
import { z } from 'zod'

export const loader = async ({ request }: LoaderFunctionArgs) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const cookies = request.headers.get('cookie')
await redirectIfUnauthorizedAccess(request.url, cookies)
return null

const peerId = params.peerId

const result = z.string().uuid().safeParse(peerId)
if (!result.success) {
throw json(null, { status: 400, statusText: 'Invalid peer ID.' })
}

const peer = await getPeer({ id: result.data })

if (!peer) {
throw json(null, { status: 400, statusText: 'Peer not found.' })
}

return json({ asset: peer.asset })
}

export default function PeerWithdrawLiquidity() {
const navigate = useNavigate()
const dismissDialog = () => navigate('..', { preventScrollReset: true })
const { asset } = useLoaderData<typeof loader>()

return (
<LiquidityDialog
onClose={dismissDialog}
title='Withdraw peer liquidity'
type='Withdraw'
asset={{ code: asset.code, scale: asset.scale }}
/>
)
}
Expand Down

0 comments on commit 9b7fcdc

Please sign in to comment.