Merge branch 'main' into inga/profile
This commit is contained in:
commit
e7a552b8ca
|
@ -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 computeTotalPool = (userContracts: Contract[], startTime = 0) => {
|
||||||
const periodFilteredContracts = userContracts.filter(
|
const periodFilteredContracts = userContracts.filter(
|
||||||
(contract) => contract.createdTime >= startTime
|
(contract) => contract.createdTime >= startTime
|
||||||
|
|
|
@ -57,6 +57,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
|
||||||
uniqueBettorIds?: string[]
|
uniqueBettorIds?: string[]
|
||||||
uniqueBettorCount?: number
|
uniqueBettorCount?: number
|
||||||
popularityScore?: number
|
popularityScore?: number
|
||||||
|
dailyScore?: number
|
||||||
followerCount?: number
|
followerCount?: number
|
||||||
featuredOnHomeRank?: number
|
featuredOnHomeRank?: number
|
||||||
likedByUserIds?: string[]
|
likedByUserIds?: string[]
|
||||||
|
|
|
@ -10,6 +10,7 @@ export type Group = {
|
||||||
totalContracts: number
|
totalContracts: number
|
||||||
totalMembers: number
|
totalMembers: number
|
||||||
aboutPostId?: string
|
aboutPostId?: string
|
||||||
|
postIds: string[]
|
||||||
chatDisabled?: boolean
|
chatDisabled?: boolean
|
||||||
mostRecentContractAddedTime?: number
|
mostRecentContractAddedTime?: number
|
||||||
cachedLeaderboard?: {
|
cachedLeaderboard?: {
|
||||||
|
|
|
@ -168,7 +168,7 @@ export const getPayoutsMultiOutcome = (
|
||||||
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
|
const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
|
||||||
const profit = winnings - amount
|
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 }
|
return { userId, profit, payout }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ export type PrivateUser = {
|
||||||
|
|
||||||
email?: string
|
email?: string
|
||||||
weeklyTrendingEmailSent?: boolean
|
weeklyTrendingEmailSent?: boolean
|
||||||
|
weeklyPortfolioUpdateEmailSent?: boolean
|
||||||
manaBonusEmailSent?: boolean
|
manaBonusEmailSent?: boolean
|
||||||
initialDeviceToken?: string
|
initialDeviceToken?: string
|
||||||
initialIpAddress?: string
|
initialIpAddress?: string
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
export const MINUTE_MS = 60 * 1000
|
export const MINUTE_MS = 60 * 1000
|
||||||
export const HOUR_MS = 60 * MINUTE_MS
|
export const HOUR_MS = 60 * MINUTE_MS
|
||||||
export const DAY_MS = 24 * HOUR_MS
|
export const DAY_MS = 24 * HOUR_MS
|
||||||
|
|
||||||
|
export const sleep = (ms: number) =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|
|
@ -582,6 +582,13 @@ $ curl https://manifold.markets/api/v0/market -X POST -H 'Content-Type: applicat
|
||||||
"initialProb":25}'
|
"initialProb":25}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### `POST /v0/market/[marketId]/close`
|
||||||
|
|
||||||
|
Closes a market on behalf of the authorized user.
|
||||||
|
- `closeTime`: Optional. Milliseconds since the epoch to close the market at. If not provided, the market will be closed immediately. Cannot provide close time in past.
|
||||||
|
|
||||||
|
|
||||||
### `POST /v0/market/[marketId]/resolve`
|
### `POST /v0/market/[marketId]/resolve`
|
||||||
|
|
||||||
Resolves a market on behalf of the authorized user.
|
Resolves a market on behalf of the authorized user.
|
||||||
|
|
|
@ -8,9 +8,8 @@ A list of community-created projects built on, or related to, Manifold Markets.
|
||||||
|
|
||||||
## Sites using Manifold
|
## 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$.
|
- [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
|
## 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)
|
- [mana](https://github.com/AnnikaCodes/mana) - A Discord bot for Manifold by [@arae](https://manifold.markets/arae)
|
||||||
|
|
||||||
## Writeups
|
## Writeups
|
||||||
|
|
||||||
- [Information Markets, Decision Markets, Attention Markets, Action Markets](https://astralcodexten.substack.com/p/information-markets-decision-markets) by Scott Alexander
|
- [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
|
- [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
|
- [Introducing the Salem/CSPI Forecasting Tournament](https://www.cspicenter.com/p/introducing-the-salemcspi-forecasting) by Richard Hanania
|
||||||
|
@ -38,3 +38,10 @@ A list of community-created projects built on, or related to, Manifold Markets.
|
||||||
|
|
||||||
- Folded origami and doodles by [@hamnox](https://manifold.markets/hamnox) 
|
- Folded origami and doodles by [@hamnox](https://manifold.markets/hamnox) 
|
||||||
- Laser-cut Foldy by [@wasabipesto](https://manifold.markets/wasabipesto) 
|
- Laser-cut Foldy by [@wasabipesto](https://manifold.markets/wasabipesto) 
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
|
@ -4,11 +4,7 @@
|
||||||
|
|
||||||
### Do I have to pay real money in order to participate?
|
### Do I have to pay real money in order to participate?
|
||||||
|
|
||||||
Nope! Each account starts with a free M$1000. If you invest it wisely, you can increase your total without ever needing to put any real money into the site.
|
Nope! Each account starts with a free 1000 mana (or M$1000 for short). If you invest it wisely, you can increase your total without ever needing to put any real money into the site.
|
||||||
|
|
||||||
### What is the name for the currency Manifold uses, represented by M$?
|
|
||||||
|
|
||||||
Manifold Dollars, or mana for short.
|
|
||||||
|
|
||||||
### Can M$ be sold for real money?
|
### Can M$ be sold for real money?
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
"mailgun-js": "0.22.0",
|
"mailgun-js": "0.22.0",
|
||||||
"module-alias": "2.2.2",
|
"module-alias": "2.2.2",
|
||||||
"node-fetch": "2",
|
"node-fetch": "2",
|
||||||
"react-masonry-css": "1.0.16",
|
|
||||||
"stripe": "8.194.0",
|
"stripe": "8.194.0",
|
||||||
"zod": "3.17.2"
|
"zod": "3.17.2"
|
||||||
},
|
},
|
||||||
|
@ -48,7 +47,8 @@
|
||||||
"@types/mailgun-js": "0.22.12",
|
"@types/mailgun-js": "0.22.12",
|
||||||
"@types/module-alias": "2.0.1",
|
"@types/module-alias": "2.0.1",
|
||||||
"@types/node-fetch": "2.6.2",
|
"@types/node-fetch": "2.6.2",
|
||||||
"firebase-functions-test": "0.3.3"
|
"firebase-functions-test": "0.3.3",
|
||||||
|
"puppeteer": "18.0.5"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|
58
functions/src/close-market.ts
Normal file
58
functions/src/close-market.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { Contract } from '../../common/contract'
|
||||||
|
import { getUser } from './utils'
|
||||||
|
|
||||||
|
import { isAdmin, isManifoldId } from '../../common/envs/constants'
|
||||||
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
|
|
||||||
|
const bodySchema = z.object({
|
||||||
|
contractId: z.string(),
|
||||||
|
closeTime: z.number().int().nonnegative().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const closemarket = newEndpoint({}, async (req, auth) => {
|
||||||
|
const { contractId, closeTime } = validate(bodySchema, req.body)
|
||||||
|
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||||
|
const contractSnap = await contractDoc.get()
|
||||||
|
if (!contractSnap.exists)
|
||||||
|
throw new APIError(404, 'No contract exists with the provided ID')
|
||||||
|
const contract = contractSnap.data() as Contract
|
||||||
|
const { creatorId } = contract
|
||||||
|
const firebaseUser = await admin.auth().getUser(auth.uid)
|
||||||
|
|
||||||
|
if (
|
||||||
|
creatorId !== auth.uid &&
|
||||||
|
!isManifoldId(auth.uid) &&
|
||||||
|
!isAdmin(firebaseUser.email)
|
||||||
|
)
|
||||||
|
throw new APIError(403, 'User is not creator of contract')
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
if (!closeTime && contract.closeTime && contract.closeTime < now)
|
||||||
|
throw new APIError(400, 'Contract already closed')
|
||||||
|
|
||||||
|
if (closeTime && closeTime < now)
|
||||||
|
throw new APIError(
|
||||||
|
400,
|
||||||
|
'Close time must be in the future. ' +
|
||||||
|
'Alternatively, do not provide a close time to close immediately.'
|
||||||
|
)
|
||||||
|
|
||||||
|
const creator = await getUser(creatorId)
|
||||||
|
if (!creator) throw new APIError(500, 'Creator not found')
|
||||||
|
|
||||||
|
const updatedContract = {
|
||||||
|
...contract,
|
||||||
|
closeTime: closeTime ? closeTime : now,
|
||||||
|
}
|
||||||
|
|
||||||
|
await contractDoc.update(updatedContract)
|
||||||
|
|
||||||
|
console.log('contract ', contractId, 'closed')
|
||||||
|
|
||||||
|
return updatedContract
|
||||||
|
})
|
||||||
|
|
||||||
|
const firestore = admin.firestore()
|
|
@ -7,6 +7,7 @@ import { getNewMultiBetInfo } from '../../common/new-bet'
|
||||||
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
||||||
import { getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
import { APIError, newEndpoint, validate } from './api'
|
||||||
|
import { addUserToContractFollowers } from './follow-market'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string().max(MAX_ANSWER_LENGTH),
|
contractId: z.string().max(MAX_ANSWER_LENGTH),
|
||||||
|
@ -96,6 +97,8 @@ export const createanswer = newEndpoint(opts, async (req, auth) => {
|
||||||
return answer
|
return answer
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await addUserToContractFollowers(contractId, auth.uid)
|
||||||
|
|
||||||
return answer
|
return answer
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ export const creategroup = newEndpoint({}, async (req, auth) => {
|
||||||
anyoneCanJoin,
|
anyoneCanJoin,
|
||||||
totalContracts: 0,
|
totalContracts: 0,
|
||||||
totalMembers: memberIds.length,
|
totalMembers: memberIds.length,
|
||||||
|
postIds: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
await groupRef.create(group)
|
await groupRef.create(group)
|
||||||
|
|
|
@ -34,11 +34,12 @@ const contentSchema: z.ZodType<JSONContent> = z.lazy(() =>
|
||||||
const postSchema = z.object({
|
const postSchema = z.object({
|
||||||
title: z.string().min(1).max(MAX_POST_TITLE_LENGTH),
|
title: z.string().min(1).max(MAX_POST_TITLE_LENGTH),
|
||||||
content: contentSchema,
|
content: contentSchema,
|
||||||
|
groupId: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createpost = newEndpoint({}, async (req, auth) => {
|
export const createpost = newEndpoint({}, async (req, auth) => {
|
||||||
const firestore = admin.firestore()
|
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)
|
const creator = await getUser(auth.uid)
|
||||||
if (!creator)
|
if (!creator)
|
||||||
|
@ -60,6 +61,18 @@ export const createpost = newEndpoint({}, async (req, auth) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await postRef.create(post)
|
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 }
|
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 { formatNumericProbability } from '../../common/pseudo-numeric'
|
||||||
|
|
||||||
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
import { sendTemplateEmail, sendTextEmail } from './send-email'
|
||||||
import { getUser } from './utils'
|
import { contractUrl, getUser } from './utils'
|
||||||
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
|
import { buildCardUrl, getOpenGraphProps } from '../../common/contract-details'
|
||||||
import { notification_reason_types } from '../../common/notification'
|
import { notification_reason_types } from '../../common/notification'
|
||||||
import { Dictionary } from 'lodash'
|
import { Dictionary } from 'lodash'
|
||||||
|
import { getNotificationDestinationsForUser } from '../../common/user-notification-preferences'
|
||||||
import {
|
import {
|
||||||
getNotificationDestinationsForUser,
|
PerContractInvestmentsData,
|
||||||
notification_preference,
|
OverallPerformanceData,
|
||||||
} from '../../common/user-notification-preferences'
|
} from 'functions/src/weekly-portfolio-emails'
|
||||||
|
|
||||||
export const sendMarketResolutionEmail = async (
|
export const sendMarketResolutionEmail = async (
|
||||||
reason: notification_reason_types,
|
reason: notification_reason_types,
|
||||||
|
@ -152,9 +153,10 @@ export const sendWelcomeEmail = async (
|
||||||
const { name } = user
|
const { name } = user
|
||||||
const firstName = name.split(' ')[0]
|
const firstName = name.split(' ')[0]
|
||||||
|
|
||||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||||
'onboarding_flow' as notification_preference
|
privateUser,
|
||||||
}`
|
'onboarding_flow'
|
||||||
|
)
|
||||||
|
|
||||||
return await sendTemplateEmail(
|
return await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
|
@ -220,9 +222,11 @@ export const sendOneWeekBonusEmail = async (
|
||||||
const { name } = user
|
const { name } = user
|
||||||
const firstName = name.split(' ')[0]
|
const firstName = name.split(' ')[0]
|
||||||
|
|
||||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||||
'onboarding_flow' as notification_preference
|
privateUser,
|
||||||
}`
|
'onboarding_flow'
|
||||||
|
)
|
||||||
|
|
||||||
return await sendTemplateEmail(
|
return await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
'Manifold Markets one week anniversary gift',
|
'Manifold Markets one week anniversary gift',
|
||||||
|
@ -252,10 +256,10 @@ export const sendCreatorGuideEmail = async (
|
||||||
|
|
||||||
const { name } = user
|
const { name } = user
|
||||||
const firstName = name.split(' ')[0]
|
const firstName = name.split(' ')[0]
|
||||||
|
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
privateUser,
|
||||||
'onboarding_flow' as notification_preference
|
'onboarding_flow'
|
||||||
}`
|
)
|
||||||
return await sendTemplateEmail(
|
return await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
'Create your own prediction market',
|
'Create your own prediction market',
|
||||||
|
@ -286,10 +290,10 @@ export const sendThankYouEmail = async (
|
||||||
|
|
||||||
const { name } = user
|
const { name } = user
|
||||||
const firstName = name.split(' ')[0]
|
const firstName = name.split(' ')[0]
|
||||||
|
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
privateUser,
|
||||||
'thank_you_for_purchases' as notification_preference
|
'thank_you_for_purchases'
|
||||||
}`
|
)
|
||||||
|
|
||||||
return await sendTemplateEmail(
|
return await sendTemplateEmail(
|
||||||
privateUser.email,
|
privateUser.email,
|
||||||
|
@ -469,9 +473,10 @@ export const sendInterestingMarketsEmail = async (
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
const unsubscribeUrl = `${DOMAIN}/notifications?tab=settings§ion=${
|
const { unsubscribeUrl } = getNotificationDestinationsForUser(
|
||||||
'trending_markets' as notification_preference
|
privateUser,
|
||||||
}`
|
'trending_markets'
|
||||||
|
)
|
||||||
|
|
||||||
const { name } = user
|
const { name } = user
|
||||||
const firstName = name.split(' ')[0]
|
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) {
|
function imageSourceUrl(contract: Contract) {
|
||||||
return buildCardUrl(getOpenGraphProps(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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ export * from './reset-betting-streaks'
|
||||||
export * from './reset-weekly-emails-flag'
|
export * from './reset-weekly-emails-flag'
|
||||||
export * from './on-update-contract-follow'
|
export * from './on-update-contract-follow'
|
||||||
export * from './on-update-like'
|
export * from './on-update-like'
|
||||||
|
export * from './weekly-portfolio-emails'
|
||||||
|
|
||||||
// v2
|
// v2
|
||||||
export * from './health'
|
export * from './health'
|
||||||
|
@ -50,6 +51,7 @@ export * from './resolve-market'
|
||||||
export * from './unsubscribe'
|
export * from './unsubscribe'
|
||||||
export * from './stripe'
|
export * from './stripe'
|
||||||
export * from './mana-bonus-email'
|
export * from './mana-bonus-email'
|
||||||
|
export * from './close-market'
|
||||||
|
|
||||||
import { health } from './health'
|
import { health } from './health'
|
||||||
import { transact } from './transact'
|
import { transact } from './transact'
|
||||||
|
@ -66,6 +68,7 @@ import { addliquidity } from './add-liquidity'
|
||||||
import { withdrawliquidity } from './withdraw-liquidity'
|
import { withdrawliquidity } from './withdraw-liquidity'
|
||||||
import { creategroup } from './create-group'
|
import { creategroup } from './create-group'
|
||||||
import { resolvemarket } from './resolve-market'
|
import { resolvemarket } from './resolve-market'
|
||||||
|
import { closemarket } from './close-market'
|
||||||
import { unsubscribe } from './unsubscribe'
|
import { unsubscribe } from './unsubscribe'
|
||||||
import { stripewebhook, createcheckoutsession } from './stripe'
|
import { stripewebhook, createcheckoutsession } from './stripe'
|
||||||
import { getcurrentuser } from './get-current-user'
|
import { getcurrentuser } from './get-current-user'
|
||||||
|
@ -91,6 +94,7 @@ const addLiquidityFunction = toCloudFunction(addliquidity)
|
||||||
const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity)
|
const withdrawLiquidityFunction = toCloudFunction(withdrawliquidity)
|
||||||
const createGroupFunction = toCloudFunction(creategroup)
|
const createGroupFunction = toCloudFunction(creategroup)
|
||||||
const resolveMarketFunction = toCloudFunction(resolvemarket)
|
const resolveMarketFunction = toCloudFunction(resolvemarket)
|
||||||
|
const closeMarketFunction = toCloudFunction(closemarket)
|
||||||
const unsubscribeFunction = toCloudFunction(unsubscribe)
|
const unsubscribeFunction = toCloudFunction(unsubscribe)
|
||||||
const stripeWebhookFunction = toCloudFunction(stripewebhook)
|
const stripeWebhookFunction = toCloudFunction(stripewebhook)
|
||||||
const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession)
|
const createCheckoutSessionFunction = toCloudFunction(createcheckoutsession)
|
||||||
|
@ -115,11 +119,12 @@ export {
|
||||||
withdrawLiquidityFunction as withdrawliquidity,
|
withdrawLiquidityFunction as withdrawliquidity,
|
||||||
createGroupFunction as creategroup,
|
createGroupFunction as creategroup,
|
||||||
resolveMarketFunction as resolvemarket,
|
resolveMarketFunction as resolvemarket,
|
||||||
|
closeMarketFunction as closemarket,
|
||||||
unsubscribeFunction as unsubscribe,
|
unsubscribeFunction as unsubscribe,
|
||||||
stripeWebhookFunction as stripewebhook,
|
stripeWebhookFunction as stripewebhook,
|
||||||
createCheckoutSessionFunction as createcheckoutsession,
|
createCheckoutSessionFunction as createcheckoutsession,
|
||||||
getCurrentUserFunction as getcurrentuser,
|
getCurrentUserFunction as getcurrentuser,
|
||||||
acceptChallenge as acceptchallenge,
|
acceptChallenge as acceptchallenge,
|
||||||
createPostFunction as createpost,
|
createPostFunction as createpost,
|
||||||
saveTwitchCredentials as savetwitchcredentials
|
saveTwitchCredentials as savetwitchcredentials,
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ export const resetWeeklyEmailsFlag = functions
|
||||||
privateUsers.map(async (user) => {
|
privateUsers.map(async (user) => {
|
||||||
return firestore.collection('private-users').doc(user.id).update({
|
return firestore.collection('private-users').doc(user.id).update({
|
||||||
weeklyTrendingEmailSent: false,
|
weeklyTrendingEmailSent: false,
|
||||||
|
weeklyPortfolioEmailSent: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { Bet } from 'common/bet'
|
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { Contract } from 'common/contract'
|
import { Bet } from '../../common/bet'
|
||||||
|
import { Contract } from '../../common/contract'
|
||||||
import { log } from './utils'
|
import { log } from './utils'
|
||||||
|
import { removeUndefinedProps } from '../../common/util/object'
|
||||||
|
import { DAY_MS, HOUR_MS } from '../../common/util/time'
|
||||||
|
|
||||||
export const scoreContracts = functions.pubsub
|
export const scoreContracts = functions
|
||||||
.schedule('every 1 hours')
|
.runWith({ memory: '4GB', timeoutSeconds: 540 })
|
||||||
|
.pubsub.schedule('every 1 hours')
|
||||||
.onRun(async () => {
|
.onRun(async () => {
|
||||||
await scoreContractsInternal()
|
await scoreContractsInternal()
|
||||||
})
|
})
|
||||||
|
@ -14,11 +17,12 @@ const firestore = admin.firestore()
|
||||||
|
|
||||||
async function scoreContractsInternal() {
|
async function scoreContractsInternal() {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const lastHour = now - 60 * 60 * 1000
|
const hourAgo = now - HOUR_MS
|
||||||
const last3Days = now - 1000 * 60 * 60 * 24 * 3
|
const dayAgo = now - DAY_MS
|
||||||
|
const threeDaysAgo = now - DAY_MS * 3
|
||||||
const activeContractsSnap = await firestore
|
const activeContractsSnap = await firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
.where('lastUpdatedTime', '>', lastHour)
|
.where('lastUpdatedTime', '>', hourAgo)
|
||||||
.get()
|
.get()
|
||||||
const activeContracts = activeContractsSnap.docs.map(
|
const activeContracts = activeContractsSnap.docs.map(
|
||||||
(doc) => doc.data() as Contract
|
(doc) => doc.data() as Contract
|
||||||
|
@ -39,16 +43,33 @@ async function scoreContractsInternal() {
|
||||||
for (const contract of contracts) {
|
for (const contract of contracts) {
|
||||||
const bets = await firestore
|
const bets = await firestore
|
||||||
.collection(`contracts/${contract.id}/bets`)
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
.where('createdTime', '>', last3Days)
|
.where('createdTime', '>', threeDaysAgo)
|
||||||
.get()
|
.get()
|
||||||
const bettors = bets.docs
|
const bettors = bets.docs
|
||||||
.map((doc) => doc.data() as Bet)
|
.map((doc) => doc.data() as Bet)
|
||||||
.map((bet) => bet.userId)
|
.map((bet) => bet.userId)
|
||||||
const score = uniq(bettors).length
|
const popularityScore = uniq(bettors).length
|
||||||
if (contract.popularityScore !== score)
|
|
||||||
|
const wasCreatedToday = contract.createdTime > dayAgo
|
||||||
|
|
||||||
|
let dailyScore: number | undefined
|
||||||
|
if (
|
||||||
|
contract.outcomeType === 'BINARY' &&
|
||||||
|
contract.mechanism === 'cpmm-1' &&
|
||||||
|
!wasCreatedToday
|
||||||
|
) {
|
||||||
|
const percentChange = Math.abs(contract.probChanges.day)
|
||||||
|
dailyScore = popularityScore * percentChange
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
contract.popularityScore !== popularityScore ||
|
||||||
|
contract.dailyScore !== dailyScore
|
||||||
|
) {
|
||||||
await firestore
|
await firestore
|
||||||
.collection('contracts')
|
.collection('contracts')
|
||||||
.doc(contract.id)
|
.doc(contract.id)
|
||||||
.update({ popularityScore: score })
|
.update(removeUndefinedProps({ popularityScore, dailyScore }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
anyoneCanJoin: true,
|
||||||
totalContracts: contracts.length,
|
totalContracts: contracts.length,
|
||||||
totalMembers: 1,
|
totalMembers: 1,
|
||||||
|
postIds: [],
|
||||||
}
|
}
|
||||||
await groupRef.create(group)
|
await groupRef.create(group)
|
||||||
// create a GroupMemberDoc for the creator
|
// create a GroupMemberDoc for the creator
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { stripewebhook, createcheckoutsession } from './stripe'
|
||||||
import { getcurrentuser } from './get-current-user'
|
import { getcurrentuser } from './get-current-user'
|
||||||
import { createpost } from './create-post'
|
import { createpost } from './create-post'
|
||||||
import { savetwitchcredentials } from './save-twitch-credentials'
|
import { savetwitchcredentials } from './save-twitch-credentials'
|
||||||
|
import { testscheduledfunction } from './test-scheduled-function'
|
||||||
|
|
||||||
type Middleware = (req: Request, res: Response, next: NextFunction) => void
|
type Middleware = (req: Request, res: Response, next: NextFunction) => void
|
||||||
const app = express()
|
const app = express()
|
||||||
|
@ -69,6 +70,7 @@ addJsonEndpointRoute('/getcurrentuser', getcurrentuser)
|
||||||
addJsonEndpointRoute('/savetwitchcredentials', savetwitchcredentials)
|
addJsonEndpointRoute('/savetwitchcredentials', savetwitchcredentials)
|
||||||
addEndpointRoute('/stripewebhook', stripewebhook, express.raw())
|
addEndpointRoute('/stripewebhook', stripewebhook, express.raw())
|
||||||
addEndpointRoute('/createpost', createpost)
|
addEndpointRoute('/createpost', createpost)
|
||||||
|
addEndpointRoute('/testscheduledfunction', testscheduledfunction)
|
||||||
|
|
||||||
app.listen(PORT)
|
app.listen(PORT)
|
||||||
console.log(`Serving functions on port ${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) => {
|
export const getContractPath = (contract: Contract) => {
|
||||||
return `/${contract.creatorUsername}/${contract.slug}`
|
return `/${contract.creatorUsername}/${contract.slug}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function contractUrl(contract: Contract) {
|
||||||
|
return `https://manifold.markets/${contract.creatorUsername}/${contract.slug}`
|
||||||
|
}
|
||||||
|
|
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
|
||||||
|
}
|
|
@ -30,10 +30,10 @@ export function AddFundsButton(props: { className?: string }) {
|
||||||
|
|
||||||
<div className="modal">
|
<div className="modal">
|
||||||
<div className="modal-box">
|
<div className="modal-box">
|
||||||
<div className="mb-6 text-xl">Get Manifold Dollars</div>
|
<div className="mb-6 text-xl">Get Mana</div>
|
||||||
|
|
||||||
<div className="mb-6 text-gray-500">
|
<div className="mb-6 text-gray-500">
|
||||||
Use Manifold Dollars to trade in your favorite markets. <br /> (Not
|
Buy mana (M$) to trade in your favorite markets. <br /> (Not
|
||||||
redeemable for cash.)
|
redeemable for cash.)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { formatMoney } from 'common/util/format'
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import { ENV_CONFIG } from 'common/envs/constants'
|
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: {
|
export function AmountInput(props: {
|
||||||
amount: number | undefined
|
amount: number | undefined
|
||||||
|
@ -34,8 +34,7 @@ export function AmountInput(props: {
|
||||||
const isInvalid = !str || isNaN(amount)
|
const isInvalid = !str || isNaN(amount)
|
||||||
onChange(isInvalid ? undefined : amount)
|
onChange(isInvalid ? undefined : amount)
|
||||||
}
|
}
|
||||||
const { width } = useWindowSize()
|
const isMobile = useIsMobile(768)
|
||||||
const isMobile = (width ?? 0) < 768
|
|
||||||
return (
|
return (
|
||||||
<Col className={className}>
|
<Col className={className}>
|
||||||
<label className="input-group mb-4">
|
<label className="input-group mb-4">
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { setCookie } from 'web/lib/util/cookie'
|
||||||
|
|
||||||
// Either we haven't looked up the logged in user yet (undefined), or we know
|
// Either we haven't looked up the logged in user yet (undefined), or we know
|
||||||
// the user is not logged in (null), or we know the user is logged in.
|
// the user is not logged in (null), or we know the user is logged in.
|
||||||
type AuthUser = undefined | null | UserAndPrivateUser
|
export type AuthUser = undefined | null | UserAndPrivateUser
|
||||||
|
|
||||||
const TEN_YEARS_SECS = 60 * 60 * 24 * 365 * 10
|
const TEN_YEARS_SECS = 60 * 60 * 24 * 365 * 10
|
||||||
const CACHED_USER_KEY = 'CACHED_USER_KEY_V2'
|
const CACHED_USER_KEY = 'CACHED_USER_KEY_V2'
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function Avatar(props: {
|
||||||
style={{ maxWidth: `${s * 0.25}rem` }}
|
style={{ maxWidth: `${s * 0.25}rem` }}
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
alt={username}
|
alt={`${username ?? 'Unknown user'} avatar`}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
// If the image doesn't load, clear the avatarUrl to show the default
|
// If the image doesn't load, clear the avatarUrl to show the default
|
||||||
// Mostly for localhost, when getting a 403 from googleusercontent
|
// Mostly for localhost, when getting a 403 from googleusercontent
|
||||||
|
|
|
@ -601,18 +601,24 @@ function BetRow(props: {
|
||||||
const isNumeric = outcomeType === 'NUMERIC'
|
const isNumeric = outcomeType === 'NUMERIC'
|
||||||
const isPseudoNumeric = outcomeType === 'PSEUDO_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 ? (
|
const saleDisplay = isAnte ? (
|
||||||
'ANTE'
|
'ANTE'
|
||||||
) : saleAmount !== undefined ? (
|
) : saleBet ? (
|
||||||
<>{formatMoney(saleAmount)} (sold)</>
|
<>{formatMoney(payout)} (sold)</>
|
||||||
) : (
|
) : (
|
||||||
formatMoney(
|
formatMoney(payout)
|
||||||
isResolved
|
|
||||||
? resolvedPayout(contract, bet)
|
|
||||||
: calculateSaleAmount(contract, bet, unfilledBets)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const payoutIfChosenDisplay =
|
const payoutIfChosenDisplay =
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Row } from './layout/row'
|
||||||
import { LoadingIndicator } from './loading-indicator'
|
import { LoadingIndicator } from './loading-indicator'
|
||||||
|
|
||||||
export function CommentInput(props: {
|
export function CommentInput(props: {
|
||||||
replyToUser?: { id: string; username: string }
|
replyTo?: { id: string; username: string }
|
||||||
// Reply to a free response answer
|
// Reply to a free response answer
|
||||||
parentAnswerOutcome?: string
|
parentAnswerOutcome?: string
|
||||||
// Reply to another comment
|
// Reply to another comment
|
||||||
|
@ -19,7 +19,7 @@ export function CommentInput(props: {
|
||||||
onSubmitComment?: (editor: Editor) => void
|
onSubmitComment?: (editor: Editor) => void
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { parentAnswerOutcome, parentCommentId, replyToUser, onSubmitComment } =
|
const { parentAnswerOutcome, parentCommentId, replyTo, onSubmitComment } =
|
||||||
props
|
props
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ export function CommentInput(props: {
|
||||||
<CommentInputTextArea
|
<CommentInputTextArea
|
||||||
editor={editor}
|
editor={editor}
|
||||||
upload={upload}
|
upload={upload}
|
||||||
replyToUser={replyToUser}
|
replyTo={replyTo}
|
||||||
user={user}
|
user={user}
|
||||||
submitComment={submitComment}
|
submitComment={submitComment}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
|
@ -67,14 +67,13 @@ export function CommentInput(props: {
|
||||||
|
|
||||||
export function CommentInputTextArea(props: {
|
export function CommentInputTextArea(props: {
|
||||||
user: User | undefined | null
|
user: User | undefined | null
|
||||||
replyToUser?: { id: string; username: string }
|
replyTo?: { id: string; username: string }
|
||||||
editor: Editor | null
|
editor: Editor | null
|
||||||
upload: Parameters<typeof TextEditor>[0]['upload']
|
upload: Parameters<typeof TextEditor>[0]['upload']
|
||||||
submitComment: () => void
|
submitComment: () => void
|
||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
}) {
|
}) {
|
||||||
const { user, editor, upload, submitComment, isSubmitting, replyToUser } =
|
const { user, editor, upload, submitComment, isSubmitting, replyTo } = props
|
||||||
props
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor?.setEditable(!isSubmitting)
|
editor?.setEditable(!isSubmitting)
|
||||||
}, [isSubmitting, editor])
|
}, [isSubmitting, editor])
|
||||||
|
@ -108,12 +107,12 @@ export function CommentInputTextArea(props: {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// insert at mention and focus
|
// insert at mention and focus
|
||||||
if (replyToUser) {
|
if (replyTo) {
|
||||||
editor
|
editor
|
||||||
.chain()
|
.chain()
|
||||||
.setContent({
|
.setContent({
|
||||||
type: 'mention',
|
type: 'mention',
|
||||||
attrs: { label: replyToUser.username, id: replyToUser.id },
|
attrs: { label: replyTo.username, id: replyTo.id },
|
||||||
})
|
})
|
||||||
.insertContent(' ')
|
.insertContent(' ')
|
||||||
.focus()
|
.focus()
|
||||||
|
@ -127,7 +126,7 @@ export function CommentInputTextArea(props: {
|
||||||
<TextEditor editor={editor} upload={upload}>
|
<TextEditor editor={editor} upload={upload}>
|
||||||
{user && !isSubmitting && (
|
{user && !isSubmitting && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-ghost btn-sm px-2 disabled:bg-inherit disabled:text-gray-300"
|
className="btn btn-ghost btn-sm disabled:bg-inherit! px-2 disabled:text-gray-300"
|
||||||
disabled={!editor || editor.isEmpty}
|
disabled={!editor || editor.isEmpty}
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
>
|
>
|
||||||
|
|
|
@ -48,18 +48,17 @@ import { Title } from './title'
|
||||||
export const SORTS = [
|
export const SORTS = [
|
||||||
{ label: 'Newest', value: 'newest' },
|
{ label: 'Newest', value: 'newest' },
|
||||||
{ label: 'Trending', value: 'score' },
|
{ label: 'Trending', value: 'score' },
|
||||||
{ label: `Most traded`, value: 'most-traded' },
|
{ label: 'Daily trending', value: 'daily-score' },
|
||||||
{ label: '24h volume', value: '24-hour-vol' },
|
{ label: '24h volume', value: '24-hour-vol' },
|
||||||
{ label: '24h change', value: 'prob-change-day' },
|
|
||||||
{ label: 'Last updated', value: 'last-updated' },
|
{ label: 'Last updated', value: 'last-updated' },
|
||||||
{ label: 'Subsidy', value: 'liquidity' },
|
{ label: 'Closing soon', value: 'close-date' },
|
||||||
{ label: 'Close date', value: 'close-date' },
|
|
||||||
{ label: 'Resolve date', value: 'resolve-date' },
|
{ label: 'Resolve date', value: 'resolve-date' },
|
||||||
{ label: 'Highest %', value: 'prob-descending' },
|
{ label: 'Highest %', value: 'prob-descending' },
|
||||||
{ label: 'Lowest %', value: 'prob-ascending' },
|
{ label: 'Lowest %', value: 'prob-ascending' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type Sort = typeof SORTS[number]['value']
|
export type Sort = typeof SORTS[number]['value']
|
||||||
|
export const PROB_SORTS = ['prob-descending', 'prob-ascending']
|
||||||
|
|
||||||
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
type filter = 'personal' | 'open' | 'closed' | 'resolved' | 'all'
|
||||||
|
|
||||||
|
@ -90,11 +89,13 @@ export function ContractSearch(props: {
|
||||||
hideGroupLink?: boolean
|
hideGroupLink?: boolean
|
||||||
hideQuickBet?: boolean
|
hideQuickBet?: boolean
|
||||||
noLinkAvatar?: boolean
|
noLinkAvatar?: boolean
|
||||||
|
showProbChange?: boolean
|
||||||
}
|
}
|
||||||
headerClassName?: string
|
headerClassName?: string
|
||||||
persistPrefix?: string
|
persistPrefix?: string
|
||||||
useQueryUrlParam?: boolean
|
useQueryUrlParam?: boolean
|
||||||
isWholePage?: boolean
|
isWholePage?: boolean
|
||||||
|
includeProbSorts?: boolean
|
||||||
noControls?: boolean
|
noControls?: boolean
|
||||||
maxResults?: number
|
maxResults?: number
|
||||||
renderContracts?: (
|
renderContracts?: (
|
||||||
|
@ -117,6 +118,7 @@ export function ContractSearch(props: {
|
||||||
headerClassName,
|
headerClassName,
|
||||||
persistPrefix,
|
persistPrefix,
|
||||||
useQueryUrlParam,
|
useQueryUrlParam,
|
||||||
|
includeProbSorts,
|
||||||
isWholePage,
|
isWholePage,
|
||||||
noControls,
|
noControls,
|
||||||
maxResults,
|
maxResults,
|
||||||
|
@ -130,6 +132,7 @@ export function ContractSearch(props: {
|
||||||
numPages: 1,
|
numPages: 1,
|
||||||
pages: [] as Contract[][],
|
pages: [] as Contract[][],
|
||||||
showTime: null as ShowTime | null,
|
showTime: null as ShowTime | null,
|
||||||
|
showProbChange: false,
|
||||||
},
|
},
|
||||||
!persistPrefix
|
!persistPrefix
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -183,8 +186,9 @@ export function ContractSearch(props: {
|
||||||
const newPage = results.hits as any as Contract[]
|
const newPage = results.hits as any as Contract[]
|
||||||
const showTime =
|
const showTime =
|
||||||
sort === 'close-date' || sort === 'resolve-date' ? sort : null
|
sort === 'close-date' || sort === 'resolve-date' ? sort : null
|
||||||
|
const showProbChange = sort === 'daily-score'
|
||||||
const pages = freshQuery ? [newPage] : [...state.pages, newPage]
|
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)
|
if (freshQuery && isWholePage) window.scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +206,12 @@ export function ContractSearch(props: {
|
||||||
}, 100)
|
}, 100)
|
||||||
).current
|
).current
|
||||||
|
|
||||||
|
const updatedCardUIOptions = useMemo(() => {
|
||||||
|
if (cardUIOptions?.showProbChange === undefined && state.showProbChange)
|
||||||
|
return { ...cardUIOptions, showProbChange: true }
|
||||||
|
return cardUIOptions
|
||||||
|
}, [cardUIOptions, state.showProbChange])
|
||||||
|
|
||||||
const contracts = state.pages
|
const contracts = state.pages
|
||||||
.flat()
|
.flat()
|
||||||
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
.filter((c) => !additionalFilter?.excludeContractIds?.includes(c.id))
|
||||||
|
@ -223,6 +233,7 @@ export function ContractSearch(props: {
|
||||||
persistPrefix={persistPrefix}
|
persistPrefix={persistPrefix}
|
||||||
hideOrderSelector={hideOrderSelector}
|
hideOrderSelector={hideOrderSelector}
|
||||||
useQueryUrlParam={useQueryUrlParam}
|
useQueryUrlParam={useQueryUrlParam}
|
||||||
|
includeProbSorts={includeProbSorts}
|
||||||
user={user}
|
user={user}
|
||||||
onSearchParametersChanged={onSearchParametersChanged}
|
onSearchParametersChanged={onSearchParametersChanged}
|
||||||
noControls={noControls}
|
noControls={noControls}
|
||||||
|
@ -241,7 +252,7 @@ export function ContractSearch(props: {
|
||||||
showTime={state.showTime ?? undefined}
|
showTime={state.showTime ?? undefined}
|
||||||
onContractClick={onContractClick}
|
onContractClick={onContractClick}
|
||||||
highlightOptions={highlightOptions}
|
highlightOptions={highlightOptions}
|
||||||
cardUIOptions={cardUIOptions}
|
cardUIOptions={updatedCardUIOptions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -256,6 +267,7 @@ function ContractSearchControls(props: {
|
||||||
additionalFilter?: AdditionalFilter
|
additionalFilter?: AdditionalFilter
|
||||||
persistPrefix?: string
|
persistPrefix?: string
|
||||||
hideOrderSelector?: boolean
|
hideOrderSelector?: boolean
|
||||||
|
includeProbSorts?: boolean
|
||||||
onSearchParametersChanged: (params: SearchParameters) => void
|
onSearchParametersChanged: (params: SearchParameters) => void
|
||||||
useQueryUrlParam?: boolean
|
useQueryUrlParam?: boolean
|
||||||
user?: User | null
|
user?: User | null
|
||||||
|
@ -275,6 +287,7 @@ function ContractSearchControls(props: {
|
||||||
user,
|
user,
|
||||||
noControls,
|
noControls,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
|
includeProbSorts,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -443,6 +456,7 @@ function ContractSearchControls(props: {
|
||||||
selectSort={selectSort}
|
selectSort={selectSort}
|
||||||
sort={sort}
|
sort={sort}
|
||||||
className={'flex flex-row gap-2'}
|
className={'flex flex-row gap-2'}
|
||||||
|
includeProbSorts={includeProbSorts}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
@ -456,6 +470,7 @@ function ContractSearchControls(props: {
|
||||||
selectSort={selectSort}
|
selectSort={selectSort}
|
||||||
sort={sort}
|
sort={sort}
|
||||||
className={'flex flex-col gap-4'}
|
className={'flex flex-col gap-4'}
|
||||||
|
includeProbSorts={includeProbSorts}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -510,6 +525,7 @@ export function SearchFilters(props: {
|
||||||
selectSort: (newSort: Sort) => void
|
selectSort: (newSort: Sort) => void
|
||||||
sort: string
|
sort: string
|
||||||
className?: string
|
className?: string
|
||||||
|
includeProbSorts?: boolean
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
filter,
|
filter,
|
||||||
|
@ -518,7 +534,13 @@ export function SearchFilters(props: {
|
||||||
selectSort,
|
selectSort,
|
||||||
sort,
|
sort,
|
||||||
className,
|
className,
|
||||||
|
includeProbSorts,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
|
const sorts = includeProbSorts
|
||||||
|
? SORTS
|
||||||
|
: SORTS.filter((sort) => !PROB_SORTS.includes(sort.value))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<select
|
<select
|
||||||
|
@ -537,7 +559,7 @@ export function SearchFilters(props: {
|
||||||
value={sort}
|
value={sort}
|
||||||
onChange={(e) => selectSort(e.target.value as Sort)}
|
onChange={(e) => selectSort(e.target.value as Sort)}
|
||||||
>
|
>
|
||||||
{SORTS.map((option) => (
|
{sorts.map((option) => (
|
||||||
<option key={option.value} value={option.value}>
|
<option key={option.value} value={option.value}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Col } from '../layout/col'
|
||||||
import {
|
import {
|
||||||
BinaryContract,
|
BinaryContract,
|
||||||
Contract,
|
Contract,
|
||||||
|
CPMMBinaryContract,
|
||||||
FreeResponseContract,
|
FreeResponseContract,
|
||||||
MultipleChoiceContract,
|
MultipleChoiceContract,
|
||||||
NumericContract,
|
NumericContract,
|
||||||
|
@ -32,6 +33,8 @@ import { track } from '@amplitude/analytics-browser'
|
||||||
import { trackCallback } from 'web/lib/service/analytics'
|
import { trackCallback } from 'web/lib/service/analytics'
|
||||||
import { getMappedValue } from 'common/pseudo-numeric'
|
import { getMappedValue } from 'common/pseudo-numeric'
|
||||||
import { Tooltip } from '../tooltip'
|
import { Tooltip } from '../tooltip'
|
||||||
|
import { SiteLink } from '../site-link'
|
||||||
|
import { ProbChange } from './prob-change-table'
|
||||||
|
|
||||||
export function ContractCard(props: {
|
export function ContractCard(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -379,3 +382,34 @@ export function PseudoNumericResolutionOrExpectation(props: {
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ContractCardProbChange(props: {
|
||||||
|
contract: CPMMBinaryContract
|
||||||
|
noLinkAvatar?: boolean
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const { contract, noLinkAvatar, className } = props
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
'mb-4 rounded-lg bg-white shadow hover:bg-gray-100 hover:shadow-lg'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AvatarDetails
|
||||||
|
contract={contract}
|
||||||
|
className={'px-6 pt-4'}
|
||||||
|
noLink={noLinkAvatar}
|
||||||
|
/>
|
||||||
|
<Row className={clsx('items-start justify-between gap-4 ', className)}>
|
||||||
|
<SiteLink
|
||||||
|
className="pl-6 pr-0 pt-2 pb-4 font-semibold text-indigo-700"
|
||||||
|
href={contractPath(contract)}
|
||||||
|
>
|
||||||
|
<span className="line-clamp-3">{contract.question}</span>
|
||||||
|
</SiteLink>
|
||||||
|
<ProbChange className="py-2 pr-4" contract={contract} />
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@ import { resolvedPayout } from 'common/calculate'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash'
|
import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash'
|
||||||
import { useState, useMemo, useEffect } from 'react'
|
import { memo } from 'react'
|
||||||
import { useComments } from 'web/hooks/use-comments'
|
import { useComments } from 'web/hooks/use-comments'
|
||||||
import { listUsers, User } from 'web/lib/firebase/users'
|
|
||||||
import { FeedBet } from '../feed/feed-bets'
|
import { FeedBet } from '../feed/feed-bets'
|
||||||
import { FeedComment } from '../feed/feed-comments'
|
import { FeedComment } from '../feed/feed-comments'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
|
@ -13,59 +12,48 @@ import { Leaderboard } from '../leaderboard'
|
||||||
import { Title } from '../title'
|
import { Title } from '../title'
|
||||||
import { BETTORS } from 'common/user'
|
import { BETTORS } from 'common/user'
|
||||||
|
|
||||||
export function ContractLeaderboard(props: {
|
export const ContractLeaderboard = memo(function ContractLeaderboard(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bets: Bet[]
|
bets: Bet[]
|
||||||
}) {
|
}) {
|
||||||
const { contract, bets } = props
|
const { contract, bets } = props
|
||||||
const [users, setUsers] = useState<User[]>()
|
|
||||||
|
|
||||||
const { userProfits, top5Ids } = useMemo(() => {
|
// Create a map of userIds to total profits (including sales)
|
||||||
// Create a map of userIds to total profits (including sales)
|
const openBets = bets.filter((bet) => !bet.isSold && !bet.sale)
|
||||||
const openBets = bets.filter((bet) => !bet.isSold && !bet.sale)
|
const betsByUser = groupBy(openBets, 'userId')
|
||||||
const betsByUser = groupBy(openBets, 'userId')
|
const userProfits = mapValues(betsByUser, (bets) => {
|
||||||
|
return {
|
||||||
const userProfits = mapValues(betsByUser, (bets) =>
|
name: bets[0].userName,
|
||||||
sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount)
|
username: bets[0].userUsername,
|
||||||
)
|
avatarUrl: bets[0].userAvatarUrl,
|
||||||
// Find the 5 users with the most profits
|
total: sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount),
|
||||||
const top5Ids = Object.entries(userProfits)
|
|
||||||
.sort(([_i1, p1], [_i2, p2]) => p2 - p1)
|
|
||||||
.filter(([, p]) => p > 0)
|
|
||||||
.slice(0, 5)
|
|
||||||
.map(([id]) => id)
|
|
||||||
return { userProfits, top5Ids }
|
|
||||||
}, [contract, bets])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (top5Ids.length > 0) {
|
|
||||||
listUsers(top5Ids).then((users) => {
|
|
||||||
const sortedUsers = sortBy(users, (user) => -userProfits[user.id])
|
|
||||||
setUsers(sortedUsers)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [userProfits, top5Ids])
|
})
|
||||||
|
// Find the 5 users with the most profits
|
||||||
|
const top5 = Object.values(userProfits)
|
||||||
|
.sort((p1, p2) => p2.total - p1.total)
|
||||||
|
.filter((p) => p.total > 0)
|
||||||
|
.slice(0, 5)
|
||||||
|
|
||||||
return users && users.length > 0 ? (
|
return top5 && top5.length > 0 ? (
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title={`🏅 Top ${BETTORS}`}
|
title={`🏅 Top ${BETTORS}`}
|
||||||
users={users || []}
|
entries={top5 || []}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Total profit',
|
header: 'Total profit',
|
||||||
renderCell: (user) => formatMoney(userProfits[user.id] || 0),
|
renderCell: (entry) => formatMoney(entry.total),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
className="mt-12 max-w-sm"
|
className="mt-12 max-w-sm"
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
})
|
||||||
|
|
||||||
export function ContractTopTrades(props: { contract: Contract; bets: Bet[] }) {
|
export function ContractTopTrades(props: { contract: Contract; bets: Bet[] }) {
|
||||||
const { contract, bets } = props
|
const { contract, bets } = props
|
||||||
// todo: this stuff should be calced in DB at resolve time
|
// todo: this stuff should be calced in DB at resolve time
|
||||||
const comments = useComments(contract.id)
|
const comments = useComments(contract.id)
|
||||||
const commentsById = keyBy(comments, 'id')
|
|
||||||
const betsById = keyBy(bets, 'id')
|
const betsById = keyBy(bets, 'id')
|
||||||
|
|
||||||
// If 'id2' is the sale of 'id1', both are logged with (id2 - id1) of profit
|
// If 'id2' is the sale of 'id1', both are logged with (id2 - id1) of profit
|
||||||
|
@ -86,29 +74,23 @@ export function ContractTopTrades(props: { contract: Contract; bets: Bet[] }) {
|
||||||
const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id
|
const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id
|
||||||
const topBettor = betsById[topBetId]?.userName
|
const topBettor = betsById[topBetId]?.userName
|
||||||
|
|
||||||
// And also the commentId of the comment with the highest profit
|
// And also the comment with the highest profit
|
||||||
const topCommentId = sortBy(
|
const topComment = sortBy(comments, (c) => c.betId && -profitById[c.betId])[0]
|
||||||
comments,
|
|
||||||
(c) => c.betId && -profitById[c.betId]
|
|
||||||
)[0]?.id
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-12 max-w-sm">
|
<div className="mt-12 max-w-sm">
|
||||||
{topCommentId && profitById[topCommentId] > 0 && (
|
{topComment && profitById[topComment.id] > 0 && (
|
||||||
<>
|
<>
|
||||||
<Title text="💬 Proven correct" className="!mt-0" />
|
<Title text="💬 Proven correct" className="!mt-0" />
|
||||||
<div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4">
|
<div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4">
|
||||||
<FeedComment
|
<FeedComment contract={contract} comment={topComment} />
|
||||||
contract={contract}
|
|
||||||
comment={commentsById[topCommentId]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Spacer h={16} />
|
<Spacer h={16} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* If they're the same, only show the comment; otherwise show both */}
|
{/* If they're the same, only show the comment; otherwise show both */}
|
||||||
{topBettor && topBetId !== topCommentId && profitById[topBetId] > 0 && (
|
{topBettor && topBetId !== topComment?.betId && profitById[topBetId] > 0 && (
|
||||||
<>
|
<>
|
||||||
<Title text="💸 Best bet" className="!mt-0" />
|
<Title text="💸 Best bet" className="!mt-0" />
|
||||||
<div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4">
|
<div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4">
|
||||||
|
|
|
@ -49,6 +49,7 @@ export function ContractTabs(props: { contract: Contract; bets: Bet[] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
currentPageForAnalytics={'contract'}
|
currentPageForAnalytics={'contract'}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Contract } from 'web/lib/firebase/contracts'
|
||||||
import { User } from 'web/lib/firebase/users'
|
import { User } from 'web/lib/firebase/users'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { ContractCard } from './contract-card'
|
import { ContractCard, ContractCardProbChange } from './contract-card'
|
||||||
import { ShowTime } from './contract-details'
|
import { ShowTime } from './contract-details'
|
||||||
import { ContractSearch } from '../contract-search'
|
import { ContractSearch } from '../contract-search'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
@ -10,6 +10,7 @@ import clsx from 'clsx'
|
||||||
import { LoadingIndicator } from '../loading-indicator'
|
import { LoadingIndicator } from '../loading-indicator'
|
||||||
import { VisibilityObserver } from '../visibility-observer'
|
import { VisibilityObserver } from '../visibility-observer'
|
||||||
import Masonry from 'react-masonry-css'
|
import Masonry from 'react-masonry-css'
|
||||||
|
import { CPMMBinaryContract } from 'common/contract'
|
||||||
|
|
||||||
export type ContractHighlightOptions = {
|
export type ContractHighlightOptions = {
|
||||||
contractIds?: string[]
|
contractIds?: string[]
|
||||||
|
@ -25,6 +26,7 @@ export function ContractsGrid(props: {
|
||||||
hideQuickBet?: boolean
|
hideQuickBet?: boolean
|
||||||
hideGroupLink?: boolean
|
hideGroupLink?: boolean
|
||||||
noLinkAvatar?: boolean
|
noLinkAvatar?: boolean
|
||||||
|
showProbChange?: boolean
|
||||||
}
|
}
|
||||||
highlightOptions?: ContractHighlightOptions
|
highlightOptions?: ContractHighlightOptions
|
||||||
trackingPostfix?: string
|
trackingPostfix?: string
|
||||||
|
@ -39,7 +41,8 @@ export function ContractsGrid(props: {
|
||||||
highlightOptions,
|
highlightOptions,
|
||||||
trackingPostfix,
|
trackingPostfix,
|
||||||
} = props
|
} = props
|
||||||
const { hideQuickBet, hideGroupLink, noLinkAvatar } = cardUIOptions || {}
|
const { hideQuickBet, hideGroupLink, noLinkAvatar, showProbChange } =
|
||||||
|
cardUIOptions || {}
|
||||||
const { contractIds, highlightClassName } = highlightOptions || {}
|
const { contractIds, highlightClassName } = highlightOptions || {}
|
||||||
const onVisibilityUpdated = useCallback(
|
const onVisibilityUpdated = useCallback(
|
||||||
(visible) => {
|
(visible) => {
|
||||||
|
@ -73,24 +76,31 @@ export function ContractsGrid(props: {
|
||||||
className="-ml-4 flex w-auto"
|
className="-ml-4 flex w-auto"
|
||||||
columnClassName="pl-4 bg-clip-padding"
|
columnClassName="pl-4 bg-clip-padding"
|
||||||
>
|
>
|
||||||
{contracts.map((contract) => (
|
{contracts.map((contract) =>
|
||||||
<ContractCard
|
showProbChange && contract.mechanism === 'cpmm-1' ? (
|
||||||
contract={contract}
|
<ContractCardProbChange
|
||||||
key={contract.id}
|
key={contract.id}
|
||||||
showTime={showTime}
|
contract={contract as CPMMBinaryContract}
|
||||||
onClick={
|
/>
|
||||||
onContractClick ? () => onContractClick(contract) : undefined
|
) : (
|
||||||
}
|
<ContractCard
|
||||||
noLinkAvatar={noLinkAvatar}
|
contract={contract}
|
||||||
hideQuickBet={hideQuickBet}
|
key={contract.id}
|
||||||
hideGroupLink={hideGroupLink}
|
showTime={showTime}
|
||||||
trackingPostfix={trackingPostfix}
|
onClick={
|
||||||
className={clsx(
|
onContractClick ? () => onContractClick(contract) : undefined
|
||||||
'mb-4 break-inside-avoid-column overflow-hidden', // prevent content from wrapping (needs overflow on firefox)
|
}
|
||||||
contractIds?.includes(contract.id) && highlightClassName
|
noLinkAvatar={noLinkAvatar}
|
||||||
)}
|
hideQuickBet={hideQuickBet}
|
||||||
/>
|
hideGroupLink={hideGroupLink}
|
||||||
))}
|
trackingPostfix={trackingPostfix}
|
||||||
|
className={clsx(
|
||||||
|
'mb-4 break-inside-avoid-column overflow-hidden', // prevent content from wrapping (needs overflow on firefox)
|
||||||
|
contractIds?.includes(contract.id) && highlightClassName
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Masonry>
|
</Masonry>
|
||||||
{loadMore && (
|
{loadMore && (
|
||||||
<VisibilityObserver
|
<VisibilityObserver
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import clsx from 'clsx'
|
|
||||||
import { ShareIcon } from '@heroicons/react/outline'
|
import { ShareIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Contract } from 'web/lib/firebase/contracts'
|
import { Contract } from 'web/lib/firebase/contracts'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
@ -10,7 +8,7 @@ import { ShareModal } from './share-modal'
|
||||||
import { FollowMarketButton } from 'web/components/follow-market-button'
|
import { FollowMarketButton } from 'web/components/follow-market-button'
|
||||||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
||||||
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Tooltip } from '../tooltip'
|
||||||
|
|
||||||
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
const { contract } = props
|
const { contract } = props
|
||||||
|
@ -23,27 +21,23 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
{user?.id !== contract.creatorId && (
|
{user?.id !== contract.creatorId && (
|
||||||
<LikeMarketButton contract={contract} user={user} />
|
<LikeMarketButton contract={contract} user={user} />
|
||||||
)}
|
)}
|
||||||
<Button
|
<Tooltip text="Share" placement="bottom" noTap noFade>
|
||||||
size="sm"
|
<Button
|
||||||
color="gray-white"
|
size="sm"
|
||||||
className={'flex'}
|
color="gray-white"
|
||||||
onClick={() => {
|
className={'flex'}
|
||||||
setShareOpen(true)
|
onClick={() => setShareOpen(true)}
|
||||||
}}
|
>
|
||||||
>
|
<ShareIcon className="h-5 w-5" aria-hidden />
|
||||||
<Row>
|
<ShareModal
|
||||||
<ShareIcon className={clsx('h-5 w-5')} aria-hidden="true" />
|
isOpen={isShareOpen}
|
||||||
</Row>
|
setOpen={setShareOpen}
|
||||||
<ShareModal
|
contract={contract}
|
||||||
isOpen={isShareOpen}
|
user={user}
|
||||||
setOpen={setShareOpen}
|
/>
|
||||||
contract={contract}
|
</Button>
|
||||||
user={user}
|
</Tooltip>
|
||||||
/>
|
<ContractInfoDialog contract={contract} />
|
||||||
</Button>
|
|
||||||
<Col className={'justify-center'}>
|
|
||||||
<ContractInfoDialog contract={contract} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { Col } from 'web/components/layout/col'
|
||||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { useMarketTipTxns } from 'web/hooks/use-tip-txns'
|
import { useMarketTipTxns } from 'web/hooks/use-tip-txns'
|
||||||
import { sum } from 'lodash'
|
import { sum } from 'lodash'
|
||||||
|
import { Tooltip } from '../tooltip'
|
||||||
|
|
||||||
export function LikeMarketButton(props: {
|
export function LikeMarketButton(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -37,37 +38,44 @@ export function LikeMarketButton(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Tooltip
|
||||||
size={'sm'}
|
text={`Tip ${formatMoney(LIKE_TIP_AMOUNT)}`}
|
||||||
className={'max-w-xs self-center'}
|
placement="bottom"
|
||||||
color={'gray-white'}
|
noTap
|
||||||
onClick={onLike}
|
noFade
|
||||||
>
|
>
|
||||||
<Col className={'relative items-center sm:flex-row'}>
|
<Button
|
||||||
<HeartIcon
|
size={'sm'}
|
||||||
className={clsx(
|
className={'max-w-xs self-center'}
|
||||||
'h-5 w-5 sm:h-6 sm:w-6',
|
color={'gray-white'}
|
||||||
totalTipped > 0 ? 'mr-2' : '',
|
onClick={onLike}
|
||||||
user &&
|
>
|
||||||
(userLikedContractIds?.includes(contract.id) ||
|
<Col className={'relative items-center sm:flex-row'}>
|
||||||
(!likes && contract.likedByUserIds?.includes(user.id)))
|
<HeartIcon
|
||||||
? 'fill-red-500 text-red-500'
|
|
||||||
: ''
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{totalTipped > 0 && (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'bg-greyscale-6 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1',
|
'h-5 w-5 sm:h-6 sm:w-6',
|
||||||
totalTipped > 99
|
totalTipped > 0 ? 'mr-2' : '',
|
||||||
? 'text-[0.4rem] sm:text-[0.5rem]'
|
user &&
|
||||||
: 'sm:text-2xs text-[0.5rem]'
|
(userLikedContractIds?.includes(contract.id) ||
|
||||||
|
(!likes && contract.likedByUserIds?.includes(user.id)))
|
||||||
|
? 'fill-red-500 text-red-500'
|
||||||
|
: ''
|
||||||
)}
|
)}
|
||||||
>
|
/>
|
||||||
{totalTipped}
|
{totalTipped > 0 && (
|
||||||
</div>
|
<div
|
||||||
)}
|
className={clsx(
|
||||||
</Col>
|
'bg-greyscale-6 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1',
|
||||||
</Button>
|
totalTipped > 99
|
||||||
|
? 'text-[0.4rem] sm:text-[0.5rem]'
|
||||||
|
: 'sm:text-2xs text-[0.5rem]'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{totalTipped}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import { partition } from 'lodash'
|
||||||
import { contractPath } from 'web/lib/firebase/contracts'
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
import { CPMMContract } from 'common/contract'
|
import { CPMMContract } from 'common/contract'
|
||||||
import { formatPercent } from 'common/util/format'
|
import { formatPercent } from 'common/util/format'
|
||||||
|
@ -8,16 +9,17 @@ import { Row } from '../layout/row'
|
||||||
import { LoadingIndicator } from '../loading-indicator'
|
import { LoadingIndicator } from '../loading-indicator'
|
||||||
|
|
||||||
export function ProbChangeTable(props: {
|
export function ProbChangeTable(props: {
|
||||||
changes:
|
changes: CPMMContract[] | undefined
|
||||||
| { positiveChanges: CPMMContract[]; negativeChanges: CPMMContract[] }
|
|
||||||
| undefined
|
|
||||||
full?: boolean
|
full?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { changes, full } = props
|
const { changes, full } = props
|
||||||
|
|
||||||
if (!changes) return <LoadingIndicator />
|
if (!changes) return <LoadingIndicator />
|
||||||
|
|
||||||
const { positiveChanges, negativeChanges } = changes
|
const [positiveChanges, negativeChanges] = partition(
|
||||||
|
changes,
|
||||||
|
(c) => c.probChanges.day > 0
|
||||||
|
)
|
||||||
|
|
||||||
const threshold = 0.01
|
const threshold = 0.01
|
||||||
const positiveAboveThreshold = positiveChanges.filter(
|
const positiveAboveThreshold = positiveChanges.filter(
|
||||||
|
@ -53,10 +55,18 @@ export function ProbChangeTable(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProbChangeRow(props: { contract: CPMMContract }) {
|
export function ProbChangeRow(props: {
|
||||||
const { contract } = props
|
contract: CPMMContract
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const { contract, className } = props
|
||||||
return (
|
return (
|
||||||
<Row className="items-center justify-between gap-4 hover:bg-gray-100">
|
<Row
|
||||||
|
className={clsx(
|
||||||
|
'items-center justify-between gap-4 hover:bg-gray-100',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
<SiteLink
|
<SiteLink
|
||||||
className="p-4 pr-0 font-semibold text-indigo-700"
|
className="p-4 pr-0 font-semibold text-indigo-700"
|
||||||
href={contractPath(contract)}
|
href={contractPath(contract)}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { CreateChallengeModal } from 'web/components/challenges/create-challenge
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { CHALLENGES_ENABLED } from 'common/challenge'
|
import { CHALLENGES_ENABLED } from 'common/challenge'
|
||||||
import ChallengeIcon from 'web/lib/icons/challenge-icon'
|
import ChallengeIcon from 'web/lib/icons/challenge-icon'
|
||||||
|
import { QRCode } from '../qr-code'
|
||||||
|
|
||||||
export function ShareModal(props: {
|
export function ShareModal(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -54,6 +55,12 @@ export function ShareModal(props: {
|
||||||
</SiteLink>{' '}
|
</SiteLink>{' '}
|
||||||
if a new user signs up using the link!
|
if a new user signs up using the link!
|
||||||
</p>
|
</p>
|
||||||
|
<QRCode
|
||||||
|
url={shareUrl}
|
||||||
|
className="self-center"
|
||||||
|
width={150}
|
||||||
|
height={150}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="2xl"
|
size="2xl"
|
||||||
color="indigo"
|
color="indigo"
|
||||||
|
|
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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { Answer } from 'common/answer'
|
import { Answer } from 'common/answer'
|
||||||
import { FreeResponseContract } from 'common/contract'
|
import { FreeResponseContract } from 'common/contract'
|
||||||
import { ContractComment } from 'common/comment'
|
import { ContractComment } from 'common/comment'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
|
@ -10,11 +10,10 @@ import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
ContractCommentInput,
|
ContractCommentInput,
|
||||||
FeedComment,
|
FeedComment,
|
||||||
|
ReplyTo,
|
||||||
} from 'web/components/feed/feed-comments'
|
} from 'web/components/feed/feed-comments'
|
||||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { User } from 'common/user'
|
|
||||||
import { useEvent } from 'web/hooks/use-event'
|
|
||||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||||
import { UserLink } from 'web/components/user-link'
|
import { UserLink } from 'web/components/user-link'
|
||||||
|
|
||||||
|
@ -27,32 +26,17 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
const { answer, contract, answerComments, tips } = props
|
const { answer, contract, answerComments, tips } = props
|
||||||
const { username, avatarUrl, name, text } = answer
|
const { username, avatarUrl, name, text } = answer
|
||||||
|
|
||||||
const [replyToUser, setReplyToUser] =
|
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
||||||
useState<Pick<User, 'id' | 'username'>>()
|
|
||||||
const [showReply, setShowReply] = useState(false)
|
|
||||||
const [highlighted, setHighlighted] = useState(false)
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const answerElementId = `answer-${answer.id}`
|
const answerElementId = `answer-${answer.id}`
|
||||||
|
const highlighted = router.asPath.endsWith(`#${answerElementId}`)
|
||||||
const scrollAndOpenReplyInput = useEvent(
|
const answerRef = useRef<HTMLDivElement>(null)
|
||||||
(comment?: ContractComment, answer?: Answer) => {
|
|
||||||
setReplyToUser(
|
|
||||||
comment
|
|
||||||
? { id: comment.userId, username: comment.userUsername }
|
|
||||||
: answer
|
|
||||||
? { id: answer.userId, username: answer.username }
|
|
||||||
: undefined
|
|
||||||
)
|
|
||||||
setShowReply(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.asPath.endsWith(`#${answerElementId}`)) {
|
if (highlighted && answerRef.current != null) {
|
||||||
setHighlighted(true)
|
answerRef.current.scrollIntoView(true)
|
||||||
}
|
}
|
||||||
}, [answerElementId, router.asPath])
|
}, [highlighted])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="relative flex-1 items-stretch gap-3">
|
<Col className="relative flex-1 items-stretch gap-3">
|
||||||
|
@ -61,6 +45,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
'gap-3 space-x-3 pt-4 transition-all duration-1000',
|
'gap-3 space-x-3 pt-4 transition-all duration-1000',
|
||||||
highlighted ? `-m-2 my-3 rounded bg-indigo-500/[0.2] p-2` : ''
|
highlighted ? `-m-2 my-3 rounded bg-indigo-500/[0.2] p-2` : ''
|
||||||
)}
|
)}
|
||||||
|
ref={answerRef}
|
||||||
id={answerElementId}
|
id={answerElementId}
|
||||||
>
|
>
|
||||||
<Avatar username={username} avatarUrl={avatarUrl} />
|
<Avatar username={username} avatarUrl={avatarUrl} />
|
||||||
|
@ -83,7 +68,9 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
<div className="sm:hidden">
|
<div className="sm:hidden">
|
||||||
<button
|
<button
|
||||||
className="text-xs font-bold text-gray-500 hover:underline"
|
className="text-xs font-bold text-gray-500 hover:underline"
|
||||||
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
|
onClick={() =>
|
||||||
|
setReplyTo({ id: answer.id, username: answer.username })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
|
@ -92,7 +79,9 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
<div className="justify-initial hidden sm:block">
|
<div className="justify-initial hidden sm:block">
|
||||||
<button
|
<button
|
||||||
className="text-xs font-bold text-gray-500 hover:underline"
|
className="text-xs font-bold text-gray-500 hover:underline"
|
||||||
onClick={() => scrollAndOpenReplyInput(undefined, answer)}
|
onClick={() =>
|
||||||
|
setReplyTo({ id: answer.id, username: answer.username })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
|
@ -107,11 +96,13 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id] ?? {}}
|
tips={tips[comment.id] ?? {}}
|
||||||
onReplyClick={scrollAndOpenReplyInput}
|
onReplyClick={() =>
|
||||||
|
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
{showReply && (
|
{replyTo && (
|
||||||
<div className="relative ml-7">
|
<div className="relative ml-7">
|
||||||
<span
|
<span
|
||||||
className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
className="absolute -left-1 -ml-[1px] mt-[1.25rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||||
|
@ -120,8 +111,8 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
<ContractCommentInput
|
<ContractCommentInput
|
||||||
contract={contract}
|
contract={contract}
|
||||||
parentAnswerOutcome={answer.number.toString()}
|
parentAnswerOutcome={answer.number.toString()}
|
||||||
replyToUser={replyToUser}
|
replyTo={replyTo}
|
||||||
onSubmitComment={() => setShowReply(false)}
|
onSubmitComment={() => setReplyTo(undefined)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ContractComment } from 'common/comment'
|
import { ContractComment } from 'common/comment'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
@ -20,6 +20,8 @@ import { Editor } from '@tiptap/react'
|
||||||
import { UserLink } from 'web/components/user-link'
|
import { UserLink } from 'web/components/user-link'
|
||||||
import { CommentInput } from '../comment-input'
|
import { CommentInput } from '../comment-input'
|
||||||
|
|
||||||
|
export type ReplyTo = { id: string; username: string }
|
||||||
|
|
||||||
export function FeedCommentThread(props: {
|
export function FeedCommentThread(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
threadComments: ContractComment[]
|
threadComments: ContractComment[]
|
||||||
|
@ -27,13 +29,7 @@ export function FeedCommentThread(props: {
|
||||||
parentComment: ContractComment
|
parentComment: ContractComment
|
||||||
}) {
|
}) {
|
||||||
const { contract, threadComments, tips, parentComment } = props
|
const { contract, threadComments, tips, parentComment } = props
|
||||||
const [showReply, setShowReply] = useState(false)
|
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
||||||
const [replyTo, setReplyTo] = useState<{ id: string; username: string }>()
|
|
||||||
|
|
||||||
function scrollAndOpenReplyInput(comment: ContractComment) {
|
|
||||||
setReplyTo({ id: comment.userId, username: comment.userUsername })
|
|
||||||
setShowReply(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="relative w-full items-stretch gap-3 pb-4">
|
<Col className="relative w-full items-stretch gap-3 pb-4">
|
||||||
|
@ -48,10 +44,12 @@ export function FeedCommentThread(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id] ?? {}}
|
tips={tips[comment.id] ?? {}}
|
||||||
onReplyClick={scrollAndOpenReplyInput}
|
onReplyClick={() =>
|
||||||
|
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showReply && (
|
{replyTo && (
|
||||||
<Col className="-pb-2 relative ml-6">
|
<Col className="-pb-2 relative ml-6">
|
||||||
<span
|
<span
|
||||||
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||||
|
@ -60,10 +58,8 @@ export function FeedCommentThread(props: {
|
||||||
<ContractCommentInput
|
<ContractCommentInput
|
||||||
contract={contract}
|
contract={contract}
|
||||||
parentCommentId={parentComment.id}
|
parentCommentId={parentComment.id}
|
||||||
replyToUser={replyTo}
|
replyTo={replyTo}
|
||||||
onSubmitComment={() => {
|
onSubmitComment={() => setReplyTo(undefined)}
|
||||||
setShowReply(false)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
@ -76,7 +72,7 @@ export function FeedComment(props: {
|
||||||
comment: ContractComment
|
comment: ContractComment
|
||||||
tips?: CommentTips
|
tips?: CommentTips
|
||||||
indent?: boolean
|
indent?: boolean
|
||||||
onReplyClick?: (comment: ContractComment) => void
|
onReplyClick?: () => void
|
||||||
}) {
|
}) {
|
||||||
const { contract, comment, tips, indent, onReplyClick } = props
|
const { contract, comment, tips, indent, onReplyClick } = props
|
||||||
const {
|
const {
|
||||||
|
@ -98,16 +94,19 @@ export function FeedComment(props: {
|
||||||
money = formatMoney(Math.abs(comment.betAmount))
|
money = formatMoney(Math.abs(comment.betAmount))
|
||||||
}
|
}
|
||||||
|
|
||||||
const [highlighted, setHighlighted] = useState(false)
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const highlighted = router.asPath.endsWith(`#${comment.id}`)
|
||||||
|
const commentRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.asPath.endsWith(`#${comment.id}`)) {
|
if (highlighted && commentRef.current != null) {
|
||||||
setHighlighted(true)
|
commentRef.current.scrollIntoView(true)
|
||||||
}
|
}
|
||||||
}, [comment.id, router.asPath])
|
}, [highlighted])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
|
ref={commentRef}
|
||||||
id={comment.id}
|
id={comment.id}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'relative',
|
'relative',
|
||||||
|
@ -174,7 +173,7 @@ export function FeedComment(props: {
|
||||||
{onReplyClick && (
|
{onReplyClick && (
|
||||||
<button
|
<button
|
||||||
className="font-bold hover:underline"
|
className="font-bold hover:underline"
|
||||||
onClick={() => onReplyClick(comment)}
|
onClick={onReplyClick}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
|
@ -204,7 +203,7 @@ export function ContractCommentInput(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
className?: string
|
className?: string
|
||||||
parentAnswerOutcome?: string | undefined
|
parentAnswerOutcome?: string | undefined
|
||||||
replyToUser?: { id: string; username: string }
|
replyTo?: ReplyTo
|
||||||
parentCommentId?: string
|
parentCommentId?: string
|
||||||
onSubmitComment?: () => void
|
onSubmitComment?: () => void
|
||||||
}) {
|
}) {
|
||||||
|
@ -226,7 +225,7 @@ export function ContractCommentInput(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommentInput
|
<CommentInput
|
||||||
replyToUser={props.replyToUser}
|
replyTo={props.replyTo}
|
||||||
parentAnswerOutcome={props.parentAnswerOutcome}
|
parentAnswerOutcome={props.parentAnswerOutcome}
|
||||||
parentCommentId={props.parentCommentId}
|
parentCommentId={props.parentCommentId}
|
||||||
onSubmitComment={onSubmitComment}
|
onSubmitComment={onSubmitComment}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { track } from 'web/lib/service/analytics'
|
||||||
import { WatchMarketModal } from 'web/components/contract/watch-market-modal'
|
import { WatchMarketModal } from 'web/components/contract/watch-market-modal'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
|
import { Tooltip } from './tooltip'
|
||||||
|
|
||||||
export const FollowMarketButton = (props: {
|
export const FollowMarketButton = (props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -23,61 +24,70 @@ export const FollowMarketButton = (props: {
|
||||||
const followers = useContractFollows(contract.id)
|
const followers = useContractFollows(contract.id)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
const watching = followers?.includes(user?.id ?? 'nope')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Tooltip
|
||||||
size={'sm'}
|
text={watching ? 'Unfollow' : 'Follow'}
|
||||||
color={'gray-white'}
|
placement="bottom"
|
||||||
onClick={async () => {
|
noTap
|
||||||
if (!user) return firebaseLogin()
|
noFade
|
||||||
if (followers?.includes(user.id)) {
|
|
||||||
await unFollowContract(contract.id, user.id)
|
|
||||||
toast("You'll no longer receive notifications from this market", {
|
|
||||||
icon: <CheckIcon className={'text-primary h-5 w-5'} />,
|
|
||||||
})
|
|
||||||
track('Unwatch Market', {
|
|
||||||
slug: contract.slug,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await followContract(contract.id, user.id)
|
|
||||||
toast("You'll now receive notifications from this market!", {
|
|
||||||
icon: <CheckIcon className={'text-primary h-5 w-5'} />,
|
|
||||||
})
|
|
||||||
track('Watch Market', {
|
|
||||||
slug: contract.slug,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!user.hasSeenContractFollowModal) {
|
|
||||||
await updateUser(user.id, {
|
|
||||||
hasSeenContractFollowModal: true,
|
|
||||||
})
|
|
||||||
setOpen(true)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{followers?.includes(user?.id ?? 'nope') ? (
|
<Button
|
||||||
<Col className={'items-center gap-x-2 sm:flex-row'}>
|
size={'sm'}
|
||||||
<EyeOffIcon
|
color={'gray-white'}
|
||||||
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
|
onClick={async () => {
|
||||||
aria-hidden="true"
|
if (!user) return firebaseLogin()
|
||||||
/>
|
if (followers?.includes(user.id)) {
|
||||||
{/* Unwatch */}
|
await unFollowContract(contract.id, user.id)
|
||||||
</Col>
|
toast("You'll no longer receive notifications from this market", {
|
||||||
) : (
|
icon: <CheckIcon className={'text-primary h-5 w-5'} />,
|
||||||
<Col className={'items-center gap-x-2 sm:flex-row'}>
|
})
|
||||||
<EyeIcon
|
track('Unwatch Market', {
|
||||||
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
|
slug: contract.slug,
|
||||||
aria-hidden="true"
|
})
|
||||||
/>
|
} else {
|
||||||
{/* Watch */}
|
await followContract(contract.id, user.id)
|
||||||
</Col>
|
toast("You'll now receive notifications from this market!", {
|
||||||
)}
|
icon: <CheckIcon className={'text-primary h-5 w-5'} />,
|
||||||
<WatchMarketModal
|
})
|
||||||
open={open}
|
track('Watch Market', {
|
||||||
setOpen={setOpen}
|
slug: contract.slug,
|
||||||
title={`You ${
|
})
|
||||||
followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched'
|
}
|
||||||
} a question!`}
|
if (!user.hasSeenContractFollowModal) {
|
||||||
/>
|
await updateUser(user.id, {
|
||||||
</Button>
|
hasSeenContractFollowModal: true,
|
||||||
|
})
|
||||||
|
setOpen(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{watching ? (
|
||||||
|
<Col className={'items-center gap-x-2 sm:flex-row'}>
|
||||||
|
<EyeOffIcon
|
||||||
|
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{/* Unwatch */}
|
||||||
|
</Col>
|
||||||
|
) : (
|
||||||
|
<Col className={'items-center gap-x-2 sm:flex-row'}>
|
||||||
|
<EyeIcon
|
||||||
|
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{/* Watch */}
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
<WatchMarketModal
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
title={`You ${
|
||||||
|
followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched'
|
||||||
|
} a question!`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,7 @@ function FollowsDialog(props: {
|
||||||
<div className="p-2 pb-1 text-xl">{user.name}</div>
|
<div className="p-2 pb-1 text-xl">{user.name}</div>
|
||||||
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
|
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
title: 'Following',
|
title: 'Following',
|
||||||
|
|
|
@ -16,29 +16,26 @@ import { usePost } from 'web/hooks/use-post'
|
||||||
export function GroupAboutPost(props: {
|
export function GroupAboutPost(props: {
|
||||||
group: Group
|
group: Group
|
||||||
isEditable: boolean
|
isEditable: boolean
|
||||||
post: Post
|
post: Post | null
|
||||||
}) {
|
}) {
|
||||||
const { group, isEditable } = props
|
const { group, isEditable } = props
|
||||||
const post = usePost(group.aboutPostId) ?? props.post
|
const post = usePost(group.aboutPostId) ?? props.post
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md bg-white p-4 ">
|
<div className="rounded-md bg-white p-4 ">
|
||||||
{isEditable ? (
|
{isEditable && <RichEditGroupAboutPost group={group} post={post} />}
|
||||||
<RichEditGroupAboutPost group={group} post={post} />
|
{!isEditable && post && <Content content={post.content} />}
|
||||||
) : (
|
|
||||||
<Content content={post.content} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
function RichEditGroupAboutPost(props: { group: Group; post: Post | null }) {
|
||||||
const { group, post } = props
|
const { group, post } = props
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false)
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
const { editor, upload } = useTextEditor({
|
const { editor, upload } = useTextEditor({
|
||||||
defaultValue: post.content,
|
defaultValue: post?.content,
|
||||||
disabled: isSubmitting,
|
disabled: isSubmitting,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -49,7 +46,7 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
||||||
content: editor.getJSON(),
|
content: editor.getJSON(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group.aboutPostId == null) {
|
if (post == null) {
|
||||||
const result = await createPost(newPost).catch((e) => {
|
const result = await createPost(newPost).catch((e) => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return e
|
return e
|
||||||
|
@ -65,6 +62,7 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteGroupAboutPost() {
|
async function deleteGroupAboutPost() {
|
||||||
|
if (post == null) return
|
||||||
await deletePost(post)
|
await deletePost(post)
|
||||||
await deleteFieldFromGroup(group, 'aboutPostId')
|
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">
|
<div className="text-center text-gray-500">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
No post has been added yet.
|
No post has been added yet.
|
||||||
|
|
|
@ -23,6 +23,7 @@ export function LandingPagePanel(props: { hotContracts: Contract[] }) {
|
||||||
height={250}
|
height={250}
|
||||||
width={250}
|
width={250}
|
||||||
className="self-center"
|
className="self-center"
|
||||||
|
alt="Manifold logo"
|
||||||
src="/flappy-logo.gif"
|
src="/flappy-logo.gif"
|
||||||
/>
|
/>
|
||||||
<div className="m-4 max-w-[550px] self-center">
|
<div className="m-4 max-w-[550px] self-center">
|
||||||
|
|
|
@ -32,7 +32,7 @@ export function ControlledTabs(props: TabProps & { activeIndex: number }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav
|
<nav
|
||||||
className={clsx('mb-4 space-x-8 border-b border-gray-200', className)}
|
className={clsx('space-x-8 border-b border-gray-200', className)}
|
||||||
aria-label="Tabs"
|
aria-label="Tabs"
|
||||||
>
|
>
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
|
|
|
@ -1,28 +1,33 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { User } from 'common/user'
|
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { SiteLink } from './site-link'
|
import { SiteLink } from './site-link'
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
|
|
||||||
export function Leaderboard(props: {
|
interface LeaderboardEntry {
|
||||||
|
username: string
|
||||||
|
name: string
|
||||||
|
avatarUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Leaderboard<T extends LeaderboardEntry>(props: {
|
||||||
title: string
|
title: string
|
||||||
users: User[]
|
entries: T[]
|
||||||
columns: {
|
columns: {
|
||||||
header: string
|
header: string
|
||||||
renderCell: (user: User) => any
|
renderCell: (entry: T) => any
|
||||||
}[]
|
}[]
|
||||||
className?: string
|
className?: string
|
||||||
maxToShow?: number
|
maxToShow?: number
|
||||||
}) {
|
}) {
|
||||||
// TODO: Ideally, highlight your own entry on the leaderboard
|
// TODO: Ideally, highlight your own entry on the leaderboard
|
||||||
const { title, columns, className } = props
|
const { title, columns, className } = props
|
||||||
const maxToShow = props.maxToShow ?? props.users.length
|
const maxToShow = props.maxToShow ?? props.entries.length
|
||||||
const users = props.users.slice(0, maxToShow)
|
const entries = props.entries.slice(0, maxToShow)
|
||||||
return (
|
return (
|
||||||
<div className={clsx('w-full px-1', className)}>
|
<div className={clsx('w-full px-1', className)}>
|
||||||
<Title text={title} className="!mt-0" />
|
<Title text={title} className="!mt-0" />
|
||||||
{users.length === 0 ? (
|
{entries.length === 0 ? (
|
||||||
<div className="ml-2 text-gray-500">None yet</div>
|
<div className="ml-2 text-gray-500">None yet</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
|
@ -37,19 +42,19 @@ export function Leaderboard(props: {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{users.map((user, index) => (
|
{entries.map((entry, index) => (
|
||||||
<tr key={user.id}>
|
<tr key={index}>
|
||||||
<td>{index + 1}</td>
|
<td>{index + 1}</td>
|
||||||
<td className="max-w-[190px]">
|
<td className="max-w-[190px]">
|
||||||
<SiteLink className="relative" href={`/${user.username}`}>
|
<SiteLink className="relative" href={`/${entry.username}`}>
|
||||||
<Row className="items-center gap-4">
|
<Row className="items-center gap-4">
|
||||||
<Avatar avatarUrl={user.avatarUrl} size={8} />
|
<Avatar avatarUrl={entry.avatarUrl} size={8} />
|
||||||
<div className="truncate">{user.name}</div>
|
<div className="truncate">{entry.name}</div>
|
||||||
</Row>
|
</Row>
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
</td>
|
</td>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<td key={column.header}>{column.renderCell(user)}</td>
|
<td key={column.header}>{column.renderCell(entry)}</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
import { ClipboardIcon, HomeIcon } from '@heroicons/react/outline'
|
|
||||||
import { Item } from './sidebar-item'
|
|
||||||
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import { trackCallback } from 'web/lib/service/analytics'
|
|
||||||
import TrophyIcon from 'web/lib/icons/trophy-icon'
|
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import NotificationsIcon from '../notifications-icon'
|
|
||||||
import router from 'next/router'
|
|
||||||
import { userProfileItem } from './bottom-nav-bar'
|
|
||||||
|
|
||||||
const mobileGroupNavigation = [
|
|
||||||
{ name: 'Markets', key: 'markets', icon: HomeIcon },
|
|
||||||
{ name: 'Leaderboard', key: 'leaderboards', icon: TrophyIcon },
|
|
||||||
{ name: 'About', key: 'about', icon: ClipboardIcon },
|
|
||||||
]
|
|
||||||
|
|
||||||
const mobileGeneralNavigation = [
|
|
||||||
{
|
|
||||||
name: 'Notifications',
|
|
||||||
key: 'notifications',
|
|
||||||
icon: NotificationsIcon,
|
|
||||||
href: '/notifications',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function GroupNavBar(props: {
|
|
||||||
currentPage: string
|
|
||||||
onClick: (key: string) => void
|
|
||||||
}) {
|
|
||||||
const { currentPage } = props
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="z-20 flex justify-between border-t-2 bg-white text-xs text-gray-700 lg:hidden">
|
|
||||||
{mobileGroupNavigation.map((item) => (
|
|
||||||
<NavBarItem
|
|
||||||
key={item.name}
|
|
||||||
item={item}
|
|
||||||
currentPage={currentPage}
|
|
||||||
onClick={props.onClick}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{mobileGeneralNavigation.map((item) => (
|
|
||||||
<NavBarItem
|
|
||||||
key={item.name}
|
|
||||||
item={item}
|
|
||||||
currentPage={currentPage}
|
|
||||||
onClick={() => {
|
|
||||||
router.push(item.href)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{user && (
|
|
||||||
<NavBarItem
|
|
||||||
key={'profile'}
|
|
||||||
currentPage={currentPage}
|
|
||||||
onClick={() => {
|
|
||||||
router.push(`/${user.username}?tab=trades`)
|
|
||||||
}}
|
|
||||||
item={userProfileItem(user)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavBarItem(props: {
|
|
||||||
item: Item
|
|
||||||
currentPage: string
|
|
||||||
onClick: (key: string) => void
|
|
||||||
}) {
|
|
||||||
const { item, currentPage } = props
|
|
||||||
const track = trackCallback(
|
|
||||||
`group navbar: ${item.trackingEventName ?? item.name}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button onClick={() => props.onClick(item.key ?? '#')}>
|
|
||||||
<a
|
|
||||||
className={clsx(
|
|
||||||
'block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700',
|
|
||||||
currentPage === item.key && 'bg-gray-200 text-indigo-700'
|
|
||||||
)}
|
|
||||||
onClick={track}
|
|
||||||
>
|
|
||||||
{item.icon && <item.icon className="my-1 mx-auto h-6 w-6" />}
|
|
||||||
{item.name}
|
|
||||||
</a>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { ClipboardIcon, HomeIcon } from '@heroicons/react/outline'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import { ManifoldLogo } from './manifold-logo'
|
|
||||||
import { ProfileSummary } from './profile-menu'
|
|
||||||
import React from 'react'
|
|
||||||
import TrophyIcon from 'web/lib/icons/trophy-icon'
|
|
||||||
import { SignInButton } from '../sign-in-button'
|
|
||||||
import NotificationsIcon from '../notifications-icon'
|
|
||||||
import { SidebarItem } from './sidebar-item'
|
|
||||||
import { buildArray } from 'common/util/array'
|
|
||||||
import { User } from 'common/user'
|
|
||||||
import { Row } from '../layout/row'
|
|
||||||
import { Spacer } from '../layout/spacer'
|
|
||||||
|
|
||||||
const groupNavigation = [
|
|
||||||
{ name: 'Markets', key: 'markets', icon: HomeIcon },
|
|
||||||
{ name: 'About', key: 'about', icon: ClipboardIcon },
|
|
||||||
{ name: 'Leaderboard', key: 'leaderboards', icon: TrophyIcon },
|
|
||||||
]
|
|
||||||
|
|
||||||
const generalNavigation = (user?: User | null) =>
|
|
||||||
buildArray(
|
|
||||||
user && {
|
|
||||||
name: 'Notifications',
|
|
||||||
href: `/notifications`,
|
|
||||||
key: 'notifications',
|
|
||||||
icon: NotificationsIcon,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export function GroupSidebar(props: {
|
|
||||||
groupName: string
|
|
||||||
className?: string
|
|
||||||
onClick: (key: string) => void
|
|
||||||
joinOrAddQuestionsButton: React.ReactNode
|
|
||||||
currentKey: string
|
|
||||||
}) {
|
|
||||||
const { className, groupName, currentKey } = props
|
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav
|
|
||||||
aria-label="Group Sidebar"
|
|
||||||
className={clsx('flex max-h-[100vh] flex-col', className)}
|
|
||||||
>
|
|
||||||
<ManifoldLogo className="pt-6" twoLine />
|
|
||||||
<Row className="pl-2 text-xl text-indigo-700 sm:mt-3">{groupName}</Row>
|
|
||||||
|
|
||||||
<div className=" min-h-0 shrink flex-col items-stretch gap-1 pt-6 lg:flex ">
|
|
||||||
{user ? (
|
|
||||||
<ProfileSummary user={user} />
|
|
||||||
) : (
|
|
||||||
<SignInButton className="mb-4" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Desktop navigation */}
|
|
||||||
{groupNavigation.map((item) => (
|
|
||||||
<SidebarItem
|
|
||||||
key={item.key}
|
|
||||||
item={item}
|
|
||||||
currentPage={currentKey}
|
|
||||||
onClick={props.onClick}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{generalNavigation(user).map((item) => (
|
|
||||||
<SidebarItem
|
|
||||||
key={item.key}
|
|
||||||
item={item}
|
|
||||||
currentPage={currentKey}
|
|
||||||
onClick={props.onClick}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Spacer h={2} />
|
|
||||||
|
|
||||||
{props.joinOrAddQuestionsButton}
|
|
||||||
</nav>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -26,9 +26,14 @@ import TrophyIcon from 'web/lib/icons/trophy-icon'
|
||||||
import { SignInButton } from '../sign-in-button'
|
import { SignInButton } from '../sign-in-button'
|
||||||
import { SidebarItem } from './sidebar-item'
|
import { SidebarItem } from './sidebar-item'
|
||||||
import { MoreButton } from './more-button'
|
import { MoreButton } from './more-button'
|
||||||
|
import { Row } from '../layout/row'
|
||||||
|
import { Spacer } from '../layout/spacer'
|
||||||
|
|
||||||
export default function Sidebar(props: { className?: string }) {
|
export default function Sidebar(props: {
|
||||||
const { className } = props
|
className?: string
|
||||||
|
logoSubheading?: string
|
||||||
|
}) {
|
||||||
|
const { className, logoSubheading } = props
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const currentPage = router.pathname
|
const currentPage = router.pathname
|
||||||
|
|
||||||
|
@ -51,7 +56,13 @@ export default function Sidebar(props: { className?: string }) {
|
||||||
aria-label="Sidebar"
|
aria-label="Sidebar"
|
||||||
className={clsx('flex max-h-[100vh] flex-col', className)}
|
className={clsx('flex max-h-[100vh] flex-col', className)}
|
||||||
>
|
>
|
||||||
<ManifoldLogo className="py-6" twoLine />
|
<ManifoldLogo className="pt-6" twoLine />
|
||||||
|
{logoSubheading && (
|
||||||
|
<Row className="pl-2 text-2xl text-indigo-700 sm:mt-3">
|
||||||
|
{logoSubheading}
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Spacer h={6} />
|
||||||
|
|
||||||
{!user && <SignInButton className="mb-4" />}
|
{!user && <SignInButton className="mb-4" />}
|
||||||
|
|
||||||
|
|
|
@ -99,8 +99,6 @@ const useIsTwitch = (user: User | null | undefined) => {
|
||||||
const isTwitch = router.pathname === '/twitch'
|
const isTwitch = router.pathname === '/twitch'
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('twich?', isTwitch)
|
|
||||||
|
|
||||||
if (isTwitch && user?.shouldShowWelcome) {
|
if (isTwitch && user?.shouldShowWelcome) {
|
||||||
updateUser(user.id, { ['shouldShowWelcome']: false })
|
updateUser(user.id, { ['shouldShowWelcome']: false })
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,15 @@ export function Page(props: {
|
||||||
className?: string
|
className?: string
|
||||||
rightSidebarClassName?: string
|
rightSidebarClassName?: string
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
logoSubheading?: string
|
||||||
}) {
|
}) {
|
||||||
const { children, rightSidebar, className, rightSidebarClassName } = props
|
const {
|
||||||
|
children,
|
||||||
|
rightSidebar,
|
||||||
|
className,
|
||||||
|
rightSidebarClassName,
|
||||||
|
logoSubheading,
|
||||||
|
} = props
|
||||||
|
|
||||||
const bottomBarPadding = 'pb-[58px] lg:pb-0 '
|
const bottomBarPadding = 'pb-[58px] lg:pb-0 '
|
||||||
return (
|
return (
|
||||||
|
@ -23,7 +30,10 @@ export function Page(props: {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Sidebar className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex" />
|
<Sidebar
|
||||||
|
logoSubheading={logoSubheading}
|
||||||
|
className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex"
|
||||||
|
/>
|
||||||
<main
|
<main
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'lg:col-span-8 lg:pt-6',
|
'lg:col-span-8 lg:pt-6',
|
||||||
|
|
|
@ -68,6 +68,7 @@ function ReferralsDialog(props: {
|
||||||
<div className="p-2 pb-1 text-xl">{user.name}</div>
|
<div className="p-2 pb-1 text-xl">{user.name}</div>
|
||||||
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
|
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
title: 'Referrals',
|
title: 'Referrals',
|
||||||
|
|
|
@ -9,12 +9,13 @@ import {
|
||||||
getUserBetContractsQuery,
|
getUserBetContractsQuery,
|
||||||
listAllContracts,
|
listAllContracts,
|
||||||
trendingContractsQuery,
|
trendingContractsQuery,
|
||||||
getContractsQuery,
|
|
||||||
} from 'web/lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { QueryClient, useQueryClient } from 'react-query'
|
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 { 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'
|
||||||
|
|
||||||
export const useContracts = () => {
|
export const useContracts = () => {
|
||||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||||
|
@ -26,6 +27,29 @@ export const useContracts = () => {
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useContractsByDailyScoreGroups = (
|
||||||
|
groupSlugs: string[] | undefined
|
||||||
|
) => {
|
||||||
|
const facetFilters = ['isResolved:false']
|
||||||
|
|
||||||
|
const { data } = useQuery(['daily-score', groupSlugs], () =>
|
||||||
|
Promise.all(
|
||||||
|
(groupSlugs ?? []).map((slug) =>
|
||||||
|
dailyScoreIndex.search<CPMMBinaryContract>('', {
|
||||||
|
facetFilters: [...facetFilters, `groupLinks.slug:${slug}`],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (!groupSlugs || !data || data.length !== groupSlugs.length)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
return zipObject(
|
||||||
|
groupSlugs,
|
||||||
|
data.map((d) => d.hits.filter((c) => c.dailyScore))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const q = new QueryClient()
|
const q = new QueryClient()
|
||||||
export const getCachedContracts = async () =>
|
export const getCachedContracts = async () =>
|
||||||
q.fetchQuery(['contracts'], () => listAllContracts(1000), {
|
q.fetchQuery(['contracts'], () => listAllContracts(1000), {
|
||||||
|
@ -40,19 +64,6 @@ export const useTrendingContracts = (maxContracts: number) => {
|
||||||
return result.data
|
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 = () => {
|
export const useInactiveContracts = () => {
|
||||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||||
|
|
||||||
|
@ -75,7 +86,7 @@ export const usePrefetchUserBetContracts = (userId: string) => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
return queryClient.prefetchQuery(
|
return queryClient.prefetchQuery(
|
||||||
['contracts', 'bets', userId],
|
['contracts', 'bets', userId],
|
||||||
() => getUserBetContracts(userId),
|
() => sleep(1000).then(() => getUserBetContracts(userId)),
|
||||||
{ staleTime: 5 * MINUTE_MS }
|
{ staleTime: 5 * MINUTE_MS }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ export const useMemberGroupIds = (user: User | null | undefined) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMemberGroupsSubscription(user: User | null | undefined) {
|
export function useMemberGroupsSubscription(user: User | null | undefined) {
|
||||||
const cachedGroups = useMemberGroups(user?.id) ?? []
|
const cachedGroups = useMemberGroups(user?.id)
|
||||||
const [groups, setGroups] = useState(cachedGroups)
|
const [groups, setGroups] = useState(cachedGroups)
|
||||||
|
|
||||||
const userId = user?.id
|
const userId = user?.id
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
// matches talwind sm breakpoint
|
export function useIsMobile(threshold?: number) {
|
||||||
export function useIsMobile() {
|
const [isMobile, setIsMobile] = useState<boolean>()
|
||||||
const { width } = useWindowSize()
|
useEffect(() => {
|
||||||
return (width ?? 0) < 640
|
// 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 { useQueryClient } from 'react-query'
|
||||||
import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
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 {
|
import {
|
||||||
getPortfolioHistory,
|
getPortfolioHistory,
|
||||||
getPortfolioHistoryQuery,
|
getPortfolioHistoryQuery,
|
||||||
|
@ -17,7 +17,7 @@ export const usePrefetchPortfolioHistory = (userId: string, period: Period) => {
|
||||||
const cutoff = getCutoff(period)
|
const cutoff = getCutoff(period)
|
||||||
return queryClient.prefetchQuery(
|
return queryClient.prefetchQuery(
|
||||||
['portfolio-history', userId, cutoff],
|
['portfolio-history', userId, cutoff],
|
||||||
() => getPortfolioHistory(userId, cutoff),
|
() => sleep(1000).then(() => getPortfolioHistory(userId, cutoff)),
|
||||||
{ staleTime: 15 * MINUTE_MS }
|
{ staleTime: 15 * MINUTE_MS }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,3 +11,29 @@ export const usePost = (postId: string | undefined) => {
|
||||||
|
|
||||||
return post
|
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 { usePrefetchUserBetContracts } from './use-contracts'
|
||||||
import { usePrefetchPortfolioHistory } from './use-portfolio-history'
|
|
||||||
import { usePrefetchUserBets } from './use-user-bets'
|
import { usePrefetchUserBets } from './use-user-bets'
|
||||||
|
|
||||||
export function usePrefetch(userId: string | undefined) {
|
export function usePrefetch(userId: string | undefined) {
|
||||||
|
@ -7,6 +6,5 @@ export function usePrefetch(userId: string | undefined) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
usePrefetchUserBets(maybeUserId),
|
usePrefetchUserBets(maybeUserId),
|
||||||
usePrefetchUserBetContracts(maybeUserId),
|
usePrefetchUserBetContracts(maybeUserId),
|
||||||
usePrefetchPortfolioHistory(maybeUserId, 'weekly'),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +1,47 @@
|
||||||
import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
import { CPMMBinaryContract } from 'common/contract'
|
||||||
import { CPMMContract } from 'common/contract'
|
import { sortBy, uniqBy } from 'lodash'
|
||||||
import { MINUTE_MS } from 'common/util/time'
|
import { useQuery } from 'react-query'
|
||||||
import { useQuery, useQueryClient } from 'react-query'
|
|
||||||
import {
|
import {
|
||||||
getProbChangesNegative,
|
probChangeAscendingIndex,
|
||||||
getProbChangesPositive,
|
probChangeDescendingIndex,
|
||||||
} from 'web/lib/firebase/contracts'
|
} from 'web/lib/service/algolia'
|
||||||
import { getValues } from 'web/lib/firebase/utils'
|
|
||||||
import { getIndexName, searchClient } from 'web/lib/service/algolia'
|
|
||||||
|
|
||||||
export const useProbChangesAlgolia = (userId: string) => {
|
export const useProbChanges = (
|
||||||
const { data: positiveData } = useQuery(['prob-change-day', userId], () =>
|
filters: { bettorId?: string; groupSlugs?: string[] } = {}
|
||||||
searchClient
|
) => {
|
||||||
.initIndex(getIndexName('prob-change-day'))
|
const { bettorId, groupSlugs } = filters
|
||||||
.search<CPMMContract>('', {
|
|
||||||
facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const { data: negativeData } = useQuery(
|
|
||||||
['prob-change-day-ascending', userId],
|
|
||||||
() =>
|
|
||||||
searchClient
|
|
||||||
.initIndex(getIndexName('prob-change-day-ascending'))
|
|
||||||
.search<CPMMContract>('', {
|
|
||||||
facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!positiveData || !negativeData) {
|
const bettorFilter = bettorId ? `uniqueBettorIds:${bettorId}` : ''
|
||||||
return undefined
|
const groupFilters = groupSlugs
|
||||||
|
? groupSlugs.map((slug) => `groupLinks.slug:${slug}`)
|
||||||
|
: []
|
||||||
|
|
||||||
|
const facetFilters = [
|
||||||
|
'isResolved:false',
|
||||||
|
'outcomeType:BINARY',
|
||||||
|
bettorFilter,
|
||||||
|
groupFilters,
|
||||||
|
]
|
||||||
|
const searchParams = {
|
||||||
|
facetFilters,
|
||||||
|
hitsPerPage: 50,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const { data: positiveChanges } = useQuery(
|
||||||
positiveChanges: positiveData.hits
|
['prob-change-day', groupSlugs],
|
||||||
.filter((c) => c.probChanges && c.probChanges.day > 0)
|
() => probChangeDescendingIndex.search<CPMMBinaryContract>('', searchParams)
|
||||||
.filter((c) => c.outcomeType === 'BINARY'),
|
)
|
||||||
negativeChanges: negativeData.hits
|
const { data: negativeChanges } = useQuery(
|
||||||
.filter((c) => c.probChanges && c.probChanges.day < 0)
|
['prob-change-day-ascending', groupSlugs],
|
||||||
.filter((c) => c.outcomeType === 'BINARY'),
|
() => probChangeAscendingIndex.search<CPMMBinaryContract>('', searchParams)
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
if (!positiveChanges || !negativeChanges) return undefined
|
||||||
export const useProbChanges = (userId: string) => {
|
|
||||||
const { data: positiveChanges } = useFirestoreQueryData(
|
const hits = uniqBy(
|
||||||
['prob-changes-day-positive', userId],
|
[...positiveChanges.hits, ...negativeChanges.hits],
|
||||||
getProbChangesPositive(userId)
|
(c) => c.id
|
||||||
)
|
).filter((c) => c.probChanges)
|
||||||
const { data: negativeChanges } = useFirestoreQueryData(
|
|
||||||
['prob-changes-day-negative', userId],
|
return sortBy(hits, (c) => Math.abs(c.probChanges.day)).reverse()
|
||||||
getProbChangesNegative(userId)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!positiveChanges || !negativeChanges) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return { positiveChanges, negativeChanges }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePrefetchProbChanges = (userId: string | undefined) => {
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
if (userId) {
|
|
||||||
queryClient.prefetchQuery(
|
|
||||||
['prob-changes-day-positive', userId],
|
|
||||||
() => getValues(getProbChangesPositive(userId)),
|
|
||||||
{ staleTime: MINUTE_MS }
|
|
||||||
)
|
|
||||||
queryClient.prefetchQuery(
|
|
||||||
['prob-changes-day-negative', userId],
|
|
||||||
() => getValues(getProbChangesNegative(userId)),
|
|
||||||
{ staleTime: MINUTE_MS }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@ import {
|
||||||
getUserBetsQuery,
|
getUserBetsQuery,
|
||||||
listenForUserContractBets,
|
listenForUserContractBets,
|
||||||
} from 'web/lib/firebase/bets'
|
} 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) => {
|
export const usePrefetchUserBets = (userId: string) => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
return queryClient.prefetchQuery(
|
return queryClient.prefetchQuery(
|
||||||
['bets', userId],
|
['bets', userId],
|
||||||
() => getUserBets(userId),
|
() => sleep(1000).then(() => getUserBets(userId)),
|
||||||
{ staleTime: MINUTE_MS }
|
{ staleTime: MINUTE_MS }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,10 @@ export function getCurrentUser(params: any) {
|
||||||
return call(getFunctionUrl('getcurrentuser'), 'GET', params)
|
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)
|
return call(getFunctionUrl('createpost'), 'POST', params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
import { partition, sortBy, sum, uniqBy } from 'lodash'
|
import { partition, sortBy, sum, uniqBy } from 'lodash'
|
||||||
|
|
||||||
import { coll, getValues, listenForValue, listenForValues } from './utils'
|
import { coll, getValues, listenForValue, listenForValues } from './utils'
|
||||||
import { BinaryContract, Contract, CPMMContract } from 'common/contract'
|
import { BinaryContract, Contract } from 'common/contract'
|
||||||
import { chooseRandomSubset } from 'common/util/random'
|
import { chooseRandomSubset } from 'common/util/random'
|
||||||
import { formatMoney, formatPercent } from 'common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { DAY_MS } from 'common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
|
@ -24,7 +24,6 @@ import { Bet } from 'common/bet'
|
||||||
import { Comment } from 'common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { ENV_CONFIG } from 'common/envs/constants'
|
import { ENV_CONFIG } from 'common/envs/constants'
|
||||||
import { getBinaryProb } from 'common/contract-details'
|
import { getBinaryProb } from 'common/contract-details'
|
||||||
import { Sort } from 'web/components/contract-search'
|
|
||||||
|
|
||||||
export const contracts = coll<Contract>('contracts')
|
export const contracts = coll<Contract>('contracts')
|
||||||
|
|
||||||
|
@ -321,51 +320,6 @@ export const getTopGroupContracts = async (
|
||||||
return await getValues<Contract>(creatorContractsQuery)
|
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 (
|
export const getRecommendedContracts = async (
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
excludeBettorId: string,
|
excludeBettorId: string,
|
||||||
|
@ -426,21 +380,3 @@ export async function getRecentBetsAndComments(contract: Contract) {
|
||||||
recentComments,
|
recentComments,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getProbChangesPositive = (userId: string) =>
|
|
||||||
query(
|
|
||||||
contracts,
|
|
||||||
where('uniqueBettorIds', 'array-contains', userId),
|
|
||||||
where('probChanges.day', '>', 0),
|
|
||||||
orderBy('probChanges.day', 'desc'),
|
|
||||||
limit(10)
|
|
||||||
) as Query<CPMMContract>
|
|
||||||
|
|
||||||
export const getProbChangesNegative = (userId: string) =>
|
|
||||||
query(
|
|
||||||
contracts,
|
|
||||||
where('uniqueBettorIds', 'array-contains', userId),
|
|
||||||
where('probChanges.day', '<', 0),
|
|
||||||
orderBy('probChanges.day', 'asc'),
|
|
||||||
limit(10)
|
|
||||||
) as Query<CPMMContract>
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ export function groupPath(
|
||||||
| 'about'
|
| 'about'
|
||||||
| typeof GROUP_CHAT_SLUG
|
| typeof GROUP_CHAT_SLUG
|
||||||
| 'leaderboards'
|
| 'leaderboards'
|
||||||
|
| 'posts'
|
||||||
) {
|
) {
|
||||||
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
|
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,3 +39,8 @@ export function listenForPost(
|
||||||
) {
|
) {
|
||||||
return listenForValue(doc(posts, postId), setPost)
|
return listenForValue(doc(posts, postId), setPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listPosts(postIds?: string[]) {
|
||||||
|
if (postIds === undefined) return []
|
||||||
|
return Promise.all(postIds.map(getPost))
|
||||||
|
}
|
||||||
|
|
|
@ -13,3 +13,13 @@ export const searchIndexName =
|
||||||
export const getIndexName = (sort: string) => {
|
export const getIndexName = (sort: string) => {
|
||||||
return `${indexPrefix}contracts-${sort}`
|
return `${indexPrefix}contracts-${sort}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const probChangeDescendingIndex = searchClient.initIndex(
|
||||||
|
getIndexName('prob-change-day')
|
||||||
|
)
|
||||||
|
export const probChangeAscendingIndex = searchClient.initIndex(
|
||||||
|
getIndexName('prob-change-day-ascending')
|
||||||
|
)
|
||||||
|
export const dailyScoreIndex = searchClient.initIndex(
|
||||||
|
getIndexName('daily-score')
|
||||||
|
)
|
||||||
|
|
|
@ -9,9 +9,6 @@ module.exports = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
optimizeFonts: false,
|
optimizeFonts: false,
|
||||||
experimental: {
|
experimental: {
|
||||||
images: {
|
|
||||||
allowFutureImage: true,
|
|
||||||
},
|
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
externalDir: true,
|
externalDir: true,
|
||||||
modularizeImports: {
|
modularizeImports: {
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
"@floating-ui/react-dom-interactions": "0.9.2",
|
"@floating-ui/react-dom-interactions": "0.9.2",
|
||||||
"@headlessui/react": "1.6.1",
|
"@headlessui/react": "1.6.1",
|
||||||
"@heroicons/react": "1.0.5",
|
"@heroicons/react": "1.0.5",
|
||||||
"@nivo/core": "0.74.0",
|
"@nivo/core": "0.80.0",
|
||||||
"@nivo/line": "0.74.0",
|
"@nivo/line": "0.80.0",
|
||||||
"@nivo/tooltip": "0.74.0",
|
"@nivo/tooltip": "0.80.0",
|
||||||
"@react-query-firebase/firestore": "0.4.2",
|
"@react-query-firebase/firestore": "0.4.2",
|
||||||
"@tiptap/core": "2.0.0-beta.182",
|
"@tiptap/core": "2.0.0-beta.182",
|
||||||
"@tiptap/extension-character-count": "2.0.0-beta.31",
|
"@tiptap/extension-character-count": "2.0.0-beta.31",
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
"gridjs-react": "5.0.2",
|
"gridjs-react": "5.0.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"next": "12.2.5",
|
"next": "12.3.1",
|
||||||
"node-fetch": "3.2.4",
|
"node-fetch": "3.2.4",
|
||||||
"prosemirror-state": "1.4.1",
|
"prosemirror-state": "1.4.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
@ -58,6 +58,7 @@
|
||||||
"react-instantsearch-hooks-web": "6.24.1",
|
"react-instantsearch-hooks-web": "6.24.1",
|
||||||
"react-query": "3.39.0",
|
"react-query": "3.39.0",
|
||||||
"react-twitter-embed": "4.0.4",
|
"react-twitter-embed": "4.0.4",
|
||||||
|
"react-masonry-css": "1.0.16",
|
||||||
"string-similarity": "^4.0.4",
|
"string-similarity": "^4.0.4",
|
||||||
"tippy.js": "6.3.7"
|
"tippy.js": "6.3.7"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useEffect } from 'react'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import Script from 'next/script'
|
import Script from 'next/script'
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||||
import { AuthProvider } from 'web/components/auth-context'
|
import { AuthProvider, AuthUser } from 'web/components/auth-context'
|
||||||
import Welcome from 'web/components/onboarding/welcome'
|
import Welcome from 'web/components/onboarding/welcome'
|
||||||
|
|
||||||
function firstLine(msg: string) {
|
function firstLine(msg: string) {
|
||||||
|
@ -24,7 +24,10 @@ function printBuildInfo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
// specially treated props that may be present in the server/static props
|
||||||
|
type ManifoldPageProps = { auth?: AuthUser }
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }: AppProps<ManifoldPageProps>) {
|
||||||
useEffect(printBuildInfo, [])
|
useEffect(printBuildInfo, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -78,7 +81,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||||
</Head>
|
</Head>
|
||||||
<AuthProvider serverUser={pageProps.auth}>
|
<AuthProvider serverUser={pageProps.auth}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Welcome {...pageProps} />
|
<Welcome />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
@ -24,14 +24,14 @@ export default function AddFundsPage() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<SEO
|
<SEO
|
||||||
title="Get Manifold Dollars"
|
title="Get Mana"
|
||||||
description="Get Manifold Dollars"
|
description="Buy mana to trade in your favorite markets on Manifold"
|
||||||
url="/add-funds"
|
url="/add-funds"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Col className="items-center">
|
<Col className="items-center">
|
||||||
<Col className="h-full rounded bg-white p-4 py-8 sm:p-8 sm:shadow-md">
|
<Col className="h-full rounded bg-white p-4 py-8 sm:p-8 sm:shadow-md">
|
||||||
<Title className="!mt-0" text="Get Manifold Dollars" />
|
<Title className="!mt-0" text="Get Mana" />
|
||||||
<img
|
<img
|
||||||
className="mb-6 block -scale-x-100 self-center"
|
className="mb-6 block -scale-x-100 self-center"
|
||||||
src="/stylized-crane-black.png"
|
src="/stylized-crane-black.png"
|
||||||
|
@ -40,8 +40,8 @@ export default function AddFundsPage() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mb-6 text-gray-500">
|
<div className="mb-6 text-gray-500">
|
||||||
Purchase Manifold Dollars to trade in your favorite markets. <br />{' '}
|
Buy mana (M$) to trade in your favorite markets. <br /> (Not
|
||||||
(Not redeemable for cash.)
|
redeemable for cash.)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-2 text-sm text-gray-500">Amount</div>
|
<div className="mb-2 text-sm text-gray-500">Amount</div>
|
||||||
|
|
28
web/pages/api/v0/market/[id]/close.ts
Normal file
28
web/pages/api/v0/market/[id]/close.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import {
|
||||||
|
CORS_ORIGIN_MANIFOLD,
|
||||||
|
CORS_ORIGIN_LOCALHOST,
|
||||||
|
} from 'common/envs/constants'
|
||||||
|
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||||
|
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||||
|
|
||||||
|
export const config = { api: { bodyParser: true } }
|
||||||
|
|
||||||
|
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
await applyCorsHeaders(req, res, {
|
||||||
|
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||||
|
methods: 'POST',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { id } = req.query
|
||||||
|
const contractId = id as string
|
||||||
|
|
||||||
|
if (req.body) req.body.contractId = contractId
|
||||||
|
try {
|
||||||
|
const backendRes = await fetchBackend(req, 'closemarket')
|
||||||
|
await forwardResponse(res, backendRes)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error talking to cloud function: ', err)
|
||||||
|
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,7 +92,7 @@ export default function ChallengesListPage() {
|
||||||
tap the button above to create a new market & challenge in one.
|
tap the button above to create a new market & challenge in one.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Tabs tabs={[...userTab, ...publicTab]} />
|
<Tabs className="mb-4" tabs={[...userTab, ...publicTab]} />
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -2,14 +2,17 @@ import { ProbChangeTable } from 'web/components/contract/prob-change-table'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
import { useProbChangesAlgolia } from 'web/hooks/use-prob-changes'
|
import { useProbChanges } from 'web/hooks/use-prob-changes'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
|
|
||||||
export default function DailyMovers() {
|
export default function DailyMovers() {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
const bettorId = user?.id ?? undefined
|
||||||
|
|
||||||
const changes = useProbChangesAlgolia(user?.id ?? '')
|
const changes = useProbChanges({ bettorId })?.filter(
|
||||||
|
(c) => Math.abs(c.probChanges.day) >= 0.01
|
||||||
|
)
|
||||||
|
|
||||||
useTracking('view daily movers')
|
useTracking('view daily movers')
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { toast, Toaster } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
|
|
||||||
import { Group, GROUP_CHAT_SLUG } from 'common/group'
|
import { Group, GROUP_CHAT_SLUG } from 'common/group'
|
||||||
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
|
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
|
||||||
|
@ -16,7 +16,7 @@ import {
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { firebaseLogin, getUser, User } from 'web/lib/firebase/users'
|
import { firebaseLogin, getUser, User } from 'web/lib/firebase/users'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser, useUserById } from 'web/hooks/use-user'
|
||||||
import {
|
import {
|
||||||
useGroup,
|
useGroup,
|
||||||
useGroupContractIds,
|
useGroupContractIds,
|
||||||
|
@ -42,17 +42,21 @@ import { GroupComment } from 'common/comment'
|
||||||
import { REFERRAL_AMOUNT } from 'common/economy'
|
import { REFERRAL_AMOUNT } from 'common/economy'
|
||||||
import { UserLink } from 'web/components/user-link'
|
import { UserLink } from 'web/components/user-link'
|
||||||
import { GroupAboutPost } from 'web/components/groups/group-about-post'
|
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 { Post } from 'common/post'
|
||||||
import { Spacer } from 'web/components/layout/spacer'
|
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 { useAdmin } from 'web/hooks/use-admin'
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
import { GroupNavBar } from 'web/components/nav/group-nav-bar'
|
|
||||||
import { ArrowLeftIcon } from '@heroicons/react/solid'
|
import { ArrowLeftIcon } from '@heroicons/react/solid'
|
||||||
import { GroupSidebar } from 'web/components/nav/group-sidebar'
|
|
||||||
import { SelectMarketsModal } from 'web/components/contract-select-modal'
|
import { SelectMarketsModal } from 'web/components/contract-select-modal'
|
||||||
import { BETTORS } from 'common/user'
|
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 const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
|
@ -70,7 +74,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
? 'all'
|
? 'all'
|
||||||
: 'open'
|
: 'open'
|
||||||
const aboutPost =
|
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 messages = group && (await listAllCommentsOnGroup(group.id))
|
||||||
|
|
||||||
const cachedTopTraderIds =
|
const cachedTopTraderIds =
|
||||||
|
@ -83,6 +88,9 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
|
|
||||||
const creator = await creatorPromise
|
const creator = await creatorPromise
|
||||||
|
|
||||||
|
const posts = ((group && (await listPosts(group.postIds))) ?? []).filter(
|
||||||
|
(p) => p != null
|
||||||
|
) as Post[]
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
group,
|
group,
|
||||||
|
@ -93,6 +101,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
messages,
|
messages,
|
||||||
aboutPost,
|
aboutPost,
|
||||||
suggestedFilter,
|
suggestedFilter,
|
||||||
|
posts,
|
||||||
},
|
},
|
||||||
|
|
||||||
revalidate: 60, // regenerate after a minute
|
revalidate: 60, // regenerate after a minute
|
||||||
|
@ -107,17 +116,19 @@ const groupSubpages = [
|
||||||
'markets',
|
'markets',
|
||||||
'leaderboards',
|
'leaderboards',
|
||||||
'about',
|
'about',
|
||||||
|
'posts',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export default function GroupPage(props: {
|
export default function GroupPage(props: {
|
||||||
group: Group | null
|
group: Group | null
|
||||||
memberIds: string[]
|
memberIds: string[]
|
||||||
creator: User
|
creator: User | null
|
||||||
topTraders: { user: User; score: number }[]
|
topTraders: { user: User; score: number }[]
|
||||||
topCreators: { user: User; score: number }[]
|
topCreators: { user: User; score: number }[]
|
||||||
messages: GroupComment[]
|
messages: GroupComment[]
|
||||||
aboutPost: Post
|
aboutPost: Post | null
|
||||||
suggestedFilter: 'open' | 'all'
|
suggestedFilter: 'open' | 'all'
|
||||||
|
posts: Post[]
|
||||||
}) {
|
}) {
|
||||||
props = usePropz(props, getStaticPropz) ?? {
|
props = usePropz(props, getStaticPropz) ?? {
|
||||||
group: null,
|
group: null,
|
||||||
|
@ -127,37 +138,43 @@ export default function GroupPage(props: {
|
||||||
topCreators: [],
|
topCreators: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
suggestedFilter: 'open',
|
suggestedFilter: 'open',
|
||||||
|
posts: [],
|
||||||
}
|
}
|
||||||
const { creator, topTraders, topCreators, suggestedFilter } = props
|
const { creator, topTraders, topCreators, suggestedFilter, posts } = props
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { slugs } = router.query as { slugs: string[] }
|
const { slugs } = router.query as { slugs: string[] }
|
||||||
const page = slugs?.[1] as typeof groupSubpages[number]
|
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 group = useGroup(props.group?.id) ?? props.group
|
||||||
const aboutPost = usePost(props.aboutPost?.id) ?? props.aboutPost
|
const aboutPost = usePost(props.aboutPost?.id) ?? props.aboutPost
|
||||||
|
|
||||||
|
let groupPosts = usePosts(group?.postIds ?? []) ?? posts
|
||||||
|
|
||||||
|
if (aboutPost != null) {
|
||||||
|
groupPosts = [aboutPost, ...groupPosts]
|
||||||
|
}
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isAdmin = useAdmin()
|
const isAdmin = useAdmin()
|
||||||
const memberIds = useMemberIds(group?.id ?? null) ?? props.memberIds
|
const memberIds = useMemberIds(group?.id ?? null) ?? props.memberIds
|
||||||
// Note: Keep in sync with sidebarPages
|
|
||||||
const [sidebarIndex, setSidebarIndex] = useState(
|
|
||||||
['markets', 'leaderboards', 'about'].indexOf(page ?? 'markets')
|
|
||||||
)
|
|
||||||
|
|
||||||
useSaveReferral(user, {
|
useSaveReferral(user, {
|
||||||
defaultReferrerUsername: creator.username,
|
defaultReferrerUsername: creator?.username,
|
||||||
groupId: group?.id,
|
groupId: group?.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (group === null || !groupSubpages.includes(page) || slugs[2]) {
|
if (group === null || !groupSubpages.includes(page) || slugs[2] || !creator) {
|
||||||
return <Custom404 />
|
return <Custom404 />
|
||||||
}
|
}
|
||||||
const isCreator = user && group && user.id === group.creatorId
|
const isCreator = user && group && user.id === group.creatorId
|
||||||
const isMember = user && memberIds.includes(user.id)
|
const isMember = user && memberIds.includes(user.id)
|
||||||
const maxLeaderboardSize = 50
|
const maxLeaderboardSize = 50
|
||||||
|
|
||||||
const leaderboardPage = (
|
const leaderboardTab = (
|
||||||
<Col>
|
<Col>
|
||||||
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
|
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
|
||||||
<GroupLeaderboard
|
<GroupLeaderboard
|
||||||
|
@ -176,7 +193,17 @@ export default function GroupPage(props: {
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
|
||||||
const aboutPage = (
|
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>
|
<Col>
|
||||||
{(group.aboutPostId != null || isCreator || isAdmin) && (
|
{(group.aboutPostId != null || isCreator || isAdmin) && (
|
||||||
<GroupAboutPost
|
<GroupAboutPost
|
||||||
|
@ -196,16 +223,21 @@ export default function GroupPage(props: {
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
|
||||||
const questionsPage = (
|
const questionsTab = (
|
||||||
<>
|
<>
|
||||||
{/* align the divs to the right */}
|
<div className={'flex justify-end '}>
|
||||||
<div className={' flex justify-end px-2 pb-2 sm:hidden'}>
|
<div
|
||||||
<div>
|
className={
|
||||||
<JoinOrAddQuestionsButtons
|
'flex items-end justify-self-end px-2 md:absolute md:top-0 md:pb-2'
|
||||||
group={group}
|
}
|
||||||
user={user}
|
>
|
||||||
isMember={!!isMember}
|
<div>
|
||||||
/>
|
<JoinOrAddQuestionsButtons
|
||||||
|
group={group}
|
||||||
|
user={user}
|
||||||
|
isMember={!!isMember}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ContractSearch
|
<ContractSearch
|
||||||
|
@ -215,92 +247,47 @@ export default function GroupPage(props: {
|
||||||
defaultFilter={suggestedFilter}
|
defaultFilter={suggestedFilter}
|
||||||
additionalFilter={{ groupSlug: group.slug }}
|
additionalFilter={{ groupSlug: group.slug }}
|
||||||
persistPrefix={`group-${group.slug}`}
|
persistPrefix={`group-${group.slug}`}
|
||||||
|
includeProbSorts
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const sidebarPages = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
title: 'Markets',
|
title: 'Markets',
|
||||||
content: questionsPage,
|
content: questionsTab,
|
||||||
href: groupPath(group.slug, 'markets'),
|
|
||||||
key: 'markets',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Leaderboards',
|
title: 'Leaderboards',
|
||||||
content: leaderboardPage,
|
content: leaderboardTab,
|
||||||
href: groupPath(group.slug, 'leaderboards'),
|
|
||||||
key: 'leaderboards',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'About',
|
title: 'About',
|
||||||
content: aboutPage,
|
content: aboutTab,
|
||||||
href: groupPath(group.slug, 'about'),
|
},
|
||||||
key: 'about',
|
{
|
||||||
|
title: 'Posts',
|
||||||
|
content: postsPage,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const pageContent = sidebarPages[sidebarIndex].content
|
|
||||||
const onSidebarClick = (key: string) => {
|
|
||||||
const index = sidebarPages.findIndex((t) => t.key === key)
|
|
||||||
setSidebarIndex(index)
|
|
||||||
// Append the page to the URL, e.g. /group/mexifold/markets
|
|
||||||
router.replace(
|
|
||||||
{ query: { ...router.query, slugs: [group.slug, key] } },
|
|
||||||
undefined,
|
|
||||||
{ shallow: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinOrAddQuestionsButton = (
|
|
||||||
<JoinOrAddQuestionsButtons
|
|
||||||
group={group}
|
|
||||||
user={user}
|
|
||||||
isMember={!!isMember}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Page logoSubheading={group.name}>
|
||||||
<TopGroupNavBar
|
<SEO
|
||||||
group={group}
|
title={group.name}
|
||||||
currentPage={sidebarPages[sidebarIndex].key}
|
description={`Created by ${creator.name}. ${group.about}`}
|
||||||
onClick={onSidebarClick}
|
url={groupPath(group.slug)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<TopGroupNavBar group={group} />
|
||||||
<div
|
<div className={'relative p-2 pt-0 md:pt-2'}>
|
||||||
className={
|
{/* TODO: Switching tabs should also update the group path */}
|
||||||
'mx-auto w-full pb-[58px] lg:grid lg:grid-cols-12 lg:gap-x-2 lg:pb-0 xl:max-w-7xl xl:gap-x-8'
|
<Tabs className={'mb-2'} tabs={tabs} defaultIndex={tabIndex} />
|
||||||
}
|
|
||||||
>
|
|
||||||
<Toaster />
|
|
||||||
<GroupSidebar
|
|
||||||
groupName={group.name}
|
|
||||||
className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex"
|
|
||||||
onClick={onSidebarClick}
|
|
||||||
joinOrAddQuestionsButton={joinOrAddQuestionsButton}
|
|
||||||
currentKey={sidebarPages[sidebarIndex].key}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SEO
|
|
||||||
title={group.name}
|
|
||||||
description={`Created by ${creator.name}. ${group.about}`}
|
|
||||||
url={groupPath(group.slug)}
|
|
||||||
/>
|
|
||||||
<main className={'px-2 pt-1 lg:col-span-8 lg:pt-6 xl:col-span-8'}>
|
|
||||||
{pageContent}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopGroupNavBar(props: {
|
export function TopGroupNavBar(props: { group: Group }) {
|
||||||
group: Group
|
|
||||||
currentPage: string
|
|
||||||
onClick: (key: string) => void
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 w-full border-b border-gray-200 md:hidden lg:col-span-12">
|
<header className="sticky top-0 z-50 w-full border-b border-gray-200 md:hidden lg:col-span-12">
|
||||||
<div className="flex items-center bg-white px-4">
|
<div className="flex items-center bg-white px-4">
|
||||||
|
@ -317,7 +304,6 @@ export function TopGroupNavBar(props: {
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GroupNavBar currentPage={props.currentPage} onClick={props.onClick} />
|
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -330,11 +316,13 @@ function JoinOrAddQuestionsButtons(props: {
|
||||||
}) {
|
}) {
|
||||||
const { group, user, isMember } = props
|
const { group, user, isMember } = props
|
||||||
return user && isMember ? (
|
return user && isMember ? (
|
||||||
<Row className={'w-full self-start pt-4'}>
|
<Row className={'mb-2 w-full self-start md:mt-2 '}>
|
||||||
<AddContractButton group={group} user={user} />
|
<AddContractButton group={group} user={user} />
|
||||||
</Row>
|
</Row>
|
||||||
) : group.anyoneCanJoin ? (
|
) : group.anyoneCanJoin ? (
|
||||||
<JoinGroupButton group={group} user={user} />
|
<div className="mb-2 md:mb-0">
|
||||||
|
<JoinGroupButton group={group} user={user} />
|
||||||
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +439,7 @@ function GroupLeaderboard(props: {
|
||||||
return (
|
return (
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
className="max-w-xl"
|
className="max-w-xl"
|
||||||
users={topUsers.map((t) => t.user)}
|
entries={topUsers.map((t) => t.user)}
|
||||||
title={title}
|
title={title}
|
||||||
columns={[
|
columns={[
|
||||||
{ header, renderCell: (user) => formatMoney(scoresByUser[user.id]) },
|
{ header, renderCell: (user) => formatMoney(scoresByUser[user.id]) },
|
||||||
|
@ -461,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 }) {
|
function AddContractButton(props: { group: Group; user: User }) {
|
||||||
const { group, user } = props
|
const { group, user } = props
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
|
@ -99,8 +99,35 @@ export default function Groups(props: {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
currentPageForAnalytics={'groups'}
|
currentPageForAnalytics={'groups'}
|
||||||
tabs={[
|
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
|
...(user
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
@ -135,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>
|
</Col>
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default function Home() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups = useMemberGroupsSubscription(user)
|
const groups = useMemberGroupsSubscription(user)
|
||||||
const { sections } = getHomeItems(groups, homeSections)
|
const { sections } = getHomeItems(groups ?? [], homeSections)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/outline'
|
import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/outline'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { toast, Toaster } from 'react-hot-toast'
|
import { toast, Toaster } from 'react-hot-toast'
|
||||||
|
import { Dictionary } from 'lodash'
|
||||||
|
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
|
@ -31,11 +32,10 @@ import { ProbChangeTable } from 'web/components/contract/prob-change-table'
|
||||||
import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
||||||
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useProbChangesAlgolia } from 'web/hooks/use-prob-changes'
|
import { useProbChanges } from 'web/hooks/use-prob-changes'
|
||||||
import { ProfitBadge } from 'web/components/bets-list'
|
import { ProfitBadge } from 'web/components/bets-list'
|
||||||
import { calculatePortfolioProfit } from 'common/calculate-metrics'
|
import { calculatePortfolioProfit } from 'common/calculate-metrics'
|
||||||
import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal'
|
import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal'
|
||||||
import { useContractsQuery } from 'web/hooks/use-contracts'
|
|
||||||
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
import { ContractsGrid } from 'web/components/contract/contracts-grid'
|
||||||
import { PillButton } from 'web/components/buttons/pill-button'
|
import { PillButton } from 'web/components/buttons/pill-button'
|
||||||
import { filterDefined } from 'common/util/array'
|
import { filterDefined } from 'common/util/array'
|
||||||
|
@ -43,6 +43,9 @@ import { updateUser } from 'web/lib/firebase/users'
|
||||||
import { isArray, keyBy } from 'lodash'
|
import { isArray, keyBy } from 'lodash'
|
||||||
import { usePrefetch } from 'web/hooks/use-prefetch'
|
import { usePrefetch } from 'web/hooks/use-prefetch'
|
||||||
import { Title } from 'web/components/title'
|
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() {
|
export default function Home() {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
@ -52,22 +55,28 @@ export default function Home() {
|
||||||
useSaveReferral()
|
useSaveReferral()
|
||||||
usePrefetch(user?.id)
|
usePrefetch(user?.id)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user === null) {
|
||||||
|
// Go to landing page if not logged in.
|
||||||
|
Router.push('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const groups = useMemberGroupsSubscription(user)
|
const groups = useMemberGroupsSubscription(user)
|
||||||
|
|
||||||
const { sections } = getHomeItems(groups, user?.homeSections ?? [])
|
const { sections } = getHomeItems(groups ?? [], user?.homeSections ?? [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (user && !user.homeSections && sections.length > 0 && groups) {
|
||||||
user &&
|
|
||||||
!user.homeSections &&
|
|
||||||
sections.length > 0 &&
|
|
||||||
groups.length > 0
|
|
||||||
) {
|
|
||||||
// Save initial home sections.
|
// Save initial home sections.
|
||||||
updateUser(user.id, { homeSections: sections.map((s) => s.id) })
|
updateUser(user.id, { homeSections: sections.map((s) => s.id) })
|
||||||
}
|
}
|
||||||
}, [user, sections, groups])
|
}, [user, sections, groups])
|
||||||
|
|
||||||
|
const groupContracts = useContractsByDailyScoreGroups(
|
||||||
|
groups?.map((g) => g.slug)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
@ -81,9 +90,17 @@ export default function Home() {
|
||||||
<DailyStats user={user} />
|
<DailyStats user={user} />
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{sections.map((section) => renderSection(section, user, groups))}
|
{!user ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{sections.map((section) =>
|
||||||
|
renderSection(section, user, groups, groupContracts)
|
||||||
|
)}
|
||||||
|
|
||||||
<TrendingGroupsSection user={user} />
|
<TrendingGroupsSection user={user} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -101,9 +118,9 @@ export default function Home() {
|
||||||
|
|
||||||
const HOME_SECTIONS = [
|
const HOME_SECTIONS = [
|
||||||
{ label: 'Daily movers', id: 'daily-movers' },
|
{ label: 'Daily movers', id: 'daily-movers' },
|
||||||
|
{ label: 'Daily trending', id: 'daily-trending' },
|
||||||
{ label: 'Trending', id: 'score' },
|
{ label: 'Trending', id: 'score' },
|
||||||
{ label: 'New', id: 'newest' },
|
{ label: 'New', id: 'newest' },
|
||||||
{ label: 'Recently updated', id: 'recently-updated-for-you' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const getHomeItems = (groups: Group[], sections: string[]) => {
|
export const getHomeItems = (groups: Group[], sections: string[]) => {
|
||||||
|
@ -122,6 +139,10 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
|
||||||
|
|
||||||
const sectionItems = filterDefined(sections.map((id) => itemsById[id]))
|
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.
|
// Add unmentioned items to the end.
|
||||||
sectionItems.push(...items.filter((item) => !sectionItems.includes(item)))
|
sectionItems.push(...items.filter((item) => !sectionItems.includes(item)))
|
||||||
|
|
||||||
|
@ -133,19 +154,20 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
|
||||||
|
|
||||||
function renderSection(
|
function renderSection(
|
||||||
section: { id: string; label: string },
|
section: { id: string; label: string },
|
||||||
user: User | null | undefined,
|
user: User,
|
||||||
groups: Group[]
|
groups: Group[] | undefined,
|
||||||
|
groupContracts: Dictionary<CPMMBinaryContract[]> | undefined
|
||||||
) {
|
) {
|
||||||
const { id, label } = section
|
const { id, label } = section
|
||||||
if (id === 'daily-movers') {
|
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 (
|
return (
|
||||||
<SearchSection
|
<SearchSection
|
||||||
key={id}
|
key={id}
|
||||||
label={label}
|
label={label}
|
||||||
sort={'last-updated'}
|
sort={'daily-score'}
|
||||||
pill="personal"
|
pill="personal"
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
|
@ -156,8 +178,23 @@ function renderSection(
|
||||||
<SearchSection key={id} label={label} sort={sort.value} user={user} />
|
<SearchSection key={id} label={label} sort={sort.value} user={user} />
|
||||||
)
|
)
|
||||||
|
|
||||||
const group = groups.find((g) => g.id === id)
|
if (groups && groupContracts) {
|
||||||
if (group) return <GroupSection key={id} group={group} user={user} />
|
const group = groups.find((g) => g.id === id)
|
||||||
|
if (group) {
|
||||||
|
const contracts = groupContracts[group.slug].filter(
|
||||||
|
(c) => Math.abs(c.probChanges.day) >= 0.01
|
||||||
|
)
|
||||||
|
if (contracts.length === 0) return null
|
||||||
|
return (
|
||||||
|
<GroupSection
|
||||||
|
key={id}
|
||||||
|
group={group}
|
||||||
|
user={user}
|
||||||
|
contracts={contracts}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -189,7 +226,7 @@ function SectionHeader(props: {
|
||||||
|
|
||||||
function SearchSection(props: {
|
function SearchSection(props: {
|
||||||
label: string
|
label: string
|
||||||
user: User | null | undefined | undefined
|
user: User
|
||||||
sort: Sort
|
sort: Sort
|
||||||
pill?: string
|
pill?: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -207,7 +244,6 @@ function SearchSection(props: {
|
||||||
defaultPill={pill}
|
defaultPill={pill}
|
||||||
noControls
|
noControls
|
||||||
maxResults={6}
|
maxResults={6}
|
||||||
headerClassName="sticky"
|
|
||||||
persistPrefix={`home-${sort}`}
|
persistPrefix={`home-${sort}`}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -216,11 +252,10 @@ function SearchSection(props: {
|
||||||
|
|
||||||
function GroupSection(props: {
|
function GroupSection(props: {
|
||||||
group: Group
|
group: Group
|
||||||
user: User | null | undefined | undefined
|
user: User
|
||||||
|
contracts: CPMMBinaryContract[]
|
||||||
}) {
|
}) {
|
||||||
const { group, user } = props
|
const { group, user, contracts } = props
|
||||||
|
|
||||||
const contracts = useContractsQuery('score', 4, { groupSlug: group.slug })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
|
@ -228,39 +263,37 @@ function GroupSection(props: {
|
||||||
<Button
|
<Button
|
||||||
color="gray-white"
|
color="gray-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (user) {
|
const homeSections = (user.homeSections ?? []).filter(
|
||||||
const homeSections = (user.homeSections ?? []).filter(
|
(id) => id !== group.id
|
||||||
(id) => id !== group.id
|
)
|
||||||
)
|
updateUser(user.id, { homeSections })
|
||||||
updateUser(user.id, { homeSections })
|
|
||||||
|
|
||||||
toast.promise(leaveGroup(group, user.id), {
|
toast.promise(leaveGroup(group, user.id), {
|
||||||
loading: 'Unfollowing group...',
|
loading: 'Unfollowing group...',
|
||||||
success: `Unfollowed ${group.name}`,
|
success: `Unfollowed ${group.name}`,
|
||||||
error: "Couldn't unfollow group, try again?",
|
error: "Couldn't unfollow group, try again?",
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<XCircleIcon className={'h-5 w-5 flex-shrink-0'} aria-hidden="true" />
|
<XCircleIcon className={'h-5 w-5 flex-shrink-0'} aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
<ContractsGrid contracts={contracts} />
|
<ContractsGrid
|
||||||
|
contracts={contracts.slice(0, 4)}
|
||||||
|
cardUIOptions={{ showProbChange: true }}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DailyMoversSection(props: { userId: string | null | undefined }) {
|
function DailyMoversSection(props: { userId: string | null | undefined }) {
|
||||||
const { userId } = props
|
const { userId } = props
|
||||||
const changes = useProbChangesAlgolia(userId ?? '')
|
const changes = useProbChanges({ bettorId: userId ?? undefined })?.filter(
|
||||||
|
(c) => Math.abs(c.probChanges.day) >= 0.01
|
||||||
|
)
|
||||||
|
|
||||||
if (changes) {
|
if (changes && changes.length === 0) {
|
||||||
const { positiveChanges, negativeChanges } = changes
|
return null
|
||||||
if (
|
|
||||||
!positiveChanges.find((c) => c.probChanges.day >= 0.01) ||
|
|
||||||
!negativeChanges.find((c) => c.probChanges.day <= -0.01)
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -332,6 +365,10 @@ export function TrendingGroupsSection(props: {
|
||||||
const count = full ? 100 : 25
|
const count = full ? 100 : 25
|
||||||
const chosenGroups = groups.slice(0, count)
|
const chosenGroups = groups.slice(0, count)
|
||||||
|
|
||||||
|
if (chosenGroups.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={className}>
|
<Col className={className}>
|
||||||
<SectionHeader label="Trending groups" href="/explore-groups">
|
<SectionHeader label="Trending groups" href="/explore-groups">
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default function Leaderboards(_props: {
|
||||||
<Col className="mx-4 items-center gap-10 lg:flex-row">
|
<Col className="mx-4 items-center gap-10 lg:flex-row">
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title={`🏅 Top ${BETTORS}`}
|
title={`🏅 Top ${BETTORS}`}
|
||||||
users={topTraders}
|
entries={topTraders}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Total profit',
|
header: 'Total profit',
|
||||||
|
@ -92,7 +92,7 @@ export default function Leaderboards(_props: {
|
||||||
|
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title="🏅 Top creators"
|
title="🏅 Top creators"
|
||||||
users={topCreators}
|
entries={topCreators}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Total bet',
|
header: 'Total bet',
|
||||||
|
@ -106,7 +106,7 @@ export default function Leaderboards(_props: {
|
||||||
<Col className="mx-4 my-10 items-center gap-10 lg:mx-0 lg:w-1/2 lg:flex-row">
|
<Col className="mx-4 my-10 items-center gap-10 lg:mx-0 lg:w-1/2 lg:flex-row">
|
||||||
<Leaderboard
|
<Leaderboard
|
||||||
title="🏅 Top followed"
|
title="🏅 Top followed"
|
||||||
users={topFollowed}
|
entries={topFollowed}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Total followers',
|
header: 'Total followers',
|
||||||
|
@ -132,6 +132,7 @@ export default function Leaderboards(_props: {
|
||||||
/>
|
/>
|
||||||
<Title text={'Leaderboards'} className={'hidden md:block'} />
|
<Title text={'Leaderboards'} className={'hidden md:block'} />
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
currentPageForAnalytics={'leaderboards'}
|
currentPageForAnalytics={'leaderboards'}
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
|
|
|
@ -971,6 +971,8 @@ function ContractResolvedNotification(props: {
|
||||||
const { sourceText, data } = notification
|
const { sourceText, data } = notification
|
||||||
const { userInvestment, userPayout } = (data as ContractResolutionData) ?? {}
|
const { userInvestment, userPayout } = (data as ContractResolutionData) ?? {}
|
||||||
const subtitle = 'resolved the market'
|
const subtitle = 'resolved the market'
|
||||||
|
const profitable = userPayout >= userInvestment
|
||||||
|
const ROI = (userPayout - userInvestment) / userInvestment
|
||||||
|
|
||||||
const resolutionDescription = () => {
|
const resolutionDescription = () => {
|
||||||
if (!sourceText) return <div />
|
if (!sourceText) return <div />
|
||||||
|
@ -1002,23 +1004,21 @@ function ContractResolvedNotification(props: {
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
userInvestment && userPayout !== undefined ? (
|
userInvestment && userPayout !== undefined ? (
|
||||||
<Row className={'gap-1 '}>
|
<>
|
||||||
Resolved: {resolutionDescription()}
|
Resolved: {resolutionDescription()} Invested:
|
||||||
Invested:
|
|
||||||
<span className={'text-primary'}>{formatMoney(userInvestment)} </span>
|
<span className={'text-primary'}>{formatMoney(userInvestment)} </span>
|
||||||
Payout:
|
Payout:
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
userPayout > 0 ? 'text-primary' : 'text-red-500',
|
profitable ? 'text-primary' : 'text-red-500',
|
||||||
'truncate'
|
'truncate text-ellipsis'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{formatMoney(userPayout)}
|
{formatMoney(userPayout)}
|
||||||
{` (${userPayout > 0 ? '+' : ''}${Math.round(
|
{userPayout > 0 &&
|
||||||
((userPayout - userInvestment) / userInvestment) * 100
|
` (${profitable ? '+' : ''}${Math.round(ROI * 100)}%)`}
|
||||||
)}%)`}
|
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span>Resolved {resolutionDescription()}</span>
|
<span>Resolved {resolutionDescription()}</span>
|
||||||
)
|
)
|
||||||
|
@ -1038,9 +1038,7 @@ function ContractResolvedNotification(props: {
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
subtitle={subtitle}
|
subtitle={subtitle}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row className={'line-clamp-2 space-x-1'}>{description}</Row>
|
||||||
<span>{description}</span>
|
|
||||||
</Row>
|
|
||||||
</NotificationFrame>
|
</NotificationFrame>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ export default function Analytics() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
currentPageForAnalytics={'stats'}
|
currentPageForAnalytics={'stats'}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -89,6 +90,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -141,6 +143,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
period?
|
period?
|
||||||
</p>
|
</p>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -198,6 +201,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={2}
|
defaultIndex={2}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -239,6 +243,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
|
|
||||||
<Title text="Daily activity" />
|
<Title text="Daily activity" />
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={0}
|
defaultIndex={0}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -293,6 +298,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -323,6 +329,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
|
|
||||||
<Title text="Ratio of Active Users" />
|
<Title text="Ratio of Active Users" />
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
@ -367,6 +374,7 @@ export function CustomAnalytics(props: Stats) {
|
||||||
Sum of bet amounts. (Divided by 100 to be more readable.)
|
Sum of bet amounts. (Divided by 100 to be more readable.)
|
||||||
</p>
|
</p>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="mb-4"
|
||||||
defaultIndex={1}
|
defaultIndex={1}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -107,7 +107,14 @@ const tourneys: Tourney[] = [
|
||||||
groupId: 'SxGRqXRpV3RAQKudbcNb',
|
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',
|
title: 'SF 2022 Ballot',
|
||||||
blurb: 'Which ballot initiatives will pass this year in SF and CA?',
|
blurb: 'Which ballot initiatives will pass this year in SF and CA?',
|
||||||
|
|
|
@ -92,7 +92,7 @@ export function PostCommentInput(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommentInput
|
<CommentInput
|
||||||
replyToUser={replyToUser}
|
replyTo={replyToUser}
|
||||||
parentCommentId={parentCommentId}
|
parentCommentId={parentCommentId}
|
||||||
onSubmitComment={onSubmitComment}
|
onSubmitComment={onSubmitComment}
|
||||||
/>
|
/>
|
||||||
|
|
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: {
|
extend: {
|
||||||
backgroundImage: {
|
|
||||||
'world-trading': "url('/world-trading-background.webp')",
|
|
||||||
},
|
|
||||||
colors: {
|
colors: {
|
||||||
'greyscale-1': '#FBFBFF',
|
'greyscale-1': '#FBFBFF',
|
||||||
'greyscale-2': '#E7E7F4',
|
'greyscale-2': '#E7E7F4',
|
||||||
|
|
569
yarn.lock
569
yarn.lock
|
@ -2476,10 +2476,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b"
|
resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b"
|
||||||
integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==
|
integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==
|
||||||
|
|
||||||
"@next/env@12.2.5":
|
"@next/env@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.2.5.tgz#d908c57b35262b94db3e431e869b72ac3e1ad3e3"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.1.tgz#18266bd92de3b4aa4037b1927aa59e6f11879260"
|
||||||
integrity sha512-vLPLV3cpPGjUPT3PjgRj7e3nio9t6USkuew3JE/jMeon/9Mvp1WyR18v3iwnCuX7eUAm1HmAbJHHLAbcu/EJcw==
|
integrity sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==
|
||||||
|
|
||||||
"@next/eslint-plugin-next@12.1.6":
|
"@next/eslint-plugin-next@12.1.6":
|
||||||
version "12.1.6"
|
version "12.1.6"
|
||||||
|
@ -2488,167 +2488,164 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
glob "7.1.7"
|
glob "7.1.7"
|
||||||
|
|
||||||
"@next/swc-android-arm-eabi@12.2.5":
|
"@next/swc-android-arm-eabi@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.5.tgz#903a5479ab4c2705d9c08d080907475f7bacf94d"
|
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz#b15ce8ad376102a3b8c0f3c017dde050a22bb1a3"
|
||||||
integrity sha512-cPWClKxGhgn2dLWnspW+7psl3MoLQUcNqJqOHk2BhNcou9ARDtC0IjQkKe5qcn9qg7I7U83Gp1yh2aesZfZJMA==
|
integrity sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==
|
||||||
|
|
||||||
"@next/swc-android-arm64@12.2.5":
|
"@next/swc-android-arm64@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.2.5.tgz#2f9a98ec4166c7860510963b31bda1f57a77c792"
|
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz#85d205f568a790a137cb3c3f720d961a2436ac9c"
|
||||||
integrity sha512-vMj0efliXmC5b7p+wfcQCX0AfU8IypjkzT64GiKJD9PgiA3IILNiGJr1fw2lyUDHkjeWx/5HMlMEpLnTsQslwg==
|
integrity sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@12.2.5":
|
"@next/swc-darwin-arm64@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.5.tgz#31b1c3c659d54be546120c488a1e1bad21c24a1d"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz#b105457d6760a7916b27e46c97cb1a40547114ae"
|
||||||
integrity sha512-VOPWbO5EFr6snla/WcxUKtvzGVShfs302TEMOtzYyWni6f9zuOetijJvVh9CCTzInnXAZMtHyNhefijA4HMYLg==
|
integrity sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==
|
||||||
|
|
||||||
"@next/swc-darwin-x64@12.2.5":
|
"@next/swc-darwin-x64@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.5.tgz#2e44dd82b2b7fef88238d1bc4d3bead5884cedfd"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz#6947b39082271378896b095b6696a7791c6e32b1"
|
||||||
integrity sha512-5o8bTCgAmtYOgauO/Xd27vW52G2/m3i5PX7MUYePquxXAnX73AAtqA3WgPXBRitEB60plSKZgOTkcpqrsh546A==
|
integrity sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==
|
||||||
|
|
||||||
"@next/swc-freebsd-x64@12.2.5":
|
"@next/swc-freebsd-x64@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.5.tgz#e24e75d8c2581bfebc75e4f08f6ddbd116ce9dbd"
|
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz#2b6c36a4d84aae8b0ea0e0da9bafc696ae27085a"
|
||||||
integrity sha512-yYUbyup1JnznMtEBRkK4LT56N0lfK5qNTzr6/DEyDw5TbFVwnuy2hhLBzwCBkScFVjpFdfiC6SQAX3FrAZzuuw==
|
integrity sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==
|
||||||
|
|
||||||
"@next/swc-linux-arm-gnueabihf@12.2.5":
|
"@next/swc-linux-arm-gnueabihf@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.5.tgz#46d8c514d834d2b5f67086013f0bd5e3081e10b9"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz#6e421c44285cfedac1f4631d5de330dd60b86298"
|
||||||
integrity sha512-2ZE2/G921Acks7UopJZVMgKLdm4vN4U0yuzvAMJ6KBavPzqESA2yHJlm85TV/K9gIjKhSk5BVtauIUntFRP8cg==
|
integrity sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@12.2.5":
|
"@next/swc-linux-arm64-gnu@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.5.tgz#91f725ac217d3a1f4f9f53b553615ba582fd3d9f"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz#8863f08a81f422f910af126159d2cbb9552ef717"
|
||||||
integrity sha512-/I6+PWVlz2wkTdWqhlSYYJ1pWWgUVva6SgX353oqTh8njNQp1SdFQuWDqk8LnM6ulheVfSsgkDzxrDaAQZnzjQ==
|
integrity sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@12.2.5":
|
"@next/swc-linux-arm64-musl@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.5.tgz#e627e8c867920995810250303cd9b8e963598383"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz#0038f07cf0b259d70ae0c80890d826dfc775d9f3"
|
||||||
integrity sha512-LPQRelfX6asXyVr59p5sTpx5l+0yh2Vjp/R8Wi4X9pnqcayqT4CUJLiHqCvZuLin3IsFdisJL0rKHMoaZLRfmg==
|
integrity sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@12.2.5":
|
"@next/swc-linux-x64-gnu@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.5.tgz#83a5e224fbc4d119ef2e0f29d0d79c40cc43887e"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz#c66468f5e8181ffb096c537f0dbfb589baa6a9c1"
|
||||||
integrity sha512-0szyAo8jMCClkjNK0hknjhmAngUppoRekW6OAezbEYwHXN/VNtsXbfzgYOqjKWxEx3OoAzrT3jLwAF0HdX2MEw==
|
integrity sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@12.2.5":
|
"@next/swc-linux-x64-musl@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.5.tgz#be700d48471baac1ec2e9539396625584a317e95"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz#c6269f3e96ac0395bc722ad97ce410ea5101d305"
|
||||||
integrity sha512-zg/Y6oBar1yVnW6Il1I/08/2ukWtOG6s3acdJdEyIdsCzyQi4RLxbbhkD/EGQyhqBvd3QrC6ZXQEXighQUAZ0g==
|
integrity sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@12.2.5":
|
"@next/swc-win32-arm64-msvc@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.5.tgz#a93e958133ad3310373fda33a79aa10af2a0aa97"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz#83c639ee969cee36ce247c3abd1d9df97b5ecade"
|
||||||
integrity sha512-3/90DRNSqeeSRMMEhj4gHHQlLhhKg5SCCoYfE3kBjGpE63EfnblYUqsszGGZ9ekpKL/R4/SGB40iCQr8tR5Jiw==
|
integrity sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==
|
||||||
|
|
||||||
"@next/swc-win32-ia32-msvc@12.2.5":
|
"@next/swc-win32-ia32-msvc@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.5.tgz#4f5f7ba0a98ff89a883625d4af0125baed8b2e19"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz#52995748b92aa8ad053440301bc2c0d9fbcf27c2"
|
||||||
integrity sha512-hGLc0ZRAwnaPL4ulwpp4D2RxmkHQLuI8CFOEEHdzZpS63/hMVzv81g8jzYA0UXbb9pus/iTc3VRbVbAM03SRrw==
|
integrity sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@12.2.5":
|
"@next/swc-win32-x64-msvc@12.3.1":
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.5.tgz#20fed129b04a0d3f632c6d0de135345bb623b1e4"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
|
||||||
integrity sha512-7h5/ahY7NeaO2xygqVrSG/Y8Vs4cdjxIjowTZ5W6CKoTKn7tmnuxlUc2h74x06FKmbhAd9agOjr/AOKyxYYm9Q==
|
integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
|
||||||
|
|
||||||
"@nivo/annotations@0.74.0":
|
"@nivo/annotations@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.74.0.tgz#f4a3474fdf8812c3812c30e08d3277e209bec0f6"
|
resolved "https://registry.yarnpkg.com/@nivo/annotations/-/annotations-0.80.0.tgz#127e4801fff7370dcfb9acfe1e335781dd65cfd5"
|
||||||
integrity sha512-nxZLKDi9YEy2zZUsOtbYL/2oAgsxK5SVZ1P3Csll+cQ96uLU6sU7jmb67AwK0nDbYk7BD3sZf/O/A9r/MCK4Ow==
|
integrity sha512-bC9z0CLjU07LULTMWsqpjovRtHxP7n8oJjqBQBLmHOGB4IfiLbrryBfu9+aEZH3VN2jXHhdpWUz+HxeZzOzsLg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nivo/colors" "0.74.0"
|
"@nivo/colors" "0.80.0"
|
||||||
"@react-spring/web" "9.2.6"
|
"@react-spring/web" "9.4.5"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
"@nivo/axes@0.74.0":
|
"@nivo/axes@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.74.0.tgz#cf7cf2277b7aca5449a040ddf3e0cf9891971199"
|
resolved "https://registry.yarnpkg.com/@nivo/axes/-/axes-0.80.0.tgz#22788855ddc45bb6a619dcd03d62d4bd8c0fc35f"
|
||||||
integrity sha512-27o1H+Br0AaeUTiRhy7OebqzYEWr1xznHOxd+Hn2Xz9kK1alGBiPgwXrkXV0Q9CtrsroQFnX2QR3JxRgOtC5fA==
|
integrity sha512-AsUyaSHGwQVSEK8QXpsn8X+poZxvakLMYW7crKY1xTGPNw+SU4SSBohPVumm2jMH3fTSLNxLhAjWo71GBJXfdA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nivo/scales" "0.74.0"
|
"@nivo/scales" "0.80.0"
|
||||||
"@react-spring/web" "9.2.6"
|
"@react-spring/web" "9.4.5"
|
||||||
d3-format "^1.4.4"
|
d3-format "^1.4.4"
|
||||||
d3-time-format "^3.0.0"
|
d3-time-format "^3.0.0"
|
||||||
|
|
||||||
"@nivo/colors@0.74.0":
|
"@nivo/colors@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.74.0.tgz#29d1e7c6f3bcab4e872a168651b3a90cfba03a4f"
|
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.80.0.tgz#5b70b4979df246d9d0d69fb638bba9764dd88b52"
|
||||||
integrity sha512-5ClckmBm3x2XdJqHMylr6erY+scEL/twoGVfyXak/L+AIhL+Gf9PQxyxyfl3Lbtc3SPeAQe0ZAO1+VrmTn7qlA==
|
integrity sha512-T695Zr411FU4RPo7WDINOAn8f79DPP10SFJmDdEqELE+cbzYVTpXqLGZ7JMv88ko7EOf9qxLQgcBqY69rp9tHQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-color "^2.0.0"
|
d3-color "^2.0.0"
|
||||||
d3-scale "^3.2.3"
|
d3-scale "^3.2.3"
|
||||||
d3-scale-chromatic "^2.0.0"
|
d3-scale-chromatic "^2.0.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
react-motion "^0.5.2"
|
|
||||||
|
|
||||||
"@nivo/core@0.74.0":
|
"@nivo/core@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.74.0.tgz#7634c78a36a8bd50a0c04c6b6f12b779a88ec2f4"
|
resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.80.0.tgz#d180cb2622158eb7bc5f984131ff07984f12297e"
|
||||||
integrity sha512-LZ3kN1PiEW0KU4PTBgaHFO757amyKZkEL4mKdAzvyNQtpq5idB3OhC/sYrBxhJaLqYcX19MgNfhIel/0KygHAg==
|
integrity sha512-6caih0RavXdWWSfde+rC2pk17WrX9YQlqK26BrxIdXzv3Ydzlh5SkrC7dR2TEvMGBhunzVeLOfiC2DWT1S8CFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nivo/recompose" "0.74.0"
|
"@nivo/recompose" "0.80.0"
|
||||||
"@react-spring/web" "9.2.6"
|
"@react-spring/web" "9.4.5"
|
||||||
d3-color "^2.0.0"
|
d3-color "^2.0.0"
|
||||||
d3-format "^1.4.4"
|
d3-format "^1.4.4"
|
||||||
d3-hierarchy "^1.1.8"
|
|
||||||
d3-interpolate "^2.0.1"
|
d3-interpolate "^2.0.1"
|
||||||
d3-scale "^3.2.3"
|
d3-scale "^3.2.3"
|
||||||
d3-scale-chromatic "^2.0.0"
|
d3-scale-chromatic "^2.0.0"
|
||||||
d3-shape "^1.3.5"
|
d3-shape "^1.3.5"
|
||||||
d3-time-format "^3.0.0"
|
d3-time-format "^3.0.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
resize-observer-polyfill "^1.5.1"
|
|
||||||
|
|
||||||
"@nivo/legends@0.74.0":
|
"@nivo/legends@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.74.0.tgz#8e5e04b2a3f980c2073a394d94c4d89fa8bc8724"
|
resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.80.0.tgz#49edc54000075b4df055f86794a8c32810269d06"
|
||||||
integrity sha512-Bfk392ngre1C8UaGoymwqK0acjjzuk0cglUSNsr0z8BAUQIVGUPthtfcxbq/yUYGJL/cxWky2QKxi9r3C0FbmA==
|
integrity sha512-h0IUIPGygpbKIZZZWIxkkxOw4SO0rqPrqDrykjaoQz4CvL4HtLIUS3YRA4akKOVNZfS5agmImjzvIe0s3RvqlQ==
|
||||||
|
|
||||||
"@nivo/line@0.74.0":
|
"@nivo/line@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.74.0.tgz#f1f430d64a81d2fe1a5fd49e5cfaa61242066927"
|
resolved "https://registry.yarnpkg.com/@nivo/line/-/line-0.80.0.tgz#ba541b0fcfd53b3a7ce865feb43c993b7cf4a7d4"
|
||||||
integrity sha512-uJssLII1UTfxrZkPrkki054LFUpSKeqS35ttwK6VLvyqs5r3SrSXn223vDRNaaxuop5oT/L3APUJQwQDqUcj3w==
|
integrity sha512-6UAD/y74qq3DDRnVb+QUPvXYojxMtwXMipGSNvCGk8omv1QZNTaUrbV+eQacvn9yh//a0yZcWipnpq0tGJyJCA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nivo/annotations" "0.74.0"
|
"@nivo/annotations" "0.80.0"
|
||||||
"@nivo/axes" "0.74.0"
|
"@nivo/axes" "0.80.0"
|
||||||
"@nivo/colors" "0.74.0"
|
"@nivo/colors" "0.80.0"
|
||||||
"@nivo/legends" "0.74.0"
|
"@nivo/legends" "0.80.0"
|
||||||
"@nivo/scales" "0.74.0"
|
"@nivo/scales" "0.80.0"
|
||||||
"@nivo/tooltip" "0.74.0"
|
"@nivo/tooltip" "0.80.0"
|
||||||
"@nivo/voronoi" "0.74.0"
|
"@nivo/voronoi" "0.80.0"
|
||||||
"@react-spring/web" "9.2.6"
|
"@react-spring/web" "9.4.5"
|
||||||
d3-shape "^1.3.5"
|
d3-shape "^1.3.5"
|
||||||
|
|
||||||
"@nivo/recompose@0.74.0":
|
"@nivo/recompose@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.74.0.tgz#057e8e1154073d7f4cb01aa8d165c3914b8bdb54"
|
resolved "https://registry.yarnpkg.com/@nivo/recompose/-/recompose-0.80.0.tgz#572048aed793321a0bada1fd176b72df5a25282e"
|
||||||
integrity sha512-qC9gzGvDIxocrJoozDjqqffOwDpuEZijeMV59KExnztCwIpQbIYVBsDdpvL+tXfWausigSlnGILGfereXJTLUQ==
|
integrity sha512-iL3g7j3nJGD9+mRDbwNwt/IXDXH6E29mhShY1I7SP91xrfusZV9pSFf4EzyYgruNJk/2iqMuaqn+e+TVFra44A==
|
||||||
dependencies:
|
dependencies:
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
"@nivo/scales@0.74.0":
|
"@nivo/scales@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.74.0.tgz#ede12b899da9e3aee7921ebce40f227e670a430d"
|
resolved "https://registry.yarnpkg.com/@nivo/scales/-/scales-0.80.0.tgz#39313fb97c8ae9633c2aa1e17adb57cb851e8a50"
|
||||||
integrity sha512-5mER71NgZGdgs8X2PgilBpAWMMGtTXrUuYOBQWDKDMgtc83MU+mphhiYfLv5e6ViZyUB5ebfEkfeIgStLqrcEA==
|
integrity sha512-4y2pQdCg+f3n4TKXC2tYuq71veZM+xPRQbOTgGYJpuBvMc7pQsXF9T5z7ryeIG9hkpXkrlyjecU6XcAG7tLSNg==
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-scale "^3.2.3"
|
d3-scale "^3.2.3"
|
||||||
d3-time "^1.0.11"
|
d3-time "^1.0.11"
|
||||||
d3-time-format "^3.0.0"
|
d3-time-format "^3.0.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
"@nivo/tooltip@0.74.0":
|
"@nivo/tooltip@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.74.0.tgz#60d94b0fecc2fc179ada3efa380e7e456982b4a5"
|
resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.80.0.tgz#07ebef47eb708a0612bd6297d5ad156bbec19d34"
|
||||||
integrity sha512-h3PUgNFF5HUeQFfx19MWS1uGK8iUDymZNY+5PyaCWDFT+0/ldXBu8uw5WYRui2KwNdTym6F0E/aT7JKczDd85w==
|
integrity sha512-qGmrreRwnCsYjn/LAuwBtxBn/tvG8y+rwgd4gkANLBAoXd3bzJyvmkSe+QJPhUG64bq57ibDK+lO2pC48a3/fw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/web" "9.2.6"
|
"@react-spring/web" "9.4.5"
|
||||||
|
|
||||||
"@nivo/voronoi@0.74.0":
|
"@nivo/voronoi@0.80.0":
|
||||||
version "0.74.0"
|
version "0.80.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.74.0.tgz#4b427955ddabd86934a2bbb95a62ff53ee97c575"
|
resolved "https://registry.yarnpkg.com/@nivo/voronoi/-/voronoi-0.80.0.tgz#59cc7ed253dc1a5bbcf614a5ac37d2468d561599"
|
||||||
integrity sha512-Q3267T1+Tlufn8LbmSYnO8x9gL+h/iwH2Uqc5CENHSZu2KPD0PB82vxpQnbDVhjadulI0rlrPA9fU3VY3q1zKg==
|
integrity sha512-zaJV3I3cRu1gHpsXCIEvp6GGlGY8P7D9CwAVCjYDGrz3W/+GKN0kA7qGyHTC97zVxJtfefxSPlP/GtOdxac+qw==
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-delaunay "^5.3.0"
|
d3-delaunay "^5.3.0"
|
||||||
d3-scale "^3.2.3"
|
d3-scale "^3.2.3"
|
||||||
|
@ -2747,50 +2744,51 @@
|
||||||
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
|
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
|
||||||
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
|
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
|
||||||
|
|
||||||
"@react-spring/animated@~9.2.6-beta.0":
|
"@react-spring/animated@~9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.2.6.tgz#58f30fb75d8bfb7ccbc156cfd6b974a8f3dfd54e"
|
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54"
|
||||||
integrity sha512-xjL6nmixYNDvnpTs1FFMsMfSC0tURwPCU3b2jWNriYGLfwZ7c/TcyaEZA7yiNnmdFnuR3f3Z27AqIgaFC083Cw==
|
integrity sha512-KWqrtvJSMx6Fj9nMJkhTwM9r6LIriExDRV6YHZV9HKQsaolUFppgkOXpC+rsL1JEtEvKv6EkLLmSqHTnuYjiIA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/shared" "~9.2.6-beta.0"
|
"@react-spring/shared" "~9.4.5"
|
||||||
"@react-spring/types" "~9.2.6-beta.0"
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@react-spring/core@~9.2.6-beta.0":
|
"@react-spring/core@~9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.2.6.tgz#ae22338fe55d070caf03abb4293b5519ba620d93"
|
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.5.tgz#4616e1adc18dd10f5731f100ebdbe9518b89ba3c"
|
||||||
integrity sha512-uPHUxmu+w6mHJrfQTMtmGJ8iZEwiVxz9kH7dRyk69bkZJt9z+w0Oj3UF4J3VcECZsbm3HRhN2ogXSAaqGjwhQw==
|
integrity sha512-83u3FzfQmGMJFwZLAJSwF24/ZJctwUkWtyPD7KYtNagrFeQKUH1I05ZuhmCmqW+2w1KDW1SFWQ43RawqfXKiiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/animated" "~9.2.6-beta.0"
|
"@react-spring/animated" "~9.4.5"
|
||||||
"@react-spring/shared" "~9.2.6-beta.0"
|
"@react-spring/rafz" "~9.4.5"
|
||||||
"@react-spring/types" "~9.2.6-beta.0"
|
"@react-spring/shared" "~9.4.5"
|
||||||
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@react-spring/rafz@~9.2.6-beta.0":
|
"@react-spring/rafz@~9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.2.6.tgz#d97484003875bf5fb5e6ec22dee97cc208363e48"
|
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.5.tgz#84f809f287f2a66bbfbc66195db340482f886bd7"
|
||||||
integrity sha512-62SivLKEpo7EfHPkxO5J3g9Cr9LF6+1A1RVOMJhkcpEYtbdbmma/d63Xp8qpMPEpk7uuWxaTb6jjyxW33pW3sg==
|
integrity sha512-swGsutMwvnoyTRxvqhfJBtGM8Ipx6ks0RkIpNX9F/U7XmyPvBMGd3GgX/mqxZUpdlsuI1zr/jiYw+GXZxAlLcQ==
|
||||||
|
|
||||||
"@react-spring/shared@~9.2.6-beta.0":
|
"@react-spring/shared@~9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.2.6.tgz#2c84e62cc0cfbbbbeb5546acd46c1f4b248bc562"
|
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.5.tgz#4c3ad817bca547984fb1539204d752a412a6d829"
|
||||||
integrity sha512-Qrm9fopKG/RxZ3Rw+4euhrpnB3uXSyiON9skHbcBfmkkzagpkUR66MX1YLrhHw0UchcZuSDnXs0Lonzt1rpWag==
|
integrity sha512-JhMh3nFKsqyag0KM5IIM8BQANGscTdd0mMv3BXsUiMZrcjQTskyfnv5qxEeGWbJGGar52qr5kHuBHtCjQOzniA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/rafz" "~9.2.6-beta.0"
|
"@react-spring/rafz" "~9.4.5"
|
||||||
"@react-spring/types" "~9.2.6-beta.0"
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@react-spring/types@~9.2.6-beta.0":
|
"@react-spring/types@~9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.2.6.tgz#f60722fcf9f8492ae16d0bdc47f0ea3c2a16d2cf"
|
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.5.tgz#9c71e5ff866b5484a7ef3db822bf6c10e77bdd8c"
|
||||||
integrity sha512-l7mCw182DtDMnCI8CB9orgTAEoFZRtdQ6aS6YeEAqYcy3nQZPmPggIHH9DxyLw7n7vBPRSzu9gCvUMgXKpTflg==
|
integrity sha512-mpRIamoHwql0ogxEUh9yr4TP0xU5CWyZxVQeccGkHHF8kPMErtDXJlxyo0lj+telRF35XNihtPTWoflqtyARmg==
|
||||||
|
|
||||||
"@react-spring/web@9.2.6":
|
"@react-spring/web@9.4.5":
|
||||||
version "9.2.6"
|
version "9.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.2.6.tgz#c4fba69e1b1b43bd1d6a62346530cfb07f2be09b"
|
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.5.tgz#b92f05b87cdc0963a59ee149e677dcaff09f680e"
|
||||||
integrity sha512-0HkRsEYR/CO3Uw46FWDWaF2wg2rUXcWE2R9AoZXthEYLUn5w9uE1mf2Jel7BxBxWGQ73owkqSQv+klA1Hb+ViQ==
|
integrity sha512-NGAkOtKmOzDEctL7MzRlQGv24sRce++0xAY7KlcxmeVkR7LRSGkoXHaIfm9ObzxPMcPHQYQhf3+X9jepIFNHQA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@react-spring/animated" "~9.2.6-beta.0"
|
"@react-spring/animated" "~9.4.5"
|
||||||
"@react-spring/core" "~9.2.6-beta.0"
|
"@react-spring/core" "~9.4.5"
|
||||||
"@react-spring/shared" "~9.2.6-beta.0"
|
"@react-spring/shared" "~9.4.5"
|
||||||
"@react-spring/types" "~9.2.6-beta.0"
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@rushstack/eslint-patch@^1.1.3":
|
"@rushstack/eslint-patch@^1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
|
@ -2933,10 +2931,10 @@
|
||||||
"@svgr/plugin-jsx" "^6.2.1"
|
"@svgr/plugin-jsx" "^6.2.1"
|
||||||
"@svgr/plugin-svgo" "^6.2.0"
|
"@svgr/plugin-svgo" "^6.2.0"
|
||||||
|
|
||||||
"@swc/helpers@0.4.3":
|
"@swc/helpers@0.4.11":
|
||||||
version "0.4.3"
|
version "0.4.11"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.3.tgz#16593dfc248c53b699d4b5026040f88ddb497012"
|
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de"
|
||||||
integrity sha512-6JrF+fdUK2zbGpJIlN7G3v966PQjyx/dPt1T9km2wj+EUBqgrxCk3uX4Kct16MIm9gGxfKRcfax2hVf5jvlTzA==
|
integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
|
@ -3566,6 +3564,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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":
|
"@typescript-eslint/eslint-plugin@5.36.0":
|
||||||
version "5.36.0"
|
version "5.36.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.0.tgz#8f159c4cdb3084eb5d4b72619a2ded942aa109e5"
|
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"
|
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||||
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
|
||||||
|
|
||||||
base64-js@^1.3.0:
|
base64-js@^1.3.0, base64-js@^1.3.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
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"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
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:
|
bluebird@^3.7.1:
|
||||||
version "3.7.2"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
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"
|
node-releases "^2.0.3"
|
||||||
picocolors "^1.0.0"
|
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:
|
buffer-equal-constant-time@1.0.1:
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
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:
|
bytes@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||||
|
@ -4545,6 +4572,11 @@ caniuse-lite@^1.0.30001230, caniuse-lite@^1.0.30001332:
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz#59590c8ffa8b5939cf4161f00827b8873ad72498"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz#59590c8ffa8b5939cf4161f00827b8873ad72498"
|
||||||
integrity sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==
|
integrity sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==
|
||||||
|
|
||||||
|
caniuse-lite@^1.0.30001406:
|
||||||
|
version "1.0.30001409"
|
||||||
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001409.tgz#6135da9dcab34cd9761d9cdb12a68e6740c5e96e"
|
||||||
|
integrity sha512-V0mnJ5dwarmhYv8/MzhJ//aW68UpvnQBXv8lJ2QUsvn2pHcmAuNtu8hQEDz37XnA1iE+lRR9CIfGWWpgJ5QedQ==
|
||||||
|
|
||||||
ccount@^1.0.0, ccount@^1.0.3:
|
ccount@^1.0.0, ccount@^1.0.3:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
|
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
|
||||||
|
@ -4645,6 +4677,11 @@ chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
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:
|
chrome-trace-event@^1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||||
|
@ -5011,7 +5048,7 @@ cross-env@^7.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn "^7.0.1"
|
cross-spawn "^7.0.1"
|
||||||
|
|
||||||
cross-fetch@^3.1.5:
|
cross-fetch@3.1.5, cross-fetch@^3.1.5:
|
||||||
version "3.1.5"
|
version "3.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||||
|
@ -5227,11 +5264,6 @@ d3-format@^1.4.4:
|
||||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
||||||
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
|
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:
|
"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
||||||
|
@ -5338,7 +5370,7 @@ debug@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
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"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
@ -5494,6 +5526,11 @@ detective@^5.2.1:
|
||||||
defined "^1.0.0"
|
defined "^1.0.0"
|
||||||
minimist "^1.2.6"
|
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:
|
dicer@^0.3.0:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.1.tgz#abf28921e3475bc5e801e74e0159fd94f927ba97"
|
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.1.tgz#abf28921e3475bc5e801e74e0159fd94f927ba97"
|
||||||
|
@ -6234,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"
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
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:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
|
@ -6316,6 +6364,13 @@ fbjs@^3.0.0, fbjs@^3.0.1:
|
||||||
setimmediate "^1.0.5"
|
setimmediate "^1.0.5"
|
||||||
ua-parser-js "^0.7.30"
|
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:
|
feed@^4.2.2:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
|
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
|
||||||
|
@ -6575,6 +6630,11 @@ fresh@0.5.2:
|
||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
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:
|
fs-extra@^10.0.1:
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||||
|
@ -7293,6 +7353,14 @@ http-proxy@^1.18.1:
|
||||||
follow-redirects "^1.0.0"
|
follow-redirects "^1.0.0"
|
||||||
requires-port "^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:
|
https-proxy-agent@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81"
|
||||||
|
@ -7301,14 +7369,6 @@ https-proxy-agent@^3.0.0:
|
||||||
agent-base "^4.3.0"
|
agent-base "^4.3.0"
|
||||||
debug "^3.1.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:
|
human-signals@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||||
|
@ -7331,6 +7391,11 @@ idb@7.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.1.tgz#d2875b3a2f205d854ee307f6d196f246fea590a7"
|
resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.1.tgz#d2875b3a2f205d854ee307f6d196f246fea590a7"
|
||||||
integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==
|
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:
|
ignore-by-default@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||||
|
@ -7404,7 +7469,7 @@ inflight@^1.0.4:
|
||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
wrappy "1"
|
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"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
@ -8559,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"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
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:
|
mkdirp@0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
||||||
|
@ -8637,31 +8707,31 @@ next-sitemap@^2.5.14:
|
||||||
"@corex/deepmerge" "^2.6.148"
|
"@corex/deepmerge" "^2.6.148"
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
|
|
||||||
next@12.2.5:
|
next@12.3.1:
|
||||||
version "12.2.5"
|
version "12.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/next/-/next-12.2.5.tgz#14fb5975e8841fad09553b8ef41fe1393602b717"
|
resolved "https://registry.yarnpkg.com/next/-/next-12.3.1.tgz#127b825ad2207faf869b33393ec8c75fe61e50f1"
|
||||||
integrity sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==
|
integrity sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@next/env" "12.2.5"
|
"@next/env" "12.3.1"
|
||||||
"@swc/helpers" "0.4.3"
|
"@swc/helpers" "0.4.11"
|
||||||
caniuse-lite "^1.0.30001332"
|
caniuse-lite "^1.0.30001406"
|
||||||
postcss "8.4.14"
|
postcss "8.4.14"
|
||||||
styled-jsx "5.0.4"
|
styled-jsx "5.0.7"
|
||||||
use-sync-external-store "1.2.0"
|
use-sync-external-store "1.2.0"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@next/swc-android-arm-eabi" "12.2.5"
|
"@next/swc-android-arm-eabi" "12.3.1"
|
||||||
"@next/swc-android-arm64" "12.2.5"
|
"@next/swc-android-arm64" "12.3.1"
|
||||||
"@next/swc-darwin-arm64" "12.2.5"
|
"@next/swc-darwin-arm64" "12.3.1"
|
||||||
"@next/swc-darwin-x64" "12.2.5"
|
"@next/swc-darwin-x64" "12.3.1"
|
||||||
"@next/swc-freebsd-x64" "12.2.5"
|
"@next/swc-freebsd-x64" "12.3.1"
|
||||||
"@next/swc-linux-arm-gnueabihf" "12.2.5"
|
"@next/swc-linux-arm-gnueabihf" "12.3.1"
|
||||||
"@next/swc-linux-arm64-gnu" "12.2.5"
|
"@next/swc-linux-arm64-gnu" "12.3.1"
|
||||||
"@next/swc-linux-arm64-musl" "12.2.5"
|
"@next/swc-linux-arm64-musl" "12.3.1"
|
||||||
"@next/swc-linux-x64-gnu" "12.2.5"
|
"@next/swc-linux-x64-gnu" "12.3.1"
|
||||||
"@next/swc-linux-x64-musl" "12.2.5"
|
"@next/swc-linux-x64-musl" "12.3.1"
|
||||||
"@next/swc-win32-arm64-msvc" "12.2.5"
|
"@next/swc-win32-arm64-msvc" "12.3.1"
|
||||||
"@next/swc-win32-ia32-msvc" "12.2.5"
|
"@next/swc-win32-ia32-msvc" "12.3.1"
|
||||||
"@next/swc-win32-x64-msvc" "12.2.5"
|
"@next/swc-win32-x64-msvc" "12.3.1"
|
||||||
|
|
||||||
no-case@^3.0.4:
|
no-case@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
|
@ -9202,15 +9272,10 @@ path-type@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
performance-now@^0.2.0:
|
pend@~1.2.0:
|
||||||
version "0.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
|
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||||
|
|
||||||
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=
|
|
||||||
|
|
||||||
picocolors@^1.0.0:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
@ -9634,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"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
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:
|
promise@^7.1.1:
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||||
|
@ -9656,7 +9726,7 @@ prompts@^2.4.2:
|
||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.5"
|
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"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
|
@ -9824,7 +9894,7 @@ proxy-agent@^3.0.3:
|
||||||
proxy-from-env "^1.0.0"
|
proxy-from-env "^1.0.0"
|
||||||
socks-proxy-agent "^4.0.1"
|
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"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
@ -9873,6 +9943,23 @@ pupa@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-goat "^2.0.0"
|
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:
|
pure-color@^1.2.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
|
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
|
||||||
|
@ -9917,13 +10004,6 @@ raf-schd@^4.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
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:
|
randombytes@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||||
|
@ -10123,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"
|
resolved "https://registry.yarnpkg.com/react-masonry-css/-/react-masonry-css-1.0.16.tgz#72b28b4ae3484e250534700860597553a10f1a2c"
|
||||||
integrity sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==
|
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:
|
react-query@3.39.0:
|
||||||
version "3.39.0"
|
version "3.39.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050"
|
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050"
|
||||||
|
@ -10269,7 +10340,7 @@ readable-stream@2, readable-stream@^2.0.1, readable-stream@~2.3.6:
|
||||||
string_decoder "~1.1.1"
|
string_decoder "~1.1.1"
|
||||||
util-deprecate "~1.0.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"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||||
|
@ -10533,11 +10604,6 @@ requires-port@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
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:
|
resolve-from@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||||
|
@ -11267,10 +11333,10 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
inline-style-parser "0.1.1"
|
inline-style-parser "0.1.1"
|
||||||
|
|
||||||
styled-jsx@5.0.4:
|
styled-jsx@5.0.7:
|
||||||
version "5.0.4"
|
version "5.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.4.tgz#5b1bd0b9ab44caae3dd1361295559706e044aa53"
|
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48"
|
||||||
integrity sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==
|
integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==
|
||||||
|
|
||||||
stylehacks@^5.1.0:
|
stylehacks@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
|
@ -11362,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"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
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:
|
teeny-request@^7.1.3:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.2.0.tgz#41347ece068f08d741e7b86df38a4498208b2633"
|
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.2.0.tgz#41347ece068f08d741e7b86df38a4498208b2633"
|
||||||
|
@ -11399,6 +11486,11 @@ text-table@^0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
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:
|
thunkify@^2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
|
resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
|
||||||
|
@ -11622,6 +11714,14 @@ unbox-primitive@^1.0.2:
|
||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
which-boxed-primitive "^1.0.2"
|
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:
|
undefsafe@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||||
|
@ -12229,6 +12329,11 @@ write-file-atomic@^3.0.0:
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
typedarray-to-buffer "^3.1.5"
|
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:
|
ws@>=7.4.6:
|
||||||
version "8.6.0"
|
version "8.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
|
||||||
|
@ -12309,6 +12414,14 @@ yargs@^16.2.0:
|
||||||
y18n "^5.0.5"
|
y18n "^5.0.5"
|
||||||
yargs-parser "^20.2.2"
|
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:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user