Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions apps/api/app/api/v1/lists/[id]/graph/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextResponse } from 'next/server'
import type { SchemaResponse } from '@play-money/api-helpers'
import { getList } from '@play-money/lists/lib/getList'
import { getListTransactionsTimeSeries } from '@play-money/lists/lib/getListTransactionsTimeSeries'
import schema from './schema'

export const dynamic = 'force-dynamic'

export async function GET(
_req: Request,
{ params }: { params: unknown }
): Promise<SchemaResponse<typeof schema.get.responses>> {
try {
const { id } = schema.get.parameters.parse(params)
await getList({ id })

const data = await getListTransactionsTimeSeries({
listId: id,
tickInterval: 1,
endAt: new Date(),
excludeTransactionTypes: ['TRADE_LOSS', 'TRADE_WIN', 'LIQUIDITY_RETURNED'],
})

return NextResponse.json({
data,
})
} catch (error) {
console.log(error) // eslint-disable-line no-console -- Log error for debugging
return NextResponse.json({ error: 'Error processing request' }, { status: 500 })
}
}
27 changes: 27 additions & 0 deletions apps/api/app/api/v1/lists/[id]/graph/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { z } from 'zod'
import { ApiEndpoints, ServerErrorSchema } from '@play-money/api-helpers'

export default {
get: {
summary: 'Get the graph for a List of Markets',
parameters: z.object({ id: z.string() }),
responses: {
200: z.object({
data: z.array(
z.object({
startAt: z.date(),
endAt: z.date(),
markets: z.array(
z.object({
id: z.string(),
probability: z.number(),
})
),
})
),
}),
404: ServerErrorSchema,
500: ServerErrorSchema,
},
},
} as const satisfies ApiEndpoints
16 changes: 16 additions & 0 deletions packages/api-helpers/client/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ export function useMarketGraph({ marketId }: { marketId: string }) {
}>(MARKET_GRAPH_PATH(marketId), { refreshInterval: FIVE_MINUTES })
}

export function LIST_GRAPH_PATH(listId: string) {
return `/v1/lists/${listId}/graph`
}
export function useListGraph({ listId }: { listId: string }) {
return useSWR<{
data: Array<{
startAt: Date
endAt: Date
markets: Array<{
id: string
probability: number
}>
}>
}>(LIST_GRAPH_PATH(listId), { refreshInterval: FIVE_MINUTES })
}

export function useMarketRelated({ marketId }: { marketId: string }) {
return useSWR<{
data: Array<ExtendedMarket>
Expand Down
7 changes: 7 additions & 0 deletions packages/database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ The **Play Money** database package manages the platform's database interactions
```bash
npx prisma migrate dev --name migration_name
```

5. **Running One-Off Scripts**:

- To run any one-off scripts, such as sending a user a gift, use the following command:
```bash
npx dotenv -- npx tsx ./packages/database/scripts/send-user-gift.ts
```
123 changes: 123 additions & 0 deletions packages/lists/components/ListGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { format } from 'date-fns'
import _ from 'lodash'
import React from 'react'
import { LineChart, Line, ResponsiveContainer, YAxis, XAxis, CartesianGrid, Tooltip as ChartTooltip } from 'recharts'
import { useListGraph, useMarketGraph } from '@play-money/api-helpers/client/hooks'
import { formatProbability } from '@play-money/markets/components/MarketProbabilityDetail'
import { Card } from '@play-money/ui/card'
import { ExtendedList } from '../types'

function CustomizedXAxisTick({ x, y, payload }: { x: number; y: number; payload: { value: string } }) {
return (
<g transform={`translate(${x},${y})`}>
<text x={0} y={0} dy={5} dx={-4} textAnchor="end" className="fill-muted-foreground/50">
{format(payload.value, 'MMM d')}
</text>
</g>
)
}

function CustomizedYAxisTick({ x, y, payload }: { x: number; y: number; payload: { value: string | number } }) {
return payload.value !== 0 ? (
<g transform={`translate(${x},${y})`}>
<text x={0} y={0} dy={4} dx={2} textAnchor="start" className="fill-muted-foreground/50">
{payload.value}%
</text>
</g>
) : (
<g />
)
}

export function ListGraph({ list, activeOptionId }: { list: ExtendedList; activeOptionId: string }) {
const { data: graph } = useListGraph({ listId: list.id })
const activeMarketIndex = list.markets.findIndex((m) => m.market.id === activeOptionId)

return (
<Card className="h-40">
{graph?.data ? (
<ResponsiveContainer width="100%" height="100%">
<LineChart width={300} height={128} data={graph.data} margin={{ top: 10, right: 0, bottom: 0, left: 0 }}>
<ChartTooltip
content={({ payload }) => {
const data = payload?.[0]?.payload
if (data) {
return (
<Card className="p-1 font-mono text-xs">
<div>{format(data.startAt, 'MMM d, yyyy')}</div>
{list.markets.map((market, i) => {
const dataOption = data.markets.find(
(o: { id: string; proability: number }) => o.id === market.market.id
)
return (
<div key={market.market.id} style={{ color: market.market.options[0].color }}>
{market.market.question}: {formatProbability(dataOption.probability)}
</div>
)
})}
</Card>
)
}
return null
}}
/>
<XAxis
height={20}
dataKey="endAt"
stroke="hsl(var(--border))"
// axisLine={false}
className="font-mono text-[10px] uppercase"
minTickGap={80}
tick={CustomizedXAxisTick}
tickFormatter={(value) => format(value, 'MMM d')}
/>
<YAxis
type="number"
domain={[0, 100]}
width={40}
stroke="hsl(var(--border))"
className="font-mono text-[10px] uppercase"
orientation="right"
tick={CustomizedYAxisTick}
tickFormatter={(value, i) => (value !== 0 && value !== 100 ? `${value}%` : '')}
/>
{list.markets.map((market, i) => (
<Line
key={market.market.id}
type="step"
dot={false}
dataKey={(data) => {
const dataOption = data.markets.find(
(o: { id: string; proability: number }) => o.id === market.market.id
)
return dataOption.probability
}}
stroke={market.market.options[0].color}
opacity={0.4}
strokeWidth={2.5}
strokeLinejoin="round"
animationDuration={750}
/>
))}
{activeMarketIndex !== -1 ? (
<Line
type="step"
dot={false}
dataKey={(data) => {
const activeOption = data.markets.find(
(option: { id: string; proability: number }) => option.id === activeOptionId
)
return activeOption.probability
}}
stroke={list.markets[activeMarketIndex].market.options[0].color}
strokeWidth={2.5}
strokeLinejoin="round"
animationDuration={750}
/>
) : null}
</LineChart>
</ResponsiveContainer>
) : null}
</Card>
)
}
25 changes: 19 additions & 6 deletions packages/lists/components/ListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { canAddToList, canModifyList } from '../rules'
import { ExtendedList } from '../types'
import { AddMoreListDialog } from './AddMoreListDialog'
import { EditListDialog } from './EditListDialog'
import { ListGraph } from './ListGraph'
import { ListMarketRow } from './ListMarketRow'
import { ListToolbar } from './ListToolbar'

Expand Down Expand Up @@ -77,6 +78,10 @@ export function ListPage({
</div>
</CardHeader>

<CardContent>
<ListGraph list={list} activeOptionId={selected[0]} />
</CardContent>

<CardContent>
{list.markets.length ? (
<Card className="divide-y">
Expand Down Expand Up @@ -117,13 +122,21 @@ export function ListPage({
<CardContent>
<ReadMoreEditor value={list.description ?? ''} maxLines={6} />

{list.tags.length ? (
{list.markets.length ? (
<div className="mt-2 flex flex-wrap gap-2">
{list.tags.map((tag) => (
<Link href={`/questions/tagged/${tag}`} key={tag}>
<Badge variant="secondary">{tag}</Badge>
</Link>
))}
{_(list.markets)
.flatMap((market) => market.market.tags)
.countBy()
.toPairs()
.sortBy(1)
.reverse()
.take(5)
.map(([tag, count]) => (
<Link href={`/questions/tagged/${tag}`} key={tag}>
<Badge variant="secondary">{tag}</Badge>
</Link>
))
.value()}
</div>
) : null}
</CardContent>
Expand Down
Loading
Loading