feat: implement locale-aware routing with [locale] dynamic segments
Some checks failed
Build and Deploy / build (push) Has been cancelled
Some checks failed
Build and Deploy / build (push) Has been cancelled
WARNING: This change breaks existing SEO URLs for Serbian locale. Changes: - Migrated from separate locale folders (src/app/en/, src/app/de/, etc.) to [locale] dynamic segments (src/app/[locale]/) - Serbian is now at /sr/ instead of / (root) - English at /en/, German at /de/, French at /fr/ - All components updated to generate locale-aware links - Root / now redirects to /sr (307 temporary redirect) SEO Impact: - Previously indexed Serbian URLs (/, /products, /about, /contact) will now return 404 or redirect to /sr/* URLs - This is a breaking change for SEO - Serbian pages should ideally remain at root (/) with only non-default locales getting prefix - Consider implementing 301 redirects from old URLs to maintain search engine rankings Technical Notes: - next-intl v4 with [locale] structure requires ALL locales to have the prefix (cannot have default locale at root) - Alternative approach would be separate folder structure per locale
This commit is contained in:
@@ -5,6 +5,7 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronDown, Star, Minus, Plus } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { Product } from "@/types/saleor";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import { getProductPrice, getProductPriceAmount, getLocalizedProduct, formatPrice } from "@/lib/saleor";
|
||||
@@ -23,14 +24,13 @@ interface ProductDetailProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
// Expandable Section Component
|
||||
function ExpandableSection({
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
function ExpandableSection({
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
@@ -44,8 +44,8 @@ function ExpandableSection({
|
||||
<span className="text-sm uppercase tracking-[0.1em] font-medium">
|
||||
{title}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={`w-5 h-5 transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}
|
||||
<ChevronDown
|
||||
className={`w-5 h-5 transition-transform duration-300 ${isOpen ? 'rotate-180' : ''}`}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</button>
|
||||
@@ -68,7 +68,6 @@ function ExpandableSection({
|
||||
);
|
||||
}
|
||||
|
||||
// Star Rating Component
|
||||
function StarRating({ rating = 5, count = 0 }: { rating?: number; count?: number }) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -88,13 +87,14 @@ function StarRating({ rating = 5, count = 0 }: { rating?: number; count?: number
|
||||
}
|
||||
|
||||
export default function ProductDetail({ product, relatedProducts, locale = "SR" }: ProductDetailProps) {
|
||||
const t = useTranslations("ProductDetail");
|
||||
const tProduct = useTranslations("Product");
|
||||
const [selectedImage, setSelectedImage] = useState(0);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [urgencyIndex, setUrgencyIndex] = useState(0);
|
||||
const { addLine, openCart } = useSaleorCheckoutStore();
|
||||
|
||||
// Cycle through urgency messages
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setUrgencyIndex(prev => (prev + 1) % 3);
|
||||
@@ -103,22 +103,21 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
}, []);
|
||||
|
||||
const urgencyMessages = [
|
||||
{ icon: "🚀", text: "Hurry up! 500+ items sold in the last 3 days!" },
|
||||
{ icon: "🛒", text: "In the carts of 2.5K people - buy before its gone!" },
|
||||
{ icon: "👀", text: "7,562 people viewed this product in the last 24 hours!" },
|
||||
{ icon: "🚀", text: t("urgency1") },
|
||||
{ icon: "🛒", text: t("urgency2") },
|
||||
{ icon: "👀", text: t("urgency3") },
|
||||
];
|
||||
|
||||
const localized = getLocalizedProduct(product, locale);
|
||||
const variant = product.variants?.[0];
|
||||
|
||||
// Get all images from media
|
||||
const images = product.media?.length > 0
|
||||
|
||||
const images = product.media?.length > 0
|
||||
? product.media.filter(m => m.type === "IMAGE")
|
||||
: [{ id: "0", url: "/placeholder-product.jpg", alt: localized.name, type: "IMAGE" as const }];
|
||||
|
||||
const handleAddToCart = async () => {
|
||||
if (!variant?.id) return;
|
||||
|
||||
|
||||
setIsAdding(true);
|
||||
try {
|
||||
await addLine(variant.id, quantity);
|
||||
@@ -132,13 +131,11 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
const price = getProductPrice(product);
|
||||
const priceAmount = getProductPriceAmount(product);
|
||||
const originalPrice = priceAmount > 0 ? formatPrice(Math.round(priceAmount * 1.30)) : null;
|
||||
|
||||
// Extract short description (first sentence or first 100 chars)
|
||||
const shortDescription = localized.description
|
||||
|
||||
const shortDescription = localized.description
|
||||
? localized.description.split('.')[0] + '.'
|
||||
: locale === "EN" ? "Premium natural oil for your beauty routine." : "Premium prirodno ulje za vašu rutinu lepote.";
|
||||
|
||||
// Parse benefits from product metadata or use defaults
|
||||
const benefits = product.metadata?.find(m => m.key === "benefits")?.value?.split(',') || [
|
||||
locale === "EN" ? "Natural" : "Prirodno",
|
||||
locale === "EN" ? "Organic" : "Organsko",
|
||||
@@ -148,12 +145,11 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
return (
|
||||
<>
|
||||
<section className="min-h-screen" id="product-detail">
|
||||
{/* Breadcrumb - with proper top padding for fixed header */}
|
||||
<div className="border-b border-[#e5e5e5] pt-[72px] lg:pt-[72px]">
|
||||
<div className="container py-5">
|
||||
<nav className="flex items-center gap-2 text-sm">
|
||||
<Link href="/" className="text-[#666666] hover:text-black transition-colors">
|
||||
{locale === "EN" ? "Home" : "Početna"}
|
||||
<Link href={`/${locale.toLowerCase()}`} className="text-[#666666] hover:text-black transition-colors">
|
||||
{t("home")}
|
||||
</Link>
|
||||
<span className="text-[#999999]">/</span>
|
||||
<span className="text-[#1a1a1a]">{localized.name}</span>
|
||||
@@ -161,17 +157,14 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Content */}
|
||||
<div className="container py-12 lg:py-16">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20">
|
||||
{/* Image Gallery - Left Side */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="flex flex-col md:flex-row gap-4"
|
||||
>
|
||||
{/* Thumbnails - Vertical on Desktop, Hidden on Mobile */}
|
||||
{images.length > 1 && (
|
||||
<div className="hidden md:flex flex-col gap-3 w-20 flex-shrink-0">
|
||||
{images.map((image, index) => (
|
||||
@@ -179,8 +172,8 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
key={image.id}
|
||||
onClick={() => setSelectedImage(index)}
|
||||
className={`relative aspect-square w-full overflow-hidden border-2 transition-colors ${
|
||||
selectedImage === index
|
||||
? "border-black"
|
||||
selectedImage === index
|
||||
? "border-black"
|
||||
: "border-transparent hover:border-[#999999]"
|
||||
}`}
|
||||
>
|
||||
@@ -194,18 +187,15 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Image */}
|
||||
<div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden flex-1">
|
||||
<img
|
||||
src={images[selectedImage].url}
|
||||
alt={images[selectedImage].alt || localized.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Carousel Navigation - Mobile Only */}
|
||||
|
||||
{images.length > 1 && (
|
||||
<>
|
||||
{/* Left Arrow */}
|
||||
<button
|
||||
onClick={() => setSelectedImage(prev => prev === 0 ? images.length - 1 : prev - 1)}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 hover:bg-white rounded-full flex items-center justify-center shadow-md transition-all hover:scale-110 md:hidden"
|
||||
@@ -215,8 +205,7 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Right Arrow */}
|
||||
|
||||
<button
|
||||
onClick={() => setSelectedImage(prev => prev === images.length - 1 ? 0 : prev + 1)}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 hover:bg-white rounded-full flex items-center justify-center shadow-md transition-all hover:scale-110 md:hidden"
|
||||
@@ -226,8 +215,7 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dot Indicators - Mobile Only */}
|
||||
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 md:hidden">
|
||||
{images.map((_, index) => (
|
||||
<button
|
||||
@@ -245,14 +233,12 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Product Info - Right Side */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="lg:pl-8"
|
||||
>
|
||||
{/* Urgency Sales Banner */}
|
||||
<motion.div
|
||||
key={urgencyIndex}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
@@ -265,26 +251,22 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
{urgencyMessages[urgencyIndex].text}
|
||||
</motion.div>
|
||||
|
||||
{/* Product Name */}
|
||||
<h1 className="text-3xl md:text-4xl font-medium mb-4 tracking-tight">
|
||||
{localized.name}
|
||||
</h1>
|
||||
|
||||
{/* Short Description */}
|
||||
<p className="text-[#666666] leading-relaxed mb-4">
|
||||
{shortDescription}
|
||||
</p>
|
||||
|
||||
{/* Stock Warning - Static */}
|
||||
<div className="flex items-center justify-start gap-2 mb-6">
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
|
||||
</span>
|
||||
<span className="text-red-600 text-sm font-medium">Stocks are running out!</span>
|
||||
<span className="text-red-600 text-sm font-medium">{t("stocksRunningOut")}</span>
|
||||
</div>
|
||||
|
||||
{/* Discount Price Display */}
|
||||
{originalPrice && priceAmount > 0 && (
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
@@ -301,25 +283,22 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Price & Rating */}
|
||||
{!originalPrice && (
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<span className="text-3xl font-medium">
|
||||
{price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")}
|
||||
{price || tProduct("outOfStock")}
|
||||
</span>
|
||||
<StarRating rating={5} count={1000} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-[#e5e5e5] mb-8" />
|
||||
|
||||
{/* Size Selector */}
|
||||
{product.variants && product.variants.length > 1 && (
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm uppercase tracking-[0.1em] font-medium">
|
||||
{locale === "EN" ? "Size" : "Veličina"}
|
||||
{t("size")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
@@ -339,10 +318,9 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quantity */}
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<span className="text-sm uppercase tracking-[0.1em] font-medium w-16">
|
||||
{locale === "EN" ? "Qty" : "Kol"}
|
||||
{t("qty")}
|
||||
</span>
|
||||
<div className="flex items-center border-2 border-[#1a1a1a]">
|
||||
<button
|
||||
@@ -362,44 +340,39 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add to Cart Button - Action verb + value */}
|
||||
{isAvailable ? (
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
disabled={isAdding}
|
||||
className="w-full h-16 bg-black text-white text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-[#333333] active:bg-[#1a1a1a] transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed mb-6 hover:scale-[1.02] shadow-lg hover:shadow-xl"
|
||||
>
|
||||
{isAdding
|
||||
? (locale === "EN" ? "Adding..." : "Dodavanje...")
|
||||
: (locale === "EN" ? "Transform My Hair & Skin" : "Transformiši kosu i kožu")
|
||||
{isAdding
|
||||
? t("adding")
|
||||
: t("transformHairSkin")
|
||||
}
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-full h-16 bg-[#f8f9fa] text-[#666666] flex items-center justify-center text-base uppercase tracking-[0.15em] mb-8">
|
||||
{locale === "EN" ? "Out of Stock" : "Nema na stanju"}
|
||||
{t("outOfStock")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Free Shipping Note - with urgency */}
|
||||
<div className="flex items-center justify-center gap-2 mb-6">
|
||||
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
|
||||
</svg>
|
||||
<p className="text-sm text-[#666666]">
|
||||
{locale === "EN"
|
||||
? "Free shipping on orders over 3,000 RSD"
|
||||
: "Besplatna dostava za porudžbine preko 3.000 RSD"}
|
||||
{t("freeShipping")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Trust Indicators */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-8 p-4 bg-[#f8f9fa] rounded-lg">
|
||||
<div className="text-center">
|
||||
<svg className="w-6 h-6 mx-auto mb-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
<p className="text-xs text-[#666666]">
|
||||
{locale === "EN" ? "30-Day Guarantee" : "30-dnevna garancija"}
|
||||
{t("guarantee")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
@@ -407,7 +380,7 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
<p className="text-xs text-[#666666]">
|
||||
{locale === "EN" ? "Secure Checkout" : "Sigurno plaćanje"}
|
||||
{t("secureCheckout")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
@@ -415,24 +388,22 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-8m15.357 8H15" />
|
||||
</svg>
|
||||
<p className="text-xs text-[#666666]">
|
||||
{locale === "EN" ? "Easy Returns" : "Lak povrat"}
|
||||
{t("easyReturns")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-[#e5e5e5] mb-8" />
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm uppercase tracking-[0.1em] font-medium">
|
||||
{locale === "EN" ? "Benefits" : "Prednosti"}
|
||||
{t("benefits")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{benefits.map((benefit, index) => (
|
||||
<span
|
||||
<span
|
||||
key={index}
|
||||
className="px-4 py-2 text-sm border border-[#e5e5e5] text-[#666666]"
|
||||
>
|
||||
@@ -442,32 +413,20 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Sections */}
|
||||
<div>
|
||||
<ExpandableSection title={locale === "EN" ? "Description" : "Opis"}>
|
||||
<ExpandableSection title={t("description")}>
|
||||
<div dangerouslySetInnerHTML={{ __html: localized.description }} />
|
||||
</ExpandableSection>
|
||||
|
||||
<ExpandableSection title={locale === "EN" ? "How to Use" : "Kako koristiti"}>
|
||||
<p>
|
||||
{locale === "EN"
|
||||
? "Apply a small amount to clean, damp hair or skin. Massage gently until absorbed. Use daily for best results."
|
||||
: "Nanesite malu količinu na čistu, vlažnu kosu ili kožu. Nežno masirajte dok se ne upije. Koristite svakodnevno za najbolje rezultate."
|
||||
}
|
||||
</p>
|
||||
|
||||
<ExpandableSection title={t("howToUse")}>
|
||||
<p>{t("howToUseText")}</p>
|
||||
</ExpandableSection>
|
||||
|
||||
<ExpandableSection title={locale === "EN" ? "Ingredients" : "Sastojci"}>
|
||||
<p>
|
||||
{locale === "EN"
|
||||
? "100% Pure Natural Oil. No additives, preservatives, or artificial fragrances."
|
||||
: "100% čisto prirodno ulje. Bez dodataka, konzervansa ili veštačkih mirisa."
|
||||
}
|
||||
</p>
|
||||
|
||||
<ExpandableSection title={t("ingredients")}>
|
||||
<p>{t("ingredientsText")}</p>
|
||||
</ExpandableSection>
|
||||
</div>
|
||||
|
||||
{/* SKU */}
|
||||
{variant?.sku && (
|
||||
<p className="text-xs text-[#999999] mt-8">
|
||||
SKU: {variant.sku}
|
||||
@@ -478,32 +437,28 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Customer Reviews */}
|
||||
<ProductReviews locale={locale} productName={localized.name} />
|
||||
|
||||
{/* As Featured In - Full Width */}
|
||||
<AsSeenIn />
|
||||
|
||||
{/* Before/After Gallery */}
|
||||
<BeforeAfterGallery />
|
||||
|
||||
{/* Related Products */}
|
||||
{relatedProducts && relatedProducts.length > 0 && (
|
||||
<section className="py-20 lg:py-28 bg-[#f8f9fa]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
{locale === "EN" ? "You May Also Like" : "Možda će vam se svideti"}
|
||||
{t("youMayAlsoLike")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium">
|
||||
{locale === "EN" ? "Similar Products" : "Slični proizvodi"}
|
||||
{t("similarProducts")}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-6 lg:gap-8">
|
||||
{relatedProducts.filter(p => p && p.id).slice(0, 4).map((relatedProduct, index) => (
|
||||
<div key={relatedProduct.id} className="w-full sm:w-[calc(50%-12px)] lg:w-[calc(25%-18px)]">
|
||||
<ProductCard
|
||||
product={relatedProduct}
|
||||
<ProductCard
|
||||
product={relatedProduct}
|
||||
index={index}
|
||||
locale={locale}
|
||||
/>
|
||||
@@ -514,17 +469,13 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Product Benefits */}
|
||||
<ProductBenefits locale={locale} />
|
||||
|
||||
{/* Trust Badges */}
|
||||
<TrustBadges />
|
||||
|
||||
{/* How It Works */}
|
||||
<HowItWorks />
|
||||
|
||||
{/* Newsletter */}
|
||||
<NewsletterSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user