Merge branch 'manifoldmarkets:main' into main

This commit is contained in:
marsteralex 2022-10-11 19:50:32 -07:00 committed by GitHub
commit 195fcf5a67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 148 additions and 56 deletions

View File

@ -1,4 +1,4 @@
import { Dictionary, groupBy, last, sum, sumBy, uniq } from 'lodash' import { Dictionary, groupBy, last, partition, sum, sumBy, uniq } from 'lodash'
import { calculatePayout, getContractBetMetrics } from './calculate' import { calculatePayout, getContractBetMetrics } from './calculate'
import { Bet, LimitBet } from './bet' import { Bet, LimitBet } from './bet'
import { import {
@ -266,7 +266,9 @@ export const calculateMetricsByContract = (
}) })
} }
export type ContractMetrics = ReturnType<typeof calculateMetricsByContract>[number] export type ContractMetrics = ReturnType<
typeof calculateMetricsByContract
>[number]
const calculatePeriodProfit = ( const calculatePeriodProfit = (
contract: CPMMBinaryContract, contract: CPMMBinaryContract,
@ -275,7 +277,10 @@ const calculatePeriodProfit = (
) => { ) => {
const days = period === 'day' ? 1 : period === 'week' ? 7 : 30 const days = period === 'day' ? 1 : period === 'week' ? 7 : 30
const fromTime = Date.now() - days * DAY_MS const fromTime = Date.now() - days * DAY_MS
const previousBets = bets.filter((b) => b.createdTime < fromTime) const [previousBets, recentBets] = partition(
bets,
(b) => b.createdTime < fromTime
)
const prevProb = contract.prob - contract.probChanges[period] const prevProb = contract.prob - contract.probChanges[period]
const prob = contract.resolutionProbability const prob = contract.resolutionProbability
@ -292,13 +297,18 @@ const calculatePeriodProfit = (
contract, contract,
prob prob
) )
const profit = currentBetsValue - previousBetsValue
const profitPercent = const { profit: recentProfit, invested: recentInvested } =
previousBetsValue === 0 ? 0 : 100 * (profit / previousBetsValue) getContractBetMetrics(contract, recentBets)
const profit = currentBetsValue - previousBetsValue + recentProfit
const invested = previousBetsValue + recentInvested
const profitPercent = invested === 0 ? 0 : 100 * (profit / invested)
return { return {
profit, profit,
profitPercent, profitPercent,
invested,
prevValue: previousBetsValue, prevValue: previousBetsValue,
value: currentBetsValue, value: currentBetsValue,
} }

View File

@ -20,7 +20,7 @@ Adapted from https://firebase.google.com/docs/functions/get-started
3. `$ firebase login` to authenticate the CLI tools to Firebase 3. `$ firebase login` to authenticate the CLI tools to Firebase
4. `$ firebase use dev` to choose the dev project 4. `$ firebase use dev` to choose the dev project
### For local development #### (Installing) For local development
0. [Install](https://cloud.google.com/sdk/docs/install) gcloud CLI 0. [Install](https://cloud.google.com/sdk/docs/install) gcloud CLI
1. If you don't have java (or see the error `Error: Process java -version has exited with code 1. Please make sure Java is installed and on your system PATH.`): 1. If you don't have java (or see the error `Error: Process java -version has exited with code 1. Please make sure Java is installed and on your system PATH.`):
@ -35,10 +35,10 @@ Adapted from https://firebase.google.com/docs/functions/get-started
## Developing locally ## Developing locally
0. `$ firebase use dev` if you haven't already 0. `$ ./dev.sh localdb` to start the local emulator and front end
1. `$ yarn serve` to spin up the emulators 0. The Emulator UI is at http://localhost:4000; the functions are hosted on :5001. 1. If you change db trigger code, you have to start (doesn't have to complete) the deploy of it to dev to cause a hard emulator code refresh `$ firebase deploy --only functions:dbTriggerNameHere`
Note: You have to kill and restart emulators when you change code; no hot reload =( - There's surely a better way to cause/react to a db trigger update but just adding this here for now as it works
2. `$ yarn dev:emulate` in `/web` to connect to emulators with the frontend 0. Note: emulated database is cleared after every shutdown 2. If you want to test a scheduled function replace your function in `test-scheduled-function.ts` and send a GET to `http://localhost:8088/testscheduledfunction` (Best user experience is via [Postman](https://www.postman.com/downloads/)!)
## Firestore Commands ## Firestore Commands

View File

@ -32,6 +32,9 @@ async function main() {
user.achievements = await awardBettingStreakBadges(user) user.achievements = await awardBettingStreakBadges(user)
console.log('Added achievements to user', user.id) console.log('Added achievements to user', user.id)
// going to ignore backfilling the proven correct badges for now // going to ignore backfilling the proven correct badges for now
} else {
// Make corrections to existing achievements
await awardMarketCreatorBadges(user)
} }
}) })
) )
@ -67,12 +70,11 @@ async function removeErrorBadges(user: User) {
async function awardMarketCreatorBadges(user: User) { async function awardMarketCreatorBadges(user: User) {
// Award market maker badges // Award market maker badges
const contracts = await getValues<Contract>( const contracts = (
firestore await getValues<Contract>(
.collection(`contracts`) firestore.collection(`contracts`).where('creatorId', '==', user.id)
.where('creatorId', '==', user.id) )
.where('resolution', '!=', 'CANCEL') ).filter((c) => !c.resolution || c.resolution != 'CANCEL')
)
const achievements = { const achievements = {
...user.achievements, ...user.achievements,
@ -81,7 +83,12 @@ async function awardMarketCreatorBadges(user: User) {
}, },
} }
for (const threshold of marketCreatorBadgeRarityThresholds) { for (const threshold of marketCreatorBadgeRarityThresholds) {
const alreadyHasBadge = user.achievements.marketCreator?.badges.some(
(b) => b.data.totalContractsCreated === threshold
)
if (alreadyHasBadge) continue
if (contracts.length >= threshold) { if (contracts.length >= threshold) {
console.log(`User ${user.id} has at least ${threshold} contracts`)
const badge = { const badge = {
type: 'MARKET_CREATOR', type: 'MARKET_CREATOR',
name: 'Market Creator', name: 'Market Creator',

View File

@ -20,7 +20,6 @@ import { getProbability } from 'common/calculate'
import { createMarket } from 'web/lib/firebase/api' import { createMarket } from 'web/lib/firebase/api'
import { removeUndefinedProps } from 'common/util/object' import { removeUndefinedProps } from 'common/util/object'
import { FIXED_ANTE } from 'common/economy' import { FIXED_ANTE } from 'common/economy'
import { useTextEditor } from 'web/components/editor'
import { LoadingIndicator } from 'web/components/loading-indicator' import { LoadingIndicator } from 'web/components/loading-indicator'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { CopyLinkButton } from '../copy-link-button' import { CopyLinkButton } from '../copy-link-button'
@ -43,7 +42,6 @@ export function CreateChallengeModal(props: {
const { user, contract, isOpen, setOpen } = props const { user, contract, isOpen, setOpen } = props
const [challengeSlug, setChallengeSlug] = useState('') const [challengeSlug, setChallengeSlug] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const { editor } = useTextEditor({ placeholder: '' })
return ( return (
<Modal open={isOpen} setOpen={setOpen}> <Modal open={isOpen} setOpen={setOpen}>
@ -64,7 +62,6 @@ export function CreateChallengeModal(props: {
question: newChallenge.question, question: newChallenge.question,
outcomeType: 'BINARY', outcomeType: 'BINARY',
initialProb: 50, initialProb: 50,
description: editor?.getJSON(),
ante: FIXED_ANTE, ante: FIXED_ANTE,
closeTime: dayjs().add(30, 'day').valueOf(), closeTime: dayjs().add(30, 'day').valueOf(),
}) })

View File

@ -17,13 +17,21 @@ export function CommentInput(props: {
// Reply to another comment // Reply to another comment
parentCommentId?: string parentCommentId?: string
onSubmitComment?: (editor: Editor) => void onSubmitComment?: (editor: Editor) => void
// unique id for autosave
pageId: string
className?: string className?: string
}) { }) {
const { parentAnswerOutcome, parentCommentId, replyTo, onSubmitComment } = const {
props parentAnswerOutcome,
parentCommentId,
replyTo,
onSubmitComment,
pageId,
} = props
const user = useUser() const user = useUser()
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
key: `comment ${pageId} ${parentCommentId ?? parentAnswerOutcome ?? ''}`,
simple: true, simple: true,
max: MAX_COMMENT_LENGTH, max: MAX_COMMENT_LENGTH,
placeholder: placeholder:
@ -80,7 +88,7 @@ export function CommentInputTextArea(props: {
const submit = () => { const submit = () => {
submitComment() submitComment()
editor?.commands?.clearContent() editor?.commands?.clearContent(true)
} }
useEffect(() => { useEffect(() => {
@ -107,7 +115,7 @@ export function CommentInputTextArea(props: {
}, },
}) })
// insert at mention and focus // insert at mention and focus
if (replyTo) { if (replyTo && editor.isEmpty) {
editor editor
.chain() .chain()
.setContent({ .setContent({

View File

@ -0,0 +1,36 @@
import { useState } from 'react'
import clsx from 'clsx'
import { buttonClass } from 'web/components/button'
import { CPMMContract } from 'common/contract'
import { LiquidityModal } from './liquidity-modal'
export function AddLiquidityButton(props: {
contract: CPMMContract
className?: string
}) {
const { contract, className } = props
const [open, setOpen] = useState(false)
const disabled =
contract.isResolved || (contract.closeTime ?? Infinity) < Date.now()
if (disabled) return <></>
return (
<a
className={clsx(
buttonClass('2xs', 'override'),
'cursor-pointer',
'gap-1 border-2 border-blue-400 text-blue-400 hover:bg-blue-400 hover:text-white',
className
)}
onClick={() => setOpen(true)}
target="_blank"
>
<div>💧 Add liquidity</div>
<LiquidityModal contract={contract} isOpen={open} setOpen={setOpen} />
</a>
)
}

View File

@ -21,7 +21,6 @@ import {
import { import {
AnswerLabel, AnswerLabel,
BinaryContractOutcomeLabel, BinaryContractOutcomeLabel,
BinaryOutcomeLabel,
CancelLabel, CancelLabel,
FreeResponseOutcomeLabel, FreeResponseOutcomeLabel,
} from '../outcome-label' } from '../outcome-label'
@ -430,17 +429,16 @@ export function ContractCardProbChange(props: {
'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm' 'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm'
)} )}
> >
<Row className="gap-1"> <Row className="gap-1 text-gray-700">
<div className="text-gray-500">Position</div> <div className="text-gray-500">Position</div>
{formatMoney(metrics.payout)} {formatMoney(metrics.payout)} {outcome}
<BinaryOutcomeLabel outcome={outcome} />
</Row> </Row>
{dayMetrics && ( {dayMetrics && (
<> <>
<Row className="items-center"> <Row className="items-center">
<div className="mr-1 text-gray-500">Daily profit</div> <div className="mr-1 text-gray-500">Daily profit</div>
<ProfitBadgeMana amount={dayMetrics.profit} /> <ProfitBadgeMana amount={dayMetrics.profit} gray />
</Row> </Row>
</> </>
)} )}

View File

@ -48,6 +48,7 @@ function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) {
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
// key: `description ${contract.id}`,
max: MAX_DESCRIPTION_LENGTH, max: MAX_DESCRIPTION_LENGTH,
defaultValue: contract.description, defaultValue: contract.description,
disabled: isSubmitting, disabled: isSubmitting,

View File

@ -20,6 +20,7 @@ import { DuplicateContractButton } from '../duplicate-contract-button'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { BETTORS, User } from 'common/user' import { BETTORS, User } from 'common/user'
import { Button } from '../button' import { Button } from '../button'
import { AddLiquidityButton } from './add-liquidity-button'
export const contractDetailsButtonClassName = export const contractDetailsButtonClassName =
'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500' 'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500'
@ -241,6 +242,9 @@ export function ContractInfoDialog(props: {
</table> </table>
<Row className="flex-wrap"> <Row className="flex-wrap">
{mechanism === 'cpmm-1' && (
<AddLiquidityButton contract={contract} className="mr-2" />
)}
<DuplicateContractButton contract={contract} /> <DuplicateContractButton contract={contract} />
</Row> </Row>
</Col> </Col>

View File

@ -9,7 +9,6 @@ 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 { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
import { LiquidityButton } from './liquidity-button'
export function ExtraContractActionsRow(props: { contract: Contract }) { export function ExtraContractActionsRow(props: { contract: Contract }) {
const { contract } = props const { contract } = props
@ -19,10 +18,9 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
return ( return (
<Row> <Row>
<FollowMarketButton contract={contract} user={user} /> <FollowMarketButton contract={contract} user={user} />
{contract.mechanism === 'cpmm-1' && (
<LiquidityButton contract={contract} user={user} />
)}
<LikeMarketButton contract={contract} user={user} /> <LikeMarketButton contract={contract} user={user} />
<Tooltip text="Share" placement="bottom" noTap noFade> <Tooltip text="Share" placement="bottom" noTap noFade>
<Button <Button
size="sm" size="sm"
@ -39,6 +37,7 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
/> />
</Button> </Button>
</Tooltip> </Tooltip>
<ContractInfoDialog contract={contract} user={user} /> <ContractInfoDialog contract={contract} user={user} />
</Row> </Row>
) )

View File

@ -23,7 +23,7 @@ export function LiquidityModal(props: {
return ( return (
<Modal open={isOpen} setOpen={setOpen} size="sm"> <Modal open={isOpen} setOpen={setOpen} size="sm">
<Col className="gap-2.5 rounded bg-white p-4 pb-8 sm:gap-4"> <Col className="gap-2.5 rounded bg-white p-4 pb-8 sm:gap-4">
<Title className="!mt-0 !mb-2" text="💧 Add a subsidy" /> <Title className="!mt-0 !mb-2" text="💧 Add liquidity" />
<div>Total liquidity subsidies: {formatMoney(totalLiquidity)}</div> <div>Total liquidity subsidies: {formatMoney(totalLiquidity)}</div>
<AddLiquidityPanel contract={contract as CPMMContract} /> <AddLiquidityPanel contract={contract as CPMMContract} />
@ -91,7 +91,7 @@ function AddLiquidityPanel(props: { contract: CPMMContract }) {
label="M$" label="M$"
error={error} error={error}
disabled={isLoading} disabled={isLoading}
inputClassName="w-16 mr-4" inputClassName="w-28 mr-4"
/> />
<Button size="md" color="blue" onClick={submit} disabled={isLoading}> <Button size="md" color="blue" onClick={submit} disabled={isLoading}>
Add Add

View File

@ -21,6 +21,7 @@ export function CreatePost(props: { group?: Group }) {
const { group } = props const { group } = props
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
key: `post ${group?.id || ''}`,
disabled: isSubmitting, disabled: isSubmitting,
}) })
@ -45,6 +46,7 @@ export function CreatePost(props: { group?: Group }) {
return e return e
}) })
if (result.post) { if (result.post) {
editor.commands.clearContent(true)
await Router.push(postPath(result.post.slug)) await Router.push(postPath(result.post.slug))
} }
} }

View File

@ -14,7 +14,7 @@ import StarterKit from '@tiptap/starter-kit'
import { Image } from '@tiptap/extension-image' import { Image } from '@tiptap/extension-image'
import { Link } from '@tiptap/extension-link' import { Link } from '@tiptap/extension-link'
import clsx from 'clsx' import clsx from 'clsx'
import { useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { Linkify } from './linkify' import { Linkify } from './linkify'
import { uploadImage } from 'web/lib/firebase/storage' import { uploadImage } from 'web/lib/firebase/storage'
import { useMutation } from 'react-query' import { useMutation } from 'react-query'
@ -41,6 +41,12 @@ import ItalicIcon from 'web/lib/icons/italic-icon'
import LinkIcon from 'web/lib/icons/link-icon' import LinkIcon from 'web/lib/icons/link-icon'
import { getUrl } from 'common/util/parse' import { getUrl } from 'common/util/parse'
import { TiptapSpoiler } from 'common/util/tiptap-spoiler' import { TiptapSpoiler } from 'common/util/tiptap-spoiler'
import {
storageStore,
usePersistentState,
} from 'web/hooks/use-persistent-state'
import { safeLocalStorage } from 'web/lib/util/local'
import { debounce } from 'lodash'
const DisplayImage = Image.configure({ const DisplayImage = Image.configure({
HTMLAttributes: { HTMLAttributes: {
@ -90,19 +96,34 @@ export function useTextEditor(props: {
defaultValue?: Content defaultValue?: Content
disabled?: boolean disabled?: boolean
simple?: boolean simple?: boolean
key?: string // unique key for autosave. If set, plz call `clearContent(true)` on submit to clear autosave
}) { }) {
const { placeholder, max, defaultValue = '', disabled, simple } = props const { placeholder, max, defaultValue, disabled, simple, key } = props
const [content, saveContent] = usePersistentState<JSONContent | undefined>(
undefined,
{
key: `text ${key}`,
store: storageStore(safeLocalStorage()),
}
)
// eslint-disable-next-line react-hooks/exhaustive-deps
const save = useCallback(debounce(saveContent, 500), [])
const editorClass = clsx( const editorClass = clsx(
proseClass, proseClass,
!simple && 'min-h-[6em]', !simple && 'min-h-[6em]',
'outline-none pt-2 px-4', 'outline-none pt-2 px-4',
'prose-img:select-auto', 'prose-img:select-auto',
'[&_.ProseMirror-selectednode]:outline-dotted [&_*]:outline-indigo-300' // selected img, emebeds '[&_.ProseMirror-selectednode]:outline-dotted [&_*]:outline-indigo-300' // selected img, embeds
) )
const editor = useEditor({ const editor = useEditor({
editorProps: { attributes: { class: editorClass } }, editorProps: {
attributes: { class: editorClass, spellcheck: simple ? 'true' : 'false' },
},
onUpdate: key ? ({ editor }) => save(editor.getJSON()) : undefined,
extensions: [ extensions: [
...editorExtensions(simple), ...editorExtensions(simple),
Placeholder.configure({ Placeholder.configure({
@ -112,7 +133,7 @@ export function useTextEditor(props: {
}), }),
CharacterCount.configure({ limit: max }), CharacterCount.configure({ limit: max }),
], ],
content: defaultValue, content: defaultValue ?? (key && content ? content : ''),
}) })
const upload = useUploadMutation(editor) const upload = useUploadMutation(editor)

View File

@ -268,6 +268,7 @@ export function ContractCommentInput(props: {
parentAnswerOutcome={parentAnswerOutcome} parentAnswerOutcome={parentAnswerOutcome}
parentCommentId={parentCommentId} parentCommentId={parentCommentId}
onSubmitComment={onSubmitComment} onSubmitComment={onSubmitComment}
pageId={contract.id}
className={className} className={className}
/> />
) )

View File

@ -20,6 +20,7 @@ import {
goldClassName, goldClassName,
silverClassName, silverClassName,
} from 'web/components/badge-display' } from 'web/components/badge-display'
import { formatMoney } from 'common/util/format'
export function BadgesModal(props: { export function BadgesModal(props: {
isOpen: boolean isOpen: boolean
@ -132,7 +133,9 @@ function ProvenCorrectBadgeItem(props: {
<Col className={'text-center'}> <Col className={'text-center'}>
<Medal rarity={rarity} /> <Medal rarity={rarity} />
<Tooltip <Tooltip
text={`Make a comment attached to a winning bet worth ${betAmount}`} text={`Make a comment attached to a winning bet worth ${formatMoney(
betAmount
)}`}
> >
<span <span
className={ className={

View File

@ -28,10 +28,17 @@ export function ProfitBadge(props: {
) )
} }
export function ProfitBadgeMana(props: { amount: number; className?: string }) { export function ProfitBadgeMana(props: {
const { amount, className } = props amount: number
const colors = gray?: boolean
amount > 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' className?: string
}) {
const { amount, gray, className } = props
const colors = gray
? 'bg-gray-100 text-gray-700'
: amount > 0
? 'bg-gray-100 text-green-800'
: 'bg-gray-100 text-red-800'
const formatted = const formatted =
ENV_CONFIG.moneyMoniker + (amount > 0 ? '+' : '') + amount.toFixed(0) ENV_CONFIG.moneyMoniker + (amount > 0 ? '+' : '') + amount.toFixed(0)

View File

@ -22,7 +22,6 @@ import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
import { getGroup, groupPath } from 'web/lib/firebase/groups' import { getGroup, groupPath } from 'web/lib/firebase/groups'
import { Group } from 'common/group' import { Group } from 'common/group'
import { useTracking } from 'web/hooks/use-tracking' import { useTracking } from 'web/hooks/use-tracking'
import { useWarnUnsavedChanges } from 'web/hooks/use-warn-unsaved-changes'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { GroupSelector } from 'web/components/groups/group-selector' import { GroupSelector } from 'web/components/groups/group-selector'
import { User } from 'common/user' import { User } from 'common/user'
@ -228,6 +227,7 @@ export function NewContract(props: {
: `e.g. I will choose the answer according to...` : `e.g. I will choose the answer according to...`
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
key: 'create market',
max: MAX_DESCRIPTION_LENGTH, max: MAX_DESCRIPTION_LENGTH,
placeholder: descriptionPlaceholder, placeholder: descriptionPlaceholder,
disabled: isSubmitting, disabled: isSubmitting,
@ -236,9 +236,6 @@ export function NewContract(props: {
: undefined, : undefined,
}) })
const isEditorFilled = editor != null && !editor.isEmpty
useWarnUnsavedChanges(!isSubmitting && (Boolean(question) || isEditorFilled))
function setCloseDateInDays(days: number) { function setCloseDateInDays(days: number) {
const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DD') const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DD')
setCloseDate(newCloseDate) setCloseDate(newCloseDate)
@ -272,6 +269,7 @@ export function NewContract(props: {
selectedGroup: selectedGroup?.id, selectedGroup: selectedGroup?.id,
isFree: false, isFree: false,
}) })
editor?.commands.clearContent(true)
await router.push(contractPath(result as Contract)) await router.push(contractPath(result as Contract))
} catch (e) { } catch (e) {
console.error('error creating contract', e, (e as any).details) console.error('error creating contract', e, (e as any).details)

View File

@ -99,10 +99,10 @@ export async function getStaticPaths() {
const groupSubpages = [ const groupSubpages = [
undefined, undefined,
GROUP_CHAT_SLUG, GROUP_CHAT_SLUG,
'overview',
'markets', 'markets',
'leaderboards', 'leaderboards',
'about', 'about',
'posts',
] as const ] as const
export default function GroupPage(props: { export default function GroupPage(props: {
@ -131,8 +131,8 @@ export default function GroupPage(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( const tabIndex = ['overview', 'markets', 'leaderboards'].indexOf(
page ?? 'markets' page === 'about' ? 'overview' : page ?? 'markets'
) )
const group = useGroup(props.group?.id) ?? props.group const group = useGroup(props.group?.id) ?? props.group

View File

@ -1220,10 +1220,9 @@ function getSourceUrl(notification: Notification) {
sourceType sourceType
)}` )}`
else if (sourceSlug) else if (sourceSlug)
return `/${sourceSlug}#${getSourceIdForLinkComponent( return `${
sourceId ?? '', sourceSlug.startsWith('/') ? sourceSlug : '/' + sourceSlug
sourceType }#${getSourceIdForLinkComponent(sourceId ?? '', sourceType)}`
)}`
} }
function getSourceIdForLinkComponent( function getSourceIdForLinkComponent(

View File

@ -94,6 +94,7 @@ export function PostCommentInput(props: {
replyTo={replyToUser} replyTo={replyToUser}
parentCommentId={parentCommentId} parentCommentId={parentCommentId}
onSubmitComment={onSubmitComment} onSubmitComment={onSubmitComment}
pageId={post.id}
/> />
) )
} }