Follow and unfollow folds
This commit is contained in:
parent
c3f49c44a0
commit
76841e53b1
|
@ -47,5 +47,10 @@ service cloud.firestore {
|
||||||
allow read;
|
allow read;
|
||||||
allow update: if request.auth.uid == resource.data.curatorId;
|
allow update: if request.auth.uid == resource.data.curatorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /folds/{foldId}/followers/{userId} {
|
||||||
|
allow read;
|
||||||
|
allow write: if request.auth.uid == userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
50
web/components/follow-fold-button.tsx
Normal file
50
web/components/follow-fold-button.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { Fold } from '../../common/fold'
|
||||||
|
import { useFollowingFold } from '../hooks/use-fold'
|
||||||
|
import { useUser } from '../hooks/use-user'
|
||||||
|
import { followFold, unfollowFold } from '../lib/firebase/folds'
|
||||||
|
|
||||||
|
export function FollowFoldButton(props: { fold: Fold; className?: string }) {
|
||||||
|
const { fold, className } = props
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
const following = useFollowingFold(fold, user)
|
||||||
|
|
||||||
|
const onFollow = () => {
|
||||||
|
if (user) followFold(fold, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUnfollow = () => {
|
||||||
|
if (user) unfollowFold(fold, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || following === undefined)
|
||||||
|
return (
|
||||||
|
<button className={clsx('btn btn-sm invisible', className)}>
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (following) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
'btn btn-primary btn-sm hover:bg-red-500 hover:border-red-500',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={onUnfollow}
|
||||||
|
>
|
||||||
|
Following
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx('btn btn-secondary bg-indigo-500 btn-sm', className)}
|
||||||
|
onClick={onFollow}
|
||||||
|
>
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,11 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Fold } from '../../common/fold'
|
import { Fold } from '../../common/fold'
|
||||||
import { listenForFold, listenForFolds } from '../lib/firebase/folds'
|
import { User } from '../../common/user'
|
||||||
|
import {
|
||||||
|
listenForFold,
|
||||||
|
listenForFolds,
|
||||||
|
listenForFollow,
|
||||||
|
} from '../lib/firebase/folds'
|
||||||
|
|
||||||
export const useFold = (foldId: string) => {
|
export const useFold = (foldId: string) => {
|
||||||
const [fold, setFold] = useState<Fold | null | undefined>()
|
const [fold, setFold] = useState<Fold | null | undefined>()
|
||||||
|
@ -21,3 +26,13 @@ export const useFolds = () => {
|
||||||
|
|
||||||
return folds
|
return folds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useFollowingFold = (fold: Fold, user: User | null | undefined) => {
|
||||||
|
const [following, setFollowing] = useState<boolean | undefined>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) return listenForFollow(fold, user, setFollowing)
|
||||||
|
}, [fold, user])
|
||||||
|
|
||||||
|
return following
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { collection, doc, query, updateDoc, where } from 'firebase/firestore'
|
import {
|
||||||
|
collection,
|
||||||
|
deleteDoc,
|
||||||
|
doc,
|
||||||
|
query,
|
||||||
|
setDoc,
|
||||||
|
updateDoc,
|
||||||
|
where,
|
||||||
|
} from 'firebase/firestore'
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from '../../../common/fold'
|
||||||
import { Contract, contractCollection } from './contracts'
|
import { Contract, contractCollection } from './contracts'
|
||||||
import { db } from './init'
|
import { db } from './init'
|
||||||
|
import { User } from './users'
|
||||||
import { getValues, listenForValue, listenForValues } from './utils'
|
import { getValues, listenForValue, listenForValues } from './utils'
|
||||||
|
|
||||||
const foldCollection = collection(db, 'folds')
|
const foldCollection = collection(db, 'folds')
|
||||||
|
@ -90,3 +99,24 @@ export function listenForFold(
|
||||||
) {
|
) {
|
||||||
return listenForValue(doc(foldCollection, foldId), setFold)
|
return listenForValue(doc(foldCollection, foldId), setFold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function followFold(fold: Fold, user: User) {
|
||||||
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
||||||
|
return setDoc(followDoc, { userId: user.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unfollowFold(fold: Fold, user: User) {
|
||||||
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
||||||
|
return deleteDoc(followDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listenForFollow(
|
||||||
|
fold: Fold,
|
||||||
|
user: User,
|
||||||
|
setFollow: (following: boolean) => void
|
||||||
|
) {
|
||||||
|
const followDoc = doc(foldCollection, fold.id, 'followers', user.id)
|
||||||
|
return listenForValue(followDoc, (value) => {
|
||||||
|
setFollow(!!value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { Leaderboard } from '../../../components/leaderboard'
|
||||||
import { formatMoney } from '../../../lib/util/format'
|
import { formatMoney } from '../../../lib/util/format'
|
||||||
import { EditFoldButton } from '../../../components/edit-fold-button'
|
import { EditFoldButton } from '../../../components/edit-fold-button'
|
||||||
import Custom404 from '../../404'
|
import Custom404 from '../../404'
|
||||||
|
import { FollowFoldButton } from '../../../components/follow-fold-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
|
||||||
|
@ -155,7 +156,11 @@ export default function FoldPage(props: {
|
||||||
<Page wide>
|
<Page wide>
|
||||||
<Row className="justify-between mb-6 px-1">
|
<Row className="justify-between mb-6 px-1">
|
||||||
<Title className="!m-0" text={fold.name} />
|
<Title className="!m-0" text={fold.name} />
|
||||||
{isCurator && <EditFoldButton className="ml-1" fold={fold} />}
|
{isCurator ? (
|
||||||
|
<EditFoldButton className="ml-1" fold={fold} />
|
||||||
|
) : (
|
||||||
|
<FollowFoldButton className="ml-1" fold={fold} />
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Col className="md:hidden text-gray-500 gap-2 mb-6 px-1">
|
<Col className="md:hidden text-gray-500 gap-2 mb-6 px-1">
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Fold } from '../../common/fold'
|
import { Fold } from '../../common/fold'
|
||||||
import { CreateFoldButton } from '../components/create-fold-button'
|
import { CreateFoldButton } from '../components/create-fold-button'
|
||||||
|
import { FollowFoldButton } from '../components/follow-fold-button'
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { Row } from '../components/layout/row'
|
import { Row } from '../components/layout/row'
|
||||||
import { Page } from '../components/page'
|
import { Page } from '../components/page'
|
||||||
|
@ -82,9 +83,7 @@ export default function Folds(props: {
|
||||||
</Link>
|
</Link>
|
||||||
<Row className="justify-between items-center gap-2">
|
<Row className="justify-between items-center gap-2">
|
||||||
<SiteLink href={foldPath(fold)}>{fold.name}</SiteLink>
|
<SiteLink href={foldPath(fold)}>{fold.name}</SiteLink>
|
||||||
<button className="btn btn-secondary btn-sm z-10 mb-1">
|
<FollowFoldButton className="z-10 mb-1" fold={fold} />
|
||||||
Follow
|
|
||||||
</button>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row className="items-center gap-2 text-gray-500 text-sm">
|
<Row className="items-center gap-2 text-gray-500 text-sm">
|
||||||
<div>12 followers</div>
|
<div>12 followers</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user