Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ingawei/undo delete wrapped #3194

Merged
merged 14 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
52 changes: 52 additions & 0 deletions backend/api/src/get-max-min-profit-2024.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is supposed to get the contract where you made the most profit, and the contract where you made the least profit in 2024. Filtered by contracts that resolved in 2024

Copy link
Collaborator

@IanPhilips IanPhilips Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I don't think you need to selecet has-yes-shares and no shares, nor answer id

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { APIHandler } from 'api/helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const getmaxminprofit2024: APIHandler<
'get-max-min-profit-2024'
> = async (props) => {
const { userId } = props
const pg = createSupabaseDirectClient()

const data = await pg.manyOrNone(
`
with filtered_data as (
select
ucm.profit,
ucm.has_yes_shares,
ucm.has_no_shares,
ucm.answer_id,
c.data
from
user_contract_metrics ucm
join
contracts c on ucm.contract_id = c.id
where
ucm.user_id = $1
and c.token = 'MANA'
and c.resolution_time >= '2024-01-01'::timestamp
and c.resolution_time <= '2024-12-31 23:59:59'::timestamp
),
min_max_profits as (
select
max(profit) as max_profit,
min(profit) as min_profit
from
filtered_data
)
select
fd.profit,
fd.has_yes_shares,
fd.has_no_shares,
fd.answer_id,
fd.data
from
filtered_data fd
join
min_max_profits mmp on fd.profit = mmp.max_profit or fd.profit = mmp.min_profit
order by fd.profit desc;
`,
[userId]
)

return data
}
46 changes: 46 additions & 0 deletions backend/api/src/get-monthly-bets-2024.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { APIHandler } from 'api/helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const getmonthlybets2024: APIHandler<'get-monthly-bets-2024'> = async (
props
) => {
const { userId } = props
const pg = createSupabaseDirectClient()

const data = await pg.manyOrNone(
`
with months as (
select
to_char(generate_series(
'2024-01-01'::timestamp with time zone,
'2024-12-01'::timestamp with time zone,
'1 month'::interval
), 'YYYY-MM') as month
),
user_bets as (
select
to_char(date_trunc('month', created_time), 'YYYY-MM') as month,
count(*) as bet_count,
coalesce(sum(abs(amount)), 0) as total_amount
from contract_bets
where
user_id = $1
and created_time >= '2024-01-01'::timestamp with time zone
and created_time < '2025-01-01'::timestamp with time zone
and not coalesce(is_cancelled, false)
and not coalesce(is_redemption, false)
group by date_trunc('month', created_time)
)
select
months.month,
coalesce(user_bets.bet_count, 0) as bet_count,
coalesce(user_bets.total_amount, 0) as total_amount
from months
left join user_bets on months.month = user_bets.month
order by months.month
`,
[userId]
)

return data
}
4 changes: 4 additions & 0 deletions backend/api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ import { generateAIMarketSuggestions } from './generate-ai-market-suggestions'
import { generateAIMarketSuggestions2 } from './generate-ai-market-suggestions-2'
import { generateAIDescription } from './generate-ai-description'
import { generateAIAnswers } from './generate-ai-answers'
import { getmonthlybets2024 } from './get-monthly-bets-2024'
import { getmaxminprofit2024 } from './get-max-min-profit-2024'
import { getNextLoanAmount } from './get-next-loan-amount'

// we define the handlers in this object in order to typecheck that every API has a handler
Expand Down Expand Up @@ -292,5 +294,7 @@ export const handlers: { [k in APIPath]: APIHandler<k> } = {
'generate-ai-market-suggestions-2': generateAIMarketSuggestions2,
'generate-ai-description': generateAIDescription,
'generate-ai-answers': generateAIAnswers,
'get-monthly-bets-2024': getmonthlybets2024,
'get-max-min-profit-2024': getmaxminprofit2024,
'get-next-loan-amount': getNextLoanAmount,
}
20 changes: 20 additions & 0 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1883,6 +1883,26 @@ export const API = (_apiTypeCheck = {
})
.strict(),
},
'get-monthly-bets-2024': {
method: 'GET',
visibility: 'public',
authed: true,
props: z.object({ userId: z.string() }),
returns: [] as { month: string; bet_count: number; total_amount: number }[],
},
'get-max-min-profit-2024': {
method: 'GET',
visibility: 'public',
authed: true,
props: z.object({ userId: z.string() }),
returns: [] as {
profit: number
data: Contract
answer_id: string | null
has_no_shares: boolean
has_yes_shares: boolean
}[],
},
'get-next-loan-amount': {
method: 'GET',
visibility: 'undocumented',
Expand Down
3 changes: 2 additions & 1 deletion web/components/nav/profile-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export function ProfileSummary(props: { user: User; className?: string }) {
<CoinNumber
amount={user?.balance}
numberType="animated"
className="mr-2 text-violet-600 dark:text-violet-400"
className="mr-1 text-violet-600 dark:text-violet-400"
/>
<span>🎁</span>
</div>
<CoinNumber
className="text-sm text-amber-600 dark:text-amber-400"
Expand Down
200 changes: 200 additions & 0 deletions web/components/wrapped/GeneralStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import clsx from 'clsx'
import { User } from 'common/user'
import { formatMoney } from 'common/util/format'
import { useEffect, useState } from 'react'
import { MonthlyBetsType } from 'web/hooks/use-wrapped-2024'
import { Spacer } from '../layout/spacer'
import { LoadingIndicator } from '../widgets/loading-indicator'
import { NavButtons } from './NavButtons'

export function GeneralStats(props: {
monthlyBets: MonthlyBetsType[] | undefined | null
goToPrevPage: () => void
goToNextPage: () => void
user: User
}) {
const { goToPrevPage, goToNextPage, monthlyBets } = props
const animateTotalSpentIn = true
const [animateMostSpentIn, setAnimateMostSpentIn] = useState(false)
const [animateGraphicIn, setAnimateGraphicIn] = useState(false)
const [animateOut, setAnimateOut] = useState(false)

//triggers for animation in
useEffect(() => {
if (!animateTotalSpentIn) return
const timeout1 = setTimeout(() => {
setAnimateMostSpentIn(true)
}, 1500)
const timeout2 = setTimeout(() => {
setAnimateGraphicIn(true)
}, 3000)
const timeout3 = setTimeout(() => {
onGoToNext()
}, 6000)
return () => {
clearTimeout(timeout1)
clearTimeout(timeout2)
clearTimeout(timeout3)
}
}, [animateTotalSpentIn])

const onGoToNext = () => {
setAnimateOut(true)
setTimeout(() => {
goToNextPage()
}, 1000)
}

if (monthlyBets == undefined) {
return (
<div className="mx-auto my-auto">
<LoadingIndicator />
</div>
)
}
const amountBetThisYear = monthlyBets.reduce((accumulator, current) => {
return accumulator + current.total_amount
}, 0)

if (monthlyBets == null) {
return <>An error occured</>
}

const monthWithMostBet = monthlyBets.reduce((max, current) => {
return current.total_amount > max.total_amount ? current : max
})
// Create a date object using the UTC constructor to prevent timezone offsets from affecting the month
const dateOfMostBet = new Date(monthWithMostBet.month)
dateOfMostBet.setDate(dateOfMostBet.getDate() + 1)

// Now you have the month with the highest number of bets
const monthName = dateOfMostBet.toLocaleString('default', {
month: 'long',
timeZone: 'UTC',
})

return (
<>
<div className="relative mx-auto my-auto max-w-lg overflow-hidden">
<div
className={clsx(
'px-4 text-2xl',
animateOut ? 'animate-fade-out' : 'animate-fade-in'
)}
>
This year you spent{' '}
<span className="font-bold text-purple-300">
{formatMoney(amountBetThisYear)}
</span>{' '}
trading on things you believed in!
</div>
<Spacer h={4} />
<div
className={clsx(
'px-4 text-2xl ',
animateMostSpentIn
? animateOut
? 'animate-fade-out'
: 'animate-fade-in'
: 'invisible'
)}
>
You traded the most in{' '}
<span className={clsx('highlight-black font-bold text-purple-300')}>
{monthName}
</span>
, spending{' '}
<span className="font-bold text-purple-300">
{formatMoney(monthWithMostBet.total_amount)}
</span>{' '}
mana!
</div>
<div
className={clsx(
animateGraphicIn
? animateOut
? 'animate-slide-right-out'
: 'animate-slide-right-in'
: 'invisible'
)}
>
<CoinBarChart data={monthlyBets} />
</div>
</div>
<NavButtons goToPrevPage={goToPrevPage} goToNextPage={onGoToNext} />
</>
)
}

const CoinBarChart = (props: { data: MonthlyBetsType[] }) => {
const { data } = props
const svgWidth = 280
const svgHeight = 350
const maxCoins = 20 // Maximum number of coins in a stack
const coinWidth = 9 // Width of the oval (coin)
const coinHeight = 3 // Height of the oval (coin)
const spacing = 35 // Horizontal spacing between stacks
const rowSpacing = svgHeight / 3 // Vertical spacing between rows

const maxManaBet = Math.max(...data.map((item) => item.total_amount))
const scaleFactor = maxManaBet > 0 ? maxCoins / maxManaBet : 1

return (
<div className="ml-6 sm:ml-20">
<svg width={svgWidth} height={svgHeight}>
{data.map((item, index) => {
const coinsInStack = Math.round(item.total_amount * scaleFactor)
const isTopRow = index < 6 // First 6 months (Jan-Jun) are in the top row
const rowIndex = isTopRow ? index : index - 6 // Adjust index for each row
const xPosition = (svgWidth / 6) * rowIndex + spacing // X position of each stack
const yBasePosition = isTopRow ? rowSpacing : rowSpacing * 2 // Y base position for each row

return (
<g key={index}>
{/* Stack of coins */}
{Array.from({ length: coinsInStack }).map((_, coinIndex) => {
const yPosition = yBasePosition - (coinIndex * coinHeight + 30)
return (
<ellipse
key={coinIndex}
cx={xPosition}
cy={yPosition}
rx={coinWidth}
ry={coinHeight}
fill="gold" // Change color as needed
stroke="#92400e"
strokeWidth="1"
/>
)
})}
{/* Month label */}
<text
x={xPosition - coinWidth}
y={yBasePosition}
fill="white"
fontSize="12"
>
{MONTHS[index]}
</text>
</g>
)
})}
</svg>
</div>
)
}

export const MONTHS = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'June',
'July',
'Aug',
'Sept',
'Oct',
'Nov',
'Dec',
]
Loading
Loading