diff --git a/apps/api/app/api/users/[id]/balance/route.ts b/apps/api/app/api/users/[id]/balance/route.ts new file mode 100644 index 00000000..2dadac4e --- /dev/null +++ b/apps/api/app/api/users/[id]/balance/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server' +import type { SchemaResponse } from '@play-money/api-helpers' +import { getBalance, transformMarketBalancesToNumbers } from '@play-money/finance/lib/getBalances' +import { getUserPrimaryAccount } from '@play-money/users/lib/getUserPrimaryAccount' +import schema from './schema' + +export const dynamic = 'force-dynamic' + +export async function GET( + _req: Request, + { params }: { params: unknown } +): Promise> { + try { + const { id } = schema.get.parameters.parse(params) + const userAccount = await getUserPrimaryAccount({ userId: id }) + const balance = await getBalance({ accountId: userAccount.id, assetType: 'CURRENCY', assetId: 'PRIMARY' }) + + return NextResponse.json({ + balance: transformMarketBalancesToNumbers([balance])[0], + }) + } catch (error) { + console.log(error) // eslint-disable-line no-console -- Log error for debugging + return NextResponse.json({ error: 'Error processing request' }, { status: 500 }) + } +} diff --git a/apps/api/app/api/users/[id]/balance/schema.ts b/apps/api/app/api/users/[id]/balance/schema.ts new file mode 100644 index 00000000..1e452305 --- /dev/null +++ b/apps/api/app/api/users/[id]/balance/schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod' +import { ServerErrorSchema, createSchema } from '@play-money/api-helpers' + +export default createSchema({ + get: { + parameters: z.object({ id: z.string() }), + responses: { + 200: z.object({ + // TODO: Hookup with NetBalance + balance: z.object({}), + }), + 404: ServerErrorSchema, + 500: ServerErrorSchema, + }, + }, +}) diff --git a/apps/api/app/api/users/[id]/positions/route.ts b/apps/api/app/api/users/[id]/positions/route.ts new file mode 100644 index 00000000..88eabefd --- /dev/null +++ b/apps/api/app/api/users/[id]/positions/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from 'next/server' +import type { SchemaResponse } from '@play-money/api-helpers' +import { getPositions } from '@play-money/finance/lib/getPositions' +import { getUserById } from '@play-money/users/lib/getUserById' +import schema from './schema' + +export const dynamic = 'force-dynamic' + +export async function GET( + req: Request, + { params }: { params: unknown } +): Promise> { + try { + const url = new URL(req.url) + const searchParams = new URLSearchParams(url.search) + const urlParams = Object.fromEntries(searchParams) + + const { id, pageSize } = schema.GET.parameters.parse({ ...(params || {}), ...urlParams }) + + const user = await getUserById({ id }) + + const { positions } = await getPositions( + { + accountId: user.primaryAccountId, + }, + { field: 'updatedAt', direction: 'desc' }, + { take: pageSize ?? 25, skip: 0 } + ) + + return NextResponse.json({ + positions, + }) + } catch (error) { + console.log(error) // eslint-disable-line no-console -- Log error for debugging + + return NextResponse.json({ error: 'Error processing request' }, { status: 500 }) + } +} diff --git a/apps/api/app/api/users/[id]/positions/schema.ts b/apps/api/app/api/users/[id]/positions/schema.ts new file mode 100644 index 00000000..988f49b1 --- /dev/null +++ b/apps/api/app/api/users/[id]/positions/schema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod' +import { ServerErrorSchema, createSchema } from '@play-money/api-helpers' +import { MarketOptionPositionSchema, UserSchema } from '@play-money/database' + +export default createSchema({ + GET: { + parameters: UserSchema.pick({ id: true }).extend({ pageSize: z.coerce.number().optional() }), + responses: { + 200: z.object({ + positions: z.array(MarketOptionPositionSchema), + }), + 404: ServerErrorSchema, + 500: ServerErrorSchema, + }, + }, +}) diff --git a/packages/api-helpers/client/index.ts b/packages/api-helpers/client/index.ts index d0ec440b..0c434082 100644 --- a/packages/api-helpers/client/index.ts +++ b/packages/api-helpers/client/index.ts @@ -1,7 +1,8 @@ import _ from 'lodash' import { CommentWithReactions } from '@play-money/comments/lib/getComment' -import { Market, User } from '@play-money/database' -import { TransactionWithEntries, LeaderboardUser } from '@play-money/finance/types' +import { Market, MarketOptionPosition, User } from '@play-money/database' +import { NetBalanceAsNumbers } from '@play-money/finance/lib/getBalances' +import { TransactionWithEntries, LeaderboardUser, ExtendedMarketOptionPosition } from '@play-money/finance/types' import { ExtendedMarket } from '@play-money/markets/types' // TODO: @casesandberg Generate this from OpenAPI schema @@ -339,6 +340,22 @@ export async function getUserTransactions({ ) } +export async function getUserPositions({ + userId, + pageSize, +}: { + userId: string + pageSize?: number +}): Promise<{ positions: Array }> { + return apiHandler<{ positions: Array }>( + `${process.env.NEXT_PUBLIC_API_URL}/v1/users/${userId}/positions${pageSize ? `?pageSize=${pageSize}` : ''}` + ) +} + +export async function getUserBalance({ userId }: { userId: string }): Promise<{ balance: NetBalanceAsNumbers }> { + return apiHandler<{ balance: NetBalanceAsNumbers }>(`${process.env.NEXT_PUBLIC_API_URL}/v1/users/${userId}/balance`) +} + export async function getUserMarkets({ userId }: { userId: string }): Promise<{ markets: Array }> { return apiHandler<{ markets: Array }>( `${process.env.NEXT_PUBLIC_API_URL}/v1/markets?createdBy=${userId}` diff --git a/packages/comments/lib/createComment.ts b/packages/comments/lib/createComment.ts index 4d1758c0..1384b8bc 100644 --- a/packages/comments/lib/createComment.ts +++ b/packages/comments/lib/createComment.ts @@ -87,6 +87,7 @@ export async function createComment({ commentCount: { increment: 1, }, + updatedAt: new Date(), }, }) } diff --git a/packages/comments/lib/updateComment.ts b/packages/comments/lib/updateComment.ts index 799b6f56..98b88843 100644 --- a/packages/comments/lib/updateComment.ts +++ b/packages/comments/lib/updateComment.ts @@ -10,7 +10,7 @@ export async function updateComment({ id, content }: { id: string; content?: str const updatedComment = await db.comment.update({ where: { id }, - data: updatedData, + data: { ...updatedData, updatedAt: new Date() }, }) return updatedComment diff --git a/packages/database/migrations/20240916222328_fix_updated_at/migration.sql b/packages/database/migrations/20240916222328_fix_updated_at/migration.sql new file mode 100644 index 00000000..bd66eecc --- /dev/null +++ b/packages/database/migrations/20240916222328_fix_updated_at/migration.sql @@ -0,0 +1,11 @@ +-- AlterTable +ALTER TABLE "Account" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "Balance" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "MarketOptionPosition" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "Transaction" ALTER COLUMN "updatedAt" DROP DEFAULT; diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 754894e5..2cadd067 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -222,7 +222,7 @@ model Transaction { initiatorId String? initiator User? @relation(fields: [initiatorId], references: [id]) createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) + updatedAt DateTime @updatedAt batchId String? batch TransactionBatch? @relation(fields: [batchId], references: [id]) marketId String? @@ -282,7 +282,7 @@ model Account { balances Balance[] positions MarketOptionPosition[] createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Balance { @@ -296,7 +296,7 @@ model Balance { marketId String? market Market? @relation(fields: [marketId], references: [id]) createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@index(fields: [accountId, assetType, assetId, marketId]) } @@ -313,7 +313,7 @@ model MarketOptionPosition { quantity Decimal value Decimal createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@unique(fields: [accountId, optionId]) @@index(fields: [accountId, optionId]) diff --git a/packages/database/scripts/backfill-counts-on-markets.ts b/packages/database/scripts/backfill-counts-on-markets.ts index 400df810..807ca1b6 100644 --- a/packages/database/scripts/backfill-counts-on-markets.ts +++ b/packages/database/scripts/backfill-counts-on-markets.ts @@ -60,6 +60,7 @@ async function main() { }, data: { liquidityCount: data._sum.amount?.toNumber(), + updatedAt: new Date(), }, }) console.log(`Successfully added liquidity count to market with id: ${market.id}`) @@ -94,6 +95,7 @@ async function main() { }, data: { uniqueTradersCount: data.length, + updatedAt: new Date(), }, }) console.log(`Successfully added unique traders count to market with id: ${market.id}`) @@ -130,6 +132,7 @@ async function main() { }, data: { uniquePromotersCount: data.length, + updatedAt: new Date(), }, }) console.log(`Successfully added unique promoters count to market with id: ${market.id}`) @@ -162,6 +165,7 @@ async function main() { }, data: { commentCount: data._count, + updatedAt: new Date(), }, }) console.log(`Successfully added comments count to market with id: ${market.id}`) diff --git a/packages/database/scripts/backfill-market-amm-accounts.ts b/packages/database/scripts/backfill-market-amm-accounts.ts index 14afed8d..b2cf0b4b 100644 --- a/packages/database/scripts/backfill-market-amm-accounts.ts +++ b/packages/database/scripts/backfill-market-amm-accounts.ts @@ -27,6 +27,7 @@ async function main() { type: 'MARKET_CLEARING' as const, }, }, + updatedAt: new Date(), }, }) console.log(`Successfully created account for market with id: ${market.id}`) diff --git a/packages/database/scripts/backfill-user-accounts.ts b/packages/database/scripts/backfill-user-accounts.ts index 1c357987..9f8c839e 100644 --- a/packages/database/scripts/backfill-user-accounts.ts +++ b/packages/database/scripts/backfill-user-accounts.ts @@ -22,6 +22,7 @@ async function main() { type: 'USER', }, }, + updatedAt: new Date(), }, }) console.log(`Successfully created account for user with id: ${user.id}`) diff --git a/packages/database/scripts/update-yes-no-market-options-createdAt.ts b/packages/database/scripts/update-yes-no-market-options-createdAt.ts index 8e897e72..2d09971c 100644 --- a/packages/database/scripts/update-yes-no-market-options-createdAt.ts +++ b/packages/database/scripts/update-yes-no-market-options-createdAt.ts @@ -35,6 +35,7 @@ async function main() { where: { id: noOption.id }, data: { createdAt: new Date(noOption.createdAt.getTime() + 1), + updatedAt: new Date(), }, }) diff --git a/packages/finance/lib/getPositions.ts b/packages/finance/lib/getPositions.ts new file mode 100644 index 00000000..19eee383 --- /dev/null +++ b/packages/finance/lib/getPositions.ts @@ -0,0 +1,48 @@ +import db from '@play-money/database' +import { ExtendedMarketOptionPosition } from '../types' + +interface PositionsFilterOptions { + accountId?: string +} + +interface SortOptions { + field: string + direction: 'asc' | 'desc' +} + +interface PaginationOptions { + skip: number + take: number +} + +export async function getPositions( + filters: PositionsFilterOptions = {}, + sort: SortOptions = { field: 'createdAt', direction: 'desc' }, + pagination: PaginationOptions = { skip: 0, take: 10 } +): Promise<{ positions: Array; total: number }> { + const [positions, total] = await Promise.all([ + db.marketOptionPosition.findMany({ + where: { + accountId: filters.accountId, + value: { gt: 0 }, + }, + include: { + market: true, + option: true, + }, + orderBy: { + [sort.field]: sort.direction, + }, + skip: pagination.skip, + take: pagination.take, + }), + db.marketOptionPosition.count({ + where: { + accountId: filters.accountId, + value: { gt: 0 }, + }, + }), + ]) + + return { positions, total } +} diff --git a/packages/finance/types.ts b/packages/finance/types.ts index af7eb291..204c574d 100644 --- a/packages/finance/types.ts +++ b/packages/finance/types.ts @@ -1,4 +1,4 @@ -import { Market, MarketOption, Transaction, TransactionEntry, User } from '@play-money/database' +import { Market, MarketOption, MarketOptionPosition, Transaction, TransactionEntry, User } from '@play-money/database' export type TransactionEntryInput = Pick< TransactionEntry, @@ -20,3 +20,8 @@ export type LeaderboardUser = { total: number rank: number } + +export type ExtendedMarketOptionPosition = MarketOptionPosition & { + market: Market + option: MarketOption +} diff --git a/packages/markets/components/MarketBalanceBreakdown.tsx b/packages/markets/components/MarketBalanceBreakdown.tsx index 789c939e..8873f080 100644 --- a/packages/markets/components/MarketBalanceBreakdown.tsx +++ b/packages/markets/components/MarketBalanceBreakdown.tsx @@ -57,16 +57,16 @@ export function MarketBalanceBreakdown({ {positions.map((position) => { const option = options.find((option) => option.id === position.optionId)! const value = new Decimal(position.value).toDecimalPlaces(4) - const cost = new Decimal(position.value).toDecimalPlaces(4) - const change = value.sub(cost).div(cost).times(100).toNumber() + const cost = new Decimal(position.cost).toDecimalPlaces(4) + const change = value.sub(cost).div(cost).times(100).round().toNumber() const changeLabel = `(${change > 0 ? '+' : ''}${change}%)` return value.toNumber() ? ( - -
-
- {option.name} + +
+
+ {option.name}
{change ? ( @@ -76,7 +76,7 @@ export function MarketBalanceBreakdown({
-
{option.name}
+
{option.name}
Cost: {new Decimal(position.cost).toDecimalPlaces(4).toString()}
diff --git a/packages/markets/lib/addLiquidity.ts b/packages/markets/lib/addLiquidity.ts index 45bc80bc..ce987c70 100644 --- a/packages/markets/lib/addLiquidity.ts +++ b/packages/markets/lib/addLiquidity.ts @@ -58,6 +58,7 @@ export async function addLiquidity({ where: { id: marketId }, data: { uniquePromotersCount: { increment: amount.toNumber() }, + updatedAt: new Date(), }, }) } diff --git a/packages/markets/lib/createMarket.ts b/packages/markets/lib/createMarket.ts index a371742d..b0f64bd8 100644 --- a/packages/markets/lib/createMarket.ts +++ b/packages/markets/lib/createMarket.ts @@ -114,6 +114,7 @@ export async function createMarket({ }, data: { marketId: createdMarket.id, + updatedAt: new Date(), }, }), db.account.update({ @@ -122,6 +123,7 @@ export async function createMarket({ }, data: { marketId: createdMarket.id, + updatedAt: new Date(), }, }), ]) diff --git a/packages/markets/lib/createMarketBuyTransaction.ts b/packages/markets/lib/createMarketBuyTransaction.ts index 8ea470c7..2c2f1600 100644 --- a/packages/markets/lib/createMarketBuyTransaction.ts +++ b/packages/markets/lib/createMarketBuyTransaction.ts @@ -49,6 +49,7 @@ export async function createMarketBuyTransaction({ liquidityCount: { increment: amount.toNumber(), }, + updatedAt: new Date(), }, }), ]) diff --git a/packages/markets/lib/createMarketLiquidityTransaction.ts b/packages/markets/lib/createMarketLiquidityTransaction.ts index 9e79ebc0..47c0121e 100644 --- a/packages/markets/lib/createMarketLiquidityTransaction.ts +++ b/packages/markets/lib/createMarketLiquidityTransaction.ts @@ -76,6 +76,7 @@ export async function createMarketLiquidityTransaction({ liquidityCount: { increment: amount.toNumber(), }, + updatedAt: new Date(), }, }) const balances = await updateMarketBalances({ ...txParams, marketId }) diff --git a/packages/markets/lib/createMarketResolveLossTransactions.ts b/packages/markets/lib/createMarketResolveLossTransactions.ts index f1c1eb63..6894cd37 100644 --- a/packages/markets/lib/createMarketResolveLossTransactions.ts +++ b/packages/markets/lib/createMarketResolveLossTransactions.ts @@ -83,6 +83,7 @@ export async function createMarketResolveLossTransactions({ decrement: quantity.toNumber(), }, value: 0, + updatedAt: new Date(), }, }) }), diff --git a/packages/markets/lib/createMarketResolveWinTransactions.ts b/packages/markets/lib/createMarketResolveWinTransactions.ts index cd6770c7..ab8bea8f 100644 --- a/packages/markets/lib/createMarketResolveWinTransactions.ts +++ b/packages/markets/lib/createMarketResolveWinTransactions.ts @@ -89,6 +89,7 @@ export async function createMarketResolveWinTransactions({ decrement: position.quantity.toNumber(), }, value: 0, + updatedAt: new Date(), }, }), updateMarketBalances({ ...txParams, marketId }), diff --git a/packages/markets/lib/createMarketSellTransaction.ts b/packages/markets/lib/createMarketSellTransaction.ts index 686420dd..4c6ae24f 100644 --- a/packages/markets/lib/createMarketSellTransaction.ts +++ b/packages/markets/lib/createMarketSellTransaction.ts @@ -49,6 +49,7 @@ export async function createMarketSellTransaction({ liquidityCount: { decrement: amount.toNumber(), }, + updatedAt: new Date(), }, }), ]) diff --git a/packages/markets/lib/marketBuy.ts b/packages/markets/lib/marketBuy.ts index 6e932c60..78b40d37 100644 --- a/packages/markets/lib/marketBuy.ts +++ b/packages/markets/lib/marketBuy.ts @@ -62,6 +62,7 @@ export async function marketBuy({ where: { id: marketId }, data: { uniqueTradersCount: { increment: 1 }, + updatedAt: new Date(), }, }), ]) diff --git a/packages/markets/lib/resolveMarket.ts b/packages/markets/lib/resolveMarket.ts index aca4e522..436c3e0e 100644 --- a/packages/markets/lib/resolveMarket.ts +++ b/packages/markets/lib/resolveMarket.ts @@ -52,7 +52,7 @@ export async function resolveMarket({ await tx.market.update({ where: { id: marketId }, - data: { resolvedAt: now, closeDate: now }, + data: { resolvedAt: now, closeDate: now, updatedAt: now }, }) }) diff --git a/packages/markets/lib/updateMarket.ts b/packages/markets/lib/updateMarket.ts index 43f0d8ed..bed03728 100644 --- a/packages/markets/lib/updateMarket.ts +++ b/packages/markets/lib/updateMarket.ts @@ -35,7 +35,7 @@ export async function updateMarket({ const updatedMarket = await db.market.update({ where: { id }, - data: updatedData, + data: { ...updatedData, updatedAt: new Date() }, }) return updatedMarket diff --git a/packages/markets/lib/updateMarketOption.ts b/packages/markets/lib/updateMarketOption.ts index a78c2538..7a768ee1 100644 --- a/packages/markets/lib/updateMarketOption.ts +++ b/packages/markets/lib/updateMarketOption.ts @@ -13,7 +13,7 @@ export async function updateMarketOption({ id, name, color }: { id: string; name const updatedMarket = await db.marketOption.update({ where: { id }, - data: updatedData, + data: { ...updatedData, updatedAt: new Date() }, }) return updatedMarket diff --git a/packages/markets/lib/updateMarketOptionProbabilities.ts b/packages/markets/lib/updateMarketOptionProbabilities.ts index bcf37ee9..ba77cdf5 100644 --- a/packages/markets/lib/updateMarketOptionProbabilities.ts +++ b/packages/markets/lib/updateMarketOptionProbabilities.ts @@ -24,6 +24,7 @@ export async function updateMarketOptionProbabilities({ }, data: { probability, + updatedAt: new Date(), }, }) }) diff --git a/packages/markets/lib/updateMarketPositionValues.test.ts b/packages/markets/lib/updateMarketPositionValues.test.ts index 2bc818b8..1fd06a69 100644 --- a/packages/markets/lib/updateMarketPositionValues.test.ts +++ b/packages/markets/lib/updateMarketPositionValues.test.ts @@ -97,11 +97,11 @@ describe('updateMarketPositionValues', () => { expect(mockTx.marketOptionPosition.update).toHaveBeenCalledWith({ where: { id: 'user-1-pos' }, - data: { value: expect.closeToDecimal(41.88) }, + data: expect.objectContaining({ value: expect.closeToDecimal(41.88) }), }) expect(mockTx.marketOptionPosition.update).toHaveBeenCalledWith({ where: { id: 'user-2-pos' }, - data: { value: expect.closeToDecimal(9.01) }, + data: expect.objectContaining({ value: expect.closeToDecimal(9.01) }), }) }) diff --git a/packages/markets/lib/updateMarketPositionValues.ts b/packages/markets/lib/updateMarketPositionValues.ts index 50325c02..186003ef 100644 --- a/packages/markets/lib/updateMarketPositionValues.ts +++ b/packages/markets/lib/updateMarketPositionValues.ts @@ -54,6 +54,7 @@ export async function updateMarketPositionValues({ where: { id: position.id }, data: { value: new Decimal(newValue.shares).sub(tax), + updatedAt: new Date(), }, }) }) diff --git a/packages/users/components/UserPlaystyleChart.tsx b/packages/users/components/UserPlaystyleChart.tsx new file mode 100644 index 00000000..1a7037d1 --- /dev/null +++ b/packages/users/components/UserPlaystyleChart.tsx @@ -0,0 +1,88 @@ +'use client' + +import React, { useState } from 'react' +import { RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Sector, Legend, ResponsiveContainer } from 'recharts' + +type DataPoint = { + subject: string + value: number +} + +function customTick({ + payload, + x, + y, + textAnchor, + stroke, + radius, +}: React.SVGProps & { payload: DataPoint }) { + return ( + + + + {payload.value} + + + + ) +} + +export function UserPlaystyleChart({ data }: { data: Array<{ subject: string; value: number; color: string }> }) { + const [size, setSize] = useState(300) + const dataMax = 4 + + const radius = size / 2 + const x = size + const wedgeSize = 180 / data.length + const startAngle = 180 - wedgeSize / 2 + const endAngle = 0 - wedgeSize / 2 + + const scaleFactor = radius / dataMax + return ( + + + + + + + {data.map((entry, index) => { + const startAngle = 180 - index * wedgeSize + const endAngle = 180 - (index + 1) * wedgeSize + const outerRadius = entry.value * scaleFactor + return ( + + ) + })} + + + + + ) +} diff --git a/packages/users/components/UserProfileLayout.tsx b/packages/users/components/UserProfileLayout.tsx index c9b971cf..48863498 100644 --- a/packages/users/components/UserProfileLayout.tsx +++ b/packages/users/components/UserProfileLayout.tsx @@ -1,11 +1,12 @@ import { format } from 'date-fns' import React from 'react' -import { getUserStats, getUserUsername } from '@play-money/api-helpers/client' +import { getUserBalance, getUserStats, getUserUsername } from '@play-money/api-helpers/client' import { CurrencyDisplay } from '@play-money/finance/components/CurrencyDisplay' import { formatNumber } from '@play-money/finance/lib/formatCurrency' import { UserAvatar } from '@play-money/ui/UserAvatar' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@play-money/ui/card' import { Separator } from '@play-money/ui/separator' +import { UserPlaystyleChart } from './UserPlaystyleChart' const DiscordIcon = ({ className }: { className: string }) => ( @@ -33,6 +34,14 @@ c21.209-2.535,41.426-8.171,60.222-16.505C497.448,118.542,479.666,137.004,459.186 ) +function getOrdersOfMagnitude(num: number): number { + if (num < 1) { + return 1 + } + + return Math.floor(Math.log10(num / 1)) + 1 +} + export async function UserProfileLayout({ params: { username }, children, @@ -42,60 +51,78 @@ export async function UserProfileLayout({ }) { const profile = await getUserUsername({ username }) const stats = await getUserStats({ userId: profile.id }) + const { balance } = await getUserBalance({ userId: profile.id }) + + const quester = + balance.subtotals['DAILY_TRADE_BONUS'] + + balance.subtotals['DAILY_MARKET_BONUS'] + + balance.subtotals['DAILY_COMMENT_BONUS'] + + balance.subtotals['DAILY_LIQUIDITY_BONUS'] + const creator = balance.subtotals['CREATOR_TRADER_BONUS'] + const trader = Math.abs(balance.subtotals['TRADE_BUY']) + const promoter = Math.abs(balance.subtotals['LIQUIDITY_DEPOSIT']) + + const playstyleData = [ + { subject: 'Quester', value: getOrdersOfMagnitude(quester) / 4, color: '#a0d8e7' }, + { subject: 'Creator', value: getOrdersOfMagnitude(creator) / 4, color: '#ffc638' }, + { subject: 'Trader', value: getOrdersOfMagnitude(trader) / 4, color: '#00cdb1' }, + { subject: 'Promoter', value: getOrdersOfMagnitude(promoter) / 4, color: '#8247ff' }, + ] return (
- - - -
- {profile.displayName} - @{profile.username} -
- {/*
+
+ + + +
+ {profile.displayName} + @{profile.username} +
+ {/*
*/} -
- -
- {profile.bio ?
{profile.bio}
: null} - {profile.twitterHandle || profile.discordHandle || profile.website ? ( -
- {profile.twitterHandle ? ( - - - {profile.twitterHandle} - - ) : null} + + +
+ {profile.bio ?
{profile.bio}
: null} + {profile.twitterHandle || profile.discordHandle || profile.website ? ( +
+ {profile.twitterHandle ? ( + + + {profile.twitterHandle} + + ) : null} - {profile.discordHandle ? ( - - - {profile.discordHandle} - - ) : null} + {profile.discordHandle ? ( + + + {profile.discordHandle} + + ) : null} - {profile.website ? ( - - - {profile.website} - - ) : null} -
- ) : null} - {/*
+ {profile.website ? ( + + + {profile.website} + + ) : null} +
+ ) : null} + {/*
Followers
*/} -
+
- + -
-
-
- +
+
+
+ +
+
Net worth
+
+
+
{formatNumber(stats.tradingVolume)}
+
Trading volume
+
+
+
{stats.totalMarkets}
+
Total markets
+
+
+
+ {stats.lastTradeAt ? ( + + ) : ( + '-' + )} +
+
Last traded
-
Net worth
-
-
-
{formatNumber(stats.tradingVolume)}
-
Trading volume
-
-
{stats.totalMarkets}
-
Total markets
+ + +
+ Joined —{' '} + {stats.activeDayCount} day{stats.activeDayCount > 1 ? 's' : ''} active
-
-
- {stats.lastTradeAt ? ( - - ) : ( - '-' - )} + + + + +
Playstyle
+ +
+ {playstyleData.map(({ subject, value, color }) => ( +
+
+
{subject}
-
Last traded
-
-
- - -
- Joined —{' '} - {stats.activeDayCount} day{stats.activeDayCount > 1 ? 's' : ''} active + ))}
-
-
+ +
{children}
diff --git a/packages/users/components/UserProfilePage.tsx b/packages/users/components/UserProfilePage.tsx index a1d175e0..e83de758 100644 --- a/packages/users/components/UserProfilePage.tsx +++ b/packages/users/components/UserProfilePage.tsx @@ -1,117 +1,222 @@ import { format } from 'date-fns' +import Decimal from 'decimal.js' import _ from 'lodash' import Link from 'next/link' import React from 'react' -import { getUserMarkets, getUserTransactions, getUserUsername } from '@play-money/api-helpers/client' +import { getUserMarkets, getUserPositions, getUserTransactions, getUserUsername } from '@play-money/api-helpers/client' import { CurrencyDisplay } from '@play-money/finance/components/CurrencyDisplay' import { calculateBalanceChanges, findBalanceChange } from '@play-money/finance/lib/helpers' +import { MarketProbabilityDetail } from '@play-money/markets/components/MarketProbabilityDetail' import { Card, CardContent } from '@play-money/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@play-money/ui/table' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@play-money/ui/tabs' import { cn } from '@play-money/ui/utils' import { UserGraph } from './UserGraph' +export async function UserTradesTable({ userId }: { userId: string }) { + const { transactions } = await getUserTransactions({ userId }) + + return ( + + + + Trade + Market + Date + {/* Profit */} + + + + {transactions.length ? ( + transactions.map((transaction) => { + if (!transaction.initiator) { + return null + } + const balanceChanges = calculateBalanceChanges(transaction) + const primaryChange = findBalanceChange({ + balanceChanges, + accountId: transaction.initiator.primaryAccountId, + assetType: 'CURRENCY', + assetId: 'PRIMARY', + }) + const optionName = transaction.options[0]?.name + + return transaction.market ? ( + + + +
+ {transaction.type === 'TRADE_BUY' ? 'Buy' : transaction.type === 'TRADE_SELL' ? 'Sell' : ''}{' '} + {_.truncate(optionName, { length: 30 })} +
+
+ +
+
+ +
{transaction.market.question}
+
+ {format(transaction.createdAt, 'MMM d, yyyy')} + {/* +
58%
+
*/} +
+ + ) : null + }) + ) : ( + + + No transactions yet + + )} +
+
+ ) +} + +export async function UserMarketsTable({ userId }: { userId: string }) { + const { markets } = await getUserMarkets({ userId }) + + return ( + + + + Market + Resolves + {/* Bonus */} + + + + {markets.length + ? markets.map((market) => { + return ( + + + +
{market.question}
+
+ + {market.closeDate ? format(market.closeDate, 'MMM d, yyyy') : '-'} + + {/* + + */} +
+ + ) + }) + : null} +
+
+ ) +} + export async function UserProfilePage({ username }: { username: string }) { const user = await getUserUsername({ username }) - const { transactions } = await getUserTransactions({ userId: user.id }) + const { positions } = await getUserPositions({ userId: user.id, pageSize: 5 }) const { markets } = await getUserMarkets({ userId: user.id }) return (
- +
- {/* Net Worth */} + Overview Trades - Markets + Questions
- - - -
- - - - - - - - - - Trade - Market - Date - {/* Profit */} - - - - {transactions.length ? ( - transactions.map((transaction) => { - if (!transaction.initiator) { - return null - } - const balanceChanges = calculateBalanceChanges(transaction) - const primaryChange = findBalanceChange({ - balanceChanges, - accountId: transaction.initiator.primaryAccountId, - assetType: 'CURRENCY', - assetId: 'PRIMARY', - }) - const optionName = transaction.options[0]?.name + +
+ + +
Recent Positions
+
+ {positions.length ? ( + positions.map((position) => { + const value = new Decimal(position.value).toDecimalPlaces(4) + const cost = new Decimal(position.cost).toDecimalPlaces(4) + const change = value.sub(cost).div(cost).times(100).round().toNumber() + const changeLabel = `(${change > 0 ? '+' : ''}${change}%)` - return transaction.market ? ( + return ( - - -
- {transaction.type === 'TRADE_BUY' - ? 'Buy' - : transaction.type === 'TRADE_SELL' - ? 'Sell' - : ''}{' '} - {_.truncate(optionName, { length: 30 })} -
-
- -
-
- -
{transaction.market.question}
-
- - {format(transaction.createdAt, 'MMM d, yyyy')} - - {/* -
58%
-
*/} -
+
+
+ + {' '} + {change ? ( + 0 ? 'text-lime-500' : 'text-red-400'}>{changeLabel} + ) : null} + {' '} + {position.option.name} +
+
{position.market.question}
+
+ + ) + }) + ) : ( +
No positions yet
+ )} +
+
+
+ + +
Recent Questions
+
+ {markets.length ? ( + markets.slice(0, 5).map((market) => { + return ( + +
+
{market.question}
+
+ +
+
- ) : null + ) }) ) : ( - - - No transactions yet - +
No questions yet
)} - -
+
+
+
+
+ + + + + @@ -119,36 +224,7 @@ export async function UserProfilePage({ username }: { username: string }) { - - - - Market - Resolves - {/* Bonus */} - - - - {markets.length - ? markets.map((market) => { - return ( - - - -
{market.question}
-
- - {market.closeDate ? format(market.closeDate, 'MMM d, yyyy') : '-'} - - {/* - - */} -
- - ) - }) - : null} -
-
+
diff --git a/packages/users/lib/updateUserById.ts b/packages/users/lib/updateUserById.ts index 99f0cc1f..a0b479dc 100644 --- a/packages/users/lib/updateUserById.ts +++ b/packages/users/lib/updateUserById.ts @@ -44,7 +44,7 @@ export async function updateUserById({ const updatedUser = await db.user.update({ where: { id }, - data: updatedData, + data: { ...updatedData, updatedAt: new Date() }, }) return updatedUser