2022-03-31 19:14:29 +00:00
|
|
|
import clsx from 'clsx'
|
2022-03-31 19:55:40 +00:00
|
|
|
import React, { useEffect, useState } from 'react'
|
2022-03-31 19:14:29 +00:00
|
|
|
import { ENV_CONFIG } from '../../common/envs/constants'
|
|
|
|
import { User } from '../../common/user'
|
2022-03-31 19:55:40 +00:00
|
|
|
import { formatMoney } from '../../common/util/format'
|
2022-03-31 19:14:29 +00:00
|
|
|
import { useUser } from '../hooks/use-user'
|
2022-04-01 04:49:10 +00:00
|
|
|
import { buyLeaderboardSlot } from '../lib/firebase/api-call'
|
2022-04-01 04:52:06 +00:00
|
|
|
import { Transaction, writeTransaction } from '../lib/firebase/transactions'
|
2022-03-31 19:14:29 +00:00
|
|
|
import { AmountInput } from './amount-input'
|
|
|
|
import { Avatar } from './avatar'
|
|
|
|
import { Col } from './layout/col'
|
|
|
|
import { Modal } from './layout/modal'
|
|
|
|
import { Row } from './layout/row'
|
|
|
|
import { SiteLink } from './site-link'
|
|
|
|
import { Title } from './title'
|
|
|
|
|
|
|
|
export function Manaboard(props: {
|
|
|
|
title: string
|
|
|
|
users: User[]
|
2022-04-01 05:16:22 +00:00
|
|
|
values: number[]
|
2022-03-31 19:14:29 +00:00
|
|
|
className?: string
|
|
|
|
}) {
|
|
|
|
// TODO: Ideally, highlight your own entry on the leaderboard
|
2022-04-01 05:41:40 +00:00
|
|
|
let { title, users, className, values } = props
|
|
|
|
|
|
|
|
const [expanded, setExpanded] = useState(false)
|
|
|
|
if (!expanded) {
|
|
|
|
users = users.slice(0, 25)
|
|
|
|
values = values.slice(0, 25)
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:14:29 +00:00
|
|
|
return (
|
|
|
|
<div className={clsx('w-full px-1', className)}>
|
|
|
|
<Title text={title} className="!mt-0" />
|
|
|
|
{users.length === 0 ? (
|
|
|
|
<div className="ml-2 text-gray-500">None yet</div>
|
|
|
|
) : (
|
|
|
|
<div className="overflow-x-auto">
|
|
|
|
<table className="table-zebra table-compact table w-full text-gray-500">
|
|
|
|
<thead>
|
|
|
|
<tr className="p-2">
|
2022-03-31 22:54:40 +00:00
|
|
|
<th>
|
|
|
|
<div className="pl-2">#</div>
|
|
|
|
</th>
|
2022-03-31 19:14:29 +00:00
|
|
|
<th>Name</th>
|
2022-03-31 22:54:40 +00:00
|
|
|
<th>Value</th>
|
2022-03-31 19:14:29 +00:00
|
|
|
<th></th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{users.map((user, index) => (
|
|
|
|
<tr key={user.id}>
|
2022-03-31 22:54:40 +00:00
|
|
|
<td>
|
|
|
|
<div className="pl-2">{index + 1}</div>
|
|
|
|
</td>
|
|
|
|
<td className="w-full" style={{ maxWidth: 190 }}>
|
2022-03-31 19:14:29 +00:00
|
|
|
<Row className="items-center gap-4">
|
|
|
|
<Avatar avatarUrl={user.avatarUrl} size={8} />
|
|
|
|
<div className="truncate">{user.name}</div>
|
|
|
|
</Row>
|
|
|
|
</td>
|
|
|
|
<td>
|
2022-03-31 22:54:40 +00:00
|
|
|
<Row className="items-center gap-4">
|
2022-04-01 05:16:22 +00:00
|
|
|
{formatMoney(values[index])}
|
2022-03-31 22:54:40 +00:00
|
|
|
<BuySlotModal
|
|
|
|
slot={index + 1}
|
|
|
|
title={`${title}`}
|
|
|
|
holder={user}
|
2022-04-01 05:16:22 +00:00
|
|
|
value={values[index]}
|
2022-03-31 22:54:40 +00:00
|
|
|
/>
|
|
|
|
</Row>
|
2022-03-31 19:14:29 +00:00
|
|
|
</td>
|
2022-03-31 22:54:40 +00:00
|
|
|
<td></td>
|
2022-03-31 19:14:29 +00:00
|
|
|
</tr>
|
|
|
|
))}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2022-04-01 05:41:40 +00:00
|
|
|
<button
|
|
|
|
className="btn btn-sm btn-outline m-2"
|
|
|
|
onClick={() => setExpanded(!expanded)}
|
|
|
|
>
|
|
|
|
{expanded ? 'Hide' : 'Show more'}
|
|
|
|
</button>
|
2022-03-31 19:14:29 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:55:40 +00:00
|
|
|
function Label(props: { children: React.ReactNode }) {
|
|
|
|
return <label className="-mb-3 text-sm">{props.children}</label>
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:14:29 +00:00
|
|
|
export function BuySlotModal(props: {
|
|
|
|
title: string
|
|
|
|
holder: User
|
|
|
|
slot: number
|
2022-03-31 19:55:40 +00:00
|
|
|
value: number
|
2022-03-31 19:14:29 +00:00
|
|
|
}) {
|
2022-03-31 19:55:40 +00:00
|
|
|
const { slot, title, holder, value } = props
|
2022-03-31 19:14:29 +00:00
|
|
|
const user = useUser()
|
|
|
|
|
|
|
|
const [open, setOpen] = useState(false)
|
2022-03-31 19:55:40 +00:00
|
|
|
const [newValue, setNewValue] = useState(value)
|
|
|
|
const [message, setMessage] = useState('')
|
|
|
|
useEffect(() => {
|
|
|
|
if (user?.name) {
|
|
|
|
setMessage(user.name)
|
|
|
|
}
|
|
|
|
}, [user])
|
|
|
|
|
2022-04-01 04:49:10 +00:00
|
|
|
const onBuy = async () => {
|
|
|
|
// Feel free to change this. - James
|
|
|
|
const slotId = `${title}-${slot}`
|
|
|
|
await buyLeaderboardSlot({ slotId, reassessValue: newValue })
|
|
|
|
}
|
|
|
|
|
2022-03-31 19:14:29 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Modal open={open} setOpen={setOpen}>
|
2022-03-31 19:55:40 +00:00
|
|
|
<Col className="gap-5 rounded-md bg-white p-6 text-gray-500">
|
2022-04-01 04:52:06 +00:00
|
|
|
<Title text={`Buy slot #${slot}`} className="!mt-0" />
|
2022-03-31 19:14:29 +00:00
|
|
|
|
2022-03-31 19:55:40 +00:00
|
|
|
<Label>Current value: {formatMoney(value)}</Label>
|
2022-03-31 22:54:40 +00:00
|
|
|
{user && (
|
2022-04-01 01:03:27 +00:00
|
|
|
<Row className="items-center gap-4 rounded-md bg-gray-100 p-2 text-sm">
|
|
|
|
<div className="pl-2">{slot}</div>
|
2022-03-31 22:54:40 +00:00
|
|
|
<Avatar avatarUrl={user.avatarUrl} size={8} />
|
|
|
|
<div className="truncate">{message}</div>
|
|
|
|
</Row>
|
|
|
|
)}
|
2022-03-31 19:55:40 +00:00
|
|
|
|
|
|
|
<Label>(Optional) set message</Label>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
className="input input-bordered w-full max-w-xs"
|
|
|
|
onChange={(e) => {
|
|
|
|
setMessage(e.target.value)
|
|
|
|
}}
|
|
|
|
value={message}
|
|
|
|
/>
|
|
|
|
|
2022-03-31 22:54:40 +00:00
|
|
|
<Label>Reassess value</Label>
|
|
|
|
<AmountInput
|
|
|
|
amount={newValue}
|
|
|
|
onChange={(amount) => setNewValue(amount ?? 0)}
|
|
|
|
error=""
|
|
|
|
label={ENV_CONFIG.moneyMoniker}
|
|
|
|
/>
|
2022-03-31 19:14:29 +00:00
|
|
|
|
2022-04-01 04:52:06 +00:00
|
|
|
<button
|
|
|
|
className="btn btn-primary"
|
|
|
|
onClick={() => {
|
|
|
|
if (user) {
|
2022-04-01 05:16:22 +00:00
|
|
|
buySlot({
|
|
|
|
holder,
|
|
|
|
buyer: user,
|
|
|
|
amount: value,
|
|
|
|
slot,
|
|
|
|
message,
|
|
|
|
newValue,
|
|
|
|
})
|
2022-04-01 04:52:06 +00:00
|
|
|
setOpen(false)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
2022-03-31 19:55:40 +00:00
|
|
|
Buy Slot ({formatMoney(value)})
|
|
|
|
</button>
|
2022-03-31 22:54:40 +00:00
|
|
|
<div className="-mt-2 text-sm">
|
|
|
|
Additional fees: {formatMoney(newValue / 10)} per hour
|
|
|
|
</div>
|
2022-03-31 19:14:29 +00:00
|
|
|
</Col>
|
|
|
|
</Modal>
|
2022-03-31 22:54:40 +00:00
|
|
|
<button
|
|
|
|
className="btn btn-outline btn-sm normal-case"
|
|
|
|
onClick={() => setOpen(true)}
|
|
|
|
>
|
2022-03-31 19:14:29 +00:00
|
|
|
Buy
|
|
|
|
</button>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
2022-04-01 04:52:06 +00:00
|
|
|
|
|
|
|
async function buySlot(options: {
|
|
|
|
holder: User
|
|
|
|
buyer: User
|
|
|
|
amount: number
|
|
|
|
slot: number
|
|
|
|
message: string
|
2022-04-01 05:16:22 +00:00
|
|
|
newValue: number
|
2022-04-01 04:52:06 +00:00
|
|
|
}) {
|
2022-04-01 05:16:22 +00:00
|
|
|
const { holder, buyer, amount, slot, message, newValue } = options
|
2022-04-01 04:52:06 +00:00
|
|
|
const createdTime = Date.now()
|
|
|
|
const buyTransaction: Transaction = {
|
|
|
|
id: '',
|
|
|
|
createdTime,
|
|
|
|
|
|
|
|
fromId: buyer.id,
|
|
|
|
fromName: buyer.name,
|
|
|
|
fromUsername: buyer.username,
|
|
|
|
fromAvatarUrl: buyer.avatarUrl,
|
|
|
|
|
|
|
|
toId: holder.id,
|
|
|
|
toName: holder.name,
|
|
|
|
toUsername: holder.username,
|
|
|
|
toAvatarUrl: holder.avatarUrl,
|
|
|
|
|
|
|
|
amount: amount,
|
|
|
|
|
|
|
|
category: 'BUY_LEADERBOARD_SLOT',
|
|
|
|
description: `${buyer.name} bought a slot from ${holder.name}`,
|
|
|
|
data: {
|
|
|
|
slot,
|
|
|
|
message,
|
2022-04-01 05:16:22 +00:00
|
|
|
newValue,
|
2022-04-01 04:52:06 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
const feeTransaction: Transaction = {
|
|
|
|
id: '',
|
|
|
|
createdTime,
|
|
|
|
|
|
|
|
fromId: holder.id,
|
|
|
|
fromName: holder.name,
|
|
|
|
fromUsername: holder.username,
|
|
|
|
fromAvatarUrl: holder.avatarUrl,
|
|
|
|
|
|
|
|
// Send fee to Manifold Markets official account
|
|
|
|
toId: 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2',
|
|
|
|
toName: 'Manifold Markets',
|
|
|
|
toUsername: 'ManifoldMarkets',
|
|
|
|
toAvatarUrl: 'https://manifold.markets/logo-bg-white.png',
|
|
|
|
|
|
|
|
amount: 10, // TODO: Calculate fee
|
|
|
|
category: 'LEADERBOARD_TAX',
|
|
|
|
description: `${holder.name} paid M$ 10 in fees`,
|
|
|
|
data: {
|
|
|
|
slot,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
writeTransaction(buyTransaction),
|
|
|
|
writeTransaction(feeTransaction),
|
|
|
|
])
|
|
|
|
}
|