179 lines
5.2 KiB
TypeScript
179 lines
5.2 KiB
TypeScript
import { useState, type ChangeEvent, type SubmitEvent } from "react";
|
|
import { RiLoader5Fill } from "@remixicon/react";
|
|
|
|
import { add } from "~/components/ui/notice/utils";
|
|
import { useIsLogin } from "~/composables/use-is-login";
|
|
import { clsn } from "~/utils";
|
|
import { getAuthorInfo, setAuthorInfo } from "~/utils/comment";
|
|
|
|
export interface CommentFormSubmitValues {
|
|
name: string;
|
|
email: string;
|
|
site: string;
|
|
content: string;
|
|
root?: string;
|
|
parent?: string;
|
|
}
|
|
|
|
interface CommentFormProps {
|
|
className?: string;
|
|
parent?: string;
|
|
root?: string;
|
|
onSubmit: (values: CommentFormSubmitValues) => Promise<unknown>;
|
|
}
|
|
|
|
export default function CommentForm({ className, parent, root, onSubmit }: CommentFormProps) {
|
|
const isLogin = useIsLogin();
|
|
const [loading, setLoading] = useState(false);
|
|
const [input, setInput] = useState(() => ({
|
|
...getAuthorInfo(),
|
|
content: "",
|
|
}));
|
|
|
|
const onChange = (ev: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = ev.target;
|
|
setInput((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const onFormSubmit = (ev: SubmitEvent) => {
|
|
ev.preventDefault();
|
|
|
|
if (!isLogin && (!input.name || !input.email || !input.content)) {
|
|
add({
|
|
title: "评论失败",
|
|
type: "error",
|
|
content: "缺少必填项",
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (!input.content) {
|
|
add({
|
|
title: "评论失败",
|
|
type: "error",
|
|
content: "缺少必填项",
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
setAuthorInfo({
|
|
name: input.name,
|
|
email: input.email,
|
|
site: input.site,
|
|
});
|
|
|
|
setLoading(true);
|
|
|
|
onSubmit({
|
|
...input,
|
|
root,
|
|
parent,
|
|
}).then(() => {
|
|
setInput((prev) => ({ ...prev, content: "" }));
|
|
setLoading(false);
|
|
}).catch(() => {
|
|
setLoading(false);
|
|
});
|
|
};
|
|
|
|
const inputClass = "w-full px-3 py-2.5 rounded-xl bg-white border-4 border-gray-200 focus:border-pink-400 outline-none transition-colors placeholder:text-neutral-400";
|
|
const labelClass = "block text-sm font-medium mb-2 text-neutral-500";
|
|
|
|
if (isLogin) {
|
|
return (
|
|
<form className={clsn("mb-8", className)} onSubmit={onFormSubmit}>
|
|
<div className="mb-4">
|
|
<h3 className="text-pink-400 font-bold text-lg mb-1">欢迎回来!</h3>
|
|
</div>
|
|
<div className="mb-4">
|
|
<textarea
|
|
name="content"
|
|
className={clsn(inputClass, "min-h-[7em] resize-y")}
|
|
placeholder="评论"
|
|
rows={3}
|
|
required
|
|
value={input.content}
|
|
onChange={onChange}
|
|
/>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
className="inline-flex items-center justify-center px-6 py-3 rounded-xl bg-pink-400 text-white font-medium hover:bg-pink-500 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"
|
|
disabled={loading}
|
|
>
|
|
{loading && <RiLoader5Fill className="animate-spin mr-2" />}
|
|
提交
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form className={clsn("mb-8", className)} onSubmit={onFormSubmit}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div className="flex flex-col gap-4">
|
|
<div>
|
|
<label className={labelClass}>昵称</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
className={inputClass}
|
|
placeholder="园友"
|
|
required
|
|
value={input.name}
|
|
onChange={onChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className={labelClass}>邮箱</label>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
className={inputClass}
|
|
placeholder="user@example.com"
|
|
required
|
|
value={input.email}
|
|
onChange={onChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className={labelClass}>站点</label>
|
|
<input
|
|
type="url"
|
|
name="site"
|
|
className={inputClass}
|
|
placeholder="https://example.com"
|
|
value={input.site}
|
|
onChange={onChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-4 md:h-full">
|
|
<div className="flex flex-col flex-1 min-h-0">
|
|
<label className={labelClass}>评论</label>
|
|
<textarea
|
|
name="content"
|
|
className={clsn(inputClass, "md:flex-1 resize-y")}
|
|
placeholder="注意言行举止,来做评论排行榜的前十名吧~"
|
|
rows={3}
|
|
required
|
|
value={input.content}
|
|
onChange={onChange}
|
|
/>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
className="w-full inline-flex items-center justify-center px-6 py-2.5 rounded-xl border-4 border-transparent bg-pink-400 text-white font-medium hover:bg-pink-500 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-not-allowed"
|
|
disabled={loading}
|
|
>
|
|
{loading && <RiLoader5Fill className="animate-spin mr-2" />}
|
|
提交
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|