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:
FRC 2022-08-30 13:39:10 +01:00 committed by GitHub
parent 7debc4925e
commit e1f19c52ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 189 additions and 3 deletions

View File

@ -10,6 +10,7 @@ export type Group = {
anyoneCanJoin: boolean
contractIds: string[]
aboutPostId?: string
chatDisabled?: boolean
mostRecentChatActivityTime?: number
mostRecentContractAddedTime?: number

View File

@ -160,7 +160,7 @@ service cloud.firestore {
allow update: if request.auth.uid == resource.data.creatorId
&& request.resource.data.diff(resource.data)
.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)
&& request.resource.data.diff(resource.data)
.affectedKeys()

View 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
View 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
}

View File

@ -1,5 +1,6 @@
import {
deleteDoc,
deleteField,
doc,
getDocs,
query,
@ -36,6 +37,10 @@ export function updateGroup(group: Group, updates: Partial<Group>) {
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) {
return deleteDoc(doc(groups, group.id))
}

View File

@ -7,7 +7,7 @@ import {
where,
} from 'firebase/firestore'
import { Post } from 'common/post'
import { coll, getValue } from './utils'
import { coll, getValue, listenForValue } from './utils'
export const posts = coll<Post>('posts')
@ -32,3 +32,10 @@ export async function getPostBySlug(slug: string) {
const docs = (await getDocs(q)).docs
return docs.length === 0 ? null : docs[0].data()
}
export function listenForPost(
postId: string,
setPost: (post: Post | null) => void
) {
return listenForValue(doc(posts, postId), setPost)
}

View File

@ -45,6 +45,11 @@ import { Button } from 'web/components/button'
import { listAllCommentsOnGroup } from 'web/lib/firebase/comments'
import { GroupComment } from 'common/comment'
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 async function getStaticPropz(props: { params: { slugs: string[] } }) {
@ -57,6 +62,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const contracts =
(group && (await listContractsByGroupSlug(group.slug))) ?? []
const aboutPost =
group && group.aboutPostId != null && (await getPost(group.aboutPostId))
const bets = await Promise.all(
contracts.map((contract: Contract) => listAllBets(contract.id))
)
@ -83,6 +90,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
creatorScores,
topCreators,
messages,
aboutPost,
},
revalidate: 60, // regenerate after a minute
@ -121,6 +129,7 @@ export default function GroupPage(props: {
creatorScores: { [userId: string]: number }
topCreators: User[]
messages: GroupComment[]
aboutPost: Post
}) {
props = usePropz(props, getStaticPropz) ?? {
group: null,
@ -146,6 +155,7 @@ export default function GroupPage(props: {
const page = slugs?.[1] as typeof groupSubpages[number]
const group = useGroup(props.group?.id) ?? props.group
const aboutPost = usePost(props.aboutPost?.id) ?? props.aboutPost
const user = useUser()
@ -176,6 +186,16 @@ export default function GroupPage(props: {
const aboutTab = (
<Col>
{group.aboutPostId != null || isCreator ? (
<GroupAboutPost
group={group}
isCreator={!!isCreator}
post={aboutPost}
/>
) : (
<div></div>
)}
<Spacer h={3} />
<GroupOverview
group={group}
creator={creator}
@ -292,7 +312,6 @@ function GroupOverview(props: {
error: "Couldn't update group",
})
}
const postFix = user ? '?referrer=' + user.username : ''
const shareUrl = `https://${ENV_CONFIG.domain}${groupPath(
group.slug