chore: transfer repo
This commit is contained in:
56
components/home/CardSection.tsx
Normal file
56
components/home/CardSection.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { CustomCard } from "@/components/ui/CustomCard";
|
||||
import { Section } from "@/components/ui/Section";
|
||||
import { SectionHeader } from "@/components/ui/SectionHeader";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
|
||||
export function CardSection() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Card data from translations
|
||||
const cardsData = [
|
||||
{
|
||||
title: t('cardSection.cards.0.title'),
|
||||
description: t('cardSection.cards.0.description'),
|
||||
imageSrc: "/assets/images/card1.png",
|
||||
imageAlt: t('cardSection.cards.0.imageAlt')
|
||||
},
|
||||
{
|
||||
title: t('cardSection.cards.1.title'),
|
||||
description: t('cardSection.cards.1.description'),
|
||||
imageSrc: "/assets/images/card2.png",
|
||||
imageAlt: t('cardSection.cards.1.imageAlt')
|
||||
},
|
||||
{
|
||||
title: t('cardSection.cards.2.title'),
|
||||
description: t('cardSection.cards.2.description'),
|
||||
imageSrc: "/assets/images/card3.png",
|
||||
imageAlt: t('cardSection.cards.2.imageAlt')
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<div className="flex flex-col items-start">
|
||||
<SectionHeader
|
||||
title={t('cardSection.title')}
|
||||
description={t('cardSection.description')}
|
||||
className="mb-[60px]"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-full">
|
||||
{cardsData.map((card, index) => (
|
||||
<CustomCard
|
||||
key={index}
|
||||
title={card.title}
|
||||
description={card.description}
|
||||
imageSrc={card.imageSrc}
|
||||
imageAlt={card.imageAlt}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
107
components/home/CustomerReviews.tsx
Normal file
107
components/home/CustomerReviews.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import { Section } from "@/components/ui/Section";
|
||||
import { Heading, Text } from "@/components/ui/Typography";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
import { container } from "@/lib/utils";
|
||||
|
||||
// Define the review data structure
|
||||
interface Review {
|
||||
id: number;
|
||||
author: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export function CustomerReviews() {
|
||||
const { t, locale } = useTranslation();
|
||||
|
||||
// Definiraj reviewse po jeziku
|
||||
const reviews: Review[] = locale === 'en' ? [
|
||||
{
|
||||
id: 1,
|
||||
author: "Emily Johnson",
|
||||
comment: "\"The Build a Box feature made gift-giving so easy and special!\""
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author: "Michael Smith",
|
||||
comment: "\"Sent's service exceeded my expectations every time!\""
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
author: "Sarah Lee",
|
||||
comment: "\"The quality of the items was outstanding!\""
|
||||
}
|
||||
] : [
|
||||
{
|
||||
id: 1,
|
||||
author: "Ana Kovačić",
|
||||
comment: "\"Opcija 'Složi kutiju' učinila je darivanje tako jednostavnim i posebnim!\""
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author: "Marko Horvat",
|
||||
comment: "\"Usluga Sent-a nadmašila je moja očekivanja svaki put!\""
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
author: "Ivana Novak",
|
||||
comment: "\"Kvaliteta proizvoda bila je izvanredna!\""
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Section className="bg-white overflow-hidden">
|
||||
{/* Keep the heading inside container */}
|
||||
<div className={container}>
|
||||
<Heading level={2} className="text-center mb-[60px]">
|
||||
{t('customerReviews.title')}
|
||||
</Heading>
|
||||
|
||||
{/* Mobile: Full width scrollable container, Desktop: Grid */}
|
||||
<div className="md:hidden -mx-4">
|
||||
<div className="flex overflow-x-auto pb-6 px-4 snap-x no-scrollbar">
|
||||
{/* Add empty div at start to ensure space */}
|
||||
<div className="shrink-0 w-[5%]"></div>
|
||||
|
||||
{reviews.map((review) => (
|
||||
<div
|
||||
key={review.id}
|
||||
className="bg-secondary shrink-0 w-[85%] sm:w-[70%] h-[235px] p-8 flex flex-col snap-center mx-3"
|
||||
>
|
||||
<Text className="mb-6 flex-grow">
|
||||
{review.comment}
|
||||
</Text>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Text className="text-primary font-bold">{review.author}</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Add empty div at end to ensure space */}
|
||||
<div className="shrink-0 w-[5%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Original grid layout */}
|
||||
<div className="hidden md:grid md:grid-cols-3 gap-8">
|
||||
{reviews.map((review) => (
|
||||
<div
|
||||
key={review.id}
|
||||
className="bg-secondary h-[235px] p-8 flex flex-col"
|
||||
>
|
||||
<Text className="mb-6 flex-grow">
|
||||
{review.comment}
|
||||
</Text>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Text className="text-primary font-bold">{review.author}</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
73
components/home/GiftBoxBuilder.tsx
Normal file
73
components/home/GiftBoxBuilder.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Heading, Text } from "@/components/ui/Typography";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
import { container } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
|
||||
export function GiftBoxBuilder() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Top frame image */}
|
||||
<div className="w-full">
|
||||
<Image
|
||||
src="/assets/images/Frame4.png"
|
||||
alt=""
|
||||
width={1440}
|
||||
height={140}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main content with pink background */}
|
||||
<div className="bg-[#F58EA7]">
|
||||
<div className={`${container} py-[80px]`}>
|
||||
<div className="flex flex-col md:flex-row md:items-center">
|
||||
{/* Image container - takes up full width on mobile, half on desktop */}
|
||||
<div className="w-full md:w-1/2 h-[240px] md:h-[500px] relative mb-8 md:mb-0">
|
||||
<Image
|
||||
src="/assets/images/build-box.png"
|
||||
alt={t('giftBoxBuilder.altText')}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Text container - takes up full width on mobile, half on desktop */}
|
||||
<div className="w-full md:w-1/2 md:pl-20">
|
||||
<Heading level={2} className="mb-4">
|
||||
{t('giftBoxBuilder.title')}
|
||||
</Heading>
|
||||
<Text size="lg" className="mb-8">
|
||||
{t('giftBoxBuilder.description')}
|
||||
</Text>
|
||||
<Button
|
||||
href="/build-box"
|
||||
variant="filled"
|
||||
size="lg"
|
||||
fullWidthMobile
|
||||
>
|
||||
{t('giftBoxBuilder.button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom frame image */}
|
||||
<div className="w-full">
|
||||
<Image
|
||||
src="/assets/images/Frame5.png"
|
||||
alt=""
|
||||
width={1440}
|
||||
height={140}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
184
components/home/HeroCarousel.tsx
Normal file
184
components/home/HeroCarousel.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Carousel } from "@/components/ui/Carousel";
|
||||
import { Section } from "@/components/ui/Section";
|
||||
import { Heading, Text } from "@/components/ui/Typography";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
import { CarouselSlide } from "@/lib/types/carousel";
|
||||
import { container } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function HeroCarousel() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Carousel data with translations
|
||||
const carouselData: CarouselSlide[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: t('hero.title'),
|
||||
titleColored: t('hero.title_colored'),
|
||||
titleEnd: t('hero.title_end'),
|
||||
description: t('hero.description'),
|
||||
buttonText: t('hero.build_box_button'),
|
||||
buttonLink: "/build-box",
|
||||
imageSrc: "/assets/images/carousel1.png"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: t('hero.title'),
|
||||
titleColored: t('hero.title_colored'),
|
||||
titleEnd: t('hero.title_end'),
|
||||
description: t('hero.description'),
|
||||
buttonText: t('hero.build_box_button'),
|
||||
buttonLink: "/build-box",
|
||||
imageSrc: "/assets/images/carousel2.png"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: t('hero.title'),
|
||||
titleColored: t('hero.title_colored'),
|
||||
titleEnd: t('hero.title_end'),
|
||||
description: t('hero.description'),
|
||||
buttonText: t('hero.build_box_button'),
|
||||
buttonLink: "/build-box",
|
||||
imageSrc: "/assets/images/carousel1.png"
|
||||
}
|
||||
];
|
||||
|
||||
// Default slide to use as fallback
|
||||
const defaultSlide: CarouselSlide = {
|
||||
id: 0,
|
||||
title: t('hero.title'),
|
||||
titleColored: t('hero.title_colored'),
|
||||
titleEnd: t('hero.title_end'),
|
||||
description: t('hero.description'),
|
||||
buttonText: t('hero.build_box_button'),
|
||||
buttonLink: "/build-box",
|
||||
imageSrc: "/assets/images/image1.png"
|
||||
};
|
||||
|
||||
// Create mobile slide components - each image needs to be in a container div
|
||||
const mobileImageSlides = carouselData.map((slide, index) => (
|
||||
<div key={slide.id} className="relative w-full h-full">
|
||||
{/* Background gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#958C87] via-[#A9A19C] via-[#BDBAB3] via-[#C5C1C0] via-[#C7C1C1] via-[#C3BFBE] to-[#C4C0BF]"></div>
|
||||
|
||||
{/* Image container - right side with padding */}
|
||||
<div className="absolute right-0 top-0 h-full w-1/2">
|
||||
<div className="relative h-full py-8">
|
||||
<Image
|
||||
src={slide.imageSrc}
|
||||
alt={slide.title}
|
||||
fill
|
||||
sizes="50vw"
|
||||
className="object-contain"
|
||||
priority={index === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
// Create desktop slide components
|
||||
const desktopSlides = carouselData.map((slide, index) => (
|
||||
<div key={slide.id} className="w-full h-full">
|
||||
{/* Background gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#958C87] via-[#A9A19C] via-[#BDBAB3] via-[#C5C1C0] via-[#C7C1C1] via-[#C3BFBE] to-[#C4C0BF]"></div>
|
||||
|
||||
{/* Image container - positioned on the right with padding */}
|
||||
<div className="absolute right-0 top-0 h-full w-[46.3%]">
|
||||
<div className="relative h-full py-12">
|
||||
<Image
|
||||
src={slide.imageSrc}
|
||||
alt={slide.title}
|
||||
fill
|
||||
sizes="46.3vw"
|
||||
className="object-contain"
|
||||
priority={index === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Container for proper alignment */}
|
||||
<div className={`relative h-full ${container} flex items-end`}>
|
||||
{/* Text overlay */}
|
||||
<div className="pb-32 max-w-2xl">
|
||||
<Heading level={1} className="mb-4 text-black text-[40px] leading-[48px] md:text-[60px] md:leading-[76px] flex flex-col font-bold">
|
||||
<span>{slide.title}</span>
|
||||
<span className="text-primary">{slide.titleColored}</span>
|
||||
<span>{slide.titleEnd}</span>
|
||||
</Heading>
|
||||
<Text size="lg" className="mb-6 text-black text-[18px] leading-[28px]">
|
||||
{slide.description}
|
||||
</Text>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
href={slide.buttonLink}
|
||||
variant="filled"
|
||||
size="lg"
|
||||
>
|
||||
{slide.buttonText}
|
||||
</Button>
|
||||
<Button
|
||||
href="/products"
|
||||
variant="custom"
|
||||
size="lg"
|
||||
>
|
||||
{t('hero.ready_made_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<Section spacing="none" fullWidth={true}>
|
||||
<div className="w-full">
|
||||
{/* Mobile version */}
|
||||
<div className="md:hidden">
|
||||
{/* Image carousel */}
|
||||
<div className="relative w-full h-[350px] bg-gradient-to-r from-[#958C87] via-[#A9A19C] via-[#BDBAB3] via-[#C5C1C0] via-[#C7C1C1] via-[#C3BFBE] to-[#C4C0BF]">
|
||||
<Carousel
|
||||
slides={mobileImageSlides}
|
||||
interval={10000}
|
||||
indicatorsClassName="absolute bottom-4 left-0 right-0"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Text content below carousel on mobile */}
|
||||
<div className="p-6 bg-white">
|
||||
<Heading level={1} className="mb-3 text-[40px] leading-[48px] flex flex-col font-bold">
|
||||
<span>{carouselData[0]?.title || defaultSlide.title}</span>
|
||||
<span className="text-primary">{carouselData[0]?.titleColored || defaultSlide.titleColored}</span>
|
||||
<span>{carouselData[0]?.titleEnd || defaultSlide.titleEnd}</span>
|
||||
</Heading>
|
||||
<Text className="mb-6 text-[18px] leading-[28px]">
|
||||
{carouselData[0]?.description || defaultSlide.description}
|
||||
</Text>
|
||||
<Button
|
||||
href={carouselData[0]?.buttonLink || defaultSlide.buttonLink}
|
||||
variant="filled"
|
||||
size="lg"
|
||||
fullWidth
|
||||
>
|
||||
{carouselData[0]?.buttonText || defaultSlide.buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop version */}
|
||||
<div className="hidden md:block relative h-[600px]">
|
||||
<Carousel
|
||||
slides={desktopSlides}
|
||||
interval={10000}
|
||||
indicatorsClassName="absolute bottom-6 left-0 right-0"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
31
components/home/NewHomePage.tsx
Normal file
31
components/home/NewHomePage.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { CardSection } from "./CardSection";
|
||||
import { CustomerReviews } from "./CustomerReviews";
|
||||
import { GiftBoxBuilder } from "./GiftBoxBuilder";
|
||||
import HeroCarousel from "./HeroCarousel";
|
||||
import { ProductSliderWrapper } from "./ProductSliderWrapper";
|
||||
import { WoltDelivery } from "./WoltDelivery";
|
||||
|
||||
export default function NewHomePage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section with Carousel */}
|
||||
<HeroCarousel />
|
||||
|
||||
{/* Card Section */}
|
||||
<CardSection />
|
||||
|
||||
{/* Gift Box Builder Section */}
|
||||
<GiftBoxBuilder />
|
||||
|
||||
{/* Product Slider Section */}
|
||||
<ProductSliderWrapper />
|
||||
|
||||
{/* Customer Reviews Section */}
|
||||
<CustomerReviews />
|
||||
|
||||
{/* Wolt Delivery Section */}
|
||||
<WoltDelivery />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
107
components/home/ProductSlider.tsx
Normal file
107
components/home/ProductSlider.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import { getProductColors } from "@/components/products/utils/colorUtils";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { ProductCard } from "@/components/ui/ProductCard";
|
||||
import { cn } from "@/lib/utils";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { Product } from "lib/shopify/types";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FiChevronLeft, FiChevronRight } from "react-icons/fi";
|
||||
|
||||
interface ProductSliderProps {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A product slider component that displays products in a horizontal scrollable slider.
|
||||
* Uses the Embla Carousel library for the sliding functionality.
|
||||
*/
|
||||
export function ProductSlider({ products }: ProductSliderProps) {
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({
|
||||
align: "start",
|
||||
containScroll: false,
|
||||
loop: false,
|
||||
});
|
||||
|
||||
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
||||
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
||||
|
||||
const scrollPrev = useCallback(() => {
|
||||
if (emblaApi) emblaApi.scrollPrev();
|
||||
}, [emblaApi]);
|
||||
|
||||
const scrollNext = useCallback(() => {
|
||||
if (emblaApi) emblaApi.scrollNext();
|
||||
}, [emblaApi]);
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
if (!emblaApi) return;
|
||||
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
||||
setNextBtnEnabled(emblaApi.canScrollNext());
|
||||
}, [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return;
|
||||
onSelect();
|
||||
emblaApi.on("select", onSelect);
|
||||
emblaApi.on("reInit", onSelect);
|
||||
}, [emblaApi, onSelect]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="overflow-visible" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.id}
|
||||
className={cn(
|
||||
"w-[85%] min-[500px]:w-[90%] min-[700px]:w-[48.5%] min-[1100px]:w-[32%] min-w-0 flex-grow-0 flex-shrink-0 mr-[2%]",
|
||||
)}
|
||||
>
|
||||
<ProductCard
|
||||
title={product.title}
|
||||
variant={product.variants[0]?.title || ""}
|
||||
price={parseFloat(product.priceRange.maxVariantPrice.amount)}
|
||||
imageSrc={product.featuredImage?.url || "/assets/images/placeholder_image.svg"}
|
||||
slug={product.handle}
|
||||
product={product}
|
||||
colors={getProductColors(product)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Previous button */}
|
||||
<Button
|
||||
onClick={scrollPrev}
|
||||
disabled={!prevBtnEnabled}
|
||||
variant="default"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"absolute left-[-18px] top-1/2 transform -translate-y-1/2 z-10",
|
||||
"w-9 h-9 text-sm bg-white shadow-md border border-gray-200 rounded-full",
|
||||
!prevBtnEnabled && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<FiChevronLeft className="w-5 h-5" />
|
||||
</Button>
|
||||
|
||||
{/* Next button */}
|
||||
<Button
|
||||
onClick={scrollNext}
|
||||
disabled={!nextBtnEnabled}
|
||||
variant="default"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"absolute right-[-18px] top-1/2 transform -translate-y-1/2 z-10",
|
||||
"w-9 h-9 text-sm bg-white shadow-md border border-gray-200 rounded-full",
|
||||
!nextBtnEnabled && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<FiChevronRight className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
69
components/home/ProductSliderSection.tsx
Normal file
69
components/home/ProductSliderSection.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Section } from "@/components/ui/Section";
|
||||
import { Heading, Text } from "@/components/ui/Typography";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
import type { Product } from "lib/shopify/types";
|
||||
import { ProductSlider } from "./ProductSlider";
|
||||
|
||||
interface ProductSliderSectionProps {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Presentation component for the product slider section
|
||||
* Handles the layout and presentation aspects, but not data fetching
|
||||
*/
|
||||
export function ProductSliderSection({ products }: ProductSliderSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="overflow-x-hidden w-full">
|
||||
<Section spacing="medium">
|
||||
<div className="mb-8">
|
||||
{/* Container for header section with relative positioning */}
|
||||
<div className="relative">
|
||||
{/* Button positioned absolutely to the right */}
|
||||
<div className="hidden sm:block absolute right-0 bottom-[0px]">
|
||||
<Button
|
||||
href="/search"
|
||||
variant="custom"
|
||||
size="lg"
|
||||
fullWidthMobile={false}
|
||||
>
|
||||
{t('productSlider.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Title and description */}
|
||||
<div className="mb-4">
|
||||
<Heading level={2}>{t('productSlider.title')}</Heading>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="lg" className="mb-4 sm:mb-0">{t('productSlider.description')}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile full-width button */}
|
||||
<div className="sm:hidden mt-2">
|
||||
<Button
|
||||
href="/search"
|
||||
variant="custom"
|
||||
size="lg"
|
||||
fullWidthMobile
|
||||
>
|
||||
{t('productSlider.button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Carousel container s overflow da se vidi 4. proizvod */}
|
||||
<div className="relative w-full overflow-visible">
|
||||
<ProductSlider products={products} />
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
components/home/ProductSliderWrapper.tsx
Normal file
25
components/home/ProductSliderWrapper.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getCollectionProducts } from 'lib/shopify';
|
||||
import { ProductSliderSection } from './ProductSliderSection';
|
||||
|
||||
/**
|
||||
* Server component responsible for fetching product data
|
||||
*/
|
||||
export async function ProductSliderWrapper() {
|
||||
// Fetch products from Shopify
|
||||
// Try to fetch from a dedicated collection first, and if not available, fetch recent products
|
||||
const products = await getCollectionProducts({
|
||||
collection: 'hidden-homepage-carousel',
|
||||
sortKey: 'CREATED_AT',
|
||||
reverse: true
|
||||
}).catch(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
// If no products found, don't render anything
|
||||
if (!products || products.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render the slider section with fetched products
|
||||
return <ProductSliderSection products={products} />;
|
||||
}
|
||||
90
components/home/WoltDelivery.tsx
Normal file
90
components/home/WoltDelivery.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Heading, Text } from "@/components/ui/Typography";
|
||||
import { useTranslation } from "@/lib/hooks/useTranslation";
|
||||
import { container } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
|
||||
export function WoltDelivery() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Top wave */}
|
||||
<div className="w-full">
|
||||
<Image
|
||||
src="/assets/images/Frame3.png"
|
||||
alt=""
|
||||
width={1440}
|
||||
height={140}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main content with background color */}
|
||||
<div className="bg-wolt">
|
||||
<div className={`${container} py-[80px] relative`}>
|
||||
{/* Desktop Wolt logo - only visible on desktop */}
|
||||
<div className="absolute top-0 right-0 z-10 hidden md:block">
|
||||
<div className="w-[160px] h-[160px] bg-wolt-blue rounded-md flex items-center justify-center">
|
||||
<Image
|
||||
src="/assets/images/wolt.png"
|
||||
alt="Wolt"
|
||||
width={120}
|
||||
height={120}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text and image layout */}
|
||||
<div className="flex flex-col md:flex-row md:items-center">
|
||||
{/* Text container - comes first on both mobile and desktop */}
|
||||
<div className="w-full md:w-1/2 md:pr-12 order-1 mb-8 md:mb-0">
|
||||
<Heading level={2} className="mb-4">
|
||||
{t('woltDelivery.title')}
|
||||
</Heading>
|
||||
<Text size="lg" className="mb-8">
|
||||
{t('woltDelivery.description')}
|
||||
</Text>
|
||||
<Button
|
||||
href="https://wolt.com"
|
||||
external
|
||||
variant="filled"
|
||||
size="lg"
|
||||
fullWidthMobile
|
||||
>
|
||||
{t('woltDelivery.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Image container with mobile logo */}
|
||||
<div className="w-full md:w-1/2 h-[310px] md:h-[400px] relative order-2 mb-12 md:mb-0">
|
||||
<Image
|
||||
src="/assets/images/wolt-image.png"
|
||||
alt={t('woltDelivery.altText')}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
|
||||
{/* Mobile Wolt logo - positioned at bottom-right of image */}
|
||||
<div className="absolute bottom-[-40px] right-[-10px] z-10 md:hidden">
|
||||
<div className="w-[120px] h-[120px] bg-wolt-blue rounded-md flex items-center justify-center">
|
||||
<Image
|
||||
src="/assets/images/wolt.png"
|
||||
alt="Wolt"
|
||||
width={90}
|
||||
height={90}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user