diff --git a/common/envs/prod.ts b/common/envs/prod.ts
index 2b1ee70e..b3b552eb 100644
--- a/common/envs/prod.ts
+++ b/common/envs/prod.ts
@@ -73,6 +73,7 @@ export const PROD_CONFIG: EnvConfig = {
'manticmarkets@gmail.com', // Manifold
'iansphilips@gmail.com', // Ian
'd4vidchee@gmail.com', // D4vid
+ 'federicoruizcassarino@gmail.com', // Fede
],
visibility: 'PUBLIC',
diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts
index 7d4a0185..bf6f5ebc 100644
--- a/common/payouts-dpm.ts
+++ b/common/payouts-dpm.ts
@@ -13,7 +13,6 @@ import { addObjects } from './util/object'
export const getDpmCancelPayouts = (contract: DPMContract, bets: Bet[]) => {
const { pool } = contract
const poolTotal = sum(Object.values(pool))
- console.log('resolved N/A, pool M$', poolTotal)
const betSum = sumBy(bets, (b) => b.amount)
@@ -58,17 +57,6 @@ export const getDpmStandardPayouts = (
liquidityFee: 0,
})
- console.log(
- 'resolved',
- outcome,
- 'pool',
- poolTotal,
- 'profits',
- profits,
- 'creator fee',
- creatorFee
- )
-
return {
payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
creatorPayout: creatorFee,
@@ -110,17 +98,6 @@ export const getNumericDpmPayouts = (
liquidityFee: 0,
})
- console.log(
- 'resolved numeric bucket: ',
- outcome,
- 'pool',
- poolTotal,
- 'profits',
- profits,
- 'creator fee',
- creatorFee
- )
-
return {
payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
creatorPayout: creatorFee,
@@ -163,17 +140,6 @@ export const getDpmMktPayouts = (
liquidityFee: 0,
})
- console.log(
- 'resolved MKT',
- p,
- 'pool',
- pool,
- 'profits',
- profits,
- 'creator fee',
- creatorFee
- )
-
return {
payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
creatorPayout: creatorFee,
@@ -216,16 +182,6 @@ export const getPayoutsMultiOutcome = (
liquidityFee: 0,
})
- console.log(
- 'resolved',
- resolutions,
- 'pool',
- poolTotal,
- 'profits',
- profits,
- 'creator fee',
- creatorFee
- )
return {
payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
creatorPayout: creatorFee,
diff --git a/common/payouts-fixed.ts b/common/payouts-fixed.ts
index 4b8de85a..99e03fac 100644
--- a/common/payouts-fixed.ts
+++ b/common/payouts-fixed.ts
@@ -1,4 +1,3 @@
-import { sum } from 'lodash'
import { Bet } from './bet'
import { getProbability } from './calculate'
@@ -43,18 +42,6 @@ export const getStandardFixedPayouts = (
const { collectedFees } = contract
const creatorPayout = collectedFees.creatorFee
-
- console.log(
- 'resolved',
- outcome,
- 'pool',
- contract.pool[outcome],
- 'payouts',
- sum(payouts),
- 'creator fee',
- creatorPayout
- )
-
const liquidityPayouts = getLiquidityPoolPayouts(
contract,
outcome,
@@ -98,18 +85,6 @@ export const getMktFixedPayouts = (
const { collectedFees } = contract
const creatorPayout = collectedFees.creatorFee
-
- console.log(
- 'resolved PROB',
- p,
- 'pool',
- p * contract.pool.YES + (1 - p) * contract.pool.NO,
- 'payouts',
- sum(payouts),
- 'creator fee',
- creatorPayout
- )
-
const liquidityPayouts = getLiquidityPoolProbPayouts(contract, p, liquidities)
return { payouts, creatorPayout, liquidityPayouts, collectedFees }
diff --git a/common/redeem.ts b/common/redeem.ts
index e0839ff8..f786a1c2 100644
--- a/common/redeem.ts
+++ b/common/redeem.ts
@@ -13,7 +13,10 @@ export const getRedeemableAmount = (bets: RedeemableBet[]) => {
const yesShares = sumBy(yesBets, (b) => b.shares)
const noShares = sumBy(noBets, (b) => b.shares)
const shares = Math.max(Math.min(yesShares, noShares), 0)
- const soldFrac = shares > 0 ? Math.min(yesShares, noShares) / shares : 0
+ const soldFrac =
+ shares > 0
+ ? Math.min(yesShares, noShares) / Math.max(yesShares, noShares)
+ : 0
const loanAmount = sumBy(bets, (bet) => bet.loanAmount ?? 0)
const loanPayment = loanAmount * soldFrac
const netAmount = shares - loanPayment
diff --git a/common/scoring.ts b/common/scoring.ts
index 39a342fd..4ef46534 100644
--- a/common/scoring.ts
+++ b/common/scoring.ts
@@ -1,8 +1,8 @@
-import { groupBy, sumBy, mapValues, partition } from 'lodash'
+import { groupBy, sumBy, mapValues } from 'lodash'
import { Bet } from './bet'
+import { getContractBetMetrics } from './calculate'
import { Contract } from './contract'
-import { getPayouts } from './payouts'
export function scoreCreators(contracts: Contract[]) {
const creatorScore = mapValues(
@@ -30,46 +30,8 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
}
export function scoreUsersByContract(contract: Contract, bets: Bet[]) {
- const { resolution } = contract
- const resolutionProb =
- contract.outcomeType == 'BINARY'
- ? contract.resolutionProbability
- : undefined
-
- const [closedBets, openBets] = partition(
- bets,
- (bet) => bet.isSold || bet.sale
- )
- const { payouts: resolvePayouts } = getPayouts(
- resolution as string,
- contract,
- openBets,
- [],
- {},
- resolutionProb
- )
-
- const salePayouts = closedBets.map((bet) => {
- const { userId, sale } = bet
- return { userId, payout: sale ? sale.amount : 0 }
- })
-
- const investments = bets
- .filter((bet) => !bet.sale)
- .map((bet) => {
- const { userId, amount, loanAmount } = bet
- const payout = -amount - (loanAmount ?? 0)
- return { userId, payout }
- })
-
- const netPayouts = [...resolvePayouts, ...salePayouts, ...investments]
-
- const userScore = mapValues(
- groupBy(netPayouts, (payout) => payout.userId),
- (payouts) => sumBy(payouts, ({ payout }) => payout)
- )
-
- return userScore
+ const betsByUser = groupBy(bets, bet => bet.userId)
+ return mapValues(betsByUser, bets => getContractBetMetrics(contract, bets).profit)
}
export function addUserScores(
diff --git a/docs/docs/api.md b/docs/docs/api.md
index e284abdf..64e26de8 100644
--- a/docs/docs/api.md
+++ b/docs/docs/api.md
@@ -60,23 +60,27 @@ Parameters:
Requires no authorization.
-### `GET /v0/groups/[slug]`
+### `GET /v0/group/[slug]`
Gets a group by its slug.
-Requires no authorization.
+Requires no authorization.
+Note: group is singular in the URL.
### `GET /v0/group/by-id/[id]`
Gets a group by its unique ID.
-Requires no authorization.
+Requires no authorization.
+Note: group is singular in the URL.
### `GET /v0/group/by-id/[id]/markets`
Gets a group's markets by its unique ID.
-Requires no authorization.
+Requires no authorization.
+Note: group is singular in the URL.
+
### `GET /v0/markets`
diff --git a/firestore.rules b/firestore.rules
index 30bf0ec9..9a72e454 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -12,7 +12,9 @@ service cloud.firestore {
'taowell@gmail.com',
'abc.sinclair@gmail.com',
'manticmarkets@gmail.com',
- 'iansphilips@gmail.com'
+ 'iansphilips@gmail.com',
+ 'd4vidchee@gmail.com',
+ 'federicoruizcassarino@gmail.com'
]
}
diff --git a/functions/src/change-user-info.ts b/functions/src/change-user-info.ts
index aa041856..ca66f1ba 100644
--- a/functions/src/change-user-info.ts
+++ b/functions/src/change-user-info.ts
@@ -37,6 +37,45 @@ export const changeUser = async (
avatarUrl?: string
}
) => {
+ // Update contracts, comments, and answers outside of a transaction to avoid contention.
+ // Using bulkWriter to supports >500 writes at a time
+ const contractsRef = firestore
+ .collection('contracts')
+ .where('creatorId', '==', user.id)
+
+ const contracts = await contractsRef.get()
+
+ const contractUpdate: Partial = removeUndefinedProps({
+ creatorName: update.name,
+ creatorUsername: update.username,
+ creatorAvatarUrl: update.avatarUrl,
+ })
+
+ const commentSnap = await firestore
+ .collectionGroup('comments')
+ .where('userUsername', '==', user.username)
+ .get()
+
+ const commentUpdate: Partial = removeUndefinedProps({
+ userName: update.name,
+ userUsername: update.username,
+ userAvatarUrl: update.avatarUrl,
+ })
+
+ const answerSnap = await firestore
+ .collectionGroup('answers')
+ .where('username', '==', user.username)
+ .get()
+ const answerUpdate: Partial = removeUndefinedProps(update)
+
+ const bulkWriter = firestore.bulkWriter()
+ commentSnap.docs.forEach((d) => bulkWriter.update(d.ref, commentUpdate))
+ answerSnap.docs.forEach((d) => bulkWriter.update(d.ref, answerUpdate))
+ contracts.docs.forEach((d) => bulkWriter.update(d.ref, contractUpdate))
+ await bulkWriter.flush()
+ console.log('Done writing!')
+
+ // Update the username inside a transaction
return await firestore.runTransaction(async (transaction) => {
if (update.username) {
update.username = cleanUsername(update.username)
@@ -58,42 +97,7 @@ export const changeUser = async (
const userRef = firestore.collection('users').doc(user.id)
const userUpdate: Partial = removeUndefinedProps(update)
-
- const contractsRef = firestore
- .collection('contracts')
- .where('creatorId', '==', user.id)
-
- const contracts = await transaction.get(contractsRef)
-
- const contractUpdate: Partial = removeUndefinedProps({
- creatorName: update.name,
- creatorUsername: update.username,
- creatorAvatarUrl: update.avatarUrl,
- })
-
- const commentSnap = await transaction.get(
- firestore
- .collectionGroup('comments')
- .where('userUsername', '==', user.username)
- )
-
- const commentUpdate: Partial = removeUndefinedProps({
- userName: update.name,
- userUsername: update.username,
- userAvatarUrl: update.avatarUrl,
- })
-
- const answerSnap = await transaction.get(
- firestore
- .collectionGroup('answers')
- .where('username', '==', user.username)
- )
- const answerUpdate: Partial = removeUndefinedProps(update)
-
transaction.update(userRef, userUpdate)
- commentSnap.docs.forEach((d) => transaction.update(d.ref, commentUpdate))
- answerSnap.docs.forEach((d) => transaction.update(d.ref, answerUpdate))
- contracts.docs.forEach((d) => transaction.update(d.ref, contractUpdate))
})
}
diff --git a/functions/src/email-templates/creating-market.html b/functions/src/email-templates/creating-market.html
index a61e8d65..df215bdc 100644
--- a/functions/src/email-templates/creating-market.html
+++ b/functions/src/email-templates/creating-market.html
@@ -186,8 +186,9 @@
font-family: Readex Pro, Arial, Helvetica,
sans-serif;
font-size: 17px;
- ">Did you know you create your own prediction market on Manifold for
+ ">Did you know you can create your own prediction market on Manifold on
any question you care about?
diff --git a/web/components/amount-input.tsx b/web/components/amount-input.tsx
index 9eff26ef..2ad745a8 100644
--- a/web/components/amount-input.tsx
+++ b/web/components/amount-input.tsx
@@ -122,6 +122,18 @@ export function BuyAmountInput(props: {
}
}
+ const parseRaw = (x: number) => {
+ if (x <= 100) return x
+ if (x <= 130) return 100 + (x - 100) * 5
+ return 250 + (x - 130) * 10
+ }
+
+ const getRaw = (x: number) => {
+ if (x <= 100) return x
+ if (x <= 250) return 100 + (x - 100) / 5
+ return 130 + (x - 250) / 10
+ }
+
return (
<>
onAmountChange(parseInt(e.target.value))}
- className="range range-lg z-40 mb-2 xl:hidden"
+ max="205"
+ value={getRaw(amount ?? 0)}
+ onChange={(e) => onAmountChange(parseRaw(parseInt(e.target.value)))}
+ className="range range-lg only-thumb z-40 mb-2 xl:hidden"
step="5"
/>
)}
diff --git a/web/components/answers/answer-bet-panel.tsx b/web/components/answers/answer-bet-panel.tsx
index 6e54b3b8..3339ded5 100644
--- a/web/components/answers/answer-bet-panel.tsx
+++ b/web/components/answers/answer-bet-panel.tsx
@@ -1,5 +1,5 @@
import clsx from 'clsx'
-import React, { useEffect, useRef, useState } from 'react'
+import React, { useState } from 'react'
import { XIcon } from '@heroicons/react/solid'
import { Answer } from 'common/answer'
@@ -25,8 +25,7 @@ import {
import { Bet } from 'common/bet'
import { track } from 'web/lib/service/analytics'
import { BetSignUpPrompt } from '../sign-up-prompt'
-import { isIOS } from 'web/lib/util/device'
-import { AlertBox } from '../alert-box'
+import { WarningConfirmationButton } from '../warning-confirmation-button'
export function AnswerBetPanel(props: {
answer: Answer
@@ -44,12 +43,6 @@ export function AnswerBetPanel(props: {
const [error, setError] = useState()
const [isSubmitting, setIsSubmitting] = useState(false)
- const inputRef = useRef(null)
- useEffect(() => {
- if (isIOS()) window.scrollTo(0, window.scrollY + 200)
- inputRef.current && inputRef.current.focus()
- }, [])
-
async function submitBet() {
if (!user || !betAmount) return
@@ -116,6 +109,15 @@ export function AnswerBetPanel(props: {
const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9)
+ const warning =
+ (betAmount ?? 0) >= 100 && bankrollFraction >= 0.5 && bankrollFraction <= 1
+ ? `You might not want to spend ${formatPercent(
+ bankrollFraction
+ )} of your balance on a single bet. \n\nCurrent balance: ${formatMoney(
+ user?.balance ?? 0
+ )}`
+ : undefined
+
return (
@@ -144,25 +146,9 @@ export function AnswerBetPanel(props: {
error={error}
setError={setError}
disabled={isSubmitting}
- inputRef={inputRef}
showSliderOnMobile
/>
- {(betAmount ?? 0) > 10 &&
- bankrollFraction >= 0.5 &&
- bankrollFraction <= 1 ? (
-
- ) : (
- ''
- )}
-
Probability
@@ -198,16 +184,17 @@ export function AnswerBetPanel(props: {
{user ? (
-
+ />
) : (
)}
diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx
index e53153b1..5811403f 100644
--- a/web/components/answers/answers-panel.tsx
+++ b/web/components/answers/answers-panel.tsx
@@ -194,7 +194,7 @@ function OpenAnswer(props: {
return (
-
+
setOpen(true)}
>
- Trade
+ Predict
) : (
@@ -60,7 +60,7 @@ export default function BetButton(props: {
)}
-
+
{
- // if (selected) {
- // if (isIOS()) window.scrollTo(0, window.scrollY + 200)
- // focusAmountInput()
- // }
- // }, [selected, focusAmountInput])
-
function onBetChoice(choice: 'YES' | 'NO') {
setOutcome(choice)
setWasSubmitted(false)
- focusAmountInput()
+
+ if (!isIOS() && !isAndroid()) {
+ focusAmountInput()
+ }
}
function onBetChange(newAmount: number | undefined) {
@@ -274,30 +271,20 @@ function BuyPanel(props: {
const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9)
const warning =
- (betAmount ?? 0) > 10 &&
- bankrollFraction >= 0.5 &&
- bankrollFraction <= 1 ? (
- = 100 && bankrollFraction >= 0.5 && bankrollFraction <= 1
+ ? `You might not want to spend ${formatPercent(
bankrollFraction
)} of your balance on a single trade. \n\nCurrent balance: ${formatMoney(
user?.balance ?? 0
- )}`}
- />
- ) : (betAmount ?? 0) > 10 && probChange >= 0.3 && bankrollFraction <= 1 ? (
-
- ) : (
- <>>
- )
+ )}`
+ : (betAmount ?? 0) > 10 && probChange >= 0.3 && bankrollFraction <= 1
+ ? `Are you sure you want to move the market by ${displayedDifference}?`
+ : undefined
return (
- {isPseudoNumeric ? 'Direction' : 'Buy'}
+ {isPseudoNumeric ? 'Direction' : 'Outcome'}
- {warning}
-
@@ -367,20 +352,20 @@ function BuyPanel(props: {
{user && (
-
+ />
)}
{wasSubmitted &&
Trade submitted!
}
@@ -750,9 +735,7 @@ function QuickOrLimitBet(props: {
return (
-
- Trade
-
+ Predict
{!hideToggle && (
[0]['upload']
submitComment: (id?: string) => void
isSubmitting: boolean
- submitOnEnter?: boolean
presetId?: string
}) {
const {
@@ -90,11 +88,8 @@ export function CommentInputTextArea(props: {
submitComment,
presetId,
isSubmitting,
- submitOnEnter,
replyToUser,
} = props
- const isMobile = (useWindowSize().width ?? 0) < 768 // TODO: base off input device (keybord vs touch)
-
useEffect(() => {
editor?.setEditable(!isSubmitting)
}, [isSubmitting, editor])
@@ -108,15 +103,14 @@ export function CommentInputTextArea(props: {
if (!editor) {
return
}
- // submit on Enter key
+ // Submit on ctrl+enter or mod+enter key
editor.setOptions({
editorProps: {
handleKeyDown: (view, event) => {
if (
- submitOnEnter &&
event.key === 'Enter' &&
!event.shiftKey &&
- (!isMobile || event.ctrlKey || event.metaKey) &&
+ (event.ctrlKey || event.metaKey) &&
// mention list is closed
!(view.state as any).mention$.active
) {
diff --git a/web/components/confirmation-button.tsx b/web/components/confirmation-button.tsx
index bc014902..8dbe90c2 100644
--- a/web/components/confirmation-button.tsx
+++ b/web/components/confirmation-button.tsx
@@ -47,13 +47,13 @@ export function ConfirmationButton(props: {
{children}
updateOpen(false)}
>
{cancelBtn?.label ?? 'Cancel'}
@@ -69,7 +69,7 @@ export function ConfirmationButton(props: {
updateOpen(true)}
>
{openModalBtn.icon}
diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx
index 40fa9da0..d63d3963 100644
--- a/web/components/contract/contract-tabs.tsx
+++ b/web/components/contract/contract-tabs.tsx
@@ -13,7 +13,6 @@ import { Tabs } from '../layout/tabs'
import { Col } from '../layout/col'
import { tradingAllowed } from 'web/lib/firebase/contracts'
import { CommentTipMap } from 'web/hooks/use-tip-txns'
-import { useBets } from 'web/hooks/use-bets'
import { useComments } from 'web/hooks/use-comments'
import { useLiquidity } from 'web/hooks/use-liquidity'
import { BetSignUpPrompt } from '../sign-up-prompt'
@@ -27,24 +26,23 @@ export function ContractTabs(props: {
comments: ContractComment[]
tips: CommentTipMap
}) {
- const { contract, user, tips } = props
+ const { contract, user, bets, tips } = props
const { outcomeType } = contract
- const bets = useBets(contract.id) ?? props.bets
- const lps = useLiquidity(contract.id) ?? []
+ const lps = useLiquidity(contract.id)
const userBets =
user && bets.filter((bet) => !bet.isAnte && bet.userId === user.id)
const visibleBets = bets.filter(
(bet) => !bet.isAnte && !bet.isRedemption && bet.amount !== 0
)
- const visibleLps = lps.filter((l) => !l.isAnte && l.amount > 0)
+ const visibleLps = lps?.filter((l) => !l.isAnte && l.amount > 0)
// Load comments here, so the badge count will be correct
const updatedComments = useComments(contract.id)
const comments = updatedComments ?? props.comments
- const betActivity = (
+ const betActivity = visibleLps && (
)
}
diff --git a/web/components/editor.tsx b/web/components/editor.tsx
index b36571ba..bb947579 100644
--- a/web/components/editor.tsx
+++ b/web/components/editor.tsx
@@ -18,7 +18,6 @@ import { uploadImage } from 'web/lib/firebase/storage'
import { useMutation } from 'react-query'
import { FileUploadButton } from './file-upload-button'
import { linkClass } from './site-link'
-import { useUsers } from 'web/hooks/use-users'
import { mentionSuggestion } from './editor/mention-suggestion'
import { DisplayMention } from './editor/mention'
import Iframe from 'common/util/tiptap-iframe'
@@ -68,8 +67,6 @@ export function useTextEditor(props: {
}) {
const { placeholder, max, defaultValue = '', disabled, simple } = props
- const users = useUsers()
-
const editorClass = clsx(
proseClass,
!simple && 'min-h-[6em]',
@@ -78,32 +75,27 @@ export function useTextEditor(props: {
'[&_.ProseMirror-selectednode]:outline-dotted [&_*]:outline-indigo-300' // selected img, emebeds
)
- const editor = useEditor(
- {
- editorProps: { attributes: { class: editorClass } },
- extensions: [
- StarterKit.configure({
- heading: simple ? false : { levels: [1, 2, 3] },
- horizontalRule: simple ? false : {},
- }),
- Placeholder.configure({
- placeholder,
- emptyEditorClass:
- 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0 cursor-text',
- }),
- CharacterCount.configure({ limit: max }),
- simple ? DisplayImage : Image,
- DisplayLink,
- DisplayMention.configure({
- suggestion: mentionSuggestion(users),
- }),
- Iframe,
- TiptapTweet,
- ],
- content: defaultValue,
- },
- [!users.length] // passed as useEffect dependency. (re-render editor when users load, to update mention menu)
- )
+ const editor = useEditor({
+ editorProps: { attributes: { class: editorClass } },
+ extensions: [
+ StarterKit.configure({
+ heading: simple ? false : { levels: [1, 2, 3] },
+ horizontalRule: simple ? false : {},
+ }),
+ Placeholder.configure({
+ placeholder,
+ emptyEditorClass:
+ 'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0 cursor-text',
+ }),
+ CharacterCount.configure({ limit: max }),
+ simple ? DisplayImage : Image,
+ DisplayLink,
+ DisplayMention.configure({ suggestion: mentionSuggestion }),
+ Iframe,
+ TiptapTweet,
+ ],
+ content: defaultValue,
+ })
const upload = useUploadMutation(editor)
diff --git a/web/components/editor/mention-suggestion.ts b/web/components/editor/mention-suggestion.ts
index e21789c9..9f016d47 100644
--- a/web/components/editor/mention-suggestion.ts
+++ b/web/components/editor/mention-suggestion.ts
@@ -1,9 +1,9 @@
import type { MentionOptions } from '@tiptap/extension-mention'
import { ReactRenderer } from '@tiptap/react'
-import { User } from 'common/user'
import { searchInAny } from 'common/util/parse'
import { orderBy } from 'lodash'
import tippy from 'tippy.js'
+import { getCachedUsers } from 'web/hooks/use-users'
import { MentionList } from './mention-list'
type Suggestion = MentionOptions['suggestion']
@@ -12,10 +12,12 @@ const beginsWith = (text: string, query: string) =>
text.toLocaleLowerCase().startsWith(query.toLocaleLowerCase())
// copied from https://tiptap.dev/api/nodes/mention#usage
-export const mentionSuggestion = (users: User[]): Suggestion => ({
- items: ({ query }) =>
+export const mentionSuggestion: Suggestion = {
+ items: async ({ query }) =>
orderBy(
- users.filter((u) => searchInAny(query, u.username, u.name)),
+ (await getCachedUsers()).filter((u) =>
+ searchInAny(query, u.username, u.name)
+ ),
[
(u) => [u.name, u.username].some((s) => beginsWith(s, query)),
'followerCountCached',
@@ -38,7 +40,7 @@ export const mentionSuggestion = (users: User[]): Suggestion => ({
popup = tippy('body', {
getReferenceClientRect: props.clientRect as any,
appendTo: () => document.body,
- content: component.element,
+ content: component?.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
@@ -46,27 +48,27 @@ export const mentionSuggestion = (users: User[]): Suggestion => ({
})
},
onUpdate(props) {
- component.updateProps(props)
+ component?.updateProps(props)
if (!props.clientRect) {
return
}
- popup[0].setProps({
+ popup?.[0].setProps({
getReferenceClientRect: props.clientRect as any,
})
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
- popup[0].hide()
+ popup?.[0].hide()
return true
}
- return (component.ref as any)?.onKeyDown(props)
+ return (component?.ref as any)?.onKeyDown(props)
},
onExit() {
- popup[0].destroy()
- component.destroy()
+ popup?.[0].destroy()
+ component?.destroy()
},
}
},
-})
+}
diff --git a/web/components/feed/feed-comments.tsx b/web/components/feed/feed-comments.tsx
index a63a4b6e..a3e9f35a 100644
--- a/web/components/feed/feed-comments.tsx
+++ b/web/components/feed/feed-comments.tsx
@@ -20,7 +20,6 @@ import { getProbability } from 'common/calculate'
import { track } from 'web/lib/service/analytics'
import { Tipper } from '../tipper'
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
-
import { Content } from '../editor'
import { Editor } from '@tiptap/react'
import { UserLink } from 'web/components/user-link'
diff --git a/web/components/groups/group-about-post.tsx b/web/components/groups/group-about-post.tsx
index ed5c20cc..b76d8037 100644
--- a/web/components/groups/group-about-post.tsx
+++ b/web/components/groups/group-about-post.tsx
@@ -22,7 +22,7 @@ export function GroupAboutPost(props: {
const post = usePost(group.aboutPostId) ?? props.post
return (
-
+
{isEditable ? (
) : (
diff --git a/web/components/layout/modal.tsx b/web/components/layout/modal.tsx
index d1a65607..435286ca 100644
--- a/web/components/layout/modal.tsx
+++ b/web/components/layout/modal.tsx
@@ -8,9 +8,10 @@ export function Modal(props: {
open: boolean
setOpen: (open: boolean) => void
size?: 'sm' | 'md' | 'lg' | 'xl'
+ position?: 'center' | 'top' | 'bottom'
className?: string
}) {
- const { children, open, setOpen, size = 'md', className } = props
+ const { children, position, open, setOpen, size = 'md', className } = props
const sizeClass = {
sm: 'max-w-sm',
@@ -19,6 +20,12 @@ export function Modal(props: {
xl: 'max-w-5xl',
}[size]
+ const positionClass = {
+ center: 'items-center',
+ top: 'items-start',
+ bottom: 'items-end',
+ }[position ?? 'bottom']
+
return (