Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. import React, { useState, useMemo, useEffect, useRef } from "react";
  2. import {
  3. AddOfferButton,
  4. AuthButtonsContainer,
  5. AuthButtonsDrawerContainer,
  6. DrawerContainer,
  7. EndIcon,
  8. FilterContainer,
  9. FilterIcon,
  10. HeaderContainer,
  11. LoginButton,
  12. LogoContainer,
  13. RegisterButton,
  14. SearchIcon,
  15. SearchInput,
  16. SearchInputMobile,
  17. ToggleDrawerButton,
  18. ToolsButtonsContainer,
  19. ToolsContainer,
  20. UserButton,
  21. UserName,
  22. } from "./Header.styled";
  23. import PropTypes from "prop-types";
  24. import {
  25. AppBar,
  26. Badge,
  27. Toolbar,
  28. useMediaQuery,
  29. Typography,
  30. } from "@mui/material";
  31. import { useTheme } from "@mui/system";
  32. import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
  33. import MailIcon from "@mui/icons-material/EmailOutlined";
  34. import Autorenew from "@mui/icons-material/Autorenew";
  35. import AccountCircle from "@mui/icons-material/PersonOutlineOutlined";
  36. import Drawer from "../MUI/DrawerComponent";
  37. import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
  38. import PopoverComponent from "../Popovers/PopoverComponent";
  39. import { MyPosts } from "../Popovers/MyPosts/MyPosts";
  40. import { MyMessages } from "../Popovers/MyMessages/MyMessages";
  41. import { MyProfile } from "../Popovers/MyProfile/MyProfile";
  42. import { ReactComponent as LogoHorizontal } from "../../assets/images/svg/logo-horizontal.svg";
  43. import selectedTheme from "../../themes";
  44. import { useTranslation } from "react-i18next";
  45. import { IconButton } from "../Buttons/IconButton/IconButton";
  46. import { useDispatch, useSelector } from "react-redux";
  47. import { selectUserId } from "../../store/selectors/loginSelectors";
  48. import { useSearch } from "../../hooks/useSearch";
  49. import { selectProfileName } from "../../store/selectors/profileSelectors";
  50. import { fetchProfile } from "../../store/actions/profile/profileActions";
  51. import { useHistory, useRouteMatch } from "react-router-dom";
  52. import { LOGIN_PAGE, REGISTER_PAGE } from "../../constants/pages";
  53. import useFilters from "../../hooks/useFilters";
  54. import FilterCard from "../Cards/FilterCard/FilterCard";
  55. import { useQueryString } from "../../hooks/useQueryString";
  56. import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers";
  57. const Header = () => {
  58. const [openDrawer, setOpenDrawer] = useState(false);
  59. const [openFilters, setOpenFilters] = useState(false);
  60. const [numberOfFilters, setNumberOfFilters] = useState(0);
  61. const { t } = useTranslation();
  62. const theme = useTheme();
  63. const searchRef = useRef(null);
  64. const matches = useMediaQuery(theme.breakpoints.down("md"));
  65. const user = useSelector(selectUserId);
  66. const search = useSearch();
  67. const dispatch = useDispatch();
  68. const name = useSelector(selectProfileName);
  69. const history = useHistory();
  70. const routeMatch = useRouteMatch();
  71. const filters = useFilters();
  72. const searchMobileRef = useRef(null);
  73. const queryStringHook = useQueryString();
  74. useEffect(() => {
  75. if (user?.length > 1) {
  76. dispatch(fetchProfile(user));
  77. }
  78. }, [user]);
  79. useEffect(() => {
  80. setUserPopoverOpen(false);
  81. setUserAnchorEl(null);
  82. return () => {
  83. setUserPopoverOpen(false);
  84. setUserAnchorEl(null);
  85. };
  86. }, []);
  87. useEffect(() => {
  88. setNumberOfFilters(filters.calculateFiltersChosen());
  89. }, [
  90. filters.selectedCategory,
  91. filters.selectedLocations,
  92. filters.selectedSubcategory,
  93. ]);
  94. useEffect(() => {
  95. if (queryStringHook.loadedFromURL) {
  96. const queryObject = new URLSearchParams(
  97. convertQueryStringFrontend(queryStringHook.queryString)
  98. );
  99. if (queryObject.has("search")) {
  100. searchRef.current.value = queryObject.get("search");
  101. searchMobileRef.current.value = queryObject.get("search");
  102. }
  103. }
  104. });
  105. const [postsPopoverOpen, setPostsPopoverOpen] = useState(false);
  106. const [postsAnchorEl, setPostsAnchorEl] = useState(null);
  107. const [msgPopoverOpen, setMsgPopoverOpen] = useState(false);
  108. const [msgAnchorEl, setMsgAnchorEl] = useState(null);
  109. const [userPopoverOpen, setUserPopoverOpen] = useState(false);
  110. const [userAnchorEl, setUserAnchorEl] = useState(null);
  111. const [shouldShow, setShouldShow] = useState(true);
  112. useEffect(() => {
  113. let shouldShowHeader = true;
  114. if (
  115. location.pathname === "/login" ||
  116. location.pathname === "/register" ||
  117. location.pathname === "/forgot-password" ||
  118. location.pathname === "/reset-password" ||
  119. location.pathname === "/"
  120. ) {
  121. shouldShowHeader = false;
  122. }
  123. if (location.pathname === "/" && user?.length === 0) {
  124. shouldShowHeader = false;
  125. }
  126. setShouldShow(shouldShowHeader);
  127. }, [location.pathname, user, routeMatch]);
  128. const handleToggleDrawer = () => {
  129. setOpenDrawer(!openDrawer);
  130. };
  131. const handleNavigateLogin = () => {
  132. setShouldShow(false);
  133. history.push(LOGIN_PAGE);
  134. };
  135. const handleNavigateRegister = () => {
  136. setShouldShow(false);
  137. history.push(REGISTER_PAGE);
  138. };
  139. const drawerContent = useMemo(
  140. () => (
  141. <DrawerContainer>
  142. {user ? (
  143. <React.Fragment>
  144. <PrimaryButton
  145. type="submit"
  146. variant="contained"
  147. height="36px"
  148. fullWidth
  149. buttoncolor={selectedTheme.primaryYellow}
  150. textcolor="black"
  151. onClick={() => handleToggleDrawer()}
  152. >
  153. {t("header.addOffer")}
  154. </PrimaryButton>
  155. <ToolsContainer mobile>
  156. <IconButton
  157. onClick={(e) => {
  158. setPostsPopoverOpen(true);
  159. setPostsAnchorEl(e.currentTarget);
  160. }}
  161. sx={{ borderRadius: "4px" }}
  162. >
  163. <Autorenew />
  164. <Typography sx={{ ml: 2 }}>Moje objave</Typography>
  165. </IconButton>
  166. <IconButton
  167. onClick={(e) => {
  168. setMsgPopoverOpen(true);
  169. setMsgAnchorEl(e.currentTarget);
  170. }}
  171. sx={{ borderRadius: "4px" }}
  172. >
  173. <Badge badgeContent={3} color="primary">
  174. <MailIcon color="action" />
  175. </Badge>
  176. <Typography sx={{ ml: 2 }}>Moje poruke</Typography>
  177. </IconButton>
  178. <IconButton
  179. onClick={(e) => {
  180. setUserPopoverOpen(true);
  181. setUserAnchorEl(e.currentTarget);
  182. }}
  183. sx={{ borderRadius: "4px" }}
  184. >
  185. <AccountCircle />
  186. <Typography sx={{ ml: 2 }}>Moj profil</Typography>
  187. </IconButton>
  188. </ToolsContainer>
  189. </React.Fragment>
  190. ) : (
  191. <AuthButtonsDrawerContainer>
  192. <RegisterButton
  193. type="submit"
  194. variant="contained"
  195. height="36px"
  196. fullWidth
  197. buttoncolor={selectedTheme.primaryYellow}
  198. textcolor={selectedTheme.primaryDarkText}
  199. onClick={handleNavigateRegister}
  200. >
  201. {t("register.headerTitle")}
  202. </RegisterButton>
  203. <LoginButton
  204. type="submit"
  205. variant="contained"
  206. height="36px"
  207. fullWidth
  208. buttoncolor={selectedTheme.primaryPurple}
  209. textcolor={selectedTheme.primaryIconBackgroundColor}
  210. onClick={handleNavigateLogin}
  211. >
  212. {t("login.headerTitle")}
  213. </LoginButton>
  214. </AuthButtonsDrawerContainer>
  215. )}
  216. </DrawerContainer>
  217. ),
  218. [handleToggleDrawer]
  219. );
  220. let listener;
  221. const handleFocusSearch = () => {
  222. listener = (event) => {
  223. if (event.keyCode === 13) {
  224. event.preventDefault();
  225. handleSearch(searchRef.current.value);
  226. }
  227. };
  228. searchRef.current.addEventListener("keyup", listener);
  229. };
  230. const handleBlurSearch = () => {
  231. searchRef.current.removeEventListener("keyup", listener);
  232. };
  233. const handleSearch = (value) => {
  234. console.log(value);
  235. if (value.length === 0) return;
  236. search.searchOffers(value);
  237. };
  238. const toggleFilters = () => {
  239. setOpenFilters((prevState) => !prevState);
  240. };
  241. return (
  242. <HeaderContainer style={{ display: shouldShow ? "block" : "none" }}>
  243. <AppBar
  244. elevation={0}
  245. position="fixed"
  246. // positionFixed
  247. sx={{ backgroundColor: "white" }}
  248. >
  249. <Toolbar>
  250. <ToolsContainer>
  251. <LogoContainer>
  252. <LogoHorizontal />
  253. </LogoContainer>
  254. {matches && (
  255. <Drawer
  256. open={openDrawer}
  257. toggleOpen={handleToggleDrawer}
  258. content={drawerContent}
  259. />
  260. )}
  261. <SearchInput
  262. fullWidth
  263. InputProps={{
  264. endAdornment: (
  265. <EndIcon size="36px">
  266. <SearchIcon
  267. onClick={() => handleSearch(searchRef.current.value)}
  268. />
  269. </EndIcon>
  270. ),
  271. }}
  272. placeholder={t("header.searchOffers")}
  273. onFocus={handleFocusSearch}
  274. onBlur={handleBlurSearch}
  275. ref={searchRef}
  276. />
  277. {user ? (
  278. <ToolsButtonsContainer mobile={matches}>
  279. {matches ? (
  280. <ToggleDrawerButton>
  281. <IconButton onClick={handleToggleDrawer}>
  282. <MenuOutlinedIcon />
  283. </IconButton>
  284. </ToggleDrawerButton>
  285. ) : (
  286. <React.Fragment>
  287. <AddOfferButton
  288. type="submit"
  289. variant="contained"
  290. fullWidth
  291. buttoncolor={selectedTheme.primaryYellow}
  292. textcolor={selectedTheme.primaryDarkText}
  293. onClick={() => {
  294. setUserPopoverOpen(false);
  295. setUserAnchorEl(null);
  296. }}
  297. >
  298. {t("header.addOffer")}
  299. </AddOfferButton>
  300. <IconButton
  301. onClick={(e) => {
  302. setPostsPopoverOpen(true);
  303. setPostsAnchorEl(e.currentTarget);
  304. }}
  305. style={{
  306. background: selectedTheme.primaryIconBackgroundColor,
  307. color: selectedTheme.primaryPurple,
  308. }}
  309. >
  310. <Autorenew />
  311. </IconButton>
  312. <IconButton
  313. onClick={(e) => {
  314. setMsgPopoverOpen(true);
  315. setMsgAnchorEl(e.currentTarget);
  316. }}
  317. style={{
  318. background: selectedTheme.primaryIconBackgroundColor,
  319. color: selectedTheme.primaryPurple,
  320. }}
  321. >
  322. <Badge badgeContent={3} color="primary">
  323. <MailIcon />
  324. </Badge>
  325. </IconButton>
  326. <UserButton
  327. onClick={(e) => {
  328. setUserPopoverOpen(true);
  329. setUserAnchorEl(e.currentTarget);
  330. }}
  331. >
  332. <UserName>{name}</UserName>
  333. <IconButton
  334. style={{
  335. background: selectedTheme.primaryIconBackgroundColor,
  336. color: selectedTheme.primaryPurple,
  337. }}
  338. >
  339. <AccountCircle />
  340. </IconButton>
  341. </UserButton>
  342. </React.Fragment>
  343. )}
  344. </ToolsButtonsContainer>
  345. ) : (
  346. <AuthButtonsContainer mobile={matches}>
  347. {matches ? (
  348. <ToggleDrawerButton>
  349. <IconButton onClick={handleToggleDrawer}>
  350. <MenuOutlinedIcon />
  351. </IconButton>
  352. </ToggleDrawerButton>
  353. ) : (
  354. <React.Fragment>
  355. <LoginButton
  356. type="submit"
  357. variant="contained"
  358. fullWidth
  359. buttoncolor={selectedTheme.primaryPurple}
  360. textcolor={selectedTheme.offerBackgroundColor}
  361. onClick={handleNavigateLogin}
  362. >
  363. {t("login.headerTitle")}
  364. </LoginButton>
  365. <RegisterButton
  366. type="submit"
  367. variant="contained"
  368. fullWidth
  369. buttoncolor={selectedTheme.primaryYellow}
  370. textcolor={selectedTheme.primaryDarkText}
  371. onClick={handleNavigateRegister}
  372. >
  373. {t("register.headerTitle")}
  374. </RegisterButton>
  375. </React.Fragment>
  376. )}
  377. </AuthButtonsContainer>
  378. )}
  379. </ToolsContainer>
  380. </Toolbar>
  381. {user && (
  382. <React.Fragment>
  383. <PopoverComponent
  384. anchorEl={postsAnchorEl}
  385. open={postsPopoverOpen}
  386. onClose={() => {
  387. setPostsPopoverOpen(false);
  388. setPostsAnchorEl(null);
  389. }}
  390. content={<MyPosts />}
  391. />
  392. <PopoverComponent
  393. anchorEl={msgAnchorEl}
  394. open={msgPopoverOpen}
  395. onClose={() => {
  396. setMsgPopoverOpen(false);
  397. setMsgAnchorEl(null);
  398. }}
  399. content={<MyMessages />}
  400. />
  401. <PopoverComponent
  402. anchorEl={userAnchorEl}
  403. open={userPopoverOpen}
  404. onClose={() => {
  405. setUserPopoverOpen(false);
  406. setUserAnchorEl(null);
  407. }}
  408. content={<MyProfile />}
  409. />
  410. </React.Fragment>
  411. )}
  412. </AppBar>
  413. <SearchInputMobile
  414. fullWidth
  415. ref={searchMobileRef}
  416. InputProps={{
  417. endAdornment: (
  418. <React.Fragment>
  419. <FilterContainer number={numberOfFilters}>
  420. <FilterIcon onClick={toggleFilters} />
  421. </FilterContainer>
  422. <EndIcon size="36px">
  423. <SearchIcon
  424. onClick={() => handleSearch(searchMobileRef.current.value)}
  425. />
  426. </EndIcon>
  427. </React.Fragment>
  428. ),
  429. }}
  430. placeholder={t("header.searchOffers")}
  431. italicPlaceholder
  432. onFocus={handleFocusSearch}
  433. onBlur={handleBlurSearch}
  434. />
  435. <FilterCard
  436. responsive={true}
  437. responsiveOpen={openFilters}
  438. closeResponsive={toggleFilters}
  439. />
  440. </HeaderContainer>
  441. );
  442. };
  443. Header.propTypes = {
  444. isGrid: PropTypes.bool,
  445. };
  446. export default Header;