parent
0a70652667
commit
4d214c01b4
|
@ -10,6 +10,7 @@ export type AnyOutcomeType =
|
||||||
| PseudoNumeric
|
| PseudoNumeric
|
||||||
| FreeResponse
|
| FreeResponse
|
||||||
| Numeric
|
| Numeric
|
||||||
|
|
||||||
export type AnyContractType =
|
export type AnyContractType =
|
||||||
| (CPMM & Binary)
|
| (CPMM & Binary)
|
||||||
| (CPMM & PseudoNumeric)
|
| (CPMM & PseudoNumeric)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export type Like = {
|
export type Like = {
|
||||||
id: string // will be id of the object liked, i.e. contract.id
|
id: string // will be id of the object liked, i.e. contract.id
|
||||||
userId: string
|
userId: string
|
||||||
type: 'contract'
|
type: 'contract' | 'post'
|
||||||
createdTime: number
|
createdTime: number
|
||||||
tipTxnId?: string // only holds most recent tip txn id
|
tipTxnId?: string // only holds most recent tip txn id
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@ export type Post = {
|
||||||
creatorName: string
|
creatorName: string
|
||||||
creatorUsername: string
|
creatorUsername: string
|
||||||
creatorAvatarUrl?: string
|
creatorAvatarUrl?: string
|
||||||
|
|
||||||
|
likedByUserIds?: string[]
|
||||||
|
likedByUserCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DateDoc = Post & {
|
export type DateDoc = Post & {
|
||||||
|
|
|
@ -103,6 +103,7 @@ export const createpost = newEndpoint({}, async (req, auth) => {
|
||||||
creatorName: creator.name,
|
creatorName: creator.name,
|
||||||
creatorUsername: creator.username,
|
creatorUsername: creator.username,
|
||||||
creatorAvatarUrl: creator.avatarUrl,
|
creatorAvatarUrl: creator.avatarUrl,
|
||||||
|
itemType: 'post',
|
||||||
})
|
})
|
||||||
|
|
||||||
await postRef.create(post)
|
await postRef.create(post)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { IconButton } from 'web/components/button'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { ShareModal } from './share-modal'
|
import { ShareModal } from './share-modal'
|
||||||
import { FollowMarketButton } from 'web/components/follow-market-button'
|
import { FollowMarketButton } from 'web/components/follow-market-button'
|
||||||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
import { LikeItemButton } from 'web/components/contract/like-item-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'
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||||
<Row className="gap-1">
|
<Row className="gap-1">
|
||||||
<FollowMarketButton contract={contract} user={user} />
|
<FollowMarketButton contract={contract} user={user} />
|
||||||
|
|
||||||
<LikeMarketButton contract={contract} user={user} />
|
<LikeItemButton item={contract} user={user} itemType={'contract'} />
|
||||||
|
|
||||||
<Tooltip text="Share" placement="bottom" noTap noFade>
|
<Tooltip text="Share" placement="bottom" noTap noFade>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
import React, { useMemo, useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { Contract } from 'common/contract'
|
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { useUserLikes } from 'web/hooks/use-likes'
|
import { useUserLikes } from 'web/hooks/use-likes'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { likeContract } from 'web/lib/firebase/likes'
|
import { likeItem } from 'web/lib/firebase/likes'
|
||||||
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like'
|
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like'
|
||||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
import { useMarketTipTxns } from 'web/hooks/use-tip-txns'
|
import { useItemTipTxns } from 'web/hooks/use-tip-txns'
|
||||||
import { sum } from 'lodash'
|
import { sum } from 'lodash'
|
||||||
import { TipButton } from './tip-button'
|
import { TipButton } from './tip-button'
|
||||||
|
import { Contract } from 'common/contract'
|
||||||
|
import { Post } from 'common/post'
|
||||||
import { TipToast } from '../tipper'
|
import { TipToast } from '../tipper'
|
||||||
|
|
||||||
export function LikeMarketButton(props: {
|
export function LikeItemButton(props: {
|
||||||
contract: Contract
|
item: Contract | Post
|
||||||
user: User | null | undefined
|
user: User | null | undefined
|
||||||
|
itemType: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, user } = props
|
const { item, user, itemType } = props
|
||||||
|
|
||||||
const tips = useMarketTipTxns(contract.id)
|
const tips = useItemTipTxns(item.id)
|
||||||
|
|
||||||
const totalTipped = useMemo(() => {
|
const totalTipped = useMemo(() => {
|
||||||
return sum(tips.map((tip) => tip.amount))
|
return sum(tips.map((tip) => tip.amount))
|
||||||
|
@ -27,21 +29,22 @@ export function LikeMarketButton(props: {
|
||||||
|
|
||||||
const [isLiking, setIsLiking] = useState(false)
|
const [isLiking, setIsLiking] = useState(false)
|
||||||
|
|
||||||
const userLikedContractIds = likes
|
const userLikedItemIds = likes
|
||||||
?.filter((l) => l.type === 'contract')
|
?.filter((l) => l.type === 'contract' || l.type === 'post')
|
||||||
.map((l) => l.id)
|
.map((l) => l.id)
|
||||||
|
|
||||||
const onLike = async () => {
|
const onLike = async () => {
|
||||||
if (!user) return firebaseLogin()
|
if (!user) return firebaseLogin()
|
||||||
|
|
||||||
setIsLiking(true)
|
setIsLiking(true)
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
likeContract(user, contract).catch(() => setIsLiking(false))
|
likeItem(user, item, itemType).catch(() => setIsLiking(false))
|
||||||
}, 3000)
|
}, 3000)
|
||||||
toast.custom(
|
toast.custom(
|
||||||
() => (
|
() => (
|
||||||
<TipToast
|
<TipToast
|
||||||
userName={contract.creatorUsername}
|
userName={item.creatorUsername}
|
||||||
onUndoClick={() => {
|
onUndoClick={() => {
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
}}
|
}}
|
||||||
|
@ -59,10 +62,10 @@ export function LikeMarketButton(props: {
|
||||||
userTipped={
|
userTipped={
|
||||||
!!user &&
|
!!user &&
|
||||||
(isLiking ||
|
(isLiking ||
|
||||||
userLikedContractIds?.includes(contract.id) ||
|
userLikedItemIds?.includes(item.id) ||
|
||||||
(!likes && !!contract.likedByUserIds?.includes(user.id)))
|
(!likes && !!item.likedByUserIds?.includes(user.id)))
|
||||||
}
|
}
|
||||||
disabled={contract.creatorId === user?.id}
|
disabled={item.creatorId === user?.id}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ import { useUserLikedContracts } from 'web/hooks/use-likes'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { XIcon } from '@heroicons/react/outline'
|
import { XIcon } from '@heroicons/react/outline'
|
||||||
import { unLikeContract } from 'web/lib/firebase/likes'
|
import { unLikeItem } from 'web/lib/firebase/likes'
|
||||||
import { contractPath } from 'web/lib/firebase/contracts'
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
|
|
||||||
export function UserLikesButton(props: { user: User; className?: string }) {
|
export function UserLikesButton(props: { user: User; className?: string }) {
|
||||||
|
@ -36,7 +36,7 @@ export function UserLikesButton(props: { user: User; className?: string }) {
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
<XIcon
|
<XIcon
|
||||||
className="ml-2 h-5 w-5 shrink-0 cursor-pointer"
|
className="ml-2 h-5 w-5 shrink-0 cursor-pointer"
|
||||||
onClick={() => unLikeContract(user.id, likedContract.id)}
|
onClick={() => unLikeItem(user.id, likedContract.id)}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -33,14 +33,14 @@ export function useTipTxns(on: {
|
||||||
}, [txns])
|
}, [txns])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMarketTipTxns(contractId: string): TipTxn[] {
|
export function useItemTipTxns(itemId: string): TipTxn[] {
|
||||||
const [txns, setTxns] = useState<TipTxn[]>([])
|
const [txns, setTxns] = useState<TipTxn[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return listenForTipTxns(contractId, (txns) => {
|
return listenForTipTxns(itemId, (txns) => {
|
||||||
setTxns(txns.filter((txn) => !txn.data.commentId))
|
setTxns(txns.filter((txn) => !txn.data.commentId))
|
||||||
})
|
})
|
||||||
}, [contractId])
|
}, [itemId])
|
||||||
|
|
||||||
return txns
|
return txns
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,23 @@ import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { Like, LIKE_TIP_AMOUNT } from 'common/like'
|
import { Like, LIKE_TIP_AMOUNT } from 'common/like'
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
|
import { Post } from 'common/post'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
function getLikesCollection(userId: string) {
|
function getLikesCollection(userId: string) {
|
||||||
return collection(db, 'users', userId, 'likes')
|
return collection(db, 'users', userId, 'likes')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unLikeContract = async (userId: string, contractId: string) => {
|
export const unLikeItem = async (userId: string, itemId: string) => {
|
||||||
const ref = await doc(getLikesCollection(userId), contractId)
|
const ref = await doc(getLikesCollection(userId), itemId)
|
||||||
return await deleteDoc(ref)
|
return await deleteDoc(ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const likeContract = async (user: User, contract: Contract) => {
|
export const likeItem = async (
|
||||||
|
user: User,
|
||||||
|
item: Contract | Post,
|
||||||
|
itemType: string
|
||||||
|
) => {
|
||||||
if (user.balance < LIKE_TIP_AMOUNT) {
|
if (user.balance < LIKE_TIP_AMOUNT) {
|
||||||
toast('You do not have enough M$ to like this market!')
|
toast('You do not have enough M$ to like this market!')
|
||||||
return
|
return
|
||||||
|
@ -28,27 +33,27 @@ export const likeContract = async (user: User, contract: Contract) => {
|
||||||
amount: LIKE_TIP_AMOUNT,
|
amount: LIKE_TIP_AMOUNT,
|
||||||
fromId: user.id,
|
fromId: user.id,
|
||||||
fromType: 'USER',
|
fromType: 'USER',
|
||||||
toId: contract.creatorId,
|
toId: item.creatorId,
|
||||||
toType: 'USER',
|
toType: 'USER',
|
||||||
token: 'M$',
|
token: 'M$',
|
||||||
category: 'TIP',
|
category: 'TIP',
|
||||||
data: { contractId: contract.id },
|
data: { contractId: item.id },
|
||||||
description: `${user.name} liked contract ${contract.id} for M$ ${LIKE_TIP_AMOUNT} to ${contract.creatorId} `,
|
description: `${user.name} liked ${itemType}${item.id} for M$ ${LIKE_TIP_AMOUNT} to ${item.creatorId} `,
|
||||||
})
|
})
|
||||||
console.log('result', result)
|
console.log('result', result)
|
||||||
}
|
}
|
||||||
// create new like in db under users collection
|
// create new like in db under users collection
|
||||||
const ref = doc(getLikesCollection(user.id), contract.id)
|
const ref = doc(getLikesCollection(user.id), item.id)
|
||||||
// contract slug and question are set via trigger
|
// contract slug and question are set via trigger
|
||||||
const like = removeUndefinedProps({
|
const like = removeUndefinedProps({
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
type: 'contract',
|
type: itemType,
|
||||||
tipTxnId: result.txn.id,
|
tipTxnId: result.txn.id,
|
||||||
} as Like)
|
} as Like)
|
||||||
track('like', {
|
track('like', {
|
||||||
contractId: contract.id,
|
itemId: item.id,
|
||||||
})
|
})
|
||||||
await setDoc(ref, like)
|
await setDoc(ref, like)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { useUser } from 'web/hooks/use-user'
|
||||||
import { usePost } from 'web/hooks/use-post'
|
import { usePost } from 'web/hooks/use-post'
|
||||||
import { SEO } from 'web/components/SEO'
|
import { SEO } from 'web/components/SEO'
|
||||||
import { Subtitle } from 'web/components/subtitle'
|
import { Subtitle } from 'web/components/subtitle'
|
||||||
|
import { LikeItemButton } from 'web/components/contract/like-item-button'
|
||||||
|
|
||||||
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
||||||
const { slugs } = props.params
|
const { slugs } = props.params
|
||||||
|
@ -81,7 +82,7 @@ export default function PostPage(props: {
|
||||||
<br />
|
<br />
|
||||||
<Subtitle className="!mt-2 px-2 pb-4" text={post.subtitle} />
|
<Subtitle className="!mt-2 px-2 pb-4" text={post.subtitle} />
|
||||||
</div>
|
</div>
|
||||||
<Row>
|
<Row className="items-center">
|
||||||
<Col className="flex-1 px-2">
|
<Col className="flex-1 px-2">
|
||||||
<div className={'inline-flex'}>
|
<div className={'inline-flex'}>
|
||||||
<div className="mr-1 text-gray-500">Created by</div>
|
<div className="mr-1 text-gray-500">Created by</div>
|
||||||
|
@ -92,27 +93,31 @@ export default function PostPage(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="px-2">
|
<Row className="items-center">
|
||||||
<Button
|
<LikeItemButton item={post} user={user} itemType={'post'} />
|
||||||
size="lg"
|
|
||||||
color="gray-white"
|
<Col className="px-2">
|
||||||
className={'flex'}
|
<Button
|
||||||
onClick={() => {
|
size="lg"
|
||||||
setShareOpen(true)
|
color="gray-white"
|
||||||
}}
|
className={'flex'}
|
||||||
>
|
onClick={() => {
|
||||||
<ShareIcon
|
setShareOpen(true)
|
||||||
className={clsx('mr-2 h-[24px] w-5')}
|
}}
|
||||||
aria-hidden="true"
|
>
|
||||||
/>
|
<ShareIcon
|
||||||
Share
|
className={clsx('mr-2 h-[24px] w-5')}
|
||||||
<SharePostModal
|
aria-hidden="true"
|
||||||
isOpen={isShareOpen}
|
/>
|
||||||
setOpen={setShareOpen}
|
Share
|
||||||
shareUrl={shareUrl}
|
<SharePostModal
|
||||||
/>
|
isOpen={isShareOpen}
|
||||||
</Button>
|
setOpen={setShareOpen}
|
||||||
</Col>
|
shareUrl={shareUrl}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Spacer h={2} />
|
<Spacer h={2} />
|
||||||
|
|
Loading…
Reference in New Issue
Block a user