Compare commits

...

11 Commits

Author SHA1 Message Date
Samandar Turgunboyev
345d8de80a Added a back button to the Product detail page 2026-03-28 16:47:01 +05:00
Samandar Turgunboyev
0c7a5127d0 comment update 2026-03-19 14:41:38 +05:00
Samandar Turgunboyev
1851327f8a error update 2026-03-19 09:31:09 +05:00
Samandar Turgunboyev
48ea82de0f comment lenth katta bolishi kerak 0dan 2026-03-18 22:19:53 +05:00
Samandar Turgunboyev
05c15a4f70 favouuriteProduct added page and page size 2026-03-16 16:11:58 +05:00
Samandar Turgunboyev
3d1d9d4860 favourite list update page 2026-03-16 16:10:25 +05:00
Samandar Turgunboyev
26c933d196 api timeout 5000 2026-03-13 21:21:50 +05:00
Samandar Turgunboyev
a842449e4d product detail fixed 2026-03-12 10:24:34 +05:00
Samandar Turgunboyev
bd346aacf3 added validation order 2026-03-11 20:06:14 +05:00
Samandar Turgunboyev
5d81f857da validation added 2026-03-11 20:01:52 +05:00
Samandar Turgunboyev
2ff4de4912 error 2026-03-11 19:42:19 +05:00
17 changed files with 402 additions and 183 deletions

View File

@@ -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') {

View File

@@ -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;

View File

@@ -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(),
});

View File

@@ -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"

View File

@@ -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 }) => (
<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')}
/>
</FormControl>
<div className="text-right text-xs text-gray-500">
{field.value?.length || 0}/300
</div>
<FormMessage />
</FormItem>
)}
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}
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={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>

View File

@@ -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({
queryKey: ['favourite_product'],
queryFn: () => product_api.favouuriteProduct(),
select(data) {
return data.data;
},
});
const loadMoreRef = useRef<HTMLDivElement>(null);
if (favourite && favourite.results.length === 0) {
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['favourite_product'],
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.pages.flatMap((page) => page.data.results);
},
});
// 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')}
</p>
</div>
</div>
<div className="mb-8">
<h1 className="text-3xl font-bold text-slate-800 mb-2">
{t('Sevimli mahsulotlar')}
</h1>
<p className="text-slate-500">
{favourite.length} {t('ta mahsulot')}
</p>
</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
.filter((product) => product.state === 'A')
.map((product) => (
<ProductCard key={product.id} product={product} />
))}
<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>
);
}

View File

@@ -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>
);
};

View File

@@ -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,13 +136,57 @@ const RefreshOrder = () => {
const { mutate, isPending } = useMutation({
mutationFn: (body: OrderCreateBody) => cart_api.createOrder(body),
onSuccess: () => {
setOrderSuccess(true);
queryClient.refetchQueries({ queryKey: ['cart_items'] });
queryClient.refetchQueries({ queryKey: ['order_list'] });
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'] });
} 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: () => {
toast.error(t('Xatolik yuz berdi'), {
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>
)}
/>

View File

@@ -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 },

View File

@@ -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;
},
};

View File

@@ -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;

View File

@@ -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": "В течение 23 дней",
"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 yoq": "В этой категории пока нет товаров",
"Tez orada qoshiladi": "Скоро будет добавлено",
"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 символ"
}

View File

@@ -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 yoq': 'Bu kategoriyada hozircha mahsulot yoq';
@@ -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;

View File

@@ -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 yoq": "Bu kategoriyada hozircha mahsulot yoq",
@@ -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"
}

View File

@@ -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}

View File

@@ -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'] });
},

View File

@@ -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 (