555 lines
15 KiB
TypeScript
555 lines
15 KiB
TypeScript
"use client";
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Mail } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { Controller, useForm } from "react-hook-form";
|
|
import z from "zod";
|
|
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/components/ui/accordion";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Field, FieldError } from "@/components/ui/field";
|
|
import {
|
|
InputGroup,
|
|
InputGroupAddon,
|
|
InputGroupInput,
|
|
} from "@/components/ui/input-group";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type FooterLink = {
|
|
title?: string;
|
|
text: string;
|
|
href?: string;
|
|
};
|
|
|
|
type SocialIcon = {
|
|
title: string;
|
|
light: string;
|
|
dark: string;
|
|
};
|
|
|
|
type SocialLink = {
|
|
link: string;
|
|
icon: SocialIcon;
|
|
};
|
|
|
|
type FooterLinksSection = {
|
|
title: string;
|
|
id: string;
|
|
links?: FooterLink[];
|
|
items?: FooterLink[];
|
|
socialMediaLinks?: SocialLink[];
|
|
};
|
|
|
|
type ContactForm = {
|
|
title?: string;
|
|
text?: string;
|
|
};
|
|
|
|
type HomeLink = {
|
|
logo: {
|
|
light: string;
|
|
dark: string;
|
|
};
|
|
link: string;
|
|
};
|
|
|
|
type FooterData = {
|
|
sections: FooterLinksSection[];
|
|
contactForm: ContactForm;
|
|
submenu?: FooterLink[];
|
|
paymentMethods?: string[];
|
|
copyrightCaption?: string;
|
|
homeLink: HomeLink;
|
|
image: string;
|
|
};
|
|
|
|
type ContactFormProps = ContactForm;
|
|
|
|
interface FooterLinksProps {
|
|
links?: FooterLink[];
|
|
}
|
|
|
|
interface FooterContactInfoProps {
|
|
items?: FooterLink[];
|
|
socialMediaLinks?: SocialLink[];
|
|
}
|
|
|
|
interface SocialMediaSectionProps {
|
|
links?: SocialLink[];
|
|
}
|
|
|
|
interface EcommerceFooter18Props extends FooterData {
|
|
className?: string;
|
|
}
|
|
|
|
const SOCIAL_ICONS = {
|
|
facebook: {
|
|
title: "Facebook",
|
|
light:
|
|
"/block-media/shadcnblocks/block/logos/facebook-icon.svg",
|
|
dark: "/block-media/shadcnblocks/block/logos/facebook-icon.svg",
|
|
},
|
|
x: {
|
|
title: "X",
|
|
light:
|
|
"/block-media/shadcnblocks/block/logos/x.svg",
|
|
dark: "/block-media/shadcnblocks/block/logos/x.svg",
|
|
},
|
|
instagram: {
|
|
title: "Instagram",
|
|
light:
|
|
"/block-media/shadcnblocks/block/logos/instagram-icon.svg",
|
|
dark: "/block-media/shadcnblocks/block/logos/instagram-icon.svg",
|
|
},
|
|
} as const;
|
|
|
|
const FOOTER_DATA: FooterData = {
|
|
homeLink: {
|
|
logo: {
|
|
light:
|
|
"/block-media/shadcnblocks/block/logos/ShadcnBlockscom-wordmark.svg",
|
|
dark: "/block-media/shadcnblocks/block/logos/ShadcnBlockscom-wordmark-white.svg",
|
|
},
|
|
link: "#",
|
|
},
|
|
sections: [
|
|
{
|
|
title: "تماس با ما",
|
|
id: "contact_us",
|
|
items: [
|
|
{
|
|
title: "پشتیبانی مشتریان",
|
|
text: "شنبه تا پنجشنبه، ۹ صبح تا ۷ شب",
|
|
},
|
|
{
|
|
title: "با ما تماس بگیرید",
|
|
text: "+1 855-987-4420 (رایگان)",
|
|
href: "tel:+18559874420",
|
|
},
|
|
{
|
|
title: "به ما ایمیل بزنید",
|
|
text: "support@lumiskin.co",
|
|
href: "mailto:support@lumiskin.co",
|
|
},
|
|
{
|
|
title: "آدرس",
|
|
text: "خیابان گلستان، پلاک ۱۲۵، واحد ۲۱۰، تهران",
|
|
},
|
|
],
|
|
socialMediaLinks: [
|
|
{
|
|
icon: SOCIAL_ICONS.facebook,
|
|
link: "#",
|
|
},
|
|
{
|
|
icon: SOCIAL_ICONS.x,
|
|
link: "#",
|
|
},
|
|
{
|
|
icon: SOCIAL_ICONS.instagram,
|
|
link: "#",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "فروشگاه",
|
|
id: "shop",
|
|
links: [
|
|
{
|
|
text: "محصولات جدید",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "پرفروشترینها",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "روتین پوست",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "هدیه و ستها",
|
|
href: "#",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "پشتیبانی",
|
|
id: "support",
|
|
links: [
|
|
{
|
|
text: "تماس با ما",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "سؤالات متداول",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "پیگیری سفارش",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "مرجوعی و تعویض",
|
|
href: "#",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "درباره ما",
|
|
id: "about",
|
|
links: [
|
|
{
|
|
text: "درباره ما",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "مواد تشکیلدهنده",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "پایداری",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "مطبوعات",
|
|
href: "#",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
contactForm: {
|
|
title: "جایی که مد روز با راحتی پیوند میخورد",
|
|
text: "لباسهایی طراحی میکنیم که به زنان کمک میکند با جزئیات دقیق، برشهای مناسب و پایههای باکیفیت، شخصیت خود را بیان کنند.",
|
|
},
|
|
submenu: [
|
|
{
|
|
text: "سیاست مرجوعی",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "شرایط استفاده",
|
|
href: "#",
|
|
},
|
|
{
|
|
text: "حریم خصوصی",
|
|
href: "#",
|
|
},
|
|
],
|
|
paymentMethods: [
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/amazonpay.svg",
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/applepay.svg",
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/mastercard.svg",
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/paypal.svg",
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/visa.svg",
|
|
"/block-media/shadcnblocks/block/ecommerce/payment-methods/discover.svg",
|
|
],
|
|
copyrightCaption: "© ۲۲۶ ساختهشده با ❤️ توسط ShadcnBlocks",
|
|
image:
|
|
"/block-media/shadcnblocks/block/ecommerce/promotional/Neatly-Organized-Clothing-Selection-2.png",
|
|
};
|
|
|
|
const EcommerceFooter18 = ({
|
|
className,
|
|
sections = FOOTER_DATA.sections,
|
|
contactForm = FOOTER_DATA.contactForm,
|
|
homeLink = FOOTER_DATA.homeLink,
|
|
paymentMethods = FOOTER_DATA.paymentMethods,
|
|
image = FOOTER_DATA.image,
|
|
copyrightCaption = FOOTER_DATA.copyrightCaption,
|
|
submenu = FOOTER_DATA.submenu,
|
|
}: EcommerceFooter18Props) => {
|
|
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
|
const allAccordionIds = sections.map(({ id }) => id);
|
|
|
|
const [activeAccordionItem, setActiveAccordionItem] =
|
|
useState<string[]>(allAccordionIds);
|
|
|
|
const handleOnChange = (ids: string[]) => {
|
|
setActiveAccordionItem(ids);
|
|
};
|
|
|
|
return (
|
|
<footer dir="rtl" lang="fa" className={cn("relative", className)}>
|
|
<div className="relative z-20 bg-background">
|
|
<div className="container">
|
|
<div className="pt-9 pb-12.5">
|
|
<div className="grid-cols-2 space-y-5 lg:grid lg:gap-x-5 lg:gap-y-5 xl:grid-cols-7">
|
|
<Accordion
|
|
value={activeAccordionItem}
|
|
disabled={isDesktop}
|
|
type="multiple"
|
|
onValueChange={handleOnChange}
|
|
className="lg:contents"
|
|
>
|
|
{sections.map(
|
|
({ title, links, items, id, socialMediaLinks }) => (
|
|
<AccordionItem
|
|
value={id}
|
|
key={id}
|
|
className="border-none xl:first:col-span-2"
|
|
>
|
|
<AccordionTrigger className="text-lg leading-snug font-semibold hover:no-underline disabled:opacity-100 lg:pt-0 lg:text-xl lg:[&>svg]:hidden">
|
|
{title}
|
|
</AccordionTrigger>
|
|
<AccordionContent>
|
|
<FooterLinks links={links} />
|
|
<FooterContactInfo
|
|
items={items}
|
|
socialMediaLinks={socialMediaLinks}
|
|
/>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
),
|
|
)}
|
|
</Accordion>
|
|
<div className="xl:col-span-2">
|
|
<ContactFormSection
|
|
title={contactForm.title}
|
|
text={contactForm.text}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 items-center justify-center gap-4 border-y py-4 lg:grid-cols-3">
|
|
<div className="max-lg:justify-self-center">
|
|
<Select defaultValue="farsi">
|
|
<SelectTrigger className="w-[180px]">
|
|
<SelectValue placeholder="انتخاب زبان" />
|
|
</SelectTrigger>
|
|
<SelectContent align="start">
|
|
<SelectGroup>
|
|
<SelectItem value="farsi">فارسی</SelectItem>
|
|
<SelectItem value="english">انگلیسی</SelectItem>
|
|
<SelectItem value="arabic">العربية</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="justify-self-center">
|
|
<a href={homeLink.link} className="inline-block w-full max-w-55">
|
|
<img
|
|
className="w-full dark:hidden"
|
|
src={homeLink.logo.light}
|
|
alt="لوگو"
|
|
/>
|
|
<img
|
|
className="hidden w-full dark:inline-block"
|
|
src={homeLink.logo.dark}
|
|
alt="لوگو"
|
|
/>
|
|
</a>
|
|
</div>
|
|
<div className="lg:justify-self-end">
|
|
<PaymentMethods cards={paymentMethods} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="relative z-20 rounded-b-xl bg-background py-4">
|
|
<div className="container flex flex-col items-center justify-between gap-2 lg:flex-row">
|
|
<p className="text-xs max-lg:text-center">{copyrightCaption}</p>
|
|
<FooterSubMenu links={submenu} />
|
|
</div>
|
|
</div>
|
|
<div className="sticky bottom-0 -mt-2.5 h-30 overflow-hidden lg:aspect-[8.458] lg:h-auto">
|
|
<img
|
|
src={image}
|
|
alt="تصویر فوتر"
|
|
className="block size-full object-cover object-center"
|
|
/>
|
|
</div>
|
|
</footer>
|
|
);
|
|
};
|
|
|
|
const newsletterFormSchema = z.object({
|
|
email: z
|
|
.string()
|
|
.min(1, "ایمیل الزامی است")
|
|
.email("آدرس ایمیل نامعتبر است"),
|
|
});
|
|
|
|
type newsletterFormType = z.infer<typeof newsletterFormSchema>;
|
|
|
|
const ContactFormSection = ({ title, text }: ContactFormProps) => {
|
|
const form = useForm({
|
|
resolver: zodResolver(newsletterFormSchema),
|
|
defaultValues: {
|
|
email: "",
|
|
},
|
|
});
|
|
|
|
const onSubmit = (data: newsletterFormType) => {
|
|
console.log(data);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
<div className="space-y-3">
|
|
<h3 className="text-lg leading-snug font-semibold lg:text-xl">
|
|
{title}
|
|
</h3>
|
|
<p className="text-sm leading-relaxed">{text}</p>
|
|
</div>
|
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
<div className="space-y-4">
|
|
<Controller
|
|
name="email"
|
|
control={form.control}
|
|
render={({ field, fieldState }) => (
|
|
<Field data-invalid={fieldState.invalid}>
|
|
<InputGroup>
|
|
<InputGroupInput
|
|
{...field}
|
|
aria-invalid={fieldState.invalid}
|
|
placeholder="آدرس ایمیل"
|
|
/>
|
|
<InputGroupAddon>
|
|
<Mail />
|
|
</InputGroupAddon>
|
|
</InputGroup>
|
|
{fieldState.invalid && (
|
|
<FieldError errors={[fieldState.error]} />
|
|
)}
|
|
</Field>
|
|
)}
|
|
/>
|
|
<Button variant="secondary">عضویت</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const FooterLinks = ({ links }: FooterLinksProps) => {
|
|
if (!links) return;
|
|
|
|
return (
|
|
<ul className="space-y-1.5">
|
|
{links.map(({ href, text }, index) => (
|
|
<li key={index}>
|
|
<a
|
|
href={href}
|
|
className="text-sm underline-offset-2 transition-all hover:underline hover:opacity-80"
|
|
>
|
|
{text}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
};
|
|
|
|
const FooterContactInfo = ({
|
|
items,
|
|
socialMediaLinks,
|
|
}: FooterContactInfoProps) => {
|
|
if (!items) return;
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<dl className="space-y-2.5 sm:space-y-1.5">
|
|
{items.map(({ text, href, title }, index) => (
|
|
<div
|
|
className="flex flex-col items-start gap-2 text-sm sm:flex-row"
|
|
key={index}
|
|
>
|
|
<dt>{title}:</dt>
|
|
{href ? (
|
|
<dd>
|
|
<a
|
|
className="break-all text-muted-foreground underline"
|
|
href={href}
|
|
>
|
|
{text}
|
|
</a>
|
|
</dd>
|
|
) : (
|
|
<dd>{text}</dd>
|
|
)}
|
|
</div>
|
|
))}
|
|
</dl>
|
|
<SocialMediaSection links={socialMediaLinks} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const PaymentMethods = ({ cards }: { cards?: string[] }) => {
|
|
if (!cards) return;
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<p className="text-xs font-light max-lg:text-center">روشهای پرداخت</p>
|
|
<ul className="flex flex-wrap items-center justify-center gap-3">
|
|
{cards.map((card) => (
|
|
<li key={crypto.randomUUID()}>
|
|
<img className="w-8" src={card} alt="روش پرداخت" />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const SocialMediaSection = ({ links }: SocialMediaSectionProps) => {
|
|
if (!links) return;
|
|
|
|
return (
|
|
<ul className="flex flex-wrap items-center gap-0.5">
|
|
{links.map(({ icon, link }) => (
|
|
<li key={crypto.randomUUID()}>
|
|
<Button size="icon-sm" asChild className="rounded-full">
|
|
<a href={link}>
|
|
<img
|
|
className="size-3.5 dark:hidden"
|
|
alt={icon.title}
|
|
src={icon.light}
|
|
/>
|
|
<img
|
|
className="hidden size-3.5 dark:block"
|
|
alt={icon.title}
|
|
src={icon.dark}
|
|
/>
|
|
</a>
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
};
|
|
|
|
const FooterSubMenu = ({ links }: { links?: FooterLink[] }) => {
|
|
if (!links) return;
|
|
|
|
return (
|
|
<ul className="flex flex-wrap gap-x-5 gap-y-1 max-lg:justify-center">
|
|
{links.map(({ href, text }) => (
|
|
<li key={crypto.randomUUID()}>
|
|
<a href={href} className="text-xs">
|
|
{text}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
};
|
|
|
|
export { EcommerceFooter18 };
|