Merge branch 'main' of https://github.com/manifoldmarkets/manifold
This commit is contained in:
commit
13cffcdaf1
|
@ -21,6 +21,25 @@ const computeInvestmentValue = (
|
|||
})
|
||||
}
|
||||
|
||||
export const computeInvestmentValueCustomProb = (
|
||||
bets: Bet[],
|
||||
contract: Contract,
|
||||
p: number
|
||||
) => {
|
||||
return sumBy(bets, (bet) => {
|
||||
if (!contract || contract.isResolved) return 0
|
||||
if (bet.sale || bet.isSold) return 0
|
||||
const { outcome, shares } = bet
|
||||
|
||||
const betP = outcome === 'YES' ? p : 1 - p
|
||||
|
||||
const payout = betP * shares
|
||||
const value = payout - (bet.loanAmount ?? 0)
|
||||
if (isNaN(value)) return 0
|
||||
return value
|
||||
})
|
||||
}
|
||||
|
||||
const computeTotalPool = (userContracts: Contract[], startTime = 0) => {
|
||||
const periodFilteredContracts = userContracts.filter(
|
||||
(contract) => contract.createdTime >= startTime
|
||||
|
|
|
@ -10,6 +10,7 @@ export type Group = {
|
|||
totalContracts: number
|
||||
totalMembers: number
|
||||
aboutPostId?: string
|
||||
postIds: string[]
|
||||
chatDisabled?: boolean
|
||||
mostRecentContractAddedTime?: number
|
||||
cachedLeaderboard?: {
|
||||
|
|
|
@ -168,7 +168,7 @@ export const getPayoutsMultiOutcome = (
|
|||
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
|
||||
const profit = winnings - amount
|
||||
|
||||
const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
|
||||
const payout = amount + (1 - DPM_FEES) * profit
|
||||
return { userId, profit, payout }
|
||||
})
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ export type PrivateUser = {
|
|||
|
||||
email?: string
|
||||
weeklyTrendingEmailSent?: boolean
|
||||
weeklyPortfolioUpdateEmailSent?: boolean
|
||||
manaBonusEmailSent?: boolean
|
||||
initialDeviceToken?: string
|
||||
initialIpAddress?: string
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export const MINUTE_MS = 60 * 1000
|
||||
export const HOUR_MS = 60 * MINUTE_MS
|
||||
export const DAY_MS = 24 * HOUR_MS
|
||||
|
||||
export const sleep = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
|
|
@ -8,9 +8,8 @@ A list of community-created projects built on, or related to, Manifold Markets.
|
|||
|
||||
## Sites using Manifold
|
||||
|
||||
- [CivicDashboard](https://civicdash.org/dashboard) - Uses Manifold to for tracked solutions for the SF city government
|
||||
- [Research.Bet](https://research.bet/) - Prediction market for scientific papers, using Manifold
|
||||
- [WagerWith.me](https://www.wagerwith.me/) — Bet with your friends, with full Manifold integration to bet with M$.
|
||||
- [Alignment Markets](https://alignmentmarkets.com/) - Bet on the progress of benchmarks in ML safety!
|
||||
|
||||
## API / Dev
|
||||
|
||||
|
@ -28,6 +27,7 @@ A list of community-created projects built on, or related to, Manifold Markets.
|
|||
- [mana](https://github.com/AnnikaCodes/mana) - A Discord bot for Manifold by [@arae](https://manifold.markets/arae)
|
||||
|
||||
## Writeups
|
||||
|
||||
- [Information Markets, Decision Markets, Attention Markets, Action Markets](https://astralcodexten.substack.com/p/information-markets-decision-markets) by Scott Alexander
|
||||
- [Mismatched Monetary Motivation in Manifold Markets](https://kevin.zielnicki.com/2022/02/17/manifold/) by Kevin Zielnicki
|
||||
- [Introducing the Salem/CSPI Forecasting Tournament](https://www.cspicenter.com/p/introducing-the-salemcspi-forecasting) by Richard Hanania
|
||||
|
@ -36,5 +36,12 @@ A list of community-created projects built on, or related to, Manifold Markets.
|
|||
|
||||
## Art
|
||||
|
||||
- Folded origami and doodles by [@hamnox](https://manifold.markets/hamnox) ![](https://i.imgur.com/nVGY4pL.png)
|
||||
- Laser-cut Foldy by [@wasabipesto](https://manifold.markets/wasabipesto) ![](https://i.imgur.com/g9S6v3P.jpg)
|
||||
- Folded origami and doodles by [@hamnox](https://manifold.markets/hamnox) ![](https://i.imgur.com/nVGY4pL.png)
|
||||
- Laser-cut Foldy by [@wasabipesto](https://manifold.markets/wasabipesto) ![](https://i.imgur.com/g9S6v3P.jpg)
|
||||
|
||||
## Alumni
|
||||
|
||||
_These projects are no longer active, but were really really cool!_
|
||||
|
||||
- [Research.Bet](https://research.bet/) - Prediction market for scientific papers, using Manifold
|
||||
- [CivicDashboard](https://civicdash.org/dashboard) - Uses Manifold to for tracked solutions for the SF city government
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
"mailgun-js": "0.22.0",
|
||||
"module-alias": "2.2.2",
|
||||
"node-fetch": "2",
|
||||
"react-masonry-css": "1.0.16",
|
||||
"stripe": "8.194.0",
|
||||
"zod": "3.17.2"
|
||||
},
|
||||
|
@ -48,7 +47,8 @@
|
|||
"@types/mailgun-js": "0.22.12",
|
||||
"@types/module-alias": "2.0.1",
|
||||
"@types/node-fetch": "2.6.2",
|
||||
"firebase-functions-test": "0.3.3"
|
||||
"firebase-functions-test": "0.3.3",
|
||||
"puppeteer": "18.0.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { getNewMultiBetInfo } from '../../common/new-bet'
|
|||
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
||||
import { getValues } from './utils'
|
||||
import { APIError, newEndpoint, validate } from './api'
|
||||
import { addUserToContractFollowers } from './follow-market'
|
||||
|
||||
const bodySchema = z.object({
|
||||
contractId: z.string().max(MAX_ANSWER_LENGTH),
|
||||
|
@ -96,6 +97,8 @@ export const createanswer = newEndpoint(opts, async (req, auth) => {
|
|||
return answer
|
||||
})
|
||||
|
||||
await addUserToContractFollowers(contractId, auth.uid)
|
||||
|
||||
return answer
|
||||
})
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ export const creategroup = newEndpoint({}, async (req, auth) => {
|
|||
anyoneCanJoin,
|
||||
totalContracts: 0,
|
||||
totalMembers: memberIds.length,
|
||||
postIds: [],
|
||||
}
|
||||
|
||||
await groupRef.create(group)
|
||||
|
|
|
@ -34,11 +34,12 @@ const contentSchema: z.ZodType<JSONContent> = z.lazy(() =>
|
|||
const postSchema = z.object({
|
||||
title: z.string().min(1).max(MAX_POST_TITLE_LENGTH),
|
||||
content: contentSchema,
|
||||
groupId: z.string().optional(),
|
||||
})
|
||||
|
||||
export const createpost = newEndpoint({}, async (req, auth) => {
|
||||
const firestore = admin.firestore()
|
||||
const { title, content } = validate(postSchema, req.body)
|
||||
const { title, content, groupId } = validate(postSchema, req.body)
|
||||
|
||||
const creator = await getUser(auth.uid)
|
||||
if (!creator)
|
||||
|
@ -60,6 +61,18 @@ export const createpost = newEndpoint({}, async (req, auth) => {
|
|||
}
|
||||
|
||||
await postRef.create(post)
|
||||
if (groupId) {
|
||||
const groupRef = firestore.collection('groups').doc(groupId)
|
||||
const group = await groupRef.get()
|
||||
if (group.exists) {
|
||||
const groupData = group.data()
|
||||
if (groupData) {
|
||||
const postIds = groupData.postIds ?? []
|
||||
postIds.push(postRef.id)
|
||||
await groupRef.update({ postIds })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { status: 'success', post }
|
||||
})
|
||||
|
|
|
@ -0,0 +1,411 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>Weekly Portfolio Update on Manifold</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
font-family:"Readex Pro", Helvetica, sans-serif;
|
||||
}
|
||||
table { margin: 0 auto; }
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0;
|
||||
mso-table-rspace: 0;
|
||||
}
|
||||
th {color:#000000; font-size:17px;}
|
||||
th, td {padding: 10px; }
|
||||
td{ font-size: 17px}
|
||||
th, td { vertical-align: center; text-align: left }
|
||||
a { vertical-align: center; text-align: left}
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
p.change{
|
||||
margin: 0; vertical-align: middle;font-size:16px;display: inline; padding: 2px; border-radius: 5px; width: 20px; text-align: right;
|
||||
}
|
||||
p.prob{
|
||||
font-size: 22px;display: inline; vertical-align: middle; font-weight: bold; width: 50px;
|
||||
}
|
||||
a.question{
|
||||
font-size: 18px;display: inline; vertical-align: middle;
|
||||
}
|
||||
td.question{
|
||||
vertical-align: middle; padding-bottom: 15px; text-align: left;
|
||||
}
|
||||
td.probs{
|
||||
text-align: right; padding-left: 10px; min-width: 115px
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG />
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width: 480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
[owa] .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width: 480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="word-spacing: normal; background-color: #f4f4f4">
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="direction:ltr;font-size:0px;padding:20px 0px 5px 0px;padding-bottom:0px;padding-left:0px;padding-right:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix"
|
||||
style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="background:#ffffff;font-size:0px;padding:10px 25px 10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:550px;">
|
||||
|
||||
<a href="https://manifold.markets" target="_blank">
|
||||
|
||||
<img alt="banner logo" height="auto"
|
||||
src="https://manifold.markets/logo-banner.png"
|
||||
style="border:none;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;"
|
||||
title="" width="550">
|
||||
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="
|
||||
background: #ffffff;
|
||||
background-color: #ffffff;
|
||||
margin: 0px auto;
|
||||
max-width: 600px;
|
||||
">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="background: #ffffff; background-color: #ffffff; width: 100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 20px 0px 0px 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="
|
||||
font-size: 0px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="vertical-align: top; margin-bottom: 30px" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||
<p class="text-build-content"
|
||||
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
|
||||
data-testid="4XoHRGw1Y"><span
|
||||
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||
</span>Hi {{name}},</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:20px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||
<p class="text-build-content"
|
||||
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 0px;"
|
||||
data-testid="4XoHRGw1Y">
|
||||
<span style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||
We ran the numbers and here's how you did this past week!
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!--/ show 5 columns with headers titled: Investment value, 7-day change, current balance, tips received, and markets made/-->
|
||||
<tr>
|
||||
<tr>
|
||||
<th style='font-size: 22px; text-align: center'>
|
||||
Profit
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style='padding-bottom: 30px; text-align: center'>
|
||||
<p class='change' style='font-size: 24px; padding:4px; {{profit_style}}'>
|
||||
{{profit}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<td align="center"
|
||||
style="font-size:0px;padding:10px 20px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="border-collapse:collapse;border-spacing:0px; ">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style='width: 170px'>
|
||||
🔥 Prediction streak
|
||||
</th>
|
||||
<td>
|
||||
{{prediction_streak}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
💸 Tips received
|
||||
</th>
|
||||
<td>
|
||||
{{tips_received}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
📈 Markets traded
|
||||
</th>
|
||||
<td>
|
||||
{{markets_traded}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
❓ Markets created
|
||||
</th>
|
||||
|
||||
<td>
|
||||
{{markets_created}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style='width: 55px'>
|
||||
🥳 Traders attracted
|
||||
</th>
|
||||
<td>
|
||||
{{unique_bettors}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 0 0 20px 0;
|
||||
text-align: center;
|
||||
">
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 20px 0px 20px 0px;
|
||||
text-align: center;
|
||||
">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="
|
||||
font-size: 0px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding: 0">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
">
|
||||
<div style="
|
||||
font-family: Ubuntu, Helvetica, Arial,
|
||||
sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
">
|
||||
<p style="margin: 10px 0">
|
||||
This e-mail has been sent to
|
||||
{{name}},
|
||||
<a href="{{unsubscribeUrl}}" style="
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
" target="_blank">click here to unsubscribe from this type of notification</a>.
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
510
functions/src/email-templates/weekly-portfolio-update.html
Normal file
510
functions/src/email-templates/weekly-portfolio-update.html
Normal file
|
@ -0,0 +1,510 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>Weekly Portfolio Update on Manifold</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
font-family:"Readex Pro", Helvetica, sans-serif;
|
||||
}
|
||||
table { margin: 0 auto; }
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0;
|
||||
mso-table-rspace: 0;
|
||||
}
|
||||
th {color:#000000; font-size:17px;}
|
||||
th, td {padding: 10px; }
|
||||
td{ font-size: 17px}
|
||||
th, td { vertical-align: center; text-align: left }
|
||||
a { vertical-align: center; text-align: left}
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
p.change{
|
||||
margin: 0; vertical-align: middle;font-size:16px;display: inline; padding: 2px; border-radius: 5px; width: 20px; text-align: right;
|
||||
}
|
||||
p.prob{
|
||||
font-size: 22px;display: inline; vertical-align: middle; font-weight: bold; width: 50px;
|
||||
}
|
||||
a.question{
|
||||
font-size: 18px;display: inline; vertical-align: middle;
|
||||
}
|
||||
td.question{
|
||||
vertical-align: middle; padding-bottom: 15px; text-align: left;
|
||||
}
|
||||
td.probs{
|
||||
text-align: right; padding-left: 10px; min-width: 115px
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG />
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Readex+Pro" rel="stylesheet" type="text/css" />
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
@import url(https://fonts.googleapis.com/css?family=Readex+Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width: 480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
[owa] .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width: 480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="word-spacing: normal; background-color: #f4f4f4">
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="direction:ltr;font-size:0px;padding:20px 0px 5px 0px;padding-bottom:0px;padding-left:0px;padding-right:0px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix"
|
||||
style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="background:#ffffff;font-size:0px;padding:10px 25px 10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:550px;">
|
||||
|
||||
<a href="https://manifold.markets" target="_blank">
|
||||
|
||||
<img alt="banner logo" height="auto"
|
||||
src="https://manifold.markets/logo-banner.png"
|
||||
style="border:none;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;"
|
||||
title="" width="550">
|
||||
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="
|
||||
background: #ffffff;
|
||||
background-color: #ffffff;
|
||||
margin: 0px auto;
|
||||
max-width: 600px;
|
||||
">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="background: #ffffff; background-color: #ffffff; width: 100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 20px 0px 0px 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="
|
||||
font-size: 0px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="vertical-align: top" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||
<p class="text-build-content"
|
||||
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 10px;"
|
||||
data-testid="4XoHRGw1Y"><span
|
||||
style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||
</span>Hi {{name}},</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="font-size:0px;padding:10px 25px;padding-top:0px;padding-bottom:20px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||
<p class="text-build-content"
|
||||
style="line-height: 24px; margin: 10px 0; margin-top: 10px; margin-bottom: 0px;"
|
||||
data-testid="4XoHRGw1Y">
|
||||
<span style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||
We ran the numbers and here's how you did this past week!
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!--/ show 5 columns with headers titled: Investment value, 7-day change, current balance, tips received, and markets made/-->
|
||||
<tr>
|
||||
<tr>
|
||||
<th style='font-size: 22px; text-align: center'>
|
||||
Profit
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style='padding-bottom: 30px; text-align: center'>
|
||||
<p class='change' style='font-size: 24px; padding:4px; {{profit_style}}'>
|
||||
{{profit}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<td align="center"
|
||||
style="font-size:0px;padding:10px 20px;padding-top:0px;padding-bottom:0px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="border-collapse:collapse;border-spacing:0px; ">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style='width: 170px'>
|
||||
🔥 Prediction streak
|
||||
</th>
|
||||
<td>
|
||||
{{prediction_streak}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
💸 Tips received
|
||||
</th>
|
||||
<td>
|
||||
{{tips_received}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
📈 Markets traded
|
||||
</th>
|
||||
<td>
|
||||
{{markets_traded}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
❓ Markets created
|
||||
</th>
|
||||
|
||||
<td>
|
||||
{{markets_created}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style='width: 55px'>
|
||||
🥳 Traders attracted
|
||||
</th>
|
||||
<td>
|
||||
{{unique_bettors}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="font-size:0px;padding:10px 25px;padding-top:20px;padding-bottom:0px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Arial, sans-serif;font-size:18px;letter-spacing:normal;line-height:1;text-align:left;color:#000000;">
|
||||
<p class="text-build-content"
|
||||
style="line-height: 24px; margin: 10px 0; margin-top: 20px; margin-bottom: 20px;"
|
||||
data-testid="4XoHRGw1Y">
|
||||
<span style="color:#000000;font-family:Arial, Helvetica, sans-serif;font-size:18px;">
|
||||
And here's some of the biggest changes in your portfolio:
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<tr>
|
||||
<td
|
||||
style="font-size:0; padding-left:10px;padding-top:10px;padding-bottom:0;word-break:break-word;">
|
||||
<table role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class='question'>
|
||||
<a class='question' href='{{question1Url}}'>
|
||||
{{question1Title}}
|
||||
<!-- Will the US economy recover from the pandemic?-->
|
||||
</a>
|
||||
</td>
|
||||
<td class='probs'>
|
||||
<p class='prob'>
|
||||
{{question1Prob}}
|
||||
<!-- 9.9%-->
|
||||
<p class='change' style='{{question1ChangeStyle}}'>
|
||||
{{question1Change}}
|
||||
<!-- +17%-->
|
||||
</p>
|
||||
</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td class='question'>
|
||||
<a class='question' href='{{question2Url}}'>
|
||||
{{question2Title}}
|
||||
<!-- Will the US economy recover from the pandemic? blah blah blah-->
|
||||
</a>
|
||||
</td>
|
||||
<td class='probs'>
|
||||
<p class='prob'>
|
||||
{{question2Prob}}
|
||||
<!-- 99.9%-->
|
||||
<p class='change' style='{{question2ChangeStyle}}'>
|
||||
{{question2Change}}
|
||||
<!-- +7%-->
|
||||
</p>
|
||||
</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<!-- <td style="{{investment_value_style}}">-->
|
||||
<td class='question'>
|
||||
<a class='question' href='{{question3Url}}'>
|
||||
{{question3Title}}
|
||||
<!-- Will the US economy recover from the pandemic?-->
|
||||
</a>
|
||||
</td>
|
||||
<td class='probs'>
|
||||
<p class='prob'>
|
||||
{{question3Prob}}
|
||||
<!-- 99.9%-->
|
||||
<p class='change' style='{{question3ChangeStyle}}'>
|
||||
{{question3Change}}
|
||||
<!-- +17%-->
|
||||
</p>
|
||||
</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<!-- <td style="{{investment_value_style}}">-->
|
||||
<td class='question'>
|
||||
<a class='question' href='{{question4Url}}'>
|
||||
{{question4Title}}
|
||||
<!-- Will the US economy recover from the pandemic?-->
|
||||
</a>
|
||||
</td>
|
||||
<td class='probs'>
|
||||
<p class='prob'>
|
||||
{{question4Prob}}
|
||||
<!-- 99.9%-->
|
||||
<p class='change' style='{{question4ChangeStyle}}'>
|
||||
{{question4Change}}
|
||||
<!-- +17%-->
|
||||
</p>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 0 0 20px 0;
|
||||
text-align: center;
|
||||
">
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin: 0px auto; max-width: 600px">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="
|
||||
direction: ltr;
|
||||
font-size: 0px;
|
||||
padding: 20px 0px 20px 0px;
|
||||
text-align: center;
|
||||
">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="
|
||||
font-size: 0px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
||||
width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding: 0">
|
||||
<table border="0" cellpadding="0" cellspacing="0"
|
||||
role="presentation" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
">
|
||||
<div style="
|
||||
font-family: Ubuntu, Helvetica, Arial,
|
||||
sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
">
|
||||
<p style="margin: 10px 0">
|
||||
This e-mail has been sent to
|
||||
{{name}},
|
||||
<a href="{{unsubscribeUrl}}" style="
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
" target="_blank">click here to unsubscribe from this type of notification</a>.
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="
|
||||
font-size: 0px;
|
||||
padding: 10px 25px;
|
||||
word-break: break-word;
|
||||
"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -12,14 +12,15 @@ import { getValueFromBucket } from '../../common/calculate-dpm'
|
|||
import { formatNumericProbability } from '../../common/pseudo-numeric'
|
||||
|
||||
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
||||
import { getUser } from './utils'
|
||||
import { contractUrl, getUser } from './utils'
|
||||
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
|
||||
import { notification_reason_types } from '../../common/notification'
|
||||
import { Dictionary } from 'lodash'
|
||||
import { getNotificationDestinationsForUser } from '../../common/user-notification-preferences'
|
||||
import {
|
||||
getNotificationDestinationsForUser,
|
||||
notification_preference,
|
||||
} from '../../common/user-notification-preferences'
|
||||
PerContractInvestmentsData,
|
||||
OverallPerformanceData,
|
||||
} from 'functions/src/weekly-portfolio-emails'
|
||||
|
||||
export const sendMarketResolutionEmail = async (
|
||||
reason: notification_reason_types,
|
||||
|
@ -152,9 +153,10 @@ export const sendWelcomeEmail = async (
|
|||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
|
||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
||||
'onboarding_flow' as notification_preference
|
||||
}`
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'onboarding_flow'
|
||||
)
|
||||
|
||||
return await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
|
@ -220,9 +222,11 @@ export const sendOneWeekBonusEmail = async (
|
|||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
|
||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
||||
'onboarding_flow' as notification_preference
|
||||
}`
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'onboarding_flow'
|
||||
)
|
||||
|
||||
return await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
'Manifold Markets one week anniversary gift',
|
||||
|
@ -252,10 +256,10 @@ export const sendCreatorGuideEmail = async (
|
|||
|
||||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
|
||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
||||
'onboarding_flow' as notification_preference
|
||||
}`
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'onboarding_flow'
|
||||
)
|
||||
return await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
'Create your own prediction market',
|
||||
|
@ -286,10 +290,10 @@ export const sendThankYouEmail = async (
|
|||
|
||||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
|
||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
||||
'thank_you_for_purchases' as notification_preference
|
||||
}`
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'thank_you_for_purchases'
|
||||
)
|
||||
|
||||
return await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
|
@ -469,9 +473,10 @@ export const sendInterestingMarketsEmail = async (
|
|||
)
|
||||
return
|
||||
|
||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
||||
'trending_markets' as notification_preference
|
||||
}`
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'trending_markets'
|
||||
)
|
||||
|
||||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
|
@ -507,10 +512,6 @@ export const sendInterestingMarketsEmail = async (
|
|||
)
|
||||
}
|
||||
|
||||
function contractUrl(contract: Contract) {
|
||||
return `https://manifold.markets/${contract.creatorUsername}/${contract.slug}`
|
||||
}
|
||||
|
||||
function imageSourceUrl(contract: Contract) {
|
||||
return buildCardUrl(getOpenGraphProps(contract))
|
||||
}
|
||||
|
@ -612,3 +613,47 @@ export const sendNewUniqueBettorsEmail = async (
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const sendWeeklyPortfolioUpdateEmail = async (
|
||||
user: User,
|
||||
privateUser: PrivateUser,
|
||||
investments: PerContractInvestmentsData[],
|
||||
overallPerformance: OverallPerformanceData
|
||||
) => {
|
||||
if (
|
||||
!privateUser ||
|
||||
!privateUser.email ||
|
||||
!privateUser.notificationPreferences.profit_loss_updates.includes('email')
|
||||
)
|
||||
return
|
||||
|
||||
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||
privateUser,
|
||||
'profit_loss_updates'
|
||||
)
|
||||
|
||||
const { name } = user
|
||||
const firstName = name.split(' ')[0]
|
||||
const templateData: Record<string, string> = {
|
||||
name: firstName,
|
||||
unsubscribeUrl,
|
||||
...overallPerformance,
|
||||
}
|
||||
investments.forEach((investment, i) => {
|
||||
templateData[`question${i + 1}Title`] = investment.questionTitle
|
||||
templateData[`question${i + 1}Url`] = investment.questionUrl
|
||||
templateData[`question${i + 1}Prob`] = investment.questionProb
|
||||
templateData[`question${i + 1}Change`] = investment.questionChange
|
||||
templateData[`question${i + 1}ChangeStyle`] = investment.questionChangeStyle
|
||||
})
|
||||
|
||||
await sendTemplateEmail(
|
||||
privateUser.email,
|
||||
// 'iansphilips@gmail.com',
|
||||
`Here's your weekly portfolio update!`,
|
||||
investments.length === 0
|
||||
? 'portfolio-update-no-movers'
|
||||
: 'portfolio-update',
|
||||
templateData
|
||||
)
|
||||
}
|
||||
|
|
|
@ -27,9 +27,10 @@ export * from './on-delete-group'
|
|||
export * from './score-contracts'
|
||||
export * from './weekly-markets-emails'
|
||||
export * from './reset-betting-streaks'
|
||||
export * from './reset-weekly-emails-flag'
|
||||
export * from './reset-weekly-emails-flags'
|
||||
export * from './on-update-contract-follow'
|
||||
export * from './on-update-like'
|
||||
export * from './weekly-portfolio-emails'
|
||||
|
||||
// v2
|
||||
export * from './health'
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'
|
|||
import * as admin from 'firebase-admin'
|
||||
import { getAllPrivateUsers } from './utils'
|
||||
|
||||
export const resetWeeklyEmailsFlag = functions
|
||||
export const resetWeeklyEmailsFlags = functions
|
||||
.runWith({
|
||||
timeoutSeconds: 300,
|
||||
memory: '4GB',
|
||||
|
@ -17,6 +17,7 @@ export const resetWeeklyEmailsFlag = functions
|
|||
privateUsers.map(async (user) => {
|
||||
return firestore.collection('private-users').doc(user.id).update({
|
||||
weeklyTrendingEmailSent: false,
|
||||
weeklyPortfolioUpdateEmailSent: false,
|
||||
})
|
||||
})
|
||||
)
|
|
@ -5,6 +5,7 @@ import { Bet } from '../../common/bet'
|
|||
import { Contract } from '../../common/contract'
|
||||
import { log } from './utils'
|
||||
import { removeUndefinedProps } from '../../common/util/object'
|
||||
import { DAY_MS, HOUR_MS } from '../../common/util/time'
|
||||
|
||||
export const scoreContracts = functions
|
||||
.runWith({ memory: '4GB', timeoutSeconds: 540 })
|
||||
|
@ -16,11 +17,12 @@ const firestore = admin.firestore()
|
|||
|
||||
async function scoreContractsInternal() {
|
||||
const now = Date.now()
|
||||
const lastHour = now - 60 * 60 * 1000
|
||||
const last3Days = now - 1000 * 60 * 60 * 24 * 3
|
||||
const hourAgo = now - HOUR_MS
|
||||
const dayAgo = now - DAY_MS
|
||||
const threeDaysAgo = now - DAY_MS * 3
|
||||
const activeContractsSnap = await firestore
|
||||
.collection('contracts')
|
||||
.where('lastUpdatedTime', '>', lastHour)
|
||||
.where('lastUpdatedTime', '>', hourAgo)
|
||||
.get()
|
||||
const activeContracts = activeContractsSnap.docs.map(
|
||||
(doc) => doc.data() as Contract
|
||||
|
@ -41,15 +43,21 @@ async function scoreContractsInternal() {
|
|||
for (const contract of contracts) {
|
||||
const bets = await firestore
|
||||
.collection(`contracts/${contract.id}/bets`)
|
||||
.where('createdTime', '>', last3Days)
|
||||
.where('createdTime', '>', threeDaysAgo)
|
||||
.get()
|
||||
const bettors = bets.docs
|
||||
.map((doc) => doc.data() as Bet)
|
||||
.map((bet) => bet.userId)
|
||||
const popularityScore = uniq(bettors).length
|
||||
|
||||
const wasCreatedToday = contract.createdTime > dayAgo
|
||||
|
||||
let dailyScore: number | undefined
|
||||
if (contract.outcomeType === 'BINARY' && contract.mechanism === 'cpmm-1') {
|
||||
if (
|
||||
contract.outcomeType === 'BINARY' &&
|
||||
contract.mechanism === 'cpmm-1' &&
|
||||
!wasCreatedToday
|
||||
) {
|
||||
const percentChange = Math.abs(contract.probChanges.day)
|
||||
dailyScore = popularityScore * percentChange
|
||||
}
|
||||
|
|
115
functions/src/scripts/contest/create-markets.ts
Normal file
115
functions/src/scripts/contest/create-markets.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Run with `npx ts-node src/scripts/contest/create-markets.ts`
|
||||
|
||||
import { data } from './criticism-and-red-teaming'
|
||||
|
||||
// Dev API key for Cause Exploration Prizes (@CEP)
|
||||
// const API_KEY = '188f014c-0ba2-4c35-9e6d-88252e281dbf'
|
||||
// DEV API key for Criticism and Red Teaming (@CARTBot)
|
||||
const API_KEY = '6ff1f78a-32fe-43b2-b31b-9e3c78c5f18c'
|
||||
|
||||
type CEPSubmission = {
|
||||
title: string
|
||||
author?: string
|
||||
link: string
|
||||
}
|
||||
|
||||
// Use the API to create a new market for this Cause Exploration Prize submission
|
||||
async function postMarket(submission: CEPSubmission) {
|
||||
const { title, author } = submission
|
||||
const response = await fetch('https://dev.manifold.markets/api/v0/market', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Key ${API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
outcomeType: 'BINARY',
|
||||
question: `"${title}" by ${author ?? 'anonymous'}`,
|
||||
description: makeDescription(submission),
|
||||
closeTime: Date.parse('2022-09-30').valueOf(),
|
||||
initialProb: 10,
|
||||
// Super secret options:
|
||||
// groupId: 'y2hcaGybXT1UfobK3XTx', // [DEV] CEP Tournament
|
||||
// groupId: 'cMcpBQ2p452jEcJD2SFw', // [PROD] Predict CEP
|
||||
groupId: 'h3MhjYbSSG6HbxY8ZTwE', // [DEV] CART
|
||||
// groupId: 'K86LmEmidMKdyCHdHNv4', // [PROD] CART
|
||||
visibility: 'unlisted',
|
||||
// TODO: Increase liquidity?
|
||||
}),
|
||||
})
|
||||
const data = await response.json()
|
||||
console.log('Created market:', data.slug)
|
||||
}
|
||||
|
||||
async function postAll() {
|
||||
for (const submission of data.slice(0, 3)) {
|
||||
await postMarket(submission)
|
||||
}
|
||||
}
|
||||
postAll()
|
||||
|
||||
/* Example curl request:
|
||||
$ curl https://manifold.markets/api/v0/market -X POST -H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Key {...}'
|
||||
--data-raw '{"outcomeType":"BINARY", \
|
||||
"question":"Is there life on Mars?", \
|
||||
"description":"I'm not going to type some long ass example description.", \
|
||||
"closeTime":1700000000000, \
|
||||
"initialProb":25}'
|
||||
*/
|
||||
|
||||
function makeDescription(submission: CEPSubmission) {
|
||||
const { title, author, link } = submission
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
content: [
|
||||
{ text: `Will ${author ?? 'anonymous'}'s post "`, type: 'text' },
|
||||
{
|
||||
marks: [
|
||||
{
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
href: link,
|
||||
class:
|
||||
'no-underline !text-indigo-700 z-10 break-words hover:underline hover:decoration-indigo-400 hover:decoration-2',
|
||||
},
|
||||
type: 'link',
|
||||
},
|
||||
],
|
||||
type: 'text',
|
||||
text: title,
|
||||
},
|
||||
{ text: '" win any prize in the ', type: 'text' },
|
||||
{
|
||||
text: 'EA Criticism and Red Teaming Contest',
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
class:
|
||||
'no-underline !text-indigo-700 z-10 break-words hover:underline hover:decoration-indigo-400 hover:decoration-2',
|
||||
href: 'https://forum.effectivealtruism.org/posts/8hvmvrgcxJJ2pYR4X/announcing-a-contest-ea-criticism-and-red-teaming',
|
||||
},
|
||||
type: 'link',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ text: '?', type: 'text' },
|
||||
],
|
||||
type: 'paragraph',
|
||||
},
|
||||
{ type: 'paragraph' },
|
||||
{
|
||||
type: 'iframe',
|
||||
attrs: {
|
||||
allowfullscreen: true,
|
||||
src: link,
|
||||
frameborder: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'doc',
|
||||
}
|
||||
}
|
1219
functions/src/scripts/contest/criticism-and-red-teaming.ts
Normal file
1219
functions/src/scripts/contest/criticism-and-red-teaming.ts
Normal file
File diff suppressed because it is too large
Load Diff
55
functions/src/scripts/contest/scrape-ea.ts
Normal file
55
functions/src/scripts/contest/scrape-ea.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
// Run with `npx ts-node src/scripts/contest/scrape-ea.ts`
|
||||
import * as fs from 'fs'
|
||||
import * as puppeteer from 'puppeteer'
|
||||
|
||||
export function scrapeEA(contestLink: string, fileName: string) {
|
||||
;(async () => {
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await page.goto(contestLink)
|
||||
|
||||
let loadMoreButton = await page.$('.LoadMore-root')
|
||||
|
||||
while (loadMoreButton) {
|
||||
await loadMoreButton.click()
|
||||
await page.waitForNetworkIdle()
|
||||
loadMoreButton = await page.$('.LoadMore-root')
|
||||
}
|
||||
|
||||
/* Run javascript inside the page */
|
||||
const data = await page.evaluate(() => {
|
||||
const list = []
|
||||
const items = document.querySelectorAll('.PostsItem2-root')
|
||||
|
||||
for (const item of items) {
|
||||
const link =
|
||||
'https://forum.effectivealtruism.org' +
|
||||
item?.querySelector('a')?.getAttribute('href')
|
||||
|
||||
// Replace '&' with '&'
|
||||
const clean = (str: string | undefined) => str?.replace(/&/g, '&')
|
||||
|
||||
list.push({
|
||||
title: clean(item?.querySelector('a>span>span')?.innerHTML),
|
||||
author: item?.querySelector('a.UsersNameDisplay-userName')?.innerHTML,
|
||||
link: link,
|
||||
})
|
||||
}
|
||||
|
||||
return list
|
||||
})
|
||||
|
||||
fs.writeFileSync(
|
||||
`./src/scripts/contest/${fileName}.ts`,
|
||||
`export const data = ${JSON.stringify(data, null, 2)}`
|
||||
)
|
||||
|
||||
console.log(data)
|
||||
await browser.close()
|
||||
})()
|
||||
}
|
||||
|
||||
scrapeEA(
|
||||
'https://forum.effectivealtruism.org/topics/criticism-and-red-teaming-contest',
|
||||
'criticism-and-red-teaming'
|
||||
)
|
|
@ -41,6 +41,7 @@ const createGroup = async (
|
|||
anyoneCanJoin: true,
|
||||
totalContracts: contracts.length,
|
||||
totalMembers: 1,
|
||||
postIds: [],
|
||||
}
|
||||
await groupRef.create(group)
|
||||
// create a GroupMemberDoc for the creator
|
||||
|
|
|
@ -28,6 +28,7 @@ import { stripewebhook, createcheckoutsession } from './stripe'
|
|||
import { getcurrentuser } from './get-current-user'
|
||||
import { createpost } from './create-post'
|
||||
import { savetwitchcredentials } from './save-twitch-credentials'
|
||||
import { testscheduledfunction } from './test-scheduled-function'
|
||||
|
||||
type Middleware = (req: Request, res: Response, next: NextFunction) => void
|
||||
const app = express()
|
||||
|
@ -69,6 +70,7 @@ addJsonEndpointRoute('/getcurrentuser', getcurrentuser)
|
|||
addJsonEndpointRoute('/savetwitchcredentials', savetwitchcredentials)
|
||||
addEndpointRoute('/stripewebhook', stripewebhook, express.raw())
|
||||
addEndpointRoute('/createpost', createpost)
|
||||
addEndpointRoute('/testscheduledfunction', testscheduledfunction)
|
||||
|
||||
app.listen(PORT)
|
||||
console.log(`Serving functions on port ${PORT}.`)
|
||||
|
|
17
functions/src/test-scheduled-function.ts
Normal file
17
functions/src/test-scheduled-function.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { APIError, newEndpoint } from './api'
|
||||
import { sendPortfolioUpdateEmailsToAllUsers } from './weekly-portfolio-emails'
|
||||
import { isProd } from './utils'
|
||||
|
||||
// Function for testing scheduled functions locally
|
||||
export const testscheduledfunction = newEndpoint(
|
||||
{ method: 'GET', memory: '4GiB' },
|
||||
async (_req) => {
|
||||
if (isProd())
|
||||
throw new APIError(400, 'This function is only available in dev mode')
|
||||
|
||||
// Replace your function here
|
||||
await sendPortfolioUpdateEmailsToAllUsers()
|
||||
|
||||
return { success: true }
|
||||
}
|
||||
)
|
|
@ -170,3 +170,7 @@ export const chargeUser = (
|
|||
export const getContractPath = (contract: Contract) => {
|
||||
return `/${contract.creatorUsername}/${contract.slug}`
|
||||
}
|
||||
|
||||
export function contractUrl(contract: Contract) {
|
||||
return `https://manifold.markets/${contract.creatorUsername}/${contract.slug}`
|
||||
}
|
||||
|
|
|
@ -46,12 +46,14 @@ async function sendTrendingMarketsEmailsToAllUsers() {
|
|||
? await getAllPrivateUsers()
|
||||
: filterDefined([await getPrivateUser('6hHpzvRG0pMq8PNJs7RZj2qlZGn2')])
|
||||
// get all users that haven't unsubscribed from weekly emails
|
||||
const privateUsersToSendEmailsTo = privateUsers.filter((user) => {
|
||||
return (
|
||||
user.notificationPreferences.trending_markets.includes('email') &&
|
||||
!user.weeklyTrendingEmailSent
|
||||
)
|
||||
})
|
||||
const privateUsersToSendEmailsTo = privateUsers
|
||||
.filter((user) => {
|
||||
return (
|
||||
user.notificationPreferences.trending_markets.includes('email') &&
|
||||
!user.weeklyTrendingEmailSent
|
||||
)
|
||||
})
|
||||
.slice(150) // Send the emails out in batches
|
||||
log(
|
||||
'Sending weekly trending emails to',
|
||||
privateUsersToSendEmailsTo.length,
|
||||
|
@ -74,6 +76,7 @@ async function sendTrendingMarketsEmailsToAllUsers() {
|
|||
trendingContracts.map((c) => c.question).join('\n ')
|
||||
)
|
||||
|
||||
// TODO: convert to Promise.all
|
||||
for (const privateUser of privateUsersToSendEmailsTo) {
|
||||
if (!privateUser.email) {
|
||||
log(`No email for ${privateUser.username}`)
|
||||
|
@ -84,6 +87,9 @@ async function sendTrendingMarketsEmailsToAllUsers() {
|
|||
})
|
||||
if (contractsAvailableToSend.length < numContractsToSend) {
|
||||
log('not enough new, unbet-on contracts to send to user', privateUser.id)
|
||||
await firestore.collection('private-users').doc(privateUser.id).update({
|
||||
weeklyTrendingEmailSent: true,
|
||||
})
|
||||
continue
|
||||
}
|
||||
// choose random subset of contracts to send to user
|
||||
|
|
280
functions/src/weekly-portfolio-emails.ts
Normal file
280
functions/src/weekly-portfolio-emails.ts
Normal file
|
@ -0,0 +1,280 @@
|
|||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
|
||||
import { Contract, CPMMContract } from '../../common/contract'
|
||||
import {
|
||||
getAllPrivateUsers,
|
||||
getPrivateUser,
|
||||
getUser,
|
||||
getValue,
|
||||
getValues,
|
||||
isProd,
|
||||
log,
|
||||
} from './utils'
|
||||
import { filterDefined } from '../../common/util/array'
|
||||
import { DAY_MS } from '../../common/util/time'
|
||||
import { partition, sortBy, sum, uniq } from 'lodash'
|
||||
import { Bet } from '../../common/bet'
|
||||
import { computeInvestmentValueCustomProb } from '../../common/calculate-metrics'
|
||||
import { sendWeeklyPortfolioUpdateEmail } from './emails'
|
||||
import { contractUrl } from './utils'
|
||||
import { Txn } from '../../common/txn'
|
||||
import { formatMoney } from '../../common/util/format'
|
||||
|
||||
// TODO: reset weeklyPortfolioUpdateEmailSent to false for all users at the start of each week
|
||||
export const weeklyPortfolioUpdateEmails = functions
|
||||
.runWith({ secrets: ['MAILGUN_KEY'], memory: '4GB' })
|
||||
// every minute on Friday for an hour at 12pm PT (UTC -07:00)
|
||||
.pubsub.schedule('* 19 * * 5')
|
||||
.timeZone('Etc/UTC')
|
||||
.onRun(async () => {
|
||||
await sendPortfolioUpdateEmailsToAllUsers()
|
||||
})
|
||||
|
||||
const firestore = admin.firestore()
|
||||
|
||||
export async function sendPortfolioUpdateEmailsToAllUsers() {
|
||||
const privateUsers = isProd()
|
||||
? // ian & stephen's ids
|
||||
// ? filterDefined([
|
||||
// await getPrivateUser('AJwLWoo3xue32XIiAVrL5SyR1WB2'),
|
||||
// await getPrivateUser('tlmGNz9kjXc2EteizMORes4qvWl2'),
|
||||
// ])
|
||||
await getAllPrivateUsers()
|
||||
: filterDefined([await getPrivateUser('6hHpzvRG0pMq8PNJs7RZj2qlZGn2')])
|
||||
// get all users that haven't unsubscribed from weekly emails
|
||||
const privateUsersToSendEmailsTo = privateUsers
|
||||
.filter((user) => {
|
||||
return isProd()
|
||||
? user.notificationPreferences.profit_loss_updates.includes('email') &&
|
||||
!user.weeklyPortfolioUpdateEmailSent
|
||||
: true
|
||||
})
|
||||
// Send emails in batches
|
||||
.slice(0, 200)
|
||||
log(
|
||||
'Sending weekly portfolio emails to',
|
||||
privateUsersToSendEmailsTo.length,
|
||||
'users'
|
||||
)
|
||||
|
||||
const usersBets: { [userId: string]: Bet[] } = {}
|
||||
// get all bets made by each user
|
||||
await Promise.all(
|
||||
privateUsersToSendEmailsTo.map(async (user) => {
|
||||
return getValues<Bet>(
|
||||
firestore.collectionGroup('bets').where('userId', '==', user.id)
|
||||
).then((bets) => {
|
||||
usersBets[user.id] = bets
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const usersToContractsCreated: { [userId: string]: Contract[] } = {}
|
||||
// Get all contracts created by each user
|
||||
await Promise.all(
|
||||
privateUsersToSendEmailsTo.map(async (user) => {
|
||||
return getValues<Contract>(
|
||||
firestore
|
||||
.collection('contracts')
|
||||
.where('creatorId', '==', user.id)
|
||||
.where('createdTime', '>', Date.now() - 7 * DAY_MS)
|
||||
).then((contracts) => {
|
||||
usersToContractsCreated[user.id] = contracts
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
// Get all txns the users received over the past week
|
||||
const usersToTxnsReceived: { [userId: string]: Txn[] } = {}
|
||||
await Promise.all(
|
||||
privateUsersToSendEmailsTo.map(async (user) => {
|
||||
return getValues<Txn>(
|
||||
firestore
|
||||
.collection(`txns`)
|
||||
.where('toId', '==', user.id)
|
||||
.where('createdTime', '>', Date.now() - 7 * DAY_MS)
|
||||
).then((txn) => {
|
||||
usersToTxnsReceived[user.id] = txn
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
// Get a flat map of all the bets that users made to get the contracts they bet on
|
||||
const contractsUsersBetOn = filterDefined(
|
||||
await Promise.all(
|
||||
uniq(
|
||||
Object.values(usersBets).flatMap((bets) =>
|
||||
bets.map((bet) => bet.contractId)
|
||||
)
|
||||
).map((contractId) =>
|
||||
getValue<Contract>(firestore.collection('contracts').doc(contractId))
|
||||
)
|
||||
)
|
||||
)
|
||||
log('Found', contractsUsersBetOn.length, 'contracts')
|
||||
let count = 0
|
||||
await Promise.all(
|
||||
privateUsersToSendEmailsTo.map(async (privateUser) => {
|
||||
const user = await getUser(privateUser.id)
|
||||
if (!user) return
|
||||
const userBets = usersBets[privateUser.id] as Bet[]
|
||||
const contractsUserBetOn = contractsUsersBetOn.filter((contract) =>
|
||||
userBets.some((bet) => bet.contractId === contract.id)
|
||||
)
|
||||
const contractsBetOnInLastWeek = uniq(
|
||||
userBets
|
||||
.filter((bet) => bet.createdTime > Date.now() - 7 * DAY_MS)
|
||||
.map((bet) => bet.contractId)
|
||||
)
|
||||
const totalTips = sum(
|
||||
usersToTxnsReceived[privateUser.id]
|
||||
.filter((txn) => txn.category === 'TIP')
|
||||
.map((txn) => txn.amount)
|
||||
)
|
||||
const greenBg = 'rgba(0,160,0,0.2)'
|
||||
const redBg = 'rgba(160,0,0,0.2)'
|
||||
const clearBg = 'rgba(255,255,255,0)'
|
||||
const roundedProfit =
|
||||
Math.round(user.profitCached.weekly) === 0
|
||||
? 0
|
||||
: Math.floor(user.profitCached.weekly)
|
||||
const performanceData = {
|
||||
profit: formatMoney(user.profitCached.weekly),
|
||||
profit_style: `background-color: ${
|
||||
roundedProfit > 0 ? greenBg : roundedProfit === 0 ? clearBg : redBg
|
||||
}`,
|
||||
markets_created:
|
||||
usersToContractsCreated[privateUser.id].length.toString(),
|
||||
tips_received: formatMoney(totalTips),
|
||||
unique_bettors: usersToTxnsReceived[privateUser.id]
|
||||
.filter((txn) => txn.category === 'UNIQUE_BETTOR_BONUS')
|
||||
.length.toString(),
|
||||
markets_traded: contractsBetOnInLastWeek.length.toString(),
|
||||
prediction_streak:
|
||||
(user.currentBettingStreak?.toString() ?? '0') + ' days',
|
||||
// More options: bonuses, tips given,
|
||||
} as OverallPerformanceData
|
||||
|
||||
const investmentValueDifferences = sortBy(
|
||||
filterDefined(
|
||||
contractsUserBetOn.map((contract) => {
|
||||
const cpmmContract = contract as CPMMContract
|
||||
if (cpmmContract === undefined || cpmmContract.prob === undefined)
|
||||
return
|
||||
const bets = userBets.filter(
|
||||
(bet) => bet.contractId === contract.id
|
||||
)
|
||||
|
||||
const marketProbabilityAWeekAgo =
|
||||
cpmmContract.prob - cpmmContract.probChanges.week
|
||||
const currentMarketProbability = cpmmContract.resolutionProbability
|
||||
? cpmmContract.resolutionProbability
|
||||
: cpmmContract.prob
|
||||
const betsValueAWeekAgo = computeInvestmentValueCustomProb(
|
||||
bets.filter((b) => b.createdTime < Date.now() - 7 * DAY_MS),
|
||||
contract,
|
||||
marketProbabilityAWeekAgo
|
||||
)
|
||||
const currentBetsValue = computeInvestmentValueCustomProb(
|
||||
bets,
|
||||
contract,
|
||||
currentMarketProbability
|
||||
)
|
||||
const marketChange =
|
||||
currentMarketProbability - marketProbabilityAWeekAgo
|
||||
return {
|
||||
currentValue: currentBetsValue,
|
||||
pastValue: betsValueAWeekAgo,
|
||||
difference: currentBetsValue - betsValueAWeekAgo,
|
||||
contractSlug: contract.slug,
|
||||
marketProbAWeekAgo: marketProbabilityAWeekAgo,
|
||||
questionTitle: contract.question,
|
||||
questionUrl: contractUrl(contract),
|
||||
questionProb: cpmmContract.resolution
|
||||
? cpmmContract.resolution
|
||||
: Math.round(cpmmContract.prob * 100) + '%',
|
||||
questionChange:
|
||||
(marketChange > 0 ? '+' : '') +
|
||||
Math.round(marketChange * 100) +
|
||||
'%',
|
||||
questionChangeStyle: `color: ${
|
||||
currentMarketProbability > marketProbabilityAWeekAgo
|
||||
? 'rgba(0,160,0,1)'
|
||||
: '#a80000'
|
||||
};`,
|
||||
} as PerContractInvestmentsData
|
||||
})
|
||||
),
|
||||
(differences) => Math.abs(differences.difference)
|
||||
).reverse()
|
||||
|
||||
log(
|
||||
'Found',
|
||||
investmentValueDifferences.length,
|
||||
'investment differences for user',
|
||||
privateUser.id
|
||||
)
|
||||
|
||||
const [winningInvestments, losingInvestments] = partition(
|
||||
investmentValueDifferences.filter(
|
||||
(diff) =>
|
||||
diff.pastValue > 0.01 &&
|
||||
Math.abs(diff.difference / diff.pastValue) > 0.01 // difference is greater than 1%
|
||||
),
|
||||
(investmentsData: PerContractInvestmentsData) => {
|
||||
return investmentsData.difference > 0
|
||||
}
|
||||
)
|
||||
// pick 3 winning investments and 3 losing investments
|
||||
const topInvestments = winningInvestments.slice(0, 2)
|
||||
const worstInvestments = losingInvestments.slice(0, 2)
|
||||
// if no bets in the last week ANd no market movers AND no markets created, don't send email
|
||||
if (
|
||||
contractsBetOnInLastWeek.length === 0 &&
|
||||
topInvestments.length === 0 &&
|
||||
worstInvestments.length === 0 &&
|
||||
usersToContractsCreated[privateUser.id].length === 0
|
||||
) {
|
||||
log('No bets in last week, no market movers, no markets created')
|
||||
await firestore.collection('private-users').doc(privateUser.id).update({
|
||||
weeklyPortfolioUpdateEmailSent: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
await sendWeeklyPortfolioUpdateEmail(
|
||||
user,
|
||||
privateUser,
|
||||
topInvestments.concat(worstInvestments) as PerContractInvestmentsData[],
|
||||
performanceData
|
||||
)
|
||||
await firestore.collection('private-users').doc(privateUser.id).update({
|
||||
weeklyPortfolioUpdateEmailSent: true,
|
||||
})
|
||||
log('Sent weekly portfolio update email to', privateUser.email)
|
||||
count++
|
||||
log('sent out emails to user count:', count)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export type PerContractInvestmentsData = {
|
||||
questionTitle: string
|
||||
questionUrl: string
|
||||
questionProb: string
|
||||
questionChange: string
|
||||
questionChangeStyle: string
|
||||
currentValue: number
|
||||
pastValue: number
|
||||
difference: number
|
||||
}
|
||||
|
||||
export type OverallPerformanceData = {
|
||||
profit: string
|
||||
prediction_streak: string
|
||||
markets_traded: string
|
||||
profit_style: string
|
||||
tips_received: string
|
||||
markets_created: string
|
||||
unique_bettors: string
|
||||
}
|
|
@ -5,7 +5,7 @@ import { formatMoney } from 'common/util/format'
|
|||
import { Col } from './layout/col'
|
||||
import { SiteLink } from './site-link'
|
||||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
|
||||
export function AmountInput(props: {
|
||||
amount: number | undefined
|
||||
|
@ -34,8 +34,7 @@ export function AmountInput(props: {
|
|||
const isInvalid = !str || isNaN(amount)
|
||||
onChange(isInvalid ? undefined : amount)
|
||||
}
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = (width ?? 0) < 768
|
||||
const isMobile = useIsMobile(768)
|
||||
return (
|
||||
<Col className={className}>
|
||||
<label className="input-group mb-4">
|
||||
|
|
|
@ -38,13 +38,26 @@ export function AnswersPanel(props: {
|
|||
const answers = (useAnswers(contract.id) ?? contract.answers).filter(
|
||||
(a) => a.number != 0 || contract.outcomeType === 'MULTIPLE_CHOICE'
|
||||
)
|
||||
const [winningAnswers, notWinningAnswers] = partition(
|
||||
answers,
|
||||
(a) => a.id === resolution || (resolutions && resolutions[a.id])
|
||||
const hasZeroBetAnswers = answers.some((answer) => totalBets[answer.id] < 1)
|
||||
|
||||
const [winningAnswers, losingAnswers] = partition(
|
||||
answers.filter((a) => (showAllAnswers ? true : totalBets[a.id] > 0)),
|
||||
(answer) =>
|
||||
answer.id === resolution || (resolutions && resolutions[answer.id])
|
||||
)
|
||||
const [visibleAnswers, invisibleAnswers] = partition(
|
||||
sortBy(notWinningAnswers, (a) => -getOutcomeProbability(contract, a.id)),
|
||||
(a) => showAllAnswers || totalBets[a.id] > 0
|
||||
const sortedAnswers = [
|
||||
...sortBy(winningAnswers, (answer) =>
|
||||
resolutions ? -1 * resolutions[answer.id] : 0
|
||||
),
|
||||
...sortBy(
|
||||
resolution ? [] : losingAnswers,
|
||||
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
|
||||
),
|
||||
]
|
||||
|
||||
const answerItems = sortBy(
|
||||
losingAnswers.length > 0 ? losingAnswers : sortedAnswers,
|
||||
(answer) => -getOutcomeProbability(contract, answer.id)
|
||||
)
|
||||
|
||||
const user = useUser()
|
||||
|
@ -94,13 +107,13 @@ export function AnswersPanel(props: {
|
|||
return (
|
||||
<Col className="gap-3">
|
||||
{(resolveOption || resolution) &&
|
||||
sortBy(winningAnswers, (a) => -(resolutions?.[a.id] ?? 0)).map((a) => (
|
||||
sortedAnswers.map((answer) => (
|
||||
<AnswerItem
|
||||
key={a.id}
|
||||
answer={a}
|
||||
key={answer.id}
|
||||
answer={answer}
|
||||
contract={contract}
|
||||
showChoice={showChoice}
|
||||
chosenProb={chosenAnswers[a.id]}
|
||||
chosenProb={chosenAnswers[answer.id]}
|
||||
totalChosenProb={chosenTotal}
|
||||
onChoose={onChoose}
|
||||
onDeselect={onDeselect}
|
||||
|
@ -114,10 +127,10 @@ export function AnswersPanel(props: {
|
|||
tradingAllowed(contract) ? '' : '-mb-6'
|
||||
)}
|
||||
>
|
||||
{visibleAnswers.map((a) => (
|
||||
<OpenAnswer key={a.id} answer={a} contract={contract} />
|
||||
{answerItems.map((item) => (
|
||||
<OpenAnswer key={item.id} answer={item} contract={contract} />
|
||||
))}
|
||||
{invisibleAnswers.length > 0 && !showAllAnswers && (
|
||||
{hasZeroBetAnswers && !showAllAnswers && (
|
||||
<Button
|
||||
className="self-end"
|
||||
color="gray-white"
|
||||
|
@ -130,7 +143,7 @@ export function AnswersPanel(props: {
|
|||
</Col>
|
||||
)}
|
||||
|
||||
{answers.length === 0 && (
|
||||
{answers.length <= 1 && (
|
||||
<div className="pb-4 text-gray-500">No answers yet...</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -610,18 +610,24 @@ function BetRow(props: {
|
|||
const isNumeric = outcomeType === 'NUMERIC'
|
||||
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
||||
|
||||
const saleAmount = saleBet?.sale?.amount
|
||||
// calculateSaleAmount is very slow right now so that's why we memoized this
|
||||
const payout = useMemo(() => {
|
||||
const saleBetAmount = saleBet?.sale?.amount
|
||||
if (saleBetAmount) {
|
||||
return saleBetAmount
|
||||
} else if (contract.isResolved) {
|
||||
return resolvedPayout(contract, bet)
|
||||
} else {
|
||||
return calculateSaleAmount(contract, bet, unfilledBets)
|
||||
}
|
||||
}, [contract, bet, saleBet, unfilledBets])
|
||||
|
||||
const saleDisplay = isAnte ? (
|
||||
'ANTE'
|
||||
) : saleAmount !== undefined ? (
|
||||
<>{formatMoney(saleAmount)} (sold)</>
|
||||
) : saleBet ? (
|
||||
<>{formatMoney(payout)} (sold)</>
|
||||
) : (
|
||||
formatMoney(
|
||||
isResolved
|
||||
? resolvedPayout(contract, bet)
|
||||
: calculateSaleAmount(contract, bet, unfilledBets)
|
||||
)
|
||||
formatMoney(payout)
|
||||
)
|
||||
|
||||
const payoutIfChosenDisplay =
|
||||
|
|
|
@ -48,6 +48,7 @@ import { Title } from './title'
|
|||
export const SORTS = [
|
||||
{ label: 'Newest', value: 'newest' },
|
||||
{ label: 'Trending', value: 'score' },
|
||||
{ label: 'Daily trending', value: 'daily-score' },
|
||||
{ label: '24h volume', value: '24-hour-vol' },
|
||||
{ label: 'Last updated', value: 'last-updated' },
|
||||
{ label: 'Closing soon', value: 'close-date' },
|
||||
|
@ -88,6 +89,7 @@ export function ContractSearch(props: {
|
|||
hideGroupLink?: boolean
|
||||
hideQuickBet?: boolean
|
||||
noLinkAvatar?: boolean
|
||||
showProbChange?: boolean
|
||||
}
|
||||
headerClassName?: string
|
||||
persistPrefix?: string
|
||||
|
@ -128,6 +130,7 @@ export function ContractSearch(props: {
|
|||
numPages: 1,
|
||||
pages: [] as Contract[][],
|
||||
showTime: null as ShowTime | null,
|
||||
showProbChange: false,
|
||||
},
|
||||
!persistPrefix
|
||||
? undefined
|
||||
|
@ -181,8 +184,9 @@ export function ContractSearch(props: {
|
|||
const newPage = results.hits as any as Contract[]
|
||||
const showTime =
|
||||
sort === 'close-date' || sort === 'resolve-date' ? sort : null
|
||||
const showProbChange = sort === 'daily-score'
|
||||
const pages = freshQuery ? [newPage] : [...state.pages, newPage]
|
||||
setState({ numPages: results.nbPages, pages, showTime })
|
||||
setState({ numPages: results.nbPages, pages, showTime, showProbChange })
|
||||
if (freshQuery && isWholePage) window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
@ -200,6 +204,12 @@ export function ContractSearch(props: {
|
|||
}, 100)
|
||||
).current
|
||||
|
||||
const updatedCardUIOptions = useMemo(() => {
|
||||
if (cardUIOptions?.showProbChange === undefined && state.showProbChange)
|
||||
return { ...cardUIOptions, showProbChange: true }
|
||||
return cardUIOptions
|
||||
}, [cardUIOptions, state.showProbChange])
|
||||
|
||||
const contracts = state.pages
|
||||
.flat()
|
||||
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
||||
|
@ -236,7 +246,7 @@ export function ContractSearch(props: {
|
|||
showTime={state.showTime ?? undefined}
|
||||
onContractClick={onContractClick}
|
||||
highlightOptions={highlightOptions}
|
||||
cardUIOptions={cardUIOptions}
|
||||
cardUIOptions={updatedCardUIOptions}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
|
94
web/components/create-post.tsx
Normal file
94
web/components/create-post.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { useState } from 'react'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Title } from 'web/components/title'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
|
||||
import { TextEditor, useTextEditor } from 'web/components/editor'
|
||||
import { createPost } from 'web/lib/firebase/api'
|
||||
import clsx from 'clsx'
|
||||
import Router from 'next/router'
|
||||
import { MAX_POST_TITLE_LENGTH } from 'common/post'
|
||||
import { postPath } from 'web/lib/firebase/posts'
|
||||
import { Group } from 'common/group'
|
||||
|
||||
export function CreatePost(props: { group?: Group }) {
|
||||
const [title, setTitle] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const { group } = props
|
||||
|
||||
const { editor, upload } = useTextEditor({
|
||||
disabled: isSubmitting,
|
||||
})
|
||||
|
||||
const isValid = editor && title.length > 0 && editor.isEmpty === false
|
||||
|
||||
async function savePost(title: string) {
|
||||
if (!editor) return
|
||||
const newPost = {
|
||||
title: title,
|
||||
content: editor.getJSON(),
|
||||
groupId: group?.id,
|
||||
}
|
||||
|
||||
const result = await createPost(newPost).catch((e) => {
|
||||
console.log(e)
|
||||
setError('There was an error creating the post, please try again')
|
||||
return e
|
||||
})
|
||||
if (result.post) {
|
||||
await Router.push(postPath(result.post.slug))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<div className="rounded-lg px-6 py-4 sm:py-0">
|
||||
<Title className="!mt-0" text="Create a post" />
|
||||
<form>
|
||||
<div className="form-control w-full">
|
||||
<label className="label">
|
||||
<span className="mb-1">
|
||||
Title<span className={'text-red-700'}> *</span>
|
||||
</span>
|
||||
</label>
|
||||
<Textarea
|
||||
placeholder="e.g. Elon Mania Post"
|
||||
className="input input-bordered resize-none"
|
||||
autoFocus
|
||||
maxLength={MAX_POST_TITLE_LENGTH}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value || '')}
|
||||
/>
|
||||
<Spacer h={6} />
|
||||
<label className="label">
|
||||
<span className="mb-1">
|
||||
Content<span className={'text-red-700'}> *</span>
|
||||
</span>
|
||||
</label>
|
||||
<TextEditor editor={editor} upload={upload} />
|
||||
<Spacer h={6} />
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className={clsx(
|
||||
'btn btn-primary normal-case',
|
||||
isSubmitting && 'loading disabled'
|
||||
)}
|
||||
disabled={isSubmitting || !isValid || upload.isLoading}
|
||||
onClick={async () => {
|
||||
setIsSubmitting(true)
|
||||
await savePost(title)
|
||||
setIsSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? 'Creating...' : 'Create a post'}
|
||||
</button>
|
||||
{error !== '' && <div className="text-red-700">{error}</div>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -16,29 +16,26 @@ import { usePost } from 'web/hooks/use-post'
|
|||
export function GroupAboutPost(props: {
|
||||
group: Group
|
||||
isEditable: boolean
|
||||
post: Post
|
||||
post: Post | null
|
||||
}) {
|
||||
const { group, isEditable } = props
|
||||
const post = usePost(group.aboutPostId) ?? props.post
|
||||
|
||||
return (
|
||||
<div className="rounded-md bg-white p-4 ">
|
||||
{isEditable ? (
|
||||
<RichEditGroupAboutPost group={group} post={post} />
|
||||
) : (
|
||||
<Content content={post.content} />
|
||||
)}
|
||||
{isEditable && <RichEditGroupAboutPost group={group} post={post} />}
|
||||
{!isEditable && post && <Content content={post.content} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
||||
function RichEditGroupAboutPost(props: { group: Group; post: Post | null }) {
|
||||
const { group, post } = props
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const { editor, upload } = useTextEditor({
|
||||
defaultValue: post.content,
|
||||
defaultValue: post?.content,
|
||||
disabled: isSubmitting,
|
||||
})
|
||||
|
||||
|
@ -49,7 +46,7 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
|||
content: editor.getJSON(),
|
||||
}
|
||||
|
||||
if (group.aboutPostId == null) {
|
||||
if (post == null) {
|
||||
const result = await createPost(newPost).catch((e) => {
|
||||
console.error(e)
|
||||
return e
|
||||
|
@ -65,6 +62,7 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
|||
}
|
||||
|
||||
async function deleteGroupAboutPost() {
|
||||
if (post == null) return
|
||||
await deletePost(post)
|
||||
await deleteFieldFromGroup(group, 'aboutPostId')
|
||||
}
|
||||
|
@ -91,7 +89,7 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
{group.aboutPostId == null ? (
|
||||
{post == null ? (
|
||||
<div className="text-center text-gray-500">
|
||||
<p className="text-sm">
|
||||
No post has been added yet.
|
||||
|
|
|
@ -9,12 +9,10 @@ import {
|
|||
getUserBetContractsQuery,
|
||||
listAllContracts,
|
||||
trendingContractsQuery,
|
||||
getContractsQuery,
|
||||
} from 'web/lib/firebase/contracts'
|
||||
import { QueryClient, useQuery, useQueryClient } from 'react-query'
|
||||
import { MINUTE_MS } from 'common/util/time'
|
||||
import { MINUTE_MS, sleep } from 'common/util/time'
|
||||
import { query, limit } from 'firebase/firestore'
|
||||
import { Sort } from 'web/components/contract-search'
|
||||
import { dailyScoreIndex } from 'web/lib/service/algolia'
|
||||
import { CPMMBinaryContract } from 'common/contract'
|
||||
import { zipObject } from 'lodash'
|
||||
|
@ -66,19 +64,6 @@ export const useTrendingContracts = (maxContracts: number) => {
|
|||
return result.data
|
||||
}
|
||||
|
||||
export const useContractsQuery = (
|
||||
sort: Sort,
|
||||
maxContracts: number,
|
||||
filters: { groupSlug?: string } = {},
|
||||
visibility?: 'public'
|
||||
) => {
|
||||
const result = useFirestoreQueryData(
|
||||
['contracts-query', sort, maxContracts, filters],
|
||||
getContractsQuery(sort, maxContracts, filters, visibility)
|
||||
)
|
||||
return result.data
|
||||
}
|
||||
|
||||
export const useInactiveContracts = () => {
|
||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||
|
||||
|
@ -101,7 +86,7 @@ export const usePrefetchUserBetContracts = (userId: string) => {
|
|||
const queryClient = useQueryClient()
|
||||
return queryClient.prefetchQuery(
|
||||
['contracts', 'bets', userId],
|
||||
() => getUserBetContracts(userId),
|
||||
() => sleep(1000).then(() => getUserBetContracts(userId)),
|
||||
{ staleTime: 5 * MINUTE_MS }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// matches talwind sm breakpoint
|
||||
export function useIsMobile() {
|
||||
const { width } = useWindowSize()
|
||||
return (width ?? 0) < 640
|
||||
export function useIsMobile(threshold?: number) {
|
||||
const [isMobile, setIsMobile] = useState<boolean>()
|
||||
useEffect(() => {
|
||||
// 640 matches tailwind sm breakpoint
|
||||
const onResize = () => setIsMobile(window.innerWidth < (threshold ?? 640))
|
||||
onResize()
|
||||
window.addEventListener('resize', onResize)
|
||||
return () => window.removeEventListener('resize', onResize)
|
||||
}, [threshold])
|
||||
return isMobile
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useQueryClient } from 'react-query'
|
||||
import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
||||
import { DAY_MS, HOUR_MS, MINUTE_MS } from 'common/util/time'
|
||||
import { DAY_MS, HOUR_MS, MINUTE_MS, sleep } from 'common/util/time'
|
||||
import {
|
||||
getPortfolioHistory,
|
||||
getPortfolioHistoryQuery,
|
||||
|
@ -17,7 +17,7 @@ export const usePrefetchPortfolioHistory = (userId: string, period: Period) => {
|
|||
const cutoff = getCutoff(period)
|
||||
return queryClient.prefetchQuery(
|
||||
['portfolio-history', userId, cutoff],
|
||||
() => getPortfolioHistory(userId, cutoff),
|
||||
() => sleep(1000).then(() => getPortfolioHistory(userId, cutoff)),
|
||||
{ staleTime: 15 * MINUTE_MS }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,3 +11,29 @@ export const usePost = (postId: string | undefined) => {
|
|||
|
||||
return post
|
||||
}
|
||||
|
||||
export const usePosts = (postIds: string[]) => {
|
||||
const [posts, setPosts] = useState<Post[]>([])
|
||||
useEffect(() => {
|
||||
if (postIds.length === 0) return
|
||||
setPosts([])
|
||||
|
||||
const unsubscribes = postIds.map((postId) =>
|
||||
listenForPost(postId, (post) => {
|
||||
if (post) {
|
||||
setPosts((posts) => [...posts, post])
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe())
|
||||
}
|
||||
}, [postIds])
|
||||
|
||||
return posts
|
||||
.filter(
|
||||
(post, index, self) => index === self.findIndex((t) => t.id === post.id)
|
||||
)
|
||||
.sort((a, b) => b.createdTime - a.createdTime)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { usePrefetchUserBetContracts } from './use-contracts'
|
||||
import { usePrefetchPortfolioHistory } from './use-portfolio-history'
|
||||
import { usePrefetchUserBets } from './use-user-bets'
|
||||
|
||||
export function usePrefetch(userId: string | undefined) {
|
||||
|
@ -7,6 +6,5 @@ export function usePrefetch(userId: string | undefined) {
|
|||
return Promise.all([
|
||||
usePrefetchUserBets(maybeUserId),
|
||||
usePrefetchUserBetContracts(maybeUserId),
|
||||
usePrefetchPortfolioHistory(maybeUserId, 'weekly'),
|
||||
])
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export const useProbChanges = (
|
|||
const hits = uniqBy(
|
||||
[...positiveChanges.hits, ...negativeChanges.hits],
|
||||
(c) => c.id
|
||||
)
|
||||
).filter((c) => c.probChanges)
|
||||
|
||||
return sortBy(hits, (c) => Math.abs(c.probChanges.day)).reverse()
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import {
|
|||
getUserBetsQuery,
|
||||
listenForUserContractBets,
|
||||
} from 'web/lib/firebase/bets'
|
||||
import { MINUTE_MS } from 'common/util/time'
|
||||
import { MINUTE_MS, sleep } from 'common/util/time'
|
||||
|
||||
export const usePrefetchUserBets = (userId: string) => {
|
||||
const queryClient = useQueryClient()
|
||||
return queryClient.prefetchQuery(
|
||||
['bets', userId],
|
||||
() => getUserBets(userId),
|
||||
() => sleep(1000).then(() => getUserBets(userId)),
|
||||
{ staleTime: MINUTE_MS }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -90,6 +90,10 @@ export function getCurrentUser(params: any) {
|
|||
return call(getFunctionUrl('getcurrentuser'), 'GET', params)
|
||||
}
|
||||
|
||||
export function createPost(params: { title: string; content: JSONContent }) {
|
||||
export function createPost(params: {
|
||||
title: string
|
||||
content: JSONContent
|
||||
groupId?: string
|
||||
}) {
|
||||
return call(getFunctionUrl('createpost'), 'POST', params)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import { Bet } from 'common/bet'
|
|||
import { Comment } from 'common/comment'
|
||||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import { getBinaryProb } from 'common/contract-details'
|
||||
import { Sort } from 'web/components/contract-search'
|
||||
|
||||
export const contracts = coll<Contract>('contracts')
|
||||
|
||||
|
@ -321,51 +320,6 @@ export const getTopGroupContracts = async (
|
|||
return await getValues<Contract>(creatorContractsQuery)
|
||||
}
|
||||
|
||||
const sortToField = {
|
||||
newest: 'createdTime',
|
||||
score: 'popularityScore',
|
||||
'most-traded': 'volume',
|
||||
'24-hour-vol': 'volume24Hours',
|
||||
'prob-change-day': 'probChanges.day',
|
||||
'last-updated': 'lastUpdated',
|
||||
liquidity: 'totalLiquidity',
|
||||
'close-date': 'closeTime',
|
||||
'resolve-date': 'resolutionTime',
|
||||
'prob-descending': 'prob',
|
||||
'prob-ascending': 'prob',
|
||||
} as const
|
||||
|
||||
const sortToDirection = {
|
||||
newest: 'desc',
|
||||
score: 'desc',
|
||||
'most-traded': 'desc',
|
||||
'24-hour-vol': 'desc',
|
||||
'prob-change-day': 'desc',
|
||||
'last-updated': 'desc',
|
||||
liquidity: 'desc',
|
||||
'close-date': 'asc',
|
||||
'resolve-date': 'desc',
|
||||
'prob-ascending': 'asc',
|
||||
'prob-descending': 'desc',
|
||||
} as const
|
||||
|
||||
export const getContractsQuery = (
|
||||
sort: Sort,
|
||||
maxItems: number,
|
||||
filters: { groupSlug?: string } = {},
|
||||
visibility?: 'public'
|
||||
) => {
|
||||
const { groupSlug } = filters
|
||||
return query(
|
||||
contracts,
|
||||
where('isResolved', '==', false),
|
||||
...(visibility ? [where('visibility', '==', visibility)] : []),
|
||||
...(groupSlug ? [where('groupSlugs', 'array-contains', groupSlug)] : []),
|
||||
orderBy(sortToField[sort], sortToDirection[sort]),
|
||||
limit(maxItems)
|
||||
)
|
||||
}
|
||||
|
||||
export const getRecommendedContracts = async (
|
||||
contract: Contract,
|
||||
excludeBettorId: string,
|
||||
|
|
|
@ -43,6 +43,7 @@ export function groupPath(
|
|||
| 'about'
|
||||
| typeof GROUP_CHAT_SLUG
|
||||
| 'leaderboards'
|
||||
| 'posts'
|
||||
) {
|
||||
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
|
||||
}
|
||||
|
|
|
@ -39,3 +39,8 @@ export function listenForPost(
|
|||
) {
|
||||
return listenForValue(doc(posts, postId), setPost)
|
||||
}
|
||||
|
||||
export async function listPosts(postIds?: string[]) {
|
||||
if (postIds === undefined) return []
|
||||
return Promise.all(postIds.map(getPost))
|
||||
}
|
||||
|
|
|
@ -9,9 +9,6 @@ module.exports = {
|
|||
reactStrictMode: true,
|
||||
optimizeFonts: false,
|
||||
experimental: {
|
||||
images: {
|
||||
allowFutureImage: true,
|
||||
},
|
||||
scrollRestoration: true,
|
||||
externalDir: true,
|
||||
modularizeImports: {
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
"@floating-ui/react-dom-interactions": "0.9.2",
|
||||
"@headlessui/react": "1.6.1",
|
||||
"@heroicons/react": "1.0.5",
|
||||
"@nivo/core": "0.74.0",
|
||||
"@nivo/line": "0.74.0",
|
||||
"@nivo/tooltip": "0.74.0",
|
||||
"@nivo/core": "0.80.0",
|
||||
"@nivo/line": "0.80.0",
|
||||
"@nivo/tooltip": "0.80.0",
|
||||
"@react-query-firebase/firestore": "0.4.2",
|
||||
"@tiptap/core": "2.0.0-beta.182",
|
||||
"@tiptap/extension-character-count": "2.0.0-beta.31",
|
||||
|
@ -58,6 +58,7 @@
|
|||
"react-instantsearch-hooks-web": "6.24.1",
|
||||
"react-query": "3.39.0",
|
||||
"react-twitter-embed": "4.0.4",
|
||||
"react-masonry-css": "1.0.16",
|
||||
"string-similarity": "^4.0.4",
|
||||
"tippy.js": "6.3.7"
|
||||
},
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Page } from 'web/components/page'
|
||||
import { Title } from 'web/components/title'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
|
||||
import { TextEditor, useTextEditor } from 'web/components/editor'
|
||||
import { createPost } from 'web/lib/firebase/api'
|
||||
import clsx from 'clsx'
|
||||
import Router from 'next/router'
|
||||
import { MAX_POST_TITLE_LENGTH } from 'common/post'
|
||||
import { postPath } from 'web/lib/firebase/posts'
|
||||
|
||||
export default function CreatePost() {
|
||||
const [title, setTitle] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const { editor, upload } = useTextEditor({
|
||||
disabled: isSubmitting,
|
||||
})
|
||||
|
||||
const isValid = editor && title.length > 0 && editor.isEmpty === false
|
||||
|
||||
async function savePost(title: string) {
|
||||
if (!editor) return
|
||||
const newPost = {
|
||||
title: title,
|
||||
content: editor.getJSON(),
|
||||
}
|
||||
|
||||
const result = await createPost(newPost).catch((e) => {
|
||||
console.log(e)
|
||||
setError('There was an error creating the post, please try again')
|
||||
return e
|
||||
})
|
||||
if (result.post) {
|
||||
await Router.push(postPath(result.post.slug))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<div className="rounded-lg px-6 py-4 sm:py-0">
|
||||
<Title className="!mt-0" text="Create a post" />
|
||||
<form>
|
||||
<div className="form-control w-full">
|
||||
<label className="label">
|
||||
<span className="mb-1">
|
||||
Title<span className={'text-red-700'}> *</span>
|
||||
</span>
|
||||
</label>
|
||||
<Textarea
|
||||
placeholder="e.g. Elon Mania Post"
|
||||
className="input input-bordered resize-none"
|
||||
autoFocus
|
||||
maxLength={MAX_POST_TITLE_LENGTH}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value || '')}
|
||||
/>
|
||||
<Spacer h={6} />
|
||||
<label className="label">
|
||||
<span className="mb-1">
|
||||
Content<span className={'text-red-700'}> *</span>
|
||||
</span>
|
||||
</label>
|
||||
<TextEditor editor={editor} upload={upload} />
|
||||
<Spacer h={6} />
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className={clsx(
|
||||
'btn btn-primary normal-case',
|
||||
isSubmitting && 'loading disabled'
|
||||
)}
|
||||
disabled={isSubmitting || !isValid || upload.isLoading}
|
||||
onClick={async () => {
|
||||
setIsSubmitting(true)
|
||||
await savePost(title)
|
||||
setIsSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? 'Creating...' : 'Create a post'}
|
||||
</button>
|
||||
{error !== '' && <div className="text-red-700">{error}</div>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { Row } from 'web/components/layout/row'
|
||||
import { firebaseLogin, getUser, User } from 'web/lib/firebase/users'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useUser, useUserById } from 'web/hooks/use-user'
|
||||
import {
|
||||
useGroup,
|
||||
useGroupContractIds,
|
||||
|
@ -42,10 +42,10 @@ import { GroupComment } from 'common/comment'
|
|||
import { REFERRAL_AMOUNT } from 'common/economy'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { GroupAboutPost } from 'web/components/groups/group-about-post'
|
||||
import { getPost } from 'web/lib/firebase/posts'
|
||||
import { getPost, listPosts, postPath } from 'web/lib/firebase/posts'
|
||||
import { Post } from 'common/post'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { usePost } from 'web/hooks/use-post'
|
||||
import { usePost, usePosts } from 'web/hooks/use-post'
|
||||
import { useAdmin } from 'web/hooks/use-admin'
|
||||
import { track } from '@amplitude/analytics-browser'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/solid'
|
||||
|
@ -53,6 +53,10 @@ import { SelectMarketsModal } from 'web/components/contract-select-modal'
|
|||
import { BETTORS } from 'common/user'
|
||||
import { Page } from 'web/components/page'
|
||||
import { Tabs } from 'web/components/layout/tabs'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
import { Title } from 'web/components/title'
|
||||
import { fromNow } from 'web/lib/util/time'
|
||||
import { CreatePost } from 'web/components/create-post'
|
||||
|
||||
export const getStaticProps = fromPropz(getStaticPropz)
|
||||
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||
|
@ -70,7 +74,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
|||
? 'all'
|
||||
: 'open'
|
||||
const aboutPost =
|
||||
group && group.aboutPostId != null && (await getPost(group.aboutPostId))
|
||||
group && group.aboutPostId != null ? await getPost(group.aboutPostId) : null
|
||||
|
||||
const messages = group && (await listAllCommentsOnGroup(group.id))
|
||||
|
||||
const cachedTopTraderIds =
|
||||
|
@ -83,6 +88,9 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
|||
|
||||
const creator = await creatorPromise
|
||||
|
||||
const posts = ((group && (await listPosts(group.postIds))) ?? []).filter(
|
||||
(p) => p != null
|
||||
) as Post[]
|
||||
return {
|
||||
props: {
|
||||
group,
|
||||
|
@ -93,6 +101,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
|||
messages,
|
||||
aboutPost,
|
||||
suggestedFilter,
|
||||
posts,
|
||||
},
|
||||
|
||||
revalidate: 60, // regenerate after a minute
|
||||
|
@ -107,17 +116,19 @@ const groupSubpages = [
|
|||
'markets',
|
||||
'leaderboards',
|
||||
'about',
|
||||
'posts',
|
||||
] as const
|
||||
|
||||
export default function GroupPage(props: {
|
||||
group: Group | null
|
||||
memberIds: string[]
|
||||
creator: User
|
||||
creator: User | null
|
||||
topTraders: { user: User; score: number }[]
|
||||
topCreators: { user: User; score: number }[]
|
||||
messages: GroupComment[]
|
||||
aboutPost: Post
|
||||
aboutPost: Post | null
|
||||
suggestedFilter: 'open' | 'all'
|
||||
posts: Post[]
|
||||
}) {
|
||||
props = usePropz(props, getStaticPropz) ?? {
|
||||
group: null,
|
||||
|
@ -127,26 +138,36 @@ export default function GroupPage(props: {
|
|||
topCreators: [],
|
||||
messages: [],
|
||||
suggestedFilter: 'open',
|
||||
posts: [],
|
||||
}
|
||||
const { creator, topTraders, topCreators, suggestedFilter } = props
|
||||
const { creator, topTraders, topCreators, suggestedFilter, posts } = props
|
||||
|
||||
const router = useRouter()
|
||||
const { slugs } = router.query as { slugs: string[] }
|
||||
const page = slugs?.[1] as typeof groupSubpages[number]
|
||||
const tabIndex = ['markets', 'leaderboard', 'about', 'posts'].indexOf(
|
||||
page ?? 'markets'
|
||||
)
|
||||
|
||||
const group = useGroup(props.group?.id) ?? props.group
|
||||
const aboutPost = usePost(props.aboutPost?.id) ?? props.aboutPost
|
||||
|
||||
let groupPosts = usePosts(group?.postIds ?? []) ?? posts
|
||||
|
||||
if (aboutPost != null) {
|
||||
groupPosts = [aboutPost, ...groupPosts]
|
||||
}
|
||||
|
||||
const user = useUser()
|
||||
const isAdmin = useAdmin()
|
||||
const memberIds = useMemberIds(group?.id ?? null) ?? props.memberIds
|
||||
|
||||
useSaveReferral(user, {
|
||||
defaultReferrerUsername: creator.username,
|
||||
defaultReferrerUsername: creator?.username,
|
||||
groupId: group?.id,
|
||||
})
|
||||
|
||||
if (group === null || !groupSubpages.includes(page) || slugs[2]) {
|
||||
if (group === null || !groupSubpages.includes(page) || slugs[2] || !creator) {
|
||||
return <Custom404 />
|
||||
}
|
||||
const isCreator = user && group && user.id === group.creatorId
|
||||
|
@ -172,6 +193,16 @@ export default function GroupPage(props: {
|
|||
</Col>
|
||||
)
|
||||
|
||||
const postsPage = (
|
||||
<>
|
||||
<Col>
|
||||
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
|
||||
{posts && <GroupPosts posts={groupPosts} group={group} />}
|
||||
</div>
|
||||
</Col>
|
||||
</>
|
||||
)
|
||||
|
||||
const aboutTab = (
|
||||
<Col>
|
||||
{(group.aboutPostId != null || isCreator || isAdmin) && (
|
||||
|
@ -234,6 +265,10 @@ export default function GroupPage(props: {
|
|||
title: 'About',
|
||||
content: aboutTab,
|
||||
},
|
||||
{
|
||||
title: 'Posts',
|
||||
content: postsPage,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
|
@ -245,7 +280,8 @@ export default function GroupPage(props: {
|
|||
/>
|
||||
<TopGroupNavBar group={group} />
|
||||
<div className={'relative p-2 pt-0 md:pt-2'}>
|
||||
<Tabs className={'mb-2'} tabs={tabs} />
|
||||
{/* TODO: Switching tabs should also update the group path */}
|
||||
<Tabs className={'mb-2'} tabs={tabs} defaultIndex={tabIndex} />
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
|
@ -413,6 +449,84 @@ function GroupLeaderboard(props: {
|
|||
)
|
||||
}
|
||||
|
||||
function GroupPosts(props: { posts: Post[]; group: Group }) {
|
||||
const { posts, group } = props
|
||||
const [showCreatePost, setShowCreatePost] = useState(false)
|
||||
const user = useUser()
|
||||
|
||||
const createPost = <CreatePost group={group} />
|
||||
|
||||
const postList = (
|
||||
<div className=" align-start w-full items-start">
|
||||
<Row className="flex justify-between">
|
||||
<Col>
|
||||
<Title text={'Posts'} className="!mt-0" />
|
||||
</Col>
|
||||
<Col>
|
||||
{user && (
|
||||
<Button
|
||||
className="btn-md"
|
||||
onClick={() => setShowCreatePost(!showCreatePost)}
|
||||
>
|
||||
Add a Post
|
||||
</Button>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className="mt-2">
|
||||
{posts.map((post) => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
{posts.length === 0 && (
|
||||
<div className="text-center text-gray-500">No posts yet</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return showCreatePost ? createPost : postList
|
||||
}
|
||||
|
||||
function PostCard(props: { post: Post }) {
|
||||
const { post } = props
|
||||
const creatorId = post.creatorId
|
||||
|
||||
const user = useUserById(creatorId)
|
||||
|
||||
if (!user) return <> </>
|
||||
|
||||
return (
|
||||
<div className="py-1">
|
||||
<Link href={postPath(post.slug)}>
|
||||
<Row
|
||||
className={
|
||||
'relative gap-3 rounded-lg bg-white p-2 shadow-md hover:cursor-pointer hover:bg-gray-100'
|
||||
}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<Avatar className="h-12 w-12" username={user?.username} />
|
||||
</div>
|
||||
<div className="">
|
||||
<div className="text-sm text-gray-500">
|
||||
<UserLink
|
||||
className="text-neutral"
|
||||
name={user?.name}
|
||||
username={user?.username}
|
||||
/>
|
||||
<span className="mx-1">•</span>
|
||||
<span className="text-gray-500">{fromNow(post.createdTime)}</span>
|
||||
</div>
|
||||
<div className="text-lg font-medium text-gray-900">
|
||||
{post.title}
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AddContractButton(props: { group: Group; user: User }) {
|
||||
const { group, user } = props
|
||||
const [open, setOpen] = useState(false)
|
||||
|
|
|
@ -102,6 +102,32 @@ export default function Groups(props: {
|
|||
className="mb-4"
|
||||
currentPageForAnalytics={'groups'}
|
||||
tabs={[
|
||||
{
|
||||
title: 'All',
|
||||
content: (
|
||||
<Col>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => debouncedQuery(e.target.value)}
|
||||
placeholder="Search groups"
|
||||
value={query}
|
||||
className="input input-bordered mb-4 w-full"
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{matchesOrderedByMostContractAndMembers.map((group) => (
|
||||
<GroupCard
|
||||
key={group.id}
|
||||
group={group}
|
||||
creator={creatorsDict[group.creatorId]}
|
||||
user={user}
|
||||
isMember={memberGroupIds.includes(group.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
),
|
||||
},
|
||||
...(user
|
||||
? [
|
||||
{
|
||||
|
@ -136,32 +162,6 @@ export default function Groups(props: {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: 'All',
|
||||
content: (
|
||||
<Col>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => debouncedQuery(e.target.value)}
|
||||
placeholder="Search groups"
|
||||
value={query}
|
||||
className="input input-bordered mb-4 w-full"
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{matchesOrderedByMostContractAndMembers.map((group) => (
|
||||
<GroupCard
|
||||
key={group.id}
|
||||
group={group}
|
||||
creator={creatorsDict[group.creatorId]}
|
||||
user={user}
|
||||
isMember={memberGroupIds.includes(group.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
|
|
|
@ -45,6 +45,7 @@ import { usePrefetch } from 'web/hooks/use-prefetch'
|
|||
import { Title } from 'web/components/title'
|
||||
import { CPMMBinaryContract } from 'common/contract'
|
||||
import { useContractsByDailyScoreGroups } from 'web/hooks/use-contracts'
|
||||
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||
|
||||
export default function Home() {
|
||||
const user = useUser()
|
||||
|
@ -54,6 +55,13 @@ export default function Home() {
|
|||
useSaveReferral()
|
||||
usePrefetch(user?.id)
|
||||
|
||||
useEffect(() => {
|
||||
if (user === null) {
|
||||
// Go to landing page if not logged in.
|
||||
Router.push('/')
|
||||
}
|
||||
})
|
||||
|
||||
const groups = useMemberGroupsSubscription(user)
|
||||
|
||||
const { sections } = getHomeItems(groups ?? [], user?.homeSections ?? [])
|
||||
|
@ -82,13 +90,17 @@ export default function Home() {
|
|||
<DailyStats user={user} />
|
||||
</Row>
|
||||
|
||||
<>
|
||||
{sections.map((section) =>
|
||||
renderSection(section, user, groups, groupContracts)
|
||||
)}
|
||||
{!user ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<>
|
||||
{sections.map((section) =>
|
||||
renderSection(section, user, groups, groupContracts)
|
||||
)}
|
||||
|
||||
<TrendingGroupsSection user={user} />
|
||||
</>
|
||||
<TrendingGroupsSection user={user} />
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -106,9 +118,9 @@ export default function Home() {
|
|||
|
||||
const HOME_SECTIONS = [
|
||||
{ label: 'Daily movers', id: 'daily-movers' },
|
||||
{ label: 'Daily trending', id: 'daily-trending' },
|
||||
{ label: 'Trending', id: 'score' },
|
||||
{ label: 'New', id: 'newest' },
|
||||
{ label: 'Recently updated', id: 'recently-updated-for-you' },
|
||||
]
|
||||
|
||||
export const getHomeItems = (groups: Group[], sections: string[]) => {
|
||||
|
@ -127,6 +139,10 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
|
|||
|
||||
const sectionItems = filterDefined(sections.map((id) => itemsById[id]))
|
||||
|
||||
// Add new home section items to the top.
|
||||
sectionItems.unshift(
|
||||
...HOME_SECTIONS.filter((item) => !sectionItems.includes(item))
|
||||
)
|
||||
// Add unmentioned items to the end.
|
||||
sectionItems.push(...items.filter((item) => !sectionItems.includes(item)))
|
||||
|
||||
|
@ -138,20 +154,20 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
|
|||
|
||||
function renderSection(
|
||||
section: { id: string; label: string },
|
||||
user: User | null | undefined,
|
||||
user: User,
|
||||
groups: Group[] | undefined,
|
||||
groupContracts: Dictionary<CPMMBinaryContract[]> | undefined
|
||||
) {
|
||||
const { id, label } = section
|
||||
if (id === 'daily-movers') {
|
||||
return <DailyMoversSection key={id} userId={user?.id} />
|
||||
return <DailyMoversSection key={id} userId={user.id} />
|
||||
}
|
||||
if (id === 'recently-updated-for-you')
|
||||
if (id === 'daily-trending')
|
||||
return (
|
||||
<SearchSection
|
||||
key={id}
|
||||
label={label}
|
||||
sort={'last-updated'}
|
||||
sort={'daily-score'}
|
||||
pill="personal"
|
||||
user={user}
|
||||
/>
|
||||
|
@ -210,7 +226,7 @@ function SectionHeader(props: {
|
|||
|
||||
function SearchSection(props: {
|
||||
label: string
|
||||
user: User | null | undefined | undefined
|
||||
user: User
|
||||
sort: Sort
|
||||
pill?: string
|
||||
}) {
|
||||
|
@ -236,7 +252,7 @@ function SearchSection(props: {
|
|||
|
||||
function GroupSection(props: {
|
||||
group: Group
|
||||
user: User | null | undefined | undefined
|
||||
user: User
|
||||
contracts: CPMMBinaryContract[]
|
||||
}) {
|
||||
const { group, user, contracts } = props
|
||||
|
@ -247,18 +263,16 @@ function GroupSection(props: {
|
|||
<Button
|
||||
color="gray-white"
|
||||
onClick={() => {
|
||||
if (user) {
|
||||
const homeSections = (user.homeSections ?? []).filter(
|
||||
(id) => id !== group.id
|
||||
)
|
||||
updateUser(user.id, { homeSections })
|
||||
const homeSections = (user.homeSections ?? []).filter(
|
||||
(id) => id !== group.id
|
||||
)
|
||||
updateUser(user.id, { homeSections })
|
||||
|
||||
toast.promise(leaveGroup(group, user.id), {
|
||||
loading: 'Unfollowing group...',
|
||||
success: `Unfollowed ${group.name}`,
|
||||
error: "Couldn't unfollow group, try again?",
|
||||
})
|
||||
}
|
||||
toast.promise(leaveGroup(group, user.id), {
|
||||
loading: 'Unfollowing group...',
|
||||
success: `Unfollowed ${group.name}`,
|
||||
error: "Couldn't unfollow group, try again?",
|
||||
})
|
||||
}}
|
||||
>
|
||||
<XCircleIcon className={'h-5 w-5 flex-shrink-0'} aria-hidden="true" />
|
||||
|
|
|
@ -971,6 +971,8 @@ function ContractResolvedNotification(props: {
|
|||
const { sourceText, data } = notification
|
||||
const { userInvestment, userPayout } = (data as ContractResolutionData) ?? {}
|
||||
const subtitle = 'resolved the market'
|
||||
const profitable = userPayout >= userInvestment
|
||||
const ROI = (userPayout - userInvestment) / userInvestment
|
||||
|
||||
const resolutionDescription = () => {
|
||||
if (!sourceText) return <div />
|
||||
|
@ -1002,23 +1004,21 @@ function ContractResolvedNotification(props: {
|
|||
|
||||
const description =
|
||||
userInvestment && userPayout !== undefined ? (
|
||||
<Row className={'gap-1 '}>
|
||||
Resolved: {resolutionDescription()}
|
||||
Invested:
|
||||
<>
|
||||
Resolved: {resolutionDescription()} Invested:
|
||||
<span className={'text-primary'}>{formatMoney(userInvestment)} </span>
|
||||
Payout:
|
||||
<span
|
||||
className={clsx(
|
||||
userPayout > 0 ? 'text-primary' : 'text-red-500',
|
||||
'truncate'
|
||||
profitable ? 'text-primary' : 'text-red-500',
|
||||
'truncate text-ellipsis'
|
||||
)}
|
||||
>
|
||||
{formatMoney(userPayout)}
|
||||
{` (${userPayout > 0 ? '+' : ''}${Math.round(
|
||||
((userPayout - userInvestment) / userInvestment) * 100
|
||||
)}%)`}
|
||||
{userPayout > 0 &&
|
||||
` (${profitable ? '+' : ''}${Math.round(ROI * 100)}%)`}
|
||||
</span>
|
||||
</Row>
|
||||
</>
|
||||
) : (
|
||||
<span>Resolved {resolutionDescription()}</span>
|
||||
)
|
||||
|
@ -1038,9 +1038,7 @@ function ContractResolvedNotification(props: {
|
|||
highlighted={highlighted}
|
||||
subtitle={subtitle}
|
||||
>
|
||||
<Row>
|
||||
<span>{description}</span>
|
||||
</Row>
|
||||
<Row className={'line-clamp-2 space-x-1'}>{description}</Row>
|
||||
</NotificationFrame>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -107,7 +107,14 @@ const tourneys: Tourney[] = [
|
|||
groupId: 'SxGRqXRpV3RAQKudbcNb',
|
||||
},
|
||||
|
||||
// Tournaments without awards get featured belows
|
||||
// Tournaments without awards get featured below
|
||||
{
|
||||
title: 'Criticism and Red Teaming Contest',
|
||||
blurb:
|
||||
'Which criticisms of Effective Altruism have been the most valuable?',
|
||||
endTime: toDate('Sep 30, 2022'),
|
||||
groupId: 'K86LmEmidMKdyCHdHNv4',
|
||||
},
|
||||
{
|
||||
title: 'SF 2022 Ballot',
|
||||
blurb: 'Which ballot initiatives will pass this year in SF and CA?',
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 266 KiB |
|
@ -15,9 +15,6 @@ module.exports = {
|
|||
}
|
||||
),
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'world-trading': "url('/world-trading-background.webp')",
|
||||
},
|
||||
colors: {
|
||||
'greyscale-1': '#FBFBFF',
|
||||
'greyscale-2': '#E7E7F4',
|
||||
|
|
394
yarn.lock
394
yarn.lock
|
@ -2553,102 +2553,99 @@
|
|||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
|
||||
integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
|
||||
|
||||
"@nivo/annotations@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.74.0.tgz#f4a3474fdf8812c3812c30e08d3277e209bec0f6"
|
||||
integrity sha512-nxZLKDi9YEy2zZUsOtbYL/2oAgsxK5SVZ1P3Csll+cQ96uLU6sU7jmb67AwK0nDbYk7BD3sZf/O/A9r/MCK4Ow==
|
||||
"@nivo/annotations@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.80.0.tgz#127e4801fff7370dcfb9acfe1e335781dd65cfd5"
|
||||
integrity sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==
|
||||
dependencies:
|
||||
"@nivo/colors" "0.74.0"
|
||||
"@react-spring/web" "9.2.6"
|
||||
"@nivo/colors" "0.80.0"
|
||||
"@react-spring/web" "9.4.5"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@nivo/axes@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.74.0.tgz#cf7cf2277b7aca5449a040ddf3e0cf9891971199"
|
||||
integrity sha512-27o1H+Br0AaeUTiRhy7OebqzYEWr1xznHOxd+Hn2Xz9kK1alGBiPgwXrkXV0Q9CtrsroQFnX2QR3JxRgOtC5fA==
|
||||
"@nivo/axes@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.80.0.tgz#22788855ddc45bb6a619dcd03d62d4bd8c0fc35f"
|
||||
integrity sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==
|
||||
dependencies:
|
||||
"@nivo/scales" "0.74.0"
|
||||
"@react-spring/web" "9.2.6"
|
||||
"@nivo/scales" "0.80.0"
|
||||
"@react-spring/web" "9.4.5"
|
||||
d3-format "^1.4.4"
|
||||
d3-time-format "^3.0.0"
|
||||
|
||||
"@nivo/colors@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.74.0.tgz#29d1e7c6f3bcab4e872a168651b3a90cfba03a4f"
|
||||
integrity sha512-5ClckmBm3x2XdJqHMylr6erY+scEL/twoGVfyXak/L+AIhL+Gf9PQxyxyfl3Lbtc3SPeAQe0ZAO1+VrmTn7qlA==
|
||||
"@nivo/colors@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.80.0.tgz#5b70b4979df246d9d0d69fb638bba9764dd88b52"
|
||||
integrity sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==
|
||||
dependencies:
|
||||
d3-color "^2.0.0"
|
||||
d3-scale "^3.2.3"
|
||||
d3-scale-chromatic "^2.0.0"
|
||||
lodash "^4.17.21"
|
||||
react-motion "^0.5.2"
|
||||
|
||||
"@nivo/core@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.74.0.tgz#7634c78a36a8bd50a0c04c6b6f12b779a88ec2f4"
|
||||
integrity sha512-LZ3kN1PiEW0KU4PTBgaHFO757amyKZkEL4mKdAzvyNQtpq5idB3OhC/sYrBxhJaLqYcX19MgNfhIel/0KygHAg==
|
||||
"@nivo/core@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.80.0.tgz#d180cb2622158eb7bc5f984131ff07984f12297e"
|
||||
integrity sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==
|
||||
dependencies:
|
||||
"@nivo/recompose" "0.74.0"
|
||||
"@react-spring/web" "9.2.6"
|
||||
"@nivo/recompose" "0.80.0"
|
||||
"@react-spring/web" "9.4.5"
|
||||
d3-color "^2.0.0"
|
||||
d3-format "^1.4.4"
|
||||
d3-hierarchy "^1.1.8"
|
||||
d3-interpolate "^2.0.1"
|
||||
d3-scale "^3.2.3"
|
||||
d3-scale-chromatic "^2.0.0"
|
||||
d3-shape "^1.3.5"
|
||||
d3-time-format "^3.0.0"
|
||||
lodash "^4.17.21"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
"@nivo/legends@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.74.0.tgz#8e5e04b2a3f980c2073a394d94c4d89fa8bc8724"
|
||||
integrity sha512-Bfk392ngre1C8UaGoymwqK0acjjzuk0cglUSNsr0z8BAUQIVGUPthtfcxbq/yUYGJL/cxWky2QKxi9r3C0FbmA==
|
||||
"@nivo/legends@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.80.0.tgz#49edc54000075b4df055f86794a8c32810269d06"
|
||||
integrity sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==
|
||||
|
||||
"@nivo/line@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.74.0.tgz#f1f430d64a81d2fe1a5fd49e5cfaa61242066927"
|
||||
integrity sha512-uJssLII1UTfxrZkPrkki054LFUpSKeqS35ttwK6VLvyqs5r3SrSXn223vDRNaaxuop5oT/L3APUJQwQDqUcj3w==
|
||||
"@nivo/line@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.80.0.tgz#ba541b0fcfd53b3a7ce865feb43c993b7cf4a7d4"
|
||||
integrity sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==
|
||||
dependencies:
|
||||
"@nivo/annotations" "0.74.0"
|
||||
"@nivo/axes" "0.74.0"
|
||||
"@nivo/colors" "0.74.0"
|
||||
"@nivo/legends" "0.74.0"
|
||||
"@nivo/scales" "0.74.0"
|
||||
"@nivo/tooltip" "0.74.0"
|
||||
"@nivo/voronoi" "0.74.0"
|
||||
"@react-spring/web" "9.2.6"
|
||||
"@nivo/annotations" "0.80.0"
|
||||
"@nivo/axes" "0.80.0"
|
||||
"@nivo/colors" "0.80.0"
|
||||
"@nivo/legends" "0.80.0"
|
||||
"@nivo/scales" "0.80.0"
|
||||
"@nivo/tooltip" "0.80.0"
|
||||
"@nivo/voronoi" "0.80.0"
|
||||
"@react-spring/web" "9.4.5"
|
||||
d3-shape "^1.3.5"
|
||||
|
||||
"@nivo/recompose@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.74.0.tgz#057e8e1154073d7f4cb01aa8d165c3914b8bdb54"
|
||||
integrity sha512-qC9gzGvDIxocrJoozDjqqffOwDpuEZijeMV59KExnztCwIpQbIYVBsDdpvL+tXfWausigSlnGILGfereXJTLUQ==
|
||||
"@nivo/recompose@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.80.0.tgz#572048aed793321a0bada1fd176b72df5a25282e"
|
||||
integrity sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==
|
||||
dependencies:
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
"@nivo/scales@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.74.0.tgz#ede12b899da9e3aee7921ebce40f227e670a430d"
|
||||
integrity sha512-5mER71NgZGdgs8X2PgilBpAWMMGtTXrUuYOBQWDKDMgtc83MU+mphhiYfLv5e6ViZyUB5ebfEkfeIgStLqrcEA==
|
||||
"@nivo/scales@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.80.0.tgz#39313fb97c8ae9633c2aa1e17adb57cb851e8a50"
|
||||
integrity sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==
|
||||
dependencies:
|
||||
d3-scale "^3.2.3"
|
||||
d3-time "^1.0.11"
|
||||
d3-time-format "^3.0.0"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@nivo/tooltip@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.74.0.tgz#60d94b0fecc2fc179ada3efa380e7e456982b4a5"
|
||||
integrity sha512-h3PUgNFF5HUeQFfx19MWS1uGK8iUDymZNY+5PyaCWDFT+0/ldXBu8uw5WYRui2KwNdTym6F0E/aT7JKczDd85w==
|
||||
"@nivo/tooltip@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.80.0.tgz#07ebef47eb708a0612bd6297d5ad156bbec19d34"
|
||||
integrity sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==
|
||||
dependencies:
|
||||
"@react-spring/web" "9.2.6"
|
||||
"@react-spring/web" "9.4.5"
|
||||
|
||||
"@nivo/voronoi@0.74.0":
|
||||
version "0.74.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.74.0.tgz#4b427955ddabd86934a2bbb95a62ff53ee97c575"
|
||||
integrity sha512-Q3267T1+Tlufn8LbmSYnO8x9gL+h/iwH2Uqc5CENHSZu2KPD0PB82vxpQnbDVhjadulI0rlrPA9fU3VY3q1zKg==
|
||||
"@nivo/voronoi@0.80.0":
|
||||
version "0.80.0"
|
||||
resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.80.0.tgz#59cc7ed253dc1a5bbcf614a5ac37d2468d561599"
|
||||
integrity sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==
|
||||
dependencies:
|
||||
d3-delaunay "^5.3.0"
|
||||
d3-scale "^3.2.3"
|
||||
|
@ -2747,50 +2744,51 @@
|
|||
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
|
||||
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
|
||||
|
||||
"@react-spring/animated@~9.2.6-beta.0":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.2.6.tgz#58f30fb75d8bfb7ccbc156cfd6b974a8f3dfd54e"
|
||||
integrity sha512-xjL6nmixYNDvnpTs1FFMsMfSC0tURwPCU3b2jWNriYGLfwZ7c/TcyaEZA7yiNnmdFnuR3f3Z27AqIgaFC083Cw==
|
||||
"@react-spring/animated@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54"
|
||||
integrity sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==
|
||||
dependencies:
|
||||
"@react-spring/shared" "~9.2.6-beta.0"
|
||||
"@react-spring/types" "~9.2.6-beta.0"
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
|
||||
"@react-spring/core@~9.2.6-beta.0":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.2.6.tgz#ae22338fe55d070caf03abb4293b5519ba620d93"
|
||||
integrity sha512-uPHUxmu+w6mHJrfQTMtmGJ8iZEwiVxz9kH7dRyk69bkZJt9z+w0Oj3UF4J3VcECZsbm3HRhN2ogXSAaqGjwhQw==
|
||||
"@react-spring/core@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.5.tgz#4616e1adc18dd10f5731f100ebdbe9518b89ba3c"
|
||||
integrity sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.2.6-beta.0"
|
||||
"@react-spring/shared" "~9.2.6-beta.0"
|
||||
"@react-spring/types" "~9.2.6-beta.0"
|
||||
"@react-spring/animated" "~9.4.5"
|
||||
"@react-spring/rafz" "~9.4.5"
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
|
||||
"@react-spring/rafz@~9.2.6-beta.0":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.2.6.tgz#d97484003875bf5fb5e6ec22dee97cc208363e48"
|
||||
integrity sha512-62SivLKEpo7EfHPkxO5J3g9Cr9LF6+1A1RVOMJhkcpEYtbdbmma/d63Xp8qpMPEpk7uuWxaTb6jjyxW33pW3sg==
|
||||
"@react-spring/rafz@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.5.tgz#84f809f287f2a66bbfbc66195db340482f886bd7"
|
||||
integrity sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==
|
||||
|
||||
"@react-spring/shared@~9.2.6-beta.0":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.2.6.tgz#2c84e62cc0cfbbbbeb5546acd46c1f4b248bc562"
|
||||
integrity sha512-Qrm9fopKG/RxZ3Rw+4euhrpnB3uXSyiON9skHbcBfmkkzagpkUR66MX1YLrhHw0UchcZuSDnXs0Lonzt1rpWag==
|
||||
"@react-spring/shared@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.5.tgz#4c3ad817bca547984fb1539204d752a412a6d829"
|
||||
integrity sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==
|
||||
dependencies:
|
||||
"@react-spring/rafz" "~9.2.6-beta.0"
|
||||
"@react-spring/types" "~9.2.6-beta.0"
|
||||
"@react-spring/rafz" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
|
||||
"@react-spring/types@~9.2.6-beta.0":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.2.6.tgz#f60722fcf9f8492ae16d0bdc47f0ea3c2a16d2cf"
|
||||
integrity sha512-l7mCw182DtDMnCI8CB9orgTAEoFZRtdQ6aS6YeEAqYcy3nQZPmPggIHH9DxyLw7n7vBPRSzu9gCvUMgXKpTflg==
|
||||
"@react-spring/types@~9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.5.tgz#9c71e5ff866b5484a7ef3db822bf6c10e77bdd8c"
|
||||
integrity sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==
|
||||
|
||||
"@react-spring/web@9.2.6":
|
||||
version "9.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.2.6.tgz#c4fba69e1b1b43bd1d6a62346530cfb07f2be09b"
|
||||
integrity sha512-0HkRsEYR/CO3Uw46FWDWaF2wg2rUXcWE2R9AoZXthEYLUn5w9uE1mf2Jel7BxBxWGQ73owkqSQv+klA1Hb+ViQ==
|
||||
"@react-spring/web@9.4.5":
|
||||
version "9.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.5.tgz#b92f05b87cdc0963a59ee149e677dcaff09f680e"
|
||||
integrity sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.2.6-beta.0"
|
||||
"@react-spring/core" "~9.2.6-beta.0"
|
||||
"@react-spring/shared" "~9.2.6-beta.0"
|
||||
"@react-spring/types" "~9.2.6-beta.0"
|
||||
"@react-spring/animated" "~9.4.5"
|
||||
"@react-spring/core" "~9.4.5"
|
||||
"@react-spring/shared" "~9.4.5"
|
||||
"@react-spring/types" "~9.4.5"
|
||||
|
||||
"@rushstack/eslint-patch@^1.1.3":
|
||||
version "1.1.3"
|
||||
|
@ -3566,6 +3564,13 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@5.36.0":
|
||||
version "5.36.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.0.tgz#8f159c4cdb3084eb5d4b72619a2ded942aa109e5"
|
||||
|
@ -4318,7 +4323,7 @@ base16@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
||||
|
||||
base64-js@^1.3.0:
|
||||
base64-js@^1.3.0, base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
@ -4348,6 +4353,15 @@ binary-extensions@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bluebird@^3.7.1:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
|
@ -4461,6 +4475,11 @@ browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4
|
|||
node-releases "^2.0.3"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
|
@ -4471,6 +4490,14 @@ buffer-from@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
|
@ -4650,6 +4677,11 @@ chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chownr@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
||||
chrome-trace-event@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||
|
@ -5016,7 +5048,7 @@ cross-env@^7.0.3:
|
|||
dependencies:
|
||||
cross-spawn "^7.0.1"
|
||||
|
||||
cross-fetch@^3.1.5:
|
||||
cross-fetch@3.1.5, cross-fetch@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||
|
@ -5232,11 +5264,6 @@ d3-format@^1.4.4:
|
|||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
||||
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
|
||||
|
||||
d3-hierarchy@^1.1.8:
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83"
|
||||
integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
|
||||
|
||||
"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
||||
|
@ -5343,7 +5370,7 @@ debug@3.1.0:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
|
@ -5499,6 +5526,11 @@ detective@^5.2.1:
|
|||
defined "^1.0.0"
|
||||
minimist "^1.2.6"
|
||||
|
||||
devtools-protocol@0.0.1036444:
|
||||
version "0.0.1036444"
|
||||
resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1036444.tgz#a570d3cdde61527c82f9b03919847b8ac7b1c2b9"
|
||||
integrity sha512-0y4f/T8H9lsESV9kKP1HDUXgHxCdniFeJh6Erq+FbdOEvp/Ydp9t8kcAAM5gOd17pMrTDlFWntoHtzzeTUWKNw==
|
||||
|
||||
dicer@^0.3.0:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.1.tgz#abf28921e3475bc5e801e74e0159fd94f927ba97"
|
||||
|
@ -6239,6 +6271,17 @@ extend@^3.0.0, extend@^3.0.2, extend@~3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
extract-zip@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
|
||||
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
|
@ -6321,6 +6364,13 @@ fbjs@^3.0.0, fbjs@^3.0.1:
|
|||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.30"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
feed@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
|
||||
|
@ -6580,6 +6630,11 @@ fresh@0.5.2:
|
|||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@^10.0.1:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
|
@ -7298,6 +7353,14 @@ http-proxy@^1.18.1:
|
|||
follow-redirects "^1.0.0"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
|
||||
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
https-proxy-agent@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
|
||||
|
@ -7306,14 +7369,6 @@ https-proxy-agent@^3.0.0:
|
|||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
|
||||
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
|
@ -7336,6 +7391,11 @@ idb@7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.1.tgz#d2875b3a2f205d854ee307f6d196f246fea590a7"
|
||||
integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==
|
||||
|
||||
ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
ignore-by-default@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||
|
@ -7409,7 +7469,7 @@ inflight@^1.0.4:
|
|||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
@ -8564,6 +8624,11 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
||||
|
@ -9207,15 +9272,10 @@ path-type@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
performance-now@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
|
||||
integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
@ -9639,6 +9699,11 @@ process-nextick-args@~2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
progress@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
promise@^7.1.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||
|
@ -9661,7 +9726,7 @@ prompts@^2.4.2:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -9829,7 +9894,7 @@ proxy-agent@^3.0.3:
|
|||
proxy-from-env "^1.0.0"
|
||||
socks-proxy-agent "^4.0.1"
|
||||
|
||||
proxy-from-env@^1.0.0:
|
||||
proxy-from-env@1.1.0, proxy-from-env@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
@ -9878,6 +9943,23 @@ pupa@^2.1.1:
|
|||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
puppeteer@18.0.5:
|
||||
version "18.0.5"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-18.0.5.tgz#873223b17b92345182c5b5e8cfbd6f3117f1547d"
|
||||
integrity sha512-s4erjxU0VtKojPvF+KvLKG6OHUPw7gO2YV1dtOsoryyCbhrs444fXb4QZqGWuTv3V/rgSCUzeixxu34g0ZkSMA==
|
||||
dependencies:
|
||||
cross-fetch "3.1.5"
|
||||
debug "4.3.4"
|
||||
devtools-protocol "0.0.1036444"
|
||||
extract-zip "2.0.1"
|
||||
https-proxy-agent "5.0.1"
|
||||
progress "2.0.3"
|
||||
proxy-from-env "1.1.0"
|
||||
rimraf "3.0.2"
|
||||
tar-fs "2.1.1"
|
||||
unbzip2-stream "1.4.3"
|
||||
ws "8.8.1"
|
||||
|
||||
pure-color@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
|
||||
|
@ -9922,13 +10004,6 @@ raf-schd@^4.0.2:
|
|||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||
|
||||
raf@^3.1.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
||||
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
|
@ -10128,15 +10203,6 @@ react-masonry-css@1.0.16:
|
|||
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
|
||||
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
|
||||
|
||||
react-motion@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
|
||||
integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
|
||||
dependencies:
|
||||
performance-now "^0.2.0"
|
||||
prop-types "^15.5.8"
|
||||
raf "^3.1.0"
|
||||
|
||||
react-query@3.39.0:
|
||||
version "3.39.0"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050"
|
||||
|
@ -10274,7 +10340,7 @@ readable-stream@2, readable-stream@^2.0.1, readable-stream@~2.3.6:
|
|||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^3.0.6, readable-stream@^3.1.1:
|
||||
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
|
@ -10538,11 +10604,6 @@ requires-port@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
|
@ -11367,6 +11428,27 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
||||
|
||||
tar-fs@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-stream@^2.1.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
dependencies:
|
||||
bl "^4.0.3"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
teeny-request@^7.1.3:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.2.0.tgz#41347ece068f08d741e7b86df38a4498208b2633"
|
||||
|
@ -11404,6 +11486,11 @@ text-table@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
|
||||
|
||||
thunkify@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
|
||||
|
@ -11627,6 +11714,14 @@ unbox-primitive@^1.0.2:
|
|||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
unbzip2-stream@1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
|
||||
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
undefsafe@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||
|
@ -12234,6 +12329,11 @@ write-file-atomic@^3.0.0:
|
|||
signal-exit "^3.0.2"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
ws@8.8.1:
|
||||
version "8.8.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
|
||||
integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==
|
||||
|
||||
ws@>=7.4.6:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
|
||||
|
@ -12314,6 +12414,14 @@ yargs@^16.2.0:
|
|||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
|
|
Loading…
Reference in New Issue
Block a user