Merge branch 'main' into link-to-comments

This commit is contained in:
Ian Philips 2022-05-16 13:16:02 -06:00
commit f1d675af37
80 changed files with 408 additions and 372 deletions

View File

@ -1,21 +1,17 @@
export const CATEGORIES = {
politics: 'Politics',
technology: 'Technology',
sports: 'Sports',
gaming: 'Gaming',
manifold: 'Manifold',
science: 'Science',
world: 'World',
fun: 'Fun',
personal: 'Personal',
sports: 'Sports',
economics: 'Economics',
personal: 'Personal',
culture: 'Culture',
manifold: 'Manifold',
covid: 'Covid',
crypto: 'Crypto',
health: 'Health',
// entertainment: 'Entertainment',
// society: 'Society',
// friends: 'Friends / Community',
// business: 'Business',
// charity: 'Charities / Non-profits',
gaming: 'Gaming',
fun: 'Fun',
} as { [category: string]: string }
export const TO_CATEGORY = Object.fromEntries(

View File

@ -43,7 +43,7 @@ export const PROD_CONFIG: EnvConfig = {
],
visibility: 'PUBLIC',
moneyMoniker: 'M$',
moneyMoniker: 'ϻ',
navbarLogoPath: '',
faviconPath: '/favicon.ico',
newQuestionPlaceholders: [

View File

@ -35,8 +35,6 @@ export function getNewContract(
? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante)
: getFreeAnswerProps(ante)
const volume = outcomeType === 'BINARY' ? 0 : ante
const contract: Contract = removeUndefinedProps({
id,
slug,
@ -56,7 +54,7 @@ export function getNewContract(
createdTime: Date.now(),
closeTime,
volume,
volume: 0,
volume24Hours: 0,
volume7Days: 0,

View File

@ -35,4 +35,5 @@ export type PrivateUser = {
unsubscribedFromGenericEmails?: boolean
initialDeviceToken?: string
initialIpAddress?: string
apiKey?: string
}

View File

@ -1,7 +1,11 @@
export const randomString = (length = 12) =>
Math.random()
.toString(16)
.substring(2, length + 2)
// Returns a cryptographically random hexadecimal string of length `length`
// (thus containing 4*`length` bits of entropy).
export const randomString = (length = 12) => {
const bytes = new Uint8Array(Math.ceil(length / 2))
crypto.getRandomValues(bytes)
const hex = bytes.reduce((s, b) => s + ('0' + b.toString(16)).slice(-2), '')
return hex.substring(0, length)
}
export function genHash(str: string) {
// xmur3
@ -42,8 +46,6 @@ export function createRNG(seed: string) {
export const shuffle = (array: any[], rand: () => number) => {
for (let i = 0; i < array.length; i++) {
const swapIndex = Math.floor(rand() * (array.length - i))
const temp = array[i]
array[i] = array[swapIndex]
array[swapIndex] = temp
;[array[i], array[swapIndex]] = [array[swapIndex], array[i]]
}
}

View File

@ -21,6 +21,9 @@ service cloud.firestore {
match /private-users/{userId} {
allow read: if resource.data.id == request.auth.uid || isAdmin();
allow update: if (resource.data.id == request.auth.uid || isAdmin())
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['apiKey']);
}
match /private-users/{userId}/views/{viewId} {

View File

@ -1,11 +1,11 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { removeUndefinedProps } from 'common/util/object'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { removeUndefinedProps } from '../../common/util/object'
import { redeemShares } from './redeem-shares'
import { getNewLiquidityProvision } from 'common/add-liquidity'
import { getNewLiquidityProvision } from '../../common/add-liquidity'
export const addLiquidity = functions.runWith({ minInstances: 1 }).https.onCall(
async (

View File

@ -2,12 +2,12 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { getUser } from './utils'
import { Contract } from 'common/contract'
import { Comment } from 'common/comment'
import { User } from 'common/user'
import { cleanUsername } from 'common/util/clean-username'
import { removeUndefinedProps } from 'common/util/object'
import { Answer } from 'common/answer'
import { Contract } from '../../common/contract'
import { Comment } from '../../common/comment'
import { User } from '../../common/user'
import { cleanUsername } from '../../common/util/clean-username'
import { removeUndefinedProps } from '../../common/util/object'
import { Answer } from '../../common/answer'
export const changeUserInfo = functions
.runWith({ minInstances: 1 })

View File

@ -1,10 +1,15 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract, DPM, FreeResponse, FullContract } from 'common/contract'
import { User } from 'common/user'
import { getNewMultiBetInfo } from 'common/new-bet'
import { Answer, MAX_ANSWER_LENGTH } from 'common/answer'
import {
Contract,
DPM,
FreeResponse,
FullContract,
} from '../../common/contract'
import { User } from '../../common/user'
import { getNewMultiBetInfo } from '../../common/new-bet'
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
import { getContract, getValues } from './utils'
import { sendNewAnswerEmail } from './emails'
import { Bet } from '../../common/bet'

View File

@ -14,10 +14,10 @@ import {
MAX_QUESTION_LENGTH,
MAX_TAG_LENGTH,
outcomeType,
} from 'common/contract'
import { slugify } from 'common/util/slugify'
import { randomString } from 'common/util/random'
import { getNewContract } from 'common/new-contract'
} from '../../common/contract'
import { slugify } from '../../common/util/slugify'
import { randomString } from '../../common/util/random'
import { getNewContract } from '../../common/new-contract'
import {
FIXED_ANTE,
getAnteBets,
@ -25,8 +25,8 @@ import {
getFreeAnswerAnte,
HOUSE_LIQUIDITY_PROVIDER_ID,
MINIMUM_ANTE,
} from 'common/antes'
import { getNoneAnswer } from 'common/answer'
} from '../../common/antes'
import { getNoneAnswer } from '../../common/answer'
export const createContract = functions
.runWith({ minInstances: 1 })

View File

@ -3,10 +3,10 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getUser } from './utils'
import { Contract } from 'common/contract'
import { slugify } from 'common/util/slugify'
import { randomString } from 'common/util/random'
import { Fold } from 'common/fold'
import { Contract } from '../../common/contract'
import { slugify } from '../../common/util/slugify'
import { randomString } from '../../common/util/random'
import { Fold } from '../../common/fold'
export const createFold = functions.runWith({ minInstances: 1 }).https.onCall(
async (

View File

@ -6,12 +6,15 @@ import {
STARTING_BALANCE,
SUS_STARTING_BALANCE,
User,
} from 'common/user'
} from '../../common/user'
import { getUser, getUserByUsername } from './utils'
import { randomString } from 'common/util/random'
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
import { randomString } from '../../common/util/random'
import {
cleanDisplayName,
cleanUsername,
} from '../../common/util/clean-username'
import { sendWelcomeEmail } from './emails'
import { isWhitelisted } from 'common/envs/constants'
import { isWhitelisted } from '../../common/envs/constants'
export const createUser = functions
.runWith({ minInstances: 1 })
@ -39,8 +42,7 @@ export const createUser = functions
const name = cleanDisplayName(rawName)
let username = cleanUsername(name)
const sameNameUser = await getUserByUsername(username)
if (sameNameUser) {
while (await getUserByUsername(username)) {
username += randomString(4)
}

View File

@ -302,7 +302,7 @@
font-family: Arial, sans-serif;
font-size: 18px;
"
>Best of luck with you forecasting!</span
>Best of luck with your forecasting!</span
>
</p>
<p

View File

@ -1,15 +1,14 @@
import * as _ from 'lodash'
import { DOMAIN, PROJECT_ID } from 'common/envs/constants'
import { Answer } from 'common/answer'
import { Bet } from 'common/bet'
import { getProbability } from 'common/calculate'
import { Comment } from 'common/comment'
import { Contract, FreeResponseContract } from 'common/contract'
import { DPM_CREATOR_FEE } from 'common/fees'
import { PrivateUser, User } from 'common/user'
import { formatMoney, formatPercent } from 'common/util/format'
import { DOMAIN, PROJECT_ID } from '../../common/envs/constants'
import { Answer } from '../../common/answer'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { Comment } from '../../common/comment'
import { Contract, FreeResponseContract } from '../../common/contract'
import { DPM_CREATOR_FEE } from '../../common/fees'
import { PrivateUser, User } from '../../common/user'
import { formatMoney, formatPercent } from '../../common/util/format'
import { sendTemplateEmail } from './send-email'
import { getPrivateUser, getUser } from './utils'

View File

@ -1,19 +1,5 @@
/* This use of module-alias hackily simulates the Typescript base URL so that
* the Firebase deploy machinery, which just uses the compiled Javascript in the
* lib directory, will be able to do imports from the root directory
* (i.e. "common/foo" instead of "../../../common/foo") just like we can in
* Typescript-land.
*
* Note that per the module-alias docs, this need to come before any other
* imports in order to work.
*
* Suggested by https://github.com/firebase/firebase-tools/issues/986 where many
* people complain about this problem.
*/
import { addPath } from 'module-alias'
addPath('./lib')
import * as admin from 'firebase-admin'
admin.initializeApp()
// export * from './keep-awake'

View File

@ -1,7 +1,7 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract } from 'common/contract'
import { Contract } from '../../common/contract'
import { getPrivateUser, getUserByUsername } from './utils'
import { sendMarketCloseEmail } from './emails'

View File

@ -3,7 +3,7 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getContract } from './utils'
import { Bet } from 'common/bet'
import { Bet } from '../../common/bet'
const firestore = admin.firestore()

View File

@ -3,10 +3,10 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getContract, getUser, getValues } from './utils'
import { Comment } from 'common/comment'
import { Comment } from '../../common/comment'
import { sendNewCommentEmail } from './emails'
import { Bet } from 'common/bet'
import { Answer } from 'common/answer'
import { Bet } from '../../common/bet'
import { Answer } from '../../common/answer'
const firestore = admin.firestore()

View File

@ -1,6 +1,6 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { View } from 'common/tracking'
import { View } from '../../common/tracking'
const firestore = admin.firestore()

View File

@ -1,18 +1,18 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import {
getNewBinaryCpmmBetInfo,
getNewBinaryDpmBetInfo,
getNewMultiBetInfo,
} from 'common/new-bet'
import { addObjects, removeUndefinedProps } from 'common/util/object'
import { Bet } from 'common/bet'
} from '../../common/new-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
import { Bet } from '../../common/bet'
import { redeemShares } from './redeem-shares'
import { Fees } from 'common/fees'
import { hasUserHitManaLimit } from 'common/calculate'
import { Fees } from '../../common/fees'
import { hasUserHitManaLimit } from '../../common/calculate'
export const placeBet = functions.runWith({ minInstances: 1 }).https.onCall(
async (

View File

@ -1,12 +1,12 @@
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { Bet } from 'common/bet'
import { getProbability } from 'common/calculate'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { Binary, CPMM, FullContract } from 'common/contract'
import { noFees } from 'common/fees'
import { User } from 'common/user'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { noFees } from '../../common/fees'
import { User } from '../../common/user'
export const redeemShares = async (userId: string, contractId: string) => {
return await firestore.runTransaction(async (transaction) => {

View File

@ -2,9 +2,9 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { Bet } from 'common/bet'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { Bet } from '../../common/bet'
import { getUser, isProd, payUser } from './utils'
import { sendMarketResolutionEmail } from './emails'
import {
@ -12,9 +12,9 @@ import {
getPayouts,
groupPayoutsByUser,
Payout,
} from 'common/payouts'
import { removeUndefinedProps } from 'common/util/object'
import { LiquidityProvision } from 'common/liquidity-provision'
} from '../../common/payouts'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
export const resolveMarket = functions
.runWith({ minInstances: 1 })

View File

@ -5,9 +5,9 @@ import { initAdmin } from './script-init'
initAdmin()
import { getValues } from '../utils'
import { View } from 'common/tracking'
import { User } from 'common/user'
import { batchedWaitAll } from 'common/util/promise'
import { View } from '../../../common/tracking'
import { User } from '../../../common/user'
import { batchedWaitAll } from '../../../common/util/promise'
const firestore = admin.firestore()

View File

@ -4,9 +4,9 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { getDpmProbability } from 'common/calculate-dpm'
import { Binary, Contract, DPM, FullContract } from 'common/contract'
import { Bet } from '../../../common/bet'
import { getDpmProbability } from '../../../common/calculate-dpm'
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
type DocRef = admin.firestore.DocumentReference
const firestore = admin.firestore()

View File

@ -4,7 +4,7 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { PrivateUser, STARTING_BALANCE, User } from 'common/user'
import { PrivateUser, STARTING_BALANCE, User } from '../../../common/user'
const firestore = admin.firestore()

View File

@ -5,10 +5,10 @@ import * as fs from 'fs'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
import { getValues } from '../utils'
import { Comment } from 'common/comment'
import { Comment } from '../../../common/comment'
const firestore = admin.firestore()

View File

@ -5,7 +5,7 @@ import { initAdmin } from './script-init'
initAdmin()
import { getValues } from '../utils'
import { Fold } from 'common/fold'
import { Fold } from '../../../common/fold'
async function lowercaseFoldTags() {
const firestore = admin.firestore()

View File

@ -4,7 +4,7 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Contract } from 'common/contract'
import { Contract } from '../../../common/contract'
const firestore = admin.firestore()

View File

@ -4,8 +4,8 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
type DocRef = admin.firestore.DocumentReference

View File

@ -4,13 +4,22 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Binary, Contract, CPMM, DPM, FullContract } from 'common/contract'
import { Bet } from 'common/bet'
import { calculateDpmPayout, getDpmProbability } from 'common/calculate-dpm'
import { User } from 'common/user'
import { getCpmmInitialLiquidity } from 'common/antes'
import { noFees } from 'common/fees'
import { addObjects } from 'common/util/object'
import {
Binary,
Contract,
CPMM,
DPM,
FullContract,
} from '../../../common/contract'
import { Bet } from '../../../common/bet'
import {
calculateDpmPayout,
getDpmProbability,
} from '../../../common/calculate-dpm'
import { User } from '../../../common/user'
import { getCpmmInitialLiquidity } from '../../../common/antes'
import { noFees } from '../../../common/fees'
import { addObjects } from '../../../common/util/object'
type DocRef = admin.firestore.DocumentReference

View File

@ -4,11 +4,14 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Binary, Contract, DPM, FullContract } from 'common/contract'
import { Bet } from 'common/bet'
import { calculateDpmShares, getDpmProbability } from 'common/calculate-dpm'
import { getSellBetInfo } from 'common/sell-bet'
import { User } from 'common/user'
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
import { Bet } from '../../../common/bet'
import {
calculateDpmShares,
getDpmProbability,
} from '../../../common/calculate-dpm'
import { getSellBetInfo } from '../../../common/sell-bet'
import { User } from '../../../common/user'
type DocRef = admin.firestore.DocumentReference

View File

@ -4,10 +4,10 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { getLoanPayouts, getPayouts } from 'common/payouts'
import { filterDefined } from 'common/util/array'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
import { getLoanPayouts, getPayouts } from '../../../common/payouts'
import { filterDefined } from '../../../common/util/array'
type DocRef = admin.firestore.DocumentReference

View File

@ -4,8 +4,8 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
type DocRef = admin.firestore.DocumentReference

View File

@ -4,8 +4,8 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { Bet } from '../../../common/bet'
import { Contract } from '../../../common/contract'
import { getValues } from '../utils'
async function removeAnswerAnte() {

View File

@ -4,7 +4,7 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Contract } from 'common/contract'
import { Contract } from '../../../common/contract'
import { getValues } from '../utils'
const firestore = admin.firestore()

View File

@ -4,8 +4,8 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Contract } from 'common/contract'
import { parseTags } from 'common/util/parse'
import { Contract } from '../../../common/contract'
import { parseTags } from '../../../common/util/parse'
import { getValues } from '../utils'
async function updateContractTags() {

View File

@ -5,9 +5,9 @@ import { initAdmin } from './script-init'
initAdmin()
import { getValues } from '../utils'
import { User } from 'common/user'
import { batchedWaitAll } from 'common/util/promise'
import { Contract } from 'common/contract'
import { User } from '../../../common/user'
import { batchedWaitAll } from '../../../common/util/promise'
import { Contract } from '../../../common/contract'
import { updateWordScores } from '../update-recommendations'
import { computeFeed } from '../update-feed'
import { getFeedContracts, getTaggedContracts } from '../get-feed-data'

View File

@ -4,9 +4,9 @@ import * as _ from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Contract } from 'common/contract'
import { Contract } from '../../../common/contract'
import { getValues } from '../utils'
import { Comment } from 'common/comment'
import { Comment } from '../../../common/comment'
async function updateLastCommentTime() {
const firestore = admin.firestore()

View File

@ -1,12 +1,12 @@
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { Bet } from 'common/bet'
import { getSellBetInfo } from 'common/sell-bet'
import { addObjects, removeUndefinedProps } from 'common/util/object'
import { Fees } from 'common/fees'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { Bet } from '../../common/bet'
import { getSellBetInfo } from '../../common/sell-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
import { Fees } from '../../common/fees'
export const sellBet = functions.runWith({ minInstances: 1 }).https.onCall(
async (

View File

@ -2,12 +2,12 @@ import * as _ from 'lodash'
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
import { Binary, CPMM, FullContract } from 'common/contract'
import { User } from 'common/user'
import { getCpmmSellBetInfo } from 'common/sell-bet'
import { addObjects, removeUndefinedProps } from 'common/util/object'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { User } from '../../common/user'
import { getCpmmSellBetInfo } from '../../common/sell-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
import { getValues } from './utils'
import { Bet } from 'common/bet'
import { Bet } from '../../common/bet'
export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall(
async (

View File

@ -54,7 +54,7 @@ export const createCheckoutSession = functions
}
const referrer =
req.query.referer || req.headers.referer || 'https://mantic.markets'
req.query.referer || req.headers.referer || 'https://manifold.markets'
const session = await stripe.checkout.sessions.create({
metadata: {

View File

@ -1,9 +1,9 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { User } from 'common/user'
import { Txn } from 'common/txn'
import { removeUndefinedProps } from 'common/util/object'
import { User } from '../../common/user'
import { Txn } from '../../common/txn'
import { removeUndefinedProps } from '../../common/util/object'
export const transact = functions
.runWith({ minInstances: 1 })

View File

@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getUser } from './utils'
import { PrivateUser } from 'common/user'
import { PrivateUser } from '../../common/user'
export const unsubscribe = functions
.runWith({ minInstances: 1 })

View File

@ -3,9 +3,9 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getValues } from './utils'
import { Contract } from 'common/contract'
import { Bet } from 'common/bet'
import { batchedWaitAll } from 'common/util/promise'
import { Contract } from '../../common/contract'
import { Bet } from '../../common/bet'
import { batchedWaitAll } from '../../common/util/promise'
const firestore = admin.firestore()

View File

@ -3,9 +3,9 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { getValue, getValues } from './utils'
import { Contract } from 'common/contract'
import { logInterpolation } from 'common/util/math'
import { DAY_MS } from 'common/util/time'
import { Contract } from '../../common/contract'
import { logInterpolation } from '../../common/util/math'
import { DAY_MS } from '../../common/util/time'
import {
getProbability,
getOutcomeProbability,
@ -15,7 +15,7 @@ import { User } from '../../common/user'
import {
getContractScore,
MAX_FEED_CONTRACTS,
} from 'common/recommended-contracts'
} from '../../common/recommended-contracts'
import { callCloudFunction } from './call-cloud-function'
import {
getFeedContracts,
@ -26,18 +26,25 @@ import { CATEGORY_LIST } from '../../common/categories'
const firestore = admin.firestore()
const BATCH_SIZE = 30
const MAX_BATCHES = 50
const getUserBatches = async () => {
const users = _.shuffle(await getValues<User>(firestore.collection('users')))
let userBatches: User[][] = []
for (let i = 0; i < users.length; i += BATCH_SIZE) {
userBatches.push(users.slice(i, i + BATCH_SIZE))
}
console.log('updating feed batches', MAX_BATCHES, 'of', userBatches.length)
return userBatches.slice(0, MAX_BATCHES)
}
export const updateFeed = functions.pubsub
.schedule('every 60 minutes')
.schedule('every 5 minutes')
.onRun(async () => {
const users = await getValues<User>(firestore.collection('users'))
const batchSize = 100
let userBatches: User[][] = []
for (let i = 0; i < users.length; i += batchSize) {
userBatches.push(users.slice(i, i + batchSize))
}
console.log('updating feed batch')
const userBatches = await getUserBatches()
await Promise.all(
userBatches.map((users) =>
@ -72,13 +79,7 @@ export const updateFeedBatch = functions.https.onCall(
export const updateCategoryFeed = functions.https.onCall(
async (data: { category: string }) => {
const { category } = data
const users = await getValues<User>(firestore.collection('users'))
const batchSize = 100
const userBatches: User[][] = []
for (let i = 0; i < users.length; i += batchSize) {
userBatches.push(users.slice(i, i + batchSize))
}
const userBatches = await getUserBatches()
await Promise.all(
userBatches.map(async (users) => {

View File

@ -3,12 +3,12 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getValue, getValues } from './utils'
import { Contract } from 'common/contract'
import { Bet } from 'common/bet'
import { User } from 'common/user'
import { ClickEvent } from 'common/tracking'
import { getWordScores } from 'common/recommended-contracts'
import { batchedWaitAll } from 'common/util/promise'
import { Contract } from '../../common/contract'
import { Bet } from '../../common/bet'
import { User } from '../../common/user'
import { ClickEvent } from '../../common/tracking'
import { getWordScores } from '../../common/recommended-contracts'
import { batchedWaitAll } from '../../common/util/promise'
import { callCloudFunction } from './call-cloud-function'
const firestore = admin.firestore()

View File

@ -3,11 +3,11 @@ import * as admin from 'firebase-admin'
import * as _ from 'lodash'
import { getValues } from './utils'
import { Contract } from 'common/contract'
import { Bet } from 'common/bet'
import { User } from 'common/user'
import { batchedWaitAll } from 'common/util/promise'
import { calculatePayout } from 'common/calculate'
import { Contract } from '../../common/contract'
import { Bet } from '../../common/bet'
import { User } from '../../common/user'
import { batchedWaitAll } from '../../common/util/promise'
import { calculatePayout } from '../../common/calculate'
const firestore = admin.firestore()

View File

@ -1,7 +1,7 @@
import * as admin from 'firebase-admin'
import { Contract } from 'common/contract'
import { PrivateUser, User } from 'common/user'
import { Contract } from '../../common/contract'
import { PrivateUser, User } from '../../common/user'
export const isProd =
admin.instanceId().app.options.projectId === 'mantic-markets'

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'
import _ from 'lodash'
import { useEffect, useRef, useState } from 'react'
import { XIcon } from '@heroicons/react/solid'

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'
import _ from 'lodash'
import { Answer } from 'common/answer'
import { DPM, FreeResponse, FullContract } from 'common/contract'

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'
import _ from 'lodash'
import { useState } from 'react'
import Textarea from 'react-expanding-textarea'

View File

@ -321,11 +321,11 @@ function BuyPanel(props: {
<Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm">
<div className="text-gray-500">Probability</div>
<Row>
<div>{formatPercent(initialProb)}</div>
<div className="mx-2"></div>
<div>{formatPercent(resultProb)}</div>
</Row>
<div>
{formatPercent(initialProb)}
<span className="mx-2"></span>
{formatPercent(resultProb)}
</div>
</Row>
<Row className="items-center justify-between gap-2 text-sm">
@ -350,12 +350,12 @@ function BuyPanel(props: {
{dpmTooltip && <InfoTooltip text={dpmTooltip} />}
</Row>
<Row className="flex-wrap items-end justify-end gap-2">
<span className="whitespace-nowrap">
<div>
<span className="mr-2 whitespace-nowrap">
{formatMoney(currentPayout)}
</span>
<span>(+{currentReturnPercent})</span>
</Row>
(+{currentReturnPercent})
</div>
</Row>
</Col>
@ -459,11 +459,11 @@ export function SellPanel(props: {
<Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm">
<div className="text-gray-500">Probability</div>
<Row>
<div>{formatPercent(initialProb)}</div>
<div className="mx-2"></div>
<div>{formatPercent(resultProb)}</div>
</Row>
<div>
{formatPercent(initialProb)}
<span className="mx-2"></span>
{formatPercent(resultProb)}
</div>
</Row>
</Col>

View File

@ -2,7 +2,6 @@ import { useState } from 'react'
import clsx from 'clsx'
import { BetPanelSwitcher } from './bet-panel'
import { Row } from './layout/row'
import { YesNoSelector } from './yes-no-selector'
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { Modal } from './layout/modal'

View File

@ -17,7 +17,7 @@ import {
} from '../hooks/use-sort-and-query-params'
import { ContractsGrid } from './contract/contracts-list'
import { Row } from './layout/row'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Spacer } from './layout/spacer'
import { useRouter } from 'next/router'
import { ENV } from 'common/envs/constants'
@ -143,7 +143,13 @@ export function ContractSearchInner(props: {
setQuery(query)
}, [query])
const isFirstRender = useRef(true)
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false
return
}
const sort = index.split('contracts-')[1] as Sort
if (sort) {
setSort(sort)

View File

@ -1,6 +1,5 @@
import clsx from 'clsx'
import Link from 'next/link'
import _ from 'lodash'
import { Row } from '../layout/row'
import { formatPercent } from 'common/util/format'
import {
@ -9,7 +8,6 @@ import {
getBinaryProbPercent,
} from 'web/lib/firebase/contracts'
import { Col } from '../layout/col'
import { Spacer } from '../layout/spacer'
import {
Binary,
CPMM,
@ -25,6 +23,8 @@ import {
} from '../outcome-label'
import { getOutcomeProbability, getTopAnswer } from 'common/calculate'
import { AbbrContractDetails } from './contract-details'
import { TagsList } from '../tags-list'
import { CATEGORY_LIST } from 'common/categories'
export function ContractCard(props: {
contract: Contract
@ -35,11 +35,16 @@ export function ContractCard(props: {
const { contract, showHotVolume, showCloseTime, className } = props
const { question, outcomeType, resolution } = contract
const { tags } = contract
const categories = tags.filter((tag) =>
CATEGORY_LIST.includes(tag.toLowerCase())
)
return (
<div>
<div
<Col
className={clsx(
'relative rounded-lg bg-white p-6 shadow-md hover:bg-gray-100',
'relative gap-3 rounded-lg bg-white p-6 shadow-md hover:bg-gray-100',
className
)}
>
@ -52,35 +57,39 @@ export function ContractCard(props: {
showHotVolume={showHotVolume}
showCloseTime={showCloseTime}
/>
<Spacer h={3} />
<Row
className={clsx(
'justify-between gap-4',
outcomeType === 'FREE_RESPONSE' && 'flex-col items-start !gap-2'
)}
>
<p
className="break-words font-medium text-indigo-700"
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
>
{question}
</p>
<Row className={clsx('justify-between gap-4')}>
<Col className="gap-3">
<p
className="break-words font-medium text-indigo-700"
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
>
{question}
</p>
{outcomeType !== 'FREE_RESPONSE' && categories.length > 0 && (
<TagsList tags={categories} noLabel />
)}
</Col>
{outcomeType === 'BINARY' && (
<BinaryResolutionOrChance
className="items-center"
contract={contract}
/>
)}
{outcomeType === 'FREE_RESPONSE' && (
<FreeResponseResolutionOrChance
className="self-end text-gray-600"
contract={contract as FullContract<DPM, FreeResponse>}
truncate="long"
/>
)}
</Row>
</div>
{outcomeType === 'FREE_RESPONSE' && (
<FreeResponseResolutionOrChance
className="self-end text-gray-600"
contract={contract as FullContract<DPM, FreeResponse>}
truncate="long"
/>
)}
{outcomeType === 'FREE_RESPONSE' && categories.length > 0 && (
<TagsList tags={categories} noLabel />
)}
</Col>
</div>
)
}

View File

@ -39,7 +39,9 @@ export function ContractDescription(props: {
if (!isCreator && !contract.description.trim()) return null
const { tags } = contract
const category = tags.find((tag) => CATEGORY_LIST.includes(tag.toLowerCase()))
const categories = tags.filter((tag) =>
CATEGORY_LIST.includes(tag.toLowerCase())
)
return (
<div
@ -50,9 +52,9 @@ export function ContractDescription(props: {
>
<Linkify text={contract.description} />
{category && (
{categories.length > 0 && (
<div className="mt-4">
<TagsList tags={[category]} label="Category" />
<TagsList tags={categories} noLabel />
</div>
)}

View File

@ -1,5 +1,4 @@
import clsx from 'clsx'
import _ from 'lodash'
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
import { TrendingUpIcon } from '@heroicons/react/solid'
import { Row } from '../layout/row'

View File

@ -1,5 +1,3 @@
import _ from 'lodash'
import { Contract } from '../../lib/firebase/contracts'
import { User } from '../../lib/firebase/users'
import { Col } from '../layout/col'

View File

@ -1,5 +1,3 @@
import _ from 'lodash'
import { Contract } from 'web/lib/firebase/contracts'
import { Comment } from 'web/lib/firebase/comments'
import { Col } from '../layout/col'

View File

@ -1,51 +1,43 @@
import clsx from 'clsx'
import _ from 'lodash'
import { User } from '../../../common/user'
import { Row } from '../layout/row'
import { CATEGORIES, CATEGORY_LIST } from '../../../common/categories'
import { updateUser } from '../../lib/firebase/users'
export function CategorySelector(props: {
user: User | null | undefined
category: string
setCategory: (category: string) => void
className?: string
}) {
const { className, user } = props
const followedCategories = user?.followedCategories ?? []
const { className, user, category, setCategory } = props
return (
<Row
className={clsx(
'mr-2 items-center space-x-2 space-y-2 overflow-x-scroll scroll-smooth pt-4 pb-4 sm:flex-wrap',
'carousel mr-2 items-center space-x-2 space-y-2 overflow-x-scroll pt-4 pb-4 sm:flex-wrap',
className
)}
>
<div />
<CategoryButton
key={'all' + followedCategories.length}
key="all"
category="All"
isFollowed={followedCategories.length === 0}
isFollowed={category === 'all'}
toggle={async () => {
if (!user?.id) return
await updateUser(user.id, {
followedCategories: [],
})
setCategory('all')
}}
/>
{CATEGORY_LIST.map((cat) => (
<CategoryButton
key={cat + followedCategories.length}
key={cat}
category={CATEGORIES[cat].split(' ')[0]}
isFollowed={followedCategories.includes(cat)}
isFollowed={cat === category}
toggle={async () => {
if (!user?.id) return
await updateUser(user.id, {
followedCategories: [cat],
})
setCategory(cat)
}}
/>
))}
@ -63,7 +55,7 @@ function CategoryButton(props: {
return (
<div
className={clsx(
'rounded-full border-2 px-4 py-1 shadow-md',
'rounded-full border-2 px-4 py-1 shadow-md hover:bg-gray-200',
'cursor-pointer select-none',
isFollowed ? 'border-gray-300 bg-gray-300' : 'bg-white'
)}

View File

@ -977,7 +977,7 @@ function FeedAnswerGroup(props: {
<Col
className={
type === 'answer'
? 'border-base-200 bg-base-200 flex-1 rounded-md p-3'
? 'border-base-200 bg-base-200 flex-1 rounded-md px-2'
: 'flex-1 gap-2'
}
>
@ -991,6 +991,12 @@ function FeedAnswerGroup(props: {
/>
</Modal>
{type == 'answer' && (
<div
className="pointer-events-none absolute -mx-2 h-full rounded-tl-md bg-green-600 bg-opacity-10"
style={{ width: `${100 * Math.max(prob, 0.01)}%` }}
></div>
)}
<Row className="my-4 gap-3">
<div className="px-1">
<Avatar username={username} avatarUrl={avatarUrl} />
@ -1023,9 +1029,7 @@ function FeedAnswerGroup(props: {
<span
className={clsx(
'text-2xl',
tradingAllowed(contract)
? 'text-green-500'
: 'text-gray-500'
tradingAllowed(contract) ? 'text-primary' : 'text-gray-500'
)}
>
{probPercent}

View File

@ -137,7 +137,7 @@ export function MobileSidebar(props: {
</div>
</Transition.Child>
<div className="mx-2 mt-5 h-0 flex-1 overflow-y-auto">
<Sidebar />
<Sidebar className="pl-2" />
</div>
</div>
</Transition.Child>

View File

@ -1,8 +1,8 @@
import Link from 'next/link'
import { firebaseLogout, User } from 'web/lib/firebase/users'
import { formatMoney } from 'common/util/format'
import { Avatar } from '../avatar'
import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
import { Row } from '../layout/row'
export function getNavigationOptions(user?: User | null) {
if (IS_PRIVATE_MANIFOLD) {
@ -27,18 +27,18 @@ export function getNavigationOptions(user?: User | null) {
]
}
export function ProfileSummary(props: { user: User | undefined }) {
export function ProfileSummary(props: { user: User }) {
const { user } = props
return (
<Row className="group items-center gap-4 rounded-md py-3 text-gray-500 group-hover:bg-gray-100 group-hover:text-gray-700">
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} noLink />
<Link href={`/${user.username}`}>
<a className="group flex flex-row items-center gap-4 rounded-md py-3 text-gray-500 hover:bg-gray-100 hover:text-gray-700">
<Avatar avatarUrl={user.avatarUrl} username={user.username} noLink />
<div className="truncate text-left">
<div>{user?.name}</div>
<div className="text-sm">
{user ? formatMoney(Math.floor(user.balance)) : ' '}
<div className="truncate">
<div>{user.name}</div>
<div className="text-sm">{formatMoney(Math.floor(user.balance))}</div>
</div>
</div>
</Row>
</a>
</Link>
)
}

View File

@ -1,9 +1,7 @@
import {
HomeIcon,
UserGroupIcon,
CakeIcon,
SearchIcon,
ChatIcon,
BookOpenIcon,
DotsHorizontalIcon,
CashIcon,
@ -110,7 +108,8 @@ function MoreButton() {
)
}
export default function Sidebar() {
export default function Sidebar(props: { className?: string }) {
const { className } = props
const router = useRouter()
const currentPage = router.pathname
@ -124,27 +123,15 @@ export default function Sidebar() {
user === null ? signedOutMobileNavigation : mobileNavigation
return (
<nav aria-label="Sidebar" className="sticky top-4 divide-gray-300 pl-2">
<div className="space-y-1 pb-6">
<ManifoldLogo twoLine />
</div>
<nav aria-label="Sidebar" className={className}>
<ManifoldLogo className="pb-6" twoLine />
<div className="mb-2" style={{ minHeight: 80 }}>
{user ? (
<Link href={`/${user.username}`}>
<a className="group">
<ProfileSummary user={user} />
</a>
</Link>
<ProfileSummary user={user} />
) : user === null ? (
<div className="py-6 text-center">
<button
className="btn btn-sm px-6 font-medium normal-case "
style={{
backgroundColor: 'white',
border: '2px solid',
color: '#3D4451',
}}
className="btn btn-sm border-2 bg-white px-6 font-medium normal-case text-gray-700"
onClick={firebaseLogin}
>
Sign in

View File

@ -12,7 +12,7 @@ export function Page(props: {
const { margin, assertUser, children, rightSidebar, suspend } = props
return (
<div>
<>
<div
className={clsx(
'mx-auto w-full pb-14 lg:grid lg:grid-cols-12 lg:gap-8 lg:pt-6 xl:max-w-7xl',
@ -20,9 +20,7 @@ export function Page(props: {
)}
style={suspend ? visuallyHiddenStyle : undefined}
>
<div className="hidden lg:col-span-2 lg:block">
<Sidebar />
</div>
<Sidebar className="sticky top-4 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:block" />
<main
className={clsx(
'lg:col-span-8',
@ -40,7 +38,7 @@ export function Page(props: {
</div>
<BottomNavBar />
</div>
</>
)
}

View File

@ -2,7 +2,6 @@ import clsx from 'clsx'
import React, { useEffect, useState } from 'react'
import { Col } from './layout/col'
import { Title } from './title'
import { User } from 'web/lib/firebase/users'
import { YesNoCancelSelector } from './yes-no-selector'
import { Spacer } from './layout/spacer'

View File

@ -13,6 +13,7 @@ export function ShareMarket(props: { contract: Contract; className?: string }) {
<Row className="mb-6 items-center">
<input
className="input input-bordered flex-1 rounded-r-none text-gray-500"
readOnly
type="text"
value={contractUrl(contract)}
/>

View File

@ -10,8 +10,11 @@ import {
getTopWeeklyContracts,
} from 'web/lib/firebase/contracts'
export const useAlgoFeed = (user: User | null | undefined) => {
const [feed, setFeed] = useState<feed>()
export const useAlgoFeed = (
user: User | null | undefined,
category: string
) => {
const [allFeed, setAllFeed] = useState<feed>()
const [categoryFeeds, setCategoryFeeds] = useState<Dictionary<feed>>()
const getTime = useTimeSinceFirstRender()
@ -20,11 +23,11 @@ export const useAlgoFeed = (user: User | null | undefined) => {
if (user) {
getUserFeed(user.id).then((feed) => {
if (feed.length === 0) {
getDefaultFeed().then((feed) => setFeed(feed))
} else setFeed(feed)
getDefaultFeed().then((feed) => setAllFeed(feed))
} else setAllFeed(feed)
trackLatency('feed', getTime())
console.log('feed load time', getTime())
console.log('"all" feed load time', getTime())
})
getCategoryFeeds(user.id).then((feeds) => {
@ -35,12 +38,9 @@ export const useAlgoFeed = (user: User | null | undefined) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user?.id])
const followedCategory = user?.followedCategories?.[0] ?? 'all'
const feed = category === 'all' ? allFeed : categoryFeeds?.[category]
const followedFeed =
followedCategory === 'all' ? feed : categoryFeeds?.[followedCategory]
return followedFeed
return feed
}
const getDefaultFeed = async () => {

View File

@ -1,5 +1,4 @@
import { doc, collection, setDoc } from 'firebase/firestore'
import _ from 'lodash'
import { db } from './init'
import { ClickEvent, LatencyEvent, View } from 'common/tracking'

View File

@ -1,5 +1,4 @@
import { collection, query, where, orderBy } from 'firebase/firestore'
import _ from 'lodash'
import { Txn } from 'common/txn'
import { db } from './init'

View File

@ -55,6 +55,13 @@ export async function updateUser(userId: string, update: Partial<User>) {
await updateDoc(doc(db, 'users', userId), { ...update })
}
export async function updatePrivateUser(
userId: string,
update: Partial<PrivateUser>
) {
await updateDoc(doc(db, 'private-users', userId), { ...update })
}
export function listenForUser(
userId: string,
setUser: (user: User | null) => void

View File

@ -1,4 +1,3 @@
import { db } from './init'
import {
getDoc,
getDocs,

View File

@ -17,7 +17,7 @@
"postbuild": "next-sitemap"
},
"dependencies": {
"@headlessui/react": "1.5.0",
"@headlessui/react": "1.6.1",
"@heroicons/react": "1.0.5",
"@nivo/core": "0.74.0",
"@nivo/line": "0.74.0",

View File

@ -26,7 +26,7 @@ function printBuildInfo() {
function MyApp({ Component, pageProps }: AppProps) {
usePreserveScroll()
useEffect(printBuildInfo)
useEffect(printBuildInfo, [])
return (
<>
@ -39,6 +39,19 @@ function MyApp({ Component, pageProps }: AppProps) {
gtag('config', 'G-SSFK1Q138D');
`}
</Script>
{/* Hotjar Tracking Code for https://manifold.markets */}
<Script id="hotjar">
{`
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:2968940,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
`}
</Script>
<Head>
<title>Manifold Markets A market for every question</title>

View File

@ -18,7 +18,7 @@ import { getDailyNewUsers } from 'web/lib/firebase/users'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz() {
const numberOfDays = 90
const numberOfDays = 45
const today = dayjs(dayjs().format('YYYY-MM-DD'))
const startDate = today.subtract(numberOfDays, 'day')

View File

@ -13,14 +13,12 @@ import { InfoTooltip } from 'web/components/info-tooltip'
import { Page } from 'web/components/page'
import { Title } from 'web/components/title'
import { ProbabilitySelector } from 'web/components/probability-selector'
import { parseWordsAsTags } from 'common/util/parse'
import { TagsList } from 'web/components/tags-list'
import { Row } from 'web/components/layout/row'
import { MAX_DESCRIPTION_LENGTH, outcomeType } from 'common/contract'
import { formatMoney } from 'common/util/format'
import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today'
import { removeUndefinedProps } from '../../common/util/object'
import { CATEGORIES, CATEGORY_LIST, TO_CATEGORY } from 'common/categories'
import { CATEGORIES } from 'common/categories'
export default function Create() {
const [question, setQuestion] = useState('')
@ -214,15 +212,13 @@ export function NewContract(props: { question: string; tag?: string }) {
<select
className="select select-bordered w-full max-w-xs"
onChange={(e) =>
setCategory(TO_CATEGORY[e.currentTarget.value] ?? '')
}
value={category}
onChange={(e) => setCategory(e.currentTarget.value ?? '')}
>
<option selected={category === ''}></option>
{CATEGORY_LIST.map((cat) => (
<option selected={category === cat} value={CATEGORIES[cat]}>
{CATEGORIES[cat]}
<option value={''}></option>
{Object.entries(CATEGORIES).map(([id, name]) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>

View File

@ -1,7 +1,5 @@
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import Router, { useRouter } from 'next/router'
import _ from 'lodash'
import { Page } from 'web/components/page'
import { ActivityFeed } from 'web/components/feed/activity-feed'
import FeedCreate from 'web/components/feed-create'
@ -15,8 +13,9 @@ import { CategorySelector } from '../components/feed/category-selector'
const Home = () => {
const user = useUser()
const [category, setCategory] = useState<string>('all')
const feed = useAlgoFeed(user)
const feed = useAlgoFeed(user, category)
const router = useRouter()
const { u: username, s: slug } = router.query
@ -40,27 +39,24 @@ const Home = () => {
return (
<>
<Page assertUser="signed-in" suspend={!!contract}>
<Col className="items-center">
<Col className="w-full max-w-[700px]">
<FeedCreate user={user ?? undefined} />
<Spacer h={2} />
<CategorySelector user={user} />
<Spacer h={1} />
{feed ? (
<ActivityFeed
feed={feed}
mode="only-recent"
getContractPath={(c) =>
`home?u=${c.creatorUsername}&s=${c.slug}`
}
/>
) : (
<LoadingIndicator className="mt-4" />
)}
</Col>
<Col className="mx-auto w-full max-w-[700px]">
<FeedCreate user={user ?? undefined} />
<Spacer h={2} />
<CategorySelector
user={user}
category={category}
setCategory={setCategory}
/>
<Spacer h={1} />
{feed ? (
<ActivityFeed
feed={feed}
mode="only-recent"
getContractPath={(c) => `home?u=${c.creatorUsername}&s=${c.slug}`}
/>
) : (
<LoadingIndicator className="mt-4" />
)}
</Col>
</Page>

View File

@ -1,5 +1,3 @@
import _ from 'lodash'
import { Col } from 'web/components/layout/col'
import { Leaderboard } from 'web/components/leaderboard'
import { Page } from 'web/components/page'

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { PencilIcon } from '@heroicons/react/outline'
import { RefreshIcon } from '@heroicons/react/outline'
import Router from 'next/router'
import { AddFundsButton } from 'web/components/add-funds-button'
@ -14,7 +14,7 @@ import { uploadImage } from 'web/lib/firebase/storage'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { User } from 'common/user'
import { updateUser } from 'web/lib/firebase/users'
import { updateUser, updatePrivateUser } from 'web/lib/firebase/users'
import { defaultBannerUrl } from 'web/components/user-page'
import { SiteLink } from 'web/components/site-link'
import Textarea from 'react-expanding-textarea'
@ -64,6 +64,7 @@ export default function ProfilePage() {
const [avatarLoading, setAvatarLoading] = useState(false)
const [name, setName] = useState(user?.name || '')
const [username, setUsername] = useState(user?.username || '')
const [apiKey, setApiKey] = useState(privateUser?.apiKey || '')
useEffect(() => {
if (user) {
@ -73,6 +74,12 @@ export default function ProfilePage() {
}
}, [user])
useEffect(() => {
if (privateUser) {
setApiKey(privateUser.apiKey || '')
}
}, [privateUser])
const updateDisplayName = async () => {
const newName = cleanDisplayName(name)
@ -104,6 +111,17 @@ export default function ProfilePage() {
}
}
const updateApiKey = async (e: React.MouseEvent) => {
const newApiKey = crypto.randomUUID()
if (user?.id != null) {
setApiKey(newApiKey)
await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => {
setApiKey(privateUser?.apiKey || '')
})
}
e.preventDefault()
}
const fileHandler = async (event: any) => {
const file = event.target.files[0]
@ -156,7 +174,6 @@ export default function ProfilePage() {
<div>
<label className="label">Display name</label>
<input
type="text"
placeholder="Display name"
@ -169,7 +186,6 @@ export default function ProfilePage() {
<div>
<label className="label">Username</label>
<input
type="text"
placeholder="Username"
@ -234,6 +250,25 @@ export default function ProfilePage() {
<AddFundsButton />
</Row>
</div>
<div>
<label className="label">API key</label>
<div className="input-group w-full">
<input
type="text"
placeholder="Click refresh to generate key"
className="input input-bordered w-full"
value={apiKey}
readOnly
/>
<button
className="btn btn-primary btn-square p-2"
onClick={updateApiKey}
>
<RefreshIcon />
</button>
</div>
</div>
</Col>
</Col>
</Page>

View File

@ -683,10 +683,10 @@
protobufjs "^6.10.0"
yargs "^16.2.0"
"@headlessui/react@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.5.0.tgz#483b44ba2c8b8d4391e1d2c863898d7dd0cc0296"
integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ==
"@headlessui/react@1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.1.tgz#d822792e589aac005462491dd62f86095e0c3bef"
integrity sha512-gMd6uIs1U4Oz718Z5gFoV0o/vD43/4zvbyiJN9Dt7PK9Ubxn+TmJwTmYwyNJc5KxxU1t0CmgTNgwZX9+4NjCnQ==
"@heroicons/react@1.0.5":
version "1.0.5"
@ -958,7 +958,7 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@react-query-firebase/firestore@^0.4.2":
"@react-query-firebase/firestore@0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@react-query-firebase/firestore/-/firestore-0.4.2.tgz#6ae52768715aa0a5c0d903dd4fd953ed417ba635"
integrity sha512-7eYp905+sfBRcBTdj7W7BAc3bI3V0D0kKca4/juOTnN4gyoNyaCNOCjLPY467dTq325hGs7BX0ol7Pw3JENdHA==
@ -4754,7 +4754,7 @@ react-motion@^0.5.2:
prop-types "^15.5.8"
raf "^3.1.0"
react-query@^3.39.0:
react-query@3.39.0:
version "3.39.0"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050"
integrity sha512-Od0IkSuS79WJOhzWBx/ys0x13+7wFqgnn64vBqqAAnZ9whocVhl/y1padD5uuZ6EIkXbFbInax0qvY7zGM0thA==