CoastalCommitsPastes/client/app/(posts)/new/components/new.tsx

341 lines
7.7 KiB
TypeScript
Raw Normal View History

2022-11-09 21:38:05 -05:00
"use client"
import { useRouter } from "next/navigation"
import { useCallback, useState } from "react"
2022-04-09 20:48:19 -04:00
import generateUUID from "@lib/generate-uuid"
import styles from "./post.module.css"
2022-11-12 03:58:21 -05:00
import EditDocumentList from "./edit-document-list"
2022-04-09 20:48:19 -04:00
import { ChangeEvent } from "react"
import DatePicker from "react-datepicker"
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
import Description from "./description"
import { PostWithFiles } from "@lib/server/prisma"
2022-11-12 04:28:06 -05:00
import PasswordModal from "../../../components/password-modal"
2022-11-12 03:58:21 -05:00
import Title from "./title"
2022-11-12 04:28:06 -05:00
import FileDropzone from "./drag-and-drop"
import Button from "@components/button"
2022-11-16 03:49:12 -05:00
import Input from "@components/input"
import ButtonDropdown from "@components/button-dropdown"
import { useToasts } from "@components/toasts"
2022-11-09 21:38:05 -05:00
const emptyDoc = {
title: "",
content: "",
id: generateUUID()
}
export type Document = {
title: string
content: string
id: string
}
const Post = ({
initialPost: stringifiedInitialPost,
2022-04-09 20:48:19 -04:00
newPostParent
}: {
initialPost?: string
2022-04-09 20:48:19 -04:00
newPostParent?: string
}) => {
2022-12-04 04:55:20 -05:00
const parsedPost = JSON.parse(stringifiedInitialPost || "{}")
2022-11-30 01:22:17 -05:00
const initialPost = parsedPost?.id ? parsedPost : null
2022-04-09 20:48:19 -04:00
const { setToast } = useToasts()
const router = useRouter()
2022-11-09 21:38:05 -05:00
const [title, setTitle] = useState(
getTitleForPostCopy(initialPost?.title) || ""
2022-04-09 20:48:19 -04:00
)
2022-11-09 21:38:05 -05:00
const [description, setDescription] = useState(initialPost?.description || "")
const [expiresAt, setExpiresAt] = useState<Date>()
2022-04-09 20:48:19 -04:00
const defaultDocs: Document[] = initialPost
? initialPost.files?.map((doc: PostWithFiles["files"][0]) => ({
2022-11-09 21:38:05 -05:00
title: doc.title,
content: doc.content,
id: doc.id
}))
: [emptyDoc]
2022-11-09 21:38:05 -05:00
const [docs, setDocs] = useState(defaultDocs)
2022-04-09 20:48:19 -04:00
const [passwordModalVisible, setPasswordModalVisible] = useState(false)
const sendRequest = useCallback(
async (
url: string,
data: {
expiresAt: Date | null
visibility?: string
2022-04-09 20:48:19 -04:00
title?: string
files?: Document[]
2022-04-09 20:48:19 -04:00
password?: string
parentId?: string
}
) => {
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
2022-04-09 20:48:19 -04:00
},
body: JSON.stringify({
title,
description,
2022-04-09 20:48:19 -04:00
files: docs,
...data
})
})
if (res.ok) {
const json = await res.json()
router.push(`/post/${json.id}`)
2022-04-09 20:48:19 -04:00
} else {
const json = await res.json()
console.error(json)
2022-04-09 20:48:19 -04:00
setToast({
id: "error",
message: "Please fill out all fields",
2022-04-09 20:48:19 -04:00
type: "error"
})
setPasswordModalVisible(false)
setSubmitting(false)
}
},
[description, docs, router, setToast, title]
2022-04-09 20:48:19 -04:00
)
const [isSubmitting, setSubmitting] = useState(false)
const onSubmit = useCallback(
async (visibility: string, password?: string) => {
2022-04-09 20:48:19 -04:00
if (visibility === "protected" && !password) {
setPasswordModalVisible(true)
return
}
setPasswordModalVisible(false)
setSubmitting(true)
let hasErrored = false
if (!title) {
setToast({
message: "Please fill out the post title",
2022-04-09 20:48:19 -04:00
type: "error"
})
hasErrored = true
}
if (!docs.length) {
setToast({
message: "Please add at least one document",
2022-04-09 20:48:19 -04:00
type: "error"
})
hasErrored = true
}
for (const doc of docs) {
if (!doc.title) {
setToast({
message: "Please fill out all the document titles",
2022-04-09 20:48:19 -04:00
type: "error"
})
hasErrored = true
}
}
if (hasErrored) {
setSubmitting(false)
return
}
await sendRequest("/api/post", {
2022-04-09 20:48:19 -04:00
title,
files: docs,
visibility,
password,
2022-11-09 21:38:05 -05:00
expiresAt: expiresAt || null,
2022-04-09 20:48:19 -04:00
parentId: newPostParent
})
},
[docs, expiresAt, newPostParent, sendRequest, setToast, title]
)
const onClosePasswordModal = () => {
setPasswordModalVisible(false)
setSubmitting(false)
}
const submitPassword = (password: string) => onSubmit("protected", password)
2022-04-09 20:48:19 -04:00
const onChangeExpiration = (date: Date) => setExpiresAt(date)
2022-04-09 20:48:19 -04:00
const onChangeTitle = useCallback((e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setTitle(e.target.value)
}, [])
2022-04-09 20:48:19 -04:00
const onChangeDescription = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setDescription(e.target.value)
},
[]
)
const updateDocTitle = (i: number) => (title: string) => {
setDocs((docs) =>
docs.map((doc, index) => (i === index ? { ...doc, title } : doc))
)
}
2022-04-09 20:48:19 -04:00
const updateDocContent = (i: number) => (content: string) => {
setDocs((docs) =>
docs.map((doc, index) => (i === index ? { ...doc, content } : doc))
)
}
2022-04-09 20:48:19 -04:00
const removeDoc = (i: number) => () => {
setDocs((docs) => docs.filter((_, index) => i !== index))
}
2022-04-09 20:48:19 -04:00
const uploadDocs = (files: Document[]) => {
// if no title is set and the only document is empty,
const isFirstDocEmpty =
docs.length <= 1 && (docs.length ? docs[0].title === "" : true)
const shouldSetTitle = !title && isFirstDocEmpty
if (shouldSetTitle) {
if (files.length === 1) {
setTitle(files[0].title)
} else if (files.length > 1) {
setTitle("Uploaded files")
2022-04-09 20:48:19 -04:00
}
}
2022-04-09 20:48:19 -04:00
if (isFirstDocEmpty) setDocs(files)
else setDocs((docs) => [...docs, ...files])
}
2022-04-09 20:48:19 -04:00
2022-11-09 21:38:05 -05:00
const onPaste = (e: ClipboardEvent) => {
const pastedText = e.clipboardData?.getData("text")
2022-04-09 20:48:19 -04:00
2022-11-09 21:38:05 -05:00
if (pastedText) {
if (!title) {
setTitle("Pasted text")
2022-04-09 20:48:19 -04:00
}
2022-11-09 21:38:05 -05:00
}
}
2022-04-09 20:48:19 -04:00
const CustomTimeInput = ({
date,
value,
onChange
}: {
date: Date
value: string
onChange: (date: string) => void
}) => (
<input
type="time"
value={value}
onChange={(e) => {
if (!isNaN(date.getTime())) {
onChange(e.target.value || date.toISOString().slice(11, 16))
}
}}
style={{
backgroundColor: "var(--bg)",
border: "1px solid var(--light-gray)",
borderRadius: "var(--radius)"
}}
required
/>
)
return (
<div style={{ paddingBottom: 200 }}>
2022-04-09 20:48:19 -04:00
<Title title={title} onChange={onChangeTitle} />
<Description description={description} onChange={onChangeDescription} />
2022-04-09 20:48:19 -04:00
<FileDropzone setDocs={uploadDocs} />
<EditDocumentList
onPaste={onPaste}
docs={docs}
updateDocTitle={updateDocTitle}
updateDocContent={updateDocContent}
removeDoc={removeDoc}
/>
<div className={styles.buttons}>
<Button
className={styles.button}
onClick={() => {
setDocs([
...docs,
{
title: "",
content: "",
id: generateUUID()
}
])
}}
type="default"
style={{
flex: 1
}}
2022-04-09 20:48:19 -04:00
>
Add a File
</Button>
<div className={styles.rightButtons}>
<DatePicker
onChange={onChangeExpiration}
customInput={
2022-11-16 03:49:12 -05:00
<Input label="Expires at" width="100%" height="40px" />
}
placeholderText="Won't expire"
selected={expiresAt}
showTimeInput={true}
// @ts-ignore
customTimeInput={<CustomTimeInput />}
timeInputLabel="Time:"
dateFormat="MM/dd/yyyy h:mm aa"
className={styles.datePicker}
clearButtonTitle={"Clear"}
// TODO: investigate why this causes margin shift if true
enableTabLoop={false}
minDate={new Date()}
/>
<ButtonDropdown iconHeight={40}>
<Button
height={40}
width={251}
onClick={() => onSubmit("unlisted")}
loading={isSubmitting}
>
Create Unlisted
</Button>
<Button height={40} width={300} onClick={() => onSubmit("private")}>
2022-04-09 20:48:19 -04:00
Create Private
</Button>
<Button height={40} width={300} onClick={() => onSubmit("public")}>
2022-04-09 20:48:19 -04:00
Create Public
</Button>
<Button
height={40}
width={300}
onClick={() => onSubmit("protected")}
>
2022-04-09 20:48:19 -04:00
Create with Password
</Button>
2022-04-09 20:48:19 -04:00
</ButtonDropdown>
</div>
</div>
<PasswordModal
creating={true}
isOpen={passwordModalVisible}
onClose={onClosePasswordModal}
onSubmit={submitPassword}
/>
</div>
)
2022-03-06 19:46:59 -05:00
}
2022-03-21 06:28:06 -04:00
export default Post