Skip to content

Commit

Permalink
External deposit uri (#219)
Browse files Browse the repository at this point in the history
* Render toast if the pricing service is down

* Allow for external deposit urls

* VQA feedback
  • Loading branch information
sashimi36 authored May 22, 2022
1 parent 8608ff1 commit 9a70f72
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 71 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
.DS_Store
*.pem
.idea
*.client-test.*

# debug
npm-debug.log*
Expand Down
132 changes: 85 additions & 47 deletions features/assets/components/AssetCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useIBCAssetInfo } from 'hooks/useIBCAssetInfo'
import { useTokenDollarValue } from 'hooks/useTokenDollarValue'
import {
ArrowUp,
ArrowUpIcon,
Button,
dollarValueFormatterWithDecimals,
IconWrapper,
ImageForTokenLogo,
styled,
Text,
} from 'junoblocks'
import { HTMLProps } from 'react'
import { HTMLProps, useState } from 'react'
import { __TRANSFERS_ENABLED__ } from 'util/constants'

import { DepositRedirectDialog } from './DepositRedirectDialog'

export enum AssetCardState {
fetching = 'FETCHING',
active = 'ACTIVE',
Expand All @@ -34,15 +35,26 @@ export const AssetCard = ({
state,
...htmlProps
}: AssetCardProps) => {
const { symbol, name, logoURI } = useIBCAssetInfo(tokenSymbol) || {}
const { symbol, name, logoURI, external_deposit_uri } =
useIBCAssetInfo(tokenSymbol) || {}
const [showingRedirectDepositDialog, setShowingRedirectDepositDialog] =
useState(false)

const [dollarValue] = useTokenDollarValue(tokenSymbol)

const handleDepositClick = () =>
const shouldPerformDepositOutsideApp = Boolean(external_deposit_uri)

const handleDepositClick = () => {
// bail early if redirecting the user to perform deposit externally
if (shouldPerformDepositOutsideApp) {
return setShowingRedirectDepositDialog(true)
}

onActionClick({
tokenSymbol: symbol,
actionType: 'deposit',
})
}

const handleWithdrawClick = () =>
onActionClick({
Expand All @@ -66,51 +78,77 @@ export const AssetCard = ({
const rendersActiveAppearance = balance > 0

return (
<StyledElementForCard
active={rendersActiveAppearance}
{...(htmlProps as any)}
kind="wrapper"
>
<StyledElementForCard kind="content">
<StyledElementForToken>
<ImageForTokenLogo logoURI={logoURI} size="big" />
<div>
<Text variant="primary">
{rendersActiveAppearance ? balance : null} {name}
</Text>
{rendersActiveAppearance && (
<Text variant="caption" css={{ paddingTop: '$1' }}>
$
{dollarValueFormatterWithDecimals(dollarValue * balance, {
includeCommaSeparation: true,
})}
<>
<StyledElementForCard
active={rendersActiveAppearance}
{...(htmlProps as any)}
kind="wrapper"
>
<StyledElementForCard kind="content">
<StyledElementForToken>
<ImageForTokenLogo logoURI={logoURI} size="big" />
<div>
<Text variant="primary">
{rendersActiveAppearance ? balance : null} {name}
</Text>
)}
</div>
</StyledElementForToken>
</StyledElementForCard>
{rendersActiveAppearance && (
<Text variant="caption" css={{ paddingTop: '$1' }}>
$
{dollarValueFormatterWithDecimals(dollarValue * balance, {
includeCommaSeparation: true,
})}
</Text>
)}
</div>
</StyledElementForToken>
</StyledElementForCard>

<StyledElementForCard kind="actions">
{balance > 0 && (
<Button
disabled={!__TRANSFERS_ENABLED__}
onClick={__TRANSFERS_ENABLED__ ? handleWithdrawClick : undefined}
iconRight={<IconWrapper icon={<ArrowUp />} />}
variant="ghost"
>
Withdraw
</Button>
)}
<Button
disabled={!__TRANSFERS_ENABLED__}
onClick={__TRANSFERS_ENABLED__ ? handleDepositClick : undefined}
iconRight={<IconWrapper icon={<ArrowUp />} rotation="180deg" />}
variant="ghost"
>
Deposit
</Button>
<StyledElementForCard kind="actions">
{shouldPerformDepositOutsideApp ? (
<Button
disabled={!__TRANSFERS_ENABLED__}
onClick={handleDepositClick}
iconRight={<ArrowUpIcon rotation="45deg" />}
variant="ghost"
>
Transfer
</Button>
) : (
<>
{balance > 0 && (
<Button
disabled={!__TRANSFERS_ENABLED__}
onClick={
__TRANSFERS_ENABLED__ ? handleWithdrawClick : undefined
}
iconRight={<ArrowUpIcon />}
variant="ghost"
>
Withdraw
</Button>
)}
<Button
disabled={!__TRANSFERS_ENABLED__}
onClick={__TRANSFERS_ENABLED__ ? handleDepositClick : undefined}
iconRight={<ArrowUpIcon rotation="180deg" />}
variant="ghost"
>
Deposit
</Button>
</>
)}
</StyledElementForCard>
</StyledElementForCard>
</StyledElementForCard>

{shouldPerformDepositOutsideApp && (
<DepositRedirectDialog
isShowing={showingRedirectDepositDialog}
onRequestClose={() => setShowingRedirectDepositDialog(false)}
tokenSymbol={tokenSymbol}
href={external_deposit_uri}
/>
)}
</>
)
}

Expand Down
39 changes: 39 additions & 0 deletions features/assets/components/DepositRedirectDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Button, Dialog, DialogContent, DialogHeader, Text } from 'junoblocks'

type DepositRedirectDialogProps = {
isShowing: boolean
onRequestClose: () => void
tokenSymbol: string
href: string
}

export const DepositRedirectDialog = ({
isShowing,
onRequestClose,
tokenSymbol,
href,
}: DepositRedirectDialogProps) => {
return (
<Dialog isShowing={isShowing} onRequestClose={onRequestClose}>
<DialogHeader paddingBottom="$10">
<Text variant="header">External asset deposit</Text>
</DialogHeader>
<DialogContent css={{ paddingBottom: '$12' }}>
<Text css={{ paddingBottom: '$12' }} variant="body">
You will be redirected to an external service to deposit your{' '}
{tokenSymbol} on the chain.
</Text>
<Button
as="a"
href={href}
target="__blank"
css={{ width: '100%' }}
size="large"
onClick={onRequestClose}
>
Proceed
</Button>
</DialogContent>
</Dialog>
)
}
1 change: 1 addition & 0 deletions hooks/useIbcAssetList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type IBCAssetInfo = {
channel: string
logoURI: string
deposit_gas_fee?: number
external_deposit_uri?: string
}

export type IBCAssetList = {
Expand Down
3 changes: 2 additions & 1 deletion public/ibc_assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"channel": "info",
"juno_channel": "channel-7",
"juno_denom": "ibc/test",
"logoURI": "https://cryptologos.cc/logos/usd-coin-usdc-logo.svg?v=014"
"logoURI": "https://cryptologos.cc/logos/usd-coin-usdc-logo.svg?v=014",
"external_deposit_uri": "http://localhost:3000"
}
]
}
23 changes: 0 additions & 23 deletions queries/tokenDollarValueQuery.ts

This file was deleted.

56 changes: 56 additions & 0 deletions queries/tokenDollarValueQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ErrorIcon, Toast } from 'junoblocks'
import React from 'react'
import { toast } from 'react-hot-toast'

import { TokenInfo } from '../hooks/useTokenList'

const pricingServiceIsDownAlert = createAlertPricingServiceIsDown()

export async function tokenDollarValueQuery(tokenIds: Array<TokenInfo['id']>) {
const prices = await fetchTokensPrice(tokenIds)
return tokenIds.map((id): number => prices[id]?.usd || 0)
}

async function fetchTokensPrice(tokenIds: Array<string>) {
const response = await fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=${tokenIds.join(
','
)}&vs_currencies=usd`,
{
method: 'GET',
}
)

if (!response.ok) {
pricingServiceIsDownAlert()
throw new Error('Cannot fetch dollar price from the API.')
}

return response.json()
}

function createAlertPricingServiceIsDown() {
let hasRenderedAlert
let timeout

function renderAlert() {
toast.custom((t) => (
<Toast
icon={<ErrorIcon />}
title="Oops, sorry! Our pricing service is temporarily down"
onClose={() => toast.dismiss(t.id)}
/>
))
}

return () => {
if (hasRenderedAlert) {
clearTimeout(timeout)
timeout = setTimeout(renderAlert, 60 * 1000)
return
}

hasRenderedAlert = true
renderAlert()
}
}
62 changes: 62 additions & 0 deletions util/externalLinkPopup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
type ExternalLinkPopupArgs = {
url: string
title: string
width: number
height: number
}

export function externalLinkPopup({
url,
title,
width: w,
height: h,
}: ExternalLinkPopupArgs) {
const dualScreenLeft =
window.screenLeft !== undefined ? window.screenLeft : window.screenX
const dualScreenTop =
window.screenTop !== undefined ? window.screenTop : window.screenY

const width = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width
const height = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
? document.documentElement.clientHeight
: screen.height

const systemZoom = width / window.screen.availWidth
const left = (width - w) / 2 / systemZoom + dualScreenLeft
const top = (height - h) / 2 / systemZoom + dualScreenTop
const newWindow = window.open(
url,
title,
`
scrollbars=yes,
width=${w / systemZoom},
height=${h / systemZoom},
top=${top},
left=${left},
location=yes,
status=yes
`
)

if (window.focus) newWindow.focus()

return newWindow
}

export function externalLinkPopupAutoWidth(
args: Omit<ExternalLinkPopupArgs, 'width' | 'height'>
) {
const width = Math.max(450, Math.round(window.innerWidth * 0.35))
const height = Math.max(300, Math.round(window.innerHeight * 0.8))
return externalLinkPopup({
...args,
width,
height,
})
}

0 comments on commit 9a70f72

Please sign in to comment.