diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx index 4d30a18..3023300 100644 --- a/src/app/checkout/page.tsx +++ b/src/app/checkout/page.tsx @@ -442,6 +442,7 @@ export default function CheckoutPage() { src={line.variant.product.media[0].url} alt={line.variant.product.name} fill + sizes="64px" className="object-cover" /> )} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6f7f202..3bb886c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,5 @@ import "./globals.css"; -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import ErrorBoundary from "@/components/providers/ErrorBoundary"; export const metadata: Metadata = { @@ -7,9 +7,8 @@ export const metadata: Metadata = { default: "ManoonOils - Premium Natural Oils for Hair & Skin", template: "%s | ManoonOils", }, - description: "Discover our premium collection of natural oils for hair and skin care. Handmade with love.", + description: "Discover our premium collection of natural oils for hair and skin care.", robots: "index, follow", - viewport: "width=device-width, initial-scale=1, maximum-scale=5", openGraph: { title: "ManoonOils - Premium Natural Oils for Hair & Skin", description: "Discover our premium collection of natural oils for hair and skin care.", @@ -18,6 +17,12 @@ export const metadata: Metadata = { }, }; +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + maximumScale: 5, +}; + // Suppress extension-caused hydration warnings const suppressHydrationWarning = true; diff --git a/src/components/cart/CartDrawer.tsx b/src/components/cart/CartDrawer.tsx index 812106b..814e42b 100644 --- a/src/components/cart/CartDrawer.tsx +++ b/src/components/cart/CartDrawer.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import Image from "next/image"; import Link from "next/link"; @@ -27,11 +27,15 @@ export default function CartDrawer() { const lines = getLines(); const total = getTotal(); const lineCount = getLineCount(); + const [initialized, setInitialized] = useState(false); - // Initialize checkout on mount + // Initialize checkout on mount (only once) useEffect(() => { - initCheckout(); - }, [initCheckout]); + if (!initialized) { + initCheckout(); + setInitialized(true); + } + }, [initialized]); // Lock body scroll when cart is open useEffect(() => { diff --git a/src/components/product/ProductDetail.tsx b/src/components/product/ProductDetail.tsx index aa654d6..fcfbcac 100644 --- a/src/components/product/ProductDetail.tsx +++ b/src/components/product/ProductDetail.tsx @@ -1,13 +1,13 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; 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 type { Product } from "@/types/saleor"; import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore"; -import { getProductPrice, getLocalizedProduct } from "@/lib/saleor"; +import { getProductPrice, getProductPriceAmount, getLocalizedProduct, formatPrice } from "@/lib/saleor"; import ProductCard from "@/components/product/ProductCard"; import ProductBenefits from "@/components/product/ProductBenefits"; import ProductReviews from "@/components/product/ProductReviews"; @@ -91,8 +91,23 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR" 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); + }, 3000); + return () => clearInterval(interval); + }, []); + + 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!" }, + ]; + const localized = getLocalizedProduct(product, locale); const variant = product.variants?.[0]; @@ -115,6 +130,8 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR" const isAvailable = variant?.quantityAvailable > 0; 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 @@ -235,24 +252,65 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR" transition={{ duration: 0.6, delay: 0.2 }} className="lg:pl-8" > + {/* Urgency Sales Banner */} + + {urgencyMessages[urgencyIndex].icon} + {urgencyMessages[urgencyIndex].text} + + {/* Product Name */}

{localized.name}

{/* Short Description */} -

+

{shortDescription}

- {/* Price & Rating */} -
- - {price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")} + {/* Stock Warning - Static */} +
+ + + - + Stocks are running out!
+ {/* Discount Price Display */} + {originalPrice && priceAmount > 0 && ( +
+
+ + {originalPrice} + + + -30% + +
+ + {price} + +
+ )} + + {/* Price & Rating */} + {!originalPrice && ( +
+ + {price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")} + + +
+ )} + {/* Divider */}
diff --git a/src/lib/saleor/index.ts b/src/lib/saleor/index.ts index 2304bb9..a3cfca2 100644 --- a/src/lib/saleor/index.ts +++ b/src/lib/saleor/index.ts @@ -28,6 +28,7 @@ export { getProducts, getProductBySlug, getProductPrice, + getProductPriceAmount, getProductImage, isProductAvailable, formatPrice, diff --git a/src/lib/saleor/mutations/Checkout.ts b/src/lib/saleor/mutations/Checkout.ts index f836cde..4095298 100644 --- a/src/lib/saleor/mutations/Checkout.ts +++ b/src/lib/saleor/mutations/Checkout.ts @@ -50,8 +50,8 @@ export const CHECKOUT_LINES_UPDATE = gql` `; export const CHECKOUT_LINES_DELETE = gql` - mutation CheckoutLinesDelete($checkoutId: ID!, $lineIds: [ID!]!) { - checkoutLinesDelete(checkoutId: $checkoutId, lineIds: $lineIds) { + mutation CheckoutLinesDelete($id: ID!, $linesIds: [ID!]!) { + checkoutLinesDelete(id: $id, linesIds: $linesIds) { checkout { ...CheckoutFragment } diff --git a/src/lib/saleor/products.ts b/src/lib/saleor/products.ts index 27b2d3b..ded4d41 100644 --- a/src/lib/saleor/products.ts +++ b/src/lib/saleor/products.ts @@ -68,6 +68,11 @@ export function getProductPrice(product: Product): string { ); } +export function getProductPriceAmount(product: Product): number { + const variant = product.variants?.[0]; + return variant?.pricing?.price?.gross?.amount || 0; +} + export function getProductImage(product: Product): string { if (product.media && product.media.length > 0) { return product.media[0].url; @@ -88,7 +93,8 @@ export function formatPrice(amount: number, currency: string = "RSD"): string { return new Intl.NumberFormat("sr-RS", { style: "currency", currency: currency, - minimumFractionDigits: 0, + minimumFractionDigits: 2, + maximumFractionDigits: 2, }).format(amount); } diff --git a/src/stores/saleorCheckoutStore.ts b/src/stores/saleorCheckoutStore.ts index 484ac91..fd6e6e6 100644 --- a/src/stores/saleorCheckoutStore.ts +++ b/src/stores/saleorCheckoutStore.ts @@ -221,8 +221,8 @@ export const useSaleorCheckoutStore = create()( const { data } = await saleorClient.mutate({ mutation: CHECKOUT_LINES_DELETE, variables: { - checkoutId: checkout.id, - lineIds: [lineId], + id: checkout.id, + linesIds: [lineId], }, });