feat(website): Next.js 16 marketing website with RTL/Farsi

Marketing website for Meezi platform:
- Server-side rendered pages: home, demo, blog, pricing
- RTL/Farsi layout with Vazirmatn font
- SEO metadata and Open Graph tags
- proxy.ts for Next.js 16 middleware convention
- MEEZI_API_URL internal Docker network routing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-27 21:34:32 +03:30
parent 131ecdbbe6
commit d62bb8d3ad
84 changed files with 16985 additions and 0 deletions
@@ -0,0 +1,195 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { CheckCircle2, AlertCircle, Send } from "lucide-react";
import { cn } from "@/lib/utils";
interface FormState {
name: string;
business: string;
phone: string;
email: string;
branches: string;
message: string;
}
export function DemoForm() {
const t = useTranslations("demo");
const [form, setForm] = useState<FormState>({
name: "",
business: "",
phone: "",
email: "",
branches: "1",
message: "",
});
const [status, setStatus] = useState<"idle" | "submitting" | "success" | "error">("idle");
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus("submitting");
try {
const res = await fetch("/api/demo", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (!res.ok) throw new Error("failed");
setStatus("success");
} catch {
setStatus("error");
}
};
const inputCls =
"w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-900 placeholder-gray-400 outline-none transition-all focus:border-brand-400 focus:ring-2 focus:ring-brand-100 disabled:opacity-50";
if (status === "success") {
return (
<div className="flex flex-col items-center gap-4 rounded-2xl border border-green-100 bg-green-50 px-8 py-12 text-center">
<CheckCircle2 className="h-14 w-14 text-green-500" />
<h3 className="text-xl font-bold text-gray-900">{t("successTitle")}</h3>
<p className="text-gray-500">{t("successDesc")}</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
{/* Name */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("nameLabel")} <span className="text-red-400">*</span>
</label>
<input
name="name"
required
value={form.name}
onChange={handleChange}
placeholder={t("namePlaceholder")}
className={inputCls}
disabled={status === "submitting"}
/>
</div>
{/* Business */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("businessLabel")} <span className="text-red-400">*</span>
</label>
<input
name="business"
required
value={form.business}
onChange={handleChange}
placeholder={t("businessPlaceholder")}
className={inputCls}
disabled={status === "submitting"}
/>
</div>
{/* Phone */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("phoneLabel")} <span className="text-red-400">*</span>
</label>
<input
name="phone"
type="tel"
required
value={form.phone}
onChange={handleChange}
placeholder={t("phonePlaceholder")}
className={inputCls}
disabled={status === "submitting"}
dir="ltr"
/>
</div>
{/* Email */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("emailLabel")}
</label>
<input
name="email"
type="email"
value={form.email}
onChange={handleChange}
placeholder={t("emailPlaceholder")}
className={inputCls}
disabled={status === "submitting"}
dir="ltr"
/>
</div>
</div>
{/* Branches */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("branchLabel")} <span className="text-red-400">*</span>
</label>
<select
name="branches"
required
value={form.branches}
onChange={handleChange}
className={inputCls}
disabled={status === "submitting"}
>
<option value="1">{t("branch1")}</option>
<option value="2-3">{t("branch2")}</option>
<option value="4-10">{t("branch3")}</option>
<option value="10+">{t("branch4")}</option>
</select>
</div>
{/* Message */}
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-700">
{t("messageLabel")}
</label>
<textarea
name="message"
rows={4}
value={form.message}
onChange={handleChange}
placeholder={t("messagePlaceholder")}
className={cn(inputCls, "resize-none")}
disabled={status === "submitting"}
/>
</div>
{status === "error" && (
<div className="flex items-center gap-2 rounded-xl border border-red-100 bg-red-50 px-4 py-3 text-sm text-red-600">
<AlertCircle className="h-4 w-4 shrink-0" />
{t("errorDesc")}
</div>
)}
<button
type="submit"
disabled={status === "submitting"}
className="flex w-full items-center justify-center gap-2 rounded-xl bg-brand-700 py-3.5 text-sm font-semibold text-white shadow-lg shadow-brand-700/25 transition-all hover:bg-brand-800 disabled:opacity-70"
>
{status === "submitting" ? (
<>
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
{t("submitting")}
</>
) : (
<>
<Send className="h-4 w-4" />
{t("submit")}
</>
)}
</button>
</form>
);
}