Build the RTL Next.js experience with live catalog, journal, customer interactions, and SEO-ready canonical routes. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { FormEvent, useState } from "react";
|
|
import { MessageSquare, Send, UserRound } from "lucide-react";
|
|
import { useAuth } from "@/components/auth-provider";
|
|
import {
|
|
CUSTOMER_URL,
|
|
MESHKEE_API_BASE,
|
|
WEBSITE_DOMAIN,
|
|
type PublicComment,
|
|
} from "@/lib/meshkee";
|
|
|
|
export function CommentsPanel({
|
|
entityType,
|
|
entityId,
|
|
comments,
|
|
}: {
|
|
entityType: "product" | "blog";
|
|
entityId: string;
|
|
comments: PublicComment[];
|
|
}) {
|
|
const { user, loading } = useAuth();
|
|
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
|
|
|
|
async function submit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!user) return;
|
|
|
|
const form = event.currentTarget;
|
|
const data = new FormData(form);
|
|
const text = String(data.get("text") ?? "").trim();
|
|
if (!text) return;
|
|
|
|
setStatus("sending");
|
|
try {
|
|
const authorName =
|
|
[user.firstName, user.lastName].filter(Boolean).join(" ") ||
|
|
user.name ||
|
|
"کاربر گلسموند";
|
|
const response = await fetch(
|
|
`${MESHKEE_API_BASE}/tenants/${WEBSITE_DOMAIN}/comments`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
entityType,
|
|
entityId,
|
|
authorName,
|
|
authorEmail: user.email || undefined,
|
|
text,
|
|
}),
|
|
},
|
|
);
|
|
if (!response.ok) throw new Error("Comment failed");
|
|
setStatus("sent");
|
|
form.reset();
|
|
} catch {
|
|
setStatus("error");
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="comments-panel">
|
|
<div className="comments-list">
|
|
{comments.length > 0 ? (
|
|
comments.map((comment) => (
|
|
<article key={comment.id}>
|
|
<span><UserRound /></span>
|
|
<div>
|
|
<h3>{comment.authorName}</h3>
|
|
<p>{comment.text}</p>
|
|
</div>
|
|
</article>
|
|
))
|
|
) : (
|
|
<div className="comments-empty">
|
|
<MessageSquare />
|
|
<p>هنوز دیدگاهی ثبت نشده است. اولین نفر باشید.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{!loading && (
|
|
user ? (
|
|
<form className="comment-form" onSubmit={submit}>
|
|
<label htmlFor={`comment-${entityId}`}>دیدگاه شما</label>
|
|
<textarea
|
|
id={`comment-${entityId}`}
|
|
name="text"
|
|
rows={5}
|
|
placeholder="نظر خود را بنویسید..."
|
|
required
|
|
/>
|
|
<button className="primary-button" type="submit" disabled={status === "sending"}>
|
|
{status === "sending" ? "در حال ارسال..." : "ثبت دیدگاه"}
|
|
<Send />
|
|
</button>
|
|
{status === "sent" && <p className="comment-success">دیدگاه شما ثبت شد و پس از تأیید نمایش داده میشود.</p>}
|
|
{status === "error" && <p className="form-error">ارسال دیدگاه انجام نشد. دوباره تلاش کنید.</p>}
|
|
</form>
|
|
) : (
|
|
<div className="comment-login-box">
|
|
<UserRound />
|
|
<div>
|
|
<h3>برای ثبت دیدگاه وارد شوید</h3>
|
|
<p>ثبت نظر برای کاربران واردشده امکانپذیر است.</p>
|
|
</div>
|
|
<a className="primary-button" href={CUSTOMER_URL}>ورود به حساب</a>
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ProductTabs({
|
|
descriptionHtml,
|
|
entityId,
|
|
comments,
|
|
}: {
|
|
descriptionHtml: string;
|
|
entityId: string;
|
|
comments: PublicComment[];
|
|
}) {
|
|
const [tab, setTab] = useState<"description" | "comments">("description");
|
|
|
|
return (
|
|
<section className="entity-tabs">
|
|
<div className="tab-buttons" role="tablist" aria-label="اطلاعات محصول">
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={tab === "description"}
|
|
className={tab === "description" ? "active" : ""}
|
|
onClick={() => setTab("description")}
|
|
>
|
|
توضیحات
|
|
</button>
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={tab === "comments"}
|
|
className={tab === "comments" ? "active" : ""}
|
|
onClick={() => setTab("comments")}
|
|
>
|
|
دیدگاهها
|
|
<span>{comments.length.toLocaleString("fa-IR")}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="tab-content">
|
|
{tab === "description" ? (
|
|
<div
|
|
className="cms-content"
|
|
dangerouslySetInnerHTML={{ __html: descriptionHtml }}
|
|
/>
|
|
) : (
|
|
<CommentsPanel entityType="product" entityId={entityId} comments={comments} />
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|