commit fdec9e4b0ea89474b309314560673950e97b6c6f Author: Jelaletdin12 Date: Mon Nov 10 10:07:48 2025 +0500 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/api.zip b/api.zip new file mode 100644 index 0000000..cdd74e3 Binary files /dev/null and b/api.zip differ diff --git a/app/[locale]/cart/page.tsx b/app/[locale]/cart/page.tsx new file mode 100644 index 0000000..3e08f0d --- /dev/null +++ b/app/[locale]/cart/page.tsx @@ -0,0 +1,171 @@ +"use client" +import { useState, useEffect } from "react" +import { Card } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" +import CartItemCard from "./ui/CartItemCard" +import OrderSummary from "./ui/OrderSummary" +import { useCart, useCreateOrder, useRegions, useAddresses, usePaymentTypes } from "@/lib/hooks" +import { useTranslations } from "next-intl" +import { useRouter } from "next/navigation" +import type { DeliveryType, PaymentTypeOption } from "./ui/types" + +export default function CartPage() { + const [isClient, setIsClient] = useState(false) + const [paymentType, setPaymentType] = useState(null) + const [deliveryType, setDeliveryType] = useState("SELECTED_DELIVERY") + const [selectedRegion, setSelectedRegion] = useState(null) + const [selectedAddress, setSelectedAddress] = useState("") + const [note, setNote] = useState("") + const router = useRouter() + + const t = useTranslations() + + const { data: cart, isLoading, isError } = useCart() + const { data: regions = [] } = useRegions() + const { data: addresses = [] } = useAddresses() + const { data: paymentTypes = [] } = usePaymentTypes() + const { mutate: createOrder, isPending: isCreatingOrder } = useCreateOrder() + + useEffect(() => { + setIsClient(true) + }, []) + + const handleDeliveryTypeChange = (type: DeliveryType) => { + setDeliveryType(type) + setSelectedAddress("") + } + + const handleCompleteOrder = () => { + if (!selectedRegion || !selectedAddress || !paymentType) { + console.warn("[v0] Missing required fields for order") + return + } + + const selectedRegionObj = regions.find((r) => r.code === selectedRegion) + + createOrder( + { + customer_address: selectedAddress, + shipping_method: deliveryType === "PICK_UP" ? "pickup" : "standart", + payment_type_id: paymentType.id, + region: selectedRegion, + note: note || undefined, + }, + { + onSuccess: () => { + // Navigate to orders page after successful order creation + router.push(`/orders`) + }, + }, + ) + } + + if (!isClient) return null + + if (isLoading) { + return ( +
+

{t("loading")}

+
+ ) + } + + if (isError || !cart?.items || cart.items.length === 0) { + return ( +
+

+ {t("emptyCart") || "Your cart is empty"} +

+
+ ) + } + + const translations = { + cart: t("cart"), + ordersIn: t("order_available_in_shops"), + pricePerUnit: t("unit_price"), + additionalPrice: t("extra_price"), + discount: t("discount"), + totalPrice: t("total_price"), + paymentType: t("payment_type"), + cash: t("cash"), + card: t("card"), + deliveryType: t("delivery_type"), + delivery: t("delivery"), + pickup: t("pickup"), + selectRegion: t("choose_region"), + selectAddress: t("choose_address"), + note: t("note"), + placeOrder: t("order"), + emptyCart: t("cart_empty"), + map: t("address"), + } + + const itemsBySeller = cart.items.reduce( + (acc, item) => { + const sellerId = item.seller.id + if (!acc[sellerId]) { + acc[sellerId] = { seller: item.seller, items: [] } + } + acc[sellerId].items.push(item) + return acc + }, + {} as Record, + ) + + return ( +
+

{translations.cart}

+ +
+ {/* Cart Items Section */} +
+ + {/* Sellers */} + {Object.entries(itemsBySeller).map(([sellerId, { seller, items }]) => ( +
+

{seller.name}

+
+ {items.map((item) => ( + + ))} +
+ {Object.entries(itemsBySeller).length > 1 && } +
+ ))} +
+
+ + {/* Order Summary Sidebar */} + {}} + onCompleteOrder={handleCompleteOrder} + isLoading={isCreatingOrder} + /> +
+
+ ) +} diff --git a/app/[locale]/cart/ui/CartItemCard.tsx b/app/[locale]/cart/ui/CartItemCard.tsx new file mode 100644 index 0000000..47da684 --- /dev/null +++ b/app/[locale]/cart/ui/CartItemCard.tsx @@ -0,0 +1,105 @@ +"use client" +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 type { CartItem, CartTranslations } from "./types" + +interface CartItemCardProps { + item: CartItem + translations: CartTranslations +} + +export default function CartItemCard({ item, translations: t }: CartItemCardProps) { + const { mutate: updateQuantity, isPending: isUpdating } = 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 }) + } + } + + const handleDelete = () => { + removeItem(item.id) + } + + return ( + +
+ {/* Product Image & Info */} +
+
+ {item.product.name} +
+
+

{item.product.name}

+

{item.seller.name}

+ +
+
+ + {/* Price & Quantity */} +
+
+

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

+

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

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

+ {t.discount} {item.discount_formatted} +

+ )} +
+ {t.totalPrice} + + {item.total_formatted || `${item.total} TMT`} + +
+
+ + {/* Quantity Controls */} +
+ +
{item.quantity}
+ +
+
+
+
+ ) +} diff --git a/app/[locale]/cart/ui/DeliveryTypeSelector.tsx b/app/[locale]/cart/ui/DeliveryTypeSelector.tsx new file mode 100644 index 0000000..77cbf5b --- /dev/null +++ b/app/[locale]/cart/ui/DeliveryTypeSelector.tsx @@ -0,0 +1,53 @@ +import React from "react"; +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; +} + +export default function DeliveryTypeSelector({ + selectedType, + onSelect, + translations: t, +}: DeliveryTypeSelectorProps) { + const deliveryOptions: { + type: DeliveryType; + label: string; + icon: typeof Truck; + }[] = [ + { type: "SELECTED_DELIVERY", label: t.delivery, icon: Truck }, + { type: "PICK_UP", label: t.pickup, icon: Warehouse }, + ]; + + return ( +
+

{t.deliveryType}

+
+ {deliveryOptions.map(({ type, label, icon: Icon }) => ( + onSelect(type)} + > +
+ + {label} +
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/app/[locale]/cart/ui/OrderSummary.tsx b/app/[locale]/cart/ui/OrderSummary.tsx new file mode 100644 index 0000000..7167d50 --- /dev/null +++ b/app/[locale]/cart/ui/OrderSummary.tsx @@ -0,0 +1,177 @@ +"use client" +import { MapPin } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Card } from "@/components/ui/card" +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 DeliveryTypeSelector from "./DeliveryTypeSelector" +import type { Order, Region, Address, DeliveryType, CartTranslations, PaymentTypeOption } from "./types" + +interface OrderSummaryProps { + order: Order + translations: CartTranslations + paymentType: PaymentTypeOption | null + deliveryType: DeliveryType + selectedRegion: string | null + selectedAddress: string + note: string + regions: Region[] + addresses: Address[] + paymentTypes: PaymentTypeOption[] + onPaymentTypeChange: (type: PaymentTypeOption) => void + onDeliveryTypeChange: (type: DeliveryType) => void + onRegionChange: (regionCode: string) => void + onAddressChange: (address: string) => void + onNoteChange: (note: string) => void + onMapOpen: () => void + onCompleteOrder: () => void + isLoading: boolean +} + +export default function OrderSummary({ + order, + translations: t, + paymentType, + deliveryType, + selectedRegion, + selectedAddress, + note, + regions, + addresses, + paymentTypes, + onPaymentTypeChange, + onDeliveryTypeChange, + onRegionChange, + onAddressChange, + onNoteChange, + onMapOpen, + onCompleteOrder, + isLoading, +}: OrderSummaryProps) { + const filteredAddresses = selectedRegion + ? addresses.filter((addr) => { + const region = regions.find((r) => r.code === selectedRegion) + return region && addr.region_id === region.id + }) + : [] + + const isFormValid = selectedRegion && selectedAddress && paymentType + + return ( + + {/* Payment Type */} +
+

{t.paymentType}

+
+ {paymentTypes.map((type) => ( + onPaymentTypeChange(type)} + > +
+ {type.name} +
+
+ ))} +
+
+ + {/* Delivery Type */} + + + {/* Region Selection */} +
+ + + {regions.map((region) => ( +
+ + +
+ ))} +
+
+ + {/* Address Selection */} + {filteredAddresses.length > 0 && ( +
+ +
+ + +
+
+ )} + + {/* Note */} +
+ +