Add shopping cart, store-items pages, contact form toast; skip workshops+portfolio
Neither workshops nor portfolio is in this tenant's enabledModules
(["products","blog","finance"]) - same reasoning as before, page/nav entry
withheld until either is actually enabled.
Shopping cart (new):
- CartProvider (client context): live cart state, add/update/remove/refresh,
reads AUTH_TOKEN_COOKIE, calls the real /businesses/{id}/cart* endpoints
(bearer-only, no guest cart - by design, not a gap)
- CartButton: header/bottom-nav icon with a live count badge and a popup
preview; "ادامه" hands off to the customer portal's own cart/checkout,
since this storefront has no checkout UI (address/payment) to hand off to
internally - out of scope for this pass
- ProductBuyPanel: quantity stepper, wired to CartProvider.addItem() instead
of its own ad-hoc fetch+token logic, toast on success
- Cart/CartItem types are a best-effort guess at an undocumented response
shape (OpenAPI gives no field-level schema) - unverified against real data
since store-items are still empty; read every field defensively
Store-items pages (new): /store-items (flat listing of purchasable variants,
always priced unlike /products) and /store-items/{id} (detail - no slug,
resolves the variant's productId and reuses the product's own gallery/
description/technical-info/comments/related-products, just with that
variant preselected).
Contact form: now shows a toast + inline success text and resets while
staying on screen, instead of swapping to a separate "thank you" panel.
New shared providers: ToastProvider (useToast) and CartProvider, both
mounted once in layout.tsx.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e676397bae
commit
d624ad8a32
@@ -2,22 +2,28 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { submitContactForm } from "@/lib/api";
|
||||
import { useToast } from "./providers/ToastProvider";
|
||||
import { CheckIcon } from "./icons";
|
||||
|
||||
type Status = "idle" | "submitting" | "sent" | "error";
|
||||
|
||||
// Posts straight to POST /tenants/moalem.shop/contact-submissions — a real,
|
||||
// documented endpoint, no bearer required.
|
||||
// documented endpoint, no bearer required. On success: a toast, inline
|
||||
// success text, and the form resets and stays put (not swapped for a
|
||||
// separate "thank you" panel) so another message can be sent right away.
|
||||
export default function ContactForm() {
|
||||
const [status, setStatus] = useState<Status>("idle");
|
||||
const { showToast } = useToast();
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.currentTarget);
|
||||
const name = String(form.get("name") || "").trim();
|
||||
const title = String(form.get("title") || "").trim();
|
||||
const text = String(form.get("text") || "").trim();
|
||||
const email = String(form.get("email") || "").trim();
|
||||
const cellNumber = String(form.get("cellNumber") || "").trim();
|
||||
const form = e.currentTarget;
|
||||
const data = new FormData(form);
|
||||
const name = String(data.get("name") || "").trim();
|
||||
const title = String(data.get("title") || "").trim();
|
||||
const text = String(data.get("text") || "").trim();
|
||||
const email = String(data.get("email") || "").trim();
|
||||
const cellNumber = String(data.get("cellNumber") || "").trim();
|
||||
if (!name || !title || !text) return;
|
||||
|
||||
setStatus("submitting");
|
||||
@@ -29,33 +35,19 @@ export default function ContactForm() {
|
||||
email: email || undefined,
|
||||
cellNumber: cellNumber || undefined,
|
||||
});
|
||||
form.reset();
|
||||
setStatus("sent");
|
||||
e.currentTarget.reset();
|
||||
showToast("پیام شما با موفقیت ارسال شد");
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
}
|
||||
|
||||
if (status === "sent") {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-line bg-surface p-10 text-center">
|
||||
<p className="text-[15px] font-bold text-ink">پیام شما با موفقیت ارسال شد.</p>
|
||||
<p className="text-[13px] text-body">همکاران ما در اسرع وقت با شما تماس میگیرند.</p>
|
||||
<button
|
||||
onClick={() => setStatus("idle")}
|
||||
className="mt-2 rounded-lg border border-line px-4 py-2 text-[13px] text-ink-soft transition hover:border-brand hover:text-brand"
|
||||
>
|
||||
ارسال پیام دیگر
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const input =
|
||||
"h-12 w-full rounded-xl border border-line bg-white px-4 text-sm outline-none transition focus:border-brand";
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<form onSubmit={handleSubmit} onChange={() => status === "sent" && setStatus("idle")} className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<input name="name" required placeholder="نام و نام خانوادگی" className={input} />
|
||||
<input name="cellNumber" placeholder="شماره موبایل" inputMode="tel" className={input} />
|
||||
@@ -75,6 +67,12 @@ export default function ContactForm() {
|
||||
{status === "error" && (
|
||||
<p className="text-[12.5px] text-brand">ارسال پیام ناموفق بود. لطفاً دوباره تلاش کنید.</p>
|
||||
)}
|
||||
{status === "sent" && (
|
||||
<p className="flex items-center gap-1.5 text-[12.5px] font-bold text-brand">
|
||||
<CheckIcon className="size-4" />
|
||||
پیام شما ارسال شد. همکاران ما در اسرع وقت با شما تماس میگیرند.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
Reference in New Issue
Block a user