first commit

This commit is contained in:
Samandar Turgunboyev
2025-12-15 18:41:13 +05:00
parent a5b46a9f26
commit 6bf86f39c6
109 changed files with 7007 additions and 295 deletions

View File

@@ -0,0 +1,321 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import formatPhone from '@/shared/lib/formatPhone';
import { Input } from '@/shared/ui/input';
import { ArrowRight, Check, Lock, Phone } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
type Step = 'phone' | 'otp';
const Login = () => {
const [step, setStep] = useState<Step>('phone');
const [phoneNumber, setPhoneNumber] = useState<string>('+998');
const [otp, setOtp] = useState<string[]>(['', '', '', '', '', '']);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [countdown, setCountdown] = useState<number>(60);
const [canResend, setCanResend] = useState<boolean>(false);
const router = useRouter();
const otpInputs = useRef<Array<HTMLInputElement | null>>([]);
/* Countdown */
useEffect(() => {
if (step === 'otp' && countdown > 0) {
const timer = setTimeout(() => setCountdown((c) => c - 1), 1000);
return () => clearTimeout(timer);
}
if (countdown === 0) {
setCanResend(true);
}
}, [countdown, step]);
/* Phone submit */
const handlePhoneSubmit = (): void => {
setError('');
if (phoneNumber.length < 9) {
setError("Telefon raqamni to'liq kiriting");
return;
}
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setStep('otp');
setCountdown(60);
setCanResend(false);
}, 1500);
};
/* OTP change */
const handleOtpChange = (index: number, value: string): void => {
if (value && !/^\d$/.test(value)) return;
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
if (value && index < 5) {
otpInputs.current[index + 1]?.focus();
}
if (newOtp.every((d) => d !== '') && index === 5) {
handleOtpSubmit(newOtp);
}
};
/* OTP keydown */
const handleOtpKeyDown = (
index: number,
e: React.KeyboardEvent<HTMLInputElement>,
): void => {
if (e.key === 'Backspace' && !otp[index] && index > 0) {
otpInputs.current[index - 1]?.focus();
}
};
/* OTP paste */
const handleOtpPaste = (e: React.ClipboardEvent<HTMLDivElement>): void => {
e.preventDefault();
const pasted = e.clipboardData.getData('text').slice(0, 6);
if (!/^\d+$/.test(pasted)) return;
const newOtp = pasted.split('');
setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]);
const lastIndex = Math.min(newOtp.length - 1, 5);
otpInputs.current[lastIndex]?.focus();
if (pasted.length === 6) {
setTimeout(() => handleOtpSubmit(newOtp), 100);
}
};
const handleOtpSubmit = (otpArray: string[] = otp): void => {
setError('');
const otpCode = otpArray.join('');
if (otpCode.length < 6) {
setError("Kodni to'liq kiriting");
return;
}
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
if (otpCode === '123456') {
localStorage.setItem('user', 'true');
router.push('/');
} else {
setError("Noto'g'ri kod. Qayta urinib ko'ring.");
setOtp(['', '', '', '', '', '']);
otpInputs.current[0]?.focus();
}
}, 1500);
};
/* Resend */
const handleResendOtp = (): void => {
if (!canResend) return;
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setCountdown(60);
setCanResend(false);
setOtp(['', '', '', '', '', '']);
otpInputs.current[0]?.focus();
alert('Yangi kod yuborildi!');
}, 1000);
};
const handleChangeNumber = (): void => {
setStep('phone');
setPhoneNumber('');
setOtp(['', '', '', '', '', '']);
setError('');
setCountdown(60);
setCanResend(false);
};
return (
<div className="custom-container flex justify-center items-center h-[85vh]">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 p-8 text-white text-center">
<div className="w-20 h-20 bg-white bg-opacity-20 rounded-full flex items-center justify-center mx-auto mb-4">
{step === 'phone' ? (
<Phone className="w-10 h-10" />
) : (
<Lock className="w-10 h-10" />
)}
</div>
<h1 className="text-2xl font-bold mb-2">
{step === 'phone' ? 'Xush kelibsiz!' : 'Kodni tasdiqlang'}
</h1>
<p className="text-blue-100">
{step === 'phone'
? 'Telefon raqamingizni kiriting'
: `${phoneNumber} raqamiga yuborilgan kodni kiriting`}
</p>
</div>
{/* Form */}
<div className="p-8">
{step === 'phone' ? (
// Phone Number Step
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Telefon raqam
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<Phone className="w-5 h-5 text-gray-400" />
</div>
<Input
type="tel"
value={formatPhone(phoneNumber)}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, '');
setPhoneNumber(value);
setError('');
}}
placeholder="+998 90 123-45-67"
maxLength={17}
className="w-full pl-12 pr-4 py-4 h-12 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-blue-500 transition text-lg"
/>
</div>
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
<button
onClick={handlePhoneSubmit}
disabled={isLoading || phoneNumber.length < 9}
className="w-full mt-6 bg-gradient-to-r from-blue-600 to-indigo-600 text-white py-4 rounded-xl font-semibold hover:from-blue-700 hover:to-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{isLoading ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
Yuborilmoqda...
</>
) : (
<>
Kodni olish
<ArrowRight className="w-5 h-5" />
</>
)}
</button>
<p className="text-center text-sm text-gray-500 mt-6">
Davom etish orqali siz bizning{' '}
<a href="#" className="text-blue-600 hover:underline">
Foydalanish shartlari
</a>{' '}
va{' '}
<a href="#" className="text-blue-600 hover:underline">
Maxfiylik siyosati
</a>
ga rozilik bildirasiz
</p>
</div>
) : (
// OTP Step
<div>
<label className="block text-sm font-medium text-gray-700 mb-4 text-center">
6 raqamli kodni kiriting
</label>
<div
className="flex gap-2 justify-center mb-6"
onPaste={handleOtpPaste}
>
{otp.map((digit, index) => (
<Input
key={index}
ref={(el) => {
otpInputs.current[index] = el;
}}
type="text"
inputMode="numeric"
maxLength={1}
value={digit}
onChange={(e) => handleOtpChange(index, e.target.value)}
onKeyDown={(e) => handleOtpKeyDown(index, e)}
className="w-12 h-14 text-center text-2xl font-bold border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition"
/>
))}
</div>
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg mb-4 text-sm text-center">
{error}
</div>
)}
<button
onClick={() => handleOtpSubmit()}
disabled={isLoading || otp.some((digit) => digit === '')}
className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 text-white py-4 rounded-xl font-semibold hover:from-blue-700 hover:to-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{isLoading ? (
<>
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
Tekshirilmoqda...
</>
) : (
<>
Tasdiqlash
<Check className="w-5 h-5" />
</>
)}
</button>
{/* Resend OTP */}
<div className="mt-6 text-center">
{canResend ? (
<button
onClick={handleResendOtp}
disabled={isLoading}
className="text-blue-600 hover:text-blue-700 font-semibold hover:underline"
>
Kodni qayta yuborish
</button>
) : (
<p className="text-gray-500 text-sm">
Kodni qayta yuborish ({countdown}s)
</p>
)}
</div>
{/* Change Number */}
<button
onClick={handleChangeNumber}
className="w-full mt-4 text-gray-600 hover:text-gray-800 font-medium"
>
{"Raqamni o'zgartirish"}
</button>
{/* Demo info */}
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
<p className="text-sm text-blue-800 text-center">
<strong>Demo uchun:</strong>
{`Kod sifatida "123456" kiriting`}
</p>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default Login;

View File

@@ -0,0 +1,318 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { Button } from '@/shared/ui/button';
import { Input } from '@/shared/ui/input';
import {
ArrowLeft,
CreditCard,
Minus,
Plus,
ShoppingBag,
Trash,
Truck,
} from 'lucide-react';
import Image from 'next/image';
import { useState } from 'react';
interface CartItem {
id: number;
name: string;
price: number;
oldPrice: number;
image: string;
quantity: number | string;
inStock: boolean;
}
const CartPage = () => {
const router = useRouter();
const [cartItems, setCartItems] = useState<CartItem[]>([
{
id: 5,
name: 'Coca-Cola 1.5L',
price: 12000,
oldPrice: 14000,
image: '/classic-coca-cola.png',
quantity: 2,
inStock: true,
},
{
id: 6,
name: 'Pepsi 2L',
price: 11000,
oldPrice: 13000,
image: '/pepsi-bottle.jpg',
quantity: 1,
inStock: true,
},
{
id: 8,
name: 'Sprite 1.5L',
price: 10000,
oldPrice: 12000,
image: '/clear-soda-bottle.png',
quantity: 3,
inStock: true,
},
]);
const subtotal = cartItems.reduce(
(sum, item) => sum + item.price * Number(item.quantity),
0,
);
const discount = cartItems.reduce((sum, item) => {
if (item.oldPrice) {
return sum + (item.oldPrice - item.price) * Number(item.quantity);
}
return sum;
}, 0);
const deliveryFee = subtotal > 50000 ? 0 : 15000;
const total = subtotal - discount + deliveryFee;
const handleQuantityChange = (id: number, type: 'increase' | 'decrease') => {
setCartItems((prev) =>
prev.map((item) => {
if (item.id === id) {
if (type === 'increase')
return { ...item, quantity: Number(item.quantity) + 1 };
if (type === 'decrease' && Number(item.quantity) > 1)
return { ...item, quantity: Number(item.quantity) - 1 };
}
return item;
}),
);
};
// Remove item from cart
const handleRemoveItem = (id: number) => {
setCartItems((prev) => prev.filter((item) => item.id !== id));
};
const handleCheckout = () => {
router.push('/cart/order');
};
if (cartItems.length === 0) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<ShoppingBag className="w-24 h-24 text-gray-300 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-gray-800 mb-2">
{"Savatingiz bo'sh"}
</h2>
<p className="text-gray-600 mb-6">
{"Mahsulotlar qo'shish uchun katalogga o'ting"}
</p>
<button
onClick={() => router.push('/')}
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition flex items-center gap-2 mx-auto"
>
<ArrowLeft className="w-5 h-5" /> Xarid qilishni boshlash
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-7xl mx-auto px-4">
{/* Header */}
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-800 mb-2">Savat</h1>
<p className="text-gray-600">{cartItems.length} ta mahsulot</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Cart Items */}
<div className="lg:col-span-2">
<div className="bg-white rounded-lg shadow-md overflow-hidden">
{cartItems.map((item, index) => (
<div
key={item.id}
className={`p-6 flex relative gap-4 ${index !== cartItems.length - 1 ? 'border-b' : ''}`}
>
{/* Product Image */}
<Button
variant={'destructive'}
size={'icon'}
onClick={() => handleRemoveItem(item.id)}
className="absolute right-2 w-7 h-7 top-2 cursor-pointer"
>
<Trash className="size-4" />
</Button>
<div className="w-24 h-40 bg-gray-100 rounded-lg flex-shrink-0 overflow-hidden">
<Image
width={500}
height={500}
src={item.image}
alt={item.name}
className="w-full h-full object-cover"
/>
</div>
{/* Product Info */}
<div className="flex-1">
<h3 className="font-semibold text-lg mb-1">{item.name}</h3>
<div className="flex items-center gap-2 mb-3 max-lg:flex-col max-lg:items-start max-lg:gap-1">
<span className="text-blue-600 font-bold text-xl">
{item.price.toLocaleString()} {"so'm"}
</span>
{item.oldPrice && (
<span className="text-gray-400 line-through text-sm">
{item.oldPrice.toLocaleString()} {"so'm"}
</span>
)}
</div>
<div className="flex items-center justify-between max-lg:flex-col max-lg:items-start max-lg:gap-1">
{/* Quantity Controls */}
<div className="flex items-center border border-gray-300 rounded-lg">
<button
onClick={() =>
handleQuantityChange(item.id, 'decrease')
}
className="p-2 cursor-pointer transition rounded-lg"
disabled={Number(item.quantity) <= 1}
>
<Minus className="w-4 h-4" />
</button>
<Input
type="text"
min={1}
value={item.quantity}
onChange={(e) => {
const value = e.target.value;
// Bo'sh qiymatga ruxsat berish
if (value === '') {
setCartItems((prev) =>
prev.map((cartItem) =>
cartItem.id === item.id
? { ...cartItem, quantity: '' }
: cartItem,
),
);
return;
}
const number = parseInt(value, 10);
if (!isNaN(number) && number > 0) {
setCartItems((prev) =>
prev.map((cartItem) =>
cartItem.id === item.id
? { ...cartItem, quantity: number }
: cartItem,
),
);
}
}}
className="w-16 text-center outline-none ring-0 focus-visible:ring-0 border-none font-semibold"
/>
<button
onClick={() =>
handleQuantityChange(item.id, 'increase')
}
className="p-2 cursor-pointer transition rounded-lg"
>
<Plus className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
{/* Order Summary */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-md p-6 sticky top-4">
<h3 className="text-xl font-bold mb-4">Buyurtma xulasasi</h3>
<div className="space-y-3 mb-4">
<div className="flex justify-between text-gray-600">
<span>Mahsulotlar narxi:</span>
<span>
{subtotal.toLocaleString()} {"so'm"}
</span>
</div>
{discount > 0 && (
<div className="flex justify-between text-green-600">
<span>Chegirma:</span>
<span>
-{discount.toLocaleString()} {"so'm"}
</span>
</div>
)}
<div className="flex justify-between text-gray-600">
<span className="flex items-center gap-1">
<Truck className="w-4 h-4" />
Yetkazib berish:
</span>
<span>
{deliveryFee === 0 ? (
<span className="text-green-600 font-semibold">
Bepul
</span>
) : (
`${deliveryFee.toLocaleString()} so'm`
)}
</span>
</div>
{deliveryFee > 0 && (
<p className="text-sm text-gray-500 bg-blue-50 p-2 rounded">
{
"50,000 so'mdan ortiq xarid qiling va yetkazib berishni bepul oling!"
}
</p>
)}
</div>
<div className="border-t pt-4 mb-6">
<div className="flex justify-between items-center">
<span className="text-lg font-semibold">Jami:</span>
<span className="text-2xl font-bold text-blue-600">
{total.toLocaleString()} {"so'm"}
</span>
</div>
</div>
<button
onClick={handleCheckout}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-semibold hover:bg-blue-700 transition flex items-center justify-center gap-2 mb-4"
>
<CreditCard className="w-5 h-5" />
Buyurtmani rasmiylashtirish
</button>
<button
onClick={() => router.push('/')}
className="w-full border-2 border-gray-300 cursor-pointer text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-50 transition flex items-center justify-center gap-2"
>
<ArrowLeft className="w-5 h-5" />
Xaridni davom ettirish
</button>
{/* Additional Info */}
<div className="mt-6 space-y-3 text-sm text-gray-600">
<div className="flex items-start gap-2">
<Truck className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<span>Tez yetkazib berish 1-2 kun ichida</span>
</div>
<div className="flex items-start gap-2">
<CreditCard className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<span>{"Xavfsiz to'lov usullari"}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CartPage;

View File

@@ -0,0 +1,437 @@
'use client';
import formatPhone from '@/shared/lib/formatPhone';
import { Input } from '@/shared/ui/input';
import { Label } from '@/shared/ui/label';
import { Textarea } from '@/shared/ui/textarea';
import {
Building2,
CheckCircle2,
Clock,
CreditCard,
MapPin,
Package,
Truck,
User,
Wallet,
} from 'lucide-react';
import Image from 'next/image';
import { useState } from 'react';
const OrderPage = () => {
const [formData, setFormData] = useState({
fullName: '',
phone: '+998',
email: '',
city: '',
address: '',
postalCode: '',
notes: '',
});
const [paymentMethod, setPaymentMethod] = useState('cash');
const [deliveryMethod, setDeliveryMethod] = useState('standard');
const [isSubmitting, setIsSubmitting] = useState(false);
const [orderSuccess, setOrderSuccess] = useState(false);
// Cart items from previous page (in real app, this would come from context/store)
const cartItems = [
{
id: 5,
name: 'Coca-Cola 1.5L',
price: 12000,
quantity: 2,
image: '/classic-coca-cola.png',
},
{
id: 6,
name: 'Pepsi 2L',
price: 11000,
quantity: 1,
image: '/pepsi-bottle.jpg',
},
{
id: 8,
name: 'Sprite 1.5L',
price: 10000,
quantity: 3,
image: '/clear-soda-bottle.png',
},
];
const subtotal = cartItems.reduce(
(sum, item) => sum + item.price * item.quantity,
0,
);
const deliveryFee =
deliveryMethod === 'express' ? 25000 : subtotal > 50000 ? 0 : 15000;
const total = subtotal + deliveryFee;
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
setIsSubmitting(false);
setOrderSuccess(true);
}, 2000);
};
if (orderSuccess) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full text-center">
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<CheckCircle2 className="w-12 h-12 text-green-600" />
</div>
<h2 className="text-2xl font-bold text-gray-800 mb-2">
Buyurtma qabul qilindi!
</h2>
<p className="text-gray-600 mb-4">
Buyurtma raqami:{' '}
<span className="font-bold">
#ORD-{Math.floor(Math.random() * 10000)}
</span>
</p>
<p className="text-gray-500 mb-6">
Buyurtmangiz muvaffaqiyatli qabul qilindi. Tez orada sizga aloqaga
chiqamiz.
</p>
<div className="bg-blue-50 p-4 rounded-lg mb-6">
<p className="text-sm text-gray-700">
Buyurtma holati haqida SMS orqali xabardor qilinasiz
</p>
</div>
<button
onClick={() => (window.location.href = '/')}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition"
>
Bosh sahifaga qaytish
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-7xl mx-auto px-4">
{/* Header */}
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-800 mb-2">
Buyurtmani rasmiylashtirish
</h1>
<p className="text-gray-600">{"Ma'lumotlaringizni to'ldiring"}</p>
</div>
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - Forms */}
<div className="lg:col-span-2 space-y-6">
{/* Contact Information */}
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center gap-2 mb-4">
<User className="w-5 h-5 text-blue-600" />
<h2 className="text-xl font-semibold">
{"Shaxsiy ma'lumotlar"}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label className="block text-sm font-medium text-gray-700 mb-2">
{"To'liq ism"}
</Label>
<Input
type="text"
name="fullName"
value={formData.fullName}
onChange={handleInputChange}
required
className="w-full border h-12 border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
placeholder="Ismingiz va familiyangiz"
/>
</div>
<div>
<Label className="block text-sm font-medium text-gray-700 mb-2">
Telefon raqam
</Label>
<Input
type="tel"
name="phone"
value={formatPhone(formData.phone)}
onChange={handleInputChange}
required
className="w-full h-12 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
placeholder="+998 90 123 45 67"
/>
</div>
</div>
</div>
{/* Delivery Address */}
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center gap-2 mb-4">
<MapPin className="w-5 h-5 text-blue-600" />
<h2 className="text-xl font-semibold">
Yetkazib berish manzili
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label className="block text-sm font-medium text-gray-700 mb-2">
Shahar
</Label>
<Input
type="text"
name="city"
value={formData.city}
onChange={handleInputChange}
required
className="w-full border h-12 border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
placeholder="Toshkent"
/>
</div>
<div className="md:col-span-2">
<Label className="block text-sm font-medium text-gray-700 mb-2">
{"To'liq manzil"}
</Label>
<Textarea
name="address"
value={formData.address}
onChange={handleInputChange}
required
rows={3}
className="w-full border min-h-32 max-h-44 border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
placeholder="Ko'cha, uy raqami, xonadon..."
/>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center gap-2 mb-4">
<Truck className="w-5 h-5 text-blue-600" />
<h2 className="text-xl font-semibold">
Yetkazib berish usuli
</h2>
</div>
<div className="space-y-3">
<label className="flex items-center p-4 border-2 rounded-lg cursor-pointer transition hover:bg-gray-50">
<Input
type="radio"
name="delivery"
value="standard"
checked={deliveryMethod === 'standard'}
onChange={(e) => setDeliveryMethod(e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<div className="ml-4 flex-1">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="w-5 h-5 text-gray-600" />
<span className="font-semibold">
Standart yetkazib berish
</span>
</div>
<span className="font-bold text-blue-600">
{subtotal > 50000 ? 'Bepul' : "15,000 so'm"}
</span>
</div>
<p className="text-sm text-gray-500 mt-1">
2-3 kun ichida
</p>
</div>
</label>
<label className="flex items-center p-4 border-2 rounded-lg cursor-pointer transition hover:bg-gray-50">
<Input
type="radio"
name="delivery"
value="express"
checked={deliveryMethod === 'express'}
onChange={(e) => setDeliveryMethod(e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<div className="ml-4 flex-1">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Package className="w-5 h-5 text-gray-600" />
<span className="font-semibold">
Tez yetkazib berish
</span>
</div>
<span className="font-bold text-blue-600">
{"25,000 so'm"}
</span>
</div>
<p className="text-sm text-gray-500 mt-1">1 kun ichida</p>
</div>
</label>
</div>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center gap-2 mb-4">
<CreditCard className="w-5 h-5 text-blue-600" />
<h2 className="text-xl font-semibold">{"To'lov usuli"}</h2>
</div>
<div className="space-y-3">
<Label className="flex items-center p-4 border-2 rounded-lg cursor-pointer transition hover:bg-gray-50">
<Input
type="radio"
name="payment"
value="cash"
checked={paymentMethod === 'cash'}
onChange={(e) => setPaymentMethod(e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<div className="ml-4 flex items-center gap-3">
<Wallet className="w-6 h-6 text-green-600" />
<div>
<span className="font-semibold">Naqd pul</span>
<p className="text-sm text-gray-500">
{"Yetkazib berishda to'lash"}
</p>
</div>
</div>
</Label>
<Label className="flex items-center p-4 border-2 rounded-lg cursor-pointer transition hover:bg-gray-50">
<Input
type="radio"
name="payment"
value="card"
checked={paymentMethod === 'card'}
onChange={(e) => setPaymentMethod(e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<div className="ml-4 flex items-center gap-3">
<CreditCard className="w-6 h-6 text-blue-600" />
<div>
<span className="font-semibold">Plastik karta</span>
<p className="text-sm text-gray-500">
{"Online to'lov"}
</p>
</div>
</div>
</Label>
<Label className="flex items-center p-4 border-2 rounded-lg cursor-pointer transition hover:bg-gray-50">
<Input
type="radio"
name="payment"
value="terminal"
checked={paymentMethod === 'terminal'}
onChange={(e) => setPaymentMethod(e.target.value)}
className="w-4 h-4 text-blue-600"
/>
<div className="ml-4 flex items-center gap-3">
<Building2 className="w-6 h-6 text-purple-600" />
<div>
<span className="font-semibold">Terminal orqali</span>
<p className="text-sm text-gray-500">
Yetkazib berishda terminal
</p>
</div>
</div>
</Label>
</div>
</div>
</div>
{/* Right Column - Order Summary */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-md p-6 sticky top-4">
<h3 className="text-xl font-bold mb-4">Mahsulotlar</h3>
{/* Cart Items */}
<div className="space-y-3 mb-4 max-h-60 overflow-y-auto">
{cartItems.map((item) => (
<div key={item.id} className="flex gap-3 pb-3 border-b">
<Image
width={500}
height={500}
src={item.image}
alt={item.name}
className="w-16 h-16 object-contain bg-gray-100 rounded"
/>
<div className="flex-1">
<h4 className="font-medium text-sm">{item.name}</h4>
<p className="text-sm text-gray-500">
{item.quantity} x {item.price.toLocaleString()}{' '}
{"so'm"}
</p>
<p className="font-semibold text-sm">
{(item.price * item.quantity).toLocaleString()}{' '}
{"so'm"}
</p>
</div>
</div>
))}
</div>
{/* Pricing */}
<div className="space-y-2 mb-4 pt-4 border-t">
<div className="flex justify-between text-gray-600">
<span>Mahsulotlar:</span>
<span>
{subtotal.toLocaleString()} {"so'm"}
</span>
</div>
<div className="flex justify-between text-gray-600">
<span>Yetkazib berish:</span>
<span>
{deliveryFee === 0 ? (
<span className="text-green-600 font-semibold">
Bepul
</span>
) : (
`${deliveryFee.toLocaleString()} so'm`
)}
</span>
</div>
</div>
<div className="border-t pt-4 mb-6">
<div className="flex justify-between items-center">
<span className="text-lg font-semibold">Jami:</span>
<span className="text-2xl font-bold text-blue-600">
{total.toLocaleString()} {"so'm"}
</span>
</div>
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-semibold hover:bg-blue-700 transition disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{isSubmitting ? (
<span className="flex items-center justify-center gap-2">
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
Yuborilmoqda...
</span>
) : (
'Buyurtmani tasdiqlash'
)}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
);
};
export default OrderPage;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { categoryList, CategoryType } from '@/widgets/welcome/lib/data';
import { ChevronRight } from 'lucide-react';
const Category = () => {
const router = useRouter();
const handleCategoryClick = (category: CategoryType) => {
router.push(`/category/${category.name}`);
};
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-6xl mx-auto">
<h1 className="text-2xl font-semibold text-gray-900 mb-6">
Kategoriyalar
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{categoryList.map((category, index) => (
<button
key={index}
onClick={() => handleCategoryClick(category)}
className="bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-between hover:border-gray-300 transition-colors"
>
<span className="text-gray-900 font-medium">{category.name}</span>
<ChevronRight className="w-5 h-5 text-gray-400" />
</button>
))}
</div>
</div>
</div>
);
};
export default Category;

View File

@@ -0,0 +1,76 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { ProductCard } from '@/widgets/categories/ui/product-card';
import { ArrowLeft } from 'lucide-react';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { subCategoriesData } from '../lib/data';
const Product = () => {
const { subId } = useParams();
const router = useRouter();
const decodedSubId = decodeURIComponent(subId as string);
const subCategory =
subCategoriesData.find((cat) => cat.name === decodedSubId) ||
subCategoriesData[0];
const [products, setProducts] = useState(subCategory.products);
const handleBack = () => {
router.back();
};
const handleRemove = (id: number) => {
setProducts((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: false } : product,
),
);
};
const handleLiked = (id: number) => {
setProducts((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: true } : product,
),
);
};
return (
<div className="custom-container p-4 mb-5">
<div>
{/* Header */}
<div className="mb-6">
<button
onClick={handleBack}
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-4"
>
<ArrowLeft className="w-5 h-5" />
<span>Orqaga</span>
</button>
<h1 className="text-2xl font-semibold text-gray-900">
{decodedSubId}
</h1>
<p className="text-gray-600 text-sm mt-1">
{subCategory.products.length} ta mahsulot
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-3">
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
handleRemove={handleRemove}
handleLiked={handleLiked}
/>
))}
</div>
</div>
</div>
);
};
export default Product;

View File

@@ -0,0 +1,45 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { categoryList } from '@/widgets/welcome/lib/data';
import { ChevronRight } from 'lucide-react';
import { useParams } from 'next/navigation';
const SubCategory = () => {
const { categoryId } = useParams();
const router = useRouter();
const category =
categoryList.find((cat) => cat.name === categoryId) || categoryList[0];
const handleSubCategoryClick = (subCategory: { name: string }) => {
router.push(`/category/${categoryId}/${subCategory.name}`);
};
return (
<div className="min-h-screen bg-gray-50 p-4">
<div className="max-w-6xl mx-auto">
<h1 className="text-2xl font-semibold text-gray-900 mb-6">
{category.name}
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{category.subCategories.map((subCategory, index) => (
<button
key={index}
onClick={() => handleSubCategoryClick(subCategory)}
className="bg-white border border-gray-200 rounded-lg p-4 flex items-center justify-between hover:border-gray-300 transition-colors"
>
<span className="text-gray-900 font-medium">
{subCategory.name}
</span>
<ChevronRight className="w-5 h-5 text-gray-400" />
</button>
))}
</div>
</div>
</div>
);
};
export default SubCategory;

View File

@@ -0,0 +1,145 @@
'use client';
import { useRouter } from '@/shared/config/i18n/navigation';
import { Button } from '@/shared/ui/button';
import { ProductCard } from '@/widgets/categories/ui/product-card';
import { Heart } from 'lucide-react';
import { useState } from 'react';
// Fake data
const LIKED_PRODUCTS = [
{
id: 1,
name: 'Samsung Galaxy S23 Ultra 256GB, Phantom Black',
price: 12500000,
oldPrice: 15000000,
image: 'https://images.unsplash.com/photo-1610945415295-d9bbf067e59c?w=400',
rating: 4.8,
reviews: 342,
discount: 17,
inStock: true,
liked: true,
},
{
id: 2,
name: 'Apple AirPods Pro 2-chi avlod (USB-C)',
price: 2850000,
oldPrice: 3200000,
image: 'https://images.unsplash.com/photo-1606841837239-c5a1a4a07af7?w=400',
rating: 4.9,
reviews: 567,
discount: 11,
liked: true,
inStock: true,
},
{
id: 3,
name: "Sony PlayStation 5 Slim 1TB + 2 ta o'yin",
price: 7500000,
oldPrice: 8500000,
image: 'https://images.unsplash.com/photo-1606813907291-d86efa9b94db?w=400',
rating: 4.7,
reviews: 234,
discount: 12,
inStock: true,
liked: true,
},
{
id: 4,
name: 'MacBook Air 13 M2 chip, 8GB RAM, 256GB SSD',
price: 14200000,
oldPrice: 16000000,
image: 'https://images.unsplash.com/photo-1517336714731-489689fd1ca8?w=400',
rating: 4.9,
reviews: 891,
liked: true,
discount: 11,
inStock: true,
},
{
id: 5,
name: 'Dyson V15 Detect Simsiz Changyutgich',
price: 6800000,
oldPrice: 7800000,
image: 'https://images.unsplash.com/photo-1558317374-067fb5f30001?w=400',
rating: 4.6,
reviews: 178,
discount: 13,
liked: true,
inStock: false,
},
{
id: 6,
name: 'Nike Air Max 270 React Erkaklar Krosovkasi',
price: 1250000,
oldPrice: 1650000,
image: 'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=400',
rating: 4.5,
liked: true,
reviews: 423,
discount: 24,
inStock: true,
},
];
export default function Favourite() {
const [likedProducts, setLikedProducts] = useState(LIKED_PRODUCTS);
const router = useRouter();
const handleRemove = (id: number) => {
setLikedProducts((prev) => prev.filter((product) => product.id !== id));
};
if (likedProducts.length === 0) {
return (
<div className="min-h-screen bg-slate-50 py-12">
<div className="container mx-auto px-4">
<div className="flex flex-col items-center justify-center py-20">
<div className="w-32 h-32 bg-slate-100 rounded-full flex items-center justify-center mb-6">
<Heart className="w-16 h-16 text-slate-300" />
</div>
<h2 className="text-2xl font-bold text-slate-800 mb-2">
{"Sevimlilar bo'sh"}
</h2>
<p className="text-slate-500 text-center max-w-md mb-8">
{`Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz.
Mahsulotlar ro'yxatiga o'ting va yoqqan mahsulotlaringizni
saqlang.`}
</p>
<Button
className="bg-blue-600 text-white px-8 py-3 rounded-xl hover:bg-blue-700"
onClick={() => router.push('/')}
>
Xarid qilishni boshlash
</Button>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-slate-50 py-8">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-slate-800 mb-2">
Sevimli mahsulotlar
</h1>
<p className="text-slate-500">{likedProducts.length} ta mahsulot</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{likedProducts.map((product) => (
<ProductCard
key={product.id}
product={product}
handleRemove={handleRemove}
/>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,360 @@
'use client';
import { Carousel, CarouselContent, CarouselItem } from '@/shared/ui/carousel';
import { ProductCard } from '@/widgets/categories/ui/product-card';
import {
Heart,
Minus,
Plus,
RotateCcw,
Shield,
ShoppingCart,
Star,
Truck,
} from 'lucide-react';
import Image from 'next/image';
import { useState } from 'react';
const ProductDetail = () => {
const [quantity, setQuantity] = useState(1);
const [selectedImage, setSelectedImage] = useState(0);
const [liked, setLiked] = useState(false);
// Fake product data
const product = {
id: 5,
name: 'Coca-Cola 1.5L',
price: 12000,
oldPrice: 14000,
image: '/classic-coca-cola.png',
rating: 4.8,
reviews: 342,
discount: 14,
inStock: true,
description:
"Coca-Cola klassik ta'mi bilan ajoyib gazlangan ichimlik. 1.5 litrlik shisha butilkada. Sovuq holda iste'mol qilish tavsiya etiladi.",
category: 'Ichimliklar',
brand: 'Coca-Cola',
volume: '1.5L',
images: [
'/classic-coca-cola.png',
'/clear-soda-bottle.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
'/classic-coca-cola.png',
],
specifications: {
Hajmi: '1.5 litr',
'Qadoq turi': 'Plastik butilka',
'Ishlab chiqaruvchi': 'Coca-Cola Company',
'Saqlash muddati': '12 oy',
'Energiya qiymati': '180 kJ / 43 kcal',
},
};
const [relatedProducts, setRelatedProducts] = useState([
{
id: 6,
name: 'Pepsi 2L',
price: 11000,
reviews: 342,
liked: false,
inStock: true,
oldPrice: 13000,
image: '/pepsi-bottle.jpg',
rating: 4.6,
discount: 15,
},
{
id: 8,
name: 'Sprite 1.5L',
price: 10000,
inStock: true,
oldPrice: 12000,
image: '/clear-soda-bottle.png',
rating: 4.5,
reviews: 342,
liked: false,
discount: 17,
},
{
id: 7,
name: 'Fanta Orange 1L',
price: 9000,
oldPrice: 10000,
inStock: true,
image: '/fanta-orange-bottle.png',
rating: 4.4,
reviews: 342,
liked: true,
discount: 10,
},
]);
const handleQuantityChange = (type: string) => {
if (type === 'increase') {
setQuantity(quantity + 1);
} else if (type === 'decrease' && quantity > 1) {
setQuantity(quantity - 1);
}
};
const addToCart = () => {
alert(`${quantity} ta ${product.name} savatchaga qo'shildi!`);
};
const handleRemove = (id: number) => {
setRelatedProducts((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: false } : product,
),
);
};
const handleLiked = (id: number) => {
setRelatedProducts((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: true } : product,
),
);
};
return (
<div className="custom-container pb-5">
<div className="">
<div className="bg-white rounded-lg shadow-md p-6 mb-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<div className="relative bg-gray-100 rounded-lg overflow-hidden mb-4">
<Image
width={500}
height={500}
src={product.images[selectedImage]}
alt={product.name}
className="w-full h-full object-cover"
/>
{product.discount > 0 && (
<div className="absolute top-4 left-4 bg-red-500 text-white px-3 py-1 rounded-full text-sm font-semibold">
-{product.discount}%
</div>
)}
{!product.inStock && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<span className="text-white text-xl font-bold">
Mavjud emas
</span>
</div>
)}
</div>
<Carousel
opts={{
align: 'start',
dragFree: true,
}}
className="w-full"
>
<CarouselContent className="-ml-2">
{product.images.map((img, index) => (
<CarouselItem
key={index}
className="pl-2 basis-1/3 sm:basis-1/4 md:basis-1/5 lg:basis-1/6"
>
<button
onClick={() => setSelectedImage(index)}
className={`aspect-square w-full rounded-lg border-2 overflow-hidden transition ${
selectedImage === index
? 'border-blue-500'
: 'border-gray-200 hover:border-blue-300'
}`}
>
<Image
src={img}
alt={`thumb-${index}`}
width={150}
height={150}
className="w-full h-full object-contain"
/>
</button>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
{/* Product Info */}
<div>
<h1 className="text-3xl font-bold text-gray-800 mb-2">
{product.name}
</h1>
{/* Rating */}
<div className="flex items-center gap-2 mb-4">
<div className="flex items-center">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`w-5 h-5 ${
i < Math.floor(product.rating)
? 'fill-yellow-400 text-yellow-400'
: 'text-gray-300'
}`}
/>
))}
</div>
<span className="text-gray-600">{product.rating}</span>
<span className="text-gray-400">({product.reviews} sharh)</span>
</div>
{/* Price */}
<div className="mb-6">
<div className="flex items-center gap-3">
<span className="text-4xl font-bold text-blue-600">
{product.price.toLocaleString()} {"so'm"}
</span>
{product.oldPrice && (
<span className="text-xl text-gray-400 line-through">
{product.oldPrice.toLocaleString()} {"so'm"}
</span>
)}
</div>
</div>
{/* Description */}
<p className="text-gray-600 mb-6">{product.description}</p>
{/* Brand and Category */}
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<span className="text-gray-500">Brand:</span>
<p className="font-semibold">{product.brand}</p>
</div>
<div>
<span className="text-gray-500">Kategoriya:</span>
<p className="font-semibold">{product.category}</p>
</div>
</div>
{/* Quantity Selector */}
<div className="mb-6">
<label className="text-gray-700 font-medium mb-2 block">
Miqdor:
</label>
<div className="flex items-center gap-4">
<div className="flex items-center border border-gray-300 rounded-lg">
<button
onClick={() => handleQuantityChange('decrease')}
className="p-3 hover:bg-gray-100 transition"
disabled={quantity <= 1}
>
<Minus className="w-5 h-5" />
</button>
<span className="px-6 font-semibold text-lg">
{quantity}
</span>
<button
onClick={() => handleQuantityChange('increase')}
className="p-3 hover:bg-gray-100 transition"
>
<Plus className="w-5 h-5" />
</button>
</div>
<span className="text-gray-600">
Jami:{' '}
<span className="font-bold text-lg">
{(product.price * quantity).toLocaleString()} {"so'm"}
</span>
</span>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-4 mb-6">
<button
onClick={addToCart}
disabled={!product.inStock}
className={`flex-1 py-4 rounded-lg font-semibold text-white flex items-center justify-center gap-2 transition ${
product.inStock
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-gray-400 cursor-not-allowed'
}`}
>
<ShoppingCart className="w-5 h-5" />
{"Savatchaga qo'shish"}
</button>
<button
onClick={() => setLiked(!liked)}
className={`p-4 rounded-lg border-2 transition ${
liked
? 'border-red-500 bg-red-50'
: 'border-gray-300 hover:border-red-500'
}`}
>
<Heart
className={`w-6 h-6 ${liked ? 'fill-red-500 text-red-500' : 'text-gray-600'}`}
/>
</button>
</div>
{/* Features */}
<div className="grid grid-cols-3 gap-4 border-t pt-6">
<div className="flex flex-col items-center text-center">
<Truck className="w-8 h-8 text-blue-600 mb-2" />
<span className="text-sm text-gray-600">
Bepul yetkazib berish
</span>
</div>
<div className="flex flex-col items-center text-center">
<Shield className="w-8 h-8 text-blue-600 mb-2" />
<span className="text-sm text-gray-600">Kafolat</span>
</div>
<div className="flex flex-col items-center text-center">
<RotateCcw className="w-8 h-8 text-blue-600 mb-2" />
<span className="text-sm text-gray-600">
14 kun qaytarish
</span>
</div>
</div>
</div>
</div>
</div>
{/* Specifications */}
<div className="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 className="text-2xl font-bold mb-4">Xususiyatlari</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{Object.entries(product.specifications).map(([key, value]) => (
<div key={key} className="flex justify-between border-b pb-2">
<span className="text-gray-600">{key}:</span>
<span className="font-semibold">{value}</span>
</div>
))}
</div>
</div>
{/* Related Products */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold mb-6">{"O'xshash mahsulotlar"}</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{relatedProducts.map((item) => (
<ProductCard
key={item.id}
product={item}
handleRemove={handleRemove}
handleLiked={handleLiked}
/>
))}
</div>
</div>
</div>
</div>
);
};
export default ProductDetail;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
'use client';
import { Input } from '@/shared/ui/input';
import {
categories,
Product,
ProductDetail,
} from '@/widgets/categories/lib/data';
import { ProductCard } from '@/widgets/categories/ui/product-card';
import { Search, X } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
const SearchResult: React.FC = () => {
const router = useRouter();
const searchParams = useSearchParams();
const queryFromUrl = searchParams.get('q') ?? '';
const [query, setQuery] = useState(queryFromUrl);
const [loading, setLoading] = useState(false);
const [results, setResults] = useState<ProductDetail[]>([]);
const allProducts = useMemo<ProductDetail[]>(() => {
return categories.flatMap((category: Product) =>
category.products.map((product) => ({
...product,
categoryName: category.name,
})),
);
}, []);
const recommendedProducts = useMemo<ProductDetail[]>(() => {
return allProducts.filter((product) => product.rating >= 4.5).slice(0, 8);
}, [allProducts]);
const handleSearch = (searchQuery: string) => {
if (!searchQuery.trim()) return;
setLoading(true);
router.push(`/search?q=${encodeURIComponent(searchQuery)}`);
setTimeout(() => {
const filtered = allProducts.filter(
(product) =>
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
product.categoryName
?.toLowerCase()
.includes(searchQuery.toLowerCase()),
);
setResults(filtered);
setLoading(false);
}, 300);
};
useEffect(() => {
if (queryFromUrl) {
handleSearch(queryFromUrl);
}
}, [queryFromUrl]);
const clearSearch = () => {
setQuery('');
setResults([]);
router.push('/search');
};
const handleRemove = (id: number) => {
setResults((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: false } : product,
),
);
};
const handleLiked = (id: number) => {
setResults((prev) =>
prev.map((product) =>
product.id === id ? { ...product, liked: true } : product,
),
);
};
return (
<div className="custom-container justify-center items-center h-screen">
<div className="lg:hidden">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<Input
value={query}
placeholder="Mahsulot nomi"
onChange={(e) => {
setQuery(e.target.value);
handleSearch(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSearch(query);
}}
className="w-full border rounded-lg pl-10 pr-10 h-12"
/>
{query && (
<button
onClick={clearSearch}
className="absolute right-7 top-1/2 -translate-y-1/2"
>
<X />
</button>
)}
</div>
</div>
<div className="px-4 py-8">
{loading ? (
<div className="text-center py-20">Yuklanmoqda...</div>
) : query ? (
results.length ? (
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
{results.map((product) => (
<ProductCard
key={product.id}
product={product}
handleRemove={handleRemove}
handleLiked={handleLiked}
/>
))}
</div>
) : (
<div className="text-center py-20">Natija topilmadi</div>
)
) : (
<div className="lg:hidden">
<h2 className="text-lg font-semibold mb-4">
Tavsiya etilgan mahsulotlar
</h2>
<div className="grid grid-cols-1 gap-4">
{recommendedProducts.map((product) => (
<ProductCard
key={product.id}
product={product}
handleRemove={handleRemove}
handleLiked={handleLiked}
/>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default SearchResult;