import { Comment } from "@prisma/client"; import axios from "axios"; import clsx from "clsx"; import { formatRelative } from "date-fns"; import { AnimatePresence, motion } from "framer-motion"; import { usePlausible } from "next-plausible"; import * as React from "react"; import { Controller, useForm } from "react-hook-form"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { createComment, CreateCommentPayload, } from "../../api-client/create-comment"; import { requiredString } from "../../utils/form-validation"; import Button from "../button"; import CompactButton from "../compact-button"; import Dropdown, { DropdownItem } from "../dropdown"; import DotsHorizontal from "../icons/dots-horizontal.svg"; import Trash from "../icons/trash.svg"; import NameInput from "../name-input"; import TruncatedLinkify from "../poll/truncated-linkify"; import UserAvater from "../poll/user-avatar"; import { useUserName } from "../user-name-context"; export interface DiscussionProps { pollId: string; canDelete?: boolean; } interface CommentForm { authorName: string; content: string; } const Discussion: React.VoidFunctionComponent = ({ pollId, canDelete, }) => { const getCommentsQueryKey = ["poll", pollId, "comments"]; const [userName, setUserName] = useUserName(); const queryClient = useQueryClient(); const { data: comments } = useQuery( getCommentsQueryKey, async () => { const res = await axios.get<{ comments: Array & { createdAt: string }>; }>(`/api/poll/${pollId}/comments`); return res.data.comments; }, { refetchInterval: 10000, // refetch every 10 seconds }, ); const plausible = usePlausible(); const { mutate: createCommentMutation } = useMutation( (payload: CreateCommentPayload) => { // post comment return createComment(payload); }, { onSuccess: (newComment) => { queryClient.setQueryData(getCommentsQueryKey, (comments) => { if (Array.isArray(comments)) { return [...comments, newComment]; } return [newComment]; }); plausible("Created comment"); }, }, ); const { mutate: deleteCommentMutation } = useMutation( async (payload: { pollId: string; commentId: string }) => { await axios.delete(`/api/poll/${pollId}/comments/${payload.commentId}`); }, { onMutate: () => { plausible("Deleted comment"); }, onSuccess: () => { queryClient.invalidateQueries(getCommentsQueryKey); }, }, ); const { register, setValue, control, handleSubmit, formState } = useForm({ defaultValues: { authorName: userName, content: "", }, }); React.useEffect(() => { setValue("authorName", userName); }, [setValue, userName]); const handleDelete = React.useCallback( (commentId: string) => { deleteCommentMutation({ pollId, commentId }); }, [deleteCommentMutation, pollId], ); if (!comments) { return null; } return (
Comments
0, })} > {comments.map((comment) => { return (
{comment.authorName} {formatRelative( new Date(comment.createdAt), Date.now(), )}
{canDelete ? ( } > { handleDelete(comment.id); }} /> ) : null}
{comment.content}
); })}
{ return new Promise((resolve, reject) => { createCommentMutation( { ...data, pollId, }, { onSuccess: () => { setUserName(data.authorName); setValue("content", ""); resolve(data); }, onError: reject, }, ); }); })} >