first commit

This commit is contained in:
Jelaletdin12
2025-11-10 10:07:48 +05:00
commit fdec9e4b0e
131 changed files with 16660 additions and 0 deletions

View File

@@ -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 (
<Card className="p-4 shadow-none border">
<div className="flex flex-col sm:flex-row gap-4">
{/* Product Image & Info */}
<div className="flex gap-4 flex-1">
<div className="relative w-[88px] h-[117px] rounded-xl border overflow-hidden flex-shrink-0">
<Image
src={item.product.image || item.product.images[0] || "/placeholder.svg"}
alt={item.product.name}
fill
className="object-contain"
/>
</div>
<div className="flex flex-col gap-2">
<h3 className="font-semibold text-base">{item.product.name}</h3>
<p className="text-sm text-gray-600">{item.seller.name}</p>
<Button
variant="ghost"
size="sm"
onClick={handleDelete}
disabled={isRemoving}
className="w-fit p-0 h-auto hover:bg-transparent hover:text-red-500"
>
<Trash2 className="h-5 w-5" />
</Button>
</div>
</div>
{/* Price & Quantity */}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 justify-between">
<div className="space-y-1">
<p className="text-sm font-semibold">
{t.pricePerUnit} <span className="text-primary">{item.price_formatted || `${item.price} TMT`}</span>
</p>
<p className="text-sm font-semibold">
{t.additionalPrice} {item.sub_total_formatted || `${item.total} TMT`}
</p>
{item.discount_formatted && item.discount_formatted !== "0 TMT" && (
<p className="text-sm font-semibold">
{t.discount} {item.discount_formatted}
</p>
)}
<div className="flex items-center gap-2">
<span className="text-sm font-semibold">{t.totalPrice}</span>
<span className="bg-green-500 text-white px-3 py-1 rounded-xl font-semibold text-base">
{item.total_formatted || `${item.total} TMT`}
</span>
</div>
</div>
{/* Quantity Controls */}
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => handleQuantityChange(-1)}
disabled={item.quantity === 1 || isUpdating}
className="rounded-xl bg-blue-50"
>
<Minus className="h-4 w-4" />
</Button>
<div className="w-12 text-center font-semibold">{item.quantity}</div>
<Button
variant="outline"
size="icon"
onClick={() => handleQuantityChange(1)}
disabled={isUpdating}
className="rounded-xl bg-blue-50"
>
<Plus className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</Card>
)
}

View File

@@ -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 (
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">{t.deliveryType}</h3>
<div className="flex gap-2">
{deliveryOptions.map(({ type, label, icon: Icon }) => (
<Card
key={type}
className={`flex-1 cursor-pointer transition-all ${
selectedType === type
? "border-2 border-[#005bff]"
: "border-2 border-gray-200"
}`}
onClick={() => onSelect(type)}
>
<div className="flex flex-col items-center justify-center p-4 gap-2">
<Icon
className={`h-8 w-8 ${
selectedType === type ? "text-[#005bff]" : ""
}`}
/>
<span className="text-xs">{label}</span>
</div>
</Card>
))}
</div>
</div>
);
}

View File

@@ -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 (
<Card className="w-full md:w-[380px] p-6 rounded-xl h-fit sticky top-20">
{/* Payment Type */}
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">{t.paymentType}</h3>
<div className="flex gap-2">
{paymentTypes.map((type) => (
<Card
key={type.id}
className={`flex-1 cursor-pointer transition-all ${
paymentType?.id === type.id ? "border-2 border-[#005bff]" : "border-2 border-gray-200"
}`}
onClick={() => onPaymentTypeChange(type)}
>
<div className="flex flex-col items-center justify-center p-4 gap-2">
<span className="text-xs font-medium">{type.name}</span>
</div>
</Card>
))}
</div>
</div>
{/* Delivery Type */}
<DeliveryTypeSelector selectedType={deliveryType} onSelect={onDeliveryTypeChange} translations={t} />
{/* Region Selection */}
<div className="mb-6">
<Label className="text-lg font-semibold mb-3 block">{t.selectRegion}</Label>
<RadioGroup value={selectedRegion || ""} onValueChange={onRegionChange} className="flex flex-wrap gap-4">
{regions.map((region) => (
<div key={region.id} className="flex items-center space-x-2">
<RadioGroupItem
value={region.code}
id={`region-${region.id}`}
className="border-2 border-gray-400 data-[state=checked]:border-[#005bff] data-[state=checked]:bg-white data-[state=checked]:[&_svg]:fill-[#005bff] data-[state=checked]:[&_svg]:stroke-[#005bff]"
/>
<Label htmlFor={`region-${region.id}`} className="cursor-pointer">
{region.name}
</Label>
</div>
))}
</RadioGroup>
</div>
{/* Address Selection */}
{filteredAddresses.length > 0 && (
<div className="mb-6">
<Label className="text-lg font-semibold mb-3 block">{t.selectAddress}</Label>
<div className="flex gap-2">
<Select value={selectedAddress} onValueChange={onAddressChange}>
<SelectTrigger className="rounded-xl">
<SelectValue placeholder={t.selectAddress} />
</SelectTrigger>
<SelectContent>
{filteredAddresses.map((addr) => (
<SelectItem key={addr.id} value={addr.address}>
{addr.title} - {addr.address}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="outline"
size="icon"
onClick={onMapOpen}
className="rounded-xl flex-shrink-0 bg-transparent"
>
<MapPin className="h-5 w-5" />
</Button>
</div>
</div>
)}
{/* Note */}
<div className="mb-6">
<Label className="text-lg font-semibold mb-3 block">{t.note}</Label>
<Textarea
value={note}
onChange={(e) => onNoteChange(e.target.value)}
className="rounded-xl resize-none"
rows={3}
placeholder={t.note}
/>
</div>
{/* Billing Summary */}
<div className="space-y-2 mb-4">
{order.billing.body.map((item, index) => (
<div key={index} className="flex justify-between text-base font-medium">
<span>{item.title}:</span>
<span>{item.value}</span>
</div>
))}
</div>
<Separator className="my-4" />
{/* Total */}
<div className="flex justify-between items-center mb-6">
<span className="text-lg font-semibold">{order.billing.footer.title}:</span>
<span className="text-lg font-bold text-green-600">{order.billing.footer.value}</span>
</div>
{/* Complete Order Button */}
<Button
onClick={onCompleteOrder}
disabled={!isFormValid || isLoading}
className="w-full rounded-xl bg-[#005bff] hover:bg-[#005bff] cursor-pointer h-12 text-lg font-bold disabled:opacity-50"
size="lg"
>
{isLoading ? t.placeOrder + "..." : t.placeOrder}
</Button>
</Card>
)
}

View File

@@ -0,0 +1,49 @@
import React from "react";
import { CreditCard } from "lucide-react";
import { Card } from "@/components/ui/card";
import { PaymentType, CartTranslations } from "./types";
interface PaymentTypeSelectorProps {
selectedType: PaymentType;
onSelect: (type: PaymentType) => void;
translations: CartTranslations;
}
export default function PaymentTypeSelector({
selectedType,
onSelect,
translations: t,
}: PaymentTypeSelectorProps) {
const paymentOptions: { type: PaymentType; label: string }[] = [
{ type: "CASH", label: t.cash },
{ type: "CARD", label: t.card },
];
return (
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">{t.paymentType}</h3>
<div className="flex gap-2">
{paymentOptions.map(({ type, label }) => (
<Card
key={type}
className={`flex-1 cursor-pointer transition-all ${
selectedType === type
? "border-2 border-[#005bff]"
: "border-2 border-gray-200"
}`}
onClick={() => onSelect(type)}
>
<div className="flex flex-col items-center justify-center p-4 gap-2">
<CreditCard
className={`h-8 w-8 ${
selectedType === type ? "text-[#005bff]" : ""
}`}
/>
<span className="text-xs">{label}</span>
</div>
</Card>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,87 @@
import type { StaticImageData } from "next/image"
export interface CartItem {
id: number
product_id: number
product: {
id: number
name: string
images: (StaticImageData | string)[]
image?: StaticImageData | string
}
seller: {
id: number
name: string
}
quantity: number
price: number
total: number
price_formatted?: string
sub_total_formatted?: string
discount_formatted?: string
total_formatted?: string
}
export interface Order {
id: number
seller: {
id: number
name: string
}
items: CartItem[]
billing: {
body: Array<{ title: string; value: string }>
footer: { title: string; value: string }
}
}
export interface Region {
id: number
code: string
name: string
}
export interface Address {
id: number
title: string
region_id: number
address: string
phone?: string
is_default?: boolean
}
export interface PickUpPoint {
id: number
name: string
address: string
}
export interface PaymentTypeOption {
id: number
name: string
code: string
}
export interface CartTranslations {
cart: string
ordersIn: string
pricePerUnit: string
additionalPrice: string
discount: string
totalPrice: string
paymentType: string
cash: string
card: string
deliveryType: string
delivery: string
pickup: string
selectRegion: string
selectAddress: string
note: string
placeOrder: string
emptyCart: string
map: string
}
export type PaymentType = "CASH" | "CARD"
export type DeliveryType = "SELECTED_DELIVERY" | "PICK_UP"