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:
@@ -3,7 +3,6 @@ import { routing } from "./src/i18n/routing";
|
||||
|
||||
export default createMiddleware({
|
||||
...routing,
|
||||
localePrefix: "as-needed",
|
||||
});
|
||||
|
||||
export const config = {
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export async function generateMetadata() {
|
||||
interface AboutPageProps {
|
||||
params: Promise<{ locale: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: AboutPageProps) {
|
||||
const { locale } = await params;
|
||||
return {
|
||||
title: "O nama - ManoonOils",
|
||||
description: "Saznajte više o ManoonOils - naša priča, misija i posvećenost prirodnoj lepoti.",
|
||||
title: locale === "sr"
|
||||
? "O nama - ManoonOils"
|
||||
: "About - ManoonOils",
|
||||
description: locale === "sr"
|
||||
? "Saznajte više o ManoonOils - naša priča, misija i posvećenost prirodnoj lepoti."
|
||||
: "Learn more about ManoonOils - our story, mission, and commitment to natural beauty.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function AboutPage() {
|
||||
export default async function AboutPage({ params }: AboutPageProps) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations("About");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
@@ -32,7 +43,7 @@ export default async function AboutPage() {
|
||||
<div className="relative h-[400px] md:h-[500px] overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2000&auto=format&fit=crop"
|
||||
alt="Proizvodnja prirodnih ulja"
|
||||
alt={locale === "sr" ? "Proizvodnja prirodnih ulja" : "Natural oils production"}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
@@ -110,8 +121,8 @@ export default async function AboutPage() {
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
384
src/app/[locale]/checkout/page.tsx
Normal file
384
src/app/[locale]/checkout/page.tsx
Normal file
@@ -0,0 +1,384 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import { formatPrice } from "@/lib/saleor";
|
||||
import { saleorClient } from "@/lib/saleor/client";
|
||||
import {
|
||||
CHECKOUT_SHIPPING_ADDRESS_UPDATE,
|
||||
CHECKOUT_BILLING_ADDRESS_UPDATE,
|
||||
CHECKOUT_COMPLETE,
|
||||
} from "@/lib/saleor/mutations/Checkout";
|
||||
import type { Checkout } from "@/types/saleor";
|
||||
|
||||
interface ShippingAddressUpdateResponse {
|
||||
checkoutShippingAddressUpdate?: {
|
||||
checkout?: Checkout;
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface BillingAddressUpdateResponse {
|
||||
checkoutBillingAddressUpdate?: {
|
||||
checkout?: Checkout;
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface CheckoutCompleteResponse {
|
||||
checkoutComplete?: {
|
||||
order?: { number: string };
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddressForm {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
streetAddress1: string;
|
||||
streetAddress2: string;
|
||||
city: string;
|
||||
postalCode: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const t = useTranslations("Checkout");
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const { checkout, refreshCheckout, getLines, getTotal } = useSaleorCheckoutStore();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [orderComplete, setOrderComplete] = useState(false);
|
||||
const [orderNumber, setOrderNumber] = useState<string | null>(null);
|
||||
|
||||
const [sameAsShipping, setSameAsShipping] = useState(true);
|
||||
const [shippingAddress, setShippingAddress] = useState<AddressForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
streetAddress1: "",
|
||||
streetAddress2: "",
|
||||
city: "",
|
||||
postalCode: "",
|
||||
phone: "",
|
||||
});
|
||||
const [billingAddress, setBillingAddress] = useState<AddressForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
streetAddress1: "",
|
||||
streetAddress2: "",
|
||||
city: "",
|
||||
postalCode: "",
|
||||
phone: "",
|
||||
});
|
||||
|
||||
const lines = getLines();
|
||||
const total = getTotal();
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkout) {
|
||||
refreshCheckout();
|
||||
}
|
||||
}, [checkout, refreshCheckout]);
|
||||
|
||||
const handleShippingChange = (field: keyof AddressForm, value: string) => {
|
||||
setShippingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
if (sameAsShipping) {
|
||||
setBillingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBillingChange = (field: keyof AddressForm, value: string) => {
|
||||
setBillingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!checkout) {
|
||||
setError(t("errorNoCheckout"));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const shippingResult = await saleorClient.mutate<ShippingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_SHIPPING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
shippingAddress: {
|
||||
...shippingAddress,
|
||||
country: "RS",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (shippingResult.data?.checkoutShippingAddressUpdate?.errors && shippingResult.data.checkoutShippingAddressUpdate.errors.length > 0) {
|
||||
throw new Error(shippingResult.data.checkoutShippingAddressUpdate.errors[0].message);
|
||||
}
|
||||
|
||||
const billingResult = await saleorClient.mutate<BillingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_BILLING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
billingAddress: {
|
||||
...billingAddress,
|
||||
country: "RS",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (billingResult.data?.checkoutBillingAddressUpdate?.errors && billingResult.data.checkoutBillingAddressUpdate.errors.length > 0) {
|
||||
throw new Error(billingResult.data.checkoutBillingAddressUpdate.errors[0].message);
|
||||
}
|
||||
|
||||
const completeResult = await saleorClient.mutate<CheckoutCompleteResponse>({
|
||||
mutation: CHECKOUT_COMPLETE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (completeResult.data?.checkoutComplete?.errors && completeResult.data.checkoutComplete.errors.length > 0) {
|
||||
throw new Error(completeResult.data.checkoutComplete.errors[0].message);
|
||||
}
|
||||
|
||||
const order = completeResult.data?.checkoutComplete?.order;
|
||||
if (order) {
|
||||
setOrderNumber(order.number);
|
||||
setOrderComplete(true);
|
||||
} else {
|
||||
throw new Error(t("errorCreatingOrder"));
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : null;
|
||||
setError(errorMessage || t("errorOccurred"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (orderComplete) {
|
||||
return (
|
||||
<>
|
||||
<Header locale={locale} />
|
||||
<main className="min-h-screen">
|
||||
<section className="pt-[120px] pb-20 px-4">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<div className="mb-6">
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-3xl font-serif mb-2">{t("orderConfirmed")}</h1>
|
||||
<p className="text-foreground-muted">{t("thankYou")}</p>
|
||||
</div>
|
||||
|
||||
{orderNumber && (
|
||||
<div className="bg-background-ice p-6 rounded-lg mb-6">
|
||||
<p className="text-sm text-foreground-muted mb-1">{t("orderNumber")}</p>
|
||||
<p className="text-2xl font-serif">#{orderNumber}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-foreground-muted mb-8">
|
||||
{t("confirmationEmail")}
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href={`/${locale}/products`}
|
||||
className="inline-block px-8 py-3 bg-foreground text-white hover:bg-accent-dark transition-colors"
|
||||
>
|
||||
{t("continueShoppingBtn")}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen">
|
||||
<section className="pt-[120px] pb-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-3xl font-serif mb-8">{t("checkout")}</h1>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-600 p-4 mb-6 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="border-b border-border pb-6">
|
||||
<h2 className="text-xl font-serif mb-4">{t("shippingAddress")}</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("firstName")}</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.firstName}
|
||||
onChange={(e) => handleShippingChange("firstName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("lastName")}</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.lastName}
|
||||
onChange={(e) => handleShippingChange("lastName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">{t("streetAddress")}</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.streetAddress1}
|
||||
onChange={(e) => handleShippingChange("streetAddress1", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
value={shippingAddress.streetAddress2}
|
||||
onChange={(e) => handleShippingChange("streetAddress2", e.target.value)}
|
||||
placeholder={t("streetAddressOptional")}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("city")}</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.city}
|
||||
onChange={(e) => handleShippingChange("city", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("postalCode")}</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.postalCode}
|
||||
onChange={(e) => handleShippingChange("postalCode", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">{t("phone")}</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={shippingAddress.phone}
|
||||
onChange={(e) => handleShippingChange("phone", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-border pb-6">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sameAsShipping}
|
||||
onChange={(e) => setSameAsShipping(e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span>{t("billingAddressSame")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || lines.length === 0}
|
||||
className="w-full py-4 bg-foreground text-white font-medium hover:bg-accent-dark transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? t("processing") : t("completeOrder", { total: formatPrice(total) })}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="bg-background-ice p-6 rounded-lg h-fit">
|
||||
<h2 className="text-xl font-serif mb-6">{t("orderSummary")}</h2>
|
||||
|
||||
{lines.length === 0 ? (
|
||||
<p className="text-foreground-muted">{t("yourCartEmpty")}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-4 mb-6">
|
||||
{lines.map((line) => (
|
||||
<div key={line.id} className="flex gap-4">
|
||||
<div className="w-16 h-16 bg-white relative flex-shrink-0">
|
||||
{line.variant.product.media[0]?.url && (
|
||||
<Image
|
||||
src={line.variant.product.media[0].url}
|
||||
alt={line.variant.product.name}
|
||||
fill
|
||||
sizes="64px"
|
||||
className="object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-sm">{line.variant.product.name}</h3>
|
||||
<p className="text-foreground-muted text-sm">
|
||||
{t("qty")}: {line.quantity}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{formatPrice(line.totalPrice.gross.amount)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border pt-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-muted">{t("subtotal")}</span>
|
||||
<span>{formatPrice(checkout?.subtotalPrice?.gross?.amount || 0)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between font-medium text-lg pt-2 border-t border-border">
|
||||
<span>{t("total")}</span>
|
||||
<span>{formatPrice(total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { Mail, MapPin, Truck, Check } from "lucide-react";
|
||||
|
||||
export default function ContactPage() {
|
||||
const t = useTranslations("Contact");
|
||||
const locale = useLocale();
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@@ -22,7 +23,7 @@ export default function ContactPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
@@ -184,8 +185,8 @@ export default function ContactPage() {
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/app/[locale]/layout.tsx
Normal file
25
src/app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||
import { routing } from "@/i18n/routing";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
@@ -11,20 +11,29 @@ import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
|
||||
import ProblemSection from "@/components/home/ProblemSection";
|
||||
import HowItWorks from "@/components/home/HowItWorks";
|
||||
|
||||
export async function generateMetadata() {
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
return {
|
||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
description: "Discover our premium collection of natural oils for hair and skin care.",
|
||||
title: locale === "sr"
|
||||
? "ManoonOils - Premium prirodna ulja za negu kose i kože"
|
||||
: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
description: locale === "sr"
|
||||
? "Otkrijte našu premium kolekciju prirodnih ulja za negu kose i kože."
|
||||
: "Discover our premium collection of natural oils for hair and skin care.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function EnglishHomepage() {
|
||||
export default async function Homepage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations("Home");
|
||||
const tBenefits = await getTranslations("Benefits");
|
||||
|
||||
|
||||
const productLocale = locale === "sr" ? "SR" : "EN";
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts("UK");
|
||||
products = await getProducts(productLocale);
|
||||
} catch (e) {
|
||||
console.log("Failed to fetch products during build");
|
||||
}
|
||||
@@ -32,12 +41,14 @@ export default async function EnglishHomepage() {
|
||||
const featuredProducts = products?.slice(0, 4) || [];
|
||||
const hasProducts = featuredProducts.length > 0;
|
||||
|
||||
const basePath = `/${locale}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<HeroVideo />
|
||||
<HeroVideo locale={locale} />
|
||||
|
||||
<AsSeenIn />
|
||||
|
||||
@@ -67,13 +78,13 @@ export default async function EnglishHomepage() {
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{featuredProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="EN" />
|
||||
<ProductCard key={product.id} product={product} index={index} locale={productLocale} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<a
|
||||
href="/en/products"
|
||||
href={`${basePath}/products`}
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
{t("viewAll")}
|
||||
@@ -102,7 +113,7 @@ export default async function EnglishHomepage() {
|
||||
{t("storyText2")}
|
||||
</p>
|
||||
<a
|
||||
href="/en/about"
|
||||
href={`${basePath}/about`}
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
{t("learnMore")}
|
||||
@@ -111,7 +122,7 @@ export default async function EnglishHomepage() {
|
||||
<div className="relative aspect-[4/3] bg-[#e8f0f5] rounded-lg overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Natural oils production"
|
||||
alt={locale === "sr" ? "Proizvodnja prirodnih ulja" : "Natural oils production"}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
@@ -208,7 +219,7 @@ export default async function EnglishHomepage() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<Footer locale={locale} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,44 @@
|
||||
import { getProductBySlug, getProducts, getLocalizedProduct } from "@/lib/saleor";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductDetail from "@/components/product/ProductDetail";
|
||||
import type { Product } from "@/types/saleor";
|
||||
import { routing } from "@/i18n/routing";
|
||||
|
||||
interface ProductPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
params: Promise<{ locale: string; slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts("SR", 100);
|
||||
const params: Array<{ slug: string }> = [];
|
||||
|
||||
products.forEach((product: Product) => {
|
||||
params.push({ slug: product.slug });
|
||||
});
|
||||
|
||||
return params;
|
||||
} catch (e) {
|
||||
return [];
|
||||
const locales = routing.locales;
|
||||
const params: Array<{ locale: string; slug: string }> = [];
|
||||
|
||||
for (const locale of locales) {
|
||||
try {
|
||||
const productLocale = locale === "sr" ? "SR" : "EN";
|
||||
const products = await getProducts(productLocale, 100);
|
||||
products.forEach((product: Product) => {
|
||||
params.push({ locale, slug: product.slug });
|
||||
});
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "SR");
|
||||
const { locale, slug } = await params;
|
||||
const productLocale = locale === "sr" ? "SR" : "EN";
|
||||
const product = await getProductBySlug(slug, productLocale);
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
title: "Proizvod nije pronađen",
|
||||
title: locale === "sr" ? "Proizvod nije pronađen" : "Product not found",
|
||||
};
|
||||
}
|
||||
|
||||
const localized = getLocalizedProduct(product, "SR");
|
||||
const localized = getLocalizedProduct(product, productLocale);
|
||||
|
||||
return {
|
||||
title: localized.name,
|
||||
@@ -43,14 +47,18 @@ export async function generateMetadata({ params }: ProductPageProps) {
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const { locale, slug } = await params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations("Product");
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "SR");
|
||||
const productLocale = locale === "sr" ? "SR" : "EN";
|
||||
const product = await getProductBySlug(slug, productLocale);
|
||||
|
||||
const basePath = locale === "sr" ? "" : `/${locale}`;
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[180px] lg:pt-[200px] pb-20 text-center px-4">
|
||||
<h1 className="text-2xl font-medium mb-4">
|
||||
@@ -60,21 +68,21 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
{t("notFoundDesc")}
|
||||
</p>
|
||||
<a
|
||||
href="/products"
|
||||
href={`${basePath}/products`}
|
||||
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
{t("browseProducts")}
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
<Footer locale={locale} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let relatedProducts: Product[] = [];
|
||||
try {
|
||||
const allProducts = await getProducts("SR", 8);
|
||||
const allProducts = await getProducts(productLocale, 8);
|
||||
relatedProducts = allProducts
|
||||
.filter((p: Product) => p.id !== product.id)
|
||||
.slice(0, 4);
|
||||
@@ -82,15 +90,15 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
<main className="min-h-screen bg-white">
|
||||
<ProductDetail
|
||||
product={product}
|
||||
relatedProducts={relatedProducts}
|
||||
locale="SR"
|
||||
locale={productLocale}
|
||||
/>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,36 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export async function generateMetadata() {
|
||||
interface ProductsPageProps {
|
||||
params: Promise<{ locale: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductsPageProps) {
|
||||
const { locale } = await params;
|
||||
return {
|
||||
title: "Proizvodi - ManoonOils",
|
||||
description: "Pregledajte našu kolekciju premium prirodnih ulja za negu kose i kože.",
|
||||
title: locale === "sr"
|
||||
? "Proizvodi - ManoonOils"
|
||||
: "Products - ManoonOils",
|
||||
description: locale === "sr"
|
||||
? "Pregledajte našu kolekciju premium prirodnih ulja za negu kose i kože."
|
||||
: "Browse our collection of premium natural oils for hair and skin care.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductsPage() {
|
||||
export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations("Products");
|
||||
const products = await getProducts("SR");
|
||||
const productLocale = locale === "sr" ? "SR" : "EN";
|
||||
const products = await getProducts(productLocale);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Header locale={locale} />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[72px] lg:pt-[72px]">
|
||||
@@ -73,7 +85,7 @@ export default async function ProductsPage() {
|
||||
key={product.id}
|
||||
product={product}
|
||||
index={index}
|
||||
locale="SR"
|
||||
locale={productLocale}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -84,8 +96,8 @@ export default async function ProductsPage() {
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,495 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import { formatPrice } from "@/lib/saleor";
|
||||
import { saleorClient } from "@/lib/saleor/client";
|
||||
import {
|
||||
CHECKOUT_SHIPPING_ADDRESS_UPDATE,
|
||||
CHECKOUT_BILLING_ADDRESS_UPDATE,
|
||||
CHECKOUT_COMPLETE,
|
||||
} from "@/lib/saleor/mutations/Checkout";
|
||||
import type { Checkout } from "@/types/saleor";
|
||||
|
||||
// GraphQL Response Types
|
||||
interface ShippingAddressUpdateResponse {
|
||||
checkoutShippingAddressUpdate?: {
|
||||
checkout?: Checkout;
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface BillingAddressUpdateResponse {
|
||||
checkoutBillingAddressUpdate?: {
|
||||
checkout?: Checkout;
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface CheckoutCompleteResponse {
|
||||
checkoutComplete?: {
|
||||
order?: { number: string };
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddressForm {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
streetAddress1: string;
|
||||
streetAddress2: string;
|
||||
city: string;
|
||||
postalCode: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const router = useRouter();
|
||||
const { checkout, refreshCheckout, getLines, getTotal } = useSaleorCheckoutStore();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [orderComplete, setOrderComplete] = useState(false);
|
||||
const [orderNumber, setOrderNumber] = useState<string | null>(null);
|
||||
|
||||
const [sameAsShipping, setSameAsShipping] = useState(true);
|
||||
const [shippingAddress, setShippingAddress] = useState<AddressForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
streetAddress1: "",
|
||||
streetAddress2: "",
|
||||
city: "",
|
||||
postalCode: "",
|
||||
phone: "",
|
||||
});
|
||||
const [billingAddress, setBillingAddress] = useState<AddressForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
streetAddress1: "",
|
||||
streetAddress2: "",
|
||||
city: "",
|
||||
postalCode: "",
|
||||
phone: "",
|
||||
});
|
||||
|
||||
const lines = getLines();
|
||||
const total = getTotal();
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkout) {
|
||||
refreshCheckout();
|
||||
}
|
||||
}, [checkout, refreshCheckout]);
|
||||
|
||||
// Redirect if cart is empty
|
||||
useEffect(() => {
|
||||
if (lines.length === 0 && !orderComplete) {
|
||||
// Optionally redirect to cart or products
|
||||
// router.push("/products");
|
||||
}
|
||||
}, [lines, orderComplete, router]);
|
||||
|
||||
const handleShippingChange = (field: keyof AddressForm, value: string) => {
|
||||
setShippingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
if (sameAsShipping) {
|
||||
setBillingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBillingChange = (field: keyof AddressForm, value: string) => {
|
||||
setBillingAddress((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!checkout) {
|
||||
setError("No active checkout. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Update shipping address
|
||||
const shippingResult = await saleorClient.mutate<ShippingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_SHIPPING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
shippingAddress: {
|
||||
...shippingAddress,
|
||||
country: "RS", // Serbia
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (shippingResult.data?.checkoutShippingAddressUpdate?.errors && shippingResult.data.checkoutShippingAddressUpdate.errors.length > 0) {
|
||||
throw new Error(shippingResult.data.checkoutShippingAddressUpdate.errors[0].message);
|
||||
}
|
||||
|
||||
// Update billing address
|
||||
const billingResult = await saleorClient.mutate<BillingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_BILLING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
billingAddress: {
|
||||
...billingAddress,
|
||||
country: "RS",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (billingResult.data?.checkoutBillingAddressUpdate?.errors && billingResult.data.checkoutBillingAddressUpdate.errors.length > 0) {
|
||||
throw new Error(billingResult.data.checkoutBillingAddressUpdate.errors[0].message);
|
||||
}
|
||||
|
||||
// Complete checkout (creates order)
|
||||
const completeResult = await saleorClient.mutate<CheckoutCompleteResponse>({
|
||||
mutation: CHECKOUT_COMPLETE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (completeResult.data?.checkoutComplete?.errors && completeResult.data.checkoutComplete.errors.length > 0) {
|
||||
throw new Error(completeResult.data.checkoutComplete.errors[0].message);
|
||||
}
|
||||
|
||||
const order = completeResult.data?.checkoutComplete?.order;
|
||||
if (order) {
|
||||
setOrderNumber(order.number);
|
||||
setOrderComplete(true);
|
||||
} else {
|
||||
throw new Error("Failed to create order");
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message || "An error occurred during checkout");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Order Success Page
|
||||
if (orderComplete) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen">
|
||||
<section className="pt-[120px] pb-20 px-4">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<div className="mb-6">
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-3xl font-serif mb-2">Order Confirmed!</h1>
|
||||
<p className="text-foreground-muted">Thank you for your purchase.</p>
|
||||
</div>
|
||||
|
||||
{orderNumber && (
|
||||
<div className="bg-background-ice p-6 rounded-lg mb-6">
|
||||
<p className="text-sm text-foreground-muted mb-1">Order Number</p>
|
||||
<p className="text-2xl font-serif">#{orderNumber}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-foreground-muted mb-8">
|
||||
You will receive a confirmation email shortly. We will contact you to arrange Cash on Delivery.
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href="/products"
|
||||
className="inline-block px-8 py-3 bg-foreground text-white hover:bg-accent-dark transition-colors"
|
||||
>
|
||||
Continue Shopping
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen">
|
||||
<section className="pt-[120px] pb-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-3xl font-serif mb-8">Checkout</h1>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-600 p-4 mb-6 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Checkout Form */}
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Shipping Address */}
|
||||
<div className="border-b border-border pb-6">
|
||||
<h2 className="text-xl font-serif mb-4">Shipping Address</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.firstName}
|
||||
onChange={(e) => handleShippingChange("firstName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.lastName}
|
||||
onChange={(e) => handleShippingChange("lastName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Street Address</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.streetAddress1}
|
||||
onChange={(e) => handleShippingChange("streetAddress1", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<input
|
||||
type="text"
|
||||
value={shippingAddress.streetAddress2}
|
||||
onChange={(e) => handleShippingChange("streetAddress2", e.target.value)}
|
||||
placeholder="Apartment, suite, etc. (optional)"
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">City</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.city}
|
||||
onChange={(e) => handleShippingChange("city", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Postal Code</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={shippingAddress.postalCode}
|
||||
onChange={(e) => handleShippingChange("postalCode", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Phone</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={shippingAddress.phone}
|
||||
onChange={(e) => handleShippingChange("phone", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Billing Address Toggle */}
|
||||
<div className="border-b border-border pb-6">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sameAsShipping}
|
||||
onChange={(e) => setSameAsShipping(e.target.checked)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span>Billing address same as shipping</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Billing Address (if different) */}
|
||||
{!sameAsShipping && (
|
||||
<div className="border-b border-border pb-6">
|
||||
<h2 className="text-xl font-serif mb-4">Billing Address</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingAddress.firstName}
|
||||
onChange={(e) => handleBillingChange("firstName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingAddress.lastName}
|
||||
onChange={(e) => handleBillingChange("lastName", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Street Address</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingAddress.streetAddress1}
|
||||
onChange={(e) => handleBillingChange("streetAddress1", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">City</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingAddress.city}
|
||||
onChange={(e) => handleBillingChange("city", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Postal Code</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={billingAddress.postalCode}
|
||||
onChange={(e) => handleBillingChange("postalCode", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium mb-1">Phone</label>
|
||||
<input
|
||||
type="tel"
|
||||
required
|
||||
value={billingAddress.phone}
|
||||
onChange={(e) => handleBillingChange("phone", e.target.value)}
|
||||
className="w-full border border-border px-4 py-2 rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Payment Method */}
|
||||
<div className="border-b border-border pb-6">
|
||||
<h2 className="text-xl font-serif mb-4">Payment Method</h2>
|
||||
<div className="bg-background-ice p-4 rounded">
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="radio"
|
||||
checked
|
||||
readOnly
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<span>Cash on Delivery (COD)</span>
|
||||
</div>
|
||||
<p className="text-sm text-foreground-muted mt-2 ml-7">
|
||||
Pay when your order is delivered to your door.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || lines.length === 0}
|
||||
className="w-full py-4 bg-foreground text-white font-medium hover:bg-accent-dark transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? "Processing..." : `Complete Order - ${formatPrice(total)}`}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Order Summary */}
|
||||
<div className="bg-background-ice p-6 rounded-lg h-fit">
|
||||
<h2 className="text-xl font-serif mb-6">Order Summary</h2>
|
||||
|
||||
{lines.length === 0 ? (
|
||||
<p className="text-foreground-muted">Your cart is empty</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-4 mb-6">
|
||||
{lines.map((line) => (
|
||||
<div key={line.id} className="flex gap-4">
|
||||
<div className="w-16 h-16 bg-white relative flex-shrink-0">
|
||||
{line.variant.product.media[0]?.url && (
|
||||
<Image
|
||||
src={line.variant.product.media[0].url}
|
||||
alt={line.variant.product.name}
|
||||
fill
|
||||
sizes="64px"
|
||||
className="object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-sm">{line.variant.product.name}</h3>
|
||||
<p className="text-foreground-muted text-sm">
|
||||
Qty: {line.quantity}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{formatPrice(line.totalPrice.gross.amount)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border pt-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-muted">Subtotal</span>
|
||||
<span>{formatPrice(checkout?.subtotalPrice?.gross?.amount || 0)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-muted">Shipping</span>
|
||||
<span>
|
||||
{checkout?.shippingPrice?.gross?.amount
|
||||
? formatPrice(checkout.shippingPrice.gross.amount)
|
||||
: "Calculated"
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between font-medium text-lg pt-2 border-t border-border">
|
||||
<span>Total</span>
|
||||
<span>{formatPrice(total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { Mail, MapPin, Truck, Check } from "lucide-react";
|
||||
|
||||
export default function ContactPage() {
|
||||
const t = useTranslations("Contact");
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
message: "",
|
||||
});
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
{t("subtitle")}
|
||||
</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight mb-4">
|
||||
{t("title")}
|
||||
</h1>
|
||||
<p className="text-[#666666]">
|
||||
{t("getInTouchDesc")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20">
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium mb-6">
|
||||
{t("getInTouch")}
|
||||
</h2>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
{t("getInTouchDesc")}
|
||||
</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<Mail className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">{t("email")}</h3>
|
||||
<p className="text-[#666666] text-sm">hello@manoonoils.com</p>
|
||||
<p className="text-[#999999] text-xs mt-1">{t("emailReply")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<Truck className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">{t("shippingTitle")}</h3>
|
||||
<p className="text-[#666666] text-sm">{t("freeShipping")}</p>
|
||||
<p className="text-[#999999] text-xs mt-1">{t("deliveryTime")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<MapPin className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">{t("location")}</h3>
|
||||
<p className="text-[#666666] text-sm">{t("locationDesc")}</p>
|
||||
<p className="text-[#999999] text-xs mt-1">{t("worldwideShipping")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#f8f9fa] p-8 md:p-10">
|
||||
{submitted ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4">
|
||||
<Check className="w-8 h-8 text-green-600" strokeWidth={1.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">{t("thankYou")}</h3>
|
||||
<p className="text-[#666666]">
|
||||
{t("thankYouDesc")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium mb-2">
|
||||
{t("name")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors"
|
||||
placeholder={t("namePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-2">
|
||||
{t("emailField")}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors"
|
||||
placeholder={t("emailPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium mb-2">
|
||||
{t("message")}
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
required
|
||||
rows={5}
|
||||
value={formData.message}
|
||||
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
|
||||
className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors resize-none"
|
||||
placeholder={t("messagePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-4 bg-black text-white text-sm uppercase tracking-[0.1em] font-medium hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
{t("sendMessage")}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-16 md:py-24 border-t border-[#e5e5e5]">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-medium text-center mb-12">
|
||||
{t("faqTitle")}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{ q: t("faq1q"), a: t("faq1a") },
|
||||
{ q: t("faq2q"), a: t("faq2a") },
|
||||
{ q: t("faq3q"), a: t("faq3a") },
|
||||
{ q: t("faq4q"), a: t("faq4a") },
|
||||
].map((faq, index) => (
|
||||
<div key={index} className="border-b border-[#e5e5e5] pb-6">
|
||||
<h3 className="font-medium mb-2">{faq.q}</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">{faq.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export const metadata = {
|
||||
title: "Über Uns - ManoonOils",
|
||||
description: "Erfahren Sie mehr über ManoonOils - unsere Geschichte, Mission und unser Engagement für natürliche Schönheit.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Unsere Geschichte</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight">Über ManoonOils</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative h-[400px] md:h-[500px] overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2000&auto=format&fit=crop"
|
||||
alt="Natürliche Ölproduktion"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
</div>
|
||||
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-16">
|
||||
<p className="text-xl md:text-2xl text-[#1a1a1a] leading-relaxed mb-8">
|
||||
ManoonOils wurde aus einer Leidenschaft für natürliche Schönheit und dem Glauben geboren,
|
||||
dass die beste Hautpflege von der Natur selbst kommt.
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
Wir glauben an die Kraft natürlicher Inhaltsstoffe. Jedes Öl in unserer
|
||||
Kollektion wird sorgfältig aufgrund seiner einzigartigen Eigenschaften und
|
||||
Vorteile ausgewählt. Von nährenden Ölen, die die Haar vitalität wiederherstellen,
|
||||
bis zu Seren, die die Haut verjüngen, stellen wir jedes Produkt mit Liebe und
|
||||
Aufmerksamkeit für Details her.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 mb-16">
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Natürliche Inhaltsstoffe</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Wir verwenden nur die feinsten natürlichen Inhaltsstoffe, die ethisch und nachhaltig
|
||||
von vertrauenswürdigen Lieferanten aus aller Welt bezogen werden.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Tierversuchsfrei</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Unsere Produkte werden niemals an Tieren getestet. Wir glauben an Schönheit
|
||||
ohne Kompromisse.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Nachhaltige Verpackung</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Wir verwenden umweltfreundliche Verpackungsmaterialien und minimieren Abfall
|
||||
während unseres gesamten Produktionsprozesses.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Handgefertigte Qualität</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Jede Flasche wird in kleinen Chargen von Hand hergestellt, um
|
||||
höchste Qualität und Frische zu gewährleisten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-12 border-t border-b border-[#e5e5e5]">
|
||||
<span className="text-caption text-[#666666] mb-4 block">Unsere Mission</span>
|
||||
<blockquote className="text-2xl md:text-3xl font-medium tracking-tight">
|
||||
“Hochwertige, natürliche Produkte anzubieten, die Ihre tägliche Schönheitsroutine verbessern.”
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-medium mb-6">Mit Liebe Handgemacht</h2>
|
||||
<p className="text-[#666666] leading-relaxed mb-6">
|
||||
Jede Flasche ManoonOils wird mit Sorgfalt von Hand hergestellt. Wir stellen unsere
|
||||
Produkte in kleinen Chargen her, um höchste Qualität und Frische zu gewährleisten.
|
||||
Wenn Sie ManoonOils verwenden, können Sie sicher sein, dass Sie etwas verwenden,
|
||||
das mit echter Sorgfalt und Fachwissen hergestellt wurde.
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
Unsere Reise begann mit einer einfachen Frage: Wie können wir Produkte entwickeln,
|
||||
die Haar und Haut wirklich pflegen? Heute innovieren wir weiter, während wir
|
||||
unserem Engagement für natürliche, effektive Schönheitslösungen treu bleiben.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { Mail, MapPin, Truck, Check } from "lucide-react";
|
||||
|
||||
export default function ContactPage() {
|
||||
const [formData, setFormData] = useState({ name: "", email: "", message: "" });
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Kontaktieren Sie Uns</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight mb-4">Kontakt</h1>
|
||||
<p className="text-[#666666]">Haben Sie Fragen? Wir würden uns freuen, von Ihnen zu hören.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20">
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium mb-6">Kontaktieren Sie Uns</h2>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
Wir sind hier um zu helfen! Ob Sie Fragen zu unseren Produkten haben,
|
||||
Hilfe bei einer Bestellung benötigen oder einfach Hallo sagen möchten.
|
||||
</p>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<Mail className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">E-Mail</h3>
|
||||
<p className="text-[#666666] text-sm">hello@manoonoils.com</p>
|
||||
<p className="text-[#999999] text-xs mt-1">Wir antworten innerhalb von 24 Stunden</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<Truck className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">Versand</h3>
|
||||
<p className="text-[#666666] text-sm">Kostenloser Versand ab €50</p>
|
||||
<p className="text-[#999999] text-xs mt-1">Lieferung innerhalb von 2-5 Werktagen</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0">
|
||||
<MapPin className="w-5 h-5 text-[#666666]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium mb-1">Standort</h3>
|
||||
<p className="text-[#666666] text-sm">Serbien</p>
|
||||
<p className="text-[#999999] text-xs mt-1">Weltweiter Versand</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f8f9fa] p-8 md:p-10">
|
||||
{submitted ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4">
|
||||
<Check className="w-8 h-8 text-green-600" strokeWidth={1.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">Vielen Dank!</h3>
|
||||
<p className="text-[#666666]">Ihre Nachricht wurde gesendet. Wir werden uns bald bei Ihnen melden.</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium mb-2">Name</label>
|
||||
<input type="text" id="name" required value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors" placeholder="Ihr Name" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-2">E-Mail</label>
|
||||
<input type="email" id="email" required value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors" placeholder="ihre@email.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium mb-2">Nachricht</label>
|
||||
<textarea id="message" required rows={5} value={formData.message} onChange={(e) => setFormData({ ...formData, message: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors resize-none" placeholder="Wie können wir Ihnen helfen?" />
|
||||
</div>
|
||||
<button type="submit" className="w-full py-4 bg-black text-white text-sm uppercase tracking-[0.1em] font-medium hover:bg-[#333333] transition-colors">Nachricht Senden</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-16 md:py-24 border-t border-[#e5e5e5]">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-medium text-center mb-12">Häufig Gestellte Fragen</h2>
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{ q: "Wie lange dauert der Versand?", a: "Bestellungen werden normalerweise innerhalb von 2-5 Werktagen für Inlandsversand geliefert. Sie erhalten eine Tracking-Nummer, sobald Ihre Bestellung versandt wurde." },
|
||||
{ q: "Sind Ihre Produkte 100% natürlich?", a: "Ja! Alle unsere Öle sind 100% natürlich, kaltgepresst und frei von Zusatzstoffen, Konservierungsstoffen oder künstlichen Duftstoffen." },
|
||||
{ q: "Was ist Ihre Rückgaberichtlinie?", a: "Wir akzeptieren Rücksendungen innerhalb von 14 Tagen nach Lieferung für ungeöffnete Produkte. Bitte kontaktieren Sie uns, wenn Sie Probleme mit Ihrer Bestellung haben." },
|
||||
{ q: "Bieten Sie Wiederverkauf an?", a: "Ja, wir bieten Wiederverkaufspreise für Bulk-Bestellungen. Bitte kontaktieren Sie uns unter hello@manoonoils.com für mehr Informationen." }
|
||||
].map((faq, index) => (
|
||||
<div key={index} className="border-b border-[#e5e5e5] pb-6">
|
||||
<h3 className="font-medium mb-2">{faq.q}</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">{faq.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16"><Footer /></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import TrustBadges from "@/components/home/TrustBadges";
|
||||
import AsSeenIn from "@/components/home/AsSeenIn";
|
||||
import ProductReviews from "@/components/product/ProductReviews";
|
||||
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
|
||||
import ProblemSection from "@/components/home/ProblemSection";
|
||||
import HowItWorks from "@/components/home/HowItWorks";
|
||||
|
||||
export const metadata = {
|
||||
title: "ManoonOils - Premium Natürliche Öle für Haar & Haut",
|
||||
description:
|
||||
"Entdecken Sie unsere Premium-Kollektion natürlicher Öle für Haar- und Hautpflege. Mit Liebe handgemacht aus den feinsten Zutaten.",
|
||||
};
|
||||
|
||||
export default async function GermanHomepage() {
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts("DE");
|
||||
} catch (e) {
|
||||
console.log("Failed to fetch products during build");
|
||||
}
|
||||
|
||||
const featuredProducts = products?.slice(0, 4) || [];
|
||||
const hasProducts = featuredProducts.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<HeroVideo />
|
||||
|
||||
<AsSeenIn />
|
||||
|
||||
<ProductReviews />
|
||||
|
||||
<TrustBadges />
|
||||
|
||||
<ProblemSection />
|
||||
|
||||
<BeforeAfterGallery />
|
||||
|
||||
<div id="main_content" className="scroll-mt-[72px] lg:scroll-mt-[72px]">
|
||||
{hasProducts && (
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
Unsere Kollektion
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">
|
||||
Premium Natürliche Öle
|
||||
</h2>
|
||||
<p className="text-[#666666] max-w-xl mx-auto">
|
||||
Kaltgepresst, rein und natürlich für Ihre tägliche Schönheitsroutine
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{featuredProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="DE" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<a
|
||||
href="/de/products"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
Alle Produkte Ansehen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<HowItWorks />
|
||||
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
Unsere Geschichte
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-6">
|
||||
Mit Liebe Handgemacht
|
||||
</h2>
|
||||
<p className="text-[#666666] mb-6 leading-relaxed">
|
||||
Jede Flasche ManoonOils wird mit größter Sorgfalt hergestellt, unter
|
||||
Verwendung traditioneller Methoden, die von Generation zu Generation
|
||||
weitergegeben wurden. Wir beziehen nur die besten organischen
|
||||
Zutaten, um Ihnen Öle zu liefern, die Haar und Haut pflegen.
|
||||
</p>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
Unser Engagement für Reinheit bedeutet: keine Zusatzstoffe, keine
|
||||
Konservierungsstoffe - nur die Güte der Natur in ihrer potentesten Form.
|
||||
</p>
|
||||
<a
|
||||
href="/de/about"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
Mehr Erfahren
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative aspect-[4/3] bg-[#e8f0f5] rounded-lg overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Natürliche Ölproduktion"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-white to-[#faf9f7]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
|
||||
Warum Uns Wählen
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium text-[#1a1a1a]">
|
||||
Der Manoon Unterschied
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||
{[
|
||||
{
|
||||
title: "100% Natürlich",
|
||||
description: "Reine, kaltgepresste Öle ohne Zusatzstoffe oder Konservierungsstoffe. Nur die Güte der Natur.",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
|
||||
<path stroke="#7eb89e" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Handgefertigt",
|
||||
description: "Jede Charge wird sorgfältig von Hand zubereitet, um höchste Qualität zu gewährleisten.",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M15.182 15.182a4.5 4.5 0 01-6.364 0M21 12a9 9 0 11-18 0 9 9 0 0118 0zM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75zm-.375 0h.008v.015h-.008V9.75zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75zm-.375 0h.008v.015h-.008V9.75z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Nachhaltig",
|
||||
description: "Ethnisch beschaffte Zutaten und umweltfreundliche Verpackungen für einen besseren Planeten.",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path stroke="#e8967a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 11-1.652.928l-.714-.093a1.125 1.125 0 00-1.906.172L4.5 15.75l-.612.153M12.75 3.031l.002-.004m0 0a8.955 8.955 0 00-4.943.834 8.974 8.974 0 004.943.834m4.943-.834a8.955 8.955 0 00-4.943-.834c2.687 0 5.18.948 7.161 2.664a8.974 8.974 0 014.943-.834z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
].map((benefit, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group"
|
||||
>
|
||||
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
|
||||
{benefit.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{benefit.title}</h3>
|
||||
<p className="text-sm text-[#666666] leading-relaxed">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-28 lg:py-32 px-4 sm:px-6 lg:px-8 bg-[#1a1a1a] text-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-white/60 mb-3 block">
|
||||
Verbunden Bleiben
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6">
|
||||
Werden Sie Teil Unserer Gemeinschaft
|
||||
</h2>
|
||||
<p className="text-white/70 mb-10 mx-auto text-lg">
|
||||
Abonnieren Sie, um exklusive Angebote, Beauty-Tipps zu erhalten und der Erste zu sein, der neue Produkte erfährt.
|
||||
</p>
|
||||
<form className="flex flex-col sm:flex-row items-stretch justify-center max-w-md mx-auto gap-0">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Geben Sie Ihre E-Mail ein"
|
||||
className="flex-1 min-w-0 px-5 h-14 bg-white/10 border border-white/20 border-b-0 sm:border-b border-r-0 sm:border-r border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left rounded-t sm:rounded-l sm:rounded-tr-none"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-8 h-14 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0 rounded-b sm:rounded-r sm:rounded-bl-none"
|
||||
>
|
||||
Abonnieren
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { getProductBySlug, getProducts, getLocalizedProduct } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductDetail from "@/components/product/ProductDetail";
|
||||
import type { Product } from "@/types/saleor";
|
||||
|
||||
interface ProductPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts("DE", 100);
|
||||
const params: Array<{ slug: string }> = [];
|
||||
products.forEach((product: Product) => {
|
||||
if (product.translation?.slug) {
|
||||
params.push({ slug: product.translation.slug });
|
||||
}
|
||||
});
|
||||
return params;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "DE");
|
||||
|
||||
if (!product) {
|
||||
return { title: "Produkt Nicht Gefunden" };
|
||||
}
|
||||
|
||||
const localized = getLocalizedProduct(product, "DE");
|
||||
|
||||
return {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
openGraph: {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
images: product.media?.[0]?.url ? [product.media[0].url] : [],
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function GermanProductPage({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "DE");
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[180px] lg:pt-[200px] pb-20 text-center px-4">
|
||||
<h1 className="text-2xl font-medium mb-4">Produkt nicht gefunden</h1>
|
||||
<p className="text-[#666666] mb-8">
|
||||
Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
</p>
|
||||
<a
|
||||
href="/de/products"
|
||||
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
Produkte Durchsuchen
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let relatedProducts: Product[] = [];
|
||||
try {
|
||||
const allProducts = await getProducts("DE", 8);
|
||||
relatedProducts = allProducts.filter((p: Product) => p.id !== product.id).slice(0, 4);
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<ProductDetail product={product} relatedProducts={relatedProducts} locale="DE" />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export const metadata = {
|
||||
title: "Produkte - ManoonOils",
|
||||
description: "Durchsuchen Sie unsere Kollektion von Premium natürlichen Ölen für Haar- und Hautpflege.",
|
||||
};
|
||||
|
||||
export default async function GermanProductsPage() {
|
||||
const products = await getProducts("DE");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[72px] lg:pt-[72px]">
|
||||
<div className="border-b border-[#e5e5e5]">
|
||||
<div className="container py-8 md:py-12">
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-2 block">
|
||||
Unsere Kollektion
|
||||
</span>
|
||||
<h1 className="text-3xl md:text-4xl font-medium">
|
||||
Alle Produkte
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-[#666666]">
|
||||
{products.length} Produkte
|
||||
</span>
|
||||
<div className="relative">
|
||||
<select
|
||||
className="appearance-none bg-transparent border border-[#e5e5e5] pl-4 pr-10 py-2 text-sm focus:outline-none focus:border-black cursor-pointer"
|
||||
defaultValue="featured"
|
||||
>
|
||||
<option value="featured">Empfohlen</option>
|
||||
<option value="newest">Neueste</option>
|
||||
<option value="price-low">Preis: Aufsteigend</option>
|
||||
<option value="price-high">Preis: Absteigend</option>
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none text-[#666666]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
{products.length === 0 ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#666666] mb-4">
|
||||
Keine Produkte verfügbar
|
||||
</p>
|
||||
<p className="text-sm text-[#999999]">
|
||||
Bitte schauen Sie später nach neuen Produkten.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
index={index}
|
||||
locale="DE"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export async function generateMetadata() {
|
||||
return {
|
||||
title: "About - ManoonOils",
|
||||
description: "Learn about ManoonOils - our story, mission, and commitment to natural beauty.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function AboutPage() {
|
||||
const t = await getTranslations("About");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
{t("subtitle")}
|
||||
</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight">
|
||||
{t("title")}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative h-[400px] md:h-[500px] overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2000&auto=format&fit=crop"
|
||||
alt="Natural oils production"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
</div>
|
||||
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-16">
|
||||
<p className="text-xl md:text-2xl text-[#1a1a1a] leading-relaxed mb-8">
|
||||
{t("intro")}
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
{t("intro2")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 mb-16">
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">
|
||||
{t("naturalIngredients")}
|
||||
</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
{t("naturalIngredientsDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">
|
||||
{t("crueltyFree")}
|
||||
</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
{t("crueltyFreeDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">
|
||||
{t("sustainablePackaging")}
|
||||
</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
{t("sustainablePackagingDesc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">
|
||||
{t("handcraftedQuality")}
|
||||
</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
{t("handcraftedQualityDesc")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-12 border-t border-b border-[#e5e5e5]">
|
||||
<span className="text-caption text-[#666666] mb-4 block">
|
||||
{t("mission")}
|
||||
</span>
|
||||
<blockquote className="text-2xl md:text-3xl font-medium tracking-tight">
|
||||
“{t("missionQuote")}”
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-medium mb-6">
|
||||
{t("handmadeTitle")}
|
||||
</h2>
|
||||
<p className="text-[#666666] leading-relaxed mb-6">
|
||||
{t("handmadeText1")}
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
{t("handmadeText2")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { getProductBySlug, getProducts, getLocalizedProduct } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductDetail from "@/components/product/ProductDetail";
|
||||
import type { Product } from "@/types/saleor";
|
||||
|
||||
interface ProductPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts("UK", 100);
|
||||
const params: Array<{ slug: string }> = [];
|
||||
|
||||
products.forEach((product: Product) => {
|
||||
if (product.translation?.slug) {
|
||||
params.push({ slug: product.translation.slug });
|
||||
}
|
||||
});
|
||||
|
||||
return params;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "EN");
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
title: "Product Not Found",
|
||||
};
|
||||
}
|
||||
|
||||
const localized = getLocalizedProduct(product, "EN");
|
||||
|
||||
return {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
openGraph: {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
images: product.media?.[0]?.url ? [product.media[0].url] : [],
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function EnglishProductPage({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "EN");
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[180px] lg:pt-[200px] pb-20 text-center px-4">
|
||||
<h1 className="text-2xl font-medium mb-4">Product not found</h1>
|
||||
<p className="text-[#666666] mb-8">
|
||||
The product you're looking for doesn't exist or has been removed.
|
||||
</p>
|
||||
<a
|
||||
href="/en/products"
|
||||
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
Browse Products
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let relatedProducts: Product[] = [];
|
||||
try {
|
||||
const allProducts = await getProducts("EN", 8);
|
||||
relatedProducts = allProducts
|
||||
.filter((p: Product) => p.id !== product.id)
|
||||
.slice(0, 4);
|
||||
} catch (e) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<ProductDetail
|
||||
product={product}
|
||||
relatedProducts={relatedProducts}
|
||||
locale="EN"
|
||||
/>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export async function generateMetadata() {
|
||||
return {
|
||||
title: "Products - ManoonOils",
|
||||
description: "Browse our collection of premium natural oils for hair and skin care.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function EnglishProductsPage() {
|
||||
const t = await getTranslations("Products");
|
||||
const products = await getProducts("UK");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[72px] lg:pt-[72px]">
|
||||
<div className="border-b border-[#e5e5e5]">
|
||||
<div className="container py-8 md:py-12">
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-2 block">
|
||||
{t("collection")}
|
||||
</span>
|
||||
<h1 className="text-3xl md:text-4xl font-medium">
|
||||
{t("allProducts")}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-[#666666]">
|
||||
{t("productsCount", { count: products.length })}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<select
|
||||
className="appearance-none bg-transparent border border-[#e5e5e5] pl-4 pr-10 py-2 text-sm focus:outline-none focus:border-black cursor-pointer"
|
||||
defaultValue="featured"
|
||||
>
|
||||
<option value="featured">{t("featured")}</option>
|
||||
<option value="newest">{t("newest")}</option>
|
||||
<option value="price-low">{t("priceLow")}</option>
|
||||
<option value="price-high">{t("priceHigh")}</option>
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none text-[#666666]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
{products.length === 0 ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#666666] mb-4">
|
||||
{t("noProducts")}
|
||||
</p>
|
||||
<p className="text-sm text-[#999999]">
|
||||
{t("checkBack")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
index={index}
|
||||
locale="EN"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export const metadata = {
|
||||
title: "À Propos - ManoonOils",
|
||||
description: "Découvrez ManoonOils - notre histoire, notre mission et notre engagement pour la beauté naturelle.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Notre Histoire</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight">À Propos de ManoonOils</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-[400px] md:h-[500px] overflow-hidden">
|
||||
<img src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2000&auto=format&fit=crop" alt="Production d'huiles naturelles" className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-black/20" />
|
||||
</div>
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div className="mb-16">
|
||||
<p className="text-xl md:text-2xl text-[#1a1a1a] leading-relaxed mb-8">ManoonOils est né d'une passion pour la beauté naturelle et de la conviction que les meilleurs soins Cutanee viennent de la nature elle-même.</p>
|
||||
<p className="text-[#666666] leading-relaxed">Nous croyons en le pouvoir des ingrédients naturels. Chaque huile de notre collection est soigneusement sélectionnée pour ses propriétés et bienfaits uniques. Des huiles nourrissantes qui restaurent la vitalité des cheveux aux sérums qui rajeunissent la peau, nous élaborons chaque produit avec amour et attention aux détails.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 mb-16">
|
||||
<div className="p-6 bg-[#f8f9fa]"><h3 className="text-lg font-medium mb-3">Ingrédients Naturels</h3><p className="text-[#666666] text-sm leading-relaxed">Nous utilisons uniquement les meilleurs ingrédients naturels, sourcés de manière éthique et durable auprès de fournisseurs de confiance du monde entier.</p></div>
|
||||
<div className="p-6 bg-[#f8f9fa]"><h3 className="text-lg font-medium mb-3">Non Testé sur Animaux</h3><p className="text-[#666666] text-sm leading-relaxed">Nos produits ne sont jamais testés sur les animaux. Nous croyons en la beauté sans compromis.</p></div>
|
||||
<div className="p-6 bg-[#f8f9fa]"><h3 className="text-lg font-medium mb-3">Emballage Durable</h3><p className="text-[#666666] text-sm leading-relaxed">Nous utilisons des matériaux d'emballage écologiques et minimisons les déchets tout au long de notre processus de production.</p></div>
|
||||
<div className="p-6 bg-[#f8f9fa]"><h3 className="text-lg font-medium mb-3">Qualité Artisanale</h3><p className="text-[#666666] text-sm leading-relaxed">Chaque flacon est fabriqué à la main en petites quantités pour garantir la plus haute qualité et fraîcheur.</p></div>
|
||||
</div>
|
||||
<div className="text-center py-12 border-t border-b border-[#e5e5e5]"><span className="text-caption text-[#666666] mb-4 block">Notre Mission</span><blockquote className="text-2xl md:text-3xl font-medium tracking-tight">“Fournir des produits naturels de qualité premium qui améliorent votre routine beauté quotidienne.”</blockquote></div>
|
||||
<div className="mt-16"><h2 className="text-2xl font-medium mb-6">Fait Main avec Amour</h2><p className="text-[#666666] leading-relaxed mb-6">Chaque flacon de ManoonOils est fabriqué avec soin. Nous produisons nos produits en petites quantités pour garantir la plus haute qualité et fraîcheur. Lorsque vous utilisez ManoonOils, vous pouvez être sûr d'utiliser quelque chose fait avec un véritable soin et expertise.</p><p className="text-[#666666] leading-relaxed">Notre voyage a commencé par une simple question: comment créer des produits qui soignent vraiment les cheveux et la peau? Aujourd'hui, nous continuons à innover tout en restant fidèles à notre engagement envers des solutions beauté naturelles et efficaces.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16"><Footer /></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import { Mail, MapPin, Truck, Check } from "lucide-react";
|
||||
|
||||
export default function ContactPage() {
|
||||
const [formData, setFormData] = useState({ name: "", email: "", message: "" });
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[104px]">
|
||||
<div className="container py-12 md:py-16">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Contactez-Nous</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight mb-4">Nous Contacter</h1>
|
||||
<p className="text-[#666666]">Des questions? Nous serions ravis de vous entendre.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20">
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium mb-6">Contactez-Nous</h2>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">Nous sommes là pour aider! Que vous ayez des questions sur nos produits, besoin d'aide avec une commande, ou simplement voulez dire bonjour.</p>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0"><Mail className="w-5 h-5 text-[#666666]" strokeWidth={1.5} /></div>
|
||||
<div><h3 className="font-medium mb-1">Email</h3><p className="text-[#666666] text-sm">hello@manoonoils.com</p><p className="text-[#999999] text-xs mt-1">Nous répondons sous 24 heures</p></div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0"><Truck className="w-5 h-5 text-[#666666]" strokeWidth={1.5} /></div>
|
||||
<div><h3 className="font-medium mb-1">Livraison</h3><p className="text-[#666666] text-sm">Livraison gratuite dès €50</p><p className="text-[#999999] text-xs mt-1">Livré sous 2-5 jours ouvrables</p></div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-[#f8f9fa] flex items-center justify-center flex-shrink-0"><MapPin className="w-5 h-5 text-[#666666]" strokeWidth={1.5} /></div>
|
||||
<div><h3 className="font-medium mb-1">Localisation</h3><p className="text-[#666666] text-sm">Serbie</p><p className="text-[#999999] text-xs mt-1">Expédition dans le monde entier</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f8f9fa] p-8 md:p-10">
|
||||
{submitted ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4"><Check className="w-8 h-8 text-green-600" strokeWidth={1.5} /></div>
|
||||
<h3 className="text-xl font-medium mb-2">Merci!</h3>
|
||||
<p className="text-[#666666]">Votre message a été envoyé. Nous reviendrons vers vous bientôt.</p>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div><label htmlFor="name" className="block text-sm font-medium mb-2">Nom</label><input type="text" id="name" required value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors" placeholder="Votre nom" /></div>
|
||||
<div><label htmlFor="email" className="block text-sm font-medium mb-2">Email</label><input type="email" id="email" required value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors" placeholder="votre@email.com" /></div>
|
||||
<div><label htmlFor="message" className="block text-sm font-medium mb-2">Message</label><textarea id="message" required rows={5} value={formData.message} onChange={(e) => setFormData({ ...formData, message: e.target.value })} className="w-full px-4 py-3 bg-white border border-[#e5e5e5] focus:outline-none focus:border-black transition-colors resize-none" placeholder="Comment pouvons-nous vous aider?" /></div>
|
||||
<button type="submit" className="w-full py-4 bg-black text-white text-sm uppercase tracking-[0.1em] font-medium hover:bg-[#333333] transition-colors">Envoyer Le Message</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="py-16 md:py-24 border-t border-[#e5e5e5]">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h2 className="text-2xl font-medium text-center mb-12">Questions Fréquentes</h2>
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{ q: "Combien de temps dure la livraison?", a: "Les commandes sont généralement livrées sous 2-5 jours ouvrables pour la livraison nationale. Vous recevrez un numéro de suivi une fois votre commande expédiée." },
|
||||
{ q: "Vos produits sont-ils 100% naturels?", a: "Oui! Toutes nos huiles sont 100% naturelles, pressées à froid et sans additifs, conservateurs ou parfums artificiels." },
|
||||
{ q: "Quelle est votre politique de retour?", a: "Nous acceptons les retours dans les 14 jours suivant la livraison pour les produits non ouverts. Veuillez nous contacter si vous avez des problèmes avec votre commande." },
|
||||
{ q: "Offrez-vous la vente en gros?", a: "Oui, nous proposons des prix de gros pour les commandes en vrac. Veuillez nous contacter à hello@manoonoils.com pour plus d'informations." }
|
||||
].map((faq, index) => (<div key={index} className="border-b border-[#e5e5e5] pb-6"><h3 className="font-medium mb-2">{faq.q}</h3><p className="text-[#666666] text-sm leading-relaxed">{faq.a}</p></div>))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div className="pt-16"><Footer /></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import TrustBadges from "@/components/home/TrustBadges";
|
||||
import AsSeenIn from "@/components/home/AsSeenIn";
|
||||
import ProductReviews from "@/components/product/ProductReviews";
|
||||
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
|
||||
import ProblemSection from "@/components/home/ProblemSection";
|
||||
import HowItWorks from "@/components/home/HowItWorks";
|
||||
|
||||
export const metadata = {
|
||||
title: "ManoonOils - Huiles Naturelles Premium pour Cheveux & Peau",
|
||||
description:
|
||||
"Découvrez notre collection premium d'huiles naturelles pour les soins capillaires et cutanés. Fait main avec amour en utilisant uniquement les meilleurs ingrédients.",
|
||||
};
|
||||
|
||||
export default async function FrenchHomepage() {
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts("FR");
|
||||
} catch (e) {
|
||||
console.log("Failed to fetch products during build");
|
||||
}
|
||||
|
||||
const featuredProducts = products?.slice(0, 4) || [];
|
||||
const hasProducts = featuredProducts.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<HeroVideo />
|
||||
<AsSeenIn />
|
||||
<ProductReviews />
|
||||
<TrustBadges />
|
||||
<ProblemSection />
|
||||
<BeforeAfterGallery />
|
||||
<div id="main_content" className="scroll-mt-[72px] lg:scroll-mt-[72px]">
|
||||
{hasProducts && (
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Notre Collection</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">Huiles Naturelles Premium</h2>
|
||||
<p className="text-[#666666] max-w-xl mx-auto">Pressées à froid, pures et naturelles pour votre routine beauté quotidienne</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{featuredProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="FR" />
|
||||
))}
|
||||
</div>
|
||||
<div className="text-center mt-12">
|
||||
<a href="/fr/products" className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors">Voir Tous Les Produits</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<HowItWorks />
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Notre Histoire</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-6">Fait Main avec Amour</h2>
|
||||
<p className="text-[#666666] mb-6 leading-relaxed">Chaque flacon de ManoonOils est fabriqué avec soin en utilisant des méthodes traditionnelles transmises de génération en génération. Nous ne sélectionnons que les meilleurs ingrédients biologiques pour vous apporter des huiles qui nourrissent les cheveux et la peau.</p>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">Notre engagement envers la pureté signifie pas d'additifs, pas de préservatifs - juste la bonté de la nature sous sa forme la plus potente.</p>
|
||||
<a href="/fr/about" className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors">En Savoir Plus</a>
|
||||
</div>
|
||||
<div className="relative aspect-[4/3] bg-[#e8f0f5] rounded-lg overflow-hidden">
|
||||
<img src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=800&auto=format&fit=crop" alt="Production d'huiles naturelles" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-white to-[#faf9f7]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">Pourquoi Nous Choisir</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium text-[#1a1a1a]">La Différence Manoon</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||
{[
|
||||
{ title: "100% Naturel", description: "Huiles pures pressées à froid sans additifs ni préservatifs. Juste la bonté de la nature.", icon: (<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/><path stroke="#7eb89e" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>) },
|
||||
{ title: "Artisanal", description: "Chaque lot est soigneusement préparé à la main pour garantir la plus haute qualité.", icon: (<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none"><path stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M15.182 15.182a4.5 4.5 0 01-6.364 0M21 12a9 9 0 11-18 0 9 9 0 0118 0zM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75zm-.375 0h.008v.015h-.008V9.75zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75zm-.375 0h.008v.015h-.008V9.75z"/></svg>) },
|
||||
{ title: "Durable", description: "Ingrédients sourcés de manière éthique et emballage écologique pour une meilleure planète.", icon: (<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none"><path stroke="#e8967a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 11-1.652.928l-.714-.093a1.125 1.125 0 00-1.906.172L4.5 15.75l-.612.153M12.75 3.031l.002-.004m0 0a8.955 8.955 0 00-4.943.834 8.974 8.974 0 004.943.834m4.943-.834a8.955 8.955 0 00-4.943-.834c2.687 0 5.18.948 7.161 2.664a8.974 8.974 0 014.943-.834z"/></svg>) },
|
||||
].map((benefit, index) => (
|
||||
<div key={index} className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group">
|
||||
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">{benefit.icon}</div>
|
||||
<h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{benefit.title}</h3>
|
||||
<p className="text-sm text-[#666666] leading-relaxed">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="py-28 lg:py-32 px-4 sm:px-6 lg:px-8 bg-[#1a1a1a] text-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-white/60 mb-3 block">Restez Connecté</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6">Rejoignez Notre Communauté</h2>
|
||||
<p className="text-white/70 mb-10 mx-auto text-lg">Abonnez-vous pour recevoir des offres exclusives, des conseils beauté et être le premier à connaître les nouveaux produits.</p>
|
||||
<form className="flex flex-col sm:flex-row items-stretch justify-center max-w-md mx-auto gap-0">
|
||||
<input type="email" placeholder="Entrez votre email" className="flex-1 min-w-0 px-5 h-14 bg-white/10 border border-white/20 border-b-0 sm:border-b border-r-0 sm:border-r border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left rounded-t sm:rounded-l sm:rounded-tr-none" />
|
||||
<button type="submit" className="px-8 h-14 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0 rounded-b sm:rounded-r sm:rounded-bl-none">S'abonner</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { getProductBySlug, getProducts, getLocalizedProduct } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductDetail from "@/components/product/ProductDetail";
|
||||
import type { Product } from "@/types/saleor";
|
||||
|
||||
interface ProductPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts("FR", 100);
|
||||
const params: Array<{ slug: string }> = [];
|
||||
products.forEach((product: Product) => {
|
||||
if (product.translation?.slug) {
|
||||
params.push({ slug: product.translation.slug });
|
||||
}
|
||||
});
|
||||
return params;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "FR");
|
||||
|
||||
if (!product) {
|
||||
return { title: "Produit Non Trouvé" };
|
||||
}
|
||||
|
||||
const localized = getLocalizedProduct(product, "FR");
|
||||
|
||||
return {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
openGraph: {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
images: product.media?.[0]?.url ? [product.media[0].url] : [],
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function FrenchProductPage({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "FR");
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[180px] lg:pt-[200px] pb-20 text-center px-4">
|
||||
<h1 className="text-2xl font-medium mb-4">Produit non trouvé</h1>
|
||||
<p className="text-[#666666] mb-8">Le produit que vous recherchez n'existe pas ou a été supprimé.</p>
|
||||
<a href="/fr/products" className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors">Parcourir Les Produits</a>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let relatedProducts: Product[] = [];
|
||||
try {
|
||||
const allProducts = await getProducts("FR", 8);
|
||||
relatedProducts = allProducts.filter((p: Product) => p.id !== product.id).slice(0, 4);
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<ProductDetail product={product} relatedProducts={relatedProducts} locale="FR" />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export const metadata = {
|
||||
title: "Produits - ManoonOils",
|
||||
description: "Parcourez notre collection d'huiles naturelles premium pour les soins capillaires et cutanés.",
|
||||
};
|
||||
|
||||
export default async function FrenchProductsPage() {
|
||||
const products = await getProducts("FR");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-white">
|
||||
<div className="pt-[72px] lg:pt-[72px]">
|
||||
<div className="border-b border-[#e5e5e5]">
|
||||
<div className="container py-8 md:py-12">
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-2 block">Notre Collection</span>
|
||||
<h1 className="text-3xl md:text-4xl font-medium">Tous Les Produits</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-[#666666]">{products.length} produits</span>
|
||||
<div className="relative">
|
||||
<select className="appearance-none bg-transparent border border-[#e5e5e5] pl-4 pr-10 py-2 text-sm focus:outline-none focus:border-black cursor-pointer" defaultValue="featured">
|
||||
<option value="featured">En Vedette</option>
|
||||
<option value="newest">Nouveautés</option>
|
||||
<option value="price-low">Prix: Croissant</option>
|
||||
<option value="price-high">Prix: Décroissant</option>
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none text-[#666666]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="py-12 md:py-16">
|
||||
<div className="container">
|
||||
{products.length === 0 ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#666666] mb-4">Aucun produit disponible</p>
|
||||
<p className="text-sm text-[#999999]">Veuillez revenir plus tard pour les nouveaux arrivages.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="FR" />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<div className="pt-16"><Footer /></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import ErrorBoundary from "@/components/providers/ErrorBoundary";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -25,27 +23,18 @@ export const viewport: Viewport = {
|
||||
maximumScale: 5,
|
||||
};
|
||||
|
||||
const suppressHydrationWarning = true;
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<html suppressHydrationWarning>
|
||||
<body className="antialiased" suppressHydrationWarning>
|
||||
<ErrorBoundary>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
218
src/app/page.tsx
218
src/app/page.tsx
@@ -1,215 +1,5 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
import TrustBadges from "@/components/home/TrustBadges";
|
||||
import AsSeenIn from "@/components/home/AsSeenIn";
|
||||
import ProductReviews from "@/components/product/ProductReviews";
|
||||
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
|
||||
import ProblemSection from "@/components/home/ProblemSection";
|
||||
import HowItWorks from "@/components/home/HowItWorks";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const t = await getTranslations("Home");
|
||||
return {
|
||||
title: "ManoonOils - Premium prirodna ulja za negu kose i kože",
|
||||
description: "Otkrijte našu premium kolekciju prirodnih ulja za negu kose i kože.",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Homepage() {
|
||||
const t = await getTranslations("Home");
|
||||
const tBenefits = await getTranslations("Benefits");
|
||||
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts("SR");
|
||||
} catch (e) {
|
||||
console.log("Failed to fetch products during build");
|
||||
}
|
||||
|
||||
const featuredProducts = products?.slice(0, 4) || [];
|
||||
const hasProducts = featuredProducts.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<HeroVideo />
|
||||
|
||||
<AsSeenIn />
|
||||
|
||||
<ProductReviews />
|
||||
|
||||
<TrustBadges />
|
||||
|
||||
<ProblemSection />
|
||||
|
||||
<BeforeAfterGallery />
|
||||
|
||||
<div id="main-content" className="scroll-mt-[72px] lg:scroll-mt-[72px]">
|
||||
{hasProducts && (
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
{t("collection")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">
|
||||
{t("premiumOils")}
|
||||
</h2>
|
||||
<p className="text-[#666666] max-w-xl mx-auto">
|
||||
{t("oilsDescription")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
|
||||
{featuredProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="SR" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-12">
|
||||
<a
|
||||
href="/products"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
{t("viewAll")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<HowItWorks />
|
||||
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
{t("ourStory")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-6">
|
||||
{t("handmadeWithLove")}
|
||||
</h2>
|
||||
<p className="text-[#666666] mb-6 leading-relaxed">
|
||||
{t("storyText1")}
|
||||
</p>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
{t("storyText2")}
|
||||
</p>
|
||||
<a
|
||||
href="/about"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
{t("learnMore")}
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative aspect-[4/3] bg-[#e8f0f5] rounded-lg overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=800&auto=format&fit=crop"
|
||||
alt="Proizvodnja prirodnih ulja"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-white to-[#faf9f7]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
|
||||
{t("whyChooseUs")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium text-[#1a1a1a]">
|
||||
{t("manoonDifference")}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
|
||||
{[
|
||||
{
|
||||
title: tBenefits("natural"),
|
||||
description: tBenefits("naturalDesc"),
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
|
||||
<path stroke="#7eb89e" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: tBenefits("handcrafted"),
|
||||
description: tBenefits("handcraftedDesc"),
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M15.182 15.182a4.5 4.5 0 01-6.364 0M21 12a9 9 0 11-18 0 9 9 0 0118 0zM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75zm-.375 0h.008v.015h-.008V9.75zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75zm-.375 0h.008v.015h-.008V9.75z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: tBenefits("sustainable"),
|
||||
description: tBenefits("sustainableDesc"),
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path stroke="#e8967a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 11-1.652.928l-.714-.093a1.125 1.125 0 00-1.906.172L4.5 15.75l-.612.153M12.75 3.031l.002-.004m0 0a8.955 8.955 0 00-4.943.834 8.974 8.974 0 004.943.834m4.943-.834a8.955 8.955 0 00-4.943-.834c2.687 0 5.18.948 7.161 2.664a8.974 8.974 0 014.943-.834z"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
].map((benefit, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group"
|
||||
>
|
||||
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
|
||||
{benefit.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{benefit.title}</h3>
|
||||
<p className="text-sm text-[#666666] leading-relaxed">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-28 lg:py-32 px-4 sm:px-6 lg:px-8 bg-[#1a1a1a] text-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-white/60 mb-3 block">
|
||||
{t("stayConnected")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6">
|
||||
{t("joinCommunity")}
|
||||
</h2>
|
||||
<p className="text-white/70 mb-10 mx-auto text-lg">
|
||||
{t("newsletterText")}
|
||||
</p>
|
||||
<form className="flex flex-col sm:flex-row items-stretch justify-center max-w-md mx-auto gap-0">
|
||||
<input
|
||||
type="email"
|
||||
placeholder={t("emailPlaceholder")}
|
||||
className="flex-1 min-w-0 px-5 h-14 bg-white/10 border border-white/20 border-b-0 sm:border-b border-r-0 sm:border-r border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left rounded-t sm:rounded-l sm:rounded-tr-none"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-8 h-14 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0 rounded-b sm:rounded-r sm:rounded-bl-none"
|
||||
>
|
||||
{t("subscribe")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default function RootPage() {
|
||||
redirect("/sr");
|
||||
}
|
||||
@@ -5,19 +5,22 @@ import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { X, Minus, Plus, Trash2, ShoppingBag } from "lucide-react";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import { formatPrice } from "@/lib/saleor";
|
||||
|
||||
export default function CartDrawer() {
|
||||
const {
|
||||
checkout,
|
||||
isOpen,
|
||||
const t = useTranslations("Cart");
|
||||
const locale = useLocale();
|
||||
const {
|
||||
checkout,
|
||||
isOpen,
|
||||
isLoading,
|
||||
error,
|
||||
closeCart,
|
||||
removeLine,
|
||||
updateLine,
|
||||
getTotal,
|
||||
closeCart,
|
||||
removeLine,
|
||||
updateLine,
|
||||
getTotal,
|
||||
getLineCount,
|
||||
getLines,
|
||||
initCheckout,
|
||||
@@ -29,7 +32,6 @@ export default function CartDrawer() {
|
||||
const lineCount = getLineCount();
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// Initialize checkout on mount (only once)
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
initCheckout();
|
||||
@@ -37,7 +39,6 @@ export default function CartDrawer() {
|
||||
}
|
||||
}, [initialized]);
|
||||
|
||||
// Lock body scroll when cart is open
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
@@ -53,7 +54,6 @@ export default function CartDrawer() {
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<motion.div
|
||||
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50"
|
||||
initial={{ opacity: 0 }}
|
||||
@@ -61,8 +61,7 @@ export default function CartDrawer() {
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={closeCart}
|
||||
/>
|
||||
|
||||
{/* Drawer */}
|
||||
|
||||
<motion.div
|
||||
className="fixed top-0 right-0 bottom-0 w-full max-w-[420px] bg-white z-50 shadow-2xl flex flex-col"
|
||||
initial={{ x: "100%" }}
|
||||
@@ -70,21 +69,19 @@ export default function CartDrawer() {
|
||||
exit={{ x: "100%" }}
|
||||
transition={{ type: "tween", duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-5 border-b border-[#e5e5e5]">
|
||||
<h2 className="text-sm uppercase tracking-[0.1em] font-medium">
|
||||
Your Cart ({lineCount})
|
||||
{t("yourCart")} ({lineCount})
|
||||
</h2>
|
||||
<button
|
||||
onClick={closeCart}
|
||||
className="p-2 -mr-2 hover:bg-black/5 rounded-full transition-colors"
|
||||
aria-label="Close cart"
|
||||
aria-label={t("closeCart")}
|
||||
>
|
||||
<X className="w-5 h-5" strokeWidth={1.5} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
<AnimatePresence>
|
||||
{error && (
|
||||
<motion.div
|
||||
@@ -95,41 +92,39 @@ export default function CartDrawer() {
|
||||
>
|
||||
<div className="p-4 bg-red-50 border-b border-red-100">
|
||||
<p className="text-red-600 text-sm">{error}</p>
|
||||
<button
|
||||
<button
|
||||
onClick={clearError}
|
||||
className="text-red-600 text-xs underline mt-1 hover:no-underline"
|
||||
>
|
||||
Dismiss
|
||||
{t("dismiss")}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Cart Items */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{lines.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full px-6">
|
||||
<div className="w-16 h-16 rounded-full bg-[#f8f9fa] flex items-center justify-center mb-6">
|
||||
<ShoppingBag className="w-8 h-8 text-[#999999]" strokeWidth={1.5} />
|
||||
</div>
|
||||
<p className="text-[#666666] mb-2">Your cart is empty</p>
|
||||
<p className="text-[#666666] mb-2">{t("yourCartEmpty")}</p>
|
||||
<p className="text-sm text-[#999999] mb-8 text-center">
|
||||
Looks like you haven't added anything to your cart yet.
|
||||
{t("looksLikeEmpty")}
|
||||
</p>
|
||||
<Link
|
||||
href="/products"
|
||||
href={`/${locale}/products`}
|
||||
onClick={closeCart}
|
||||
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
Start Shopping
|
||||
{t("startShopping")}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 space-y-6">
|
||||
{lines.map((line) => (
|
||||
<div key={line.id} className="flex gap-4">
|
||||
{/* Product Image */}
|
||||
<div className="w-24 h-24 bg-[#f8f9fa] relative flex-shrink-0 overflow-hidden">
|
||||
{line.variant.product.media[0]?.url ? (
|
||||
<Image
|
||||
@@ -145,8 +140,7 @@ export default function CartDrawer() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Product Info */}
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-medium truncate">
|
||||
{line.variant.product.name}
|
||||
@@ -162,8 +156,7 @@ export default function CartDrawer() {
|
||||
line.variant.pricing?.price?.gross?.currency
|
||||
)}
|
||||
</p>
|
||||
|
||||
{/* Quantity Controls */}
|
||||
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<div className="flex items-center border border-[#e5e5e5]">
|
||||
<button
|
||||
@@ -184,13 +177,12 @@ export default function CartDrawer() {
|
||||
<Plus className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Remove Button */}
|
||||
|
||||
<button
|
||||
onClick={() => removeLine(line.id)}
|
||||
disabled={isLoading}
|
||||
className="p-2 text-[#999999] hover:text-red-500 transition-colors"
|
||||
aria-label="Remove item"
|
||||
aria-label={t("removeItem")}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" strokeWidth={1.5} />
|
||||
</button>
|
||||
@@ -202,65 +194,56 @@ export default function CartDrawer() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer with Checkout */}
|
||||
{lines.length > 0 && (
|
||||
<div className="border-t border-[#e5e5e5] bg-white">
|
||||
{/* Order Summary */}
|
||||
<div className="p-6 space-y-3">
|
||||
{/* Subtotal */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[#666666]">Subtotal</span>
|
||||
<span className="text-[#666666]">{t("subtotal")}</span>
|
||||
<span className="font-medium">
|
||||
{formatPrice(checkout?.subtotalPrice?.gross?.amount || 0)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Shipping */}
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[#666666]">Shipping</span>
|
||||
<span className="text-[#666666]">{t("shipping")}</span>
|
||||
<span className="text-[#666666]">
|
||||
{checkout?.shippingPrice?.gross?.amount
|
||||
{checkout?.shippingPrice?.gross?.amount
|
||||
? formatPrice(checkout.shippingPrice.gross.amount)
|
||||
: "Calculated at checkout"
|
||||
: t("calculatedAtCheckout")
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
|
||||
<div className="border-t border-[#e5e5e5] my-4" />
|
||||
|
||||
{/* Total */}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm uppercase tracking-[0.05em] font-medium">Total</span>
|
||||
<span className="text-sm uppercase tracking-[0.05em] font-medium">{t("total")}</span>
|
||||
<span className="text-lg font-medium">
|
||||
{formatPrice(total)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{(checkout?.subtotalPrice?.gross?.amount || 0) < 5000 && (
|
||||
<p className="text-xs text-[#666666] text-center">
|
||||
Free shipping on orders over {formatPrice(5000)}
|
||||
{t("freeShippingOver", { amount: formatPrice(5000) })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
|
||||
<div className="px-6 pb-6 space-y-3">
|
||||
{/* Checkout Button */}
|
||||
<Link
|
||||
href="/checkout"
|
||||
href={`/${locale}/checkout`}
|
||||
onClick={closeCart}
|
||||
className="block w-full py-4 bg-black text-white text-center text-sm uppercase tracking-[0.1em] font-medium hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
{isLoading ? "Processing..." : "Checkout"}
|
||||
{isLoading ? t("processing") : t("checkout")}
|
||||
</Link>
|
||||
|
||||
{/* Continue Shopping */}
|
||||
|
||||
<button
|
||||
onClick={closeCart}
|
||||
className="block w-full py-3 text-center text-sm text-[#666666] hover:text-black transition-colors"
|
||||
>
|
||||
Continue Shopping
|
||||
{t("continueShopping")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,4 +253,4 @@ export default function CartDrawer() {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const mediaLogos = [
|
||||
{ name: "VOGUE", style: "serif" },
|
||||
@@ -15,10 +16,10 @@ const mediaLogos = [
|
||||
|
||||
function LogoItem({ name }: { name: string }) {
|
||||
const isSerif = name === "VOGUE" || name === "ELLE" || name === "COSMOPOLITAN" || name === "Bazaar" || name === "GLAMOUR";
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center px-10 py-4 grayscale opacity-40 hover:grayscale-0 hover:opacity-100 transition-all duration-500 flex-shrink-0">
|
||||
<span
|
||||
<span
|
||||
className={`
|
||||
text-xl md:text-2xl tracking-[0.15em] text-white font-bold
|
||||
${isSerif ? 'font-serif italic' : 'font-sans uppercase'}
|
||||
@@ -34,6 +35,8 @@ function LogoItem({ name }: { name: string }) {
|
||||
}
|
||||
|
||||
export default function AsSeenIn() {
|
||||
const t = useTranslations("AsSeenIn");
|
||||
|
||||
return (
|
||||
<section className="py-12 bg-[#1a1a1a] overflow-hidden border-y border-white/10">
|
||||
<div className="container mx-auto px-4 mb-8">
|
||||
@@ -44,19 +47,14 @@ export default function AsSeenIn() {
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
As Featured In
|
||||
{t("title")}
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* Scrolling Marquee */}
|
||||
<div className="relative">
|
||||
{/* Left gradient fade */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-32 bg-gradient-to-r from-[#1a1a1a] to-transparent z-10 pointer-events-none" />
|
||||
|
||||
{/* Right gradient fade */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-32 bg-gradient-to-l from-[#1a1a1a] to-transparent z-10 pointer-events-none" />
|
||||
|
||||
{/* Marquee container */}
|
||||
|
||||
<div className="flex overflow-hidden">
|
||||
<motion.div
|
||||
className="flex items-center gap-16"
|
||||
@@ -72,11 +70,9 @@ export default function AsSeenIn() {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* First set of logos */}
|
||||
{mediaLogos.map((logo, index) => (
|
||||
<LogoItem key={`first-${index}`} name={logo.name} />
|
||||
))}
|
||||
{/* Duplicate for seamless loop */}
|
||||
{mediaLogos.map((logo, index) => (
|
||||
<LogoItem key={`second-${index}`} name={logo.name} />
|
||||
))}
|
||||
@@ -85,4 +81,4 @@ export default function AsSeenIn() {
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useState, useRef } from "react";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
|
||||
const results = [
|
||||
{
|
||||
@@ -25,6 +26,7 @@ const results = [
|
||||
];
|
||||
|
||||
function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
|
||||
const t = useTranslations("BeforeAfterGallery");
|
||||
const [sliderPosition, setSliderPosition] = useState(50);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -44,34 +46,30 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
|
||||
|
||||
return (
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Before/After Slider */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative aspect-[4/3] rounded-2xl overflow-hidden shadow-2xl cursor-ew-resize select-none"
|
||||
onMouseMove={handleMouseMove}
|
||||
onTouchMove={handleTouchMove}
|
||||
>
|
||||
{/* After Image */}
|
||||
<img
|
||||
src={result.afterImg}
|
||||
alt="After - Smooth skin"
|
||||
alt="After"
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Before Image (clipped) */}
|
||||
<div
|
||||
className="absolute inset-0 overflow-hidden"
|
||||
style={{ width: `${sliderPosition}%` }}
|
||||
>
|
||||
<img
|
||||
src={result.beforeImg}
|
||||
alt="Before - Wrinkled skin"
|
||||
alt="Before"
|
||||
className="absolute inset-0 h-full object-cover"
|
||||
style={{ width: `${100 / (sliderPosition / 100)}%`, maxWidth: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Slider Handle */}
|
||||
<div
|
||||
className="absolute top-0 bottom-0 w-1 bg-white shadow-lg cursor-ew-resize"
|
||||
style={{ left: `${sliderPosition}%`, transform: 'translateX(-50%)' }}
|
||||
@@ -83,16 +81,14 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Labels */}
|
||||
<div className="absolute top-3 left-3 bg-black/70 text-white px-3 py-1.5 rounded-full text-xs font-medium backdrop-blur-sm">
|
||||
BEFORE
|
||||
{t("before")}
|
||||
</div>
|
||||
<div className="absolute top-3 right-3 bg-black/70 text-white px-3 py-1.5 rounded-full text-xs font-medium backdrop-blur-sm">
|
||||
AFTER
|
||||
{t("after")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline and Rating */}
|
||||
<div className="flex items-center justify-center gap-4 mt-4">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -113,18 +109,19 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verified Badge */}
|
||||
<div className="flex items-center justify-center gap-1.5 mt-2">
|
||||
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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>
|
||||
<span className="text-xs text-green-700 font-medium">Verified</span>
|
||||
<span className="text-xs text-green-700 font-medium">{t("verified")}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BeforeAfterGallery() {
|
||||
const t = useTranslations("BeforeAfterGallery");
|
||||
const locale = useLocale();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const goToPrev = () => {
|
||||
@@ -146,14 +143,13 @@ export default function BeforeAfterGallery() {
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
Real Results
|
||||
{t("realResults")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">
|
||||
See the Transformation
|
||||
{t("seeTransformation")}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
{/* Desktop: Two transformations side by side */}
|
||||
<div className="hidden md:flex gap-6 max-w-6xl mx-auto">
|
||||
{results.map((result, index) => (
|
||||
<motion.div
|
||||
@@ -169,7 +165,6 @@ export default function BeforeAfterGallery() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile: Carousel with one transformation at a time */}
|
||||
<div className="md:hidden relative max-w-md mx-auto">
|
||||
<div className="overflow-hidden">
|
||||
<motion.div
|
||||
@@ -182,28 +177,26 @@ export default function BeforeAfterGallery() {
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Carousel Navigation */}
|
||||
<button
|
||||
onClick={goToPrev}
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center"
|
||||
aria-label="Previous transformation"
|
||||
aria-label="Previous"
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={goToNext}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center"
|
||||
aria-label="Next transformation"
|
||||
aria-label="Next"
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dot Indicators */}
|
||||
<div className="flex justify-center gap-2 mt-6">
|
||||
{results.map((_, index) => (
|
||||
<button
|
||||
@@ -212,13 +205,12 @@ export default function BeforeAfterGallery() {
|
||||
className={`w-2 h-2 rounded-full transition-all ${
|
||||
selectedIndex === index ? "bg-black w-4" : "bg-gray-300"
|
||||
}`}
|
||||
aria-label={`Go to transformation ${index + 1}`}
|
||||
aria-label={`Go to ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<motion.div
|
||||
className="text-center mt-12"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -227,13 +219,13 @@ export default function BeforeAfterGallery() {
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
>
|
||||
<a
|
||||
href="/products"
|
||||
href={`/${locale}/products`}
|
||||
className="inline-block px-10 py-4 bg-black text-white text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-[#333] transition-colors"
|
||||
>
|
||||
Start Your Transformation
|
||||
{t("startTransformation")}
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
export default function Hero() {
|
||||
const locale = useLocale();
|
||||
return (
|
||||
<section className="relative h-screen min-h-[600px] flex items-center justify-center overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-background-ice/50 to-background" />
|
||||
@@ -48,7 +50,7 @@ export default function Hero() {
|
||||
transition={{ duration: 0.8, delay: 0.8 }}
|
||||
>
|
||||
<Link
|
||||
href="/en/products"
|
||||
href={`/${locale}/products`}
|
||||
className="inline-block px-10 py-4 bg-foreground text-white text-lg tracking-wide hover:bg-accent-dark transition-colors duration-300"
|
||||
>
|
||||
Shop Now
|
||||
|
||||
@@ -5,8 +5,13 @@ import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export default function HeroVideo() {
|
||||
interface HeroVideoProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export default function HeroVideo({ locale = "sr" }: HeroVideoProps) {
|
||||
const t = useTranslations("Home.hero");
|
||||
const localePath = `/${locale}`;
|
||||
|
||||
const scrollToContent = () => {
|
||||
const element = document.getElementById("main-content");
|
||||
@@ -84,13 +89,13 @@ export default function HeroVideo() {
|
||||
className="flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
>
|
||||
<Link
|
||||
href="/products"
|
||||
href={`${localePath}/products`}
|
||||
className="inline-block px-10 py-4 bg-white text-black text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-white/90 transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
{t("ctaButton")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
href={`${localePath}/about`}
|
||||
className="inline-block px-10 py-4 border border-white/50 text-white text-[13px] uppercase tracking-[0.15em] font-medium hover:bg-white/10 transition-all duration-300"
|
||||
>
|
||||
{t("learnStory")}
|
||||
|
||||
@@ -1,41 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
|
||||
export default function HowItWorks() {
|
||||
const steps = [
|
||||
{
|
||||
number: "01",
|
||||
title: "Choose Your Oil",
|
||||
description: "Select from our collection of pure, cold-pressed oils formulated for your specific hair and skin needs.",
|
||||
icon: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
title: "Apply Daily",
|
||||
description: "Massage a few drops into damp hair or skin. Our oils absorb instantly—never greasy, always nourishing.",
|
||||
icon: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
title: "See Results",
|
||||
description: "Experience transformation in 4-6 weeks. Shinier hair, radiant skin, and confidence that glows.",
|
||||
icon: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="#FFD700">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
const t = useTranslations("HowItWorks");
|
||||
const locale = useLocale();
|
||||
const steps = t.raw("steps") as Array<{ title: string; description: string }>;
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-gradient-to-b from-white to-[#faf9f7]">
|
||||
@@ -48,10 +19,10 @@ export default function HowItWorks() {
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
|
||||
Simple Process
|
||||
{t("title")}
|
||||
</span>
|
||||
<h2 className="text-4xl md:text-5xl font-medium text-[#1a1a1a]">
|
||||
How ManoonOils Works
|
||||
{t("subtitle")}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
|
||||
</motion.div>
|
||||
@@ -66,11 +37,10 @@ export default function HowItWorks() {
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.15 }}
|
||||
>
|
||||
{/* Connector line (not on last item) */}
|
||||
{index < steps.length - 1 && (
|
||||
<div className="hidden md:block absolute top-16 left-[55%] w-[90%] h-[2px]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#c9a962]/40 to-transparent rounded-full" />
|
||||
<motion.div
|
||||
<motion.div
|
||||
className="absolute inset-y-0 left-0 w-2 bg-[#FFD700] rounded-full"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
@@ -80,21 +50,33 @@ export default function HowItWorks() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step card */}
|
||||
|
||||
<div className="relative p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500">
|
||||
{/* Number badge */}
|
||||
<div className="absolute -top-5 left-1/2 -translate-x-1/2">
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#c9a962] to-[#FFD700] flex items-center justify-center shadow-lg">
|
||||
<span className="text-white text-lg font-bold">{step.number}</span>
|
||||
<span className="text-white text-lg font-bold">0{index + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Icon */}
|
||||
|
||||
<div className="w-20 h-20 mx-auto mt-4 mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
|
||||
{step.icon}
|
||||
{index === 0 && (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007z" />
|
||||
</svg>
|
||||
)}
|
||||
{index === 1 && (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||
</svg>
|
||||
)}
|
||||
{index === 2 && (
|
||||
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="#FFD700">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{step.title}</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed max-w-xs mx-auto">
|
||||
{step.description}
|
||||
@@ -104,7 +86,6 @@ export default function HowItWorks() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<motion.div
|
||||
className="text-center mt-20"
|
||||
initial={{ opacity: 0 }}
|
||||
@@ -113,10 +94,10 @@ export default function HowItWorks() {
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
>
|
||||
<a
|
||||
href="/products"
|
||||
href={`/${locale}/products`}
|
||||
className="group relative inline-flex items-center gap-3 px-12 py-5 bg-gradient-to-r from-[#1a1a1a] to-[#333333] text-white text-[13px] uppercase tracking-[0.2em] font-semibold hover:from-[#c9a962] hover:to-[#FFD700] transition-all duration-500 rounded-full shadow-lg hover:shadow-xl"
|
||||
>
|
||||
<span>Start Your Transformation</span>
|
||||
<span>{t("startTransformation")}</span>
|
||||
<svg className="w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17.25 8.25L21 12m0 0l-3.75 3.75M21 12H3" />
|
||||
</svg>
|
||||
@@ -125,4 +106,4 @@ export default function HowItWorks() {
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { motion } from "framer-motion";
|
||||
import { Star, ShoppingBag } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import type { Product } from "@/types/saleor";
|
||||
import { getProductPrice, getProductImage, formatPrice, parseDescription } from "@/lib/saleor";
|
||||
@@ -13,6 +14,7 @@ interface NewHeroProps {
|
||||
}
|
||||
|
||||
export default function NewHero({ featuredProduct }: NewHeroProps) {
|
||||
const locale = useLocale();
|
||||
const { addLine, openCart } = useSaleorCheckoutStore();
|
||||
|
||||
const handleAddToCart = async () => {
|
||||
@@ -150,13 +152,13 @@ export default function NewHero({ featuredProduct }: NewHeroProps) {
|
||||
|
||||
<div className="flex gap-4 justify-end">
|
||||
<Link
|
||||
href="/products"
|
||||
href={`/${locale}/products`}
|
||||
className="inline-block bg-[#1A1A1A] text-white px-8 py-4 text-sm tracking-wide hover:bg-[#1A1A1A]/90 transition-colors"
|
||||
>
|
||||
Shop Collection
|
||||
</Link>
|
||||
<Link
|
||||
href="/about"
|
||||
href={`/${locale}/about`}
|
||||
className="inline-block border border-[#1A1A1A] text-[#1A1A1A] px-8 py-4 text-sm tracking-wide hover:bg-[#1A1A1A] hover:text-white transition-colors"
|
||||
>
|
||||
Our Story
|
||||
@@ -168,7 +170,7 @@ export default function NewHero({ featuredProduct }: NewHeroProps) {
|
||||
{/* Mobile CTA */}
|
||||
<div className="lg:hidden relative z-10 px-6 pb-12">
|
||||
<Link
|
||||
href="/products"
|
||||
href={`/${locale}/products`}
|
||||
className="block w-full bg-[#1A1A1A] text-white text-center py-4 text-sm tracking-wide"
|
||||
>
|
||||
Shop Now
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
|
||||
export default function NewsletterSection() {
|
||||
const t = useTranslations("Newsletter");
|
||||
const [email, setEmail] = useState("");
|
||||
const [status, setStatus] = useState<"idle" | "success" | "error">("idle");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// TODO: Connect to newsletter service
|
||||
setStatus("success");
|
||||
setEmail("");
|
||||
};
|
||||
@@ -26,9 +27,7 @@ export default function NewsletterSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
className="font-serif italic text-4xl lg:text-5xl xl:text-[3.5rem] text-[#1A1A1A] tracking-tight leading-[1.1] mb-6"
|
||||
>
|
||||
Get 10% off your
|
||||
<br />
|
||||
first order
|
||||
{t("stayConnected")}
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
@@ -38,8 +37,7 @@ export default function NewsletterSection() {
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-[#4A4A4A] mb-8"
|
||||
>
|
||||
Join the ManoonOils community and receive exclusive offers,
|
||||
skincare tips, and early access to new products.
|
||||
{t("newsletterText")}
|
||||
</motion.p>
|
||||
|
||||
<motion.form
|
||||
@@ -54,7 +52,7 @@ export default function NewsletterSection() {
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Enter your email"
|
||||
placeholder={t("emailPlaceholder")}
|
||||
required
|
||||
className="flex-1 px-4 py-3 border border-[#1A1A1A]/10 rounded-[4px] text-sm focus:outline-none focus:border-[#1A1A1A]/30 transition-colors"
|
||||
/>
|
||||
@@ -62,7 +60,7 @@ export default function NewsletterSection() {
|
||||
type="submit"
|
||||
className="inline-flex items-center justify-center gap-2 bg-[#1A1A1A] text-white px-6 py-3 text-sm font-medium hover:bg-[#1A1A1A]/90 transition-colors rounded-[4px]"
|
||||
>
|
||||
Subscribe
|
||||
{t("subscribe")}
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</motion.form>
|
||||
@@ -73,7 +71,7 @@ export default function NewsletterSection() {
|
||||
animate={{ opacity: 1 }}
|
||||
className="text-sm text-emerald-600 mt-4"
|
||||
>
|
||||
Thank you! Check your email for your discount code.
|
||||
Hvala vam! Proverite email za vaš kod za popust.
|
||||
</motion.p>
|
||||
)}
|
||||
|
||||
@@ -84,11 +82,10 @@ export default function NewsletterSection() {
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="text-xs text-[#4A4A4A]/60 mt-4"
|
||||
>
|
||||
By subscribing, you agree to our Privacy Policy. Unsubscribe
|
||||
anytime.
|
||||
Prijavom prihvatate našu Politiku privatnosti. Možete se odjaviti bilo kada.
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function ProblemSection() {
|
||||
const t = useTranslations("ProblemSection");
|
||||
const problems = t.raw("problems") as Array<{ problem: string; description: string }>;
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -14,47 +18,19 @@ export default function ProblemSection() {
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
|
||||
The Problem
|
||||
{t("title")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6 leading-tight text-[#1a1a1a]">
|
||||
Tired of Hair & Skin Products That Don't Deliver?
|
||||
{t("subtitle")}
|
||||
</h2>
|
||||
<p className="text-[#666666] text-lg max-w-xl mx-auto">
|
||||
You deserve better than products filled with harsh chemicals and empty promises
|
||||
{t("description")}
|
||||
</p>
|
||||
<div className="w-16 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-8 rounded-full" />
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8 max-w-5xl mx-auto mt-16">
|
||||
{[
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
problem: "Dry, Damaged Hair",
|
||||
description: "Products leave your hair brittle, frizzy, and breaking despite expensive treatments",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#e8967a" strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
),
|
||||
problem: "Confusing Ingredients",
|
||||
description: "Can't pronounce what's in your skincare. parabens, sulfates, synthetic fragrances—dangerous toxins",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#7eb89e" strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
),
|
||||
problem: "No Real Results",
|
||||
description: "Countless products promise miracles but deliver nothing but empty promises and wasted money",
|
||||
},
|
||||
].map((item, index) => (
|
||||
{problems.map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group"
|
||||
@@ -64,11 +40,24 @@ export default function ProblemSection() {
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{ y: -5 }}
|
||||
>
|
||||
{/* Decorative top border */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-20 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] rounded-b-full opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
|
||||
|
||||
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
|
||||
{item.icon}
|
||||
{index === 0 && (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
)}
|
||||
{index === 1 && (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#e8967a" strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
)}
|
||||
{index === 2 && (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
|
||||
<path stroke="#7eb89e" strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-[#1a1a1a] mb-3">{item.problem}</h3>
|
||||
<p className="text-sm text-[#666666] leading-relaxed">{item.description}</p>
|
||||
@@ -78,4 +67,4 @@ export default function ProblemSection() {
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const badges = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-6 h-6 text-yellow-400" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
),
|
||||
stats: "4.9/5",
|
||||
label: "Average Rating",
|
||||
subtext: "Based on 1000+ reviews",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
),
|
||||
stats: "50,000+",
|
||||
label: "Happy Customers",
|
||||
subtext: "Worldwide",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#7eb89e" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
|
||||
</svg>
|
||||
),
|
||||
stats: "100%",
|
||||
label: "Natural Ingredients",
|
||||
subtext: "No additives",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#e8967a" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" />
|
||||
</svg>
|
||||
),
|
||||
stats: "Free",
|
||||
label: "Shipping",
|
||||
subtext: "Orders over 3,000 RSD",
|
||||
},
|
||||
];
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function TrustBadges() {
|
||||
const t = useTranslations("TrustBadges");
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -56,32 +16,103 @@ export default function TrustBadges() {
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
{badges.map((badge, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: index * 0.1 }}
|
||||
whileHover={{ y: -3 }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
|
||||
{badge.icon}
|
||||
</div>
|
||||
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
|
||||
{badge.stats}
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
|
||||
{badge.label}
|
||||
</p>
|
||||
<p className="text-xs text-[#888888] mt-0.5">
|
||||
{badge.subtext}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0 }}
|
||||
whileHover={{ y: -3 }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
|
||||
<svg className="w-6 h-6 text-yellow-400" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
|
||||
4.9/5
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
|
||||
{t("averageRating")}
|
||||
</p>
|
||||
<p className="text-xs text-[#888888] mt-0.5">
|
||||
{t("basedOnReviews")}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0.1 }}
|
||||
whileHover={{ y: -3 }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
|
||||
50,000+
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
|
||||
{t("happyCustomers")}
|
||||
</p>
|
||||
<p className="text-xs text-[#888888] mt-0.5">
|
||||
{t("worldwide")}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0.2 }}
|
||||
whileHover={{ y: -3 }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#7eb89e" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
|
||||
100%
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
|
||||
{t("naturalIngredients")}
|
||||
</p>
|
||||
<p className="text-xs text-[#888888] mt-0.5">
|
||||
{t("noAdditives")}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: 0.3 }}
|
||||
whileHover={{ y: -3 }}
|
||||
>
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
|
||||
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#e8967a" strokeWidth="1.5">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
|
||||
Free
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
|
||||
{t("freeShipping")}
|
||||
</p>
|
||||
<p className="text-xs text-[#888888] mt-0.5">
|
||||
{t("ordersOver")}
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Instagram, Facebook } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const footerLinks = {
|
||||
shop: [
|
||||
{ label: "All Products", href: "/products" },
|
||||
{ label: "Hair Care", href: "/products" },
|
||||
{ label: "Skin Care", href: "/products" },
|
||||
{ label: "Gift Sets", href: "/products" },
|
||||
],
|
||||
about: [
|
||||
{ label: "Our Story", href: "/about" },
|
||||
{ label: "Process", href: "/about" },
|
||||
{ label: "Sustainability", href: "/about" },
|
||||
],
|
||||
help: [
|
||||
{ label: "FAQ", href: "/contact" },
|
||||
{ label: "Shipping", href: "/contact" },
|
||||
{ label: "Returns", href: "/contact" },
|
||||
{ label: "Contact Us", href: "/contact" },
|
||||
],
|
||||
};
|
||||
interface FooterProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export default function Footer() {
|
||||
export default function Footer({ locale = "sr" }: FooterProps) {
|
||||
const t = useTranslations("Footer");
|
||||
const currentYear = new Date().getFullYear();
|
||||
const localePath = `/${locale}`;
|
||||
|
||||
const footerLinks = {
|
||||
shop: [
|
||||
{ label: t("allProducts"), href: `${localePath}/products` },
|
||||
{ label: t("hairCare"), href: `${localePath}/products` },
|
||||
{ label: t("skinCare"), href: `${localePath}/products` },
|
||||
{ label: t("giftSets"), href: `${localePath}/products` },
|
||||
],
|
||||
about: [
|
||||
{ label: t("ourStory"), href: `${localePath}/about` },
|
||||
{ label: t("process"), href: `${localePath}/about` },
|
||||
{ label: t("sustainability"), href: `${localePath}/about` },
|
||||
],
|
||||
help: [
|
||||
{ label: t("faq"), href: `${localePath}/contact` },
|
||||
{ label: t("shipping"), href: `${localePath}/contact` },
|
||||
{ label: t("returns"), href: `${localePath}/contact` },
|
||||
{ label: t("contactUs"), href: `${localePath}/contact` },
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="bg-white border-t border-[#e5e5e5]">
|
||||
{/* Main Footer */}
|
||||
<div className="container py-16 lg:py-20">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8">
|
||||
{/* Brand Column */}
|
||||
<div className="lg:col-span-4">
|
||||
<Link href="/" className="inline-block mb-6">
|
||||
<Link href={localePath} className="inline-block mb-6">
|
||||
<Image
|
||||
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
||||
alt="ManoonOils"
|
||||
@@ -42,9 +49,8 @@ export default function Footer() {
|
||||
/>
|
||||
</Link>
|
||||
<p className="text-[#666666] text-sm leading-relaxed max-w-xs mb-6">
|
||||
Premium natural oils for hair and skin care. Handcrafted with love using traditional methods.
|
||||
{t("brandDescription")}
|
||||
</p>
|
||||
{/* Social Links */}
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href="https://instagram.com"
|
||||
@@ -67,13 +73,11 @@ export default function Footer() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links Columns - All aligned at top */}
|
||||
<div className="lg:col-span-8">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-8">
|
||||
{/* Shop */}
|
||||
<div className="flex flex-col">
|
||||
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
|
||||
Shop
|
||||
{t("shop")}
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.shop.map((link) => (
|
||||
@@ -89,10 +93,9 @@ export default function Footer() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* About */}
|
||||
<div className="flex flex-col">
|
||||
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
|
||||
About
|
||||
{t("about")}
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.about.map((link) => (
|
||||
@@ -108,10 +111,9 @@ export default function Footer() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Help */}
|
||||
<div className="flex flex-col">
|
||||
<h4 className="text-xs uppercase tracking-[0.15em] font-medium mb-5 text-[#1a1a1a]">
|
||||
Help
|
||||
{t("help")}
|
||||
</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.help.map((link) => (
|
||||
@@ -131,18 +133,15 @@ export default function Footer() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="border-t border-[#e5e5e5]">
|
||||
<div className="container py-6">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
{/* Copyright */}
|
||||
<p className="text-xs text-[#999999]">
|
||||
© {currentYear} ManoonOils. All rights reserved.
|
||||
© {currentYear} ManoonOils. {t("allRights")}
|
||||
</p>
|
||||
|
||||
{/* Payment Methods */}
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-[#999999]">We accept:</span>
|
||||
<span className="text-xs text-[#999999]">{t("weAccept")}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-[#666666] px-2 py-1 border border-[#e5e5e5] rounded">
|
||||
Visa
|
||||
@@ -160,4 +159,4 @@ export default function Footer() {
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,28 @@ import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
|
||||
import { User, ShoppingBag, Menu, X } from "lucide-react";
|
||||
import CartDrawer from "@/components/cart/CartDrawer";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/products", label: "Products" },
|
||||
{ href: "/about", label: "About" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
];
|
||||
interface HeaderProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
export default function Header({ locale = "sr" }: HeaderProps) {
|
||||
const t = useTranslations("Header");
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const { getLineCount, toggleCart, initCheckout } = useSaleorCheckoutStore();
|
||||
|
||||
const itemCount = getLineCount();
|
||||
const localePath = `/${locale}`;
|
||||
|
||||
// Initialize checkout on mount
|
||||
useEffect(() => {
|
||||
initCheckout();
|
||||
}, [initCheckout]);
|
||||
|
||||
// Track scroll for header styling
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrolled(window.scrollY > 50);
|
||||
@@ -35,7 +34,6 @@ export default function Header() {
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// Lock body scroll when mobile menu is open
|
||||
useEffect(() => {
|
||||
if (mobileMenuOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
@@ -47,6 +45,12 @@ export default function Header() {
|
||||
};
|
||||
}, [mobileMenuOpen]);
|
||||
|
||||
const navLinks = [
|
||||
{ href: `${localePath}/products`, label: t("products") },
|
||||
{ href: `${localePath}/about`, label: t("about") },
|
||||
{ href: `${localePath}/contact`, label: t("contact") },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
@@ -57,16 +61,14 @@ export default function Header() {
|
||||
}`}
|
||||
>
|
||||
<div className="relative flex items-center justify-between h-[72px] px-4 lg:px-6">
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
className="lg:hidden p-2 -ml-2 hover:bg-black/5 rounded-full transition-colors"
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
aria-label="Open menu"
|
||||
aria-label={t("openMenu")}
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Left side - Desktop Nav */}
|
||||
<nav className="hidden lg:flex items-center gap-10">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
@@ -80,8 +82,7 @@ export default function Header() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Logo - Centered (absolute on desktop, flex on mobile) */}
|
||||
<Link href="/" className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2">
|
||||
<Link href={localePath || "/"} className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2">
|
||||
<Image
|
||||
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
||||
alt="ManoonOils"
|
||||
@@ -92,11 +93,10 @@ export default function Header() {
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Right side - Icons */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
className="p-2 hover:bg-black/5 rounded-full transition-colors hidden sm:block"
|
||||
aria-label="Account"
|
||||
aria-label={t("account")}
|
||||
>
|
||||
<User className="w-5 h-5" strokeWidth={1.5} />
|
||||
</button>
|
||||
@@ -104,7 +104,7 @@ export default function Header() {
|
||||
<button
|
||||
className="p-2 hover:bg-black/5 rounded-full transition-colors relative"
|
||||
onClick={toggleCart}
|
||||
aria-label="Open cart"
|
||||
aria-label={t("openCart")}
|
||||
>
|
||||
<ShoppingBag className="w-5 h-5" strokeWidth={1.5} />
|
||||
{itemCount > 0 && (
|
||||
@@ -117,7 +117,6 @@ export default function Header() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Mobile Menu Overlay */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
@@ -128,9 +127,8 @@ export default function Header() {
|
||||
className="fixed inset-0 z-[60] bg-white"
|
||||
>
|
||||
<div className="container h-full flex flex-col">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between h-[72px]">
|
||||
<Link href="/" onClick={() => setMobileMenuOpen(false)}>
|
||||
<Link href={localePath || "/"} onClick={() => setMobileMenuOpen(false)}>
|
||||
<Image
|
||||
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
|
||||
alt="ManoonOils"
|
||||
@@ -142,13 +140,12 @@ export default function Header() {
|
||||
<button
|
||||
className="p-2 -mr-2 hover:bg-black/5 rounded-full transition-colors"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
aria-label="Close menu"
|
||||
aria-label={t("closeMenu")}
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation */}
|
||||
<nav className="flex-1 flex flex-col justify-center gap-8">
|
||||
{navLinks.map((link, index) => (
|
||||
<motion.div
|
||||
@@ -168,7 +165,6 @@ export default function Header() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Mobile Footer */}
|
||||
<div className="py-8 border-t border-[#e5e5e5]">
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
@@ -179,13 +175,13 @@ export default function Header() {
|
||||
}}
|
||||
>
|
||||
<ShoppingBag className="w-5 h-5" strokeWidth={1.5} />
|
||||
Cart ({itemCount})
|
||||
{t("cart")} ({itemCount})
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-2 text-sm text-[#666666] hover:text-black transition-colors"
|
||||
>
|
||||
<User className="w-5 h-5" strokeWidth={1.5} />
|
||||
Account
|
||||
{t("account")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,4 +193,4 @@ export default function Header() {
|
||||
<CartDrawer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ProductBenefitsProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}>
|
||||
<path stroke="#c9a962" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
|
||||
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
|
||||
</svg>
|
||||
),
|
||||
title: "Pure & Natural",
|
||||
description: "100% natural ingredients with no additives or preservatives",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#e8967a"/>
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: "Cruelty Free",
|
||||
description: "Never tested on animals, ethically sourced ingredients",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: "Made with Love",
|
||||
description: "Handcrafted in small batches for maximum quality",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="#c9a962"/>
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="#b8944f" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: "Visible Results",
|
||||
description: "See noticeable improvements in 4-6 weeks",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ProductBenefits({ locale = "SR" }: ProductBenefitsProps) {
|
||||
const t = useTranslations("ProductBenefits");
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}>
|
||||
<path stroke="#c9a962" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
|
||||
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
|
||||
</svg>
|
||||
),
|
||||
title: t("pureNatural"),
|
||||
description: t("pureNaturalDesc"),
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#e8967a"/>
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: t("crueltyFree"),
|
||||
description: t("crueltyFreeDesc"),
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: t("madeWithLove"),
|
||||
description: t("madeWithLoveDesc"),
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="#c9a962"/>
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="#b8944f" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
),
|
||||
title: t("visibleResults"),
|
||||
description: t("visibleResultsDesc"),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-gradient-to-b from-white to-[#faf9f7]">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -61,10 +64,10 @@ export default function ProductBenefits({ locale = "SR" }: ProductBenefitsProps)
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#c9a962] mb-3 block font-medium">
|
||||
{locale === "EN" ? "Why Choose This Product" : "Zašto odabrati ovaj proizvod"}
|
||||
{t("whyChoose")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium">
|
||||
{locale === "EN" ? "The Manoon Difference" : "Manoon razlika"}
|
||||
{t("manoonDifference")}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
@@ -90,4 +93,4 @@ export default function ProductBenefits({ locale = "SR" }: ProductBenefitsProps)
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { Product } from "@/types/saleor";
|
||||
import { getProductPrice, getProductImage, getLocalizedProduct } from "@/lib/saleor";
|
||||
|
||||
@@ -13,10 +14,12 @@ interface ProductCardProps {
|
||||
}
|
||||
|
||||
export default function ProductCard({ product, index = 0, locale = "SR" }: ProductCardProps) {
|
||||
const t = useTranslations("ProductCard");
|
||||
const image = getProductImage(product);
|
||||
const price = getProductPrice(product);
|
||||
const localized = getLocalizedProduct(product, locale);
|
||||
const isAvailable = product.variants?.[0]?.quantityAvailable > 0;
|
||||
const urlLocale = locale === "SR" ? "sr" : "en";
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -25,8 +28,7 @@ export default function ProductCard({ product, index = 0, locale = "SR" }: Produ
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<Link href={`/products/${localized.slug}`} className="group block">
|
||||
{/* Image Container */}
|
||||
<Link href={`/${urlLocale}/products/${localized.slug}`} className="group block">
|
||||
<div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden mb-4">
|
||||
{image ? (
|
||||
<img
|
||||
@@ -37,44 +39,40 @@ export default function ProductCard({ product, index = 0, locale = "SR" }: Produ
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center text-[#999999]">
|
||||
<span className="text-sm">No image</span>
|
||||
<span className="text-sm">{t("noImage")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Out of Stock Overlay */}
|
||||
|
||||
{!isAvailable && (
|
||||
<div className="absolute inset-0 bg-white/80 flex items-center justify-center">
|
||||
<span className="text-sm uppercase tracking-[0.1em] text-[#666666]">
|
||||
{locale === "EN" ? "Out of Stock" : "Nema na stanju"}
|
||||
{t("outOfStock")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hover Quick Add (optional) */}
|
||||
<div className="absolute inset-x-0 bottom-0 p-4 translate-y-full group-hover:translate-y-0 transition-transform duration-300">
|
||||
<button
|
||||
<button
|
||||
className="w-full py-3 bg-black text-white text-xs uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
// Quick add functionality can be added here
|
||||
}}
|
||||
>
|
||||
{locale === "EN" ? "Quick Add" : "Dodaj u korpu"}
|
||||
{t("quickAdd")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Info */}
|
||||
|
||||
<div className="text-center">
|
||||
<h3 className="text-[15px] font-medium text-[#1a1a1a] mb-1 group-hover:text-[#666666] transition-colors line-clamp-1">
|
||||
{localized.name}
|
||||
</h3>
|
||||
|
||||
|
||||
<p className="text-[14px] text-[#666666]">
|
||||
{price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")}
|
||||
{price || t("contactForPrice")}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface Review {
|
||||
id: number;
|
||||
name: string;
|
||||
location: string;
|
||||
text: string;
|
||||
rating: number;
|
||||
}
|
||||
|
||||
interface ProductReviewsProps {
|
||||
locale?: string;
|
||||
productName?: string;
|
||||
}
|
||||
|
||||
const reviews = [
|
||||
{ id: 1, name: "Ana M.", location: "Belgrade", text: "Manoon Anti-age Serum transformed my skin in just 2 weeks!", rating: 5 },
|
||||
{ id: 2, name: "Milica P.", location: "Novi Sad", text: "The best day serum I've ever used. My wrinkles are visibly reduced.", rating: 5 },
|
||||
{ id: 3, name: "Jelena K.", location: "Belgrade", text: "Manoon night serum is pure magic. Wake up with glowing skin every morning.", rating: 5 },
|
||||
{ id: 4, name: "Stefan R.", location: "Subotica", text: "The Anti-age Set is worth every dinar. My wife and I both use it.", rating: 5 },
|
||||
{ id: 5, name: "Marija T.", location: "Kragujevac", text: "Finally found a serum that actually works! Manoon delivers on its promises.", rating: 5 },
|
||||
{ id: 6, name: "Nikola V.", location: "Niš", text: "My fine lines are disappearing. This day serum is incredible.", rating: 5 },
|
||||
{ id: 7, name: "Ivana L.", location: "Belgrade", text: "Manoon morning glow serum smells divine and works even better.", rating: 5 },
|
||||
{ id: 8, name: "Dejan M.", location: "Novi Sad", text: "The night serum has transformed my skincare routine completely.", rating: 5 },
|
||||
{ id: 9, name: "Sanja B.", location: "Kragujevac", text: "My skin looks 10 years younger after using Manoon for a month.", rating: 5 },
|
||||
{ id: 10, name: "Marko J.", location: "Subotica", text: "The anti-age set makes a perfect gift. My mother loves it!", rating: 5 },
|
||||
{ id: 11, name: "Petra D.", location: "Niš", text: "The texture of Manoon serum is so luxurious. Worth every penny.", rating: 5 },
|
||||
{ id: 12, name: "Luka G.", location: "Belgrade", text: "Day serum absorbs instantly. No greasy feeling at all!", rating: 5 },
|
||||
{ id: 13, name: "Maja S.", location: "Novi Sad", text: "My esthetician asked what I'm using. Manoon is now my secret!", rating: 5 },
|
||||
{ id: 14, name: "Vladimir P.", location: "Kragujevac", text: "The night serum works while I sleep. Wake up to visibly smoother skin.", rating: 5 },
|
||||
{ id: 15, name: "Katarina N.", location: "Subotica", text: "The Anti-age Set arrived beautifully packaged. Perfect for gifting.", rating: 5 },
|
||||
{ id: 16, name: "Bojan R.", location: "Niš", text: "Been using Manoon for 3 months. My wrinkles are noticeably reduced.", rating: 5 },
|
||||
{ id: 17, name: "Tamara F.", location: "Belgrade", text: "The day serum provides the perfect base under makeup.", rating: 5 },
|
||||
{ id: 18, name: "Aleksandar K.", location: "Novi Sad", text: "Finally a Serbian brand that competes with luxury international brands!", rating: 5 },
|
||||
{ id: 19, name: "Natalia M.", location: "Kragujevac", text: "My sensitive skin loves Manoon. No irritation at all.", rating: 5 },
|
||||
{ id: 20, name: "Filip T.", location: "Subotica", text: "The anti-age serum is lightweight yet incredibly effective.", rating: 5 },
|
||||
{ id: 21, name: "Andrea L.", location: "Niš", text: "Manoon night serum is my evening ritual. Skin looks amazing!", rating: 5 },
|
||||
{ id: 22, name: "Ognjen P.", location: "Belgrade", text: "My friends keep asking what changed in my skincare routine.", rating: 5 },
|
||||
{ id: 23, name: "Mila J.", location: "Novi Sad", text: "The Anti-age Set includes everything you need. Great value!", rating: 5 },
|
||||
{ id: 24, name: "Dragan S.", location: "Kragujevac", text: "Even my husband noticed the difference. He now uses the day serum too!", rating: 5 },
|
||||
{ id: 25, name: "Jovana V.", location: "Subotica", text: "The morning glow serum gives the most beautiful luminosity.", rating: 5 },
|
||||
{ id: 26, name: "Stefan M.", location: "Niš", text: "Manoon products are now essential in my daily routine.", rating: 5 },
|
||||
{ id: 27, name: "Ana R.", location: "Belgrade", text: "The night serum helped clear my complexion. Skin looks so healthy!", rating: 5 },
|
||||
{ id: 28, name: "Nenad L.", location: "Novi Sad", text: "Anti-aging results visible within weeks. Highly recommend Manoon!", rating: 5 },
|
||||
{ id: 29, name: "Sofija D.", location: "Kragujevac", text: "The texture is divine. Feels like a luxury spa treatment at home.", rating: 5 },
|
||||
{ id: 30, name: "Velibor K.", location: "Subotica", text: "My crow's feet have diminished significantly. Thank you Manoon!", rating: 5 },
|
||||
{ id: 31, name: "Irena M.", location: "Niš", text: "The Anti-age Set makes the perfect birthday gift for my mother.", rating: 5 },
|
||||
{ id: 32, name: "Radoslav P.", location: "Belgrade", text: "Professional quality serum at an honest price. Serbian excellence!", rating: 5 },
|
||||
{ id: 33, name: "Jelena B.", location: "Novi Sad", text: "My skin has never been this hydrated. Day serum is amazing!", rating: 5 },
|
||||
{ id: 34, name: "Dimitrije S.", location: "Kragujevac", text: "The night serum is worth its weight in gold. Pure luxury!", rating: 5 },
|
||||
{ id: 35, name: "Minela G.", location: "Subotica", text: "Manoon lives up to the hype. My skin looks refreshed and young.", rating: 5 },
|
||||
{ id: 36, name: "Zoran T.", location: "Niš", text: "I've tried many serums. Manoon is by far the most effective.", rating: 5 },
|
||||
{ id: 37, name: "Mirjana F.", location: "Belgrade", text: "The Anti-age Set transformed my mother's skincare routine completely.", rating: 5 },
|
||||
{ id: 38, name: "Ivan J.", location: "Novi Sad", text: "Fast-acting serum with real results. I recommend Manoon to everyone.", rating: 5 },
|
||||
{ id: 39, name: "Kristina P.", location: "Kragujevac", text: "The morning glow serum gives such a beautiful dewy finish.", rating: 5 },
|
||||
{ id: 40, name: "Bratislav L.", location: "Subotica", text: "Noticeable results in just 2 weeks. This serum is the real deal!", rating: 5 },
|
||||
{ id: 41, name: "Zorica M.", location: "Niš", text: "The night serum erased years from my face. Absolutely miraculous!", rating: 5 },
|
||||
{ id: 42, name: "Patrik N.", location: "Belgrade", text: "Premium quality Serbian skincare that rivals international luxury brands.", rating: 5 },
|
||||
{ id: 43, name: "Simona K.", location: "Novi Sad", text: "Manoon Anti-age Serum is the best investment in my skin ever.", rating: 5 },
|
||||
{ id: 44, name: "Mladen D.", location: "Kragujevac", text: "The day serum absorbs in seconds. No waiting around!", rating: 5 },
|
||||
{ id: 45, name: "Ljiljana R.", location: "Subotica", text: "Gifting the Anti-age Set to my sisters. They loved it!", rating: 5 },
|
||||
{ id: 46, name: "Tomislav V.", location: "Niš", text: "My wrinkles are visibly reduced after using Manoon for a month.", rating: 5 },
|
||||
{ id: 47, name: "Emilija S.", location: "Belgrade", text: "The night serum leaves my skin so soft and renewed every morning.", rating: 5 },
|
||||
{ id: 48, name: "Andrija P.", location: "Novi Sad", text: "Manoon day serum is perfect under sunscreen. Essential duo!", rating: 5 },
|
||||
{ id: 49, name: "Miona L.", location: "Kragujevac", text: "My skin looks radiant and youthful. Couldn't be happier with Manoon!", rating: 5 },
|
||||
{ id: 50, name: "Slavko M.", location: "Subotica", text: "The Anti-age Set delivers visible results. True Serbian quality!", rating: 5 },
|
||||
];
|
||||
|
||||
function ReviewCard({ review }: { review: typeof reviews[0] }) {
|
||||
function ReviewCard({ review }: { review: Review }) {
|
||||
return (
|
||||
<div className="flex-shrink-0 w-80 bg-white p-6 rounded-2xl shadow-sm border border-[#f0ede8] mx-3">
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
@@ -90,7 +46,10 @@ function ReviewCard({ review }: { review: typeof reviews[0] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProductReviews({ locale = "SR", productName = "this product" }: ProductReviewsProps) {
|
||||
export default function ProductReviews(_props: ProductReviewsProps) {
|
||||
const t = useTranslations("ProductReviews");
|
||||
const reviews = t.raw("reviews") as Review[];
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-[#faf9f7] overflow-hidden">
|
||||
<div className="container mx-auto px-4 mb-8">
|
||||
@@ -102,12 +61,12 @@ export default function ProductReviews({ locale = "SR", productName = "this prod
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
Customer Reviews
|
||||
{t("customerReviews")}
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium">
|
||||
What Customers Say
|
||||
{t("whatCustomersSay")}
|
||||
</h2>
|
||||
|
||||
|
||||
<div className="flex items-center justify-center gap-4 mt-4">
|
||||
<span className="text-5xl font-bold text-[#1a1a1a]">4.9</span>
|
||||
<div>
|
||||
@@ -118,21 +77,16 @@ export default function ProductReviews({ locale = "SR", productName = "this prod
|
||||
</svg>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-sm text-[#666666] mt-1">Based on 1000+ reviews</p>
|
||||
<p className="text-sm text-[#666666] mt-1">{t("basedOnReviews")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scrolling Reviews Marquee */}
|
||||
<div className="relative">
|
||||
{/* Left gradient fade */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-20 bg-gradient-to-r from-[#faf9f7] to-transparent z-10 pointer-events-none" />
|
||||
|
||||
{/* Right gradient fade */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-20 bg-gradient-to-l from-[#faf9f7] to-transparent z-10 pointer-events-none" />
|
||||
|
||||
{/* First row - left to right */}
|
||||
|
||||
<div className="flex overflow-hidden mb-4">
|
||||
<motion.div
|
||||
className="flex items-center gap-0"
|
||||
@@ -154,7 +108,6 @@ export default function ProductReviews({ locale = "SR", productName = "this prod
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Second row - right to left */}
|
||||
<div className="flex overflow-hidden">
|
||||
<motion.div
|
||||
className="flex items-center gap-0"
|
||||
@@ -178,4 +131,4 @@ export default function ProductReviews({ locale = "SR", productName = "this prod
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
358
src/i18n/messages/de.json
Normal file
358
src/i18n/messages/de.json
Normal file
@@ -0,0 +1,358 @@
|
||||
{
|
||||
"Navigation": {
|
||||
"home": "Startseite",
|
||||
"products": "Produkte",
|
||||
"about": "Über uns",
|
||||
"contact": "Kontakt"
|
||||
},
|
||||
"Home": {
|
||||
"hero": {
|
||||
"title": "Premium Natürliche Öle",
|
||||
"subtitle": "Für Haar- und Hautpflege",
|
||||
"lovedBy": "Von 50.000+ Kunden weltweit geliebt",
|
||||
"transformHeadline": "Transformieren Sie Ihr Haar & Haut",
|
||||
"withNaturalOils": "mit 100% Natürlichen Ölen",
|
||||
"subtitleText": "Kaltgepresste, biologische Öle mit Liebe handgefertigt. Keine Zusatzstoffe, keine Konservierungsstoffe - nur die reinste Güte der Natur für Ihr tägliches Schönheitsritual.",
|
||||
"ctaButton": "Mein Haar & Haut transformieren",
|
||||
"learnStory": "Unsere Geschichte entdecken",
|
||||
"moneyBack": "30-Tage Geld-zurück",
|
||||
"freeShipping": "Kostenloser Versand über 3.000 RSD",
|
||||
"crueltyFree": "Tierversuchsfrei"
|
||||
},
|
||||
"collection": "Unsere Kollektion",
|
||||
"premiumOils": "Premium Natürliche Öle",
|
||||
"oilsDescription": "Kaltgepresste, reine und natürliche Öle für Ihre tägliche Schönheitsroutine",
|
||||
"viewAll": "Alle Produkte ansehen",
|
||||
"ourStory": "Unsere Geschichte",
|
||||
"handmadeWithLove": "Mit Liebe handgefertigt",
|
||||
"storyText1": "Jede Flasche ManoonOils wird mit Sorgfalt unter Verwendung traditioneller Methoden hergestellt, die von Generation zu Generation weitergegeben werden. Wir beziehen nur die feinsten biologischen Zutaten, um Ihnen Öle zu bringen, die Haar und Haut pflegen.",
|
||||
"storyText2": "Unser Engagement für Reinheit bedeutet keine Zusatzstoffe, keine Konservierungsstoffe - nur die Güte der Natur in ihrer potentesten Form.",
|
||||
"learnMore": "Mehr erfahren",
|
||||
"whyChooseUs": "Warum uns wählen",
|
||||
"manoonDifference": "Der Manoon Unterschied",
|
||||
"stayConnected": "Bleiben Sie verbunden",
|
||||
"joinCommunity": "Werden Sie Teil unserer Gemeinschaft",
|
||||
"newsletterText": "Abonnieren Sie, um exklusive Angebote, Schönheitstipps zu erhalten und als Erster über neue Produkte informiert zu werden.",
|
||||
"emailPlaceholder": "Geben Sie Ihre E-Mail ein",
|
||||
"subscribe": "Abonnieren"
|
||||
},
|
||||
"Benefits": {
|
||||
"natural": "100% Natürlich",
|
||||
"naturalDesc": "Reine, kaltgepresste Öle ohne Zusatzstoffe oder Konservierungsstoffe. Nur die Güte der Natur.",
|
||||
"handcrafted": "Handgefertigt",
|
||||
"handcraftedDesc": "Jede Charge wird sorgfältig von Hand zubereitet, um höchste Qualität zu gewährleisten.",
|
||||
"sustainable": "Nachhaltig",
|
||||
"sustainableDesc": "Ethnisch beschaffte Zutaten und umweltfreundliche Verpackungen für einen besseren Planeten."
|
||||
},
|
||||
"Products": {
|
||||
"collection": "Unsere Kollektion",
|
||||
"allProducts": "Alle Produkte",
|
||||
"productsCount": "{count} Produkte",
|
||||
"featured": "Empfohlen",
|
||||
"newest": "Neueste",
|
||||
"priceLow": "Preis: Aufsteigend",
|
||||
"priceHigh": "Preis: Absteigend",
|
||||
"noProducts": "Keine Produkte verfügbar",
|
||||
"checkBack": "Bitte schauen Sie später für neue Produkte vorbei."
|
||||
},
|
||||
"Product": {
|
||||
"addToCart": "In den Warenkorb",
|
||||
"outOfStock": "Nicht auf Lager",
|
||||
"details": "Details",
|
||||
"ingredients": "Zutaten",
|
||||
"usage": "Anwendung",
|
||||
"related": "Das könnte Ihnen auch gefallen",
|
||||
"notFound": "Produkt nicht gefunden",
|
||||
"notFoundDesc": "Das gesuchte Produkt existiert nicht oder wurde entfernt.",
|
||||
"browseProducts": "Produkte durchsuchen"
|
||||
},
|
||||
"Cart": {
|
||||
"title": "Ihr Warenkorb",
|
||||
"empty": "Ihr Warenkorb ist leer",
|
||||
"emptyDesc": "Es sieht so aus, als hätten Sie noch nichts in Ihren Warenkorb gelegt.",
|
||||
"continueShopping": "Weiter einkaufen",
|
||||
"checkout": "Zur Kasse",
|
||||
"subtotal": "Zwischensumme",
|
||||
"shipping": "Versand",
|
||||
"shippingCalc": "Wird an der Kasse berechnet",
|
||||
"total": "Gesamt",
|
||||
"freeShipping": "Kostenloser Versand bei Bestellungen über {amount}",
|
||||
"remove": "Entfernen",
|
||||
"processes": "Wird bearbeitet...",
|
||||
"cartEmpty": "Ihr Warenkorb ist leer"
|
||||
},
|
||||
"About": {
|
||||
"title": "Über uns",
|
||||
"subtitle": "Unsere Geschichte",
|
||||
"intro": "ManoonOils wurde aus einer Leidenschaft für natürliche Schönheit und dem Glauben geboren, dass die beste Hautpflege von der Natur selbst kommt.",
|
||||
"intro2": "Wir glauben an die Kraft natürlicher Inhaltsstoffe. Jedes Öl in unserer Kollektion wurde sorgfältig aufgrund seiner einzigartigen Eigenschaften und Vorteile ausgewählt. Von nährenden Ölen, die die Haarlebenskraft wiederherstellen, bis zu Seren, die die Haut verjüngen, stellen wir jedes Produkt mit Liebe und Liebe zum Detail her.",
|
||||
"naturalIngredients": "Natürliche Inhaltsstoffe",
|
||||
"naturalIngredientsDesc": "Wir verwenden nur die feinsten natürlichen Inhaltsstoffe, die ethisch und nachhaltig von vertrauenswürdigen Lieferanten weltweit beschafft werden.",
|
||||
"crueltyFree": "Tierversuchsfrei",
|
||||
"crueltyFreeDesc": "Unsere Produkte werden niemals an Tieren getestet. Wir glauben an Schönheit ohne Kompromisse.",
|
||||
"sustainablePackaging": "Nachhaltige Verpackung",
|
||||
"sustainablePackagingDesc": "Wir verwenden umweltfreundliche Verpackungsmaterialien und minimieren Abfall während unseres gesamten Produktionsprozesses.",
|
||||
"handcraftedQuality": "Handwerkliche Qualität",
|
||||
"handcraftedQualityDesc": "Jede Flasche wird in kleinen Chargen handgefertigt, um höchste Qualität und Frische zu gewährleisten.",
|
||||
"mission": "Unsere Mission",
|
||||
"missionQuote": "\"Hochwertige, natürliche Produkte anzubieten, die Ihre tägliche Schönheitsroutine verbessern.\"",
|
||||
"handmadeTitle": "Mit Liebe handgefertigt",
|
||||
"handmadeText1": "Jede Flasche ManoonOils wird mit Sorgfalt handgefertigt. Wir stellen unsere Produkte in kleinen Chargen her, um höchste Qualität und Frische zu gewährleisten. Wenn Sie ManoonOils verwenden, können Sie sicher sein, dass Sie etwas verwenden, das mit echter Sorgfalt und Fachwissen hergestellt wurde.",
|
||||
"handmadeText2": "Unsere Reise begann mit einer einfachen Frage: Wie können wir Produkte herstellen, die sowohl Haar als auch Haut wirklich pflegen? Heute innovieren wir weiter, während wir unserem Engagement für natürliche, effektive Schönheitslösungen treu bleiben."
|
||||
},
|
||||
"Contact": {
|
||||
"title": "Kontakt",
|
||||
"subtitle": "Kontakt aufnehmen",
|
||||
"getInTouch": "Kontakt aufnehmen",
|
||||
"getInTouchDesc": "Wir sind hier um zu helfen! Ob Sie Fragen zu unseren Produkten haben, Hilfe mit einer Bestellung benötigen oder einfach Hallo sagen möchten - wir würden uns freuen, von Ihnen zu hören.",
|
||||
"email": "E-Mail",
|
||||
"emailReply": "Wir antworten innerhalb von 24 Stunden",
|
||||
"shippingTitle": "Versand",
|
||||
"freeShipping": "Kostenloser Versand über 3.000 RSD",
|
||||
"deliveryTime": "Geliefert innerhalb von 2-5 Werktagen",
|
||||
"location": "Standort",
|
||||
"locationDesc": "Serbien",
|
||||
"worldwideShipping": "Versand weltweit",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Ihr Name",
|
||||
"emailField": "E-Mail",
|
||||
"emailPlaceholder": "ihre@email.com",
|
||||
"message": "Nachricht",
|
||||
"messagePlaceholder": "Wie können wir Ihnen helfen?",
|
||||
"sendMessage": "Nachricht senden",
|
||||
"thankYou": "Vielen Dank!",
|
||||
"thankYouDesc": "Ihre Nachricht wurde gesendet. Wir werden uns in Kürze bei Ihnen melden.",
|
||||
"faqTitle": "Häufig gestellte Fragen",
|
||||
"faq1q": "Wie lange dauert der Versand?",
|
||||
"faq1a": "Bestellungen werden in der Regel innerhalb von 2-5 Werktagen für Inlandsversand geliefert. Sie erhalten eine Tracking-Nummer, sobald Ihre Bestellung versandt wurde.",
|
||||
"faq2q": "Sind Ihre Produkte 100% natürlich?",
|
||||
"faq2a": "Ja! Alle unsere Öle sind 100% natürlich, kaltgepresst und frei von jeglichen Zusatzstoffen, Konservierungsstoffen oder künstlichen Duftstoffen.",
|
||||
"faq3q": "Wie ist Ihre Rückgaberichtlinie?",
|
||||
"faq3a": "Wir akzeptieren Rücksendungen innerhalb von 14 Tagen nach Lieferung für ungeöffnete Produkte. Bitte kontaktieren Sie uns, wenn Sie Probleme mit Ihrer Bestellung haben.",
|
||||
"faq4q": "Bieten Sie Großhandel an?",
|
||||
"faq4a": "Ja, wir bieten Großhandelspreise für Bulk-Bestellungen. Bitte kontaktieren Sie uns unter hello@manoonoils.com für mehr Informationen."
|
||||
},
|
||||
"Footer": {
|
||||
"quickLinks": "Schnelle Links",
|
||||
"customerService": "Kundenservice",
|
||||
"contact": "Kontakt",
|
||||
"shipping": "Versand",
|
||||
"returns": "Rückgabe",
|
||||
"faq": "FAQ",
|
||||
"followUs": "Folgen Sie uns",
|
||||
"newsletter": "Newsletter",
|
||||
"newsletterDesc": "Abonnieren Sie unseren Newsletter für exklusive Angebote und Updates.",
|
||||
"copyright": "Alle Rechte vorbehalten.",
|
||||
"allRights": "Alle Rechte vorbehalten.",
|
||||
"shop": "Shop",
|
||||
"allProducts": "Alle Produkte",
|
||||
"hairCare": "Haarpflege",
|
||||
"skinCare": "Hautpflege",
|
||||
"giftSets": "Geschenksets",
|
||||
"about": "Über uns",
|
||||
"ourStory": "Unsere Geschichte",
|
||||
"process": "Prozess",
|
||||
"sustainability": "Nachhaltigkeit",
|
||||
"help": "Hilfe",
|
||||
"contactUs": "Kontaktieren Sie uns",
|
||||
"brandDescription": "Premium natürliche Öle für Haar- und Hautpflege. Handgefertigt mit Liebe unter Verwendung traditioneller Methoden.",
|
||||
"weAccept": "Wir akzeptieren:"
|
||||
},
|
||||
"Common": {
|
||||
"loading": "Laden...",
|
||||
"error": "Ein Fehler ist aufgetreten",
|
||||
"tryAgain": "Erneut versuchen",
|
||||
"close": "Schließen",
|
||||
"back": "Zurück",
|
||||
"next": "Weiter",
|
||||
"previous": "Vorherige",
|
||||
"search": "Suchen",
|
||||
"noResults": "Keine Ergebnisse gefunden"
|
||||
},
|
||||
"Testimonials": {
|
||||
"title": "Was unsere Kunden sagen",
|
||||
"verified": "Verifizierter Kauf",
|
||||
"reviews": [
|
||||
{
|
||||
"name": "Sarah M.",
|
||||
"skinType": "Trockene, empfindliche Haut",
|
||||
"text": "Ich habe im Laufe der Jahre unzählige Öle ausprobiert, aber ManoonOils ist anders. Meine Haut hat sich noch nie so genährt und gesund angefühlt. Das Arganöl ist jetzt ein Grundnahrungsmittel in meiner Routine."
|
||||
},
|
||||
{
|
||||
"name": "James K.",
|
||||
"skinType": "Haarpflege-Enthusiast",
|
||||
"text": "Endlich ein Öl gefunden, das meinen Frizz wirklich bändigt, ohne mein Haar fettig zu machen. Das Jojobaöl wirkt auch bei meinem Bart Wunder. Sehr empfehlenswert!"
|
||||
},
|
||||
{
|
||||
"name": "Emma L.",
|
||||
"skinType": "Mischhaut",
|
||||
"text": "War zuerst skeptisch, aber nach 3 Wochen Hagebuttenöl hat sich meine Hauttextur dramatisch verbessert. Die Qualität ist unübertroffen."
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProductReviews": {
|
||||
"customerReviews": "Kundenbewertungen",
|
||||
"whatCustomersSay": "Was Kunden sagen",
|
||||
"basedOnReviews": "Basierend auf 1000+ Bewertungen",
|
||||
"reviews": [
|
||||
{ "id": 1, "name": "Ana M.", "location": "Belgrad", "text": "Manoon Anti-Age Serum hat meine Haut in nur 2 Wochen transformiert!", "rating": 5 },
|
||||
{ "id": 2, "name": "Milica P.", "location": "Novi Sad", "text": "Das beste Tageserum, das ich je verwendet habe. Meine Falten sind sichtbar reduziert.", "rating": 5 },
|
||||
{ "id": 3, "name": "Jelena K.", "location": "Belgrad", "text": "Manoon Nachtserum ist pure Magie. Aufwachen mit strahlender Haut jeden Morgen.", "rating": 5 },
|
||||
{ "id": 4, "name": "Stefan R.", "location": "Subotica", "text": "Das Anti-Age Set ist jeden Dinar wert. Meine Frau und ich benutzen es beide.", "rating": 5 },
|
||||
{ "id": 5, "name": "Marija T.", "location": "Kragujevac", "text": "Endlich ein Serum gefunden, das wirklich funktioniert! Manoon hält seine Versprechen.", "rating": 5 }
|
||||
]
|
||||
},
|
||||
"TrustBadges": {
|
||||
"averageRating": "Durchschnittliche Bewertung",
|
||||
"basedOnReviews": "Basierend auf 1000+ Bewertungen",
|
||||
"happyCustomers": "Zufriedene Kunden",
|
||||
"worldwide": "Weltweit",
|
||||
"naturalIngredients": "Natürliche Inhaltsstoffe",
|
||||
"noAdditives": "Keine Zusatzstoffe",
|
||||
"freeShipping": "Kostenloser Versand",
|
||||
"ordersOver": "Bestellungen über 3.000 RSD"
|
||||
},
|
||||
"ProblemSection": {
|
||||
"title": "Das Problem",
|
||||
"subtitle": "Müde von Haar- & Hautprodukten, die nicht liefern?",
|
||||
"description": "Sie verdienen mehr als Produkte voller aggressiver Chemikalien und leerer Versprechen",
|
||||
"problems": [
|
||||
{
|
||||
"problem": "Trockenes, beschädigtes Haar",
|
||||
"description": "Produkte hinterlassen Ihr Haar brüchig, frizzig und brechend trotz teurer Behandlungen"
|
||||
},
|
||||
{
|
||||
"problem": "Verwirrende Inhaltsstoffe",
|
||||
"description": "Sie können nicht aussprechen, was in Ihrer Hautpflege ist. Parabene, Sulfate, synthetische Duftstoffe - gefährliche Toxine"
|
||||
},
|
||||
{
|
||||
"problem": "Keine echten Ergebnisse",
|
||||
"description": "Unzählige Produkte versprechen Wunder, aber liefern nur leere Versprechen und verschwendetes Geld"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AsSeenIn": {
|
||||
"title": "Wie gesehen in"
|
||||
},
|
||||
"BeforeAfterGallery": {
|
||||
"realResults": "Echte Ergebnisse",
|
||||
"seeTransformation": "Sehen Sie die Transformation",
|
||||
"startTransformation": "Starten Sie Ihre Transformation",
|
||||
"before": "VORHER",
|
||||
"after": "NACHHER",
|
||||
"verified": "Verifiziert",
|
||||
"timeline": "Nach {weeks}"
|
||||
},
|
||||
"HowItWorks": {
|
||||
"title": "Einfacher Prozess",
|
||||
"subtitle": "Wie ManoonOils funktioniert",
|
||||
"startTransformation": "Starten Sie Ihre Transformation",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Wählen Sie Ihr Öl",
|
||||
"description": "Wählen Sie aus unserer Kollektion von reinen, kaltgepressten Ölen, die für Ihre spezifischen Haar- und Hautbedürfnisse formuliert sind."
|
||||
},
|
||||
{
|
||||
"title": "Täglich anwenden",
|
||||
"description": "Massieren Sie einige Tropfen in feuchtes Haar oder Haut. Unsere Öle ziehen sofort ein - nie fettig, immer pflegend."
|
||||
},
|
||||
{
|
||||
"title": "Ergebnisse sehen",
|
||||
"description": "Erleben Sie Transformation in 4-6 Wochen. Glänzenderes Haar, strahlende Haut und Selbstvertrauen, das strahlt."
|
||||
}
|
||||
]
|
||||
},
|
||||
"Header": {
|
||||
"products": "Produkte",
|
||||
"about": "Über uns",
|
||||
"contact": "Kontakt",
|
||||
"cart": "Warenkorb",
|
||||
"account": "Konto",
|
||||
"openMenu": "Menü öffnen",
|
||||
"closeMenu": "Menü schließen",
|
||||
"openCart": "Warenkorb öffnen"
|
||||
},
|
||||
"ProductCard": {
|
||||
"noImage": "Kein Bild",
|
||||
"outOfStock": "Nicht auf Lager",
|
||||
"quickAdd": "Schnell hinzufügen",
|
||||
"contactForPrice": "Preis anfragen"
|
||||
},
|
||||
"ProductDetail": {
|
||||
"home": "Startseite",
|
||||
"outOfStock": "Nicht auf Lager",
|
||||
"size": "Größe",
|
||||
"qty": "Menge",
|
||||
"adding": "Wird hinzugefügt...",
|
||||
"transformHairSkin": "Mein Haar & Haut transformieren",
|
||||
"freeShipping": "Kostenloser Versand bei Bestellungen über 3.000 RSD",
|
||||
"guarantee": "30-Tage-Garantie",
|
||||
"secureCheckout": "Sicheres Bezahlen",
|
||||
"easyReturns": "Einfache Rückgabe",
|
||||
"benefits": "Vorteile",
|
||||
"description": "Beschreibung",
|
||||
"howToUse": "Anwendung",
|
||||
"howToUseText": "Eine kleine Menge auf saubere, feuchte Haut oder Haare auftragen. Sanft einmassieren, bis es eingezogen ist. Täglich für beste Ergebnisse verwenden.",
|
||||
"ingredients": "Inhaltsstoffe",
|
||||
"ingredientsText": "100% Reines Natürliches Öl. Keine Zusatzstoffe, Konservierungsstoffe oder künstliche Duftstoffe.",
|
||||
"youMayAlsoLike": "Das könnte Ihnen auch gefallen",
|
||||
"similarProducts": "Ähnliche Produkte",
|
||||
"stocksRunningOut": "Vorräte gehen zur Neige!",
|
||||
"urgency1": "Beeilen Sie sich! 500+ Artikel in den letzten 3 Tagen verkauft!",
|
||||
"urgency2": "In den Warenkörben von 2,5K Menschen - kaufen Sie, bevor es weg ist!",
|
||||
"urgency3": "7.562 Personen haben sich dieses Produkt in den letzten 24 Stunden angesehen!"
|
||||
},
|
||||
"Newsletter": {
|
||||
"stayConnected": "Bleiben Sie verbunden",
|
||||
"joinCommunity": "Werden Sie Teil unserer Gemeinschaft",
|
||||
"newsletterText": "Abonnieren Sie, um exklusive Angebote, Schönheitstipps zu erhalten und als Erster über neue Produkte informiert zu werden.",
|
||||
"emailPlaceholder": "Geben Sie Ihre E-Mail ein",
|
||||
"subscribe": "Abonnieren"
|
||||
},
|
||||
"ProductBenefits": {
|
||||
"whyChoose": "Warum dieses Produkt wählen",
|
||||
"manoonDifference": "Der Manoon Unterschied",
|
||||
"pureNatural": "Rein & Natürlich",
|
||||
"pureNaturalDesc": "100% natürliche Inhaltsstoffe ohne Zusatzstoffe oder Konservierungsstoffe",
|
||||
"crueltyFree": "Tierversuchsfrei",
|
||||
"crueltyFreeDesc": "Nie an Tieren getestet, ethisch beschaffte Inhaltsstoffe",
|
||||
"madeWithLove": "Mit Liebe hergestellt",
|
||||
"madeWithLoveDesc": "In kleinen Chargen handgefertigt für maximale Qualität",
|
||||
"visibleResults": "Sichtbare Ergebnisse",
|
||||
"visibleResultsDesc": "Erkennbare Verbesserungen in 4-6 Wochen"
|
||||
},
|
||||
"Checkout": {
|
||||
"checkout": "Kasse",
|
||||
"shippingAddress": "Lieferadresse",
|
||||
"firstName": "Vorname",
|
||||
"lastName": "Nachname",
|
||||
"streetAddress": "Straße und Nummer",
|
||||
"streetAddressOptional": "Wohnung, Suite, etc. (optional)",
|
||||
"city": "Stadt",
|
||||
"postalCode": "Postleitzahl",
|
||||
"phone": "Telefon",
|
||||
"billingAddressSame": "Rechnungsadresse gleich Lieferadresse",
|
||||
"billingAddress": "Rechnungsadresse",
|
||||
"paymentMethod": "Zahlungsmethode",
|
||||
"cashOnDelivery": "Nachnahme (COD)",
|
||||
"cashOnDeliveryDesc": "Bezahlen Sie, wenn Ihre Bestellung an Ihre Tür geliefert wird.",
|
||||
"processing": "Wird bearbeitet...",
|
||||
"completeOrder": "Bestellung abschließen - {total}",
|
||||
"orderSummary": "Bestellübersicht",
|
||||
"qty": "Menge",
|
||||
"subtotal": "Zwischensumme",
|
||||
"shipping": "Versand",
|
||||
"calculated": "Berechnet",
|
||||
"total": "Gesamt",
|
||||
"yourCartEmpty": "Ihr Warenkorb ist leer",
|
||||
"continueShopping": "Weiter einkaufen",
|
||||
"errorNoCheckout": "Keine aktive Kasse. Bitte versuchen Sie es erneut.",
|
||||
"errorOccurred": "Ein Fehler ist during des Checkouts aufgetreten.",
|
||||
"errorCreatingOrder": "Bestellung konnte nicht erstellt werden.",
|
||||
"orderConfirmed": "Bestellung bestätigt!",
|
||||
"thankYou": "Vielen Dank für Ihren Einkauf.",
|
||||
"orderNumber": "Bestellnummer",
|
||||
"confirmationEmail": "Sie erhalten in Kürze eine Bestätigungs-E-Mail. Wir werden Sie kontaktieren, um Nachnahme zu arrangieren.",
|
||||
"continueShoppingBtn": "Weiter einkaufen"
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@
|
||||
"sustainableDesc": "Ethically sourced ingredients and eco-friendly packaging for a better planet."
|
||||
},
|
||||
"Products": {
|
||||
"collection": "Our Collection",
|
||||
"allProducts": "All Products",
|
||||
"productsCount": "{count} products",
|
||||
"featured": "Featured",
|
||||
@@ -175,5 +176,251 @@
|
||||
"text": "Was skeptical at first but after 3 weeks of using the rosehip oil, my skin texture has improved dramatically. The quality is unmatched."
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProductReviews": {
|
||||
"customerReviews": "Customer Reviews",
|
||||
"whatCustomersSay": "What Customers Say",
|
||||
"basedOnReviews": "Based on 1000+ reviews",
|
||||
"reviews": [
|
||||
{ "id": 1, "name": "Ana M.", "location": "Belgrade", "text": "Manoon Anti-age Serum transformed my skin in just 2 weeks!", "rating": 5 },
|
||||
{ "id": 2, "name": "Milica P.", "location": "Novi Sad", "text": "The best day serum I've ever used. My wrinkles are visibly reduced.", "rating": 5 },
|
||||
{ "id": 3, "name": "Jelena K.", "location": "Belgrade", "text": "Manoon night serum is pure magic. Wake up with glowing skin every morning.", "rating": 5 },
|
||||
{ "id": 4, "name": "Stefan R.", "location": "Subotica", "text": "The Anti-age Set is worth every dinar. My wife and I both use it.", "rating": 5 },
|
||||
{ "id": 5, "name": "Marija T.", "location": "Kragujevac", "text": "Finally found a serum that actually works! Manoon delivers on its promises.", "rating": 5 },
|
||||
{ "id": 6, "name": "Nikola V.", "location": "Niš", "text": "My fine lines are disappearing. This day serum is incredible.", "rating": 5 },
|
||||
{ "id": 7, "name": "Ivana L.", "location": "Belgrade", "text": "Manoon morning glow serum smells divine and works even better.", "rating": 5 },
|
||||
{ "id": 8, "name": "Dejan M.", "location": "Novi Sad", "text": "The night serum has transformed my skincare routine completely.", "rating": 5 },
|
||||
{ "id": 9, "name": "Sanja B.", "location": "Kragujevac", "text": "My skin looks 10 years younger after using Manoon for a month.", "rating": 5 },
|
||||
{ "id": 10, "name": "Marko J.", "location": "Subotica", "text": "The anti-age set makes a perfect gift. My mother loves it!", "rating": 5 },
|
||||
{ "id": 11, "name": "Petra D.", "location": "Niš", "text": "The texture of Manoon serum is so luxurious. Worth every penny.", "rating": 5 },
|
||||
{ "id": 12, "name": "Luka G.", "location": "Belgrade", "text": "Day serum absorbs instantly. No greasy feeling at all!", "rating": 5 },
|
||||
{ "id": 13, "name": "Maja S.", "location": "Novi Sad", "text": "My esthetician asked what I'm using. Manoon is now my secret!", "rating": 5 },
|
||||
{ "id": 14, "name": "Vladimir P.", "location": "Kragujevac", "text": "The night serum works while I sleep. Wake up to visibly smoother skin.", "rating": 5 },
|
||||
{ "id": 15, "name": "Katarina N.", "location": "Subotica", "text": "The Anti-age Set arrived beautifully packaged. Perfect for gifting.", "rating": 5 },
|
||||
{ "id": 16, "name": "Bojan R.", "location": "Niš", "text": "Been using Manoon for 3 months. My wrinkles are noticeably reduced.", "rating": 5 },
|
||||
{ "id": 17, "name": "Tamara F.", "location": "Belgrade", "text": "The day serum provides the perfect base under makeup.", "rating": 5 },
|
||||
{ "id": 18, "name": "Aleksandar K.", "location": "Novi Sad", "text": "Finally a Serbian brand that competes with luxury international brands!", "rating": 5 },
|
||||
{ "id": 19, "name": "Natalia M.", "location": "Kragujevac", "text": "My sensitive skin loves Manoon. No irritation at all.", "rating": 5 },
|
||||
{ "id": 20, "name": "Filip T.", "location": "Subotica", "text": "The anti-age serum is lightweight yet incredibly effective.", "rating": 5 },
|
||||
{ "id": 21, "name": "Andrea L.", "location": "Niš", "text": "Manoon night serum is my evening ritual. Skin looks amazing!", "rating": 5 },
|
||||
{ "id": 22, "name": "Ognjen P.", "location": "Belgrade", "text": "My friends keep asking what changed in my skincare routine.", "rating": 5 },
|
||||
{ "id": 23, "name": "Mila J.", "location": "Novi Sad", "text": "The Anti-age Set includes everything you need. Great value!", "rating": 5 },
|
||||
{ "id": 24, "name": "Dragan S.", "location": "Kragujevac", "text": "Even my husband noticed the difference. He now uses the day serum too!", "rating": 5 },
|
||||
{ "id": 25, "name": "Jovana V.", "location": "Subotica", "text": "The morning glow serum gives the most beautiful luminosity.", "rating": 5 },
|
||||
{ "id": 26, "name": "Stefan M.", "location": "Niš", "text": "Manoon products are now essential in my daily routine.", "rating": 5 },
|
||||
{ "id": 27, "name": "Ana R.", "location": "Belgrade", "text": "The night serum helped clear my complexion. Skin looks so healthy!", "rating": 5 },
|
||||
{ "id": 28, "name": "Nenad L.", "location": "Novi Sad", "text": "Anti-aging results visible within weeks. Highly recommend Manoon!", "rating": 5 },
|
||||
{ "id": 29, "name": "Sofija D.", "location": "Kragujevac", "text": "The texture is divine. Feels like a luxury spa treatment at home.", "rating": 5 },
|
||||
{ "id": 30, "name": "Velibor K.", "location": "Subotica", "text": "My crow's feet have diminished significantly. Thank you Manoon!", "rating": 5 },
|
||||
{ "id": 31, "name": "Irena M.", "location": "Niš", "text": "The Anti-age Set makes the perfect birthday gift for my mother.", "rating": 5 },
|
||||
{ "id": 32, "name": "Radoslav P.", "location": "Belgrade", "text": "Professional quality serum at an honest price. Serbian excellence!", "rating": 5 },
|
||||
{ "id": 33, "name": "Jelena B.", "location": "Novi Sad", "text": "My skin has never been this hydrated. Day serum is amazing!", "rating": 5 },
|
||||
{ "id": 34, "name": "Dimitrije S.", "location": "Kragujevac", "text": "The night serum is worth its weight in gold. Pure luxury!", "rating": 5 },
|
||||
{ "id": 35, "name": "Minela G.", "location": "Subotica", "text": "Manoon lives up to the hype. My skin looks refreshed and young.", "rating": 5 },
|
||||
{ "id": 36, "name": "Zoran T.", "location": "Niš", "text": "I've tried many serums. Manoon is by far the most effective.", "rating": 5 },
|
||||
{ "id": 37, "name": "Mirjana F.", "location": "Belgrade", "text": "The Anti-age Set transformed my mother's skincare routine completely.", "rating": 5 },
|
||||
{ "id": 38, "name": "Ivan J.", "location": "Novi Sad", "text": "Fast-acting serum with real results. I recommend Manoon to everyone.", "rating": 5 },
|
||||
{ "id": 39, "name": "Kristina P.", "location": "Kragujevac", "text": "The morning glow serum gives such a beautiful dewy finish.", "rating": 5 },
|
||||
{ "id": 40, "name": "Bratislav L.", "location": "Subotica", "text": "Noticeable results in just 2 weeks. This serum is the real deal!", "rating": 5 },
|
||||
{ "id": 41, "name": "Zorica M.", "location": "Niš", "text": "The night serum erased years from my face. Absolutely miraculous!", "rating": 5 },
|
||||
{ "id": 42, "name": "Patrik N.", "location": "Belgrade", "text": "Premium quality Serbian skincare that rivals international luxury brands.", "rating": 5 },
|
||||
{ "id": 43, "name": "Simona K.", "location": "Novi Sad", "text": "Manoon Anti-age Serum is the best investment in my skin ever.", "rating": 5 },
|
||||
{ "id": 44, "name": "Mladen D.", "location": "Kragujevac", "text": "The day serum absorbs in seconds. No waiting around!", "rating": 5 },
|
||||
{ "id": 45, "name": "Ljiljana R.", "location": "Subotica", "text": "Gifting the Anti-age Set to my sisters. They loved it!", "rating": 5 },
|
||||
{ "id": 46, "name": "Tomislav V.", "location": "Niš", "text": "My wrinkles are visibly reduced after using Manoon for a month.", "rating": 5 },
|
||||
{ "id": 47, "name": "Emilija S.", "location": "Belgrade", "text": "The night serum leaves my skin so soft and renewed every morning.", "rating": 5 },
|
||||
{ "id": 48, "name": "Andrija P.", "location": "Novi Sad", "text": "Manoon day serum is perfect under sunscreen. Essential duo!", "rating": 5 },
|
||||
{ "id": 49, "name": "Miona L.", "location": "Kragujevac", "text": "My skin looks radiant and youthful. Couldn't be happier with Manoon!", "rating": 5 },
|
||||
{ "id": 50, "name": "Slavko M.", "location": "Subotica", "text": "The Anti-age Set delivers visible results. True Serbian quality!", "rating": 5 }
|
||||
]
|
||||
},
|
||||
"TrustBadges": {
|
||||
"averageRating": "Average Rating",
|
||||
"basedOnReviews": "Based on 1000+ reviews",
|
||||
"happyCustomers": "Happy Customers",
|
||||
"worldwide": "Worldwide",
|
||||
"naturalIngredients": "Natural Ingredients",
|
||||
"noAdditives": "No additives",
|
||||
"freeShipping": "Free Shipping",
|
||||
"ordersOver": "Orders over 3,000 RSD"
|
||||
},
|
||||
"ProblemSection": {
|
||||
"title": "The Problem",
|
||||
"subtitle": "Tired of Hair & Skin Products That Don't Deliver?",
|
||||
"description": "You deserve better than products filled with harsh chemicals and empty promises",
|
||||
"problems": [
|
||||
{
|
||||
"problem": "Dry, Damaged Hair",
|
||||
"description": "Products leave your hair brittle, frizzy, and breaking despite expensive treatments"
|
||||
},
|
||||
{
|
||||
"problem": "Confusing Ingredients",
|
||||
"description": "Can't pronounce what's in your skincare. parabens, sulfates, synthetic fragrances—dangerous toxins"
|
||||
},
|
||||
{
|
||||
"problem": "No Real Results",
|
||||
"description": "Countless products promise miracles but deliver nothing but empty promises and wasted money"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AsSeenIn": {
|
||||
"title": "As Featured In"
|
||||
},
|
||||
"BeforeAfterGallery": {
|
||||
"realResults": "Real Results",
|
||||
"seeTransformation": "See the Transformation",
|
||||
"startTransformation": "Start Your Transformation",
|
||||
"before": "BEFORE",
|
||||
"after": "AFTER",
|
||||
"verified": "Verified",
|
||||
"timeline": "After {weeks}"
|
||||
},
|
||||
"HowItWorks": {
|
||||
"title": "Simple Process",
|
||||
"subtitle": "How ManoonOils Works",
|
||||
"startTransformation": "Start Your Transformation",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Choose Your Oil",
|
||||
"description": "Select from our collection of pure, cold-pressed oils formulated for your specific hair and skin needs."
|
||||
},
|
||||
{
|
||||
"title": "Apply Daily",
|
||||
"description": "Massage a few drops into damp hair or skin. Our oils absorb instantly—never greasy, always nourishing."
|
||||
},
|
||||
{
|
||||
"title": "See Results",
|
||||
"description": "Experience transformation in 4-6 weeks. Shinier hair, radiant skin, and confidence that glows."
|
||||
}
|
||||
]
|
||||
},
|
||||
"Header": {
|
||||
"products": "Products",
|
||||
"about": "About",
|
||||
"contact": "Contact",
|
||||
"cart": "Cart",
|
||||
"account": "Account",
|
||||
"openMenu": "Open menu",
|
||||
"closeMenu": "Close menu",
|
||||
"openCart": "Open cart"
|
||||
},
|
||||
"Footer": {
|
||||
"shop": "Shop",
|
||||
"allProducts": "All Products",
|
||||
"hairCare": "Hair Care",
|
||||
"skinCare": "Skin Care",
|
||||
"giftSets": "Gift Sets",
|
||||
"about": "About",
|
||||
"ourStory": "Our Story",
|
||||
"process": "Process",
|
||||
"sustainability": "Sustainability",
|
||||
"help": "Help",
|
||||
"faq": "FAQ",
|
||||
"shipping": "Shipping",
|
||||
"returns": "Returns",
|
||||
"contactUs": "Contact Us",
|
||||
"brandDescription": "Premium natural oils for hair and skin care. Handcrafted with love using traditional methods.",
|
||||
"weAccept": "We accept:",
|
||||
"allRights": "All rights reserved."
|
||||
},
|
||||
"ProductCard": {
|
||||
"noImage": "No image",
|
||||
"outOfStock": "Out of Stock",
|
||||
"quickAdd": "Quick Add",
|
||||
"contactForPrice": "Contact for price"
|
||||
},
|
||||
"ProductDetail": {
|
||||
"home": "Home",
|
||||
"outOfStock": "Out of Stock",
|
||||
"size": "Size",
|
||||
"qty": "Qty",
|
||||
"adding": "Adding...",
|
||||
"transformHairSkin": "Transform My Hair & Skin",
|
||||
"freeShipping": "Free shipping on orders over 3,000 RSD",
|
||||
"guarantee": "30-Day Guarantee",
|
||||
"secureCheckout": "Secure Checkout",
|
||||
"easyReturns": "Easy Returns",
|
||||
"benefits": "Benefits",
|
||||
"description": "Description",
|
||||
"howToUse": "How to Use",
|
||||
"howToUseText": "Apply a small amount to clean, damp hair or skin. Massage gently until absorbed. Use daily for best results.",
|
||||
"ingredients": "Ingredients",
|
||||
"ingredientsText": "100% Pure Natural Oil. No additives, preservatives, or artificial fragrances.",
|
||||
"youMayAlsoLike": "You May Also Like",
|
||||
"similarProducts": "Similar Products",
|
||||
"stocksRunningOut": "Stocks are running out!",
|
||||
"urgency1": "Hurry up! 500+ items sold in the last 3 days!",
|
||||
"urgency2": "In the carts of 2.5K people - buy before its gone!",
|
||||
"urgency3": "7,562 people viewed this product in the last 24 hours!"
|
||||
},
|
||||
"Newsletter": {
|
||||
"stayConnected": "Stay Connected",
|
||||
"joinCommunity": "Join Our Community",
|
||||
"newsletterText": "Subscribe to receive exclusive offers, beauty tips, and be the first to know about new products.",
|
||||
"emailPlaceholder": "Enter your email",
|
||||
"subscribe": "Subscribe"
|
||||
},
|
||||
"ProductBenefits": {
|
||||
"whyChoose": "Why Choose This Product",
|
||||
"manoonDifference": "The Manoon Difference",
|
||||
"pureNatural": "Pure & Natural",
|
||||
"pureNaturalDesc": "100% natural ingredients with no additives or preservatives",
|
||||
"crueltyFree": "Cruelty Free",
|
||||
"crueltyFreeDesc": "Never tested on animals, ethically sourced ingredients",
|
||||
"madeWithLove": "Made with Love",
|
||||
"madeWithLoveDesc": "Handcrafted in small batches for maximum quality",
|
||||
"visibleResults": "Visible Results",
|
||||
"visibleResultsDesc": "See noticeable improvements in 4-6 weeks"
|
||||
},
|
||||
"Cart": {
|
||||
"yourCart": "Your Cart",
|
||||
"closeCart": "Close cart",
|
||||
"dismiss": "Dismiss",
|
||||
"yourCartEmpty": "Your cart is empty",
|
||||
"looksLikeEmpty": "Looks like you haven't added anything to your cart yet.",
|
||||
"startShopping": "Start Shopping",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
"calculatedAtCheckout": "Calculated at checkout",
|
||||
"total": "Total",
|
||||
"freeShippingOver": "Free shipping on orders over {amount}",
|
||||
"processing": "Processing...",
|
||||
"checkout": "Checkout",
|
||||
"continueShopping": "Continue Shopping",
|
||||
"removeItem": "Remove item"
|
||||
},
|
||||
"Checkout": {
|
||||
"checkout": "Checkout",
|
||||
"shippingAddress": "Shipping Address",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"streetAddress": "Street Address",
|
||||
"streetAddressOptional": "Apartment, suite, etc. (optional)",
|
||||
"city": "City",
|
||||
"postalCode": "Postal Code",
|
||||
"phone": "Phone",
|
||||
"billingAddressSame": "Billing address same as shipping",
|
||||
"billingAddress": "Billing Address",
|
||||
"paymentMethod": "Payment Method",
|
||||
"cashOnDelivery": "Cash on Delivery (COD)",
|
||||
"cashOnDeliveryDesc": "Pay when your order is delivered to your door.",
|
||||
"processing": "Processing...",
|
||||
"completeOrder": "Complete Order - {total}",
|
||||
"orderSummary": "Order Summary",
|
||||
"qty": "Qty",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
"calculated": "Calculated",
|
||||
"total": "Total",
|
||||
"yourCartEmpty": "Your cart is empty",
|
||||
"continueShopping": "Continue Shopping",
|
||||
"errorNoCheckout": "No active checkout. Please try again.",
|
||||
"errorOccurred": "An error occurred during checkout.",
|
||||
"errorCreatingOrder": "Failed to create order.",
|
||||
"orderConfirmed": "Order Confirmed!",
|
||||
"thankYou": "Thank you for your purchase.",
|
||||
"orderNumber": "Order Number",
|
||||
"confirmationEmail": "You will receive a confirmation email shortly. We will contact you to arrange Cash on Delivery.",
|
||||
"continueShoppingBtn": "Continue Shopping"
|
||||
}
|
||||
}
|
||||
|
||||
358
src/i18n/messages/fr.json
Normal file
358
src/i18n/messages/fr.json
Normal file
@@ -0,0 +1,358 @@
|
||||
{
|
||||
"Navigation": {
|
||||
"home": "Accueil",
|
||||
"products": "Produits",
|
||||
"about": "À propos",
|
||||
"contact": "Contact"
|
||||
},
|
||||
"Home": {
|
||||
"hero": {
|
||||
"title": "Huiles Naturelles Premium",
|
||||
"subtitle": "Pour les soins capillaires et cutanés",
|
||||
"lovedBy": "Apprécié par 50 000+ clients dans le monde",
|
||||
"transformHeadline": "Transformez Vos Cheveux & Peau",
|
||||
"withNaturalOils": "avec des Huiles 100% Naturelles",
|
||||
"subtitleText": "Huiles biologiques cold-pressed, artisanales avec amour. Sans additifs, sans conservateurs - juste la pureté de la nature pour votre rituel beauté quotidien.",
|
||||
"ctaButton": "Transformer Mes Cheveux & Ma Peau",
|
||||
"learnStory": "Découvrir Notre Histoire",
|
||||
"moneyBack": "30 Jours Satisfait",
|
||||
"freeShipping": "Livraison Gratuite +3.000 RSD",
|
||||
"crueltyFree": "Cruelty Free"
|
||||
},
|
||||
"collection": "Notre Collection",
|
||||
"premiumOils": "Huiles Naturelles Premium",
|
||||
"oilsDescription": "Huiles cold-pressed, pures et naturelles pour votre routine beauté quotidienne",
|
||||
"viewAll": "Voir Tous Les Produits",
|
||||
"ourStory": "Notre Histoire",
|
||||
"handmadeWithLove": "Fait Main avec Amour",
|
||||
"storyText1": "Chaque flacon de ManoonOils est crafted avec soin en utilisant des méthodes traditionnelles transmises de génération en génération. Nous aprovisonnons uniquement les meilleurs ingrédients biologiques pour vous apporter des huiles qui nourrissent les cheveux et la peau.",
|
||||
"storyText2": "Notre engagement envers la pureté signifie aucun additif, aucun conservateur - juste la bonté de la nature dans sa forme la plus potente.",
|
||||
"learnMore": "En Savoir Plus",
|
||||
"whyChooseUs": "Pourquoi Nous Choisir",
|
||||
"manoonDifference": "La Différence Manoon",
|
||||
"stayConnected": "Restez Connectés",
|
||||
"joinCommunity": "Rejoignez Notre Communauté",
|
||||
"newsletterText": "Abonnez-vous pour recevoir des offres exclusives, des conseils beauté et être les premiers informés des nouveaux produits.",
|
||||
"emailPlaceholder": "Entrez votre email",
|
||||
"subscribe": "S'abonner"
|
||||
},
|
||||
"Benefits": {
|
||||
"natural": "100% Naturel",
|
||||
"naturalDesc": "Huiles pures cold-pressed sans additifs ni conservateurs. Juste la bonté de la nature.",
|
||||
"handcrafted": "Artisanal",
|
||||
"handcraftedDesc": "Chaque lot est soigneusement préparé à la main pour assurer la plus haute qualité.",
|
||||
"sustainable": "Durable",
|
||||
"sustainableDesc": "Ingrédients sourcés éthiquement et emballage écologique pour une meilleure planète."
|
||||
},
|
||||
"Products": {
|
||||
"collection": "Notre Collection",
|
||||
"allProducts": "Tous Les Produits",
|
||||
"productsCount": "{count} produits",
|
||||
"featured": "En Vedette",
|
||||
"newest": "Nouveautés",
|
||||
"priceLow": "Prix: Croissant",
|
||||
"priceHigh": "Prix: Décroissant",
|
||||
"noProducts": "Aucun produit disponible",
|
||||
"checkBack": "Veuillez vérifier plus tard pour les nouveaux arrivages."
|
||||
},
|
||||
"Product": {
|
||||
"addToCart": "Ajouter au Panier",
|
||||
"outOfStock": "Rupture de Stock",
|
||||
"details": "Détails",
|
||||
"ingredients": "Ingrédients",
|
||||
"usage": "Utilisation",
|
||||
"related": "Vous Aimerez Aussi",
|
||||
"notFound": "Produit non trouvé",
|
||||
"notFoundDesc": "Le produit que vous recherchez n'existe pas ou a été supprimé.",
|
||||
"browseProducts": "Parcourir les Produits"
|
||||
},
|
||||
"Cart": {
|
||||
"title": "Votre Panier",
|
||||
"empty": "Votre panier est vide",
|
||||
"emptyDesc": "Il semble que vous n'ayez pas encore ajouté d'articles à votre panier.",
|
||||
"continueShopping": "Continuer les Achats",
|
||||
"checkout": "Commander",
|
||||
"subtotal": "Sous-total",
|
||||
"shipping": "Livraison",
|
||||
"shippingCalc": "Calculé à la caisse",
|
||||
"total": "Total",
|
||||
"freeShipping": "Livraison gratuite sur les commandes de {amount}",
|
||||
"remove": "Supprimer",
|
||||
"processes": "En cours...",
|
||||
"cartEmpty": "Votre panier est vide"
|
||||
},
|
||||
"About": {
|
||||
"title": "À Propos",
|
||||
"subtitle": "Notre Histoire",
|
||||
"intro": "ManoonOils est né d'une passion pour la beauté naturelle et de la conviction que les meilleurs soins cutanés viennent de la nature elle-même.",
|
||||
"intro2": "Nous croyons au pouvoir des ingrédients naturels. Chaque huile de notre collection est soigneusement sélectionnée pour ses propriétés et bienfaits uniques. Des huiles nourrissantes qui restaurent la vitalité des cheveux aux sérums qui rajeunissent la peau, nous élaborons chaque produit avec amour et attention aux détails.",
|
||||
"naturalIngredients": "Ingrédients Naturels",
|
||||
"naturalIngredientsDesc": "Nous utilisons uniquement les meilleurs ingrédients naturels, sourcés de manière éthique et durable auprès de fournisseurs de confiance dans le monde entier.",
|
||||
"crueltyFree": "Cruelty Free",
|
||||
"crueltyFreeDesc": "Nos produits ne sont jamais testés sur les animaux. Nous croyons en la beauté sans compromis.",
|
||||
"sustainablePackaging": "Emballage Durable",
|
||||
"sustainablePackagingDesc": "Nous utilisons des matériaux d'emballage écologiques et minimisons les déchets tout au long de notre processus de production.",
|
||||
"handcraftedQuality": "Qualité Artisanale",
|
||||
"handcraftedQualityDesc": "Chaque flacon est fabriqué à la main en petites séries pour garantir la plus haute qualité et fraîcheur.",
|
||||
"mission": "Notre Mission",
|
||||
"missionQuote": "\"Fournir des produits premium de qualité, naturels qui améliorent votre routine beauté quotidienne.\"",
|
||||
"handmadeTitle": "Fait Main avec Amour",
|
||||
"handmadeText1": "Chaque flacon de ManoonOils est fabriqué à la main avec soin. Nous produisons nos produits en petites séries pour garantir la plus haute qualité et fraîcheur. Lorsque vous utilisez ManoonOils, vous pouvez être assuré d'utiliser quelque chose fabriqué avec un véritable souci et une expertise.",
|
||||
"handmadeText2": "Notre voyage a commencé par une question simple: comment pouvons-nous créer des produits qui truly nourrissent à la fois les cheveux et la peau? Aujourd'hui, nous continuons à innover tout en restant fidèles à notre engagement envers des solutions beauté naturelles et efficaces."
|
||||
},
|
||||
"Contact": {
|
||||
"title": "Contact",
|
||||
"subtitle": "Contactez-nous",
|
||||
"getInTouch": "Contactez-nous",
|
||||
"getInTouchDesc": "Nous sommes là pour aider! Que vous ayez des questions sur nos produits, besoin d'aide avec une commande, ou simplement souhaitiez dire bonjour, nous aimerions avoir de vos nouvelles.",
|
||||
"email": "Email",
|
||||
"emailReply": "Nous répondons dans les 24 heures",
|
||||
"shippingTitle": "Livraison",
|
||||
"freeShipping": "Livraison gratuite +3.000 RSD",
|
||||
"deliveryTime": "Livré dans 2-5 jours ouvrables",
|
||||
"location": "Localisation",
|
||||
"locationDesc": "Serbie",
|
||||
"worldwideShipping": "Livraison dans le monde entier",
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "Votre nom",
|
||||
"emailField": "Email",
|
||||
"emailPlaceholder": "votre@email.com",
|
||||
"message": "Message",
|
||||
"messagePlaceholder": "Comment pouvons-nous vous aider?",
|
||||
"sendMessage": "Envoyer le message",
|
||||
"thankYou": "Merci!",
|
||||
"thankYouDesc": "Votre message a été envoyé. Nous vous répondrons bientôt.",
|
||||
"faqTitle": "Questions Fréquemment Posées",
|
||||
"faq1q": "Combien de temps dure la livraison?",
|
||||
"faq1a": "Les commandes sont généralement livrées sous 2-5 jours ouvrables pour la livraison nationale. Vous recevrez un numéro de suivi dès que votre commande sera expédiée.",
|
||||
"faq2q": "Vos produits sont-ils 100% naturels?",
|
||||
"faq2a": "Oui! Toutes nos huiles sont 100% naturelles, cold-pressed et exemptes de tout additif, conservateur ou parfum artificiel.",
|
||||
"faq3q": "Quelle est votre politique de retour?",
|
||||
"faq3a": "Nous acceptons les retours dans les 14 jours suivant la livraison pour les produits non ouverts. Veuillez nous contacter si vous avez des problèmes avec votre commande.",
|
||||
"faq4q": "Offrez-vous des ventes en gros?",
|
||||
"faq4a": "Oui, nous offrons des prix de gros pour les commandes en grande quantité. Veuillez nous contacter à hello@manoonoils.com pour plus d'informations."
|
||||
},
|
||||
"Footer": {
|
||||
"quickLinks": "Liens Rapides",
|
||||
"customerService": "Service Client",
|
||||
"contact": "Contact",
|
||||
"shipping": "Livraison",
|
||||
"returns": "Retours",
|
||||
"faq": "FAQ",
|
||||
"followUs": "Suivez-nous",
|
||||
"newsletter": "Newsletter",
|
||||
"newsletterDesc": "Abonnez-vous à notre newsletter pour des offres exclusives et des mises à jour.",
|
||||
"copyright": "Tous droits réservés.",
|
||||
"allRights": "Tous droits réservés.",
|
||||
"shop": "Boutique",
|
||||
"allProducts": "Tous Les Produits",
|
||||
"hairCare": "Soins Capillaires",
|
||||
"skinCare": "Soins Cutanés",
|
||||
"giftSets": "Coffrets Cadeaux",
|
||||
"about": "À Propos",
|
||||
"ourStory": "Notre Histoire",
|
||||
"process": "Processus",
|
||||
"sustainability": "Durabilité",
|
||||
"help": "Aide",
|
||||
"contactUs": "Contactez-nous",
|
||||
"brandDescription": "Huiles naturelles premium pour les soins capillaires et cutanés. Fait main avec amour en utilisant des méthodes traditionnelles.",
|
||||
"weAccept": "Nous acceptons:"
|
||||
},
|
||||
"Common": {
|
||||
"loading": "Chargement...",
|
||||
"error": "Une erreur est survenue",
|
||||
"tryAgain": "Réessayer",
|
||||
"close": "Fermer",
|
||||
"back": "Retour",
|
||||
"next": "Suivant",
|
||||
"previous": "Précédent",
|
||||
"search": "Rechercher",
|
||||
"noResults": "Aucun résultat trouvé"
|
||||
},
|
||||
"Testimonials": {
|
||||
"title": "Ce que disent nos clients",
|
||||
"verified": "Achat vérifié",
|
||||
"reviews": [
|
||||
{
|
||||
"name": "Sarah M.",
|
||||
"skinType": "Peau sèche et sensible",
|
||||
"text": "J'ai essayé d'innombrables huiles au fil des ans, mais ManoonOils est différent. Ma peau n'a jamais été aussi nourrie et en bonne santé. L'huile d'argan est maintenant un élément essentiels de ma routine."
|
||||
},
|
||||
{
|
||||
"name": "James K.",
|
||||
"skinType": "Passionné de soins capillaires",
|
||||
"text": "Enfin trouvé une huile qui rassemble vraiment mes frisottis sans rendre mes cheveux gras. L'huile de jojoba fait des merveilles pour ma barbe aussi. Je recommande vivement!"
|
||||
},
|
||||
{
|
||||
"name": "Emma L.",
|
||||
"skinType": "Peau mixte",
|
||||
"text": "J'étais sceptique au début mais après 3 semaines d'utilisation de l'huile de rose musquée, la texture de ma peau s'est améliorée dramatiquement. La qualité est incomparable."
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProductReviews": {
|
||||
"customerReviews": "Avis Clients",
|
||||
"whatCustomersSay": "Ce Que Disent Les Clients",
|
||||
"basedOnReviews": "Basé sur 1000+ avis",
|
||||
"reviews": [
|
||||
{ "id": 1, "name": "Ana M.", "location": "Belgrade", "text": "Le Sérum Anti-âge Manoon a transformé ma peau en seulement 2 semaines!", "rating": 5 },
|
||||
{ "id": 2, "name": "Milica P.", "location": "Novi Sad", "text": "Le meilleur sérum de jour que j'aie jamais utilisé. Mes rides sont visiblement réduites.", "rating": 5 },
|
||||
{ "id": 3, "name": "Jelena K.", "location": "Belgrade", "text": "Le sérum de nuit Manoon est de la magie pure. Réveillez-vous avec une peau qui rayonne chaque matin.", "rating": 5 },
|
||||
{ "id": 4, "name": "Stefan R.", "location": "Subotica", "text": "Le Set Anti-âge vaut chaque dinar. Ma femme et moi l'utilisons tous les deux.", "rating": 5 },
|
||||
{ "id": 5, "name": "Marija T.", "location": "Kragujevac", "text": "J'ai enfin trouvé un sérum qui fonctionne vraiment! Manoon tient ses promesses.", "rating": 5 }
|
||||
]
|
||||
},
|
||||
"TrustBadges": {
|
||||
"averageRating": "Note Moyenne",
|
||||
"basedOnReviews": "Basé sur 1000+ avis",
|
||||
"happyCustomers": "Clients Satisfaits",
|
||||
"worldwide": "Dans le Monde Entier",
|
||||
"naturalIngredients": "Ingrédients Naturels",
|
||||
"noAdditives": "Sans Additifs",
|
||||
"freeShipping": "Livraison Gratuite",
|
||||
"ordersOver": "Commandes +3.000 RSD"
|
||||
},
|
||||
"ProblemSection": {
|
||||
"title": "Le Problème",
|
||||
"subtitle": "Fatigué des Produits Capillaires & Cutanés Qui Ne Delivrent Pas?",
|
||||
"description": "Vous méritez mieux que des produits remplis de produits chimiques agressifs et de promesses vides",
|
||||
"problems": [
|
||||
{
|
||||
"problem": "Cheveux Secs et Endommagés",
|
||||
"description": "Les produits laissent vos cheveux cassants, crépus et se cassent malgré des traitements coûteux"
|
||||
},
|
||||
{
|
||||
"problem": "Ingrédients Déroutants",
|
||||
"description": "Vous ne pouvez pas prononcer ce qu'il y a dans vos soins cutanés. Parabènes, sulfates, parfums synthétiques - toxines dangereuses"
|
||||
},
|
||||
{
|
||||
"problem": "Aucun Vrai Résultat",
|
||||
"description": "D'innombrables produits promettent des miracles mais ne livrent que des promesses vides et de l'argent gaspillé"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AsSeenIn": {
|
||||
"title": "Comme Vus Dans"
|
||||
},
|
||||
"BeforeAfterGallery": {
|
||||
"realResults": "Résultats Réels",
|
||||
"seeTransformation": "Voir la Transformation",
|
||||
"startTransformation": "Commencez Votre Transformation",
|
||||
"before": "AVANT",
|
||||
"after": "APRÈS",
|
||||
"verified": "Vérifié",
|
||||
"timeline": "Après {weeks}"
|
||||
},
|
||||
"HowItWorks": {
|
||||
"title": "Processus Simple",
|
||||
"subtitle": "Comment ManoonOils Fonctionne",
|
||||
"startTransformation": "Commencez Votre Transformation",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Choisissez Votre Huile",
|
||||
"description": "Sélectionnez parmi notre collection d'huiles pures cold-pressed formulées pour vos besoins spécifiques en cheveux et peau."
|
||||
},
|
||||
{
|
||||
"title": "Appliquez Quotidiennement",
|
||||
"description": "Massez quelques gouttes dans des cheveux ou une peau humides. Nos huiles s'absorbent instantanément - jamais grasses, toujours nourrissantes."
|
||||
},
|
||||
{
|
||||
"title": "Voyez les Résultats",
|
||||
"description": "Vivez la transformation en 4-6 semaines. Cheveux plus brillants, peau radieuse et confiance qui rayonne."
|
||||
}
|
||||
]
|
||||
},
|
||||
"Header": {
|
||||
"products": "Produits",
|
||||
"about": "À Propos",
|
||||
"contact": "Contact",
|
||||
"cart": "Panier",
|
||||
"account": "Compte",
|
||||
"openMenu": "Ouvrir le menu",
|
||||
"closeMenu": "Fermer le menu",
|
||||
"openCart": "Ouvrir le panier"
|
||||
},
|
||||
"ProductCard": {
|
||||
"noImage": "Pas d'image",
|
||||
"outOfStock": "Rupture de Stock",
|
||||
"quickAdd": "Ajout Rapide",
|
||||
"contactForPrice": "Contacter pour le prix"
|
||||
},
|
||||
"ProductDetail": {
|
||||
"home": "Accueil",
|
||||
"outOfStock": "Rupture de Stock",
|
||||
"size": "Taille",
|
||||
"qty": "Qté",
|
||||
"adding": "Ajout en cours...",
|
||||
"transformHairSkin": "Transformer Mes Cheveux & Ma Peau",
|
||||
"freeShipping": "Livraison gratuite sur les commandes de +3.000 RSD",
|
||||
"guarantee": "Garantie 30 Jours",
|
||||
"secureCheckout": "Paiement Sécurisé",
|
||||
"easyReturns": "Retours Faciles",
|
||||
"benefits": "Bienfaits",
|
||||
"description": "Description",
|
||||
"howToUse": "Comment Utiliser",
|
||||
"howToUseText": "Appliquez une petite quantité sur des cheveux ou une peau propres et humides. Massez doucement jusqu'à absorption. Utilisez quotidiennement pour de meilleurs résultats.",
|
||||
"ingredients": "Ingrédients",
|
||||
"ingredientsText": "100% Huile Naturelle Pure. Aucun additif, conservateur ou parfum artificiel.",
|
||||
"youMayAlsoLike": "Vous Aimerez Aussi",
|
||||
"similarProducts": "Produits Similaires",
|
||||
"stocksRunningOut": "Les stocks s'épuisent!",
|
||||
"urgency1": "Dépêchez-vous! 500+ articles vendus ces 3 derniers jours!",
|
||||
"urgency2": "Dans les paniers de 2,5K personnes - achetez avant qu'il ne disparaisse!",
|
||||
"urgency3": "7 562 personnes ont vu ce produit ces dernières 24 heures!"
|
||||
},
|
||||
"Newsletter": {
|
||||
"stayConnected": "Restez Connectés",
|
||||
"joinCommunity": "Rejoignez Notre Communauté",
|
||||
"newsletterText": "Abonnez-vous pour recevoir des offres exclusives, des conseils beauté et être les premiers informés des nouveaux produits.",
|
||||
"emailPlaceholder": "Entrez votre email",
|
||||
"subscribe": "S'abonner"
|
||||
},
|
||||
"ProductBenefits": {
|
||||
"whyChoose": "Pourquoi Choisir Ce Produit",
|
||||
"manoonDifference": "La Différence Manoon",
|
||||
"pureNatural": "Pur & Naturel",
|
||||
"pureNaturalDesc": "Ingrédients 100% naturels sans additifs ni conservateurs",
|
||||
"crueltyFree": "Cruelty Free",
|
||||
"crueltyFreeDesc": "Jamais testé sur les animaux, ingrédients sourcés éthiquement",
|
||||
"madeWithLove": "Fait avec Amour",
|
||||
"madeWithLoveDesc": "Fabriqué à la main en petites séries pour une qualité maximale",
|
||||
"visibleResults": "Résultats Visibles",
|
||||
"visibleResultsDesc": "Des améliorations perceptibles en 4-6 semaines"
|
||||
},
|
||||
"Checkout": {
|
||||
"checkout": "Commande",
|
||||
"shippingAddress": "Adresse de Livraison",
|
||||
"firstName": "Prénom",
|
||||
"lastName": "Nom",
|
||||
"streetAddress": "Rue et Numéro",
|
||||
"streetAddressOptional": "Appartement, suite, etc. (optionnel)",
|
||||
"city": "Ville",
|
||||
"postalCode": "Code Postal",
|
||||
"phone": "Téléphone",
|
||||
"billingAddressSame": "L'adresse de facturation est la même que l'adresse de livraison",
|
||||
"billingAddress": "Adresse de Facturation",
|
||||
"paymentMethod": "Mode de Paiement",
|
||||
"cashOnDelivery": "Contre-remboursement (COD)",
|
||||
"cashOnDeliveryDesc": "Payez lorsque votre commande est livrée à votre porte.",
|
||||
"processing": "En cours...",
|
||||
"completeOrder": "Finaliser la Commande - {total}",
|
||||
"orderSummary": "Résumé de la Commande",
|
||||
"qty": "Qté",
|
||||
"subtotal": "Sous-total",
|
||||
"shipping": "Livraison",
|
||||
"calculated": "Calculé",
|
||||
"total": "Total",
|
||||
"yourCartEmpty": "Votre panier est vide",
|
||||
"continueShopping": "Continuer les Achats",
|
||||
"errorNoCheckout": "Pas de paiement actif. Veuillez réessayer.",
|
||||
"errorOccurred": "Une erreur s'est produite lors du paiement.",
|
||||
"errorCreatingOrder": "Échec de la création de la commande.",
|
||||
"orderConfirmed": "Commande Confirmée!",
|
||||
"thankYou": "Merci pour votre achat.",
|
||||
"orderNumber": "Numéro de Commande",
|
||||
"confirmationEmail": "Vous recevrez bientôt un email de confirmation. Nous vous contacterons pour organiser le paiement contre-remboursement.",
|
||||
"continueShoppingBtn": "Continuer les Achats"
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@
|
||||
"sustainableDesc": "Etički nabavljeni sastojci i ekološka ambalaža za bolju planetu."
|
||||
},
|
||||
"Products": {
|
||||
"collection": "Naša kolekcija",
|
||||
"allProducts": "Svi proizvodi",
|
||||
"productsCount": "{count} proizvoda",
|
||||
"featured": "Istaknuto",
|
||||
@@ -175,5 +176,251 @@
|
||||
"text": "U početku sam bila skeptična, ali nakon 3 nedelje korišćenja ulja od šipka, tekstura moje kože se drastično poboljšala. Kvalitet je neuporediv."
|
||||
}
|
||||
]
|
||||
},
|
||||
"ProductReviews": {
|
||||
"customerReviews": "Ocene kupaca",
|
||||
"whatCustomersSay": "Šta kupci kažu",
|
||||
"basedOnReviews": "Na osnovu 1000+ recenzija",
|
||||
"reviews": [
|
||||
{ "id": 1, "name": "Ana M.", "location": "Beograd", "text": "Manoon Anti-age Serum je transformisao moju kožu za samo 2 nedelje!", "rating": 5 },
|
||||
{ "id": 2, "name": "Milica P.", "location": "Novi Sad", "text": "Najbolji dnevni serum koji sam ikada koristila. Moje bore su vidno smanjene.", "rating": 5 },
|
||||
{ "id": 3, "name": "Jelena K.", "location": "Beograd", "text": "Manoon noćni serum je čista magija. Probudite se sa blistavom kožom svako jutro.", "rating": 5 },
|
||||
{ "id": 4, "name": "Stefan R.", "location": "Subotica", "text": "Anti-age set vredi svaki dinar. Moja supruga i ja ga oboje koristimo.", "rating": 5 },
|
||||
{ "id": 5, "name": "Marija T.", "location": "Kragujevac", "text": "Konačno sam pronašla serum koji zaista deluje! Manoon ispunjava obećanja.", "rating": 5 },
|
||||
{ "id": 6, "name": "Nikola V.", "location": "Niš", "text": "Moje fine linije nestaju. Ovaj dnevni serum je neverovatan.", "rating": 5 },
|
||||
{ "id": 7, "name": "Ivana L.", "location": "Beograd", "text": "Manoon jutarnji serum miriše božanstveno i još bolje deluje.", "rating": 5 },
|
||||
{ "id": 8, "name": "Dejan M.", "location": "Novi Sad", "text": "Noćni serum je potpuno transformisao moju rutinu nege kože.", "rating": 5 },
|
||||
{ "id": 9, "name": "Sanja B.", "location": "Kragujevac", "text": "Moja koža izgleda 10 godina mlađe nakon mesec dana korišćenja Manoon-a.", "rating": 5 },
|
||||
{ "id": 10, "name": "Marko J.", "location": "Subotica", "text": "Anti-age set je savršen poklon. Moja majka ga obožava!", "rating": 5 },
|
||||
{ "id": 11, "name": "Petra D.", "location": "Niš", "text": "Tekstura Manoon seruma je toliko luksuzna. Vredi svaki dinar.", "rating": 5 },
|
||||
{ "id": 12, "name": "Luka G.", "location": "Beograd", "text": "Dnevni serum se upija momentalno. Nikakav masni osećaj!", "rating": 5 },
|
||||
{ "id": 13, "name": "Maja S.", "location": "Novi Sad", "text": "Moj kozmetičar je pitao šta koristim. Manoon je sada moja tajna!", "rating": 5 },
|
||||
{ "id": 14, "name": "Vladimir P.", "location": "Kragujevac", "text": "Noćni serum deluje dok spavam. Probudite se sa vidljivo glatkom kožom.", "rating": 5 },
|
||||
{ "id": 15, "name": "Katarina N.", "location": "Subotica", "text": "Anti-age set je stigao lepo upakovan. Savršen za poklone.", "rating": 5 },
|
||||
{ "id": 16, "name": "Bojan R.", "location": "Niš", "text": "Koristim Manoon 3 meseca. Moje bore su primetno smanjene.", "rating": 5 },
|
||||
{ "id": 17, "name": "Tamara F.", "location": "Beograd", "text": "Dnevni serum pruža savršenu bazu ispod šminke.", "rating": 5 },
|
||||
{ "id": 18, "name": "Aleksandar K.", "location": "Novi Sad", "text": "Konačno srpski brend koji se takmiči sa luksuznim međunarodnim brendovima!", "rating": 5 },
|
||||
{ "id": 19, "name": "Natalia M.", "location": "Kragujevac", "text": "Moja osetljiva koža obožava Manoon. Bez ikakve iritacije.", "rating": 5 },
|
||||
{ "id": 20, "name": "Filip T.", "location": "Subotica", "text": "Anti-age serum je lagan, a opet neverovatno efektan.", "rating": 5 },
|
||||
{ "id": 21, "name": "Andrea L.", "location": "Niš", "text": "Manoon noćni serum je moja večernja rutina. Koža izgleda neverovatno!", "rating": 5 },
|
||||
{ "id": 22, "name": "Ognjen P.", "location": "Beograd", "text": "Prijatelji neprestano pitaju šta sam promenio u rutini nege kože.", "rating": 5 },
|
||||
{ "id": 23, "name": "Mila J.", "location": "Novi Sad", "text": "Anti-age set sadrži sve što vam treba. Odlična vrednost!", "rating": 5 },
|
||||
{ "id": 24, "name": "Dragan S.", "location": "Kragujevac", "text": "Čak je i moj muž primetio razliku. Sada i on koristi dnevni serum!", "rating": 5 },
|
||||
{ "id": 25, "name": "Jovana V.", "location": "Subotica", "text": "Jutarnji serum za sjaj daje najlepšu luminoznost.", "rating": 5 },
|
||||
{ "id": 26, "name": "Stefan M.", "location": "Niš", "text": "Manoon proizvodi su sada neophodni u mojoj dnevnoj rutini.", "rating": 5 },
|
||||
{ "id": 27, "name": "Ana R.", "location": "Beograd", "text": "Noćni serum je pomogao da se pročisti ten. Koža izgleda tako zdravo!", "rating": 5 },
|
||||
{ "id": 28, "name": "Nenad L.", "location": "Novi Sad", "text": "Anti-aging rezultati vidljivi za par nedelja. Toplo preporučujem Manoon!", "rating": 5 },
|
||||
{ "id": 29, "name": "Sofija D.", "location": "Kragujevac", "text": "Tekstura je božanstvena. Oseća se kao luksuzni spa tretman kod kuće.", "rating": 5 },
|
||||
{ "id": 30, "name": "Velibor K.", "location": "Subotica", "text": "Moje bore oko očiju su se značajno smanjile. Hvala Manoon!", "rating": 5 },
|
||||
{ "id": 31, "name": "Irena M.", "location": "Niš", "text": "Anti-age set je savršen poklon za rođendan moje majke.", "rating": 5 },
|
||||
{ "id": 32, "name": "Radoslav P.", "location": "Beograd", "text": "Profesionalni kvalitet seruma po poštenoj ceni. Srpska izvrsnost!", "rating": 5 },
|
||||
{ "id": 33, "name": "Jelena B.", "location": "Novi Sad", "text": "Moja koža nikad nije bila ovako hidrirana. Dnevni serum je neverovatan!", "rating": 5 },
|
||||
{ "id": 34, "name": "Dimitrije S.", "location": "Kragujevac", "text": "Noćni serum vredi svog zlata. Čista luksuz!", "rating": 5 },
|
||||
{ "id": 35, "name": "Minela G.", "location": "Subotica", "text": "Manoon ispunjava očekivanja. Moja koža izgleda osveženo i mlado.", "rating": 5 },
|
||||
{ "id": 36, "name": "Zoran T.", "location": "Niš", "text": "Isprobao sam mnoge serume. Manoon je daleko najefektivniji.", "rating": 5 },
|
||||
{ "id": 37, "name": "Mirjana F.", "location": "Beograd", "text": "Anti-age set je potpuno transformisao rutinu nege kože moje majke.", "rating": 5 },
|
||||
{ "id": 38, "name": "Ivan J.", "location": "Novi Sad", "text": "Brzo delujući serum sa stvarnim rezultatima. Preporučujem Manoon svima.", "rating": 5 },
|
||||
{ "id": 39, "name": "Kristina P.", "location": "Kragujevac", "text": "Jutarnji serum za sjaj daje tako lep deve sjaj.", "rating": 5 },
|
||||
{ "id": 40, "name": "Bratislav L.", "location": "Subotica", "text": "Primetni rezultati za samo 2 nedelje. Ovaj serum je prava stvar!", "rating": 5 },
|
||||
{ "id": 41, "name": "Zorica M.", "location": "Niš", "text": "Noćni serum je izbrisao godine sa mog lica. Apsolutno čudesno!", "rating": 5 },
|
||||
{ "id": 42, "name": "Patrik N.", "location": "Beograd", "text": "Premium kvalitet srpske kozmetike koja se takmiči sa međunarodnim luksuznim brendovima.", "rating": 5 },
|
||||
{ "id": 43, "name": "Simona K.", "location": "Novi Sad", "text": "Manoon Anti-age Serum je najbolja investicija u moju kožu ikada.", "rating": 5 },
|
||||
{ "id": 44, "name": "Mladen D.", "location": "Kragujevac", "text": "Dnevni serum se upije za sekundu. Nema čekanja!", "rating": 5 },
|
||||
{ "id": 45, "name": "Ljiljana R.", "location": "Subotica", "text": "Poklanjam Anti-age set sestrama. Obožale su ga!", "rating": 5 },
|
||||
{ "id": 46, "name": "Tomislav V.", "location": "Niš", "text": "Moje bore su vidno smanjene nakon mesec dana korišćenja Manoon-a.", "rating": 5 },
|
||||
{ "id": 47, "name": "Emilija S.", "location": "Beograd", "text": "Noćni serum ostavlja moju kožu tako mekom i obnovljenom svako jutro.", "rating": 5 },
|
||||
{ "id": 48, "name": "Andrija P.", "location": "Novi Sad", "text": "Manoon dnevni serum je savršen ispod sunscreena. Neophodna kombinacija!", "rating": 5 },
|
||||
{ "id": 49, "name": "Miona L.", "location": "Kragujevac", "text": "Moja koža izgleda zračno i mlado. Ne mogu biti srećnija sa Manoon-om!", "rating": 5 },
|
||||
{ "id": 50, "name": "Slavko M.", "location": "Subotica", "text": "Anti-age set daje vidljive rezultate. Prava srpska kvaliteta!", "rating": 5 }
|
||||
]
|
||||
},
|
||||
"TrustBadges": {
|
||||
"averageRating": "Prosečna ocena",
|
||||
"basedOnReviews": "Na osnovu 1000+ recenzija",
|
||||
"happyCustomers": "Srećni kupci",
|
||||
"worldwide": "Širom sveta",
|
||||
"naturalIngredients": "Prirodni sastojci",
|
||||
"noAdditives": "Bez aditiva",
|
||||
"freeShipping": "Besplatna dostava",
|
||||
"ordersOver": "Porudžbine preko 3.000 RSD"
|
||||
},
|
||||
"ProblemSection": {
|
||||
"title": "Problem",
|
||||
"subtitle": "Zamareni proizvodima za kosu i kožu koji ne ispunjavaju obećanja?",
|
||||
"description": "Zaslužujete više od proizvoda punih grubih hemikalija i praznih obećanja",
|
||||
"problems": [
|
||||
{
|
||||
"problem": "Suva, oštećena kosa",
|
||||
"description": "Proizvodi ostavljaju vašu kosu krhkom, frizirajućom i lomljivom uprkos skupim tretmanima"
|
||||
},
|
||||
{
|
||||
"problem": "Zbunjeni sastojci",
|
||||
"description": "Ne možete izgovoriti šta je u vašoj nezi kože. Parabeni, sulfati, sintetički mirisi—opasni toksini"
|
||||
},
|
||||
{
|
||||
"problem": "Bez stvarnih rezultata",
|
||||
"description": "Bezbroj proizvoda obećava čuda ali isporučuju samo prazna obećanja i utrošen novac"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AsSeenIn": {
|
||||
"title": "Kao što je viđeno u"
|
||||
},
|
||||
"BeforeAfterGallery": {
|
||||
"realResults": "Stvarni rezultati",
|
||||
"seeTransformation": "Pogledajte transformaciju",
|
||||
"startTransformation": "Započnite vašu transformaciju",
|
||||
"before": "PRE",
|
||||
"after": "POSLE",
|
||||
"verified": "Potvrđeno",
|
||||
"timeline": "Nakon {weeks}"
|
||||
},
|
||||
"HowItWorks": {
|
||||
"title": "Jednostavan proces",
|
||||
"subtitle": "Kako ManoonOils funkcioniše",
|
||||
"startTransformation": "Započnite vašu transformaciju",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Izaberite vaše ulje",
|
||||
"description": "Izaberite iz naše kolekcije čistih, hladno ceđenih ulja formulisanih za vaše specifične potrebe kose i kože."
|
||||
},
|
||||
{
|
||||
"title": "Nanesite svakodnevno",
|
||||
"description": "Umasirajte nekoliko kapi u vlažnu kosu ili kožu. Naša ulja se momentalno upijaju—nikada masna, uvek negujuća."
|
||||
},
|
||||
{
|
||||
"title": "Vidite rezultate",
|
||||
"description": "Doživite transformaciju za 4-6 nedelja. Sjajnija kosa, zračna koža i samopouzdanje koje sija."
|
||||
}
|
||||
]
|
||||
},
|
||||
"Header": {
|
||||
"products": "Proizvodi",
|
||||
"about": "O nama",
|
||||
"contact": "Kontakt",
|
||||
"cart": "Korpa",
|
||||
"account": "Nalog",
|
||||
"openMenu": "Otvori meni",
|
||||
"closeMenu": "Zatvori meni",
|
||||
"openCart": "Otvori korpu"
|
||||
},
|
||||
"Footer": {
|
||||
"shop": "Prodavnica",
|
||||
"allProducts": "Svi proizvodi",
|
||||
"hairCare": "Nega kose",
|
||||
"skinCare": "Nega kože",
|
||||
"giftSets": "Poklon setovi",
|
||||
"about": "O nama",
|
||||
"ourStory": "Naša priča",
|
||||
"process": "Proces",
|
||||
"sustainability": "Održivost",
|
||||
"help": "Pomoć",
|
||||
"faq": "Česta pitanja",
|
||||
"shipping": "Dostava",
|
||||
"returns": "Povrat",
|
||||
"contactUs": "Kontaktirajte nas",
|
||||
"brandDescription": "Premium prirodna ulja za negu kose i kože. Ručno pravljena sa ljubavlju, korišćenjem tradicionalnih metoda.",
|
||||
"weAccept": "Prihvatamo:",
|
||||
"allRights": "Sva prava zadržana."
|
||||
},
|
||||
"ProductCard": {
|
||||
"noImage": "Nema slike",
|
||||
"outOfStock": "Nema na stanju",
|
||||
"quickAdd": "Brzo dodavanje",
|
||||
"contactForPrice": "Kontaktirajte za cenu"
|
||||
},
|
||||
"ProductDetail": {
|
||||
"home": "Početna",
|
||||
"outOfStock": "Nema na stanju",
|
||||
"size": "Veličina",
|
||||
"qty": "Kol",
|
||||
"adding": "Dodavanje...",
|
||||
"transformHairSkin": "Transformiši kosu i kožu",
|
||||
"freeShipping": "Besplatna dostava za porudžbine preko 3.000 RSD",
|
||||
"guarantee": "30-dnevna garancija",
|
||||
"secureCheckout": "Sigurno plaćanje",
|
||||
"easyReturns": "Lak povrat",
|
||||
"benefits": "Prednosti",
|
||||
"description": "Opis",
|
||||
"howToUse": "Kako koristiti",
|
||||
"howToUseText": "Nanesite malu količinu na čistu, vlažnu kosu ili kožu. Nežno masirajte dok se ne upije. Koristite svakodnevno za najbolje rezultate.",
|
||||
"ingredients": "Sastojci",
|
||||
"ingredientsText": "100% čisto prirodno ulje. Bez dodataka, konzervansa ili veštačkih mirisa.",
|
||||
"youMayAlsoLike": "Možda će vam se svideti",
|
||||
"similarProducts": "Slični proizvodi",
|
||||
"stocksRunningOut": "Zalihe se smanjuju!",
|
||||
"urgency1": "Požuri! 500+ proizvoda prodato u poslednja 3 dana!",
|
||||
"urgency2": "U korpama 2.5K ljudi - kupi pre nego što nestane!",
|
||||
"urgency3": "7.562 osobe su pogledale ovaj proizvod u poslednja 24 sata!"
|
||||
},
|
||||
"Newsletter": {
|
||||
"stayConnected": "Ostanite povezani",
|
||||
"joinCommunity": "Pridružite se našoj zajednici",
|
||||
"newsletterText": "Pretplatite se da biste primali ekskluzivne ponude, savete za negu i budite prvi koji ćete saznati za nove proizvode.",
|
||||
"emailPlaceholder": "Unesite vaš email",
|
||||
"subscribe": "Pretplatite se"
|
||||
},
|
||||
"ProductBenefits": {
|
||||
"whyChoose": "Zašto odabrati ovaj proizvod",
|
||||
"manoonDifference": "Manoon razlika",
|
||||
"pureNatural": "Čisto i prirodno",
|
||||
"pureNaturalDesc": "100% prirodni sastojci bez aditiva ili konzervansa",
|
||||
"crueltyFree": "Bez okrutnosti",
|
||||
"crueltyFreeDesc": "Nikada testirano na životinjama, etički nabavljeni sastojci",
|
||||
"madeWithLove": "Napravljeno sa ljubavlju",
|
||||
"madeWithLoveDesc": "Ručno pravljeno u malim serijama za maksimalni kvalitet",
|
||||
"visibleResults": "Vidljivi rezultati",
|
||||
"visibleResultsDesc": "Primetna poboljšanja za 4-6 nedelja"
|
||||
},
|
||||
"Cart": {
|
||||
"yourCart": "Vaša korpa",
|
||||
"closeCart": "Zatvori korpu",
|
||||
"dismiss": "Odbaci",
|
||||
"yourCartEmpty": "Vaša korpa je prazna",
|
||||
"looksLikeEmpty": "Izgleda da još uvek niste dodali ništa u korpu.",
|
||||
"startShopping": "Započni kupovinu",
|
||||
"subtotal": "Ukupno",
|
||||
"shipping": "Dostava",
|
||||
"calculatedAtCheckout": "Racunato pri kupovini",
|
||||
"total": "Ukupno",
|
||||
"freeShippingOver": "Besplatna dostava za porudžbine preko {amount}",
|
||||
"processing": "Obrađivanje...",
|
||||
"checkout": "Kupovina",
|
||||
"continueShopping": "Nastavi kupovinu",
|
||||
"removeItem": "Ukloni proizvod"
|
||||
},
|
||||
"Checkout": {
|
||||
"checkout": "Kupovina",
|
||||
"shippingAddress": "Adresa za dostavu",
|
||||
"firstName": "Ime",
|
||||
"lastName": "Prezime",
|
||||
"streetAddress": "Ulica i broj",
|
||||
"streetAddressOptional": "Stan, apartman, itd. (opciono)",
|
||||
"city": "Grad",
|
||||
"postalCode": "Poštanski broj",
|
||||
"phone": "Telefon",
|
||||
"billingAddressSame": "Adresa za naplatu je ista kao adresa za dostavu",
|
||||
"billingAddress": "Adresa za naplatu",
|
||||
"paymentMethod": "Način plaćanja",
|
||||
"cashOnDelivery": "Pouzećem (COD)",
|
||||
"cashOnDeliveryDesc": "Platite kada vam narudžbina bude isporučena na vrata.",
|
||||
"processing": "Obrađivanje...",
|
||||
"completeOrder": "Završi narudžbinu - {total}",
|
||||
"orderSummary": "Pregled narudžbine",
|
||||
"qty": "Kol",
|
||||
"subtotal": "Ukupno",
|
||||
"shipping": "Dostava",
|
||||
"calculated": "Po obračunu",
|
||||
"total": "Ukupno",
|
||||
"yourCartEmpty": "Vaša korpa je prazna",
|
||||
"continueShopping": "Nastavi kupovinu",
|
||||
"errorNoCheckout": "Nema aktivne korpe. Molimo pokušajte ponovo.",
|
||||
"errorOccurred": "Došlo je do greške prilikom kupovine.",
|
||||
"errorCreatingOrder": "Neuspešno kreiranje narudžbine.",
|
||||
"orderConfirmed": "Narudžbina potvrđena!",
|
||||
"thankYou": "Hvala vam na kupovini!",
|
||||
"orderNumber": "Broj narudžbine",
|
||||
"confirmationEmail": "Uскoro ćete primiti email potvrde. Kontaktiraćemo vas da dogovorimo pouzećem plaćanje.",
|
||||
"continueShoppingBtn": "Nastavi kupovinu"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { routing } from './routing';
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
|
||||
if (!locale || !routing.locales.includes(locale as any)) {
|
||||
if (!locale || !routing.locales.includes(locale as typeof routing.locales[number])) {
|
||||
locale = routing.defaultLocale;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
||||
locale,
|
||||
messages: (await import(`./messages/${locale}.json`)).default
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -4,4 +4,4 @@ export const routing = defineRouting({
|
||||
locales: ["sr", "en", "de", "fr"],
|
||||
defaultLocale: "sr",
|
||||
localePrefix: "as-needed",
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user