Create Glassmond storefront with Meshkee integration

Build the RTL Next.js experience with live catalog, journal, customer interactions, and SEO-ready canonical routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 00:08:00 +03:30
co-authored by Cursor
commit 98fde02322
66 changed files with 8280 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
"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>
);
}