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