Initial commit: Ali Alemi storefront (Next.js, Meshkee API integration)

Farsi RTL Next.js 16 storefront for استاد علی عالمی, cloned from the
original Meshkee web-builder site and rebuilt with Tailwind 4, live
Meshkee API integration (products, store items, portfolios, blog,
workshops, comments, cart, customer auth), and standalone production
output for the shared-VM deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
amirhosein.ashourloo
2026-09-16 09:43:10 +03:30
co-authored by Claude Sonnet 5
commit 8f102a8250
106 changed files with 11605 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
"use client";
import { useState, type FormEvent } from "react";
import { IconCheck } from "@/components/icons";
import { useToast } from "@/components/providers/ToastProvider";
import { submitContactForm } from "@/lib/meshkee/contact";
export function ContactForm() {
const [sent, setSent] = useState(false);
const [submitting, setSubmitting] = useState(false);
const { showToast } = useToast();
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = event.currentTarget;
const data = new FormData(form);
setSubmitting(true);
try {
await submitContactForm({
title: "پیام از فرم تماس با ما",
name: String(data.get("name") ?? ""),
cellNumber: String(data.get("phone") ?? ""),
text: String(data.get("message") ?? ""),
});
showToast("پیام شما با موفقیت ارسال شد.");
setSent(true);
form.reset();
setTimeout(() => setSent(false), 4000);
} catch {
showToast("ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.", "error");
} finally {
setSubmitting(false);
}
}
return (
<form
onSubmit={handleSubmit}
className="grid gap-5 rounded-3xl bg-white p-8 shadow-sm ring-1 ring-line md:grid-cols-2"
>
{sent && (
<div className="flex items-center gap-3 rounded-2xl bg-primary-soft px-5 py-4 md:col-span-2">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary text-white">
<IconCheck className="h-4 w-4" />
</span>
<p className="text-sm font-medium text-ink-soft">
پیام شما ارسال شد. از اینکه با استاد علی عالمی در ارتباط هستید سپاسگزاریم.
</p>
</div>
)}
<div className="md:col-span-1">
<label htmlFor="name" className="mb-2 block text-sm font-medium text-ink-soft">
نام و نام خانوادگی
</label>
<input
id="name"
name="name"
required
type="text"
className="w-full rounded-xl border border-line px-4 py-3 text-sm outline-none transition-colors focus:border-primary"
placeholder="نام شما"
/>
</div>
<div className="md:col-span-1">
<label htmlFor="phone" className="mb-2 block text-sm font-medium text-ink-soft">
شماره تماس
</label>
<input
id="phone"
name="phone"
required
type="tel"
dir="ltr"
className="w-full rounded-xl border border-line px-4 py-3 text-sm outline-none transition-colors focus:border-primary"
placeholder="0912 000 0000"
/>
</div>
<div className="md:col-span-2">
<label htmlFor="message" className="mb-2 block text-sm font-medium text-ink-soft">
پیام شما
</label>
<textarea
id="message"
name="message"
required
rows={5}
className="w-full resize-none rounded-xl border border-line px-4 py-3 text-sm outline-none transition-colors focus:border-primary"
placeholder="متن پیام..."
/>
</div>
<div className="md:col-span-2">
<button
type="submit"
disabled={submitting}
className="w-full rounded-full bg-primary px-7 py-3.5 text-sm font-semibold text-white transition-colors hover:bg-primary-dark disabled:opacity-60 md:w-auto"
>
{submitting ? "در حال ارسال..." : "ارسال پیام"}
</button>
</div>
</form>
);
}
+51
View File
@@ -0,0 +1,51 @@
import Image from "next/image";
import { CONTACT } from "@/lib/site";
import { IconInstagram, IconLocation, IconPhone } from "@/components/icons";
const ITEMS = [
{ icon: IconLocation, text: CONTACT.address, href: undefined },
{ icon: IconPhone, text: CONTACT.phoneLabel, href: `tel:${CONTACT.phone}`, dir: "ltr" as const },
{ icon: IconInstagram, text: CONTACT.instagramLabel, href: CONTACT.instagram, dir: "ltr" as const },
];
export function ContactInfo() {
return (
<section className="py-16 md:py-24">
<div className="boxed grid items-center gap-14 md:grid-cols-2">
<div className="relative mx-auto w-full max-w-md">
<div className="absolute -inset-x-6 -top-6 bottom-10 rounded-[2.5rem] bg-primary-soft" aria-hidden />
<Image
src="/images/contact-photo.jpg"
alt="ارتباط با استاد علی عالمی"
width={520}
height={600}
className="relative w-full rounded-[2.25rem] object-cover shadow-xl"
/>
</div>
<div>
<p className="text-sm font-semibold text-primary">راه‌های ارتباطی</p>
<h2 className="mt-2 text-3xl font-bold text-ink">با ما تماس بگیرید</h2>
<p className="mt-4 text-base leading-8 text-body">ما هر روز هفته آماده‌ی شنیدن نظرات شما هستیم.</p>
<ul className="mt-8 space-y-4">
{ITEMS.map(({ icon: Icon, text, href, dir }) => (
<li key={text} className="flex items-center gap-4 rounded-2xl bg-primary-soft p-4">
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-primary text-white">
<Icon className="h-5 w-5" />
</span>
{href ? (
<a href={href} target={href.startsWith("http") ? "_blank" : undefined} rel="noopener noreferrer" dir={dir} className="text-sm font-medium text-ink-soft hover:text-primary">
{text}
</a>
) : (
<span className="text-sm font-medium text-ink-soft">{text}</span>
)}
</li>
))}
</ul>
</div>
</div>
</section>
);
}
@@ -0,0 +1,28 @@
import { CUSTOMER_URL } from "@/lib/site";
import { IconArrow } from "@/components/icons";
export function CustomerClubBanner() {
return (
<section className="relative overflow-hidden bg-gradient-to-l from-secondary to-primary py-14">
<div
className="pointer-events-none absolute inset-0 opacity-[0.08]"
style={{
backgroundImage: "radial-gradient(currentColor 1.5px, transparent 1.5px)",
backgroundSize: "20px 20px",
color: "#ffffff",
}}
aria-hidden
/>
<div className="boxed relative flex flex-col items-center justify-between gap-6 text-center md:flex-row md:text-right">
<h2 className="text-2xl font-bold text-white md:text-3xl">در باشگاه مشتریان ما عضو شوید</h2>
<a
href={`${CUSTOMER_URL}/register`}
className="flex shrink-0 items-center gap-2 rounded-full bg-white px-7 py-3.5 text-sm font-semibold text-secondary transition-transform hover:scale-105"
>
عضویت رایگان
<IconArrow className="h-4 w-4 -scale-x-100" />
</a>
</div>
</section>
);
}
+45
View File
@@ -0,0 +1,45 @@
import { CONTACT } from "@/lib/site";
import { IconLocation } from "@/components/icons";
import { ContactForm } from "./ContactForm";
export function MessageSection() {
const mapsHref = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(CONTACT.address)}`;
return (
<section className="bg-surface py-16 md:py-24">
<div className="boxed">
<h2 className="text-center text-3xl font-bold text-ink">به ما پیام بفرستید!</h2>
<div className="mt-12 grid gap-8 lg:grid-cols-[1.4fr_1fr]">
<ContactForm />
<a
href={mapsHref}
target="_blank"
rel="noopener noreferrer"
className="group relative flex min-h-[280px] flex-col justify-between overflow-hidden rounded-3xl bg-secondary p-8 text-white transition-colors hover:bg-secondary-dark"
>
<div
className="pointer-events-none absolute inset-0 opacity-[0.08]"
style={{
backgroundImage: "radial-gradient(currentColor 1.5px, transparent 1.5px)",
backgroundSize: "18px 18px",
}}
aria-hidden
/>
<span className="relative flex h-12 w-12 items-center justify-center rounded-xl bg-white/10 text-primary">
<IconLocation className="h-6 w-6" />
</span>
<div className="relative">
<p className="text-lg font-bold">موقعیت مکانی ما</p>
<p className="mt-2 text-sm leading-7 text-white/70">{CONTACT.address}</p>
<p className="mt-4 text-xs font-semibold text-primary group-hover:underline">
مشاهده در نقشه ←
</p>
</div>
</a>
</div>
</div>
</section>
);
}