Skip to content

Commit

Permalink
feat: adds MVP exchange screen in buy/sell view
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeanribeiro committed Jul 18, 2024
1 parent 746c846 commit 6a8f492
Show file tree
Hide file tree
Showing 28 changed files with 526 additions and 231 deletions.
3 changes: 3 additions & 0 deletions packages/desktop/features/buy-sell.features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { IFeatureFlag } from '@lib/features/interfaces'

const buySellFeatures: IFeatureFlag = {
enabled: true,
sell: {
enabled: false,
},
}

export default buySellFeatures
10 changes: 6 additions & 4 deletions packages/desktop/lib/electron/managers/transak.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,14 @@ export default class TransakManager implements ITransakManager {
}

private getUrl(data: ITransakWindowData): string {
const { address, currency, service, amount } = data
const { address, currency, service, amount, paymentMethod } = data
const apiKey = process.env.TRANSAK_API_KEY

if (typeof apiKey !== 'string') {
throw new Error('Undefined Transak API key')
}

if (!Object.values(FiatCurrency).includes(currency as FiatCurrency)) {
if (!Object.keys(FiatCurrency).includes(currency)) {
throw new Error('Invalid Transak currency')
}

Expand All @@ -243,9 +243,10 @@ export default class TransakManager implements ITransakManager {

const queryParams: QueryParameters = {
apiKey,
defaultFiatCurrency: currency,
defaultFiatAmount: amount,
fiatCurrency: currency,
fiatAmount: amount,
walletAddress: address,
paymentMethod: paymentMethod,
productsAvailed: service,
cryptoCurrencyCode: 'IOTA',
network: 'miota',
Expand All @@ -256,6 +257,7 @@ export default class TransakManager implements ITransakManager {
disablePaymentMethods: ['apple_pay', 'google_pay'],
excludeFiatCurrencies: 'USD',
colorMode,
hideExchangeScreen: true,
}

const urlObject = buildUrl({ base: TRANSAK_WIDGET_URL, query: queryParams })
Expand Down
6 changes: 6 additions & 0 deletions packages/desktop/views/dashboard/buy-sell/BuySell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
import features from '@features/features'
import { BuySellMainView } from './views'
import { dashboardRoute, DashboardRoute } from '@core/router'
import { onMount } from 'svelte'
import { updateTransakFiatCurrencies } from '@auxiliary/transak'
$: if (features.analytics.dashboardRoute.buySell.enabled && $dashboardRoute === DashboardRoute.BuySell) {
Platform.trackEvent('buy-sell-route', { route: $dashboardRoute })
}
onMount(() => {
void updateTransakFiatCurrencies()
})
</script>

<div class="w-full h-full flex flex-col flex-nowrap p-8 relative flex-1">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
import { formatCurrency } from '@core/i18n/utils'
import { getMarketPriceForToken, getTokenValueFromFiatAmount } from '@core/market/actions'
import { formatTokenAmount } from '@core/token'
import { truncateString } from '@core/utils'
import { Tile, Text } from '@bloomwalletio/ui'
import { TokenAvatar } from '@ui'
import { selectedAccountTokens } from '@core/token/stores'
import { SupportedStardustNetworkId } from '@core/network'
import { MarketCurrency } from '@core/market'
export let token = $selectedAccountTokens[SupportedStardustNetworkId.Iota]?.baseCoin
export let onClick: (() => unknown) | undefined = undefined
export let selected = false
export let fiatValue = '0'
export let currency: MarketCurrency
$: marketPrice = token ? getMarketPriceForToken(token, currency) : undefined
$: tokenAmount = token && currency ? getTokenValueFromFiatAmount(fiatValue, token, currency) : BigInt(0)
</script>

{#if token && token.metadata}
<Tile {onClick} {selected} surface={1} width="full">
<div class="w-full flex flex-row items-center gap-2">
<TokenAvatar {token} />
<div class="flex flex-col w-full">
<div class="flex flex-row w-full justify-between">
<Text>
{token.metadata.name
? truncateString(token.metadata.name, 13, 0)
: truncateString(token.id, 6, 7)}
</Text>
<Text align="right">
{token.metadata
? `≈ ${formatTokenAmount(tokenAmount ?? BigInt(0), token.metadata, { round: false })}`
: '-'}
</Text>
</div>
<div class="flex flex-row w-full justify-between">
<Text fontWeight="medium" textColor="secondary">
{marketPrice ? formatCurrency(marketPrice, currency) : '-'}
</Text>
<Text fontWeight="medium" textColor="secondary" align="right">
{fiatValue !== undefined ? formatCurrency(fiatValue ?? '', currency) : '-'}
</Text>
</div>
</div>
</div>
</Tile>
{/if}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { activeProfile } from '@core/profile/stores'
import { AmountInput } from '@ui'
import { Text } from '@bloomwalletio/ui'
export let currency = $activeProfile?.settings?.marketCurrency?.toUpperCase()
export let value: string
let inputLength: number
const fontSize: 'text-32' | 'text-48' | 'text-64' = 'text-64'
$: value, (inputLength = getInputLength())
$: maxWidth = `${(inputLength * Number(/\d+/.exec(fontSize)?.[0] ?? 0) * 2) / 3}px`
function getInputLength(): number {
const length = value?.length || 1
const isDecimal = value?.includes('.') || value?.includes(',')
return length - (isDecimal ? 0.5 : 0)
}
</script>

<div class="flex justify-center items-end gap-1">
<AmountInput bind:value maxDecimals={2} {fontSize} {maxWidth} />
<Text class="pb-5">{currency}</Text>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<script lang="ts">
import { Button, Icon, IconName, IOption, SelectInput, Tabs, Text } from '@bloomwalletio/ui'
import { DISCORD_URL } from '@contexts/settings/constants'
import { ISettingsState, settingsState } from '@contexts/settings/stores'
import { selectedAccount, selectedAccountIndex } from '@core/account/stores'
import { openUrlInBrowser, Platform } from '@core/app'
import { localize } from '@core/i18n'
import { FiatCurrency } from '@core/market'
import { MarketCoinId } from '@core/market/enums'
import { marketCoinPrices } from '@core/market/stores'
import { isDashboardSideBarExpanded } from '@core/ui'
import { drawerState } from '@desktop/auxiliary/drawer/stores'
import { DrawerState } from '@desktop/auxiliary/drawer/types'
import { IPopupState, IProfileAuthPopupState, popupState, profileAuthPopup } from '@desktop/auxiliary/popup'
import { isFeatureEnabled } from '@lib/features/utils'
import { Pane } from '@ui'
import { onDestroy, tick } from 'svelte'
import { TokenTile, TransakAmountInput } from './'
import { transakFiatCurrencies } from '@auxiliary/transak'
const CURRENCY_OPTIONS: IOption[] = Object.keys(FiatCurrency).map((currency) => ({
value: currency,
}))
let selectedCurrencyOption: IOption = CURRENCY_OPTIONS[0]
let error: boolean = false
$: fiatValue = String(getDefaultFiatAmount(FiatCurrency[selectedCurrencyOption.value as keyof typeof FiatCurrency]))
Platform.onEvent('transak-loaded', () => (isTransakLoading = false))
Platform.onEvent('transak-not-loaded', () => (error = true))
function onButtonClick(): void {
openUrlInBrowser(DISCORD_URL)
}
const TABS = [
{ key: 'BUY', value: localize('views.buySell.tabs.buy') },
{ key: 'SELL', value: localize('views.buySell.tabs.sell') },
]
let selectedTab = TABS[0]
$: paymentOptions =
$transakFiatCurrencies?.[selectedCurrency as keyof typeof FiatCurrency]?.paymentOptions.map((option) => ({
value: option.id,
label: option.name,
})) ?? []
$: selectedPaymentOption = paymentOptions?.[0]
$: selectedCurrency = selectedCurrencyOption.value
let isTransakOpen: boolean = false
let isTransakLoading: boolean = false
$: $isDashboardSideBarExpanded, void updateTransakBounds()
$: if ($selectedAccountIndex !== undefined) {
void closeTransak()
}
$: isTransakOpen, void handleOverlayChanges($popupState, $profileAuthPopup, $settingsState, $drawerState)
async function handleOverlayChanges(
state: IPopupState,
profilePopupState: IProfileAuthPopupState,
settingsState: ISettingsState,
drawerState: DrawerState
): Promise<void> {
if (state.active || profilePopupState.active || settingsState.open || drawerState?.id) {
await Platform.hideTransak()
} else {
await tick()
await Platform.showTransak()
}
}
let transakContainer: HTMLDivElement | undefined
async function updateTransakBounds(): Promise<void> {
if (!transakContainer) {
return
}
const rect = transakContainer.getBoundingClientRect()
const transakPaneStyles = getComputedStyle(transakContainer?.children[0])
const extractDigitsToNumbers = (str: string) => Number(str?.replace(/\D/g, '') ?? 0)
const borderTop = extractDigitsToNumbers(transakPaneStyles?.borderTopWidth)
const borderBottom = extractDigitsToNumbers(transakPaneStyles?.borderBottomWidth)
const borderLeft = extractDigitsToNumbers(transakPaneStyles?.borderLeftWidth)
const borderRight = extractDigitsToNumbers(transakPaneStyles?.borderRightWidth)
await Platform.updateTransakBounds({
x: rect.x + borderLeft,
y: rect.y + borderTop,
width: rect.width - borderLeft - borderRight,
height: rect.height - borderTop - borderBottom,
})
}
function getDefaultFiatAmount(currency: FiatCurrency): number {
const DEFAULT_FIAT_AMOUNT = 100
switch (currency) {
case FiatCurrency.USD:
case FiatCurrency.EUR:
case FiatCurrency.GBP:
return DEFAULT_FIAT_AMOUNT
default: {
const conversionRate =
$marketCoinPrices[MarketCoinId.Iota]?.[currency] /
$marketCoinPrices[MarketCoinId.Iota]?.[FiatCurrency.USD]
const fiatAmount = DEFAULT_FIAT_AMOUNT * conversionRate
const roundedAmount = customRound(fiatAmount)
return roundedAmount
}
}
}
function customRound(number) {
const magnitude = Math.pow(10, Math.floor(Math.log10(number)))
return magnitude <= 10
? Math.round(number / magnitude) * magnitude
: Math.round((number / magnitude) * 10) * (magnitude / 10)
}
export async function resetTransak(): Promise<void> {
isTransakLoading = true
await Platform.closeTransak()
isTransakOpen = false
await Platform.openTransak({
currency: selectedCurrencyOption.value as keyof typeof FiatCurrency,
address: $selectedAccount?.depositAddress ?? '',
service: selectedTab.key as 'BUY' | 'SELL',
amount: Number(fiatValue),
paymentMethod: selectedPaymentOption.value ?? '',
})
isTransakOpen = true
await updateTransakBounds()
}
async function closeTransak(): Promise<void> {
await Platform.closeTransak()
isTransakOpen = false
}
onDestroy(() => {
void Platform.closeTransak()
isTransakOpen = false
Platform.removeListenersForEvent('reset-transak')
})
</script>

<svelte:window on:resize={updateTransakBounds} />

<div class="w-full h-full" bind:this={transakContainer}>
<Pane classes="px-6 pb-6 pt-4 bg-surface dark:bg-surface-dark shadow-lg w-full h-full">
{#if error}
<div class="flex flex-col justify-center items-center w-full h-full gap-4">
<Icon name={IconName.ArrowDownUp} size="lg" textColor="brand" />
<Text type="body1">{localize('views.buySell.error.title')}</Text>
<Text textColor="secondary" align="center">{localize('views.buySell.error.description')}</Text>
<Button on:click={onButtonClick} text={localize('actions.visitDiscord')} />
</div>
{:else}
<div
class="flex flex-col justify-between items-center w-full h-full gap-8 {isTransakOpen &&
!isTransakLoading
? 'opacity-0 pointer-events-none'
: ''}"
>
{#if isFeatureEnabled('buySell.sell')}
<div class="w-full">
<Tabs bind:selectedTab tabs={TABS} />
</div>
{/if}
<div class="flex flex-col items-center gap-4 w-full">
<div class="w-3/4">
<SelectInput
label="Currency"
options={CURRENCY_OPTIONS}
bind:selected={selectedCurrencyOption}
/>
</div>
<TransakAmountInput currency={selectedCurrency} bind:value={fiatValue} />
<TokenTile {fiatValue} currency={FiatCurrency[selectedCurrency]} />
<div class="w-full">
<SelectInput
label="Payment method"
options={paymentOptions}
bind:selected={selectedPaymentOption}
hideValue
/>
</div>
</div>
<Button text={selectedTab.value} on:click={resetTransak} />
</div>
{/if}
</Pane>
</div>
Loading

0 comments on commit 6a8f492

Please sign in to comment.