Redesign phase 1: Homepage polish and design system foundation
- Fix newsletter subscribe box centering on homepage - Fix header overlap on product pages (pt-[72px] instead of pt-[100px]) - Add scroll-mt-[72px] for smooth scroll anchor offset - Add HeroVideo component with video hero placeholder - Add REDESIGN_SPECIFICATION.md with 9-phase design plan - Clean up globals.css theme declarations and comments - Update Header with improved sticky behavior and cart - Update ProductDetail with better layout and spacing - Update CartDrawer with improved slide-out cart UI - Add English translations for updated pages - Various CSS refinements across pages
This commit is contained in:
@@ -8,59 +8,114 @@ export const metadata = {
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-8">
|
||||
Our Story
|
||||
</h1>
|
||||
|
||||
<div className="prose prose-lg max-w-none text-foreground-muted space-y-6">
|
||||
<p>
|
||||
ManoonOils was born from a passion for natural beauty and the belief
|
||||
that the best skincare comes from nature itself. Our journey began with
|
||||
a simple question: how can we create products that truly nurture both
|
||||
hair and skin?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We believe in the power of natural ingredients. Every oil in our
|
||||
collection is carefully selected for its unique properties and
|
||||
benefits. From nourishing oils that restore hair vitality to serums
|
||||
that rejuvenate skin, we craft each product with love and attention
|
||||
to detail.
|
||||
</p>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p>
|
||||
Our mission is to provide premium quality, natural products that
|
||||
enhance your daily beauty routine. We are committed to:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li>Using only the finest natural ingredients</li>
|
||||
<li>Cruelty-free and ethical production</li>
|
||||
<li>Sustainable packaging practices</li>
|
||||
<li>Transparency in our formulations</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Handmade with Love
|
||||
</h2>
|
||||
<p>
|
||||
Every bottle of ManoonOils is handcrafted with care. We small-batch
|
||||
produce our products to ensure the highest quality and freshness.
|
||||
When you use ManoonOils, you can feel confident that you're using
|
||||
something made with genuine care and expertise.
|
||||
</p>
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Page Header */}
|
||||
<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">Our Story</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight">
|
||||
About ManoonOils
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero Image */}
|
||||
<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>
|
||||
|
||||
{/* Content */}
|
||||
<section className="py-16 md:py-24">
|
||||
<div className="container">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
{/* Introduction */}
|
||||
<div className="mb-16">
|
||||
<p className="text-xl md:text-2xl text-[#1a1a1a] leading-relaxed mb-8">
|
||||
ManoonOils was born from a passion for natural beauty and the belief
|
||||
that the best skincare comes from nature itself.
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
We believe in the power of natural ingredients. Every oil in our
|
||||
collection is carefully selected for its unique properties and
|
||||
benefits. From nourishing oils that restore hair vitality to serums
|
||||
that rejuvenate skin, we craft each product with love and attention
|
||||
to detail.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Values Grid */}
|
||||
<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">Natural Ingredients</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
We use only the finest natural ingredients, sourced ethically and sustainably
|
||||
from trusted suppliers around the world.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Cruelty-Free</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Our products are never tested on animals. We believe in beauty
|
||||
without compromise.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Sustainable Packaging</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
We use eco-friendly packaging materials and minimize waste
|
||||
throughout our production process.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-[#f8f9fa]">
|
||||
<h3 className="text-lg font-medium mb-3">Handcrafted Quality</h3>
|
||||
<p className="text-[#666666] text-sm leading-relaxed">
|
||||
Every bottle is handcrafted in small batches to ensure
|
||||
the highest quality and freshness.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mission */}
|
||||
<div className="text-center py-12 border-t border-b border-[#e5e5e5]">
|
||||
<span className="text-caption text-[#666666] mb-4 block">Our Mission</span>
|
||||
<blockquote className="text-2xl md:text-3xl font-medium tracking-tight">
|
||||
“To provide premium quality, natural products that enhance
|
||||
your daily beauty routine.”
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
{/* Story Section */}
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-medium mb-6">Handmade with Love</h2>
|
||||
<p className="text-[#666666] leading-relaxed mb-6">
|
||||
Every bottle of ManoonOils is handcrafted with care. We small-batch
|
||||
produce our products to ensure the highest quality and freshness.
|
||||
When you use ManoonOils, you can feel confident that you're using
|
||||
something made with genuine care and expertise.
|
||||
</p>
|
||||
<p className="text-[#666666] leading-relaxed">
|
||||
Our journey began with a simple question: how can we create products
|
||||
that truly nurture both hair and skin? Today, we continue to innovate
|
||||
while staying true to our commitment to natural, effective beauty solutions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,29 @@ import {
|
||||
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;
|
||||
@@ -94,7 +117,7 @@ export default function CheckoutPage() {
|
||||
|
||||
try {
|
||||
// Update shipping address
|
||||
const shippingResult = await saleorClient.mutate({
|
||||
const shippingResult = await saleorClient.mutate<ShippingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_SHIPPING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
@@ -105,12 +128,12 @@ export default function CheckoutPage() {
|
||||
},
|
||||
});
|
||||
|
||||
if (shippingResult.data?.checkoutShippingAddressUpdate?.errors?.length > 0) {
|
||||
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({
|
||||
const billingResult = await saleorClient.mutate<BillingAddressUpdateResponse>({
|
||||
mutation: CHECKOUT_BILLING_ADDRESS_UPDATE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
@@ -121,19 +144,19 @@ export default function CheckoutPage() {
|
||||
},
|
||||
});
|
||||
|
||||
if (billingResult.data?.checkoutBillingAddressUpdate?.errors?.length > 0) {
|
||||
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({
|
||||
const completeResult = await saleorClient.mutate<CheckoutCompleteResponse>({
|
||||
mutation: CHECKOUT_COMPLETE,
|
||||
variables: {
|
||||
checkoutId: checkout.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (completeResult.data?.checkoutComplete?.errors?.length > 0) {
|
||||
if (completeResult.data?.checkoutComplete?.errors && completeResult.data.checkoutComplete.errors.length > 0) {
|
||||
throw new Error(completeResult.data.checkoutComplete.errors[0].message);
|
||||
}
|
||||
|
||||
@@ -154,9 +177,10 @@ export default function CheckoutPage() {
|
||||
// Order Success Page
|
||||
if (orderComplete) {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<>
|
||||
<Header />
|
||||
<section className="pt-24 pb-20 px-4">
|
||||
<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">
|
||||
@@ -187,16 +211,19 @@ export default function CheckoutPage() {
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
<Footer />
|
||||
</main>
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="pt-24 pb-20 px-4">
|
||||
<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>
|
||||
|
||||
@@ -457,8 +484,11 @@ export default function CheckoutPage() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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({
|
||||
@@ -18,97 +19,185 @@ export default function ContactPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-8">
|
||||
Contact Us
|
||||
</h1>
|
||||
|
||||
<p className="text-foreground-muted text-center mb-12">
|
||||
Have questions? We'd love to hear from you.
|
||||
</p>
|
||||
|
||||
{submitted ? (
|
||||
<div className="bg-green-50 text-green-700 p-6 text-center">
|
||||
<p className="text-lg">Thank you for your message!</p>
|
||||
<p className="mt-2">We'll get back to you soon.</p>
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Page Header */}
|
||||
<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">Get in Touch</span>
|
||||
<h1 className="text-4xl md:text-5xl font-medium tracking-tight mb-4">
|
||||
Contact Us
|
||||
</h1>
|
||||
<p className="text-[#666666]">
|
||||
Have questions? We'd love to hear from you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Section */}
|
||||
<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">
|
||||
{/* Contact Info */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-medium mb-6">Get in Touch</h2>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
We're here to help! Whether you have questions about our products,
|
||||
need assistance with an order, or just want to say hello, we'd love to hear from you.
|
||||
</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">We reply within 24 hours</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">Shipping</h3>
|
||||
<p className="text-[#666666] text-sm">Free shipping over 3,000 RSD</p>
|
||||
<p className="text-[#999999] text-xs mt-1">Delivered within 2-5 business days</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">Location</h3>
|
||||
<p className="text-[#666666] text-sm">Serbia</p>
|
||||
<p className="text-[#999999] text-xs mt-1">Shipping nationwide</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 border border-border focus:outline-none focus:border-foreground"
|
||||
/>
|
||||
</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 border border-border focus:outline-none focus:border-foreground"
|
||||
/>
|
||||
</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 border border-border focus:outline-none focus:border-foreground resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3 bg-foreground text-white hover:bg-accent-dark transition-colors"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<div className="mt-16 pt-8 border-t border-border/30">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Email</h3>
|
||||
<p className="text-foreground-muted">hello@manoonoils.com</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Shipping</h3>
|
||||
<p className="text-foreground-muted">Free over 3000 RSD</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-serif mb-2">Location</h3>
|
||||
<p className="text-foreground-muted">Serbia</p>
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<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">Thank You!</h3>
|
||||
<p className="text-[#666666]">
|
||||
Your message has been sent. We'll get back to you soon.
|
||||
</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="Your name"
|
||||
/>
|
||||
</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="your@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="How can we help you?"
|
||||
/>
|
||||
</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"
|
||||
>
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ 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">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{
|
||||
q: "How long does shipping take?",
|
||||
a: "Orders are typically delivered within 2-5 business days for domestic shipping. You'll receive a tracking number once your order ships."
|
||||
},
|
||||
{
|
||||
q: "Are your products 100% natural?",
|
||||
a: "Yes! All our oils are 100% natural, cold-pressed, and free from any additives, preservatives, or artificial fragrances."
|
||||
},
|
||||
{
|
||||
q: "What is your return policy?",
|
||||
a: "We accept returns within 14 days of delivery for unopened products. Please contact us if you have any issues with your order."
|
||||
},
|
||||
{
|
||||
q: "Do you offer wholesale?",
|
||||
a: "Yes, we offer wholesale pricing for bulk orders. Please contact us at hello@manoonoils.com for more information."
|
||||
}
|
||||
].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>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,2 @@
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
|
||||
export const metadata = {
|
||||
title: "About - ManoonOils",
|
||||
description: "Learn about ManoonOils - our story, mission, and commitment to natural beauty.",
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-8">
|
||||
Our Story
|
||||
</h1>
|
||||
|
||||
<div className="prose prose-lg max-w-none text-foreground-muted space-y-6">
|
||||
<p>
|
||||
ManoonOils was born from a passion for natural beauty and the belief
|
||||
that the best skincare comes from nature itself. Our journey began with
|
||||
a simple question: how can we create products that truly nurture both
|
||||
hair and skin?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We believe in the power of natural ingredients. Every oil in our
|
||||
collection is carefully selected for its unique properties and
|
||||
benefits. From nourishing oils that restore hair vitality to serums
|
||||
that rejuvenate skin, we craft each product with love and attention
|
||||
to detail.
|
||||
</p>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Our Mission
|
||||
</h2>
|
||||
<p>
|
||||
Our mission is to provide premium quality, natural products that
|
||||
enhance your daily beauty routine. We are committed to:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li>Using only the finest natural ingredients</li>
|
||||
<li>Cruelty-free and ethical production</li>
|
||||
<li>Sustainable packaging practices</li>
|
||||
<li>Transparency in our formulations</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="font-serif text-2xl text-foreground mt-8 mb-4">
|
||||
Handmade with Love
|
||||
</h2>
|
||||
<p>
|
||||
Every bottle of ManoonOils is handcrafted with care. We small-batch
|
||||
produce our products to ensure the highest quality and freshness.
|
||||
When you use ManoonOils, you can feel confident that you're using
|
||||
something made with genuine care and expertise.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
// Re-export from main about page
|
||||
export { default, metadata } from "../../about/page";
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import AnnouncementBar from "@/components/home/AnnouncementBar";
|
||||
import NewHero from "@/components/home/NewHero";
|
||||
import StatsSection from "@/components/home/StatsSection";
|
||||
import FeaturesSection from "@/components/home/FeaturesSection";
|
||||
import TestimonialsSection from "@/components/home/TestimonialsSection";
|
||||
import NewsletterSection from "@/components/home/NewsletterSection";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
export const metadata = {
|
||||
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
|
||||
@@ -17,61 +13,160 @@ export const metadata = {
|
||||
export default async function Homepage() {
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts();
|
||||
products = await getProducts("EN");
|
||||
} catch (e) {
|
||||
// Fallback for build time when API is unavailable
|
||||
console.log('Failed to fetch products during build');
|
||||
}
|
||||
const featuredProduct = products.find((p) => p.status === "publish");
|
||||
const publishedProducts = products
|
||||
.filter((p) => p.status === "publish")
|
||||
.slice(0, 4);
|
||||
|
||||
const featuredProducts = products.slice(0, 4);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white">
|
||||
<AnnouncementBar />
|
||||
<div className="pt-10">
|
||||
<Header />
|
||||
</div>
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Hero Section with Video Background */}
|
||||
<HeroVideo />
|
||||
|
||||
{/* New Hero Section */}
|
||||
<NewHero featuredProduct={featuredProduct} />
|
||||
{/* Main Content */}
|
||||
<div id="main-content">
|
||||
{/* Products Grid Section */}
|
||||
{featuredProducts.length > 0 && (
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="container">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Our Collection</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">Premium Natural Oils</h2>
|
||||
<p className="text-[#666666] max-w-xl mx-auto">
|
||||
Cold-pressed, pure, and natural oils for your daily beauty routine
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats & Philosophy Section */}
|
||||
<StatsSection />
|
||||
{/* Products Grid */}
|
||||
<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" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Features Section */}
|
||||
<FeaturesSection />
|
||||
{/* View All Link */}
|
||||
<div className="text-center mt-12">
|
||||
<a
|
||||
href="/en/products"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
View All Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection />
|
||||
|
||||
{/* Newsletter Section */}
|
||||
<NewsletterSection />
|
||||
|
||||
{/* Products Grid Section */}
|
||||
{publishedProducts.length > 0 && (
|
||||
<section className="py-20 px-6 bg-white">
|
||||
<div className="max-w-[1400px] mx-auto">
|
||||
<h2 className="font-serif italic text-4xl text-center mb-4">
|
||||
Our Collection
|
||||
</h2>
|
||||
<p className="text-center text-[#4A4A4A] mb-12 max-w-2xl mx-auto">
|
||||
Cold-pressed, pure, and natural oils for your daily beauty routine
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{publishedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
{/* Brand Story Section */}
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]">
|
||||
<div className="container">
|
||||
<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">Our Story</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-6">Handmade with Love</h2>
|
||||
<p className="text-[#666666] mb-6 leading-relaxed">
|
||||
Every bottle of ManoonOils is crafted with care using traditional
|
||||
methods passed down through generations. We source only the finest
|
||||
organic ingredients to bring you oils that nourish both hair and skin.
|
||||
</p>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
Our commitment to purity means no additives, no preservatives -
|
||||
just nature's goodness in its most potent form.
|
||||
</p>
|
||||
<a
|
||||
href="/en/about"
|
||||
className="inline-block text-sm uppercase tracking-[0.1em] border-b border-black pb-1 hover:text-[#666666] hover:border-[#666666] transition-colors"
|
||||
>
|
||||
Learn More
|
||||
</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="Natural oils production"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
{/* Benefits Section */}
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="container">
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">Why Choose Us</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium">The Manoon Difference</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-12">
|
||||
{[
|
||||
{
|
||||
title: "100% Natural",
|
||||
description: "Pure, cold-pressed oils with no additives or preservatives. Just nature's goodness.",
|
||||
},
|
||||
{
|
||||
title: "Handcrafted",
|
||||
description: "Each batch is carefully prepared by hand to ensure the highest quality.",
|
||||
},
|
||||
{
|
||||
title: "Sustainable",
|
||||
description: "Ethically sourced ingredients and eco-friendly packaging for a better planet.",
|
||||
},
|
||||
].map((benefit, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-[#e8f0f5] flex items-center justify-center">
|
||||
<span className="text-2xl font-medium text-[#1a1a1a]">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-3">{benefit.title}</h3>
|
||||
<p className="text-[#666666]">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Newsletter Section */}
|
||||
<section className="py-28 lg:py-32 px-4 sm:px-6 lg:px-8 bg-[#1a1a1a] text-white mb-16">
|
||||
<div className="container">
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-white/60 mb-3 block">Stay Connected</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6">Join Our Community</h2>
|
||||
<p className="text-white/70 mb-10 mx-auto text-lg">
|
||||
Subscribe to receive exclusive offers, beauty tips, and be the first to know about new products.
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<form className="inline-flex flex-col sm:flex-row">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
className="w-64 sm:w-80 px-5 h-14 bg-white/10 border border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Import ProductCard here to avoid circular dependency
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
@@ -1,70 +1,97 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
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";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
interface ProductPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
// Disable static generation - this page will be server-rendered
|
||||
export const generateStaticParams = undefined;
|
||||
|
||||
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = await params;
|
||||
let product = null;
|
||||
|
||||
// Generate static params for all products
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const products = await getProducts();
|
||||
product = products.find((p) => (p.slug || p.id.toString()) === slug);
|
||||
const products = await getProducts("EN", 100);
|
||||
const params: Array<{ slug: string }> = [];
|
||||
|
||||
products.forEach((product: Product) => {
|
||||
// English slug (if translation exists)
|
||||
if (product.translation?.slug) {
|
||||
params.push({ slug: product.translation.slug });
|
||||
} else {
|
||||
params.push({ slug: product.slug });
|
||||
}
|
||||
});
|
||||
|
||||
return params;
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const { slug } = await params;
|
||||
const product = await getProductBySlug(slug, "EN");
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<main className="min-h-screen bg-white">
|
||||
<Header />
|
||||
<div className="pt-24 text-center">
|
||||
<h1 className="text-2xl">Product not found</h1>
|
||||
<div className="pt-[120px] 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="/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>
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const image = product.images?.[0]?.src || '/placeholder.jpg';
|
||||
const price = product.sale_price || product.price;
|
||||
// Get related products
|
||||
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 (
|
||||
<main className="min-h-screen">
|
||||
<main className="min-h-screen bg-white">
|
||||
<Header />
|
||||
|
||||
<section className="pt-24 pb-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="relative aspect-[4/5] bg-background-ice overflow-hidden">
|
||||
<img
|
||||
src={image}
|
||||
alt={product.name}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-4xl font-serif mb-4">{product.name}</h1>
|
||||
|
||||
<div className="text-2xl mb-6">{price} RSD</div>
|
||||
|
||||
<div className="prose max-w-none mb-8" dangerouslySetInnerHTML={{ __html: product.description || '' }} />
|
||||
|
||||
<button
|
||||
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium text-center hover:bg-opacity-90 transition-all"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ProductDetail
|
||||
product={product}
|
||||
relatedProducts={relatedProducts}
|
||||
locale="EN"
|
||||
/>
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
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: "Products - ManoonOils",
|
||||
@@ -9,38 +10,71 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function ProductsPage() {
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts();
|
||||
} catch (e) {
|
||||
console.log('Failed to fetch products during build');
|
||||
}
|
||||
|
||||
const publishedProducts = products.filter((p) => p.status === "publish");
|
||||
const products = await getProducts("EN");
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-16">
|
||||
All Products
|
||||
</h1>
|
||||
|
||||
{publishedProducts.length === 0 ? (
|
||||
<p className="text-center text-foreground-muted">No products available</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{publishedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Page Header */}
|
||||
<div className="pt-[104px]">
|
||||
<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">Our Collection</span>
|
||||
<h1 className="text-3xl md:text-4xl font-medium">All Products</h1>
|
||||
</div>
|
||||
|
||||
{/* Sort Dropdown */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-[#666666]">{products.length} products</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">Featured</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="price-low">Price: Low to High</option>
|
||||
<option value="price-high">Price: High to Low</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>
|
||||
|
||||
{/* Products Grid */}
|
||||
<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">No products available</p>
|
||||
<p className="text-sm text-[#999999]">Please check back later for new arrivals.</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>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,87 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* ============================================
|
||||
MANOONOILS DESIGN SYSTEM
|
||||
Inspired by premium skincare brands
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
--background: #f0f4f8;
|
||||
--background-ice: #e8f0f5;
|
||||
--foreground: #1a1a1a;
|
||||
--foreground-muted: #666666;
|
||||
--accent: #a8c5d8;
|
||||
--accent-dark: #7ba3bc;
|
||||
--white: #ffffff;
|
||||
--border: #d1d9e0;
|
||||
/* Primary Colors */
|
||||
--color-white: #ffffff;
|
||||
--color-background: #fafafa;
|
||||
--color-background-alt: #f5f5f5;
|
||||
--color-foreground: #1a1a1a;
|
||||
--color-foreground-muted: #666666;
|
||||
--color-foreground-subtle: #999999;
|
||||
|
||||
/* Accent Colors */
|
||||
--color-accent: #e8f0f5;
|
||||
--color-accent-dark: #a8c5d8;
|
||||
--color-accent-blue: #e8f0f5;
|
||||
--color-gold: #c9a962;
|
||||
--color-gold-light: #d4b978;
|
||||
|
||||
/* UI Colors */
|
||||
--color-border: #e5e5e5;
|
||||
--color-border-dark: #d1d1d1;
|
||||
--color-cta: #000000;
|
||||
--color-cta-hover: #333333;
|
||||
--color-overlay: rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* Spacing */
|
||||
--space-xs: 4px;
|
||||
--space-sm: 8px;
|
||||
--space-md: 16px;
|
||||
--space-lg: 24px;
|
||||
--space-xl: 32px;
|
||||
--space-2xl: 48px;
|
||||
--space-3xl: 64px;
|
||||
--space-4xl: 96px;
|
||||
--space-5xl: 128px;
|
||||
|
||||
/* Typography */
|
||||
--font-display: 'DM Sans', sans-serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-base: 250ms ease;
|
||||
--transition-slow: 350ms ease;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-background-ice: var(--background-ice);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-foreground-muted: var(--foreground-muted);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-dark: var(--accent-dark);
|
||||
--color-white: var(--white);
|
||||
--color-border: var(--border);
|
||||
--font-display: var(--font-cedrat);
|
||||
--font-body: var(--font-dm-sans);
|
||||
/* Colors - reference :root variables */
|
||||
--color-white: var(--color-white);
|
||||
--color-background: var(--color-background);
|
||||
--color-background-alt: var(--color-background-alt);
|
||||
--color-foreground: var(--color-foreground);
|
||||
--color-foreground-muted: var(--color-foreground-muted);
|
||||
--color-foreground-subtle: var(--color-foreground-subtle);
|
||||
--color-accent: var(--color-accent);
|
||||
--color-accent-dark: var(--color-accent-dark);
|
||||
--color-accent-blue: var(--color-accent-blue);
|
||||
--color-gold: var(--color-gold);
|
||||
--color-gold-light: var(--color-gold-light);
|
||||
--color-border: var(--color-border);
|
||||
--color-border-dark: var(--color-border-dark);
|
||||
--color-cta: var(--color-cta);
|
||||
--color-cta-hover: var(--color-cta-hover);
|
||||
--color-overlay: var(--color-overlay);
|
||||
|
||||
/* Typography */
|
||||
--font-display: var(--font-display);
|
||||
--font-body: var(--font-body);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cedrat Display';
|
||||
src: url('https://fonts.gstatic.com/s/cedratdisplay/v16/0nkoC9_pK3CvS5lZuZ7MAUmK5w.woff2') format('woff2');
|
||||
font-weight: 400 900;
|
||||
font-display: swap;
|
||||
}
|
||||
/* ============================================
|
||||
FONT IMPORTS
|
||||
============================================ */
|
||||
|
||||
@font-face {
|
||||
font-family: 'DM Sans';
|
||||
@@ -38,20 +90,284 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hjp-Ek-_EeA.woff2') format('woff2');
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BASE STYLES
|
||||
============================================ */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
background: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Cedrat Display', serif;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: clamp(1.25rem, 3vw, 1.75rem);
|
||||
}
|
||||
|
||||
.text-display {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.text-body {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
.text-uppercase {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.text-caption {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--color-foreground-muted);
|
||||
}
|
||||
|
||||
.text-subtle {
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITY CLASSES
|
||||
============================================ */
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
padding-left: 48px;
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.container-narrow {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.container-wide {
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
/* Section spacing */
|
||||
.section {
|
||||
padding-top: var(--space-4xl);
|
||||
padding-bottom: var(--space-4xl);
|
||||
}
|
||||
|
||||
.section-sm {
|
||||
padding-top: var(--space-2xl);
|
||||
padding-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
/* Flex utilities */
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
INTERACTIVE ELEMENTS
|
||||
============================================ */
|
||||
|
||||
/* Button Base */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 14px 32px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-cta);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--color-cta-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--color-foreground);
|
||||
border: 1px solid var(--color-border-dark);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--color-foreground);
|
||||
color: var(--color-white);
|
||||
border-color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* Link underline animation */
|
||||
.link-underline {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-underline::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
transition: width var(--transition-base);
|
||||
}
|
||||
|
||||
.link-underline:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FORM ELEMENTS
|
||||
============================================ */
|
||||
|
||||
input, textarea, select {
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-foreground);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
SCROLLBAR
|
||||
============================================ */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-background-alt);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border-dark);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-foreground-muted);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ANIMATIONS
|
||||
============================================ */
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn var(--transition-slow) forwards;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slideUp var(--transition-slow) forwards;
|
||||
}
|
||||
|
||||
.animate-slide-in-right {
|
||||
animation: slideInRight var(--transition-slow) forwards;
|
||||
}
|
||||
|
||||
/* Marquee Animations */
|
||||
@@ -64,51 +380,48 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee-slow {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-marquee {
|
||||
animation: marquee 25s linear infinite;
|
||||
}
|
||||
|
||||
.animate-marquee-slow {
|
||||
animation: marquee-slow 35s linear infinite;
|
||||
animation: marquee 35s linear infinite;
|
||||
}
|
||||
|
||||
.animate-marquee-fast {
|
||||
animation: marquee 15s linear infinite;
|
||||
/* ============================================
|
||||
ACCESSIBILITY
|
||||
============================================ */
|
||||
|
||||
/* Focus visible styles */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-foreground);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.font-serif {
|
||||
font-family: 'Cedrat Display', serif;
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth scroll */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
/* Screen reader only */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
199
src/app/page.tsx
199
src/app/page.tsx
@@ -1,12 +1,7 @@
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
import Header from "@/components/layout/Header";
|
||||
import Footer from "@/components/layout/Footer";
|
||||
import AnnouncementBar from "@/components/home/AnnouncementBar";
|
||||
import NewHero from "@/components/home/NewHero";
|
||||
import StatsSection from "@/components/home/StatsSection";
|
||||
import FeaturesSection from "@/components/home/FeaturesSection";
|
||||
import TestimonialsSection from "@/components/home/TestimonialsSection";
|
||||
import NewsletterSection from "@/components/home/NewsletterSection";
|
||||
import HeroVideo from "@/components/home/HeroVideo";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
export const metadata = {
|
||||
@@ -24,51 +19,169 @@ export default async function Homepage() {
|
||||
console.log('Failed to fetch products during build');
|
||||
}
|
||||
|
||||
const featuredProduct = products[0];
|
||||
const publishedProducts = products.slice(0, 4);
|
||||
const featuredProducts = products?.slice(0, 4) || [];
|
||||
const hasProducts = featuredProducts.length > 0;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-white">
|
||||
<AnnouncementBar />
|
||||
<div className="pt-10">
|
||||
<Header />
|
||||
</div>
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Hero Section with Video Background */}
|
||||
<HeroVideo />
|
||||
|
||||
{/* New Hero Section */}
|
||||
<NewHero featuredProduct={featuredProduct} />
|
||||
{/* Main Content */}
|
||||
<div id="main-content" className="scroll-mt-[72px] lg:scroll-mt-[72px]">
|
||||
{/* Products Grid Section */}
|
||||
{hasProducts && (
|
||||
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
|
||||
Our Collection
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-4">
|
||||
Premium Natural Oils
|
||||
</h2>
|
||||
<p className="text-[#666666] max-w-xl mx-auto">
|
||||
Cold-pressed, pure, and natural oils for your daily beauty routine
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats & Philosophy Section */}
|
||||
<StatsSection />
|
||||
{/* Products Grid */}
|
||||
<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>
|
||||
|
||||
{/* Features Section */}
|
||||
<FeaturesSection />
|
||||
{/* View All Link */}
|
||||
<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"
|
||||
>
|
||||
View All Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection />
|
||||
|
||||
{/* Newsletter Section */}
|
||||
<NewsletterSection />
|
||||
|
||||
{/* Products Grid Section */}
|
||||
{publishedProducts.length > 0 && (
|
||||
<section className="py-20 px-6 bg-white">
|
||||
<div className="max-w-[1400px] mx-auto">
|
||||
<h2 className="font-serif italic text-4xl text-center mb-4">
|
||||
Our Collection
|
||||
</h2>
|
||||
<p className="text-center text-[#4A4A4A] mb-12 max-w-2xl mx-auto">
|
||||
Cold-pressed, pure, and natural oils for your daily beauty routine
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{publishedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} locale="SR" />
|
||||
))}
|
||||
{/* Brand Story Section */}
|
||||
<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">
|
||||
Our Story
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium mb-6">
|
||||
Handmade with Love
|
||||
</h2>
|
||||
<p className="text-[#666666] mb-6 leading-relaxed">
|
||||
Every bottle of ManoonOils is crafted with care using traditional
|
||||
methods passed down through generations. We source only the finest
|
||||
organic ingredients to bring you oils that nourish both hair and skin.
|
||||
</p>
|
||||
<p className="text-[#666666] mb-8 leading-relaxed">
|
||||
Our commitment to purity means no additives, no preservatives -
|
||||
just nature's goodness in its most potent form.
|
||||
</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"
|
||||
>
|
||||
Learn More
|
||||
</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="Natural oils production"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Benefits Section */}
|
||||
<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">
|
||||
Why Choose Us
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl font-medium">
|
||||
The Manoon Difference
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-12">
|
||||
{[
|
||||
{
|
||||
title: "100% Natural",
|
||||
description: "Pure, cold-pressed oils with no additives or preservatives. Just nature's goodness.",
|
||||
},
|
||||
{
|
||||
title: "Handcrafted",
|
||||
description: "Each batch is carefully prepared by hand to ensure the highest quality.",
|
||||
},
|
||||
{
|
||||
title: "Sustainable",
|
||||
description: "Ethically sourced ingredients and eco-friendly packaging for a better planet.",
|
||||
},
|
||||
].map((benefit, index) => (
|
||||
<div key={index} className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-[#e8f0f5] flex items-center justify-center">
|
||||
<span className="text-2xl font-medium text-[#1a1a1a]">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-3">{benefit.title}</h3>
|
||||
<p className="text-[#666666]">{benefit.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Newsletter 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">
|
||||
Stay Connected
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6">
|
||||
Join Our Community
|
||||
</h2>
|
||||
<p className="text-white/70 mb-10 mx-auto text-lg">
|
||||
Subscribe to receive exclusive offers, beauty tips, and be the first to know about new products.
|
||||
</p>
|
||||
{/* Newsletter Form - Centered */}
|
||||
<form className="flex flex-col sm:flex-row items-center justify-center w-full sm:w-auto gap-0">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
className="w-full sm:w-64 md:w-80 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"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full sm:w-auto 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"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Image from "next/image";
|
||||
import { getProductBySlug, getProducts, getProductPrice, getProductImage, getLocalizedProduct, formatPrice } from "@/lib/saleor";
|
||||
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 {
|
||||
@@ -52,6 +52,12 @@ export async function generateMetadata({ params }: ProductPageProps) {
|
||||
"en": product.translation?.slug ? `/products/${product.translation.slug}` : `/products/${product.slug}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: localized.name,
|
||||
description: localized.seoDescription || localized.description?.slice(0, 160),
|
||||
images: product.media?.[0]?.url ? [product.media[0].url] : [],
|
||||
type: 'website',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,111 +67,57 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<>
|
||||
<Header />
|
||||
<div className="pt-24 text-center">
|
||||
<h1 className="text-2xl">
|
||||
{locale === "en" ? "Product not found" : "Proizvod nije pronađen"}
|
||||
</h1>
|
||||
</div>
|
||||
<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">
|
||||
{locale === "en" ? "Product not found" : "Proizvod nije pronađen"}
|
||||
</h1>
|
||||
<p className="text-[#666666] mb-8">
|
||||
{locale === "en"
|
||||
? "The product you're looking for doesn't exist or has been removed."
|
||||
: "Proizvod koji tražite ne postoji ili je uklonjen."}
|
||||
</p>
|
||||
<a
|
||||
href="/products"
|
||||
className="inline-block px-8 py-3 bg-black text-white text-sm uppercase tracking-[0.1em] hover:bg-[#333333] transition-colors"
|
||||
>
|
||||
{locale === "en" ? "Browse Products" : "Pregledaj proizvode"}
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const localized = getLocalizedProduct(product, locale.toUpperCase());
|
||||
const image = getProductImage(product);
|
||||
const price = getProductPrice(product);
|
||||
const variant = product.variants?.[0];
|
||||
const isAvailable = variant?.quantityAvailable > 0;
|
||||
|
||||
// Determine language based on which slug matched
|
||||
const isEnglishSlug = slug === product.translation?.slug;
|
||||
const currentLocale = isEnglishSlug ? "en" : "sr";
|
||||
const currentLocale = isEnglishSlug ? "EN" : "SR";
|
||||
|
||||
// URLs for language switcher
|
||||
const serbianUrl = `/products/${product.slug}`;
|
||||
const englishUrl = product.translation?.slug
|
||||
? `/products/${product.translation.slug}`
|
||||
: serbianUrl;
|
||||
// Get related products (same category or just other products)
|
||||
let relatedProducts: Product[] = [];
|
||||
try {
|
||||
const allProducts = await getProducts(currentLocale, 8);
|
||||
relatedProducts = allProducts
|
||||
.filter((p: Product) => p.id !== product.id)
|
||||
.slice(0, 4);
|
||||
} catch (e) {
|
||||
// Ignore error, just won't show related products
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="pt-24 pb-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
{/* Product Image */}
|
||||
<div className="relative aspect-[4/5] bg-background-ice overflow-hidden">
|
||||
<Image
|
||||
src={image}
|
||||
alt={localized.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Product Info */}
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-4xl font-serif mb-4">{localized.name}</h1>
|
||||
|
||||
{price && (
|
||||
<div className="text-2xl mb-6">{price}</div>
|
||||
)}
|
||||
|
||||
{localized.description && (
|
||||
<div
|
||||
className="prose max-w-none mb-8"
|
||||
dangerouslySetInnerHTML={{ __html: localized.description }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Add to Cart Button */}
|
||||
<button
|
||||
className="inline-block bg-foreground text-white px-8 py-4 text-lg font-medium text-center hover:bg-opacity-90 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={!isAvailable}
|
||||
>
|
||||
{isAvailable
|
||||
? (currentLocale === "en" ? "Add to Cart" : "Dodaj u korpu")
|
||||
: (currentLocale === "en" ? "Out of Stock" : "Nema na stanju")
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* SKU */}
|
||||
{variant?.sku && (
|
||||
<p className="mt-4 text-sm text-foreground-muted">
|
||||
SKU: {variant.sku}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Language Switcher */}
|
||||
<div className="mt-8 pt-8 border-t">
|
||||
<p className="text-sm text-foreground-muted mb-2">
|
||||
{currentLocale === "en" ? "Language:" : "Jezik:"}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a
|
||||
href={serbianUrl}
|
||||
className={`text-sm font-medium ${currentLocale === "sr" ? "text-foreground" : "text-foreground-muted hover:text-foreground"}`}
|
||||
>
|
||||
🇷🇸 Srpski
|
||||
</a>
|
||||
<a
|
||||
href={englishUrl}
|
||||
className={`text-sm font-medium ${currentLocale === "en" ? "text-foreground" : "text-foreground-muted hover:text-foreground"}`}
|
||||
>
|
||||
🇬🇧 English
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main className="min-h-screen bg-white">
|
||||
<ProductDetail
|
||||
product={product}
|
||||
relatedProducts={relatedProducts}
|
||||
locale={currentLocale}
|
||||
/>
|
||||
</main>
|
||||
<Footer />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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: "Products - ManoonOils",
|
||||
@@ -15,37 +16,91 @@ interface ProductsPageProps {
|
||||
export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
const { locale = "sr" } = await params;
|
||||
const products = await getProducts(locale.toUpperCase());
|
||||
const localeUpper = locale.toUpperCase();
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pt-16 md:pt-20">
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<section className="py-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-center mb-16">
|
||||
{locale === "en" ? "All Products" : "Svi Proizvodi"}
|
||||
</h1>
|
||||
|
||||
{products.length === 0 ? (
|
||||
<p className="text-center text-foreground-muted">
|
||||
{locale === "en" ? "No products available" : "Nema dostupnih proizvoda"}
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
index={index}
|
||||
locale={locale.toUpperCase()}
|
||||
/>
|
||||
))}
|
||||
<main className="min-h-screen bg-white">
|
||||
{/* Page Header */}
|
||||
<div className="pt-[140px] lg:pt-[160px]">
|
||||
<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">
|
||||
{localeUpper === "EN" ? "Our Collection" : "Naša kolekcija"}
|
||||
</span>
|
||||
<h1 className="text-3xl md:text-4xl font-medium">
|
||||
{localeUpper === "EN" ? "All Products" : "Svi Proizvodi"}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Sort Dropdown */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-[#666666]">
|
||||
{products.length} {localeUpper === "EN" ? "products" : "proizvoda"}
|
||||
</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">
|
||||
{localeUpper === "EN" ? "Featured" : "Istaknuto"}
|
||||
</option>
|
||||
<option value="newest">
|
||||
{localeUpper === "EN" ? "Newest" : "Najnovije"}
|
||||
</option>
|
||||
<option value="price-low">
|
||||
{localeUpper === "EN" ? "Price: Low to High" : "Cena: Rastuće"}
|
||||
</option>
|
||||
<option value="price-high">
|
||||
{localeUpper === "EN" ? "Price: High to Low" : "Cena: Opadajuće"}
|
||||
</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>
|
||||
|
||||
{/* Products Grid */}
|
||||
<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">
|
||||
{localeUpper === "EN" ? "No products available" : "Nema dostupnih proizvoda"}
|
||||
</p>
|
||||
<p className="text-sm text-[#999999]">
|
||||
{localeUpper === "EN"
|
||||
? "Please check back later for new arrivals."
|
||||
: "Molimo proverite ponovo kasnije za nove proizvode."}
|
||||
</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={localeUpper}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
<div className="pt-16">
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { MetadataRoute } from "next";
|
||||
import { getProducts } from "@/lib/woocommerce";
|
||||
import { getProducts } from "@/lib/saleor";
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
|
||||
|
||||
let products: any[] = [];
|
||||
try {
|
||||
products = await getProducts();
|
||||
products = await getProducts("SR", 100);
|
||||
} catch (e) {
|
||||
console.log('Failed to fetch products for sitemap during build');
|
||||
}
|
||||
|
||||
const productUrls = products
|
||||
.filter((p) => p.status === "publish")
|
||||
.map((product) => ({
|
||||
url: `${baseUrl}/products/${product.slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: 0.8,
|
||||
}));
|
||||
const productUrls = products.map((product) => ({
|
||||
url: `${baseUrl}/products/${product.slug}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: 0.8,
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user