From 881f75a372fd1ccd319ebec5ef9b05521eba78e0 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Sun, 6 Sep 2026 11:20:47 +0330 Subject: [PATCH] refactor(mobile): refine bottom nav tabs, hardware back dismissal, and live shop search --- .../components/MobileBottomNav.tsx | 364 +- graphify-out/.graphify_labels.json | 18 +- graphify-out/.graphify_labels.json.sig | 2 +- graphify-out/2026-09-06/.graphify_labels.json | 78 +- graphify-out/2026-09-06/GRAPH_REPORT.md | 320 +- graphify-out/2026-09-06/graph.json | 10266 ++++++++-------- graphify-out/2026-09-06/manifest.json | 1284 +- graphify-out/GRAPH_REPORT.md | 118 +- graphify-out/cache/stat-index.json | 2 +- graphify-out/graph.html | 8 +- graphify-out/graph.json | 4066 +++--- graphify-out/manifest.json | 1268 +- 12 files changed, 9184 insertions(+), 8610 deletions(-) diff --git a/frontend/application/components/MobileBottomNav.tsx b/frontend/application/components/MobileBottomNav.tsx index 08193fd..f0c6069 100644 --- a/frontend/application/components/MobileBottomNav.tsx +++ b/frontend/application/components/MobileBottomNav.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback, useRef } from "react"; import Link from "next/link"; +import Image from "next/image"; import { usePathname, useRouter } from "next/navigation"; import { Home, ShoppingBag, - Sparkles, ShoppingCart, Menu, Pill, @@ -18,17 +18,19 @@ import { Building2, FileHeart, User, - LogOut, Search, ChevronLeft, Dog, - PhoneCall + PhoneCall, + Sparkles, + ArrowRight } from "lucide-react"; import { useCartStore } from "../lib/store/cartStore"; import { useUserStore } from "../lib/store/userStore"; import { useUIStore } from "../lib/store/uiStore"; import { useSettingsStore } from "../lib/store/settingsStore"; -import { toast } from "sonner"; +import { productService } from "../lib/services/productService"; +import { Product } from "../lib/data/products"; import { toPersian } from "../lib/utils"; const SOLUTION_ITEMS = [ @@ -66,74 +68,151 @@ export default function MobileBottomNav({ const pathname = usePathname(); const router = useRouter(); - const [isSolutionsOpen, setIsSolutionsOpen] = useState(false); - const [isMenuOpen, setIsMenuOpen] = useState(false); + const [activeSheet, setActiveSheet] = useState<"shop" | "menu" | null>(null); const [searchQuery, setSearchQuery] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [isSearching, setIsSearching] = useState(false); const [isMounted, setIsMounted] = useState(false); + const isNavigatingBackRef = useRef(false); + const { getTotalItems } = useCartStore(); const setCartOpen = useUIStore((state) => state.setCartOpen); const setB2BPortalOpen = useUIStore((state) => state.setB2BPortalOpen); const isB2BEnabled = useSettingsStore((s) => s.getBoolean('b2bRegistrationOpen', true) && s.getBoolean('b2b_enabled', true)); const isPrescriptionEnabled = useSettingsStore((s) => s.getText('PRESCRIPTION_UPLOAD_ENABLED', 'true') !== 'false'); - const { isLoggedIn, profile, logout, setAuthModalOpen } = useUserStore(); + const { isLoggedIn, setAuthModalOpen } = useUserStore(); useEffect(() => { setIsMounted(true); }, []); + // Close sheet with history navigation support (Back button dismiss) + const closeSheet = useCallback(() => { + if (!activeSheet) return; + setActiveSheet(null); + if (typeof window !== "undefined" && window.location.hash === "#sheet") { + isNavigatingBackRef.current = true; + window.history.back(); + } + }, [activeSheet]); + + const openSheet = useCallback((sheet: "shop" | "menu") => { + setActiveSheet(sheet); + if (typeof window !== "undefined") { + window.history.pushState({ mobileSheet: sheet }, "", "#sheet"); + } + }, []); + + // Handle hardware / browser back button popstate useEffect(() => { - setIsSolutionsOpen(false); - setIsMenuOpen(false); + const handlePopState = () => { + if (isNavigatingBackRef.current) { + isNavigatingBackRef.current = false; + return; + } + if (activeSheet) { + setActiveSheet(null); + } + }; + + window.addEventListener("popstate", handlePopState); + return () => window.removeEventListener("popstate", handlePopState); + }, [activeSheet]); + + // Close sheet on route change + useEffect(() => { + setActiveSheet(null); + setSearchQuery(""); + setSearchResults([]); }, [pathname]); + // Live search debounced query + useEffect(() => { + if (!searchQuery.trim()) { + setSearchResults([]); + setIsSearching(false); + return; + } + + const timer = setTimeout(async () => { + setIsSearching(true); + try { + const res = await productService.getProducts({ query: searchQuery.trim(), limit: 6 }); + setSearchResults(res.data || []); + } catch (err) { + console.error("Live search failed:", err); + } finally { + setIsSearching(false); + } + }, 250); + + return () => clearTimeout(timer); + }, [searchQuery]); + const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault(); if (searchQuery.trim()) { - setIsSolutionsOpen(false); - router.push(`/shop?q=${encodeURIComponent(searchQuery.trim())}`); + const q = searchQuery.trim(); + closeSheet(); + router.push(`/shop?search=${encodeURIComponent(q)}`); + } else { + closeSheet(); + router.push("/shop"); + } + }; + + const handleAccountClick = () => { + if (isLoggedIn) { + router.push("/dashboard"); + } else { + setAuthModalOpen(true); } }; const totalCartItems = isMounted ? getTotalItems() : 0; const isHomeActive = pathname === "/"; - const isShopActive = pathname === "/shop"; + const isShopActive = pathname.startsWith("/shop") || activeSheet === "shop"; + const isAccountActive = pathname.startsWith("/dashboard"); return ( <> - {/* 1. SOLUTIONS BOTTOM SHEET */} - {isSolutionsOpen && ( + {/* 1. SHOP & SOLUTIONS BOTTOM SHEET (Modal for Shop Tab) */} + {activeSheet === "shop" && (
setIsSolutionsOpen(false)} + onClick={closeSheet} >
e.stopPropagation()} dir="rtl" > + {/* Grab handle */}
+ {/* Header with Direct Shop Link */}
- +
-

راهکارهای بالینی و تخصصی

-

جستجو بر اساس بیماری و علائم بالینی پت

+

فروشگاه و راهکارهای دارویی

+

جستجو در داروها، مکمل‌ها و علائم بالینی

+ {/* Search Input */}
setSearchQuery(e.target.value)} - className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl py-2.5 pr-10 pl-3 text-xs outline-none focus:border-canina-blue font-vazir" + className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl py-2.5 pr-10 pl-14 text-xs outline-none focus:border-canina-blue font-vazir text-medical-gray-900" + autoFocus /> + + {/* Live Search Results (if typing) */} + {searchQuery.trim().length > 0 && ( +
+
+ + {isSearching ? "در حال جستجو..." : `نتایج زنده (${toPersian(searchResults.length)})`} + + +
+ + {searchResults.length === 0 && !isSearching ? ( +

دارویی با این نام یافت نشد

+ ) : ( +
+ {searchResults.map((prod) => ( + + {prod.image ? ( + {prod.name} + ) : ( +
+ +
+ )} +
+
{prod.name}
+
{prod.scientificTagline || prod.category}
+
+
+ {prod.price} +
+ + ))} +
+ )} +
+ )} + + {/* Direct Link to All Products */} + +
+ + مشاهده کاتالوگ و همه محصولات +
+ + + + {/* Prescription Upload Banner CTA if enabled */} {isPrescriptionEnabled && ( )} -
+ {/* 4 Major Clinical Categories with Symptoms */} +
+
دسته‌بندی‌ها و راهکارهای درمانی:
{SOLUTION_ITEMS.map((item) => (
setIsSolutionsOpen(false)} - className="flex items-center justify-between mb-2.5" + onClick={closeSheet} + className="flex items-center justify-between mb-2" >
{item.icon} @@ -191,7 +348,7 @@ export default function MobileBottomNav({ setIsSolutionsOpen(false)} + onClick={closeSheet} className="px-2.5 py-1 bg-white border border-medical-gray-200/80 rounded-lg text-[10px] font-bold text-medical-gray-600 hover:border-canina-blue hover:text-canina-blue transition-all" > {sol} @@ -205,24 +362,26 @@ export default function MobileBottomNav({
)} - {/* 2. MENU & USER PROFILE BOTTOM SHEET */} - {isMenuOpen && ( + {/* 2. MENU BOTTOM SHEET (Modal for Menu Tab) */} + {activeSheet === "menu" && (
setIsMenuOpen(false)} + onClick={closeSheet} >
e.stopPropagation()} dir="rtl" > + {/* Grab handle */}
+ {/* Header */}
-

منو و حساب کاربری

+

منوی بخش‌های کنینا

- {isLoggedIn ? ( -
-
-
- -
-
-
- {profile.firstName ? `${profile.firstName} ${profile.lastName || ''}` : 'کاربر گرامی'} -
-
- {profile.mobile || profile.email} -
-
-
- setIsMenuOpen(false)} - className="px-3 py-1.5 bg-canina-blue text-white rounded-xl text-xs font-bold" - > - داشبورد - -
- ) : ( - - )} - -
+ {/* Navigation Links Grid */} +
setIsMenuOpen(false)} - className="flex items-center gap-2.5 p-3 bg-white border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" + onClick={closeSheet} + className="flex items-center gap-2.5 p-3.5 bg-medical-gray-50 border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" > شناسنامه پت‌ها @@ -282,8 +402,8 @@ export default function MobileBottomNav({ setIsMenuOpen(false)} - className="flex items-center gap-2.5 p-3 bg-white border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" + onClick={closeSheet} + className="flex items-center gap-2.5 p-3.5 bg-medical-gray-50 border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" > کاتالوگ دارویی @@ -291,8 +411,8 @@ export default function MobileBottomNav({ setIsMenuOpen(false)} - className="flex items-center gap-2.5 p-3 bg-white border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" + onClick={closeSheet} + className="flex items-center gap-2.5 p-3.5 bg-medical-gray-50 border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" > دانشنامه علمی @@ -300,8 +420,8 @@ export default function MobileBottomNav({ setIsMenuOpen(false)} - className="flex items-center gap-2.5 p-3 bg-white border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" + onClick={closeSheet} + className="flex items-center gap-2.5 p-3.5 bg-medical-gray-50 border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" > مجله سلامت پت @@ -309,8 +429,8 @@ export default function MobileBottomNav({ setIsMenuOpen(false)} - className="flex items-center gap-2.5 p-3 bg-white border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" + onClick={closeSheet} + className="flex items-center gap-2.5 p-3.5 bg-medical-gray-50 border border-medical-gray-200/80 rounded-2xl text-xs font-bold text-medical-gray-800 hover:border-canina-blue" >
+ {/* Contact & Support */}
setIsMenuOpen(false)} + onClick={closeSheet} className="w-full flex items-center justify-between p-3 rounded-xl hover:bg-medical-gray-50 text-xs font-bold text-medical-gray-700" >
@@ -343,35 +464,21 @@ export default function MobileBottomNav({
- - {isLoggedIn && ( - - )}
)} - {/* 3. FIXED FLOATING BOTTOM APP NAVIGATION BAR */} + {/* 3. FIXED 5-TAB APP BOTTOM NAVIGATION BAR */}
-
4258 nodes · 7823 edges · 329 communities
+
4258 nodes · 7827 edges · 333 communities