Files
erika/app/(ui)/share/[id]/client.tsx
T
2026-08-27 14:11:20 +00:00

227 lines
7.1 KiB
TypeScript

"use client";
import { useState, useTransition } from "react";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Clipboard, Link2, LogIn, MessageSquare, Trash2 } from "lucide-react";
import { toast } from "sonner";
import {
createCommentAction,
deleteCommentAction,
deleteShareAction,
} from "@/app/actions/share";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { MAX_SHARE_COMMENT_LENGTH } from "@/lib/share/limits";
import { getShareContentForClipboard } from "@/lib/share/copy";
export function CopyShareActions() {
async function copyText() {
const content = getShareContentForClipboard((id) =>
document.getElementById(id),
);
if (content === null) return;
await navigator.clipboard.writeText(content);
toast.success("คัดลอกข้อความแล้ว");
}
async function copyLink() {
await navigator.clipboard.writeText(window.location.href);
toast.success("คัดลอกลิงก์แล้ว");
}
return (
<div className="flex flex-wrap items-center gap-2">
<Button type="button" size="sm" onClick={copyText}>
<Clipboard data-icon="inline-start" />
คัดลอกข้อความ
</Button>
<Button type="button" variant="outline" size="sm" onClick={copyLink}>
<Link2 data-icon="inline-start" />
คัดลอกลิงก์
</Button>
</div>
);
}
export function CommentComposer({
shareId,
authenticated,
}: {
shareId: string;
authenticated: boolean;
}) {
const router = useRouter();
const [body, setBody] = useState("");
const [error, setError] = useState<string | null>(null);
const [pending, startTransition] = useTransition();
if (!authenticated) {
return (
<Button
type="button"
variant="outline"
onClick={() => signIn("discord", { callbackUrl: window.location.href })}
>
<LogIn data-icon="inline-start" />
เข้าสู่ระบบเพื่อแสดงความคิดเห็น
</Button>
);
}
return (
<form
onSubmit={(event) => {
event.preventDefault();
setError(null);
startTransition(async () => {
const result = await createCommentAction(shareId, body);
if (!result.success) {
setError(result.error);
return;
}
setBody("");
toast.success("เพิ่มความคิดเห็นแล้ว");
router.refresh();
});
}}
>
<Field data-invalid={Boolean(error)}>
<FieldLabel htmlFor="comment-body">แสดงความคิดเห็น</FieldLabel>
<Textarea
id="comment-body"
value={body}
onChange={(event) => setBody(event.target.value)}
maxLength={MAX_SHARE_COMMENT_LENGTH}
aria-invalid={Boolean(error)}
placeholder="เขียนความคิดเห็นของคุณ…"
className="min-h-24 resize-y"
/>
<div className="flex flex-wrap items-center justify-between gap-3">
<FieldDescription>
{body.length.toLocaleString("th-TH")} / 2,000
</FieldDescription>
<Button type="submit" size="sm" disabled={pending || !body.trim()}>
{pending ? <Spinner data-icon="inline-start" /> : <MessageSquare data-icon="inline-start" />}
ส่งความคิดเห็น
</Button>
</div>
{error && <FieldError>{error}</FieldError>}
</Field>
</form>
);
}
export function DeleteShareButton({ shareId }: { shareId: string }) {
const router = useRouter();
const [pending, setPending] = useState(false);
async function remove() {
setPending(true);
const result = await deleteShareAction(shareId);
if (!result.success) {
setPending(false);
toast.error(result.error);
return;
}
toast.success("ลบข้อความแชร์แล้ว");
router.push("/share");
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm" disabled={pending}>
{pending ? <Spinner data-icon="inline-start" /> : <Trash2 data-icon="inline-start" />}
ลบข้อความนี้
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>ลบข้อความแชร์นี้หรือไม่</AlertDialogTitle>
<AlertDialogDescription>
ข้อความ รูปประกอบ และความคิดเห็นทั้งหมดจะถูกลบถาวร
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>ยกเลิก</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={remove}>
ลบถาวร
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
export function DeleteCommentButton({
shareId,
commentId,
}: {
shareId: string;
commentId: string;
}) {
const router = useRouter();
const [pending, setPending] = useState(false);
async function remove() {
setPending(true);
const result = await deleteCommentAction(shareId, commentId);
setPending(false);
if (!result.success) {
toast.error(result.error);
return;
}
toast.success("ลบความคิดเห็นแล้ว");
router.refresh();
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon-sm"
disabled={pending}
aria-label="ลบความคิดเห็น"
>
{pending ? <Spinner /> : <Trash2 />}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>ลบความคิดเห็นนี้หรือไม่</AlertDialogTitle>
<AlertDialogDescription>
การลบความคิดเห็นไม่สามารถย้อนกลับได้
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>ยกเลิก</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={remove}>
ลบความคิดเห็น
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}