feat: support for posting anonymous comment

This commit is contained in:
DIYgod 2023-06-14 00:01:45 +01:00
parent a5690eb03b
commit 99a1cadc09
No known key found for this signature in database
5 changed files with 192 additions and 45 deletions

View File

@ -2,36 +2,39 @@ import { createContract } from "crossbell"
import { NextResponse } from "next/server"
import { ANONYMOUS_ACCOUNT_PRIVATEKEY } from "~/lib/env.server"
import { NextServerResponse, getQuery } from "~/lib/server-helper"
import { NextServerResponse } from "~/lib/server-helper"
const contract = createContract(ANONYMOUS_ACCOUNT_PRIVATEKEY)
// /api/anonymous/comment?target=1-1&content=hello&name=Me
export async function GET(req: Request): Promise<Response> {
const { target, content, name } = getQuery(req)
// /api/anonymous/comment
export async function POST(req: Request): Promise<Response> {
const { targetCharacterId, targetNoteId, content, name, email } =
await req.json()
if (!target || !content) {
return NextResponse.json({ error: "Missing target or content" })
if (!targetCharacterId || !targetNoteId || !content || !name || !email) {
return NextResponse.json({ error: "Missing required fields" })
}
const res = new NextServerResponse()
const { data } = await contract.note.postForNote({
targetCharacterId: target.split("-")[0],
targetNoteId: target.split("-")[1],
targetCharacterId,
targetNoteId,
characterId: 56592,
metadataOrUri: {
tags: ["comment"],
sources: ["xlog"],
content: content,
},
...(name && {
content,
attributes: [
{
trait_type: "xlog_display_name",
trait_type: "xlog_sender_name",
value: name,
},
{
trait_type: "xlog_sender_email",
value: email,
},
],
}),
},
})
return res.status(200).json({

View File

@ -1,5 +1,5 @@
import type { CharacterEntity, NoteEntity } from "crossbell"
import { useCallback, useEffect, useRef } from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import { EditorView } from "@codemirror/view"
@ -11,7 +11,11 @@ import { Button } from "~/components/ui/Button"
import { editorUpload } from "~/editor/Multimedia"
import { useUploadFile } from "~/hooks/useUploadFile"
import { useTranslation } from "~/lib/i18n/client"
import { useCommentPage, useUpdateComment } from "~/queries/page"
import {
useAnonymousComment,
useCommentPage,
useUpdateComment,
} from "~/queries/page"
import { CodeMirrorEditor } from "../ui/CodeMirror"
import { Input } from "../ui/Input"
@ -38,6 +42,8 @@ export const CommentInput = ({
const commentPage = useCommentPage()
const updateComment = useUpdateComment()
const { t } = useTranslation("site")
const anonymousComment = useAnonymousComment()
const [anonymous, setAnonymous] = useState(false)
const form = useForm({
defaultValues: {
@ -45,6 +51,16 @@ export const CommentInput = ({
},
})
const anonymousForm = useForm({
defaultValues: {
name: "",
email: "",
} as {
name: string
email: string
},
})
const inputContent = form.watch("content").trim()
const handleSubmit = form.handleSubmit(async (values) => {
@ -61,24 +77,52 @@ export const CommentInput = ({
})
}
} else {
commentPage.mutate({
characterId,
noteId,
content: values.content,
externalUrl: window.location.href,
originalCharacterId,
originalNoteId,
})
if (anonymous) {
if (
values.content &&
anonymousForm.getValues("name") &&
anonymousForm.getValues("email")
) {
anonymousComment.mutate({
targetCharacterId: characterId,
targetNoteId: noteId,
content: values.content,
name: anonymousForm.getValues("name"),
email: anonymousForm.getValues("email"),
originalCharacterId,
originalNoteId,
})
}
} else {
commentPage.mutate({
characterId,
noteId,
content: values.content,
externalUrl: window.location.href,
originalCharacterId,
originalNoteId,
})
}
}
}
})
useEffect(() => {
if (commentPage.isSuccess || updateComment.isSuccess) {
if (
commentPage.isSuccess ||
updateComment.isSuccess ||
anonymousComment.isSuccess
) {
form.reset()
onSubmitted?.()
}
}, [commentPage.isSuccess, updateComment.isSuccess, form, onSubmitted])
}, [
commentPage.isSuccess,
updateComment.isSuccess,
anonymousComment.isSuccess,
form,
onSubmitted,
])
const cmViewRef = useRef<EditorView>()
const onCreateEditor = useCallback((view: EditorView) => {
@ -112,6 +156,33 @@ export const CommentInput = ({
),
[account?.character, t, form],
)
let submitText = "Connect"
if (anonymous) {
submitText = "Submit"
} else if (account) {
if (!account.character) {
submitText = "Create Character"
} else if (comment) {
submitText = "Confirm Modification"
} else {
submitText = "Submit"
}
}
let submitDisabled = false
const name = anonymousForm.watch("name").trim()
const email = anonymousForm.watch("email").trim()
if (account?.character) {
if (!inputContent || inputContent === comment?.metadata?.content?.content) {
submitDisabled = true
}
} else if (anonymous) {
if (!name || !email || !inputContent) {
submitDisabled = true
}
}
return (
<div className="xlog-comment-input flex">
<Avatar
@ -129,7 +200,7 @@ export const CommentInput = ({
form.setValue("content", val)
}}
handleDropFile={handleDropFile}
className="mb-2 p-3 h-[74px] border focus-within:border-accent border-[var(--border-color)] rounded-lg outline-2 outline-transparent cursor-text"
className="mb-2 p-3 h-[74px] border focus-within:border-accent border-[var(--border-color)] rounded-lg outline-2 outline-transparent cursor-text transition-colors"
placeholder={t("Write a comment on the blockchain") || ""}
maxLength={600}
onCreateEditor={onCreateEditor}
@ -170,25 +241,49 @@ export const CommentInput = ({
</>
)}
</Popover>
<Button
type="submit"
isLoading={commentPage.isLoading || updateComment.isLoading}
isDisabled={
!!account?.character &&
(!inputContent ||
inputContent === comment?.metadata?.content?.content)
}
>
{t(
account
? !account.character
? "Create Character"
: comment
? "Confirm Modification"
: "Submit"
: "Connect",
<div className="flex items-center relative">
{!account && (
<div>
<div className="mr-6 flex items-center">
<input
type="checkbox"
id="anonymous"
name="anonymous"
checked={anonymous}
onChange={(e) => setAnonymous(e.target.checked)}
/>
<label className="text-gray-500 pl-1" htmlFor="anonymous">
{t("Comment Without Login")}
</label>
</div>
{anonymous && (
<div className="absolute right-0 top-full border rounded-lg px-6 pt-4 pb-5 space-y-2 bg-white z-10 mt-4">
<Input
label={t("Name") || ""}
id="name"
{...anonymousForm.register("name")}
/>
<Input
label={t("Email") || ""}
id="email"
{...anonymousForm.register("email")}
/>
</div>
)}
</div>
)}
</Button>
<Button
type="submit"
isLoading={
commentPage.isLoading ||
updateComment.isLoading ||
anonymousComment.isLoading
}
isDisabled={submitDisabled}
>
{t(submitText)}
</Button>
</div>
</div>
</form>
</div>

View File

@ -84,5 +84,8 @@
"Search for your interest": "搜索你感兴趣的内容",
"results": "条结果",
"My xLog": "我的 xLog",
"Click to select files": "点击选择文件"
"Click to select files": "点击选择文件",
"Comment Without Login": "免登录评论",
"Name": "名称",
"Email": "邮箱"
}

View File

@ -754,6 +754,28 @@ export async function getComments({
return res
}
export async function anonymousComment(input: {
targetCharacterId: number
targetNoteId: number
content: string
name: string
email: string
}) {
return await fetch("/api/anonymous/comment", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
targetCharacterId: input.targetCharacterId,
targetNoteId: input.targetNoteId,
content: input.content,
name: input.name,
email: input.email,
}),
})
}
export async function updateComment(
{
content,

View File

@ -340,6 +340,30 @@ export function useCommentPage() {
}
}
export function useAnonymousComment() {
const queryClient = useQueryClient()
return useMutation(
async (
payload: Parameters<typeof pageModel.anonymousComment>[0] & {
originalNoteId?: number
originalCharacterId?: number
},
) => {
return pageModel.anonymousComment(payload)
},
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries([
"getComments",
variables.originalCharacterId || variables.targetCharacterId,
variables.originalNoteId || variables.targetNoteId,
])
},
},
)
}
export function useUpdateComment() {
const queryClient = useQueryClient()
const contract = useContract()