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,6 +93,9 @@ export default function PostPage(props: { | ||||||
|               /> |               /> | ||||||
|             </div> |             </div> | ||||||
|           </Col> |           </Col> | ||||||
|  |           <Row className="items-center"> | ||||||
|  |             <LikeItemButton item={post} user={user} itemType={'post'} /> | ||||||
|  | 
 | ||||||
|             <Col className="px-2"> |             <Col className="px-2"> | ||||||
|               <Button |               <Button | ||||||
|                 size="lg" |                 size="lg" | ||||||
|  | @ -114,6 +118,7 @@ export default function PostPage(props: { | ||||||
|               </Button> |               </Button> | ||||||
|             </Col> |             </Col> | ||||||
|           </Row> |           </Row> | ||||||
|  |         </Row> | ||||||
| 
 | 
 | ||||||
|         <Spacer h={2} /> |         <Spacer h={2} /> | ||||||
|         <div className="rounded-lg bg-white px-6 py-4 sm:py-0"> |         <div className="rounded-lg bg-white px-6 py-4 sm:py-0"> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user