Compare commits
11 Commits
9ec9ea586f
...
345d8de80a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
345d8de80a | ||
|
|
0c7a5127d0 | ||
|
|
1851327f8a | ||
|
|
48ea82de0f | ||
|
|
05c15a4f70 | ||
|
|
3d1d9d4860 | ||
|
|
26c933d196 | ||
|
|
a842449e4d | ||
|
|
bd346aacf3 | ||
|
|
5d81f857da | ||
|
|
2ff4de4912 |
@@ -18,7 +18,7 @@ export async function generateMetadata({
|
||||
}: PageProps): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
try {
|
||||
const res = await product_api.favouuriteProduct();
|
||||
const res = await product_api.favouuriteProduct({ page: 1, page_size: 1 });
|
||||
const total = res.data.total;
|
||||
|
||||
if (locale === 'ru') {
|
||||
|
||||
@@ -58,7 +58,7 @@ export interface OrderCreateBody {
|
||||
person_code: string;
|
||||
currency_code: string;
|
||||
owner_person_code: string;
|
||||
note: string;
|
||||
note?: string;
|
||||
order_products: {
|
||||
inventory_kind: string;
|
||||
product_code: string;
|
||||
|
||||
@@ -3,6 +3,9 @@ import { z } from 'zod';
|
||||
export const orderForm = z.object({
|
||||
long: z.string().min(1, { message: 'Majburiy maydon' }),
|
||||
lat: z.string().min(1, { message: 'Majburiy maydon' }),
|
||||
comment: z.string().max(300, 'Izoh 300 ta belgidan oshmasligi kerak'),
|
||||
comment: z
|
||||
.string()
|
||||
// .min(1, "Eng kamida 1ta belgi bo'lishi kerak")
|
||||
.max(300, 'Izoh 300 ta belgidan oshmasligi kerak'),
|
||||
city: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ const CartPage = () => {
|
||||
|
||||
const isGram = item.product.meansurement?.name?.toLowerCase() === 'gr';
|
||||
const STEP = isGram ? 100 : 1;
|
||||
const MIN_QTY = isGram ? 100 : 0.5;
|
||||
const MIN_QTY = isGram ? 100 : 1;
|
||||
|
||||
let updatedQty: number;
|
||||
if (newValue !== undefined) {
|
||||
@@ -329,23 +329,6 @@ const CartPage = () => {
|
||||
}
|
||||
}, 500);
|
||||
}}
|
||||
onBlur={() => {
|
||||
// Blur bo'lganda noto'g'ri qiymatlarni tuzatish
|
||||
const isGram =
|
||||
item.product.meansurement?.name?.toLowerCase() ===
|
||||
'gr';
|
||||
const MIN_QTY = isGram ? 100 : 0.5;
|
||||
let value = quantities[item.id] ?? item.quantity;
|
||||
|
||||
if (!value || isNaN(value) || value < MIN_QTY)
|
||||
value = MIN_QTY;
|
||||
|
||||
setInputValues((prev) => ({
|
||||
...prev,
|
||||
[item.id]: String(value),
|
||||
}));
|
||||
handleQuantityChange(item.id, 0, value);
|
||||
}}
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
className="w-16 text-center border-none p-0"
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
ZoomControl,
|
||||
} from '@pbe/react-yandex-maps';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import { format } from 'date-fns';
|
||||
import { uz } from 'date-fns/locale';
|
||||
import {
|
||||
@@ -63,6 +64,15 @@ interface CoordsData {
|
||||
polygon: [number, number][][];
|
||||
}
|
||||
|
||||
type DRFError = {
|
||||
[key: string]: Array<
|
||||
| string
|
||||
| {
|
||||
[key: string]: string[];
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
// Yetkazib berish vaqt oraliqlar
|
||||
const deliveryTimeSlots = [
|
||||
{ id: 1, label: '10:00 - 12:00', start: '10:00', end: '12:00' },
|
||||
@@ -106,7 +116,7 @@ const OrderPage = () => {
|
||||
queryClinet.refetchQueries({ queryKey: ['cart_items'] });
|
||||
} else if (message.errors && message.errors.length > 0) {
|
||||
// Xatolik bo'lsa chiqarish
|
||||
toast.error(t('Xatolik yuz berdi: ') + message.errors[0].message, {
|
||||
toast.error(t('Xatolik yuz berdi') + message.errors[0].message, {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
@@ -118,8 +128,38 @@ const OrderPage = () => {
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(t('Xatolik yuz berdi: Mahsulot omborxoda yetarli emas'), {
|
||||
onError: (error: AxiosError<DRFError>) => {
|
||||
const data = error.response?.data;
|
||||
|
||||
let message = t('Xatolik yuz berdi');
|
||||
|
||||
if (data) {
|
||||
// 🔥 DRF validation error parsing
|
||||
if (typeof data === 'object') {
|
||||
const firstKey = Object.keys(data)[0]; // "order"
|
||||
const firstValue = data[firstKey];
|
||||
|
||||
if (Array.isArray(firstValue)) {
|
||||
const firstErrorObj = firstValue[0]; // { note: [...] }
|
||||
|
||||
if (typeof firstErrorObj === 'object') {
|
||||
const innerKey = Object.keys(firstErrorObj)[0]; // "note"
|
||||
const innerValue = firstErrorObj[innerKey];
|
||||
|
||||
if (Array.isArray(innerValue)) {
|
||||
message = innerValue[0]; // "This field may not be blank."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
if (!error.response) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
toast.error(message, {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
@@ -221,6 +261,7 @@ const OrderPage = () => {
|
||||
};
|
||||
|
||||
const cityValue = form.watch('city');
|
||||
const comment = form.watch('comment');
|
||||
|
||||
useEffect(() => {
|
||||
if (!cityValue || cityValue.length < 3) return;
|
||||
@@ -254,6 +295,14 @@ const OrderPage = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.comment.length > 300) {
|
||||
toast.error(t('Izoh 300ta belgidan oshib ketdi'), {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Yetkazib berish vaqtini tekshirish
|
||||
if (!deliveryDate) {
|
||||
toast.error(t('Yetkazib berish sanasini tanlang'), {
|
||||
@@ -290,7 +339,7 @@ const OrderPage = () => {
|
||||
}));
|
||||
if (user) {
|
||||
const dealTime = formatDate.format(deliveryDate, 'DD.MM.YYYY');
|
||||
|
||||
const note = value.comment.trim() ? value.comment : undefined;
|
||||
mutate({
|
||||
order: [
|
||||
{
|
||||
@@ -304,13 +353,13 @@ const OrderPage = () => {
|
||||
person_code: user?.username,
|
||||
currency_code: '860',
|
||||
owner_person_code: user?.username,
|
||||
note: value.comment,
|
||||
order_products: order_products,
|
||||
note: note,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
toast.error(t('Xatolik yuz berdi'), {
|
||||
toast.error(t('Xatolik yuz berdi: Foydalanuvchi topilmadi'), {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
@@ -360,7 +409,7 @@ const OrderPage = () => {
|
||||
<p className="text-gray-600">{t("Ma'lumotlaringizni to'ldiring")}</p>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<div onSubmit={form.handleSubmit(onSubmit)} className="contents">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="contents">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Contact Information */}
|
||||
@@ -368,25 +417,47 @@ const OrderPage = () => {
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="comment"
|
||||
render={({ field }) => (
|
||||
render={({ field }) => {
|
||||
const length = field.value?.length || 0;
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<Label className="block mt-3 text-sm font-medium text-gray-700">
|
||||
{t('Izoh')}
|
||||
</Label>
|
||||
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
maxLength={300}
|
||||
className="w-full min-h-42 max-h-64 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
|
||||
placeholder={t('Izoh')}
|
||||
className={cn(
|
||||
'w-full min-h-42 max-h-64 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500',
|
||||
length > 300 &&
|
||||
'border-red-500 focus-visible:border-red-500 focus-visible:ring-none focus-visible:outline-0',
|
||||
)}
|
||||
placeholder={t(
|
||||
'Izoh 300 ta belgidan oshmasligi kerak',
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="text-right text-xs text-gray-500">
|
||||
{field.value?.length || 0}/300
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'text-right text-xs',
|
||||
comment.length > 300
|
||||
? 'text-red-500'
|
||||
: 'text-gray-500',
|
||||
)}
|
||||
>
|
||||
{comment.length}/300
|
||||
</div>
|
||||
{comment.length > 300 && (
|
||||
<p className="text-red-500 text-md">
|
||||
{t('Izoh 300 ta belgidan oshmasligi kerak')}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -649,7 +720,6 @@ const OrderPage = () => {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
onClick={form.handleSubmit(onSubmit)}
|
||||
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"
|
||||
>
|
||||
{isPending ? (
|
||||
@@ -663,7 +733,7 @@ const OrderPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,25 +3,54 @@
|
||||
import { product_api } from '@/shared/config/api/product/api';
|
||||
import { useRouter } from '@/shared/config/i18n/navigation';
|
||||
import { Button } from '@/shared/ui/button';
|
||||
import { Card } from '@/shared/ui/card';
|
||||
import { Skeleton } from '@/shared/ui/skeleton';
|
||||
import { ProductCard } from '@/widgets/categories/ui/product-card';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Heart } from 'lucide-react';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { Heart, Loader2 } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export default function Favourite() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
const { data: favourite, isLoading } = useQuery({
|
||||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['favourite_product'],
|
||||
queryFn: () => product_api.favouuriteProduct(),
|
||||
initialPageParam: 1,
|
||||
queryFn: ({ pageParam }) =>
|
||||
product_api.favouuriteProduct({ page: pageParam, page_size: 30 }),
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (lastPage.data.has_next) return lastPage.data.page + 1;
|
||||
return undefined;
|
||||
},
|
||||
select(data) {
|
||||
return data.data;
|
||||
return data.pages.flatMap((page) => page.data.results);
|
||||
},
|
||||
});
|
||||
|
||||
if (favourite && favourite.results.length === 0) {
|
||||
// Intersection Observer — loadMoreRef ko'ringanda keyingi sahifani yuklaydi
|
||||
useEffect(() => {
|
||||
const el = loadMoreRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||
|
||||
const favourite = data ?? [];
|
||||
|
||||
if (!isLoading && favourite.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen py-12">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -54,8 +83,6 @@ export default function Favourite() {
|
||||
<Skeleton className="h-8 w-64 mb-2" />
|
||||
<Skeleton className="h-4 w-32" />
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<div key={i} className="rounded-xl border p-3 space-y-3">
|
||||
@@ -71,37 +98,32 @@ export default function Favourite() {
|
||||
|
||||
return (
|
||||
<div className="custom-container">
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-slate-800 mb-2">
|
||||
{t('Sevimli mahsulotlar')}
|
||||
</h1>
|
||||
<p className="text-slate-500">
|
||||
{favourite && favourite.total} {t('ta mahsulot')}
|
||||
{favourite.length} {t('ta mahsulot')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-30">
|
||||
{isLoading &&
|
||||
Array.from({ length: 6 }).map((_, index) => (
|
||||
<Card className="p-3 space-y-3 rounded-xl" key={index}>
|
||||
<Skeleton className="h-40 sm:h-48 md:h-56 w-full rounded-lg" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
<Skeleton className="h-10 w-full rounded-lg" />
|
||||
</Card>
|
||||
))}
|
||||
{favourite &&
|
||||
!isLoading &&
|
||||
favourite?.results
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-4 mb-8">
|
||||
{favourite
|
||||
.filter((product) => product.state === 'A')
|
||||
.map((product) => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* Sentinel element — screen pastiga yetganda trigger bo'ladi */}
|
||||
<div ref={loadMoreRef} className="h-10" />
|
||||
|
||||
{/* Yuklash indikatori */}
|
||||
{isFetchingNextPage && (
|
||||
<div className="flex justify-center py-6">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,24 @@ import { product_api } from '@/shared/config/api/product/api';
|
||||
import { BASE_URL } from '@/shared/config/api/URLs';
|
||||
import { useRouter } from '@/shared/config/i18n/navigation';
|
||||
import { useCartId } from '@/shared/hooks/cartId';
|
||||
import formatDate from '@/shared/lib/formatDate';
|
||||
import formatPrice from '@/shared/lib/formatPrice';
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
import { Button } from '@/shared/ui/button';
|
||||
import { Input } from '@/shared/ui/input';
|
||||
import { Skeleton } from '@/shared/ui/skeleton';
|
||||
import { userStore } from '@/widgets/welcome/lib/hook';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Heart, Minus, Plus, Shield, ShoppingCart, Truck } from 'lucide-react';
|
||||
import {
|
||||
ChevronLeft,
|
||||
Heart,
|
||||
Minus,
|
||||
Plus,
|
||||
Shield,
|
||||
ShoppingCart,
|
||||
Truck,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import { useParams } from 'next/navigation';
|
||||
@@ -26,6 +36,9 @@ const ProductDetail = () => {
|
||||
const { cart_id } = useCartId();
|
||||
const { user } = userStore();
|
||||
const router = useRouter();
|
||||
// const [api, setApi] = useState<CarouselApi>();
|
||||
// const [canScrollPrev, setCanScrollPrev] = useState(false);
|
||||
// const [canScrollNext, setCanScrollNext] = useState(false);
|
||||
|
||||
const [quantity, setQuantity] = useState<number | string>(1);
|
||||
|
||||
@@ -50,6 +63,12 @@ const ProductDetail = () => {
|
||||
enabled: !!cart_id,
|
||||
});
|
||||
|
||||
// const { data: recomendation, isLoading: recLoad } = useQuery({
|
||||
// queryKey: ['product_list', data?.group.id],
|
||||
// queryFn: () => product_api.list({ page: 1, page_size: 12 }),
|
||||
// select: (res) => res.data.results,
|
||||
// });
|
||||
|
||||
const favouriteMutation = useMutation({
|
||||
mutationFn: (productId: string) => product_api.favourite(productId),
|
||||
|
||||
@@ -67,6 +86,27 @@ const ProductDetail = () => {
|
||||
},
|
||||
});
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!api) return;
|
||||
|
||||
// const updateButtons = () => {
|
||||
// setCanScrollPrev(api.canScrollPrev());
|
||||
// setCanScrollNext(api.canScrollNext());
|
||||
// };
|
||||
|
||||
// updateButtons();
|
||||
// api.on('select', updateButtons);
|
||||
// api.on('reInit', updateButtons);
|
||||
|
||||
// return () => {
|
||||
// api.off('select', updateButtons);
|
||||
// api.off('reInit', updateButtons);
|
||||
// };
|
||||
// }, [api]);
|
||||
|
||||
// const scrollPrev = () => api?.scrollPrev();
|
||||
// const scrollNext = () => api?.scrollNext();
|
||||
|
||||
const measurement = data?.meansurement?.name?.toLowerCase() || '';
|
||||
const isGram = measurement === 'gr';
|
||||
|
||||
@@ -264,6 +304,16 @@ const ProductDetail = () => {
|
||||
|
||||
return (
|
||||
<div className="custom-container pb-8">
|
||||
<div className="mb-5">
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className="w-fit"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<ChevronLeft />
|
||||
{t('Orqaga')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 bg-white p-6 rounded-lg shadow">
|
||||
<div>
|
||||
<Image
|
||||
@@ -338,7 +388,7 @@ const ProductDetail = () => {
|
||||
{t('Jami')}: {formatPrice(Number(subtotal) * numericQty, true)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-3 mb-6">
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg flex justify-center items-center gap-2"
|
||||
@@ -368,6 +418,15 @@ const ProductDetail = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{data?.updated_at && data.payment_type === 'cash' && (
|
||||
<div className="bg-yellow-50 border border-yellow-400 text-yellow-800 p-3 mb-4 rounded-md">
|
||||
<p className="text-xs font-medium">
|
||||
{t("Narxi o'zgargan bo'lishi mumkin")} • {t('Yangilangan')}:{' '}
|
||||
{formatDate.format(data.updated_at, 'DD-MM-YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mt-6 border-t pt-4">
|
||||
<div className="text-center">
|
||||
<Truck className="mx-auto mb-1" />
|
||||
@@ -380,6 +439,55 @@ const ProductDetail = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="mt-10 bg-white p-6 rounded-lg shadow relative">
|
||||
<Button
|
||||
onClick={scrollPrev}
|
||||
disabled={!canScrollPrev}
|
||||
className="absolute top-1/2 left-0 -translate-x-1/2 z-20 rounded-full"
|
||||
size={'icon'}
|
||||
variant={'outline'}
|
||||
>
|
||||
<ChevronLeft size={32} />
|
||||
</Button>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-4">{t("O'xshash mahsulotlar")}</h2>
|
||||
|
||||
<Carousel setApi={setApi}>
|
||||
<CarouselContent>
|
||||
{recLoad &&
|
||||
Array.from({ length: 6 }).map((_, i) => (
|
||||
<CarouselItem
|
||||
key={i}
|
||||
className="basis-1/1 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
|
||||
>
|
||||
<Skeleton className="h-60 w-full" />
|
||||
</CarouselItem>
|
||||
))}
|
||||
|
||||
{recomendation
|
||||
?.filter((p) => p.state === 'A')
|
||||
.map((p) => (
|
||||
<CarouselItem
|
||||
key={p.id}
|
||||
className="basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 xl:basis-1/6 pb-2"
|
||||
>
|
||||
<ProductCard product={p} />
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
|
||||
<Button
|
||||
onClick={scrollNext}
|
||||
disabled={!canScrollNext}
|
||||
className="absolute top-1/2 -translate-x-1/2 z-20 -right-10 rounded-full"
|
||||
size={'icon'}
|
||||
variant={'outline'}
|
||||
>
|
||||
<ChevronRight size={32} />
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
ZoomControl,
|
||||
} from '@pbe/react-yandex-maps';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
Calendar as CalIcon,
|
||||
CheckCircle2,
|
||||
@@ -72,6 +73,15 @@ interface CoordsData {
|
||||
polygon: [number, number][][];
|
||||
}
|
||||
|
||||
type DRFError = {
|
||||
[key: string]: Array<
|
||||
| string
|
||||
| {
|
||||
[key: string]: string[];
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
const RefreshOrder = () => {
|
||||
const [deliveryDate, setDeliveryDate] = useState<Date>();
|
||||
const [selectedTimeSlot, setSelectedTimeSlot] = useState<string>('');
|
||||
@@ -126,16 +136,60 @@ const RefreshOrder = () => {
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: (body: OrderCreateBody) => cart_api.createOrder(body),
|
||||
onSuccess: () => {
|
||||
onSuccess: (res) => {
|
||||
const message = JSON.parse(res.data.response);
|
||||
|
||||
if (message.successes && message.successes.length > 0) {
|
||||
setOrderSuccess(true);
|
||||
queryClient.refetchQueries({ queryKey: ['cart_items'] });
|
||||
queryClient.refetchQueries({ queryKey: ['order_list'] });
|
||||
},
|
||||
onError: () => {
|
||||
} else if (message.errors && message.errors.length > 0) {
|
||||
toast.error(t('Xatolik yuz berdi') + message.errors[0].message, {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
} else {
|
||||
toast.error(t('Xatolik yuz berdi'), {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (error: AxiosError<DRFError>) => {
|
||||
const data = error.response?.data;
|
||||
|
||||
let message = t('Xatolik yuz berdi');
|
||||
|
||||
if (data) {
|
||||
// 🔥 DRF validation error parsing
|
||||
if (typeof data === 'object') {
|
||||
const firstKey = Object.keys(data)[0]; // "order"
|
||||
const firstValue = data[firstKey];
|
||||
|
||||
if (Array.isArray(firstValue)) {
|
||||
const firstErrorObj = firstValue[0]; // { note: [...] }
|
||||
|
||||
if (typeof firstErrorObj === 'object') {
|
||||
const innerKey = Object.keys(firstErrorObj)[0]; // "note"
|
||||
const innerValue = firstErrorObj[innerKey];
|
||||
|
||||
if (Array.isArray(innerValue)) {
|
||||
message = innerValue[0]; // "This field may not be blank."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
if (!error.response) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
toast.error(message, {
|
||||
richColors: true,
|
||||
position: 'top-center',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -321,7 +375,7 @@ const RefreshOrder = () => {
|
||||
}));
|
||||
if (user) {
|
||||
const dealTime = formatDate.format(deliveryDate, 'DD.MM.YYYY');
|
||||
|
||||
const note = value.comment.trim() ? value.comment : undefined;
|
||||
mutate({
|
||||
order: [
|
||||
{
|
||||
@@ -335,7 +389,7 @@ const RefreshOrder = () => {
|
||||
person_code: user?.username,
|
||||
currency_code: '860',
|
||||
owner_person_code: user?.username,
|
||||
note: value.comment,
|
||||
note: note,
|
||||
order_products: order_products,
|
||||
},
|
||||
],
|
||||
@@ -354,6 +408,7 @@ const RefreshOrder = () => {
|
||||
);
|
||||
|
||||
const totalItems = orderItems.reduce((sum, item) => sum + item.quantity, 0);
|
||||
const comment = form.watch('comment');
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -373,7 +428,7 @@ const RefreshOrder = () => {
|
||||
<p className="text-gray-500 mb-6">
|
||||
{t("Ushbu buyurtma mavjud emas yoki o'chirilgan")}
|
||||
</p>
|
||||
<Button onClick={() => (window.location.href = '/profile/history')}>
|
||||
<Button onClick={() => (window.location.href = '/profile')}>
|
||||
{t('Buyurtmalar tarixiga qaytish')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -442,15 +497,31 @@ const RefreshOrder = () => {
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
maxLength={300}
|
||||
className="w-full min-h-42 max-h-64 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500"
|
||||
placeholder={t('Izoh')}
|
||||
className={cn(
|
||||
'w-full min-h-42 max-h-64 border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500',
|
||||
comment.length > 300 &&
|
||||
'border-red-500 focus-visible:border-red-500 focus-visible:ring-none focus-visible:outline-0',
|
||||
)}
|
||||
placeholder={t(
|
||||
'Izoh 300 ta belgidan oshmasligi kerak',
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="text-right text-xs text-gray-500">
|
||||
{field.value?.length || 0}/300
|
||||
<div
|
||||
className={cn(
|
||||
'text-right text-xs',
|
||||
comment.length > 300
|
||||
? 'text-red-500'
|
||||
: 'text-gray-500',
|
||||
)}
|
||||
>
|
||||
{comment.length}/300
|
||||
</div>
|
||||
<FormMessage />
|
||||
{comment.length > 300 && (
|
||||
<p className="text-red-500 text-md">
|
||||
{t('Izoh 300 ta belgidan oshmasligi kerak')}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { BASE_URL } from './URLs';
|
||||
|
||||
const httpClient = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 10000,
|
||||
timeout: 50000,
|
||||
});
|
||||
|
||||
httpClient.interceptors.request.use(
|
||||
@@ -45,10 +45,6 @@ httpClient.interceptors.response.use(
|
||||
if ((error as AxiosError)?.status === 403) {
|
||||
try {
|
||||
const refToken = getRefToken();
|
||||
if (!refToken) {
|
||||
removeToken();
|
||||
removeRefToken();
|
||||
}
|
||||
const { data } = await axios.post(
|
||||
`${BASE_URL}api/v1/accounts/refresh/token/`,
|
||||
{ refresh: refToken },
|
||||
@@ -62,10 +58,6 @@ httpClient.interceptors.response.use(
|
||||
} else if ((error as AxiosError)?.status === 401) {
|
||||
try {
|
||||
const refToken = getRefToken();
|
||||
if (!refToken) {
|
||||
removeToken();
|
||||
removeRefToken();
|
||||
}
|
||||
const { data } = await axios.post(
|
||||
`${BASE_URL}api/v1/accounts/refresh/token/`,
|
||||
{ refresh: refToken },
|
||||
|
||||
@@ -55,8 +55,11 @@ export const product_api = {
|
||||
return res;
|
||||
},
|
||||
|
||||
async favouuriteProduct(): Promise<AxiosResponse<FavouriteProduct>> {
|
||||
const res = await httpClient.get(API_URLS.FavouriteProduct);
|
||||
async favouuriteProduct(params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
}): Promise<AxiosResponse<FavouriteProduct>> {
|
||||
const res = await httpClient.get(API_URLS.FavouriteProduct, { params });
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ export interface ProductDetail {
|
||||
litr: null | string;
|
||||
box_type_code: null | string;
|
||||
box_quant: null | string;
|
||||
groups: { id: number; name: string }[];
|
||||
group: { id: number; name: string };
|
||||
state: 'A' | 'P';
|
||||
barcodes: string;
|
||||
article_code: null | string;
|
||||
|
||||
@@ -40,12 +40,10 @@
|
||||
"To'g'ri email manzilini kiriting": "Введите правильный адрес электронной почты",
|
||||
"To'g'ri telefon raqamini kiriting": "Введите правильный номер телефона",
|
||||
"File yuklash majburiy": "Загрузка файла обязательна",
|
||||
|
||||
"Maxfiylik Siyosati": "Политика конфиденциальности",
|
||||
"Gastro Market sizning ma'lumotlaringiz xavfsizligini jiddiy qabul qiladi": "Gastro Market серьезно относится к безопасности ваших данных",
|
||||
"Oxirgi yangilanish: 16 Dekabr 2025": "Последнее обновление: 16 декабря 2025",
|
||||
"Ushbu Maxfiylik Siyosati Gastro Market online magazinida siz tomonidan taqdim etilgan shaxsiy ma'lumotlarni qanday to'playmiz": "Эта Политика конфиденциальности объясняет, как Gastro Market собирает, использует и защищает персональные данные, предоставленные вами в онлайн-магазине. Используя наши услуги, вы соглашаетесь с описанными практиками.",
|
||||
|
||||
"Biz To'playdigan Ma'lumotlar": "1. Сбор информации",
|
||||
"Sizning tajribangizni yaxshilash uchun biz quyidagi ma'lumotlarni to'playmiz": "Имя, адрес электронной почты, номер телефона",
|
||||
"Kompaniya nomi, website, hamkorlik so'rovlari": "Название компании, веб-сайт, запросы на сотрудничество",
|
||||
@@ -55,7 +53,6 @@
|
||||
"Kompaniya Ma'lumotlari": "Информация о компании",
|
||||
"Fayllar:": "Файлы:",
|
||||
"Texnik Ma'lumotlar": "Технические данные",
|
||||
|
||||
"Ma'lumotlardan Foydalanish": "2. Использование данных",
|
||||
"To'plangan ma'lumotlaringizdan quyidagi maqsadlarda foydalanamiz:": "Мы будем использовать собранные вами данные для следующих целей:",
|
||||
"Hamkorlik so'rovlarini qayta ishlash va javob berish": "Обработка и ответ на запросы на сотрудничество",
|
||||
@@ -63,27 +60,23 @@
|
||||
"Sayt xavfsizligini ta'minlash va firibgarlikka qarshi kurashish": "Обеспечение безопасности сайта и предотвращение мошенничества",
|
||||
"Foydalanuvchi tajribasini tahlil qilish va yaxshilash": "Анализ и улучшение пользовательского опыта",
|
||||
"Qonuniy talablarni bajarish": "Соблюдение законных требований",
|
||||
|
||||
"Ma'lumotlar Xavfsizligi": "3. Безопасность данных",
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni himoya qilish uchun zamonaviy xavfsizlik choralarini qo'llaymiz:": "Мы применяем современные меры безопасности для защиты ваших персональных данных:",
|
||||
"SSL/TLS shifrlash orqali ma'lumotlar uzatish": "Передача данных с шифрованием SSL/TLS",
|
||||
"Xavfsiz serverlar va ma'lumotlar bazasida saqlash": "Хранение на безопасных серверах и в базе данных",
|
||||
"Cheklangan kirish huquqlari va autentifikatsiya": "Ограниченный доступ и аутентификация",
|
||||
"Doimiy xavfsizlik monitoringi va yangilanishlar": "Постоянный мониторинг безопасности и обновления",
|
||||
|
||||
"Ma'lumotlarni Ulashish": "4. Раскрытие данных",
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni uchinchi shaxslarga sotmaymiz": "Мы не будем продавать ваши персональные данные третьим лицам.",
|
||||
"Sizning roziligingiz bilan": "С вашего согласия",
|
||||
"Qonuniy talablar bo'yicha": "В соответствии с законом",
|
||||
"Xizmat ko'rsatuvchi ishonchli hamkorlar bilan (maxfiylik shartnomalari ostida)": "С надежными партнерами (соглашения о конфиденциальности)",
|
||||
"Kompaniya birlashuvi yoki sotilishi holatida": "В случае слияния или продажи компании",
|
||||
|
||||
"Biz Bilan Bog'lanish": "Связь с нами",
|
||||
"Ushbu Maxfiylik Siyosati yoki shaxsiy ma'lumotlaringiz haqida savollaringiz bo'lsa, biz bilan bog'laning:": "Если у вас есть вопросы по данной Политике конфиденциальности или вашим данным, свяжитесь с нами:",
|
||||
"Telefon": "Телефон",
|
||||
"Toshkent, O'zbekiston": "Ташкент, Узбекистан",
|
||||
"Manzil": "Адрес",
|
||||
|
||||
"Miqdor": "Количество",
|
||||
"Jami": "Итого",
|
||||
"Bepul yetkazib berish": "Бесплатная доставка",
|
||||
@@ -94,25 +87,21 @@
|
||||
"Ishlab chiqaruvchi": "Производитель",
|
||||
"Hajmi": "Объем",
|
||||
"O'xshash mahsulotlar": "Похожие товары",
|
||||
|
||||
"Hech narsa topilmadi": "Не найдено",
|
||||
"Qidiruv natijalari": "Результаты поиска",
|
||||
"Tavsiya etiladi": "Рекомендуется",
|
||||
"Yuklanmoqda": "Загрузка...",
|
||||
"Natija topilmadi": "Нет результатов",
|
||||
|
||||
"Asosiy": "Основная",
|
||||
"Katalog": "Каталог",
|
||||
"Sevimli": "Избранное",
|
||||
"Savatda": "В корзине",
|
||||
"Profil": "Профиль",
|
||||
|
||||
"Username yoki parol xato kiritildi": "Неверное имя пользователя или пароль",
|
||||
"Tizimga kirish": "Войти в систему",
|
||||
"Username": "Имя пользователя",
|
||||
"Parol": "Пароль",
|
||||
"Kirish": "Вход",
|
||||
|
||||
"Savatingiz bo'sh": "Ваша корзина пуста",
|
||||
"Mahsulotlar qo'shish uchun katalogga o'ting": "Перейти в каталог, чтобы добавить товары",
|
||||
"Xarid qilishni boshlash": "Начать покупку",
|
||||
@@ -153,31 +142,25 @@
|
||||
"Manzilni qidirish": "Поиск адреса",
|
||||
"Toshkent": "Ташкент",
|
||||
"Mening joylashuvim": "Моё местоположение",
|
||||
|
||||
"Yetkazib berish usuli": "Способ доставки",
|
||||
"Standart yetkazib berish": "Стандартная доставка",
|
||||
"2-3 kun ichida": "В течение 2–3 дней",
|
||||
"Tez yetkazib berish": "Экспресс-доставка",
|
||||
"1 kun ichida": "В течение 1 дня",
|
||||
|
||||
"To'lov usuli": "Способ оплаты",
|
||||
"Naqd pul": "Наличные",
|
||||
"Yetkazib berishda to'lash": "Оплата при доставке",
|
||||
"Plastik karta": "Банковская карта",
|
||||
"Online to'lov": "Онлайн-оплата",
|
||||
|
||||
"Mahsulotlar": "Товары",
|
||||
"Buyurtmani tasdiqlash": "Подтвердить заказ",
|
||||
|
||||
"Majburiy maydon": "Обязательное поле",
|
||||
"Xato raqam kiritildi": "Введен неверный номер",
|
||||
"Orqaga": "Назад",
|
||||
|
||||
"Sevimlilar bo'sh": "Избранное пусто",
|
||||
"Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz": "Вы пока не добавили ни одного товара в избранное. Перейдите в каталог и сохраните понравившиеся товары.",
|
||||
"Sevimli mahsulotlar": "Избранные товары",
|
||||
"Ism email manzil telefon raqami": "Имя, адрес электронной почты, номер телефона",
|
||||
|
||||
"Faol buyurtmalar": "Активные заказы",
|
||||
"Barchasi": "Все",
|
||||
"Buyurtmalar tarixi": "История заказов",
|
||||
@@ -186,7 +169,6 @@
|
||||
"Umumiy": "Общее",
|
||||
"Buyurtmalar": "Заказы",
|
||||
"Tarix": "История",
|
||||
|
||||
"Faol": "Активно",
|
||||
"Tugadi": "Заканчивается",
|
||||
"Yetkazish": "Доставка",
|
||||
@@ -200,17 +182,14 @@
|
||||
"Agarda sizda kirish uchun login va parol yo'q bolsa iltimos bizga murojat qiling": "Если у вас нет логина и пароля для входа, пожалуйста, свяжитесь с нами",
|
||||
"Murojat qilish": "Заявление",
|
||||
"So'rov yuborildi!": "Запрос успешно отправлен!",
|
||||
|
||||
"Tez-tez So'raladigan Savollar": "Часто задаваемые вопросы",
|
||||
"Gastro Market haqida eng ko'p so'raladigan savollarga javoblar": "Ответы на самые часто задаваемые вопросы о Gastro Market",
|
||||
|
||||
"Barcha mahsulotlar": "Все товары",
|
||||
"Xatolik yuz berdi: Mahsulot omborxonada yetarli emas": "Произошла ошибка: недостаточно товаров на складе",
|
||||
"Bu kategoriyada hozircha mahsulot yo‘q": "В этой категории пока нет товаров",
|
||||
"Tez orada qo‘shiladi": "Скоро будет добавлено",
|
||||
"Hozirchali bu kategoriyada mahsulot yo'q": "Пока нет товаров в этой категории",
|
||||
"Kataloglar": "Каталог",
|
||||
|
||||
"Naqd bilan olinadi": "Получено наличными",
|
||||
"Pul o'tkazish yo'li bilan olinadi": "Получено путём перевода денег",
|
||||
"Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Внимание! Цена продукта может измениться",
|
||||
@@ -225,5 +204,9 @@
|
||||
"Qayta buyurtma": "Заказать заново",
|
||||
"Yangilangan": "Обновлено",
|
||||
"Narxi o'zgargan bo'lishi mumkin": "Цена может быть изменена",
|
||||
"ga yangilandi": "обновлено"
|
||||
"ga yangilandi": "обновлено",
|
||||
"Izoh 300 ta belgidan oshmasligi kerak": "Комментарий не должен превышать 300 символов",
|
||||
"Xatolik yuz berdi: Foydalanuvchi topilmadi": "Ошибка: Пользователь не найден",
|
||||
"Izoh 300ta belgidan oshib ketdi": "Комментарий превышает 300 символов",
|
||||
"Izoh kamida 1ta belgi bo'lishi kerak": "Должно быть как минимум 1 символ"
|
||||
}
|
||||
@@ -43,12 +43,10 @@ declare const messages: {
|
||||
"To'g'ri email manzilini kiriting": "To'g'ri email manzilini kiriting";
|
||||
"To'g'ri telefon raqamini kiriting": "To'g'ri telefon raqamini kiriting";
|
||||
'File yuklash majburiy': 'File yuklash majburiy';
|
||||
|
||||
'Maxfiylik Siyosati': 'Maxfiylik Siyosati';
|
||||
"Gastro Market sizning ma'lumotlaringiz xavfsizligini jiddiy qabul qiladi": "Gastro Market sizning ma'lumotlaringiz xavfsizligini jiddiy qabul qiladi";
|
||||
'Oxirgi yangilanish: 16 Dekabr 2025': 'Oxirgi yangilanish: 16 Dekabr 2025';
|
||||
"Ushbu Maxfiylik Siyosati Gastro Market online magazinida siz tomonidan taqdim etilgan shaxsiy ma'lumotlarni qanday to'playmiz": "Ushbu Maxfiylik Siyosati Gastro Market online magazinida siz tomonidan taqdim etilgan shaxsiy ma'lumotlarni qanday to'playmiz, ishlatamiz va himoya qilishimizni tushuntiradi. Xizmatlarimizdan foydalanish orqali siz ushbu siyosatda tasvirlangan amaliyotlarga rozilik bildirasiz.";
|
||||
|
||||
"Biz To'playdigan Ma'lumotlar": "1. Biz To'playdigan Ma'lumotlar";
|
||||
"Sizning tajribangizni yaxshilash uchun biz quyidagi ma'lumotlarni to'playmiz": "Sizning tajribangizni yaxshilash uchun biz quyidagi ma'lumotlarni to'playmiz:";
|
||||
'Ism email manzil telefon raqami': 'Ism, email manzil, telefon raqami';
|
||||
@@ -59,7 +57,6 @@ declare const messages: {
|
||||
"Kompaniya Ma'lumotlari": "Kompaniya Ma'lumotlari";
|
||||
'Fayllar:': 'Fayllar:';
|
||||
"Texnik Ma'lumotlar": "Texnik Ma'lumotlar";
|
||||
|
||||
"Ma'lumotlardan Foydalanish": "2. Ma'lumotlardan Foydalanish";
|
||||
"To'plangan ma'lumotlaringizdan quyidagi maqsadlarda foydalanamiz:": "To'plangan ma'lumotlaringizdan quyidagi maqsadlarda foydalanamiz:";
|
||||
"Hamkorlik so'rovlarini qayta ishlash va javob berish": "Hamkorlik so'rovlarini qayta ishlash va javob berish";
|
||||
@@ -67,27 +64,23 @@ declare const messages: {
|
||||
"Sayt xavfsizligini ta'minlash va firibgarlikka qarshi kurashish": "Sayt xavfsizligini ta'minlash va firibgarlikka qarshi kurashish";
|
||||
'Foydalanuvchi tajribasini tahlil qilish va yaxshilash': 'Foydalanuvchi tajribasini tahlil qilish va yaxshilash';
|
||||
'Qonuniy talablarni bajarish': 'Qonuniy talablarni bajarish';
|
||||
|
||||
"Ma'lumotlar Xavfsizligi": "3. Ma'lumotlar Xavfsizligi";
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni himoya qilish uchun zamonaviy xavfsizlik choralarini qo'llaymiz:": "Biz sizning shaxsiy ma'lumotlaringizni himoya qilish uchun zamonaviy xavfsizlik choralarini qo'llaymiz:";
|
||||
"SSL/TLS shifrlash orqali ma'lumotlar uzatish": "SSL/TLS shifrlash orqali ma'lumotlar uzatish";
|
||||
"Xavfsiz serverlar va ma'lumotlar bazasida saqlash": "Xavfsiz serverlar va ma'lumotlar bazasida saqlash";
|
||||
'Cheklangan kirish huquqlari va autentifikatsiya': 'Cheklangan kirish huquqlari va autentifikatsiya';
|
||||
'Doimiy xavfsizlik monitoringi va yangilanishlar': 'Doimiy xavfsizlik monitoringi va yangilanishlar';
|
||||
|
||||
"Ma'lumotlarni Ulashish": "4. Ma'lumotlarni Ulashish";
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni uchinchi shaxslarga sotmaymiz": "Biz sizning shaxsiy ma'lumotlaringizni uchinchi shaxslarga sotmaymiz";
|
||||
'Sizning roziligingiz bilan': 'Sizning roziligingiz bilan';
|
||||
"Qonuniy talablar bo'yicha": "Qonuniy talablar bo'yicha";
|
||||
"Xizmat ko'rsatuvchi ishonchli hamkorlar bilan (maxfiylik shartnomalari ostida)": "Xizmat ko'rsatuvchi ishonchli hamkorlar bilan (maxfiylik shartnomalari ostida)";
|
||||
'Kompaniya birlashuvi yoki sotilishi holatida': 'Kompaniya birlashuvi yoki sotilishi holatida';
|
||||
|
||||
"Biz Bilan Bog'lanish": "Biz Bilan Bog'lanish";
|
||||
"Ushbu Maxfiylik Siyosati yoki shaxsiy ma'lumotlaringiz haqida savollaringiz bo'lsa, biz bilan bog'laning:": "Ushbu Maxfiylik Siyosati yoki shaxsiy ma'lumotlaringiz haqida savollaringiz bo'lsa, biz bilan bog'laning:";
|
||||
Telefon: 'Telefon';
|
||||
"Toshkent, O'zbekiston": "Toshkent, O'zbekiston";
|
||||
Manzil: 'Manzil';
|
||||
|
||||
Miqdor: 'Miqdor';
|
||||
Jami: 'Jami';
|
||||
'Bepul yetkazib berish': 'Bepul yetkazib berish';
|
||||
@@ -98,25 +91,21 @@ declare const messages: {
|
||||
'Ishlab chiqaruvchi': 'Ishlab chiqaruvchi';
|
||||
Hajmi: 'Hajmi';
|
||||
"O'xshash mahsulotlar": "O'xshash mahsulotlar";
|
||||
|
||||
'Hech narsa topilmadi': 'Hech narsa topilmadi';
|
||||
'Qidiruv natijalari': 'Qidiruv natijalari';
|
||||
'Tavsiya etiladi': 'Tavsiya etiladi';
|
||||
Yuklanmoqda: 'Yuklanmoqda....';
|
||||
'Natija topilmadi': 'Natija topilmadi';
|
||||
|
||||
Asosiy: 'Asosiy';
|
||||
Katalog: 'Katalog';
|
||||
Sevimli: 'Sevimli';
|
||||
Savatda: 'Savatda';
|
||||
Profil: 'Profil';
|
||||
|
||||
'Username yoki parol xato kiritildi': 'Username yoki parol xato kiritildi';
|
||||
'Tizimga kirish': 'Tizimga kirish';
|
||||
Username: 'Username';
|
||||
Parol: 'Parol';
|
||||
Kirish: 'Kirish';
|
||||
|
||||
"Savatingiz bo'sh": "Savatingiz bo'sh";
|
||||
"Mahsulotlar qo'shish uchun katalogga o'ting": "Mahsulotlar qo'shish uchun katalogga o'ting";
|
||||
'Xarid qilishni boshlash': 'Xarid qilishni boshlash';
|
||||
@@ -131,7 +120,6 @@ declare const messages: {
|
||||
'Xaridni davom ettirish': 'Xaridni davom ettirish';
|
||||
'Tez yetkazib berish 1-2 kun ichida': 'Tez yetkazib berish 1-2 kun ichida';
|
||||
"Xavfsiz to'lov usullari": "Xavfsiz to'lov usullari";
|
||||
|
||||
'Buyurtma qabul qilindi!': 'Buyurtma qabul qilindi!';
|
||||
'Buyurtma raqami': 'Buyurtma raqami';
|
||||
'Buyurtmangiz muvaffaqiyatli qabul qilindi': 'Buyurtmangiz muvaffaqiyatli qabul qilindi.';
|
||||
@@ -176,7 +164,6 @@ declare const messages: {
|
||||
"Sevimlilar bo'sh": "Sevimlilar bo'sh";
|
||||
"Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz": "Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz. Mahsulotlar ro'yxatiga o'ting va yoqqan mahsulotlaringizni saqlang.";
|
||||
'Sevimli mahsulotlar': 'Sevimli mahsulotlar';
|
||||
|
||||
'Faol buyurtmalar': 'Faol buyurtmalar';
|
||||
Barchasi: 'Barchasi';
|
||||
'Buyurtmalar tarixi': 'Buyurtmalar tarixi';
|
||||
@@ -185,7 +172,6 @@ declare const messages: {
|
||||
Umumiy: 'Umumiy';
|
||||
Buyurtmalar: 'Buyurtmalar';
|
||||
Tarix: 'Tarix';
|
||||
|
||||
Faol: 'Faol';
|
||||
Tugadi: 'Tugadi';
|
||||
Yetkazish: 'Yetkazish';
|
||||
@@ -199,10 +185,8 @@ declare const messages: {
|
||||
"Agarda sizda kirish uchun login va parol yo'q bolsa iltimos bizga murojat qiling": "Agarda sizda kirish uchun login va parol yo'q bolsa iltimos bizga murojat qiling";
|
||||
'Murojat qilish': 'Murojat qilish';
|
||||
"So'rov yuborildi!": "So'rov yuborildi!";
|
||||
|
||||
"Tez-tez So'raladigan Savollar": "Tez-tez So'raladigan Savollar";
|
||||
"Gastro Market haqida eng ko'p so'raladigan savollarga javoblar": "Gastro Market haqida eng ko'p so'raladigan savollarga javoblar";
|
||||
|
||||
'Barcha mahsulotlar': 'Barcha mahsulotlar';
|
||||
'Xatolik yuz berdi: Mahsulot omborxonada yetarli emas': 'Xatolik yuz berdi: Mahsulot omborxonada yetarli emas';
|
||||
'Bu kategoriyada hozircha mahsulot yo‘q': 'Bu kategoriyada hozircha mahsulot yo‘q';
|
||||
@@ -211,7 +195,6 @@ declare const messages: {
|
||||
Kataloglar: 'Kataloglar';
|
||||
'Naqd bilan olinadi': 'Naqd bilan olinadi';
|
||||
"Pul o'tkazish yo'li bilan olinadi": "Pul o'tkazish yo'li bilan olinadi";
|
||||
|
||||
"Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin";
|
||||
"So'nggi yangilanish:": "So'nggi yangilanish";
|
||||
only_available: 'Faqat {maxBalance} dona mavjud';
|
||||
@@ -225,5 +208,9 @@ declare const messages: {
|
||||
Yangilangan: 'Yangilangan';
|
||||
"Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin";
|
||||
'ga yangilandi': 'ga yangilandi';
|
||||
'Izoh 300 ta belgidan oshmasligi kerak': 'Izoh 300 ta belgidan oshmasligi kerak';
|
||||
'Xatolik yuz berdi: Foydalanuvchi topilmadi': 'Xatolik yuz berdi: Foydalanuvchi topilmadi';
|
||||
'Izoh 300ta belgidan oshib ketdi': 'Izoh 300ta belgidan oshib ketdi';
|
||||
"Izoh kamida 1ta belgi bo'lishi kerak": "Izoh kamida 1ta belgi bo'lishi kerak";
|
||||
};
|
||||
export default messages;
|
||||
|
||||
@@ -40,12 +40,10 @@
|
||||
"To'g'ri email manzilini kiriting": "To'g'ri email manzilini kiriting",
|
||||
"To'g'ri telefon raqamini kiriting": "To'g'ri telefon raqamini kiriting",
|
||||
"File yuklash majburiy": "File yuklash majburiy",
|
||||
|
||||
"Maxfiylik Siyosati": "Maxfiylik Siyosati",
|
||||
"Gastro Market sizning ma'lumotlaringiz xavfsizligini jiddiy qabul qiladi": "Gastro Market sizning ma'lumotlaringiz xavfsizligini jiddiy qabul qiladi",
|
||||
"Oxirgi yangilanish: 16 Dekabr 2025": "Oxirgi yangilanish: 16 Dekabr 2025",
|
||||
"Ushbu Maxfiylik Siyosati Gastro Market online magazinida siz tomonidan taqdim etilgan shaxsiy ma'lumotlarni qanday to'playmiz": "Ushbu Maxfiylik Siyosati Gastro Market online magazinida siz tomonidan taqdim etilgan shaxsiy ma'lumotlarni qanday to'playmiz, ishlatamiz va himoya qilishimizni tushuntiradi. Xizmatlarimizdan foydalanish orqali siz ushbu siyosatda tasvirlangan amaliyotlarga rozilik bildirasiz.",
|
||||
|
||||
"Biz To'playdigan Ma'lumotlar": "1. Biz To'playdigan Ma'lumotlar",
|
||||
"Sizning tajribangizni yaxshilash uchun biz quyidagi ma'lumotlarni to'playmiz": "Sizning tajribangizni yaxshilash uchun biz quyidagi ma'lumotlarni to'playmiz:",
|
||||
"Ism email manzil telefon raqami": "Ism, email manzil, telefon raqami",
|
||||
@@ -56,7 +54,6 @@
|
||||
"Kompaniya Ma'lumotlari": "Kompaniya Ma'lumotlari",
|
||||
"Fayllar:": "Fayllar:",
|
||||
"Texnik Ma'lumotlar": "Texnik Ma'lumotlar",
|
||||
|
||||
"Ma'lumotlardan Foydalanish": "2. Ma'lumotlardan Foydalanish",
|
||||
"To'plangan ma'lumotlaringizdan quyidagi maqsadlarda foydalanamiz:": "To'plangan ma'lumotlaringizdan quyidagi maqsadlarda foydalanamiz:",
|
||||
"Hamkorlik so'rovlarini qayta ishlash va javob berish": "Hamkorlik so'rovlarini qayta ishlash va javob berish",
|
||||
@@ -64,27 +61,23 @@
|
||||
"Sayt xavfsizligini ta'minlash va firibgarlikka qarshi kurashish": "Sayt xavfsizligini ta'minlash va firibgarlikka qarshi kurashish",
|
||||
"Foydalanuvchi tajribasini tahlil qilish va yaxshilash": "Foydalanuvchi tajribasini tahlil qilish va yaxshilash",
|
||||
"Qonuniy talablarni bajarish": "Qonuniy talablarni bajarish",
|
||||
|
||||
"Ma'lumotlar Xavfsizligi": "3. Ma'lumotlar Xavfsizligi",
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni himoya qilish uchun zamonaviy xavfsizlik choralarini qo'llaymiz:": "Biz sizning shaxsiy ma'lumotlaringizni himoya qilish uchun zamonaviy xavfsizlik choralarini qo'llaymiz:",
|
||||
"SSL/TLS shifrlash orqali ma'lumotlar uzatish": "SSL/TLS shifrlash orqali ma'lumotlar uzatish",
|
||||
"Xavfsiz serverlar va ma'lumotlar bazasida saqlash": "Xavfsiz serverlar va ma'lumotlar bazasida saqlash",
|
||||
"Cheklangan kirish huquqlari va autentifikatsiya": "Cheklangan kirish huquqlari va autentifikatsiya",
|
||||
"Doimiy xavfsizlik monitoringi va yangilanishlar": "Doimiy xavfsizlik monitoringi va yangilanishlar",
|
||||
|
||||
"Ma'lumotlarni Ulashish": "4. Ma'lumotlarni Ulashish",
|
||||
"Biz sizning shaxsiy ma'lumotlaringizni uchinchi shaxslarga sotmaymiz": "Biz sizning shaxsiy ma'lumotlaringizni uchinchi shaxslarga sotmaymiz",
|
||||
"Sizning roziligingiz bilan": "Sizning roziligingiz bilan",
|
||||
"Qonuniy talablar bo'yicha": "Qonuniy talablar bo'yicha",
|
||||
"Xizmat ko'rsatuvchi ishonchli hamkorlar bilan (maxfiylik shartnomalari ostida)": "Xizmat ko'rsatuvchi ishonchli hamkorlar bilan (maxfiylik shartnomalari ostida)",
|
||||
"Kompaniya birlashuvi yoki sotilishi holatida": "Kompaniya birlashuvi yoki sotilishi holatida",
|
||||
|
||||
"Biz Bilan Bog'lanish": "Biz Bilan Bog'lanish",
|
||||
"Ushbu Maxfiylik Siyosati yoki shaxsiy ma'lumotlaringiz haqida savollaringiz bo'lsa, biz bilan bog'laning:": "Ushbu Maxfiylik Siyosati yoki shaxsiy ma'lumotlaringiz haqida savollaringiz bo'lsa, biz bilan bog'laning:",
|
||||
"Telefon": "Telefon",
|
||||
"Toshkent, O'zbekiston": "Toshkent, O'zbekiston",
|
||||
"Manzil": "Manzil",
|
||||
|
||||
"Miqdor": "Miqdor",
|
||||
"Jami": "Jami",
|
||||
"Bepul yetkazib berish": "Bepul yetkazib berish",
|
||||
@@ -95,25 +88,21 @@
|
||||
"Ishlab chiqaruvchi": "Ishlab chiqaruvchi",
|
||||
"Hajmi": "Hajmi",
|
||||
"O'xshash mahsulotlar": "O'xshash mahsulotlar",
|
||||
|
||||
"Hech narsa topilmadi": "Hech narsa topilmadi",
|
||||
"Qidiruv natijalari": "Qidiruv natijalari",
|
||||
"Tavsiya etiladi": "Tavsiya etiladi",
|
||||
"Yuklanmoqda": "Yuklanmoqda....",
|
||||
"Natija topilmadi": "Natija topilmadi",
|
||||
|
||||
"Asosiy": "Asosiy",
|
||||
"Katalog": "Katalog",
|
||||
"Sevimli": "Sevimli",
|
||||
"Savatda": "Savatda",
|
||||
"Profil": "Profil",
|
||||
|
||||
"Username yoki parol xato kiritildi": "Username yoki parol xato kiritildi",
|
||||
"Tizimga kirish": "Tizimga kirish",
|
||||
"Username": "Username",
|
||||
"Parol": "Parol",
|
||||
"Kirish": "Kirish",
|
||||
|
||||
"Savatingiz bo'sh": "Savatingiz bo'sh",
|
||||
"Mahsulotlar qo'shish uchun katalogga o'ting": "Mahsulotlar qo'shish uchun katalogga o'ting",
|
||||
"Xarid qilishni boshlash": "Xarid qilishni boshlash",
|
||||
@@ -128,7 +117,6 @@
|
||||
"Xaridni davom ettirish": "Xaridni davom ettirish",
|
||||
"Tez yetkazib berish 1-2 kun ichida": "Tez yetkazib berish 1-2 kun ichida",
|
||||
"Xavfsiz to'lov usullari": "Xavfsiz to'lov usullari",
|
||||
|
||||
"Buyurtma qabul qilindi!": "Buyurtma qabul qilindi!",
|
||||
"Buyurtma raqami": "Buyurtma raqami",
|
||||
"Buyurtmangiz muvaffaqiyatli qabul qilindi": "Buyurtmangiz muvaffaqiyatli qabul qilindi.",
|
||||
@@ -173,7 +161,6 @@
|
||||
"Sevimlilar bo'sh": "Sevimlilar bo'sh",
|
||||
"Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz": "Hali hech qanday mahsulotni sevimlilarga qo'shmadingiz. Mahsulotlar ro'yxatiga o'ting va yoqqan mahsulotlaringizni saqlang.",
|
||||
"Sevimli mahsulotlar": "Sevimli mahsulotlar",
|
||||
|
||||
"Faol buyurtmalar": "Faol buyurtmalar",
|
||||
"Barchasi": "Barchasi",
|
||||
"Buyurtmalar tarixi": "Buyurtmalar tarixi",
|
||||
@@ -182,7 +169,6 @@
|
||||
"Umumiy": "Umumiy",
|
||||
"Buyurtmalar": "Buyurtmalar",
|
||||
"Tarix": "Tarix",
|
||||
|
||||
"Faol": "Faol",
|
||||
"Tugadi": "Tugadi",
|
||||
"Yetkazish": "Yetkazish",
|
||||
@@ -196,10 +182,8 @@
|
||||
"Agarda sizda kirish uchun login va parol yo'q bolsa iltimos bizga murojat qiling": "Agarda sizda kirish uchun login va parol yo'q bolsa iltimos bizga murojat qiling",
|
||||
"Murojat qilish": "Murojat qilish",
|
||||
"So'rov yuborildi!": "So'rov yuborildi!",
|
||||
|
||||
"Tez-tez So'raladigan Savollar": "Tez-tez So'raladigan Savollar",
|
||||
"Gastro Market haqida eng ko'p so'raladigan savollarga javoblar": "Gastro Market haqida eng ko'p so'raladigan savollarga javoblar",
|
||||
|
||||
"Barcha mahsulotlar": "Barcha mahsulotlar",
|
||||
"Xatolik yuz berdi: Mahsulot omborxonada yetarli emas": "Xatolik yuz berdi: Mahsulot omborxonada yetarli emas",
|
||||
"Bu kategoriyada hozircha mahsulot yo‘q": "Bu kategoriyada hozircha mahsulot yo‘q",
|
||||
@@ -208,7 +192,6 @@
|
||||
"Kataloglar": "Kataloglar",
|
||||
"Naqd bilan olinadi": "Naqd bilan olinadi",
|
||||
"Pul o'tkazish yo'li bilan olinadi": "Pul o'tkazish yo'li bilan olinadi",
|
||||
|
||||
"Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin": "Diqqat! Mahsulot narxi o'zgargan bo'lishi mumkin",
|
||||
"So'nggi yangilanish:": "So'nggi yangilanish",
|
||||
"only_available": "Faqat {maxBalance} dona mavjud",
|
||||
@@ -221,5 +204,9 @@
|
||||
"Qayta buyurtma": "Qayta buyurtma",
|
||||
"Yangilangan": "Yangilangan",
|
||||
"Narxi o'zgargan bo'lishi mumkin": "Narxi o'zgargan bo'lishi mumkin",
|
||||
"ga yangilandi": "ga yangilandi"
|
||||
"ga yangilandi": "ga yangilandi",
|
||||
"Izoh 300 ta belgidan oshmasligi kerak": "Izoh 300 ta belgidan oshmasligi kerak",
|
||||
"Xatolik yuz berdi: Foydalanuvchi topilmadi": "Xatolik yuz berdi: Foydalanuvchi topilmadi",
|
||||
"Izoh 300ta belgidan oshib ketdi": "Izoh 300ta belgidan oshib ketdi",
|
||||
"Izoh kamida 1ta belgi bo'lishi kerak": "Izoh kamida 1ta belgi bo'lishi kerak"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'border-input placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -173,7 +173,8 @@ export function ProductCard({
|
||||
mutationFn: (productId: string) => product_api.favourite(productId),
|
||||
|
||||
onSuccess: () => {
|
||||
queryClient.refetchQueries({ queryKey: ['product_list'] });
|
||||
queryClient.refetchQueries({ queryKey: ['list'] });
|
||||
queryClient.refetchQueries({ queryKey: ['all_products'] });
|
||||
queryClient.refetchQueries({ queryKey: ['favourite_product'] });
|
||||
},
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
} from '@/shared/ui/dropdown-menu';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation';
|
||||
import {
|
||||
useParams,
|
||||
usePathname,
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
} from 'next/navigation';
|
||||
import { languages } from '../lib/data';
|
||||
|
||||
export function ChangeLang() {
|
||||
@@ -18,12 +23,16 @@ export function ChangeLang() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const changeLocale = (locale: LanguageRoutes) => {
|
||||
const segments = pathname.split('/');
|
||||
segments[1] = locale;
|
||||
|
||||
const newPath = segments.join('/');
|
||||
router.push(newPath);
|
||||
const query = searchParams.toString();
|
||||
|
||||
router.push(query ? `${newPath}?${query}` : newPath);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user