Post in a group about page. (#803)
* Dashboards in Group about page * Rename group dashboard to 'About Post' * Fixed James nits
This commit is contained in:
parent
7debc4925e
commit
e1f19c52ab
|
@ -10,6 +10,7 @@ export type Group = {
|
||||||
anyoneCanJoin: boolean
|
anyoneCanJoin: boolean
|
||||||
contractIds: string[]
|
contractIds: string[]
|
||||||
|
|
||||||
|
aboutPostId?: string
|
||||||
chatDisabled?: boolean
|
chatDisabled?: boolean
|
||||||
mostRecentChatActivityTime?: number
|
mostRecentChatActivityTime?: number
|
||||||
mostRecentContractAddedTime?: number
|
mostRecentContractAddedTime?: number
|
||||||
|
|
|
@ -160,7 +160,7 @@ service cloud.firestore {
|
||||||
allow update: if request.auth.uid == resource.data.creatorId
|
allow update: if request.auth.uid == resource.data.creatorId
|
||||||
&& request.resource.data.diff(resource.data)
|
&& request.resource.data.diff(resource.data)
|
||||||
.affectedKeys()
|
.affectedKeys()
|
||||||
.hasOnly(['name', 'about', 'contractIds', 'memberIds', 'anyoneCanJoin' ]);
|
.hasOnly(['name', 'about', 'contractIds', 'memberIds', 'anyoneCanJoin', 'aboutPostId' ]);
|
||||||
allow update: if (request.auth.uid in resource.data.memberIds || resource.data.anyoneCanJoin)
|
allow update: if (request.auth.uid in resource.data.memberIds || resource.data.anyoneCanJoin)
|
||||||
&& request.resource.data.diff(resource.data)
|
&& request.resource.data.diff(resource.data)
|
||||||
.affectedKeys()
|
.affectedKeys()
|
||||||
|
|
141
web/components/groups/group-about-post.tsx
Normal file
141
web/components/groups/group-about-post.tsx
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import { useAdmin } from 'web/hooks/use-admin'
|
||||||
|
import { Row } from '../layout/row'
|
||||||
|
import { Content } from '../editor'
|
||||||
|
import { TextEditor, useTextEditor } from 'web/components/editor'
|
||||||
|
import { Button } from '../button'
|
||||||
|
import { Spacer } from '../layout/spacer'
|
||||||
|
import { Group } from 'common/group'
|
||||||
|
import { deleteFieldFromGroup, updateGroup } from 'web/lib/firebase/groups'
|
||||||
|
import PencilIcon from '@heroicons/react/solid/PencilIcon'
|
||||||
|
import { DocumentRemoveIcon } from '@heroicons/react/solid'
|
||||||
|
import { createPost } from 'web/lib/firebase/api'
|
||||||
|
import { Post } from 'common/post'
|
||||||
|
import { deletePost, updatePost } from 'web/lib/firebase/posts'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { usePost } from 'web/hooks/use-post'
|
||||||
|
|
||||||
|
export function GroupAboutPost(props: {
|
||||||
|
group: Group
|
||||||
|
isCreator: boolean
|
||||||
|
post: Post
|
||||||
|
}) {
|
||||||
|
const { group, isCreator } = props
|
||||||
|
const post = usePost(group.aboutPostId) ?? props.post
|
||||||
|
const isAdmin = useAdmin()
|
||||||
|
|
||||||
|
if (group.aboutPostId == null && !isCreator) {
|
||||||
|
return <p className="text-center">No post has been created </p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md bg-white p-4">
|
||||||
|
{isCreator || isAdmin ? (
|
||||||
|
<RichEditGroupAboutPost group={group} post={post} />
|
||||||
|
) : (
|
||||||
|
<Content content={post.content} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RichEditGroupAboutPost(props: { group: Group; post: Post }) {
|
||||||
|
const { group, post } = props
|
||||||
|
const [editing, setEditing] = useState(false)
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
|
const { editor, upload } = useTextEditor({
|
||||||
|
defaultValue: post.content,
|
||||||
|
disabled: isSubmitting,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function savePost() {
|
||||||
|
if (!editor) return
|
||||||
|
const newPost = {
|
||||||
|
title: group.name,
|
||||||
|
content: editor.getJSON(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.aboutPostId == null) {
|
||||||
|
const result = await createPost(newPost).catch((e) => {
|
||||||
|
console.error(e)
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
await updateGroup(group, {
|
||||||
|
aboutPostId: result.post.id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await updatePost(post, {
|
||||||
|
content: newPost.content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteGroupAboutPost() {
|
||||||
|
await deletePost(post)
|
||||||
|
await deleteFieldFromGroup(group, 'aboutPostId')
|
||||||
|
}
|
||||||
|
|
||||||
|
return editing ? (
|
||||||
|
<>
|
||||||
|
<TextEditor editor={editor} upload={upload} />
|
||||||
|
<Spacer h={2} />
|
||||||
|
<Row className="gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setIsSubmitting(true)
|
||||||
|
await savePost()
|
||||||
|
setEditing(false)
|
||||||
|
setIsSubmitting(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button color="gray" onClick={() => setEditing(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{group.aboutPostId == null ? (
|
||||||
|
<div className="text-center text-gray-500">
|
||||||
|
<p className="text-sm">
|
||||||
|
No post has been added yet.
|
||||||
|
<Spacer h={2} />
|
||||||
|
<Button onClick={() => setEditing(true)}>Add a post</Button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute top-0 right-0 z-10 space-x-2">
|
||||||
|
<Button
|
||||||
|
color="gray"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
setEditing(true)
|
||||||
|
editor?.commands.focus('end')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilIcon className="inline h-4 w-4" />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="gray"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => {
|
||||||
|
deleteGroupAboutPost()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DocumentRemoveIcon className="inline h-4 w-4" />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Content content={post.content} />
|
||||||
|
<Spacer h={2} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
13
web/hooks/use-post.ts
Normal file
13
web/hooks/use-post.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Post } from 'common/post'
|
||||||
|
import { listenForPost } from 'web/lib/firebase/posts'
|
||||||
|
|
||||||
|
export const usePost = (postId: string | undefined) => {
|
||||||
|
const [post, setPost] = useState<Post | null | undefined>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (postId) return listenForPost(postId, setPost)
|
||||||
|
}, [postId])
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
deleteDoc,
|
deleteDoc,
|
||||||
|
deleteField,
|
||||||
doc,
|
doc,
|
||||||
getDocs,
|
getDocs,
|
||||||
query,
|
query,
|
||||||
|
@ -36,6 +37,10 @@ export function updateGroup(group: Group, updates: Partial<Group>) {
|
||||||
return updateDoc(doc(groups, group.id), updates)
|
return updateDoc(doc(groups, group.id), updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteFieldFromGroup(group: Group, field: string) {
|
||||||
|
return updateDoc(doc(groups, group.id), { [field]: deleteField() })
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteGroup(group: Group) {
|
export function deleteGroup(group: Group) {
|
||||||
return deleteDoc(doc(groups, group.id))
|
return deleteDoc(doc(groups, group.id))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
where,
|
where,
|
||||||
} from 'firebase/firestore'
|
} from 'firebase/firestore'
|
||||||
import { Post } from 'common/post'
|
import { Post } from 'common/post'
|
||||||
import { coll, getValue } from './utils'
|
import { coll, getValue, listenForValue } from './utils'
|
||||||
|
|
||||||
export const posts = coll<Post>('posts')
|
export const posts = coll<Post>('posts')
|
||||||
|
|
||||||
|
@ -32,3 +32,10 @@ export async function getPostBySlug(slug: string) {
|
||||||
const docs = (await getDocs(q)).docs
|
const docs = (await getDocs(q)).docs
|
||||||
return docs.length === 0 ? null : docs[0].data()
|
return docs.length === 0 ? null : docs[0].data()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listenForPost(
|
||||||
|
postId: string,
|
||||||
|
setPost: (post: Post | null) => void
|
||||||
|
) {
|
||||||
|
return listenForValue(doc(posts, postId), setPost)
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,11 @@ import { Button } from 'web/components/button'
|
||||||
import { listAllCommentsOnGroup } from 'web/lib/firebase/comments'
|
import { listAllCommentsOnGroup } from 'web/lib/firebase/comments'
|
||||||
import { GroupComment } from 'common/comment'
|
import { GroupComment } from 'common/comment'
|
||||||
import { REFERRAL_AMOUNT } from 'common/economy'
|
import { REFERRAL_AMOUNT } from 'common/economy'
|
||||||
|
import { GroupAboutPost } from 'web/components/groups/group-about-post'
|
||||||
|
import { getPost } from 'web/lib/firebase/posts'
|
||||||
|
import { Post } from 'common/post'
|
||||||
|
import { Spacer } from 'web/components/layout/spacer'
|
||||||
|
import { usePost } from 'web/hooks/use-post'
|
||||||
|
|
||||||
export const getStaticProps = fromPropz(getStaticPropz)
|
export const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
|
@ -57,6 +62,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
const contracts =
|
const contracts =
|
||||||
(group && (await listContractsByGroupSlug(group.slug))) ?? []
|
(group && (await listContractsByGroupSlug(group.slug))) ?? []
|
||||||
|
|
||||||
|
const aboutPost =
|
||||||
|
group && group.aboutPostId != null && (await getPost(group.aboutPostId))
|
||||||
const bets = await Promise.all(
|
const bets = await Promise.all(
|
||||||
contracts.map((contract: Contract) => listAllBets(contract.id))
|
contracts.map((contract: Contract) => listAllBets(contract.id))
|
||||||
)
|
)
|
||||||
|
@ -83,6 +90,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||||
creatorScores,
|
creatorScores,
|
||||||
topCreators,
|
topCreators,
|
||||||
messages,
|
messages,
|
||||||
|
aboutPost,
|
||||||
},
|
},
|
||||||
|
|
||||||
revalidate: 60, // regenerate after a minute
|
revalidate: 60, // regenerate after a minute
|
||||||
|
@ -121,6 +129,7 @@ export default function GroupPage(props: {
|
||||||
creatorScores: { [userId: string]: number }
|
creatorScores: { [userId: string]: number }
|
||||||
topCreators: User[]
|
topCreators: User[]
|
||||||
messages: GroupComment[]
|
messages: GroupComment[]
|
||||||
|
aboutPost: Post
|
||||||
}) {
|
}) {
|
||||||
props = usePropz(props, getStaticPropz) ?? {
|
props = usePropz(props, getStaticPropz) ?? {
|
||||||
group: null,
|
group: null,
|
||||||
|
@ -146,6 +155,7 @@ export default function GroupPage(props: {
|
||||||
const page = slugs?.[1] as typeof groupSubpages[number]
|
const page = slugs?.[1] as typeof groupSubpages[number]
|
||||||
|
|
||||||
const group = useGroup(props.group?.id) ?? props.group
|
const group = useGroup(props.group?.id) ?? props.group
|
||||||
|
const aboutPost = usePost(props.aboutPost?.id) ?? props.aboutPost
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
|
@ -176,6 +186,16 @@ export default function GroupPage(props: {
|
||||||
|
|
||||||
const aboutTab = (
|
const aboutTab = (
|
||||||
<Col>
|
<Col>
|
||||||
|
{group.aboutPostId != null || isCreator ? (
|
||||||
|
<GroupAboutPost
|
||||||
|
group={group}
|
||||||
|
isCreator={!!isCreator}
|
||||||
|
post={aboutPost}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
)}
|
||||||
|
<Spacer h={3} />
|
||||||
<GroupOverview
|
<GroupOverview
|
||||||
group={group}
|
group={group}
|
||||||
creator={creator}
|
creator={creator}
|
||||||
|
@ -292,7 +312,6 @@ function GroupOverview(props: {
|
||||||
error: "Couldn't update group",
|
error: "Couldn't update group",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const postFix = user ? '?referrer=' + user.username : ''
|
const postFix = user ? '?referrer=' + user.username : ''
|
||||||
const shareUrl = `https://${ENV_CONFIG.domain}${groupPath(
|
const shareUrl = `https://${ENV_CONFIG.domain}${groupPath(
|
||||||
group.slug
|
group.slug
|
||||||
|
|
Loading…
Reference in New Issue
Block a user