diff --git a/app/[locale]/cart/page.tsx b/app/[locale]/cart/page.tsx index 3e08f0d..009dfe3 100644 --- a/app/[locale]/cart/page.tsx +++ b/app/[locale]/cart/page.tsx @@ -20,12 +20,16 @@ export default function CartPage() { const t = useTranslations() - const { data: cart, isLoading, isError } = useCart() + // useCart dönen data yapısı: { message: "success", data: [...] } + const { data: cartResponse, isLoading, isError } = useCart() const { data: regions = [] } = useRegions() const { data: addresses = [] } = useAddresses() const { data: paymentTypes = [] } = usePaymentTypes() const { mutate: createOrder, isPending: isCreatingOrder } = useCreateOrder() + // Cart items'ı doğru şekilde al + const cartItems = cartResponse?.data || [] + useEffect(() => { setIsClient(true) }, []) @@ -37,12 +41,10 @@ export default function CartPage() { const handleCompleteOrder = () => { if (!selectedRegion || !selectedAddress || !paymentType) { - console.warn("[v0] Missing required fields for order") + console.warn("Missing required fields for order") return } - const selectedRegionObj = regions.find((r) => r.code === selectedRegion) - createOrder( { customer_address: selectedAddress, @@ -53,7 +55,6 @@ export default function CartPage() { }, { onSuccess: () => { - // Navigate to orders page after successful order creation router.push(`/orders`) }, }, @@ -70,7 +71,7 @@ export default function CartPage() { ) } - if (isError || !cart?.items || cart.items.length === 0) { + if (isError || cartItems.length === 0) { return (

@@ -101,18 +102,30 @@ export default function CartPage() { map: t("address"), } - const itemsBySeller = cart.items.reduce( + // Group items by seller (from channel) + const itemsBySeller = cartItems.reduce( (acc, item) => { - const sellerId = item.seller.id + const sellerId = item.product.channel?.[0]?.id || 0 + const sellerName = item.product.channel?.[0]?.name || "Unknown Seller" + if (!acc[sellerId]) { - acc[sellerId] = { seller: item.seller, items: [] } + acc[sellerId] = { + seller: { id: sellerId, name: sellerName }, + items: [] + } } acc[sellerId].items.push(item) return acc }, - {} as Record, + {} as Record ) + // Calculate total + const totalAmount = cartItems.reduce((sum, item) => { + const price = parseFloat(item.product.price_amount || "0") + return sum + (price * item.product_quantity) + }, 0) + return (

{translations.cart}

@@ -126,9 +139,34 @@ export default function CartPage() {

{seller.name}

- {items.map((item) => ( - - ))} + {items.map((item) => { + const price = parseFloat(item.product.price_amount || "0") + const quantity = item.product_quantity + const total = price * quantity + + return ( + m.images_800x800 || m.thumbnail) || [] + } + }} + translations={translations} + /> + ) + })}
{Object.entries(itemsBySeller).length > 1 && }
@@ -141,10 +179,27 @@ export default function CartPage() { order={{ id: 1, seller: { id: 1, name: "Store" }, - items: cart.items, + items: cartItems.map(item => ({ + ...item, + quantity: item.product_quantity, + price: parseFloat(item.product.price_amount || "0"), + total: parseFloat(item.product.price_amount || "0") * item.product_quantity, + seller: { + id: item.product.channel?.[0]?.id || 0, + name: item.product.channel?.[0]?.name || "Unknown" + } + })), billing: { - body: [{ title: t("goods"), value: `${cart.total_formatted || `${cart.total} TMT`}` }], - footer: { title: t("total"), value: `${cart.total_formatted || `${cart.total} TMT`}` }, + body: [ + { + title: t("goods"), + value: `${totalAmount.toFixed(2)} TMT` + } + ], + footer: { + title: t("total"), + value: `${totalAmount.toFixed(2)} TMT` + }, }, }} translations={translations} @@ -168,4 +223,4 @@ export default function CartPage() {

) -} +} \ No newline at end of file diff --git a/app/[locale]/cart/ui/CartItemCard.tsx b/app/[locale]/cart/ui/CartItemCard.tsx index 47da684..b4d31bc 100644 --- a/app/[locale]/cart/ui/CartItemCard.tsx +++ b/app/[locale]/cart/ui/CartItemCard.tsx @@ -1,29 +1,147 @@ "use client" +import { useState, useEffect, useRef } from "react" import Image from "next/image" import { Minus, Plus, Trash2 } from "lucide-react" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" -import { useUpdateCartItemQuantity, useRemoveFromCart } from "@/lib/hooks" +import { + useUpdateCartItemQuantity, + useRemoveFromCart +} from "@/lib/hooks" import type { CartItem, CartTranslations } from "./types" interface CartItemCardProps { item: CartItem translations: CartTranslations + onUpdate?: () => void } -export default function CartItemCard({ item, translations: t }: CartItemCardProps) { - const { mutate: updateQuantity, isPending: isUpdating } = useUpdateCartItemQuantity() +export default function CartItemCard({ + item, + translations: t, + onUpdate +}: CartItemCardProps) { + const [localQuantity, setLocalQuantity] = useState(item.quantity) + const [pendingQuantity, setPendingQuantity] = useState(item.quantity) + const [isLoading, setIsLoading] = useState(false) + const updateTimeoutRef = useRef() + + const { mutate: updateQuantity } = useUpdateCartItemQuantity() const { mutate: removeItem, isPending: isRemoving } = useRemoveFromCart() - const handleQuantityChange = (delta: number) => { - const newQuantity = item.quantity + delta - if (newQuantity >= 1) { - updateQuantity({ itemId: item.id, quantity: newQuantity }) + // Sync local quantity with server quantity + useEffect(() => { + setLocalQuantity(item.quantity) + setPendingQuantity(item.quantity) + }, [item.quantity]) + + // Debounced update effect + useEffect(() => { + if (pendingQuantity === item.quantity) { + return } + + // Clear previous timeout + if (updateTimeoutRef.current) { + clearTimeout(updateTimeoutRef.current) + } + + // Set new timeout for update + updateTimeoutRef.current = setTimeout(() => { + setIsLoading(true) + + if (pendingQuantity <= 0) { + removeItem(item.id, { + onSuccess: () => { + onUpdate?.() + }, + onError: (error) => { + console.error("Failed to remove item:", error) + // Revert on error + setLocalQuantity(item.quantity) + setPendingQuantity(item.quantity) + }, + onSettled: () => { + setIsLoading(false) + }, + }) + } else { + updateQuantity( + { itemId: item.id, quantity: pendingQuantity }, + { + onSuccess: () => { + onUpdate?.() + }, + onError: (error) => { + console.error("Failed to update quantity:", error) + // Revert on error + setLocalQuantity(item.quantity) + setPendingQuantity(item.quantity) + }, + onSettled: () => { + setIsLoading(false) + }, + } + ) + } + }, 300) + + return () => { + if (updateTimeoutRef.current) { + clearTimeout(updateTimeoutRef.current) + } + } + }, [pendingQuantity, item.quantity, item.id, updateQuantity, removeItem, onUpdate]) + + const handleQuantityIncrease = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + + if (isLoading) return + + const newQuantity = localQuantity + 1 + setLocalQuantity(newQuantity) + setPendingQuantity(newQuantity) + } + + const handleQuantityDecrease = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + + if (isLoading) return + + const newQuantity = localQuantity - 1 + + if (newQuantity < 1) { + handleDelete() + return + } + + setLocalQuantity(newQuantity) + setPendingQuantity(newQuantity) } const handleDelete = () => { - removeItem(item.id) + setIsLoading(true) + removeItem(item.id, { + onSuccess: () => { + onUpdate?.() + }, + onError: (error) => { + console.error("Failed to remove item:", error) + }, + onSettled: () => { + setIsLoading(false) + }, + }) + } + + const getImageSrc = () => { + if (item.product.image) return item.product.image + if (item.product.images && item.product.images.length > 0) { + return item.product.images[0] + } + return "/placeholder.svg" } return ( @@ -33,7 +151,7 @@ export default function CartItemCard({ item, translations: t }: CartItemCardProp
{item.product.name} @@ -58,10 +176,14 @@ export default function CartItemCard({ item, translations: t }: CartItemCardProp

- {t.pricePerUnit} {item.price_formatted || `${item.price} TMT`} + {t.pricePerUnit}{" "} + + {item.price_formatted || `${item.price} TMT`} +

- {t.additionalPrice} {item.sub_total_formatted || `${item.total} TMT`} + {t.additionalPrice}{" "} + {item.sub_total_formatted || `${item.total} TMT`}

{item.discount_formatted && item.discount_formatted !== "0 TMT" && (

@@ -81,18 +203,20 @@ export default function CartItemCard({ item, translations: t }: CartItemCardProp -

{item.quantity}
+
+ {localQuantity} +
) -} +} \ No newline at end of file diff --git a/app/[locale]/cart/ui/DeliveryTypeSelector.tsx b/app/[locale]/cart/ui/DeliveryTypeSelector.tsx index 77cbf5b..df452ed 100644 --- a/app/[locale]/cart/ui/DeliveryTypeSelector.tsx +++ b/app/[locale]/cart/ui/DeliveryTypeSelector.tsx @@ -1,12 +1,12 @@ -import React from "react"; -import { Truck, Warehouse } from "lucide-react"; -import { Card } from "@/components/ui/card"; -import { DeliveryType, CartTranslations } from "./types"; +"use client" +import { Truck, Warehouse } from "lucide-react" +import { Card } from "@/components/ui/card" +import { DeliveryType, CartTranslations } from "./types" interface DeliveryTypeSelectorProps { - selectedType: DeliveryType; - onSelect: (type: DeliveryType) => void; - translations: CartTranslations; + selectedType: DeliveryType + onSelect: (type: DeliveryType) => void + translations: CartTranslations } export default function DeliveryTypeSelector({ @@ -15,13 +15,13 @@ export default function DeliveryTypeSelector({ translations: t, }: DeliveryTypeSelectorProps) { const deliveryOptions: { - type: DeliveryType; - label: string; - icon: typeof Truck; + type: DeliveryType + label: string + icon: typeof Truck }[] = [ { type: "SELECTED_DELIVERY", label: t.delivery, icon: Truck }, { type: "PICK_UP", label: t.pickup, icon: Warehouse }, - ]; + ] return (
@@ -30,9 +30,9 @@ export default function DeliveryTypeSelector({ {deliveryOptions.map(({ type, label, icon: Icon }) => ( onSelect(type)} @@ -40,14 +40,18 @@ export default function DeliveryTypeSelector({
- {label} + + {label} +
))}
- ); + ) } \ No newline at end of file diff --git a/app/[locale]/cart/ui/OrderSummary.tsx b/app/[locale]/cart/ui/OrderSummary.tsx index 7167d50..cb083d7 100644 --- a/app/[locale]/cart/ui/OrderSummary.tsx +++ b/app/[locale]/cart/ui/OrderSummary.tsx @@ -6,9 +6,22 @@ import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { Textarea } from "@/components/ui/textarea" import { Separator } from "@/components/ui/separator" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from "@/components/ui/select" import DeliveryTypeSelector from "./DeliveryTypeSelector" -import type { Order, Region, Address, DeliveryType, CartTranslations, PaymentTypeOption } from "./types" +import type { + Order, + Region, + Address, + DeliveryType, + CartTranslations, + PaymentTypeOption +} from "./types" interface OrderSummaryProps { order: Order @@ -51,6 +64,7 @@ export default function OrderSummary({ onCompleteOrder, isLoading, }: OrderSummaryProps) { + // Filter addresses based on selected region const filteredAddresses = selectedRegion ? addresses.filter((addr) => { const region = regions.find((r) => r.code === selectedRegion) @@ -58,11 +72,12 @@ export default function OrderSummary({ }) : [] + // Validate form completion const isFormValid = selectedRegion && selectedAddress && paymentType return ( - {/* Payment Type */} + {/* Payment Type Selection */}

{t.paymentType}

@@ -70,7 +85,9 @@ export default function OrderSummary({ onPaymentTypeChange(type)} > @@ -82,13 +99,23 @@ export default function OrderSummary({
- {/* Delivery Type */} - + {/* Delivery Type Selection */} + {/* Region Selection */}
- - + + {regions.map((region) => (
-
@@ -104,10 +134,12 @@ export default function OrderSummary({
- {/* Address Selection */} - {filteredAddresses.length > 0 && ( + {/* Address Selection (only show when region is selected) */} + {selectedRegion && filteredAddresses.length > 0 && (
- +