refactored some code

This commit is contained in:
Jelaletdin12
2025-12-24 22:04:44 +05:00
parent 342fb31906
commit 7538bdb813
12 changed files with 197 additions and 111 deletions

View File

@@ -2,12 +2,10 @@
2. Harytlar kem kas bolanu ucin home page doly gorkezenok
3. Filter nahili isleyar
4. Order nadip otmen etmeli.
5. Review feed back yazylyan yer bamy bolmalymy
6. Open Store api field ler nahili bolmaly.
7. Delivery type soramaly, type lar yok

View File

@@ -7,6 +7,8 @@ import ProductCard from "@/features/home/components/ProductCard";
import type { Favorite } from "@/lib/types/api";
import EmptyFavorites from "@/features/favorites/components/EmptyFavorites";
import ErrorPage from "@/components/ErrorPage";
import Placeholder from "@/public/logo.webp";
export default function FavoritesPage() {
const t = useTranslations();
const { data: favorites, isLoading, isError } = useFavorites();
@@ -58,7 +60,7 @@ export default function FavoritesPage() {
media.images_400x400 ||
media.thumbnail
)
.filter(Boolean) || ["/placeholder-product.jpg"];
.filter(Boolean) || [Placeholder];
const formattedPrice = product.price_amount
? `${parseFloat(product.price_amount).toFixed(2)} TMT`

View File

@@ -176,7 +176,7 @@ export default function OpenStorePage({
<form onSubmit={handleSubmit} className="space-y-4">
{/* First Name */}
<div className="space-y-2">
<Label htmlFor="firstName">{t("firstName")}</Label>
<Label htmlFor="firstName">{t("enter_first_name")}</Label>
<Input
id="firstName"
name="firstName"
@@ -191,7 +191,7 @@ export default function OpenStorePage({
{/* Last Name */}
<div className="space-y-2">
<Label htmlFor="lastName">{t("lastName")}</Label>
<Label htmlFor="lastName">{t("enter_last_name")}</Label>
<Input
id="lastName"
name="lastName"
@@ -206,7 +206,7 @@ export default function OpenStorePage({
{/* Email */}
<div className="space-y-2">
<Label htmlFor="email">{t("email")}</Label>
<Label htmlFor="email">{t("enter_email")}</Label>
<Input
id="email"
name="email"
@@ -222,7 +222,7 @@ export default function OpenStorePage({
{/* Phone */}
<div className="space-y-2">
<Label htmlFor="phone">{t("phone")}</Label>
<Label htmlFor="phone">{t("enter_phone")}</Label>
<Input
id="phone"
name="phone"
@@ -273,7 +273,7 @@ export default function OpenStorePage({
className="w-full cursor-pointer bg-[#005bff] hover:bg-[#0041c4]"
disabled={loading}
>
{loading ? "Загрузка..." : t("submit")}
{loading ? t("submitting") : t("submit")}
</Button>
</form>
</CardContent>

View File

@@ -1,9 +1,10 @@
// Header.tsx
"use client";
import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import Image from "next/image";
import { X, Search, User as UserIcon } from "lucide-react";
import { X, Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import Logo from "@/public/logo.webp";
import CategoryMenu from "./ui/CategoryMenu";
@@ -69,6 +70,7 @@ export default function Header({ locale = "ru" }: HeaderProps) {
</Link>
<Button
data-catalog-trigger
onClick={toggleCategoryMenu}
className="cursor-pointer hidden gap-2 rounded-lg font-bold lg:flex hover:bg-[#005bff] bg-[#005bff] text-white"
size="lg"

View File

@@ -68,7 +68,12 @@ const cartCount = useCartCount()
}, [ordersData]);
const handleLogout = () => {
logout();
logout(undefined, {
onSuccess: () => {
router.push(`/${locale}`);
router.refresh();
}
});
};
const buttons: ActionButtonData[] = useMemo(

View File

@@ -1,28 +1,79 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { useCategories } from "@/lib/hooks"
import { Skeleton } from "@/components/ui/skeleton"
// CategoryMenu.tsx
"use client";
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
import { useCategories } from "@/lib/hooks";
import { Skeleton } from "@/components/ui/skeleton";
interface CategoryMenuProps {
isOpen: boolean
onClose: () => void
isOpen: boolean;
onClose: () => void;
}
export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) {
const [hoveredCategory, setHoveredCategory] = useState<number | null>(null)
const { data: categories, isLoading } = useCategories()
const [hoveredCategory, setHoveredCategory] = useState<number | null>(null);
const { data: categories, isLoading } = useCategories();
const menuRef = useRef<HTMLDivElement>(null);
if (!isOpen) return null
// Click outside to close
useEffect(() => {
if (!isOpen) return;
const categoryList = categories || []
const activeCategory = hoveredCategory !== null ? categoryList[hoveredCategory] : null
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (target.closest("[data-catalog-trigger]")) {
return;
}
if (menuRef.current && !menuRef.current.contains(target)) {
onClose();
}
};
// Add listener after a small delay to prevent immediate closing
const timeoutId = setTimeout(() => {
document.addEventListener("mousedown", handleClickOutside);
}, 100);
return () => {
clearTimeout(timeoutId);
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen, onClose]);
// ESC key to close
useEffect(() => {
if (!isOpen) return;
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
onClose();
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [isOpen, onClose]);
if (!isOpen) return null;
const categoryList = categories || [];
const activeCategory =
hoveredCategory !== null ? categoryList[hoveredCategory] : null;
return (
<div className="fixed left-0 right-0 top-15 z-40 bg-white border-b shadow-lg max-w-[1504px] mx-auto">
<>
<div className="fixed inset-0 bg-black/20 z-30" onClick={onClose} />
{/* Menu */}
<div
ref={menuRef}
className="fixed left-0 right-0 top-16 z-40 bg-white border-b rounded-b-lg shadow-lg max-w-[1504px] mx-auto"
>
<div className="mx-auto px-4">
<div className="flex">
<CategoryList
@@ -32,49 +83,67 @@ export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) {
onCategoryClick={onClose}
/>
{activeCategory?.children && <SubcategoryList category={activeCategory} onSubcategoryClick={onClose} />}
{activeCategory?.children && (
<SubcategoryList
category={activeCategory}
onSubcategoryClick={onClose}
/>
)}
</div>
</div>
</div>
)
</>
);
}
interface CategoryListProps {
categories: any[]
isLoading: boolean
onCategoryHover: (index: number) => void
onCategoryClick: () => void
categories: any[];
isLoading: boolean;
onCategoryHover: (index: number) => void;
onCategoryClick: () => void;
}
function CategoryList({ categories, isLoading, onCategoryHover, onCategoryClick }: CategoryListProps) {
function CategoryList({
categories,
isLoading,
onCategoryHover,
onCategoryClick,
}: CategoryListProps) {
return (
<div className="w-[280px] border-r">
<div className="max-h-[calc(100vh-4rem)] overflow-y-auto py-2">
{isLoading
? [1, 2, 3, 4, 5].map((i) => <Skeleton key={i} className="h-10 mx-4 my-2 rounded" />)
? [1, 2, 3, 4, 5].map((i) => (
<Skeleton key={i} className="h-10 mx-4 my-2 rounded" />
))
: categories.map((category, index) => (
<Link
key={category.id}
href={`/category/${category.slug}?category_id=${category.id}`}
onClick={onCategoryClick}
onMouseEnter={() => onCategoryHover(index)}
className="flex items-center gap-3 px-4 py-3 rounded-xl hover:bg-gray-100 hover:text-primary transition-colors"
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-gray-100 hover:text-primary transition-colors"
>
{category.icon_class && <i className={`${category.icon_class} text-xl`}></i>}
{category.icon_class && (
<i className={`${category.icon_class} text-xl`} />
)}
<span>{category.name}</span>
</Link>
))}
</div>
</div>
)
);
}
interface SubcategoryListProps {
category: any
onSubcategoryClick: () => void
category: any;
onSubcategoryClick: () => void;
}
function SubcategoryList({ category, onSubcategoryClick }: SubcategoryListProps) {
function SubcategoryList({
category,
onSubcategoryClick,
}: SubcategoryListProps) {
return (
<div className="flex-1 p-6">
<h3 className="text-xl font-semibold mb-4">{category.name}</h3>
@@ -91,5 +160,5 @@ function SubcategoryList({ category, onSubcategoryClick }: SubcategoryListProps)
))}
</div>
</div>
)
);
}

View File

@@ -3,7 +3,7 @@
"use client";
import { useEffect, type ReactNode } from "react";
import { useRouter, usePathname } from "next/navigation";
import { useRouter } from "next/navigation";
import { useAuthStatus, useGetGuestToken } from "@/lib/hooks/useAuth";
import { useUserProfile } from "@/features/profile/hooks/useUserProfile";
import Preloader from "@/components/PageLoader/PreLoader";
@@ -23,14 +23,12 @@ export default function AuthWrapper({
locale,
}: AuthWrapperProps) {
const router = useRouter();
const pathname = usePathname();
const { isAuthenticated, isLoading } = useAuthStatus();
const { mutate: getGuestToken, isPending: isGettingGuestToken } = useGetGuestToken();
const { mutate: getGuestToken, isPending: isGettingGuestToken } =
useGetGuestToken();
// Fetch user profile only if authenticated
useUserProfile();
// Initialize guest token if needed
useEffect(() => {
if (isLoading) return;
@@ -39,23 +37,20 @@ export default function AuthWrapper({
}
}, [isLoading, getGuestToken, isGettingGuestToken]);
// Handle redirects
useEffect(() => {
if (isLoading || isGettingGuestToken) return;
// Redirect to login if auth required but not authenticated
if (requireAuth && !isAuthenticated) {
const redirect = redirectTo || `/${locale}/login`;
const returnUrl = pathname !== redirect ? `?returnUrl=${encodeURIComponent(pathname)}` : "";
router.push(`${redirect}${returnUrl}`);
router.push(`/${locale}`);
return;
}
if (isAuthenticated && (pathname.includes("/login") || pathname.includes("/register"))) {
router.push(`/${locale}`);
}
}, [isAuthenticated, isLoading, requireAuth, pathname, router, locale, redirectTo, isGettingGuestToken]);
}, [
isAuthenticated,
isLoading,
requireAuth,
router,
locale,
isGettingGuestToken,
]);
if (isLoading || (requireAuth && !isAuthenticated)) {
return <Preloader />;
}

View File

@@ -1,14 +1,15 @@
"use client"
import Image, { type StaticImageData } from "next/image"
import { Swiper, SwiperSlide } from "swiper/react"
import { Autoplay } from "swiper/modules"
import "swiper/css"
"use client";
import Image, { type StaticImageData } from "next/image";
import Link from "next/link";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay } from "swiper/modules";
import "swiper/css";
type CarouselItem = {
title: string
image: StaticImageData | string
url?: string | null
}
title: string;
image: StaticImageData | string;
url?: string | null;
};
export default function HeroCarousel({ items }: { items: CarouselItem[] }) {
return (
@@ -21,6 +22,20 @@ export default function HeroCarousel({ items }: { items: CarouselItem[] }) {
>
{items.map((item, i) => (
<SwiperSlide key={i}>
{item.url ? (
<Link
href={item.url}
className="block relative w-full h-[200px] sm:h-[300px] md:h-[496px]"
>
<Image
src={item.image}
alt={item.title}
fill
className="object-cover"
priority={i === 0}
/>
</Link>
) : (
<div className="relative w-full h-[200px] sm:h-[300px] md:h-[496px]">
<Image
src={item.image}
@@ -30,9 +45,10 @@ export default function HeroCarousel({ items }: { items: CarouselItem[] }) {
priority={i === 0}
/>
</div>
)}
</SwiperSlide>
))}
</Swiper>
</section>
)
);
}

View File

@@ -347,7 +347,7 @@ export default function ProductCard({
{isOutOfStock && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center z-10">
<Badge variant="secondary" className="text-sm font-bold">
Out of Stock
{t("outOfStock")}
</Badge>
</div>
)}

View File

@@ -171,6 +171,7 @@
"share_experience": "Поделитесь опытом с этим товаром",
"rating": "Рейтинг",
"your_review": "Ваш отзыв",
"submit": "Отправить",
"submitting": "Отправляется...",
"submit_review": "Отправить отзыв",
"characters": "символы",
@@ -185,7 +186,11 @@
"collection_not_found": "Коллекция не найдена",
"added_to_favorites": "Товар добавлен в избранное",
"submit_success": "Отзыв отправлен",
"submit_error": "Произошла ошибка"
"submit_error": "Произошла ошибка",
"title": "Открыть магазин",
"enter_email": "Введите email",
"uploadPatent": "Загрузить патент",
"outOfStock": "Нет в наличии"
}

View File

@@ -172,6 +172,7 @@
"share_experience": "Bu haryt barada öz teswiriňizi ýazyň",
"rating": "Reýting",
"your_review": "Teswiriňiz",
"sumbit": "Ugratmak",
"submitting": "Ugradylýar...",
"submit_review": "Teswiri ugrat",
"characters": "simbol",
@@ -186,7 +187,9 @@
"collection_not_found": "Kolleksiýa tapylmady",
"added_to_favorites": "Haryt saýlananlara goşuldy",
"submit_success": "Üstünlikli ugradyldy",
"submit_error": "Ýalňyşlyk ýüze çykdy"
"submit_error": "Ýalňyşlyk ýüze çykdy",
"title": "Magazin aç",
"enter_email": "Poçtaňyzy ýazyň",
"uploadPatent": "Patent goş",
"outOfStock": "Ammarda ýok"
}

View File

@@ -222,25 +222,16 @@ export function useLogout() {
try {
await apiClient.post("/auth/logout", {}, { timeout: 5000 });
} catch (error) {
// Logout should succeed even if server call fails
console.warn("[Logout] Server call failed, clearing local state anyway");
}
},
onSuccess: () => {
TokenStorage.clearTokens();
queryClient.clear();
if (typeof window !== "undefined") {
window.location.href = "/";
}
},
onError: () => {
TokenStorage.clearTokens();
queryClient.clear();
if (typeof window !== "undefined") {
window.location.href = "/";
}
},
});
}