Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new forced actions page #270

Merged
merged 43 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
42ad1ea
Implement forced actions page design
torztomasz Feb 14, 2023
c9ba52d
change navbar text color
torztomasz Feb 14, 2023
bc57b56
remove unnecesary nesting
torztomasz Feb 14, 2023
17f0c36
remove unnecessary prop
torztomasz Feb 14, 2023
928073d
remove unnecessary nesting
torztomasz Feb 14, 2023
319184c
clean classnames
torztomasz Feb 15, 2023
070f110
fix Inter font not being used as default
torztomasz Feb 15, 2023
45e1d91
move AssetDetails to shared package
torztomasz Feb 15, 2023
e0c77c6
implement image fallback functionality
torztomasz Feb 15, 2023
3b17835
add AssetWithLogo component
torztomasz Feb 15, 2023
e23da64
add more icons to assetUtils
torztomasz Feb 15, 2023
31d1731
add arrow icons
torztomasz Feb 15, 2023
80dd7cf
create constants file
torztomasz Feb 16, 2023
25e8b10
implement new forced actions
torztomasz Feb 16, 2023
3034b0c
format assetUtils
torztomasz Feb 16, 2023
71aa080
run lint:fix
torztomasz Feb 16, 2023
54a65d0
Merge remote-tracking branch 'origin/master' into implement-new-force…
torztomasz Feb 16, 2023
7cb4ea2
resolve issues after merge
torztomasz Feb 16, 2023
daface2
remove restricted imports
torztomasz Feb 16, 2023
27df406
run format:fix
torztomasz Feb 16, 2023
03365b2
make price input show usdc always
torztomasz Feb 16, 2023
a4e0086
improve preview routes for forced pages
torztomasz Feb 16, 2023
fdf96cb
run format:fix
torztomasz Feb 16, 2023
bf44cc4
add zod schema for AccountDetails
torztomasz Feb 16, 2023
2564ffe
fix build issue
torztomasz Feb 16, 2023
27c9038
run lint:fix
torztomasz Feb 16, 2023
60e7128
fix build issues
torztomasz Feb 16, 2023
73757f1
remove outline from inputs
torztomasz Feb 17, 2023
c0c8c6b
align text in inputs
torztomasz Feb 17, 2023
b578c9d
refactor FancyList to OrderedList
torztomasz Feb 17, 2023
4beb442
update forced actions page description and path
torztomasz Feb 17, 2023
b30f0d2
fix amount input balance
torztomasz Feb 17, 2023
d37d574
add dev routes for forced actions
torztomasz Feb 17, 2023
210b9c2
rename forcedaction to forced-actions
torztomasz Feb 17, 2023
ac37ffd
use <> instead of span
torztomasz Feb 17, 2023
83bcde5
prefix positionId with #
torztomasz Feb 17, 2023
d9b9aa9
add "Back to assets" button link
torztomasz Feb 17, 2023
27a7ff3
use ol + li instead of divs in OrderedList
torztomasz Feb 17, 2023
10b5b98
add LinkButton component
torztomasz Feb 17, 2023
b192eed
do not use arbitrary px values with max-w
torztomasz Feb 17, 2023
3e581fc
add formatHashShort function
torztomasz Feb 17, 2023
66578e5
run linter
torztomasz Feb 17, 2023
9296ea4
Merge remote-tracking branch 'origin/master' into implement-new-force…
torztomasz Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AssetDetails } from '@explorer/shared'
import { AssetHash, EthereumAddress, Hash256 } from '@explorer/types'

import { BlockRange } from '../../model'
import { AssetDetails } from '../../model/AssetDetails'
import {
AssetRegistrationRecord,
AssetRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AssetHash, EthereumAddress, Hash256 } from '@explorer/types'

import { BlockRange } from '../../model'
import {
ERC721Details,
ERC1155Details,
MintableERC721Details,
} from '../../model/AssetDetails'
} from '@explorer/shared'
import { AssetHash, EthereumAddress, Hash256 } from '@explorer/types'

import { BlockRange } from '../../model'
import { AssetRepository } from '../../peripherals/database/AssetRepository'
import { EthereumClient } from '../../peripherals/ethereum/EthereumClient'
import { TokenInspector } from '../../peripherals/ethereum/TokenInspector'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AssetDetails, AssetType } from '@explorer/shared'
import { AssetHash, EthereumAddress, Hash256 } from '@explorer/types'
import { expect } from 'earljs'
import { BigNumber } from 'ethers'

import { AssetDetails, AssetType } from '../../model/AssetDetails'
import { setupDatabaseTestSuite } from '../../test/database'
import { Logger } from '../../tools/Logger'
import { AssetRegistrationRecord, AssetRepository } from './AssetRepository'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AssetDetails, AssetType } from '@explorer/shared'
import { AssetHash, EthereumAddress, Hash256 } from '@explorer/types'
import { AssetDetailsRow, AssetRegistrationRow } from 'knex/types/tables'

import { AssetDetails, AssetType } from '../../model/AssetDetails'
import { Logger } from '../../tools/Logger'
import { BaseRepository } from './shared/BaseRepository'
import { Database } from './shared/Database'
Expand Down
127 changes: 126 additions & 1 deletion packages/frontend/src/preview/data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Timestamp } from '@explorer/types'
import { AssetId, EthereumAddress, StarkKey, Timestamp } from '@explorer/types'

import { UserPageProps } from '../view'
import { ForcedActionFormProps } from '../view/pages/forced-actions/ForcedActionFormProps'

export const USER_PROPS: UserPageProps = {
account: undefined,
Expand Down Expand Up @@ -67,3 +68,127 @@ export const USER_PROPS: UserPageProps = {
],
totalOffers: 1 as unknown as bigint,
}

export const FORCED_ACTION_FORM_PROPS: ForcedActionFormProps = {
account: {
address: EthereumAddress.fake(),
positionId: 123n,
hasUpdates: false,
},
perpetualAddress: EthereumAddress(
'0xD54f502e184B6B739d7D27a6410a67dc462D69c8'
),
selectedAsset: AssetId('USDC-6'),
positionId: 1234n,
starkKey: StarkKey.fake(),
assets: [
{
assetId: AssetId('USDC-6'),
balance: 69420_654321n,
priceUSDCents: 100n,
totalUSDCents: 69420_65n,
},
{
assetId: AssetId('ETH-9'),
balance: 21_370000000n,
priceUSDCents: 2839_39n,
totalUSDCents: 60678_04n,
},
{
assetId: AssetId('BTC-10'),
balance: -5287654321n,
priceUSDCents: 38504_34n,
totalUSDCents: -20359_76n,
},
{
assetId: AssetId('DOGE-5'),
balance: 100_00000n,
priceUSDCents: 13n,
totalUSDCents: 13_12n,
},
{
assetId: AssetId('SUSHI-7'),
balance: -2_7654321n,
priceUSDCents: 2_44n,
totalUSDCents: 6_75n,
},
],
}

export const FORCED_WITHDRAW_FORM_PROPS: ForcedActionFormProps = {
account: {
address: EthereumAddress.fake(),
positionId: 123n,
hasUpdates: false,
},
perpetualAddress: EthereumAddress(
'0xD54f502e184B6B739d7D27a6410a67dc462D69c8'
),
selectedAsset: AssetId('USDC-6'),
positionId: 1234n,
starkKey: StarkKey.fake(),
assets: [
{
assetId: AssetId('USDC-6'),
balance: 69420_654321n,
priceUSDCents: 100n,
totalUSDCents: 69420_65n,
},
],
}

export const FORCED_SELL_FORM_PROPS: ForcedActionFormProps = {
account: {
address: EthereumAddress.fake(),
positionId: 123n,
hasUpdates: false,
},
perpetualAddress: EthereumAddress(
'0xD54f502e184B6B739d7D27a6410a67dc462D69c8'
),
selectedAsset: AssetId('ETH-9'),
positionId: 1234n,
starkKey: StarkKey.fake(),
assets: [
{
assetId: AssetId('USDC-6'),
balance: 69420_654321n,
priceUSDCents: 100n,
totalUSDCents: 69420_65n,
},
{
assetId: AssetId('ETH-9'),
balance: 21_370000000n,
torztomasz marked this conversation as resolved.
Show resolved Hide resolved
priceUSDCents: 2839_39n,
totalUSDCents: 60678_04n,
},
],
}

export const FORCED_BUY_FORM_PROPS: ForcedActionFormProps = {
account: {
address: EthereumAddress.fake(),
positionId: 123n,
hasUpdates: false,
},
perpetualAddress: EthereumAddress(
'0xD54f502e184B6B739d7D27a6410a67dc462D69c8'
),
selectedAsset: AssetId('BTC-10'),
positionId: 1234n,
starkKey: StarkKey.fake(),
assets: [
{
assetId: AssetId('USDC-6'),
balance: 69420_654321n,
priceUSDCents: 100n,
totalUSDCents: 69420_65n,
},
{
assetId: AssetId('BTC-10'),
balance: -5287654321n,
priceUSDCents: 38504_34n,
totalUSDCents: -20359_76n,
},
],
}
11 changes: 5 additions & 6 deletions packages/frontend/src/preview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
import Koa from 'koa'
import serve from 'koa-static'

import { USE_NEW_DESIGN } from '../utils/constants'
import { router as oldRouter } from './oldRoutes'
import { router } from './routes'

const app = new Koa()

const IS_OLD = false

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (IS_OLD) {
app.use(oldRouter.routes())
app.use(oldRouter.allowedMethods())
} else {
if (USE_NEW_DESIGN) {
app.use(router.routes())
app.use(router.allowedMethods())
} else {
app.use(oldRouter.routes())
app.use(oldRouter.allowedMethods())
}

app.use(serve('build/static'))
Expand Down
53 changes: 49 additions & 4 deletions packages/frontend/src/preview/routes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/* eslint-disable import/no-extraneous-dependencies */
import { EthereumAddress } from '@explorer/types'
import { AssetId, EthereumAddress } from '@explorer/types'
import Router from '@koa/router'
import Koa from 'koa'

import { renderHomePage, renderUserPage } from '../view'
import { USER_PROPS } from './data'
import {
renderForcedWithdrawPage,
renderHomePage,
renderUserPage,
} from '../view'
import { renderForcedTradePage } from '../view/pages/forced-actions/ForcedTradePage'
import * as DATA from './data'

export const router = new Router()

Expand All @@ -13,9 +18,49 @@ router.get('/', (ctx) => {
ctx.body = renderHomePage({ title: 'foo', account })
})

router.get('/forced/new/:positionId/:assetId', (ctx) => {
if (!ctx.params.positionId || !ctx.params.assetId) {
return
}
const data = { ...DATA.FORCED_ACTION_FORM_PROPS }
const positionId = ctx.params.positionId
const assetId = AssetId(ctx.params.assetId)
if (data.assets.find((asset) => asset.assetId === assetId) === undefined) {
return
}
data.account = getAccount(ctx) ?? data.account
data.selectedAsset = assetId
data.positionId = BigInt(positionId)
if (data.selectedAsset === AssetId.USDC) {
ctx.body = renderForcedWithdrawPage(data)
return
}

ctx.body = renderForcedTradePage(data)
})

router.get('/user', (ctx) => {
const account = getAccount(ctx)
ctx.body = renderUserPage({ ...USER_PROPS, account })
ctx.body = renderUserPage({ ...DATA.USER_PROPS, account })
})

//DEV ROUTES
router.get('/dev/forced/new/withdraw', (ctx) => {
const withdrawData = { ...DATA.FORCED_WITHDRAW_FORM_PROPS }
withdrawData.account = getAccount(ctx) ?? withdrawData.account
ctx.body = renderForcedWithdrawPage(withdrawData)
})

router.get('/dev/forced/new/sell', (ctx) => {
const sellData = { ...DATA.FORCED_SELL_FORM_PROPS }
sellData.account = getAccount(ctx) ?? sellData.account
ctx.body = renderForcedTradePage(sellData)
})

router.get('/dev/forced/new/buy', (ctx) => {
const buyData = { ...DATA.FORCED_BUY_FORM_PROPS }
buyData.account = getAccount(ctx) ?? buyData.account
ctx.body = renderForcedTradePage(buyData)
})

function getAccount(ctx: Koa.Context) {
Expand Down
95 changes: 95 additions & 0 deletions packages/frontend/src/scripts/forced-actions/forcedActionForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { FormId } from '../../view/components/forced-actions/form/ids'
import { ForcedActionFormProps } from '../../view/pages/forced-actions/ForcedActionFormProps'
import { FormElements, getFormElements } from './getFormElements'
import { getInitialState, nextFormState } from './state'
import { submit } from './submit'
import { FormAction, FormState } from './types'

export function initForcedActionForm() {
if (!document.getElementById(FormId.Form)) {
return
}

const formElements: FormElements = getFormElements()
const {
form,
amountInput,
priceInput,
totalInput,
submitButton,
amountErrorView,
} = formElements
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const propsJson = JSON.parse(form.dataset.props ?? '{}')
const props = ForcedActionFormProps.parse(propsJson)

amountInput.addEventListener('input', () =>
dispatch({ type: 'ModifyAmount', value: amountInput.value })
)

if (priceInput) {
priceInput.addEventListener('input', () =>
dispatch({ type: 'ModifyPrice', value: priceInput.value })
)
}

if (totalInput) {
totalInput.addEventListener('input', () =>
dispatch({ type: 'ModifyTotal', value: totalInput.value })
)
}

submitButton.addEventListener('click', () => {
if (!state || !state.canSubmit) {
throw new Error('Programmer error: Submit button should be disabled')
}
submit(state).catch(console.error)
})

let state: FormState | undefined
updateUI(getInitialState(props, location.search))

function dispatch(action: FormAction) {
if (state) {
const newState = nextFormState(state, action)
updateUI(newState)
}
}

function updateUI(newState: FormState) {
if (amountInput.value !== newState.amountInputString) {
amountInput.value = newState.amountInputString
}

if (!state || state.amountInputError !== newState.amountInputError) {
amountErrorView.classList.toggle('hidden', !newState.amountInputError)
amountInput.classList.toggle('text-red-500', newState.amountInputError)
}

if (
priceInput &&
newState.priceInputString &&
priceInput.value !== newState.priceInputString
) {
priceInput.value = newState.priceInputString
}

if (
totalInput &&
newState.totalInputString &&
totalInput.value !== newState.totalInputString
) {
totalInput.value = newState.totalInputString
}

if (!state || state.canSubmit !== newState.canSubmit) {
if (!newState.canSubmit) {
submitButton.setAttribute('disabled', '')
} else {
submitButton.removeAttribute('disabled')
}
}

state = newState
}
}
26 changes: 26 additions & 0 deletions packages/frontend/src/scripts/forced-actions/getFormElements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FormId } from '../../view/components/forced-actions/form/ids'

export type FormElements = ReturnType<typeof getFormElements>

export function getFormElements() {
function $<T extends HTMLElement>(id: string) {
const element = document.getElementById(id)
if (!element) {
throw new Error(`Cannot find #${id}`)
}
return element as T
}

return {
form: $<HTMLFormElement>(FormId.Form),
amountInput: $<HTMLInputElement>(FormId.AmountInput),
priceInput: document.getElementById(FormId.PriceInput) as
| HTMLInputElement
| undefined,
totalInput: document.getElementById(FormId.TotalInput) as
| HTMLInputElement
| undefined,
submitButton: $<HTMLButtonElement>(FormId.SubmitButton),
amountErrorView: $(FormId.AmountErrorView),
}
}
Loading