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 { useUser } from '../hooks/use-user' | ||||
| 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 { Avatar } from './avatar' | ||||
| import { Col } from './layout/col' | ||||
|  | @ -18,10 +22,11 @@ export function Manaboard(props: { | |||
|   title: string | ||||
|   users: User[] | ||||
|   values: number[] | ||||
|   createdTimes: number[] | ||||
|   className?: string | ||||
| }) { | ||||
|   // 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) | ||||
|   if (!expanded) { | ||||
|  | @ -67,6 +72,7 @@ export function Manaboard(props: { | |||
|                         title={`${title}`} | ||||
|                         holder={user} | ||||
|                         value={values[index]} | ||||
|                         createdTime={createdTimes[index]} | ||||
|                       /> | ||||
|                     </Row> | ||||
|                   </td> | ||||
|  | @ -96,8 +102,9 @@ export function BuySlotModal(props: { | |||
|   holder: User | ||||
|   slot: number | ||||
|   value: number | ||||
|   createdTime: number | ||||
| }) { | ||||
|   const { slot, title, holder, value } = props | ||||
|   const { slot, title, holder, value, createdTime } = props | ||||
|   const user = useUser() | ||||
| 
 | ||||
|   const [open, setOpen] = useState(false) | ||||
|  | @ -109,10 +116,29 @@ export function BuySlotModal(props: { | |||
|     } | ||||
|   }, [user]) | ||||
| 
 | ||||
|   const onBuy = async () => { | ||||
|     // Feel free to change this. - James
 | ||||
|     const slotId = `${title}-${slot}` | ||||
|     await buyLeaderboardSlot({ slotId, reassessValue: newValue }) | ||||
|   // const onBuy = async () => {
 | ||||
|   //   // Feel free to change this. - James
 | ||||
|   //   const slotId = `${title}-${slot}`
 | ||||
|   //   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 ( | ||||
|  | @ -148,22 +174,7 @@ export function BuySlotModal(props: { | |||
|             label={ENV_CONFIG.moneyMoniker} | ||||
|           /> | ||||
| 
 | ||||
|           <button | ||||
|             className="btn btn-primary" | ||||
|             onClick={() => { | ||||
|               if (user) { | ||||
|                 buySlot({ | ||||
|                   holder, | ||||
|                   buyer: user, | ||||
|                   amount: value, | ||||
|                   slot, | ||||
|                   message, | ||||
|                   newValue, | ||||
|                 }) | ||||
|                 setOpen(false) | ||||
|               } | ||||
|             }} | ||||
|           > | ||||
|           <button className="btn btn-primary" onClick={onBuy}> | ||||
|             Buy Slot ({formatMoney(value)}) | ||||
|           </button> | ||||
|           <div className="-mt-2 text-sm"> | ||||
|  | @ -181,19 +192,16 @@ export function BuySlotModal(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| async function buySlot(options: { | ||||
|   holder: User | ||||
| function buyTransaction(options: { | ||||
|   buyer: User | ||||
|   holder: User | ||||
|   buyData: SlotData | ||||
|   amount: number | ||||
|   slot: number | ||||
|   message: string | ||||
|   newValue: number | ||||
| }) { | ||||
|   const { holder, buyer, amount, slot, message, newValue } = options | ||||
|   const createdTime = Date.now() | ||||
|   const buyTransaction: Transaction = { | ||||
| }): Transaction { | ||||
|   const { buyer, holder, buyData, amount } = options | ||||
|   return { | ||||
|     id: '', | ||||
|     createdTime, | ||||
|     createdTime: Date.now(), | ||||
| 
 | ||||
|     fromId: buyer.id, | ||||
|     fromName: buyer.name, | ||||
|  | @ -205,20 +213,30 @@ async function buySlot(options: { | |||
|     toUsername: holder.username, | ||||
|     toAvatarUrl: holder.avatarUrl, | ||||
| 
 | ||||
|     amount: amount, | ||||
|     amount, | ||||
| 
 | ||||
|     category: 'BUY_LEADERBOARD_SLOT', | ||||
|     description: `${buyer.name} bought a slot from ${holder.name}`, | ||||
|     data: { | ||||
|       slot, | ||||
|       message, | ||||
|       newValue, | ||||
|     }, | ||||
|     data: buyData, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   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: '', | ||||
|     createdTime, | ||||
|     createdTime: Date.now(), | ||||
| 
 | ||||
|     fromId: holder.id, | ||||
|     fromName: holder.name, | ||||
|  | @ -231,16 +249,12 @@ async function buySlot(options: { | |||
|     toUsername: 'ManifoldMarkets', | ||||
|     toAvatarUrl: 'https://manifold.markets/logo-bg-white.png', | ||||
| 
 | ||||
|     amount: 10, // TODO: Calculate fee
 | ||||
|     amount: tax, | ||||
| 
 | ||||
|     category: 'LEADERBOARD_TAX', | ||||
|     description: `${holder.name} paid M$ 10 in fees`, | ||||
|     data: { | ||||
|       slot, | ||||
|     }, | ||||
|   } | ||||
| 
 | ||||
|   await Promise.all([ | ||||
|     writeTransaction(buyTransaction), | ||||
|     writeTransaction(feeTransaction), | ||||
|   ]) | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,11 @@ import { Title } from '../components/title' | |||
| import { useTransactions } from '../hooks/use-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 async function getStaticPropz() { | ||||
|   const [topTraders, topCreators] = await Promise.all([ | ||||
|  | @ -94,11 +99,12 @@ function Explanation() { | |||
|     </div> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
| // [ ] Draw attention to leaderboard
 | ||||
| // [ ] Show total returned to Manifold
 | ||||
| // [ ] Restrict to at most buying one slot per user?
 | ||||
| export default function Manaboards(props: { | ||||
|   topTraders: User[] | ||||
|   topCreators: User[] | ||||
|  | @ -112,6 +118,7 @@ export default function Manaboards(props: { | |||
|   const values = Array.from(Array(topTraders.length).keys()) | ||||
|     .map((i) => i + 1) | ||||
|     .reverse() | ||||
|   const createdTimes = new Array(topTraders.length).fill(0) | ||||
| 
 | ||||
|   // Find the most recent purchases of each slot, and replace the entries in topTraders
 | ||||
|   const transactions = useTransactions() ?? [] | ||||
|  | @ -124,6 +131,7 @@ export default function Manaboards(props: { | |||
|       const slot = data.slot | ||||
|       topTraders[slot - 1] = buyer | ||||
|       values[slot - 1] = data.newValue | ||||
|       createdTimes[slot - 1] = txn.createdTime | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -161,10 +169,68 @@ export default function Manaboards(props: { | |||
|         </p> | ||||
|       </div> | ||||
| 
 | ||||
|       <Col className="mt-6 items-center gap-10"> | ||||
|         <Manaboard title="" users={topTraders} values={values} /> | ||||
|       <Col className="mt-6 gap-10"> | ||||
|         <Manaboard | ||||
|           title="" | ||||
|           users={topTraders} | ||||
|           values={values} | ||||
|           createdTimes={createdTimes} | ||||
|         /> | ||||
|         {/* <Manaboard title="🏅 Top creators" users={topCreators} /> */} | ||||
| 
 | ||||
|         <div className="text-sm"> | ||||
|           <Title text={'Transaction log'} /> | ||||
|           <TransactionsTable txns={_.reverse(sortedTxns)} /> | ||||
|         </div> | ||||
|       </Col> | ||||
|     </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