change theme chatbot and search
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
VITE_TOKEN_NAME = 'dzone_token'
|
VITE_TOKEN_NAME = 'dzone_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'dzone_refresh_token'
|
||||||
VITE_BASE_URL = 'https://api-dzone.danakcorp.com'
|
VITE_BASE_URL = 'https://api-dzone.danakcorp.com'
|
||||||
# VITE_BASE_URL = 'http://localhost:4000'
|
# VITE_BASE_URL = 'https://nhrq9m1d-4000.eun1.devtunnels.ms'
|
||||||
|
|
||||||
VITE_SLUG_ID = 'slug_id'
|
VITE_SLUG_ID = 'slug_id'
|
||||||
+43
-43
@@ -1,5 +1,5 @@
|
|||||||
import { FC, useState, useRef, useEffect } from "react";
|
import { FC, useState, useRef, useEffect } from "react";
|
||||||
import { Send, CloseCircle, Wifi } from "iconsax-react";
|
import { Send, CloseCircle, User } from "iconsax-react";
|
||||||
import io, { Socket } from "socket.io-client";
|
import io, { Socket } from "socket.io-client";
|
||||||
import { SOCKET_EVENTS } from "./constants";
|
import { SOCKET_EVENTS } from "./constants";
|
||||||
import { useSocketHandlers } from "./hooks/useSocketHandlers";
|
import { useSocketHandlers } from "./hooks/useSocketHandlers";
|
||||||
@@ -423,38 +423,38 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get connection status icon and text
|
// Get connection status icon and text
|
||||||
const getConnectionStatusInfo = () => {
|
// const getConnectionStatusInfo = () => {
|
||||||
switch (connectionStatus) {
|
// switch (connectionStatus) {
|
||||||
case "connected":
|
// case "connected":
|
||||||
return {
|
// return {
|
||||||
icon: <Wifi size={16} className="text-green-400" />,
|
// icon: <Wifi size={16} className="text-green-400" />,
|
||||||
text: "متصل",
|
// text: "متصل",
|
||||||
className: "bg-green-100 text-green-800",
|
// className: "bg-green-100 text-green-800",
|
||||||
};
|
// };
|
||||||
case "connecting":
|
// case "connecting":
|
||||||
return {
|
// return {
|
||||||
icon: <Wifi size={16} className="text-yellow-400 animate-pulse" />,
|
// icon: <Wifi size={16} className="text-yellow-400 animate-pulse" />,
|
||||||
text: "در حال اتصال...",
|
// text: "در حال اتصال...",
|
||||||
className: "bg-yellow-100 text-yellow-800",
|
// className: "bg-yellow-100 text-yellow-800",
|
||||||
};
|
// };
|
||||||
case "error":
|
// case "error":
|
||||||
return {
|
// return {
|
||||||
icon: <Wifi size={16} className="text-red-500" />,
|
// icon: <Wifi size={16} className="text-red-500" />,
|
||||||
text: "خطا در اتصال",
|
// text: "خطا در اتصال",
|
||||||
className: "bg-red-100 text-red-800",
|
// className: "bg-red-100 text-red-800",
|
||||||
};
|
// };
|
||||||
default:
|
// default:
|
||||||
return {
|
// return {
|
||||||
icon: <Wifi size={16} variant="Bold" className="text-gray-500" />,
|
// icon: <Wifi size={16} variant="Bold" className="text-gray-500" />,
|
||||||
text: "قطع اتصال",
|
// text: "قطع اتصال",
|
||||||
className: "bg-gray-100 text-gray-800",
|
// className: "bg-gray-100 text-gray-800",
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const connectionInfo = getConnectionStatusInfo();
|
// const connectionInfo = getConnectionStatusInfo();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -465,7 +465,7 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
style={{ height: "500px", direction: "rtl" }}
|
style={{ height: "500px", direction: "rtl" }}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-gradient-to-l from-blue-500 to-blue-600 text-white p-3.5 rounded-t-xl flex justify-between items-center shadow-sm">
|
<div className="bg-black text-white p-3.5 rounded-t-xl flex justify-between items-center shadow-sm">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className={`w-3 h-3 rounded-full ml-2 ${connectionStatus === "connected"
|
className={`w-3 h-3 rounded-full ml-2 ${connectionStatus === "connected"
|
||||||
@@ -477,7 +477,7 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
></div>
|
></div>
|
||||||
<h3 className="font-bold text-base">چت بات داناک</h3>
|
<h3 className="font-bold text-base">چت بات داناک</h3>
|
||||||
{/* Always show a compact connection status */}
|
{/* Always show a compact connection status */}
|
||||||
<div
|
{/* <div
|
||||||
className={`flex items-center text-xs mr-2 ml-2 ${connectionStatus === "connected"
|
className={`flex items-center text-xs mr-2 ml-2 ${connectionStatus === "connected"
|
||||||
? "text-green-200"
|
? "text-green-200"
|
||||||
: connectionStatus === "connecting"
|
: connectionStatus === "connecting"
|
||||||
@@ -486,11 +486,11 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{connectionInfo.icon}
|
{connectionInfo.icon}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="focus:outline-none hover:bg-blue-700 p-1 rounded-full transition-colors"
|
className="focus:outline-none hover:bg-gray-700 p-1 rounded-full transition-colors"
|
||||||
aria-label="بستن چت"
|
aria-label="بستن چت"
|
||||||
>
|
>
|
||||||
<CloseCircle size={20} />
|
<CloseCircle size={20} />
|
||||||
@@ -520,13 +520,13 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
} animate-fade-in`}
|
} animate-fade-in`}
|
||||||
>
|
>
|
||||||
{message.isUser && (
|
{message.isUser && (
|
||||||
<div className="w-7 h-7 rounded-full bg-blue-600 flex items-center justify-center ml-2 flex-shrink-0">
|
<div className="w-7 h-7 rounded-full bg-black flex items-center justify-center ml-2 flex-shrink-0">
|
||||||
<span className="text-white text-xs">👤</span>
|
<span className="text-white text-xs"><User size={14} color="white" /></span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`relative px-3.5 py-2.5 pb-4 rounded-lg max-w-[85%] ${message.isUser
|
className={`relative px-3.5 py-2.5 pb-4 rounded-lg max-w-[85%] ${message.isUser
|
||||||
? "bg-blue-600 text-white rounded-tr-none shadow-sm animate-slide-right"
|
? "bg-black text-white rounded-tr-none shadow-sm animate-slide-right"
|
||||||
: message.isSystem
|
: message.isSystem
|
||||||
? message.metadata?.type === "error"
|
? message.metadata?.type === "error"
|
||||||
? "bg-red-100 text-red-800 rounded-tl-none"
|
? "bg-red-100 text-red-800 rounded-tl-none"
|
||||||
@@ -541,7 +541,7 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
: "bg-yellow-100 text-yellow-800 rounded-tl-none"
|
: "bg-yellow-100 text-yellow-800 rounded-tl-none"
|
||||||
: "bg-white text-gray-800 rounded-tl-none shadow-sm animate-slide-left"
|
: "bg-white text-gray-800 rounded-tl-none shadow-sm animate-slide-left"
|
||||||
} ${message.streaming
|
} ${message.streaming
|
||||||
? "border-r-4 border-blue-400 streaming-message animate-pulse"
|
? "border-r-4 border-gray-400 streaming-message animate-pulse"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -572,8 +572,8 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!message.isUser && !message.isSystem && (
|
{!message.isUser && !message.isSystem && (
|
||||||
<div className="w-7 h-7 rounded-full bg-blue-100 flex items-center justify-center mr-2 flex-shrink-0">
|
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0">
|
||||||
<span className="text-blue-600 text-xs">🤖</span>
|
<span className="text-gray-600 text-xs">🤖</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -589,8 +589,8 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-7 h-7 rounded-full bg-blue-100 flex items-center justify-center mr-2 flex-shrink-0">
|
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0">
|
||||||
<span className="text-blue-600 text-xs">🤖</span>
|
<span className="text-gray-600 text-xs">🤖</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -621,7 +621,7 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
|
|||||||
isProcessing ||
|
isProcessing ||
|
||||||
connectionStatus !== "connected"
|
connectionStatus !== "connected"
|
||||||
? "bg-gray-300"
|
? "bg-gray-300"
|
||||||
: "bg-blue-600 hover:bg-blue-700"
|
: "bg-gray-800 hover:bg-gray-900"
|
||||||
} text-white rounded-full w-9 h-9 flex items-center justify-center focus:outline-none transition-colors flex-shrink-0`}
|
} text-white rounded-full w-9 h-9 flex items-center justify-center focus:outline-none transition-colors flex-shrink-0`}
|
||||||
>
|
>
|
||||||
<Send
|
<Send
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const ChatbotButton: FC = () => {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={toggleChatbot}
|
onClick={toggleChatbot}
|
||||||
className="fixed bottom-5 right-5 bg-blue-600 text-white rounded-full w-14 h-14 flex items-center justify-center shadow-lg hover:bg-blue-700 transition-colors z-40"
|
className="fixed bottom-5 right-5 bg-primary text-white rounded-full w-14 h-14 flex items-center justify-center shadow-lg hover:bg-gray-800 transition-colors z-40"
|
||||||
aria-label="چت با پشتیبانی"
|
aria-label="چت با پشتیبانی"
|
||||||
>
|
>
|
||||||
<MessageText1 size={24} />
|
<MessageText1 size={24} />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
getCompanyRequest,
|
getCompanyRequest,
|
||||||
getMyCompany,
|
getMyCompany,
|
||||||
getSliders,
|
getSliders,
|
||||||
|
searchCompany,
|
||||||
} from "../service/HomeService";
|
} from "../service/HomeService";
|
||||||
|
|
||||||
export const useGetSlugId = () => {
|
export const useGetSlugId = () => {
|
||||||
@@ -57,3 +58,11 @@ export const useGetMyCompany = () => {
|
|||||||
queryFn: () => getMyCompany(),
|
queryFn: () => getMyCompany(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSearchCompany = (query: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["search-company", query],
|
||||||
|
queryFn: () => searchCompany(query),
|
||||||
|
enabled: !!query,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -40,3 +40,8 @@ export const getSliders = async () => {
|
|||||||
const { data } = await axios.get(`/sliders/list/public`);
|
const { data } = await axios.get(`/sliders/list/public`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const searchCompany = async (query: string) => {
|
||||||
|
const { data } = await axios.get(`/search?q=${query}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
+243
-8
@@ -1,6 +1,6 @@
|
|||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import Input from '../components/Input'
|
import Input from '../components/Input'
|
||||||
import { ArrowDown2, CloseCircle, HambergerMenu, Logout } from 'iconsax-react'
|
import { ArrowDown2, CloseCircle, HambergerMenu, Logout, SearchNormal } from 'iconsax-react'
|
||||||
import AvatarImage from '../assets/images/avatar_image.png'
|
import AvatarImage from '../assets/images/avatar_image.png'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||||
@@ -13,10 +13,16 @@ import SideBarItem from './SideBarItem'
|
|||||||
import Button from '../components/Button'
|
import Button from '../components/Button'
|
||||||
import { buildPath } from '../helpers/utils'
|
import { buildPath } from '../helpers/utils'
|
||||||
import { getToken } from '../config/func'
|
import { getToken } from '../config/func'
|
||||||
|
import { useSearchCompany } from '../pages/home/hooks/useHomeData'
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
|
|
||||||
const { isLogin, setIsLogin } = useSharedStore()
|
const { isLogin, setIsLogin } = useSharedStore()
|
||||||
|
const [search, setSearch] = useState<string>('')
|
||||||
|
const { data: searchData, isLoading: searchLoading } = useSearchCompany(search)
|
||||||
|
const [showSearchResults, setShowSearchResults] = useState(false)
|
||||||
|
const [inputFocused, setInputFocused] = useState(false)
|
||||||
|
const [showMobileSearch, setShowMobileSearch] = useState(false)
|
||||||
|
|
||||||
const checkLogin = async () => {
|
const checkLogin = async () => {
|
||||||
const token = await getToken()
|
const token = await getToken()
|
||||||
setIsLogin(!!token)
|
setIsLogin(!!token)
|
||||||
@@ -31,6 +37,60 @@ const Header: FC = () => {
|
|||||||
const { data } = useGetProfile(isLogin ? true : false)
|
const { data } = useGetProfile(isLogin ? true : false)
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearchChange = (value: string) => {
|
||||||
|
setSearch(value)
|
||||||
|
// Show results if there's text
|
||||||
|
if (value) {
|
||||||
|
setShowSearchResults(true)
|
||||||
|
} else {
|
||||||
|
setShowSearchResults(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
setInputFocused(true)
|
||||||
|
// Show results if there's text when focused
|
||||||
|
if (search) {
|
||||||
|
setShowSearchResults(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputBlur = (e: React.FocusEvent) => {
|
||||||
|
// Don't hide if clicking on the dropdown
|
||||||
|
const searchContainer = document.getElementById('search-dropdown-container')
|
||||||
|
const mobileSearchContainer = document.getElementById('mobile-search-dropdown-container')
|
||||||
|
|
||||||
|
if ((searchContainer && searchContainer.contains(e.relatedTarget as Node)) ||
|
||||||
|
(mobileSearchContainer && mobileSearchContainer.contains(e.relatedTarget as Node))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise hide and remove focus
|
||||||
|
setInputFocused(false)
|
||||||
|
setTimeout(() => {
|
||||||
|
// Small delay to allow click events to process first
|
||||||
|
setShowSearchResults(false)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleMobileSearch = () => {
|
||||||
|
setShowMobileSearch(prev => !prev)
|
||||||
|
if (!showMobileSearch) {
|
||||||
|
// When opening search, set focus
|
||||||
|
setTimeout(() => {
|
||||||
|
const mobileSearchInput = document.getElementById('mobile-search-input')
|
||||||
|
if (mobileSearchInput) {
|
||||||
|
(mobileSearchInput as HTMLInputElement).focus()
|
||||||
|
setInputFocused(true)
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
// When closing search, clear results
|
||||||
|
setSearch('')
|
||||||
|
setShowSearchResults(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPopoverKey((prevKey) => prevKey + 1);
|
setPopoverKey((prevKey) => prevKey + 1);
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
@@ -39,23 +99,196 @@ const Header: FC = () => {
|
|||||||
check()
|
check()
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e: MouseEvent) => {
|
||||||
|
const searchContainer = document.getElementById('search-dropdown-container')
|
||||||
|
const mobileSearchContainer = document.getElementById('mobile-search-dropdown-container')
|
||||||
|
const searchInput = document.getElementById('search-input-container')
|
||||||
|
const mobileSearchInput = document.getElementById('mobile-search-input-container')
|
||||||
|
|
||||||
|
// Check for both desktop and mobile separately to avoid false negatives
|
||||||
|
const isClickOutsideDesktopSearch =
|
||||||
|
searchContainer && !searchContainer.contains(e.target as Node) &&
|
||||||
|
searchInput && !searchInput.contains(e.target as Node);
|
||||||
|
|
||||||
|
const isClickOutsideMobileSearch =
|
||||||
|
mobileSearchContainer && !mobileSearchContainer.contains(e.target as Node) &&
|
||||||
|
mobileSearchInput && !mobileSearchInput.contains(e.target as Node);
|
||||||
|
|
||||||
|
// Only proceed if we have proper elements to check and click is outside both
|
||||||
|
if ((searchContainer && searchInput && isClickOutsideDesktopSearch) ||
|
||||||
|
(mobileSearchContainer && mobileSearchInput && isClickOutsideMobileSearch)) {
|
||||||
|
setShowSearchResults(false)
|
||||||
|
setInputFocused(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||||
|
{/* Desktop Search */}
|
||||||
<div className='min-w-[270px] hidden xl:block'>
|
<div className='min-w-[270px] hidden xl:block relative'>
|
||||||
|
<div id="search-input-container">
|
||||||
<Input
|
<Input
|
||||||
variant='search'
|
variant='search'
|
||||||
placeholder={t('header.search')}
|
placeholder={t('header.search')}
|
||||||
|
onChangeSearchFinal={handleSearchChange}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
isLogin &&
|
{showSearchResults && search && inputFocused && (
|
||||||
|
<div
|
||||||
|
id="search-dropdown-container"
|
||||||
|
className='absolute z-20 mt-1 w-full bg-white rounded-xl shadow-lg max-h-80 overflow-y-auto'
|
||||||
|
>
|
||||||
|
{searchLoading && (
|
||||||
|
<div className='p-4 text-center text-sm text-description'>
|
||||||
|
در حال جستجو...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchLoading && searchData?.data?.length === 0 && (
|
||||||
|
<div className='p-4 text-center text-sm text-description'>
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchLoading && searchData?.data?.length > 0 && (
|
||||||
|
<div className='py-2'>
|
||||||
|
{searchData.data.map((company: any) => (
|
||||||
|
<Link
|
||||||
|
to={buildPath(`company/${company.id}`, slug)}
|
||||||
|
key={company.id}
|
||||||
|
className='flex items-center gap-3 px-4 py-2 hover:bg-gray-50 cursor-pointer'
|
||||||
|
onClick={() => {
|
||||||
|
setShowSearchResults(false)
|
||||||
|
setSearch('')
|
||||||
|
setInputFocused(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='w-10 h-10 rounded-full overflow-hidden bg-gray-100'>
|
||||||
|
{company.profileImageUrl && (
|
||||||
|
<img
|
||||||
|
src={company.profileImageUrl}
|
||||||
|
alt={company.name}
|
||||||
|
className='w-full h-full object-cover'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<span className='text-sm font-medium'>{company.name}</span>
|
||||||
|
<span className='text-xs text-description'>{company.chiefExecutiveOfficer}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Layout - Only hamburger menu now */}
|
||||||
|
<div className={`${showMobileSearch ? 'w-full' : ''} items-center`}>
|
||||||
|
{!showMobileSearch && isLogin && (
|
||||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||||
<HambergerMenu size={24} color='black' />
|
<HambergerMenu size={24} color='black' />
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
|
{/* Full width mobile search when active */}
|
||||||
|
{showMobileSearch && (
|
||||||
|
<div className='flex w-full items-center gap-2'>
|
||||||
|
<div className='flex-1' id="mobile-search-input-container">
|
||||||
|
<Input
|
||||||
|
id="mobile-search-input"
|
||||||
|
variant='search'
|
||||||
|
placeholder={t('header.search')}
|
||||||
|
onChangeSearchFinal={handleSearchChange}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={toggleMobileSearch}
|
||||||
|
className='p-2 flex-shrink-0 flex items-center justify-center'
|
||||||
|
>
|
||||||
|
<CloseCircle size={20} color='#8C90A3' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Search Results */}
|
||||||
|
{showMobileSearch && showSearchResults && search && (
|
||||||
|
<div
|
||||||
|
id="mobile-search-dropdown-container"
|
||||||
|
className='fixed right-4 left-4 top-16 z-50 bg-white rounded-xl shadow-lg max-h-[calc(100vh-80px)] overflow-y-auto'
|
||||||
|
>
|
||||||
|
{searchLoading && (
|
||||||
|
<div className='p-4 text-center text-sm text-description'>
|
||||||
|
در حال جستجو...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchLoading && searchData?.data?.length === 0 && (
|
||||||
|
<div className='p-4 text-center text-sm text-description'>
|
||||||
|
نتیجهای یافت نشد
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchLoading && searchData?.data?.length > 0 && (
|
||||||
|
<div className='py-2'>
|
||||||
|
{searchData.data.map((company: any) => (
|
||||||
|
<Link
|
||||||
|
to={buildPath(`company/${company.id}`, slug)}
|
||||||
|
key={company.id}
|
||||||
|
className='flex items-center gap-3 px-4 py-2 hover:bg-gray-50 cursor-pointer'
|
||||||
|
onClick={() => {
|
||||||
|
setShowSearchResults(false)
|
||||||
|
setSearch('')
|
||||||
|
setInputFocused(false)
|
||||||
|
setShowMobileSearch(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='w-10 h-10 rounded-full overflow-hidden bg-gray-100'>
|
||||||
|
{company.profileImageUrl && (
|
||||||
|
<img
|
||||||
|
src={company.profileImageUrl}
|
||||||
|
alt={company.name}
|
||||||
|
className='w-full h-full object-cover'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<span className='text-sm font-medium'>{company.name}</span>
|
||||||
|
<span className='text-xs text-description'>{company.chiefExecutiveOfficer}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||||
<div className='flex xl:gap-6 gap-4 items-center'>
|
<div className={`flex xl:gap-6 gap-4 items-center ${showMobileSearch ? 'hidden xl:flex' : ''}`}>
|
||||||
|
{/* Mobile search icon - only visible on mobile when search is not active */}
|
||||||
|
{!showMobileSearch && (
|
||||||
|
<div onClick={toggleMobileSearch} className='xl:hidden block'>
|
||||||
|
<SearchNormal size={20} color='black' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
{
|
{
|
||||||
isLogin ? (
|
isLogin ? (
|
||||||
<Notifications />
|
<Notifications />
|
||||||
@@ -67,6 +300,8 @@ const Header: FC = () => {
|
|||||||
onClick={() => navigate(buildPath('auth/login', slug))}
|
onClick={() => navigate(buildPath('auth/login', slug))}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{/* User profile menu */}
|
||||||
{
|
{
|
||||||
data && (
|
data && (
|
||||||
<Popover className="relative" key={popoverKey}>
|
<Popover className="relative" key={popoverKey}>
|
||||||
|
|||||||
Reference in New Issue
Block a user