Add customer auth, real cart/checkout, store-items pages, comments, contact form
- Real in-site login/register (password + SMS OTP) against Meshkee's /auth API, with self-healing access-token refresh (15 min lifetime) and an in-flight-refresh guard so AuthContext and CartContext don't race each other into a failed concurrent refresh. - Real cart: header cart button/badge, a cart popup, and a working "add to cart" on both product cards (where a specific variant id is known) and the product/store-item detail buy-box (always resolves a purchasable variant, even for single-SKU products with no selectable variation). - /checkout: address + cash/bank-transfer payment, posts to the real checkout API and creates an order; refreshes the cart afterward so the header badge doesn't go stale. - New /store-items and /store-items/[id] pages (variant-level browsing, separate from the product-level /products catalog), cross-referencing the parent product for images/description/technical-info. - Product gallery: click-to-open fullscreen lightbox alongside the existing hover-zoom. - Comments tab wired to the real comments API, gated to logged-in users (a product decision — the API itself doesn't require auth) with a login prompt otherwise. - Similar-products row on product/store-item detail pages. - Contact page wired to the real contact-submissions API, with a toast and inline confirmation on success, and the form resetting. - CONTEXT.md: a maintained architecture/conventions doc for this repo, now auto-loaded via CLAUDE.md, covering the Meshkee integration quirks and UI conventions established across this project. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c2552af079
commit
66f92b634e
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Breadcrumb } from "@/components/ui/Breadcrumb";
|
||||
import { Toast } from "@/components/ui/Toast";
|
||||
import { submitContactForm } from "@/lib/meshkee";
|
||||
|
||||
const inputClass =
|
||||
"w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none transition-colors focus:border-red";
|
||||
|
||||
const initialForm = { title: "", name: "", email: "", cellNumber: "", text: "" };
|
||||
|
||||
export default function ContactPage() {
|
||||
const [form, setForm] = useState(initialForm);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [toast, setToast] = useState(false);
|
||||
|
||||
const update = (field: keyof typeof form) => (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
||||
setForm((prev) => ({ ...prev, [field]: event.target.value }));
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
const res = await submitContactForm({
|
||||
title: form.title,
|
||||
name: form.name,
|
||||
email: form.email || undefined,
|
||||
cellNumber: form.cellNumber || undefined,
|
||||
text: form.text,
|
||||
});
|
||||
setBusy(false);
|
||||
if (!res.ok) {
|
||||
setError(res.message ?? "ارسال پیام ناموفق بود.");
|
||||
return;
|
||||
}
|
||||
setSuccess(true);
|
||||
setForm(initialForm);
|
||||
setToast(true);
|
||||
setTimeout(() => setToast(false), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-10">
|
||||
<Breadcrumb items={[{ label: "خانه", href: "/" }, { label: "تماس با ما" }]} />
|
||||
<h1 className="mb-2 text-2xl font-bold text-red-dark">تماس با ما</h1>
|
||||
<p className="mb-6 text-sm text-muted">
|
||||
سوال یا پیشنهادی دارید؟ فرم زیر را پر کنید تا در اسرع وقت پاسخ دهیم.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<input
|
||||
required
|
||||
placeholder="نام و نام خانوادگی"
|
||||
value={form.name}
|
||||
onChange={update("name")}
|
||||
className={inputClass}
|
||||
/>
|
||||
<input
|
||||
required
|
||||
placeholder="موضوع پیام"
|
||||
value={form.title}
|
||||
onChange={update("title")}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="ایمیل (اختیاری)"
|
||||
value={form.email}
|
||||
onChange={update("email")}
|
||||
className={inputClass}
|
||||
/>
|
||||
<input
|
||||
type="tel"
|
||||
placeholder="شماره تماس (اختیاری)"
|
||||
value={form.cellNumber}
|
||||
onChange={update("cellNumber")}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
required
|
||||
rows={5}
|
||||
placeholder="متن پیام"
|
||||
value={form.text}
|
||||
onChange={update("text")}
|
||||
className={inputClass}
|
||||
/>
|
||||
|
||||
{error && <p className="text-sm text-red-dark">{error}</p>}
|
||||
{success && <p className="text-sm text-green-700">پیام شما با موفقیت ارسال شد. متشکریم!</p>}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={busy}
|
||||
className="rounded-xl bg-red px-8 py-3 text-sm font-medium text-white transition-colors hover:bg-red-dark disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{busy ? "در حال ارسال..." : "ارسال پیام"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<Toast message="پیام شما با موفقیت ارسال شد ✓" show={toast} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user