Calculate the tax, and show a table of all transactions
This commit is contained in:
parent
7ffef0294a
commit
432575ae41
|
@ -5,7 +5,11 @@ import { User } from '../../common/user'
|
||||||
import { formatMoney } from '../../common/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { buyLeaderboardSlot } from '../lib/firebase/api-call'
|
import { buyLeaderboardSlot } from '../lib/firebase/api-call'
|
||||||
import { Transaction, writeTransaction } from '../lib/firebase/transactions'
|
import {
|
||||||
|
SlotData,
|
||||||
|
Transaction,
|
||||||
|
writeTransaction,
|
||||||
|
} from '../lib/firebase/transactions'
|
||||||
import { AmountInput } from './amount-input'
|
import { AmountInput } from './amount-input'
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
|
@ -18,10 +22,11 @@ export function Manaboard(props: {
|
||||||
title: string
|
title: string
|
||||||
users: User[]
|
users: User[]
|
||||||
values: number[]
|
values: number[]
|
||||||
|
createdTimes: number[]
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
// TODO: Ideally, highlight your own entry on the leaderboard
|
// TODO: Ideally, highlight your own entry on the leaderboard
|
||||||
let { title, users, className, values } = props
|
let { title, users, className, values, createdTimes } = props
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
|
@ -67,6 +72,7 @@ export function Manaboard(props: {
|
||||||
title={`${title}`}
|
title={`${title}`}
|
||||||
holder={user}
|
holder={user}
|
||||||
value={values[index]}
|
value={values[index]}
|
||||||
|
createdTime={createdTimes[index]}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</td>
|
</td>
|
||||||
|
@ -96,8 +102,9 @@ export function BuySlotModal(props: {
|
||||||
holder: User
|
holder: User
|
||||||
slot: number
|
slot: number
|
||||||
value: number
|
value: number
|
||||||
|
createdTime: number
|
||||||
}) {
|
}) {
|
||||||
const { slot, title, holder, value } = props
|
const { slot, title, holder, value, createdTime } = props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
@ -109,10 +116,29 @@ export function BuySlotModal(props: {
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const onBuy = async () => {
|
// const onBuy = async () => {
|
||||||
// Feel free to change this. - James
|
// // Feel free to change this. - James
|
||||||
const slotId = `${title}-${slot}`
|
// const slotId = `${title}-${slot}`
|
||||||
await buyLeaderboardSlot({ slotId, reassessValue: newValue })
|
// await buyLeaderboardSlot({ slotId, reassessValue: newValue })
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function onBuy() {
|
||||||
|
if (user) {
|
||||||
|
// Start transactions, but don't block
|
||||||
|
const buyData = { slot, newValue, message }
|
||||||
|
const buyTxn = buyTransaction({
|
||||||
|
buyer: user,
|
||||||
|
holder,
|
||||||
|
amount: value,
|
||||||
|
buyData,
|
||||||
|
})
|
||||||
|
await Promise.all([
|
||||||
|
writeTransaction(buyTxn),
|
||||||
|
writeTransaction(taxTransaction({ holder, slot, value, createdTime })),
|
||||||
|
])
|
||||||
|
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -148,22 +174,7 @@ export function BuySlotModal(props: {
|
||||||
label={ENV_CONFIG.moneyMoniker}
|
label={ENV_CONFIG.moneyMoniker}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button className="btn btn-primary" onClick={onBuy}>
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => {
|
|
||||||
if (user) {
|
|
||||||
buySlot({
|
|
||||||
holder,
|
|
||||||
buyer: user,
|
|
||||||
amount: value,
|
|
||||||
slot,
|
|
||||||
message,
|
|
||||||
newValue,
|
|
||||||
})
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Buy Slot ({formatMoney(value)})
|
Buy Slot ({formatMoney(value)})
|
||||||
</button>
|
</button>
|
||||||
<div className="-mt-2 text-sm">
|
<div className="-mt-2 text-sm">
|
||||||
|
@ -181,19 +192,16 @@ export function BuySlotModal(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buySlot(options: {
|
function buyTransaction(options: {
|
||||||
holder: User
|
|
||||||
buyer: User
|
buyer: User
|
||||||
|
holder: User
|
||||||
|
buyData: SlotData
|
||||||
amount: number
|
amount: number
|
||||||
slot: number
|
}): Transaction {
|
||||||
message: string
|
const { buyer, holder, buyData, amount } = options
|
||||||
newValue: number
|
return {
|
||||||
}) {
|
|
||||||
const { holder, buyer, amount, slot, message, newValue } = options
|
|
||||||
const createdTime = Date.now()
|
|
||||||
const buyTransaction: Transaction = {
|
|
||||||
id: '',
|
id: '',
|
||||||
createdTime,
|
createdTime: Date.now(),
|
||||||
|
|
||||||
fromId: buyer.id,
|
fromId: buyer.id,
|
||||||
fromName: buyer.name,
|
fromName: buyer.name,
|
||||||
|
@ -205,20 +213,30 @@ async function buySlot(options: {
|
||||||
toUsername: holder.username,
|
toUsername: holder.username,
|
||||||
toAvatarUrl: holder.avatarUrl,
|
toAvatarUrl: holder.avatarUrl,
|
||||||
|
|
||||||
amount: amount,
|
amount,
|
||||||
|
|
||||||
category: 'BUY_LEADERBOARD_SLOT',
|
category: 'BUY_LEADERBOARD_SLOT',
|
||||||
description: `${buyer.name} bought a slot from ${holder.name}`,
|
description: `${buyer.name} bought a slot from ${holder.name}`,
|
||||||
data: {
|
data: buyData,
|
||||||
slot,
|
|
||||||
message,
|
|
||||||
newValue,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const feeTransaction: Transaction = {
|
function taxTransaction(options: {
|
||||||
|
holder: User
|
||||||
|
slot: number
|
||||||
|
value: number
|
||||||
|
createdTime: number
|
||||||
|
}): Transaction {
|
||||||
|
const { holder, slot, value, createdTime } = options
|
||||||
|
|
||||||
|
const APRIL_FOOLS_PT = 1648796400000
|
||||||
|
const elapsedMs = Date.now() - (createdTime || APRIL_FOOLS_PT)
|
||||||
|
const elapsedHours = elapsedMs / 1000 / 60 / 60
|
||||||
|
const tax = elapsedHours * (value / 10)
|
||||||
|
|
||||||
|
return {
|
||||||
id: '',
|
id: '',
|
||||||
createdTime,
|
createdTime: Date.now(),
|
||||||
|
|
||||||
fromId: holder.id,
|
fromId: holder.id,
|
||||||
fromName: holder.name,
|
fromName: holder.name,
|
||||||
|
@ -231,16 +249,12 @@ async function buySlot(options: {
|
||||||
toUsername: 'ManifoldMarkets',
|
toUsername: 'ManifoldMarkets',
|
||||||
toAvatarUrl: 'https://manifold.markets/logo-bg-white.png',
|
toAvatarUrl: 'https://manifold.markets/logo-bg-white.png',
|
||||||
|
|
||||||
amount: 10, // TODO: Calculate fee
|
amount: tax,
|
||||||
|
|
||||||
category: 'LEADERBOARD_TAX',
|
category: 'LEADERBOARD_TAX',
|
||||||
description: `${holder.name} paid M$ 10 in fees`,
|
description: `${holder.name} paid M$ 10 in fees`,
|
||||||
data: {
|
data: {
|
||||||
slot,
|
slot,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
writeTransaction(buyTransaction),
|
|
||||||
writeTransaction(feeTransaction),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@ import { Title } from '../components/title'
|
||||||
import { useTransactions } from '../hooks/use-transactions'
|
import { useTransactions } from '../hooks/use-transactions'
|
||||||
import { SlotData, Transaction } from '../lib/firebase/transactions'
|
import { SlotData, Transaction } from '../lib/firebase/transactions'
|
||||||
|
|
||||||
|
import { Grid, _ as r } from 'gridjs-react'
|
||||||
|
import 'gridjs/dist/theme/mermaid.css'
|
||||||
|
import { html } from 'gridjs'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
export const getStaticProps = fromPropz(getStaticPropz)
|
export const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz() {
|
export async function getStaticPropz() {
|
||||||
const [topTraders, topCreators] = await Promise.all([
|
const [topTraders, topCreators] = await Promise.all([
|
||||||
|
@ -94,11 +99,12 @@ function Explanation() {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODOs
|
// TODOs
|
||||||
// [ ] Correctly calculate tax
|
|
||||||
// [ ] List history of purchases at the bottom
|
|
||||||
// [ ] Restrict to at most buying one slot per user?
|
|
||||||
// [ ] Deduct amount from user's balance, either in UX or for real
|
// [ ] Deduct amount from user's balance, either in UX or for real
|
||||||
|
// [ ] Draw attention to leaderboard
|
||||||
|
// [ ] Show total returned to Manifold
|
||||||
|
// [ ] Restrict to at most buying one slot per user?
|
||||||
export default function Manaboards(props: {
|
export default function Manaboards(props: {
|
||||||
topTraders: User[]
|
topTraders: User[]
|
||||||
topCreators: User[]
|
topCreators: User[]
|
||||||
|
@ -112,6 +118,7 @@ export default function Manaboards(props: {
|
||||||
const values = Array.from(Array(topTraders.length).keys())
|
const values = Array.from(Array(topTraders.length).keys())
|
||||||
.map((i) => i + 1)
|
.map((i) => i + 1)
|
||||||
.reverse()
|
.reverse()
|
||||||
|
const createdTimes = new Array(topTraders.length).fill(0)
|
||||||
|
|
||||||
// Find the most recent purchases of each slot, and replace the entries in topTraders
|
// Find the most recent purchases of each slot, and replace the entries in topTraders
|
||||||
const transactions = useTransactions() ?? []
|
const transactions = useTransactions() ?? []
|
||||||
|
@ -124,6 +131,7 @@ export default function Manaboards(props: {
|
||||||
const slot = data.slot
|
const slot = data.slot
|
||||||
topTraders[slot - 1] = buyer
|
topTraders[slot - 1] = buyer
|
||||||
values[slot - 1] = data.newValue
|
values[slot - 1] = data.newValue
|
||||||
|
createdTimes[slot - 1] = txn.createdTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,10 +169,68 @@ export default function Manaboards(props: {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Col className="mt-6 items-center gap-10">
|
<Col className="mt-6 gap-10">
|
||||||
<Manaboard title="" users={topTraders} values={values} />
|
<Manaboard
|
||||||
|
title=""
|
||||||
|
users={topTraders}
|
||||||
|
values={values}
|
||||||
|
createdTimes={createdTimes}
|
||||||
|
/>
|
||||||
{/* <Manaboard title="🏅 Top creators" users={topCreators} /> */}
|
{/* <Manaboard title="🏅 Top creators" users={topCreators} /> */}
|
||||||
|
|
||||||
|
<div className="text-sm">
|
||||||
|
<Title text={'Transaction log'} />
|
||||||
|
<TransactionsTable txns={_.reverse(sortedTxns)} />
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TransactionsTable(props: { txns: Transaction[] }) {
|
||||||
|
const { txns } = props
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
data={txns}
|
||||||
|
search={true}
|
||||||
|
// sort={true}
|
||||||
|
pagination={{
|
||||||
|
enabled: true,
|
||||||
|
limit: 25,
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
id: 'data',
|
||||||
|
name: 'Slot',
|
||||||
|
formatter: (cell) => (cell as SlotData).slot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'category',
|
||||||
|
name: 'Type',
|
||||||
|
formatter: (cell) =>
|
||||||
|
cell === 'BUY_LEADERBOARD_SLOT' ? 'Buy' : 'Tax',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'amount',
|
||||||
|
name: 'Amount',
|
||||||
|
formatter: (cell) => formatMoney(cell as number),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fromUsername',
|
||||||
|
name: 'From',
|
||||||
|
},
|
||||||
|
{ id: 'toUsername', name: 'To' },
|
||||||
|
{
|
||||||
|
id: 'createdTime',
|
||||||
|
name: 'Time',
|
||||||
|
formatter: (cell) =>
|
||||||
|
html(
|
||||||
|
`<span class="whitespace-nowrap">${dayjs(cell as number).format(
|
||||||
|
'h:mma'
|
||||||
|
)}</span>`
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user