Add about/contact pages, real social icons, and gallery scroll fix
- Add /about (business-info driven, falls back to existing copy) and /contact (business-info contact details + a working form posting to the contact-submissions API) - Point header/footer "About"/"Contact" links at the new pages instead of homepage anchors - Replace the hand-drawn social icons with the real brand SVGs from public/icons (Instagram, Telegram, WhatsApp, Bale, Eitaa), inlined with currentColor so hover states work; shared via a single socialLinks list used by both header and footer - Remove the horizontal scrollbar under the product gallery thumbnails by wrapping them instead of scrolling
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16.159" height="12.176" viewBox="0 0 16.159 12.176">
|
||||
<path id="Path_19534" data-name="Path 19534" d="M12.119.553a1.783,1.783,0,0,0-2.583,0L5.554,4.536a1.783,1.783,0,0,0,0,2.583l8.486,8.487a1.826,1.826,0,1,0,2.583-2.583l-7.2-7.2,2.691-2.691A1.783,1.783,0,0,0,12.119.553Z" transform="translate(0 17.176) rotate(-90)" fill="#333132"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="17.129" height="17.253" viewBox="0 0 17.129 17.253">
|
||||
<g id="Group_18956" data-name="Group 18956" transform="translate(-234.629 -321.162)">
|
||||
<path id="Path_19148" data-name="Path 19148" d="M240.486,335.387c-1.7-.823-2-2.692-.833-4.651-1.033-1.49-.66-3.185.845-3.579,0,.15,0,.286,0,.421a1.166,1.166,0,0,0,1.051,1.364,5.687,5.687,0,0,0,1.982,0,15.547,15.547,0,0,0,2.452-.849,1.989,1.989,0,0,0,.646-.507.674.674,0,0,0,.054-1.014A3.4,3.4,0,0,0,243.5,325.2a7.943,7.943,0,0,0-5.707,2.9,3.6,3.6,0,0,0-.866,1.828,2.775,2.775,0,0,0,.988,2.571.9.9,0,0,1,.329.709,3.457,3.457,0,0,0,2.49,2.99.615.615,0,0,0,.148,0c.135-.582.26-1.143.4-1.7.079-.321.244-.423.605-.331a4.768,4.768,0,0,0,4.037-.712,27.252,27.252,0,0,0,2.734-2.289c.759-.7,1.443-1.476,2.175-2.2a4.509,4.509,0,0,1,.871-.652c.018.694.241,1.332-.4,1.956a20.882,20.882,0,0,0-1.995,2.53,21.226,21.226,0,0,1-3.368,3.818,7.278,7.278,0,0,1-8.226.939,5.578,5.578,0,0,1-3.013-4.384c-.628-4.348,2.591-10,7.24-11.651a5.354,5.354,0,0,1,3.508-.106,4.521,4.521,0,0,1,3.133,5.034,4.73,4.73,0,0,1-3.028,4.224c-.625.274-1.237.578-1.855.869C242.05,332.313,240.64,333.3,240.486,335.387Z" transform="translate(0)" fill="#333132"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg id="Group_3409" data-name="Group 3409" xmlns="http://www.w3.org/2000/svg" width="15.746" height="15.746" viewBox="0 0 15.746 15.746">
|
||||
<path id="Path_2388" data-name="Path 2388" d="M11.4,0H4.345A4.35,4.35,0,0,0,0,4.345V11.4a4.35,4.35,0,0,0,4.345,4.345H11.4A4.35,4.35,0,0,0,15.746,11.4V4.345A4.35,4.35,0,0,0,11.4,0Zm2.949,11.4A2.952,2.952,0,0,1,11.4,14.349H4.345A2.952,2.952,0,0,1,1.4,11.4V4.345A2.952,2.952,0,0,1,4.345,1.4H11.4a2.952,2.952,0,0,1,2.948,2.948V11.4Z" transform="translate(0)" fill="#333132"/>
|
||||
<path id="Path_2389" data-name="Path 2389" d="M45.025,40.97a4.057,4.057,0,1,0,4.057,4.057A4.062,4.062,0,0,0,45.025,40.97Zm0,6.718a2.66,2.66,0,1,1,2.66-2.66A2.663,2.663,0,0,1,45.025,47.688Z" transform="translate(-37.152 -37.154)" fill="#333132"/>
|
||||
<path id="Path_2390" data-name="Path 2390" d="M119.946,28.251a1.024,1.024,0,1,0,.725.3A1.029,1.029,0,0,0,119.946,28.251Z" transform="translate(-107.845 -25.62)" fill="#333132"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 948 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm121.8 169.9l-40.7 191.8c-3 13.6-11.1 16.9-22.4 10.5l-62-45.7-29.9 28.8c-3.3 3.3-6.1 6.1-12.5 6.1l4.4-63.1 114.9-103.8c5-4.4-1.1-6.9-7.7-2.5l-142 89.4-61.2-19.1c-13.3-4.2-13.6-13.3 2.8-19.7l239.1-92.2c11.1-4 20.8 2.7 17.2 19.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M380.9 97.1C339 55.1 283.2 32 223.9 32c-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480l117.7-30.9c32.4 17.7 68.9 27 106.1 27h.1c122.3 0 224.1-99.6 224.1-222 0-59.3-25.2-115-67.1-157zm-157 341.6c-33.2 0-65.7-8.9-94-25.7l-6.7-4-69.8 18.3L72 359.2l-4.4-7c-18.5-29.4-28.2-63.3-28.2-98.2 0-101.7 82.8-184.5 184.6-184.5 49.3 0 95.6 19.2 130.4 54.1 34.8 34.9 56.2 81.2 56.1 130.5 0 101.8-84.9 184.6-186.6 184.6zm101.2-138.2c-5.5-2.8-32.8-16.2-37.9-18-5.1-1.9-8.8-2.8-12.5 2.8-3.7 5.6-14.3 18-17.6 21.8-3.2 3.7-6.5 4.2-12 1.4-32.6-16.3-54-29.1-75.5-66-5.7-9.8 5.7-9.1 16.3-30.3 1.8-3.7.9-6.9-.5-9.7-1.4-2.8-12.5-30.1-17.1-41.2-4.5-10.8-9.1-9.3-12.5-9.5-3.2-.2-6.9-.2-10.6-.2-3.7 0-9.7 1.4-14.8 6.9-5.1 5.6-19.4 19-19.4 46.3 0 27.3 19.9 53.7 22.6 57.4 2.8 3.7 39.1 59.7 94.8 83.8 35.2 15.2 49 16.5 66.6 13.9 10.7-1.6 32.8-13.4 37.4-26.4 4.6-13 4.6-24.1 3.2-26.4-1.3-2.5-5-3.9-10.5-6.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,88 @@
|
||||
import Link from "next/link";
|
||||
import { getBusinessInfo, safe } from "@/lib/api";
|
||||
import { ShieldIcon, TagIcon, TruckIcon } from "@/components/icons";
|
||||
|
||||
const FALLBACK_ABOUT =
|
||||
"فروشگاه اینترنتی کودکانه، تولیدکننده و عرضهکننده پوشاک بچگانه با جدیدترین طرحها برای نوزادان، دختران و پسران است. تمام محصولات با پارچههای نرم و مناسب پوست حساس کودکان تولید میشوند و پیش از انتشار در سایت، از نظر کیفیت دوخت و اصالت بررسی میشوند تا خیالتان برای انتخاب لباس فرزندتان راحت باشد.";
|
||||
|
||||
const points = [
|
||||
{
|
||||
icon: ShieldIcon,
|
||||
title: "ضمانت اصالت کالا",
|
||||
desc: "تمامی محصولات پیش از ارسال از نظر کیفیت و اصالت بررسی میشوند.",
|
||||
},
|
||||
{
|
||||
icon: TruckIcon,
|
||||
title: "ارسال سریع و رایگان",
|
||||
desc: "ارسال به سراسر کشور با پیک و پست پیشتاز، رایگان برای خریدهای بالای سقف تعیینشده.",
|
||||
},
|
||||
{
|
||||
icon: TagIcon,
|
||||
title: "قیمت مناسب و عمده",
|
||||
desc: "خرید مستقیم از تولیدی با قیمت رقابتی برای مشتریان خرد و عمده.",
|
||||
},
|
||||
];
|
||||
|
||||
export const metadata = {
|
||||
title: "درباره ما | کودکانه",
|
||||
};
|
||||
|
||||
export default async function AboutPage() {
|
||||
const info = await safe(getBusinessInfo(), null);
|
||||
const about = info?.about?.trim() || FALLBACK_ABOUT;
|
||||
const vision = info?.vision?.trim();
|
||||
|
||||
return (
|
||||
<main className="boxed py-8 md:py-12">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-14 items-center">
|
||||
<div className="overflow-hidden rounded-lg border border-line">
|
||||
<img
|
||||
src="/images/about/about.svg"
|
||||
alt=""
|
||||
className="w-full aspect-[9/8] object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="font-en text-xs md:text-sm font-semibold tracking-[4px] text-gold-dark uppercase">
|
||||
About Us
|
||||
</span>
|
||||
<h1 className="mt-1.5 text-xl md:text-3xl font-extrabold text-ink">درباره ما</h1>
|
||||
<span className="mt-3 block h-[3px] w-14 bg-brand rounded-full" />
|
||||
|
||||
<p className="mt-5 text-sm md:text-[15px] leading-7 md:leading-8 text-body">{about}</p>
|
||||
|
||||
{vision && (
|
||||
<>
|
||||
<h2 className="mt-6 text-sm md:text-base font-bold text-ink">چشمانداز ما</h2>
|
||||
<p className="mt-2 text-sm md:text-[15px] leading-7 md:leading-8 text-body">
|
||||
{vision}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-7 space-y-5">
|
||||
{points.map((p) => (
|
||||
<div key={p.title} className="flex items-start gap-3.5">
|
||||
<span className="flex items-center justify-center size-11 rounded-lg bg-brand-light text-brand shrink-0">
|
||||
<p.icon className="size-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="text-sm md:text-[15px] font-bold text-ink">{p.title}</h3>
|
||||
<p className="mt-1 text-xs md:text-sm text-body leading-6">{p.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/contact"
|
||||
className="mt-8 inline-flex items-center rounded-md bg-brand px-6 py-3 text-sm font-bold text-white hover:bg-brand-dark transition-colors"
|
||||
>
|
||||
تماس با ما
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { getBusinessInfo, safe } from "@/lib/api";
|
||||
import { siteInfo } from "@/data/site";
|
||||
import { socialLinks } from "@/components/socialLinks";
|
||||
import ContactForm from "@/components/ContactForm";
|
||||
import { MailIcon, PhoneIcon, PinIcon } from "@/components/icons";
|
||||
|
||||
export const metadata = {
|
||||
title: "تماس با ما | کودکانه",
|
||||
};
|
||||
|
||||
// business-info's emails/phoneNumbers/addresses shape isn't documented —
|
||||
// read either a plain string or a {value|number|email|address|...} object.
|
||||
function toText(item: unknown): string {
|
||||
if (typeof item === "string") return item;
|
||||
if (item && typeof item === "object") {
|
||||
const obj = item as Record<string, unknown>;
|
||||
const val = obj.value ?? obj.number ?? obj.email ?? obj.address ?? obj.text ?? obj.label;
|
||||
if (typeof val === "string") return val;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export default async function ContactPage() {
|
||||
const info = await safe(getBusinessInfo(), null);
|
||||
|
||||
const phone = info?.phoneNumbers.map(toText).find(Boolean) || siteInfo.phone;
|
||||
const email = info?.emails.map(toText).find(Boolean) || siteInfo.email;
|
||||
const address = info?.addresses.map(toText).find(Boolean) || siteInfo.address;
|
||||
|
||||
return (
|
||||
<main className="boxed py-8 md:py-12">
|
||||
<div className="mb-8 md:mb-10">
|
||||
<span className="font-en text-xs md:text-sm font-semibold tracking-[4px] text-gold-dark uppercase">
|
||||
Contact Us
|
||||
</span>
|
||||
<h1 className="mt-1.5 text-xl md:text-3xl font-extrabold text-ink">تماس با ما</h1>
|
||||
<span className="mt-3 block h-[3px] w-14 bg-brand rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[320px_1fr] gap-8 lg:gap-12">
|
||||
<div className="space-y-5">
|
||||
<div className="rounded-xl border border-line p-5">
|
||||
<ul className="space-y-4 text-sm">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="flex items-center justify-center size-9 rounded-lg bg-brand-light text-brand shrink-0">
|
||||
<PhoneIcon className="size-4.5" />
|
||||
</span>
|
||||
<span className="pt-1.5" dir="ltr">
|
||||
{phone}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="flex items-center justify-center size-9 rounded-lg bg-brand-light text-brand shrink-0">
|
||||
<MailIcon className="size-4.5" />
|
||||
</span>
|
||||
<span className="pt-1.5" dir="ltr">
|
||||
{email}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="flex items-center justify-center size-9 rounded-lg bg-brand-light text-brand shrink-0">
|
||||
<PinIcon className="size-4.5" />
|
||||
</span>
|
||||
<span className="pt-1.5">{address}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-line p-5">
|
||||
<p className="text-xs font-bold text-ink mb-3">ما را دنبال کنید</p>
|
||||
<div className="flex items-center gap-2.5">
|
||||
{socialLinks.map(({ icon: Icon, label, href }) => (
|
||||
<a
|
||||
key={label}
|
||||
href={href}
|
||||
aria-label={label}
|
||||
className="flex items-center justify-center size-9 rounded-full border border-line text-ink transition-all duration-200 hover:border-brand hover:bg-brand hover:text-white hover:scale-110"
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-line p-5 md:p-8">
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { submitContactForm } from "@/lib/api";
|
||||
import { CheckIcon } from "./icons";
|
||||
|
||||
type Status = "idle" | "loading" | "success" | "error";
|
||||
|
||||
const inputClass =
|
||||
"w-full rounded-lg border border-line bg-surface px-4 h-11 text-sm outline-none focus:border-brand transition-colors";
|
||||
|
||||
export default function ContactForm() {
|
||||
const [status, setStatus] = useState<Status>("idle");
|
||||
|
||||
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
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("loading");
|
||||
try {
|
||||
await submitContactForm({
|
||||
name,
|
||||
title,
|
||||
text,
|
||||
email: email || undefined,
|
||||
cellNumber: cellNumber || undefined,
|
||||
});
|
||||
setStatus("success");
|
||||
form.reset();
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
}
|
||||
|
||||
if (status === "success") {
|
||||
return (
|
||||
<div className="rounded-xl border border-brand/30 bg-brand-light p-6 text-center">
|
||||
<span className="mx-auto flex items-center justify-center size-11 rounded-full bg-brand text-white mb-3">
|
||||
<CheckIcon className="size-5" />
|
||||
</span>
|
||||
<p className="text-sm font-bold text-brand">پیام شما با موفقیت ارسال شد.</p>
|
||||
<p className="mt-1 text-xs text-body">بهزودی با شما تماس میگیریم.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-xs font-bold text-ink mb-1.5">
|
||||
نام و نام خانوادگی *
|
||||
</label>
|
||||
<input id="name" name="name" required className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-xs font-bold text-ink mb-1.5">
|
||||
موضوع *
|
||||
</label>
|
||||
<input id="title" name="title" required className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-xs font-bold text-ink mb-1.5">
|
||||
ایمیل
|
||||
</label>
|
||||
<input id="email" name="email" type="email" dir="ltr" className={inputClass} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="cellNumber" className="block text-xs font-bold text-ink mb-1.5">
|
||||
شماره تماس
|
||||
</label>
|
||||
<input id="cellNumber" name="cellNumber" type="tel" dir="ltr" className={inputClass} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="text" className="block text-xs font-bold text-ink mb-1.5">
|
||||
متن پیام *
|
||||
</label>
|
||||
<textarea
|
||||
id="text"
|
||||
name="text"
|
||||
required
|
||||
rows={5}
|
||||
className={`${inputClass} h-auto py-3 resize-none`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status === "error" && (
|
||||
<p className="text-xs text-red-600">ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === "loading"}
|
||||
className="w-full sm:w-auto flex items-center justify-center rounded-md bg-brand px-8 h-12 text-sm font-bold text-white hover:bg-brand-dark transition-colors disabled:opacity-60"
|
||||
>
|
||||
{status === "loading" ? "در حال ارسال..." : "ارسال پیام"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
+21
-32
@@ -3,14 +3,8 @@ import Logo from "./Logo";
|
||||
import { headerNavLinks, siteInfo } from "@/data/site";
|
||||
import { categoryHref } from "@/lib/slug";
|
||||
import type { CategoryNode } from "@/lib/types";
|
||||
import {
|
||||
InstagramIcon,
|
||||
MailIcon,
|
||||
PhoneIcon,
|
||||
PinIcon,
|
||||
TelegramIcon,
|
||||
WhatsappIcon,
|
||||
} from "./icons";
|
||||
import { socialLinks } from "./socialLinks";
|
||||
import { MailIcon, PhoneIcon, PinIcon } from "./icons";
|
||||
|
||||
export default function Footer({ categoryColumns }: { categoryColumns: CategoryNode[] }) {
|
||||
const categoryItems = categoryColumns.flatMap((col) => col.children).slice(0, 6);
|
||||
@@ -25,27 +19,16 @@ export default function Footer({ categoryColumns }: { categoryColumns: CategoryN
|
||||
دختران و پسران با ارسال به سراسر کشور.
|
||||
</p>
|
||||
<div className="mt-5 flex items-center gap-3">
|
||||
<a
|
||||
href="#"
|
||||
aria-label="اینستاگرام"
|
||||
className="flex items-center justify-center size-9 rounded-full border border-white/15 hover:border-gold hover:text-gold transition-colors"
|
||||
>
|
||||
<InstagramIcon className="size-4" />
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
aria-label="تلگرام"
|
||||
className="flex items-center justify-center size-9 rounded-full border border-white/15 hover:border-gold hover:text-gold transition-colors"
|
||||
>
|
||||
<TelegramIcon className="size-4" />
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
aria-label="واتساپ"
|
||||
className="flex items-center justify-center size-9 rounded-full border border-white/15 hover:border-gold hover:text-gold transition-colors"
|
||||
>
|
||||
<WhatsappIcon className="size-4" />
|
||||
</a>
|
||||
{socialLinks.map(({ icon: Icon, label, href }) => (
|
||||
<a
|
||||
key={label}
|
||||
href={href}
|
||||
aria-label={label}
|
||||
className="flex items-center justify-center size-9 rounded-full border border-white/15 transition-all duration-200 hover:border-gold hover:bg-gold hover:text-ink hover:scale-110"
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,9 +37,15 @@ export default function Footer({ categoryColumns }: { categoryColumns: CategoryN
|
||||
<ul className="space-y-2.5 text-sm">
|
||||
{headerNavLinks.map((link) => (
|
||||
<li key={link.title}>
|
||||
<a href={link.href} className="hover:text-gold transition-colors">
|
||||
{link.title}
|
||||
</a>
|
||||
{link.href.startsWith("#") ? (
|
||||
<a href={link.href} className="hover:text-gold transition-colors">
|
||||
{link.title}
|
||||
</a>
|
||||
) : (
|
||||
<Link href={link.href} className="hover:text-gold transition-colors">
|
||||
{link.title}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
+47
-39
@@ -4,26 +4,13 @@ import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Logo from "./Logo";
|
||||
import SearchBox from "./SearchBox";
|
||||
import {
|
||||
BagIcon,
|
||||
ChevronDownIcon,
|
||||
CloseIcon,
|
||||
InstagramIcon,
|
||||
MenuIcon,
|
||||
TelegramIcon,
|
||||
WhatsappIcon,
|
||||
} from "./icons";
|
||||
import { BagIcon, ChevronDownIcon, CloseIcon, MenuIcon } from "./icons";
|
||||
import { socialLinks } from "./socialLinks";
|
||||
import { headerNavLinks } from "@/data/site";
|
||||
import { CUSTOMER_PORTAL_URL } from "@/lib/config";
|
||||
import { categoryHref } from "@/lib/slug";
|
||||
import type { CategoryNode } from "@/lib/types";
|
||||
|
||||
const socialLinks = [
|
||||
{ icon: InstagramIcon, label: "اینستاگرام" },
|
||||
{ icon: TelegramIcon, label: "تلگرام" },
|
||||
{ icon: WhatsappIcon, label: "واتساپ" },
|
||||
];
|
||||
|
||||
export default function Header({ categoryColumns }: { categoryColumns: CategoryNode[] }) {
|
||||
const [productsOpen, setProductsOpen] = useState(false);
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
@@ -115,24 +102,34 @@ export default function Header({ categoryColumns }: { categoryColumns: CategoryN
|
||||
)}
|
||||
</div>
|
||||
|
||||
{headerNavLinks.map((link) => (
|
||||
<a
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
className="flex h-full items-center px-4 text-sm font-medium text-white/90 hover:text-gold transition-colors"
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
))}
|
||||
{headerNavLinks.map((link) =>
|
||||
link.href.startsWith("#") ? (
|
||||
<a
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
className="flex h-full items-center px-4 text-sm font-medium text-white/90 hover:text-gold transition-colors"
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
className="flex h-full items-center px-4 text-sm font-medium text-white/90 hover:text-gold transition-colors"
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{socialLinks.map(({ icon: Icon, label }) => (
|
||||
{socialLinks.map(({ icon: Icon, label, href }) => (
|
||||
<a
|
||||
key={label}
|
||||
href="#"
|
||||
href={href}
|
||||
aria-label={label}
|
||||
className="flex items-center justify-center size-8 rounded-md bg-white/10 text-white hover:bg-white/20 transition-colors"
|
||||
className="flex items-center justify-center size-8 rounded-md bg-white/10 text-white transition-all duration-200 hover:bg-gold hover:text-ink hover:scale-110"
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</a>
|
||||
@@ -207,15 +204,26 @@ export default function Header({ categoryColumns }: { categoryColumns: CategoryN
|
||||
</div>
|
||||
)}
|
||||
|
||||
{headerNavLinks.map((link) => (
|
||||
<a
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
className="block py-3 text-sm font-bold text-ink border-b border-line"
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
))}
|
||||
{headerNavLinks.map((link) =>
|
||||
link.href.startsWith("#") ? (
|
||||
<a
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
className="block py-3 text-sm font-bold text-ink border-b border-line"
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="block py-3 text-sm font-bold text-ink border-b border-line"
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
|
||||
<a
|
||||
href={CUSTOMER_PORTAL_URL}
|
||||
@@ -225,12 +233,12 @@ export default function Header({ categoryColumns }: { categoryColumns: CategoryN
|
||||
</a>
|
||||
|
||||
<div className="mt-5 flex items-center justify-center gap-2">
|
||||
{socialLinks.map(({ icon: Icon, label }) => (
|
||||
{socialLinks.map(({ icon: Icon, label, href }) => (
|
||||
<a
|
||||
key={label}
|
||||
href="#"
|
||||
href={href}
|
||||
aria-label={label}
|
||||
className="flex items-center justify-center size-9 rounded-md border border-line text-ink"
|
||||
className="flex items-center justify-center size-9 rounded-md border border-line text-ink transition-all duration-200 hover:border-brand hover:bg-brand hover:text-white hover:scale-110"
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</a>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function ProductGallery({ images, title }: { images: string[]; ti
|
||||
</div>
|
||||
|
||||
{gallery.length > 1 && (
|
||||
<div className="mt-3 flex gap-2.5 overflow-x-auto">
|
||||
<div className="mt-3 flex flex-wrap gap-2.5">
|
||||
{gallery.map((src, i) => (
|
||||
<button
|
||||
key={src + i}
|
||||
|
||||
@@ -105,26 +105,54 @@ export function TagIcon({ className }: IconProps) {
|
||||
|
||||
export function InstagramIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" className={className} {...base}>
|
||||
<rect x="3.5" y="3.5" width="17" height="17" rx="5" />
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<circle cx="17" cy="7" r="0.6" fill="currentColor" stroke="none" />
|
||||
<svg viewBox="0 0 15.746 15.746" className={className} fill="currentColor" stroke="none">
|
||||
<path d="M11.4,0H4.345A4.35,4.35,0,0,0,0,4.345V11.4a4.35,4.35,0,0,0,4.345,4.345H11.4A4.35,4.35,0,0,0,15.746,11.4V4.345A4.35,4.35,0,0,0,11.4,0Zm2.949,11.4A2.952,2.952,0,0,1,11.4,14.349H4.345A2.952,2.952,0,0,1,1.4,11.4V4.345A2.952,2.952,0,0,1,4.345,1.4H11.4a2.952,2.952,0,0,1,2.948,2.948V11.4Z" />
|
||||
<path
|
||||
d="M45.025,40.97a4.057,4.057,0,1,0,4.057,4.057A4.062,4.062,0,0,0,45.025,40.97Zm0,6.718a2.66,2.66,0,1,1,2.66-2.66A2.663,2.663,0,0,1,45.025,47.688Z"
|
||||
transform="translate(-37.152 -37.154)"
|
||||
/>
|
||||
<path
|
||||
d="M119.946,28.251a1.024,1.024,0,1,0,.725.3A1.029,1.029,0,0,0,119.946,28.251Z"
|
||||
transform="translate(-107.845 -25.62)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TelegramIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" className={className} fill="currentColor" stroke="none">
|
||||
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.894 8.221-1.97 9.28c-.145.658-.537.818-1.084.508l-3-2.21-1.446 1.394c-.16.16-.295.295-.605.295l.213-3.053 5.56-5.023c.242-.213-.054-.334-.373-.121l-6.869 4.326-2.96-.924c-.643-.204-.658-.643.136-.953l11.57-4.461c.538-.196 1.006.128.828.941z" />
|
||||
<svg viewBox="0 0 496 512" className={className} fill="currentColor" stroke="none">
|
||||
<path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm121.8 169.9l-40.7 191.8c-3 13.6-11.1 16.9-22.4 10.5l-62-45.7-29.9 28.8c-3.3 3.3-6.1 6.1-12.5 6.1l4.4-63.1 114.9-103.8c5-4.4-1.1-6.9-7.7-2.5l-142 89.4-61.2-19.1c-13.3-4.2-13.6-13.3 2.8-19.7l239.1-92.2c11.1-4 20.8 2.7 17.2 19.5z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function WhatsappIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" className={className} fill="currentColor" stroke="none">
|
||||
<path d="M12.04 2.003c-5.5 0-9.96 4.46-9.96 9.96 0 1.76.46 3.48 1.34 4.99L2 22l5.2-1.36a9.94 9.94 0 0 0 4.84 1.24c5.5 0 9.96-4.46 9.96-9.96 0-2.66-1.04-5.16-2.92-7.04a9.9 9.9 0 0 0-7.04-2.92zm0 1.8c2.15 0 4.17.84 5.7 2.36a8.03 8.03 0 0 1 2.36 5.7c0 4.44-3.62 8.06-8.07 8.06a8.05 8.05 0 0 1-4.1-1.12l-.29-.17-3.06.8.82-2.99-.19-.31a7.98 7.98 0 0 1-1.23-4.28c0-4.45 3.61-8.06 8.06-8.06zm-4.4 4.6c-.15 0-.39.05-.6.29-.2.23-.78.76-.78 1.85s.8 2.15.91 2.3c.11.15 1.56 2.47 3.83 3.37.53.22.95.35 1.28.44.54.15 1.03.13 1.42.08.43-.07 1.34-.55 1.53-1.08.19-.53.19-.99.13-1.08-.06-.1-.2-.15-.43-.27-.22-.11-1.34-.66-1.55-.74-.21-.08-.36-.11-.51.11-.15.22-.58.74-.71.89-.13.15-.26.16-.48.05-.22-.11-.95-.35-1.81-1.12-.67-.6-1.12-1.33-1.25-1.55-.13-.22-.01-.34.1-.45.1-.1.22-.26.33-.39.11-.13.15-.22.22-.37.07-.15.04-.28-.02-.39-.05-.11-.51-1.27-.71-1.74-.19-.45-.38-.39-.51-.4h-.44z" />
|
||||
<svg viewBox="0 0 448 512" className={className} fill="currentColor" stroke="none">
|
||||
<path d="M380.9 97.1C339 55.1 283.2 32 223.9 32c-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480l117.7-30.9c32.4 17.7 68.9 27 106.1 27h.1c122.3 0 224.1-99.6 224.1-222 0-59.3-25.2-115-67.1-157zm-157 341.6c-33.2 0-65.7-8.9-94-25.7l-6.7-4-69.8 18.3L72 359.2l-4.4-7c-18.5-29.4-28.2-63.3-28.2-98.2 0-101.7 82.8-184.5 184.6-184.5 49.3 0 95.6 19.2 130.4 54.1 34.8 34.9 56.2 81.2 56.1 130.5 0 101.8-84.9 184.6-186.6 184.6zm101.2-138.2c-5.5-2.8-32.8-16.2-37.9-18-5.1-1.9-8.8-2.8-12.5 2.8-3.7 5.6-14.3 18-17.6 21.8-3.2 3.7-6.5 4.2-12 1.4-32.6-16.3-54-29.1-75.5-66-5.7-9.8 5.7-9.1 16.3-30.3 1.8-3.7.9-6.9-.5-9.7-1.4-2.8-12.5-30.1-17.1-41.2-4.5-10.8-9.1-9.3-12.5-9.5-3.2-.2-6.9-.2-10.6-.2-3.7 0-9.7 1.4-14.8 6.9-5.1 5.6-19.4 19-19.4 46.3 0 27.3 19.9 53.7 22.6 57.4 2.8 3.7 39.1 59.7 94.8 83.8 35.2 15.2 49 16.5 66.6 13.9 10.7-1.6 32.8-13.4 37.4-26.4 4.6-13 4.6-24.1 3.2-26.4-1.3-2.5-5-3.9-10.5-6.6z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function BaleIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 16.159 12.176" className={className} fill="currentColor" stroke="none">
|
||||
<path
|
||||
d="M12.119.553a1.783,1.783,0,0,0-2.583,0L5.554,4.536a1.783,1.783,0,0,0,0,2.583l8.486,8.487a1.826,1.826,0,1,0,2.583-2.583l-7.2-7.2,2.691-2.691A1.783,1.783,0,0,0,12.119.553Z"
|
||||
transform="translate(0 17.176) rotate(-90)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EitaaIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 17.129 17.253" className={className} fill="currentColor" stroke="none">
|
||||
<path
|
||||
d="M240.486,335.387c-1.7-.823-2-2.692-.833-4.651-1.033-1.49-.66-3.185.845-3.579,0,.15,0,.286,0,.421a1.166,1.166,0,0,0,1.051,1.364,5.687,5.687,0,0,0,1.982,0,15.547,15.547,0,0,0,2.452-.849,1.989,1.989,0,0,0,.646-.507.674.674,0,0,0,.054-1.014A3.4,3.4,0,0,0,243.5,325.2a7.943,7.943,0,0,0-5.707,2.9,3.6,3.6,0,0,0-.866,1.828,2.775,2.775,0,0,0,.988,2.571.9.9,0,0,1,.329.709,3.457,3.457,0,0,0,2.49,2.99.615.615,0,0,0,.148,0c.135-.582.26-1.143.4-1.7.079-.321.244-.423.605-.331a4.768,4.768,0,0,0,4.037-.712,27.252,27.252,0,0,0,2.734-2.289c.759-.7,1.443-1.476,2.175-2.2a4.509,4.509,0,0,1,.871-.652c.018.694.241,1.332-.4,1.956a20.882,20.882,0,0,0-1.995,2.53,21.226,21.226,0,0,1-3.368,3.818,7.278,7.278,0,0,1-8.226.939,5.578,5.578,0,0,1-3.013-4.384c-.628-4.348,2.591-10,7.24-11.651a5.354,5.354,0,0,1,3.508-.106,4.521,4.521,0,0,1,3.133,5.034,4.73,4.73,0,0,1-3.028,4.224c-.625.274-1.237.578-1.855.869C242.05,332.313,240.64,333.3,240.486,335.387Z"
|
||||
transform="translate(-234.629 -321.162)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BaleIcon, EitaaIcon, InstagramIcon, TelegramIcon, WhatsappIcon } from "./icons";
|
||||
|
||||
export const socialLinks = [
|
||||
{ icon: InstagramIcon, label: "اینستاگرام", href: "#" },
|
||||
{ icon: TelegramIcon, label: "تلگرام", href: "#" },
|
||||
{ icon: WhatsappIcon, label: "واتساپ", href: "#" },
|
||||
{ icon: BaleIcon, label: "بله", href: "#" },
|
||||
{ icon: EitaaIcon, label: "ایتا", href: "#" },
|
||||
];
|
||||
+2
-2
@@ -8,7 +8,7 @@ export const siteInfo = {
|
||||
|
||||
export const headerNavLinks = [
|
||||
{ title: "تخفیفهای ویژه", href: "#special-sale" },
|
||||
{ title: "درباره ما", href: "#about" },
|
||||
{ title: "درباره ما", href: "/about" },
|
||||
{ title: "سوالات متداول", href: "#faq" },
|
||||
{ title: "تماس با ما", href: "#contact" },
|
||||
{ title: "تماس با ما", href: "/contact" },
|
||||
];
|
||||
|
||||
@@ -3,6 +3,8 @@ import type {
|
||||
ApiBlog,
|
||||
ApiCategory,
|
||||
ApiProduct,
|
||||
BusinessInfo,
|
||||
ContactFormInput,
|
||||
EntityComment,
|
||||
PagedResult,
|
||||
ProductVariation,
|
||||
@@ -31,6 +33,18 @@ async function apiGet<T>(path: string, params?: QueryParams): Promise<T> {
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function apiPost<T>(path: string, body: unknown): Promise<T> {
|
||||
const res = await fetch(`${API_BASE}${path}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`API request failed (${res.status}): ${path}`);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
// Swallows errors so a backend hiccup degrades a section instead of
|
||||
// crashing the whole page.
|
||||
export async function safe<T>(promise: Promise<T>, fallback: T): Promise<T> {
|
||||
@@ -125,6 +139,16 @@ export function getStoreSpecials(): Promise<StoreSpecial[]> {
|
||||
);
|
||||
}
|
||||
|
||||
export function getBusinessInfo(): Promise<BusinessInfo> {
|
||||
return apiGet(`/tenants/${SITE_DOMAIN}/website/business-info`);
|
||||
}
|
||||
|
||||
export function submitContactForm(
|
||||
data: ContactFormInput
|
||||
): Promise<{ submission: unknown; message: string }> {
|
||||
return apiPost(`/tenants/${SITE_DOMAIN}/contact-submissions`, data);
|
||||
}
|
||||
|
||||
export function findSlot(
|
||||
slots: StaticImageSlot[],
|
||||
key: string
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
export interface SocialMediaLinks {
|
||||
whatsapp?: string;
|
||||
telegram?: string;
|
||||
instagram?: string;
|
||||
linkedin?: string;
|
||||
youtube?: string;
|
||||
aparat?: string;
|
||||
}
|
||||
|
||||
// emails/phoneNumbers/addresses shapes aren't documented and this tenant
|
||||
// has none set yet — kept loose and read defensively at render time.
|
||||
export interface BusinessInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
nameFa: string;
|
||||
about: string;
|
||||
vision: string;
|
||||
logoUrl: string | null;
|
||||
faviconUrl: string | null;
|
||||
emails: unknown[];
|
||||
phoneNumbers: unknown[];
|
||||
socialMedia: SocialMediaLinks;
|
||||
addresses: unknown[];
|
||||
}
|
||||
|
||||
export interface ContactFormInput {
|
||||
title: string;
|
||||
name: string;
|
||||
text: string;
|
||||
email?: string;
|
||||
cellNumber?: string;
|
||||
}
|
||||
|
||||
export interface StaticImageItem {
|
||||
id: string;
|
||||
url: string;
|
||||
|
||||
Reference in New Issue
Block a user