This commit is contained in:
Pico2x 2022-08-29 15:59:52 +01:00
parent 2aff501c66
commit cc2a1962bc
5 changed files with 30 additions and 36 deletions

View File

@ -2,11 +2,11 @@ import { JSONContent } from '@tiptap/core'
export type Post = { export type Post = {
id: string id: string
name: string title: string
content: JSONContent content: JSONContent
creatorId: string // User id creatorId: string // User id
createdTime: number createdTime: number
slug: string slug: string
} }
export const MAX_POST_NAME_LENGTH = 480 export const MAX_POST_TITLE_LENGTH = 480

View File

@ -3,7 +3,7 @@ import * as admin from 'firebase-admin'
import { getUser } from './utils' import { getUser } from './utils'
import { slugify } from '../../common/util/slugify' import { slugify } from '../../common/util/slugify'
import { randomString } from '../../common/util/random' import { randomString } from '../../common/util/random'
import { Post, MAX_POST_NAME_LENGTH } from '../../common/post' import { Post, MAX_POST_TITLE_LENGTH } from '../../common/post'
import { APIError, newEndpoint, validate } from './api' import { APIError, newEndpoint, validate } from './api'
import { JSONContent } from '@tiptap/core' import { JSONContent } from '@tiptap/core'
import { z } from 'zod' import { z } from 'zod'
@ -32,21 +32,21 @@ const contentSchema: z.ZodType<JSONContent> = z.lazy(() =>
) )
const postSchema = z.object({ const postSchema = z.object({
name: z.string().min(1).max(MAX_POST_NAME_LENGTH), title: z.string().min(1).max(MAX_POST_TITLE_LENGTH),
content: contentSchema, content: contentSchema,
}) })
export const createpost = newEndpoint({}, async (req, auth) => { export const createpost = newEndpoint({}, async (req, auth) => {
const firestore = admin.firestore() const firestore = admin.firestore()
const { name, content } = validate(postSchema, req.body) const { title, content } = validate(postSchema, req.body)
const creator = await getUser(auth.uid) const creator = await getUser(auth.uid)
if (!creator) if (!creator)
throw new APIError(400, 'No user exists with the authenticated user ID.') throw new APIError(400, 'No user exists with the authenticated user ID.')
console.log('creating post owned by', creator.username, 'named', name) console.log('creating post owned by', creator.username, 'titled', title)
const slug = await getSlug(name) const slug = await getSlug(title)
const postRef = firestore.collection('posts').doc() const postRef = firestore.collection('posts').doc()
@ -54,18 +54,18 @@ export const createpost = newEndpoint({}, async (req, auth) => {
id: postRef.id, id: postRef.id,
creatorId: creator.id, creatorId: creator.id,
slug, slug,
name, title,
createdTime: Date.now(), createdTime: Date.now(),
content: content, content: content,
} }
await postRef.create(post) await postRef.create(post)
return { status: 'success', post: post } return { status: 'success', post }
}) })
export const getSlug = async (name: string) => { export const getSlug = async (title: string) => {
const proposedSlug = slugify(name) const proposedSlug = slugify(title)
const preexistingPost = await getPostFromSlug(proposedSlug) const preexistingPost = await getPostFromSlug(proposedSlug)

View File

@ -1,5 +1,6 @@
import { auth } from './users' import { auth } from './users'
import { APIError, getFunctionUrl } from 'common/api' import { APIError, getFunctionUrl } from 'common/api'
import { JSONContent } from '@tiptap/core'
export { APIError } from 'common/api' export { APIError } from 'common/api'
export async function call(url: string, method: string, params: any) { export async function call(url: string, method: string, params: any) {
@ -89,6 +90,6 @@ export function getCurrentUser(params: any) {
return call(getFunctionUrl('getcurrentuser'), 'GET', params) return call(getFunctionUrl('getcurrentuser'), 'GET', params)
} }
export function createPost(params: any) { export function createPost(params: { title: string; content: JSONContent }) {
return call(getFunctionUrl('createpost'), 'POST', params) return call(getFunctionUrl('createpost'), 'POST', params)
} }

View File

@ -7,26 +7,25 @@ import Textarea from 'react-expanding-textarea'
import { TextEditor, useTextEditor } from 'web/components/editor' import { TextEditor, useTextEditor } from 'web/components/editor'
import { createPost } from 'web/lib/firebase/api' import { createPost } from 'web/lib/firebase/api'
import clsx from 'clsx' import clsx from 'clsx'
import { useRouter } from 'next/router' import Router from 'next/router'
import { Post, MAX_POST_NAME_LENGTH } from 'common/post' import { MAX_POST_TITLE_LENGTH } from 'common/post'
import { postPath } from 'web/lib/firebase/posts' import { postPath } from 'web/lib/firebase/posts'
export default function CreatePost() { export default function CreatePost() {
const [name, setName] = useState('') const [title, setTitle] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const router = useRouter()
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
disabled: isSubmitting, disabled: isSubmitting,
}) })
const isValid = editor && name.length > 0 && editor.isEmpty === false const isValid = editor && title.length > 0 && editor.isEmpty === false
async function savePost(name: string) { async function savePost(title: string) {
if (!editor) return if (!editor) return
const newPost = { const newPost = {
name: name, title: title,
content: editor.getJSON(), content: editor.getJSON(),
} }
@ -36,7 +35,7 @@ export default function CreatePost() {
return e return e
}) })
if (result.post) { if (result.post) {
await router.push(postPath((result.post as Post).slug)) await Router.push(postPath(result.post.slug))
} }
} }
@ -49,16 +48,16 @@ export default function CreatePost() {
<div className="form-control w-full"> <div className="form-control w-full">
<label className="label"> <label className="label">
<span className="mb-1"> <span className="mb-1">
Name<span className={'text-red-700'}>*</span> Title<span className={'text-red-700'}> *</span>
</span> </span>
</label> </label>
<Textarea <Textarea
placeholder="e.g. Elon Mania Post" placeholder="e.g. Elon Mania Post"
className="input input-bordered resize-none" className="input input-bordered resize-none"
autoFocus autoFocus
maxLength={MAX_POST_NAME_LENGTH} maxLength={MAX_POST_TITLE_LENGTH}
value={name} value={title}
onChange={(e) => setName(e.target.value || '')} onChange={(e) => setTitle(e.target.value || '')}
/> />
<Spacer h={6} /> <Spacer h={6} />
<label className="label"> <label className="label">
@ -78,7 +77,7 @@ export default function CreatePost() {
disabled={isSubmitting || !isValid || upload.isLoading} disabled={isSubmitting || !isValid || upload.isLoading}
onClick={async () => { onClick={async () => {
setIsSubmitting(true) setIsSubmitting(true)
await savePost(name) await savePost(title)
setIsSubmitting(false) setIsSubmitting(false)
}} }}
> >

View File

@ -1,6 +1,5 @@
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { fromPropz, usePropz } from 'web/hooks/use-propz'
import { postPath, getPostBySlug } from 'web/lib/firebase/posts' import { postPath, getPostBySlug } from 'web/lib/firebase/posts'
import { Post } from 'common/post' import { Post } from 'common/post'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
@ -18,13 +17,11 @@ import { Col } from 'web/components/layout/col'
import { ENV_CONFIG } from 'common/envs/constants' import { ENV_CONFIG } from 'common/envs/constants'
import Custom404 from 'web/pages/404' import Custom404 from 'web/pages/404'
export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticProps(props: { params: { slugs: string[] } }) {
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const { slugs } = props.params const { slugs } = props.params
const post = await getPostBySlug(slugs[0]) const post = await getPostBySlug(slugs[0])
const creatorPromise = post ? getUser(post.creatorId) : null const creator = post ? await getUser(post.creatorId) : null
const creator = await creatorPromise
return { return {
props: { props: {
@ -41,12 +38,9 @@ export async function getStaticPaths() {
} }
export default function PostPage(props: { post: Post; creator: User }) { export default function PostPage(props: { post: Post; creator: User }) {
props = usePropz(props, getStaticPropz) ?? {
post: null,
}
const [isShareOpen, setShareOpen] = useState(false) const [isShareOpen, setShareOpen] = useState(false)
if (props.post === null) { if (props.post == null) {
return <Custom404 /> return <Custom404 />
} }
@ -56,7 +50,7 @@ export default function PostPage(props: { post: Post; creator: User }) {
<Page> <Page>
<div className="mx-auto w-full max-w-3xl "> <div className="mx-auto w-full max-w-3xl ">
<Spacer h={1} /> <Spacer h={1} />
<Title className="!mt-0" text={props.post.name} /> <Title className="!mt-0" text={props.post.title} />
<Row> <Row>
<Col className="flex-1"> <Col className="flex-1">
<div className={'inline-flex'}> <div className={'inline-flex'}>