| @@ -34,10 +34,6 @@ body, | |||
| flex: 1 0 auto; | |||
| } | |||
| #root { | |||
| margin-top: 80px; | |||
| } | |||
| input[type='search']::-webkit-search-decoration, | |||
| input[type='search']::-webkit-search-cancel-button, | |||
| input[type='search']::-webkit-search-results-button, | |||
| @@ -6,7 +6,7 @@ import { useDispatch, useSelector } from "react-redux"; | |||
| import { NavLink } from "react-router-dom"; | |||
| import * as Yup from "yup"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { fetchUser } from "../../../store/actions/login/loginActions"; | |||
| import { fetchLogin } from "../../../store/actions/login/loginActions"; | |||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../../constants/pages"; | |||
| import { ReactComponent as VisibilityOn } from "../../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../../assets/images/svg/eye.svg"; | |||
| @@ -67,7 +67,7 @@ const CreateOffer = ({ history }) => { | |||
| const handleSubmit = (values) => { | |||
| const { username: email, password: password } = values; | |||
| dispatch( | |||
| fetchUser({ | |||
| fetchLogin({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| @@ -17,7 +17,6 @@ import { SelectField } from "../CreateOffer.styled"; | |||
| const FirstPartCreateOffer = (props) => { | |||
| const { t } = useTranslation(); | |||
| const handleSubmit = (values) => { | |||
| console.log(values); | |||
| props.handleNext(values); | |||
| }; | |||
| const formik = useFormik({ | |||
| @@ -17,7 +17,6 @@ import { conditionSelectEnum } from "../../../../enums/conditionEnum"; | |||
| const SecondPartCreateOffer = () => { | |||
| const [images, setImages] = useState([null, null, null]); // 3 images | |||
| const setImage = (index, image) => { | |||
| console.log(images); | |||
| setImages((prevState) => { | |||
| let newState = [...prevState]; | |||
| newState[index] = image; | |||
| @@ -107,7 +107,6 @@ const FilterRadioDropdown = (props) => { | |||
| </DropdownItem> | |||
| )} | |||
| {dataToShow.map((item) => { | |||
| {/* console.log("item: ", item); */} | |||
| return ( | |||
| <DropdownItem | |||
| key={item.name} | |||
| @@ -36,6 +36,8 @@ import { IconButton } from "../Buttons/IconButton/IconButton"; | |||
| import { Icon } from "../Icon/Icon"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectJWTToken } from "../../store/selectors/loginSelectors"; | |||
| import { useSearch } from "../../hooks/useSearch"; | |||
| import { selectProfileName } from "../../store/selectors/profileSelectors"; | |||
| const Header = () => { | |||
| const [openDrawer, setOpenDrawer] = useState(false); | |||
| @@ -44,12 +46,14 @@ const Header = () => { | |||
| const searchRef = useRef(null); | |||
| const matches = useMediaQuery(theme.breakpoints.down("md")); | |||
| const user = useSelector(selectJWTToken); | |||
| const search = useSearch(); | |||
| const name = useSelector(selectProfileName); | |||
| const handleToggleDrawer = () => { | |||
| setOpenDrawer(!openDrawer); | |||
| }; | |||
| const [postsPopoverOpen, setPostsPopoverOpen] = useState(false); | |||
| const [postsAnchorEl, setPostsAnchorEl] = useState(null); | |||
| @@ -71,28 +75,25 @@ const Header = () => { | |||
| ) { | |||
| shouldShowHeader = false; | |||
| } | |||
| console.log(user); | |||
| if (location.pathname === "/" && user.JwtToken?.length === 0) { | |||
| shouldShowHeader = false; | |||
| } | |||
| setShouldShow(shouldShowHeader); | |||
| }, [location, user]); | |||
| // let listener; | |||
| let listener; | |||
| const handleFocusSearch = () => { | |||
| // console.log('focus'); | |||
| // listener = (event) => { | |||
| // if (event.keyCode === 13) { | |||
| // event.preventDefault(); | |||
| // console.log('detektovano'); | |||
| // } | |||
| // } | |||
| // searchRef.current.addEventListener('keyup', listener); | |||
| } | |||
| listener = (event) => { | |||
| if (event.keyCode === 13) { | |||
| event.preventDefault(); | |||
| search.searchOffers(searchRef.current.value) | |||
| } | |||
| } | |||
| searchRef.current.addEventListener('keyup', listener); | |||
| }; | |||
| const handleBlurSearch = () => { | |||
| // console.log('blur'); | |||
| // searchRef.current.removeEventListener('keyup', listener); | |||
| } | |||
| searchRef.current.removeEventListener('keyup', listener); | |||
| }; | |||
| const drawerContent = useMemo( | |||
| () => ( | |||
| @@ -150,9 +151,10 @@ const Header = () => { | |||
| return ( | |||
| <AppBar | |||
| elevation={0} | |||
| positionFixed | |||
| sx={{ backgroundColor: "white"}} | |||
| style={{display: shouldShow ? "flex" : "none"}} | |||
| position="fixed" | |||
| // positionFixed | |||
| sx={{ backgroundColor: "white" }} | |||
| style={{ display: shouldShow ? "flex" : "none" }} | |||
| > | |||
| <Toolbar> | |||
| <ToolsContainer> | |||
| @@ -224,13 +226,13 @@ const Header = () => { | |||
| <MailIcon /> | |||
| </Badge> | |||
| </IconButton> | |||
| <UserButton onClick={(e) => { | |||
| setUserPopoverOpen(true); | |||
| setUserAnchorEl(e.currentTarget); | |||
| }}> | |||
| <UserName> | |||
| Username korisnika | |||
| </UserName> | |||
| <UserButton | |||
| onClick={(e) => { | |||
| setUserPopoverOpen(true); | |||
| setUserAnchorEl(e.currentTarget); | |||
| }} | |||
| > | |||
| <UserName>{name}</UserName> | |||
| <IconButton | |||
| style={{ | |||
| background: selectedTheme.primaryIconBackgroundColor, | |||
| @@ -38,6 +38,7 @@ export const DrawerContainer = styled(Box)` | |||
| export const ToolsContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| justify-content: ${(props) => (props.mobile ? "center" : "space-between")}; | |||
| align-items: ${(props) => (props.mobile ? "start" : "center")}; | |||
| ${(props) => !props.mobile && `width: 100%;`} | |||
| @@ -52,10 +53,22 @@ export const LogoContainer = styled(Box)` | |||
| `; | |||
| export const ToolsButtonsContainer = styled(Box)` | |||
| display: flex; | |||
| flex: 4; | |||
| justify-content: space-between; | |||
| min-width: ${props => props.mobile ? "40px" : "600px"}; | |||
| max-width: 600px; | |||
| align-items: center; | |||
| flex-wrap: nowrap; | |||
| @media (max-width: 1200px) { | |||
| min-width: 400px; | |||
| } | |||
| @media (max-width: 950px) { | |||
| min-width: 250px; | |||
| } | |||
| @media (max-width: 800px) { | |||
| min-width: 0px; | |||
| width: 0px; | |||
| } | |||
| `; | |||
| export const ToggleDrawerButton = styled(Box)` | |||
| max-width: 40px; | |||
| @@ -5,7 +5,6 @@ import { | |||
| HeaderButtons, | |||
| HeaderContainer, | |||
| HeaderLocation, | |||
| // HeaderLocation, | |||
| HeaderOptions, | |||
| HeaderSelect, | |||
| IconStyled, | |||
| @@ -14,13 +13,10 @@ import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-g | |||
| import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-grid-line.svg"; | |||
| import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| // import { useSelector } from "react-redux"; | |||
| // import { selectFilters } from "../../../store/selectors/filtersSelectors"; | |||
| // import Mockupdata from "../../Cards/FilterCard/Mockupdata"; | |||
| import Option from "../../Select/Option/Option"; | |||
| // import { useHistory, useLocation, useRouteMatch } from "react-router-dom"; | |||
| import { sortEnum } from "../../../enums/sortEnum"; | |||
| import useFilters from "../../../hooks/useFilters"; | |||
| import useSorting from "../../../hooks/useSorting"; | |||
| const DownArrow = (props) => ( | |||
| <IconStyled {...props}> | |||
| @@ -30,13 +26,18 @@ const DownArrow = (props) => ( | |||
| const Header = (props) => { | |||
| const filters = useFilters(); | |||
| const sorting = useSorting(); | |||
| const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | |||
| const [headerString, setHeaderString] = useState("SVE KATEGORIJE"); | |||
| useEffect(() => { | |||
| setSortOption(sorting.selectedSortOption); | |||
| }, [sorting.selectedSortOption]); | |||
| useEffect(() => { | |||
| if (filters.isApplied) { | |||
| let headerStringLocal = "SVE KATEGORIJE"; | |||
| if (filters.selectedCategory?.name) { | |||
| console.log(filters.selectedCategory); | |||
| headerStringLocal = filters.selectedCategory.name; | |||
| if (filters.selectedSubcategory?.name) { | |||
| headerStringLocal += ` | ${filters.selectedSubcategory.name}`; | |||
| @@ -66,7 +67,7 @@ const Header = (props) => { | |||
| for (const sortOption in sortEnum) { | |||
| if (sortEnum[sortOption].value === event.target.value) { | |||
| chosenOption = sortEnum[sortOption]; | |||
| filters.setSelectedSortOption(chosenOption); | |||
| sorting.setSelectedSortOption(chosenOption); | |||
| } | |||
| } | |||
| }; | |||
| @@ -98,9 +99,7 @@ const Header = (props) => { | |||
| </HeaderButton> | |||
| </HeaderButtons> | |||
| <HeaderSelect | |||
| defaultValue={ | |||
| filters.selectedSortOption ? filters.selectedSortOption.value : 0 | |||
| } | |||
| value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value} | |||
| IconComponent={DownArrow} | |||
| width="209px" | |||
| height="34px" | |||
| @@ -3,15 +3,13 @@ import PropTypes from "prop-types"; | |||
| import { MarketPlaceContainer } from "./MarketPlace.styled"; | |||
| import Header from "./Header/Header"; | |||
| import Offers from "./Offers/Offers"; | |||
| import useFilters from "../../hooks/useFilters"; | |||
| const MarketPlace = () => { | |||
| const [isGrid, setIsGrid] = useState(false); | |||
| const filters = useFilters(); | |||
| return ( | |||
| <MarketPlaceContainer> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} filters={filters} /> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} /> | |||
| <Offers isGrid={isGrid} /> | |||
| </MarketPlaceContainer> | |||
| ); | |||
| @@ -10,6 +10,7 @@ import { useDispatch, useSelector } from "react-redux"; | |||
| import { | |||
| selectNoMoreOffers, | |||
| selectOffers, | |||
| selectPinnedOffers, | |||
| } from "../../../store/selectors/offersSelectors"; | |||
| import useFilters from "../../../hooks/useFilters"; | |||
| @@ -17,6 +18,7 @@ const Offers = (props) => { | |||
| const filters = useFilters(); | |||
| const [page, setPage] = useState(2); | |||
| const [initialLoad, setInitialLoad] = useState(true); | |||
| const pinnedOffers = useSelector(selectPinnedOffers); | |||
| const offers = useSelector(selectOffers); | |||
| const dispatch = useDispatch(); | |||
| const offersRef = useRef(null); | |||
| @@ -47,8 +49,8 @@ const Offers = (props) => { | |||
| }, [page, noMoreOffersStatus, filters.queryString, timeout, offers]); | |||
| useEffect(() => { | |||
| setPage(Math.floor(offers.length / 10) + 1); | |||
| }, [offers]); | |||
| setPage(Math.floor((offers.length + pinnedOffers.length ) / 10) + 1); | |||
| }, [offers, pinnedOffers]); | |||
| useEffect(() => { | |||
| dispatch(fetchOffers({ page: 1 })); | |||
| @@ -68,11 +70,15 @@ const Offers = (props) => { | |||
| }, [filters.queryString, initialLoad]); | |||
| return ( | |||
| <OffersContainer | |||
| ref={offersRef} | |||
| onScroll={() => console.log("proba")} | |||
| onClick={() => console.log(offersRef)} | |||
| > | |||
| <OffersContainer ref={offersRef}> | |||
| <> | |||
| {pinnedOffers != undefined && | |||
| pinnedOffers.map((item) => { | |||
| return ( | |||
| <OfferCard key={item._id} offer={item} halfwidth={props.isGrid} /> | |||
| ); | |||
| })} | |||
| </> | |||
| {offers != undefined && | |||
| offers.map((item) => { | |||
| return ( | |||
| @@ -1,5 +1,9 @@ | |||
| import React from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { fetchChats } from "../../../store/actions/chat/chatActions"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import HeaderPopover from "../HeaderPopover/HeaderPopover"; | |||
| const dummyData1 = [ | |||
| @@ -7,22 +11,49 @@ const dummyData1 = [ | |||
| alt: "Remy Sharp", | |||
| src: "/static/images/avatar/1.jpg", | |||
| title: "Coca-Cola", | |||
| text: "Kompresor je stigao. Samo..." | |||
| text: "Kompresor je stigao. Samo...", | |||
| }, | |||
| { | |||
| alt: "Travis Howard", | |||
| src: "/static/images/avatar/2.jpg", | |||
| title: "Voda Vrnjci", | |||
| text: "Poslao sam vodu. Ukupno i..." | |||
| } | |||
| ] | |||
| text: "Poslao sam vodu. Ukupno i...", | |||
| }, | |||
| ]; | |||
| const convertMessages = (messages) => { | |||
| const allMessages = [ | |||
| ...messages.chatsFromMyOffers, | |||
| ...messages.initiatedChats, | |||
| ]; | |||
| const lastMessageDate = Math.max( | |||
| ...allMessages.map((item) => new Date(item._modified)) | |||
| ); | |||
| const lastMessage = allMessages.find( | |||
| (item) => | |||
| JSON.stringify(new Date(item._modified)) === | |||
| JSON.stringify(new Date(lastMessageDate)) | |||
| ); | |||
| const lastSecondMessageDate = Math.max([...allMessages.filter(item => item._id !== lastMessage._id).map(item => new Date(item._modified))]) | |||
| console.log(lastSecondMessageDate); | |||
| console.log(allMessages.filter(item => item._id !== lastMessage._id).map(item => new Date(item._modified))) | |||
| console.log(lastMessage); | |||
| }; | |||
| export const MyMessages = () => { | |||
| const {t} = useTranslation(); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const userId = useSelector(selectUserId); | |||
| const chats = useSelector(selectLatestChats); | |||
| convertMessages(chats); | |||
| useEffect(() => { | |||
| dispatch(fetchChats(userId)); | |||
| }, []); | |||
| return ( | |||
| <HeaderPopover | |||
| title={t("header.myMessages")} | |||
| items={dummyData1} | |||
| buttonText={t("header.checkEverything")} /> | |||
| buttonText={t("header.checkEverything")} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -1,18 +1,23 @@ | |||
| import React, { useEffect } from 'react'; | |||
| import { Redirect, Route } from 'react-router'; | |||
| import { useDispatch } from 'react-redux'; | |||
| import { useDispatch, useSelector } from 'react-redux'; | |||
| import { authenticateUser } from '../../store/actions/login/loginActions'; | |||
| // import { selectIsUserAuthenticated } from '../../store/selectors/userSelectors'; | |||
| import { LOGIN_PAGE } from '../../constants/pages'; | |||
| import { fetchProfile } from '../../store/actions/profile/profileActions'; | |||
| import { selectUserId } from '../../store/selectors/loginSelectors'; | |||
| const PrivateRoute = ({ ...props }) => { | |||
| const dispatch = useDispatch(); | |||
| const userId = useSelector(selectUserId); | |||
| // const isUserAuthenticated = useSelector(selectIsUserAuthenticated); | |||
| const isUserAuthenticated = true; | |||
| useEffect(() => { | |||
| if (!isUserAuthenticated) { | |||
| dispatch(authenticateUser()); | |||
| } else { | |||
| dispatch(fetchProfile(userId)) | |||
| } | |||
| }, [isUserAuthenticated]); // eslint-disable-line | |||
| @@ -3,7 +3,6 @@ import PropTypes from 'prop-types' | |||
| import { OptionIcon, OptionStyled } from './Option.styled' | |||
| const Option = props => { | |||
| console.log(props) | |||
| return ( | |||
| <OptionStyled {...props} value={props.value} > | |||
| {props.startIcon ? ( | |||
| @@ -16,6 +16,7 @@ const Select = (props) => { | |||
| fullWidth={props.fullwidth} | |||
| open={isOpened} | |||
| onClick={() => handleOpen()} | |||
| value={props.value} | |||
| width={props.width} | |||
| height={props.height} | |||
| className={props.className} | |||
| @@ -39,6 +40,7 @@ Select.propTypes = { | |||
| defaultValue: PropTypes.number, | |||
| className: PropTypes.string, | |||
| onChange: PropTypes.func, | |||
| value: PropTypes.any, | |||
| }; | |||
| Select.defaultProps = { | |||
| fullwidth: true, | |||
| @@ -1,8 +1,8 @@ | |||
| import React, { useEffect, useRef, useState } from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import { TextFieldContainer, TextFieldStyled } from "./TextField.styled"; | |||
| import PropTypes from "prop-types"; | |||
| export const TextField = (props) => { | |||
| export const TextField = React.forwardRef((props, ref) => { | |||
| const [isFieldEmpty, setIsFieldEmpty] = useState(true); | |||
| // for italicPlaceholder | |||
| @@ -13,7 +13,6 @@ export const TextField = (props) => { | |||
| setIsFieldEmpty(false); | |||
| } | |||
| }, [props.value]); | |||
| const textInputRef = useRef(); | |||
| return ( | |||
| <TextFieldContainer | |||
| @@ -22,6 +21,7 @@ export const TextField = (props) => { | |||
| height={props.height} | |||
| > | |||
| <TextFieldStyled | |||
| inputRef={ref} | |||
| placeholder={props.placeholder} | |||
| width={props.width} | |||
| height={props.height} | |||
| @@ -46,14 +46,15 @@ export const TextField = (props) => { | |||
| italicplaceholder={(props.italicPlaceholder && isFieldEmpty) ? "true" : "false"} | |||
| ref={textInputRef} | |||
| focused={props.focused} | |||
| > | |||
| {props.children} | |||
| </TextFieldStyled> | |||
| </TextFieldContainer> | |||
| ); | |||
| }; | |||
| }); | |||
| TextField.displayName = "TextField"; | |||
| TextField.propTypes = { | |||
| history: PropTypes.shape({ | |||
| @@ -87,8 +88,8 @@ TextField.propTypes = { | |||
| ref: PropTypes.any, | |||
| minRows: PropTypes.number, | |||
| multiline: PropTypes.bool, | |||
| onFocus: PropTypes.onFocus, | |||
| onBlur: PropTypes.onBlur, | |||
| onFocus: PropTypes.func, | |||
| onBlur: PropTypes.func, | |||
| focused: PropTypes.bool, | |||
| InputProps: PropTypes.shape({ | |||
| startAdornment: PropTypes.node, | |||
| @@ -2,14 +2,13 @@ import { useEffect, useState } from "react"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { sortEnum } from "../enums/sortEnum"; | |||
| import { fetchCategories } from "../store/actions/categories/categoriesActions"; | |||
| import { | |||
| setFilteredCategory, | |||
| setFilteredLocations, | |||
| setFilteredSortOption, | |||
| setFilteredSubcategory, | |||
| setIsAppliedStatus, | |||
| setQueryString, | |||
| } from "../store/actions/filters/filtersActions"; | |||
| import { fetchLocations } from "../store/actions/locations/locationsActions"; | |||
| import { | |||
| @@ -18,22 +17,22 @@ import { | |||
| } from "../store/selectors/categoriesSelectors"; | |||
| import { | |||
| selectAppliedStatus, | |||
| selectQueryString, | |||
| selectSelectedCategory, | |||
| selectSelectedLocations, | |||
| selectSelectedSortOption, | |||
| selectSelectedSubcategory, | |||
| } from "../store/selectors/filtersSelectors"; | |||
| import qs from "query-string"; | |||
| import { selectLocations } from "../store/selectors/locationsSelectors"; | |||
| import { fetchOffers } from "../store/actions/offers/offersActions"; | |||
| import { HOME_PAGE } from "../constants/pages"; | |||
| import { convertQueryString } from "../util/helpers/queryHelpers"; | |||
| const useFilters = () => { | |||
| const [queryString, setQueryString] = useState(); | |||
| const queryString = useSelector(selectQueryString); | |||
| const selectedCategory = useSelector(selectSelectedCategory); | |||
| const selectedSubcategory = useSelector(selectSelectedSubcategory); | |||
| const selectedLocations = useSelector(selectSelectedLocations); | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const [loadedFromQS, setLoadedFromQS] = useState(false); | |||
| const [loaded, setLoadedStatus] = useState(false); | |||
| const isApplied = useSelector(selectAppliedStatus); | |||
| @@ -43,7 +42,6 @@ const useFilters = () => { | |||
| selectSubcategories(selectedCategory?.name) | |||
| ); | |||
| const locations = useSelector(selectLocations); | |||
| const sortOptions = sortEnum; | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| if (!loaded) { | |||
| @@ -86,17 +84,17 @@ const useFilters = () => { | |||
| setSelectedLocations([...locationsToPush]); | |||
| } | |||
| // For future changes if needed | |||
| if (queryObject._des_date || queryObject._des_popular) { | |||
| if (queryObject._des_date === "false") { | |||
| setSelectedSortOption(sortOptions.OLD); | |||
| } | |||
| if (queryObject._des_date === "true") { | |||
| setSelectedSortOption(sortOptions.NEW); | |||
| } | |||
| if (queryObject._des_popular === "true") { | |||
| setSelectedSortOption(sortOptions.POPULAR); | |||
| } | |||
| } | |||
| // if (queryObject.sortBy) { | |||
| // if (queryObject.sortBy === sortOptions.OLD.queryString) { | |||
| // setSelectedSortOption(sortOptions.OLD); | |||
| // } | |||
| // if (queryObject.sortBy === sortOptions.NEW.queryString) { | |||
| // setSelectedSortOption(sortOptions.NEW); | |||
| // } | |||
| // if (queryObject.sortBy === sortOptions.POPULAR.queryString) { | |||
| // setSelectedSortOption(sortOptions.POPULAR); | |||
| // } | |||
| // } | |||
| setLoadedFromQS(true); | |||
| dispatch(setIsAppliedStatus(true)); | |||
| } | |||
| @@ -105,59 +103,22 @@ const useFilters = () => { | |||
| useEffect(() => { | |||
| if (loadedFromQS) { | |||
| let qsArray = []; | |||
| if (selectedCategory && selectedCategory?._id !== 0) { | |||
| qsArray.push("category=" + encodeURIComponent(selectedCategory.name)); | |||
| } | |||
| if (selectedSubcategory && selectedSubcategory?._id !== 0) { | |||
| qsArray.push( | |||
| "subcategory=" + encodeURIComponent(selectedSubcategory.name) | |||
| ); | |||
| } | |||
| if (selectedLocations && selectedLocations?.length > 0) { | |||
| selectedLocations.forEach((location) => { | |||
| qsArray.push("location=" + encodeURIComponent(location.city)); | |||
| }); | |||
| } | |||
| if (selectedSortOption) { | |||
| let _des_date = null; | |||
| let _des_popular = null; | |||
| if (selectedSortOption === sortOptions.NEW) { | |||
| _des_date = true; | |||
| } | |||
| if (selectedSortOption === sortOptions.OLD) { | |||
| _des_date = false; | |||
| } | |||
| if (selectedSortOption === sortOptions.POPULAR) { | |||
| _des_popular = true; | |||
| } | |||
| if (_des_date !== null) | |||
| qsArray.push("_des_date=" + encodeURIComponent(_des_date)); | |||
| if (_des_popular !== null) | |||
| qsArray.push("_des_popular=" + encodeURIComponent(_des_popular)); | |||
| } | |||
| let qsStringFromArray = "?" + qsArray.join("&"); | |||
| console.log(qsStringFromArray); | |||
| setQueryString(qsStringFromArray); | |||
| makeQueryString(); | |||
| } | |||
| }, [ | |||
| selectedCategory, | |||
| selectedLocations, | |||
| selectedSortOption, | |||
| selectedSubcategory, | |||
| loadedFromQS, | |||
| ]); | |||
| useEffect(() => { | |||
| console.log("QS: ", queryString); | |||
| }, [queryString]); | |||
| // Apply everything | |||
| const applyFilters = () => { | |||
| dispatch(setIsAppliedStatus(true)); | |||
| dispatch(fetchOffers({ queryString: queryString })); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: queryString, | |||
| search: convertQueryString(queryString), | |||
| }); | |||
| window.scrollTo({ | |||
| top: 0, | |||
| @@ -165,12 +126,41 @@ const useFilters = () => { | |||
| }); | |||
| }; | |||
| // Clear function | |||
| const clearFilters = () => { | |||
| setSelectedLocations([]); | |||
| setSelectedSubcategory(); | |||
| setSelectedCategory(); | |||
| }; | |||
| // Helper function | |||
| const makeQueryString = () => { | |||
| let qsArray = []; | |||
| if (selectedCategory && selectedCategory?._id !== 0) { | |||
| qsArray.push("category=" + encodeURIComponent(selectedCategory.name)); | |||
| } | |||
| if (selectedSubcategory && selectedSubcategory?._id !== 0) { | |||
| qsArray.push( | |||
| "subcategory=" + encodeURIComponent(selectedSubcategory.name) | |||
| ); | |||
| } | |||
| if (selectedLocations && selectedLocations?.length > 0) { | |||
| selectedLocations.forEach((location) => { | |||
| qsArray.push("location=" + encodeURIComponent(location.city)); | |||
| }); | |||
| } | |||
| let qsStringFromArray = "?" + qsArray.join("&"); | |||
| // if (queryString.length > 1) { | |||
| // dispatch(setQueryString(queryString + "&" + qsStringFromArray)); | |||
| // } else { | |||
| // let queryStringEdited = new URLSearchParams(qsStringFromArray); | |||
| dispatch(setQueryString(qsStringFromArray)); | |||
| // } | |||
| }; | |||
| // Setters | |||
| const setSelectedCategory = (payload) => { | |||
| if (isApplied !== false) { | |||
| dispatch(setIsAppliedStatus(false)); | |||
| @@ -189,12 +179,6 @@ const useFilters = () => { | |||
| } | |||
| dispatch(setFilteredLocations(payload)); | |||
| }; | |||
| const setSelectedSortOption = (payload) => { | |||
| if (isApplied !== false) { | |||
| dispatch(setIsAppliedStatus(false)); | |||
| } | |||
| dispatch(setFilteredSortOption(payload)); | |||
| }; | |||
| return { | |||
| selectedCategory, | |||
| setSelectedCategory, | |||
| @@ -202,16 +186,14 @@ const useFilters = () => { | |||
| setSelectedSubcategory, | |||
| selectedLocations, | |||
| setSelectedLocations, | |||
| selectedSortOption, | |||
| setSelectedSortOption, | |||
| categories, | |||
| subcategories, | |||
| locations, | |||
| sortOptions, | |||
| queryString, | |||
| applyFilters, | |||
| clearFilters, | |||
| isApplied, | |||
| makeQueryString | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,17 @@ | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { fetchOffers } from "../store/actions/offers/offersActions"; | |||
| import { selectQueryString } from "../store/selectors/filtersSelectors"; | |||
| export const useSearch = () => { | |||
| const dispatch = useDispatch(); | |||
| const queryString = useSelector(selectQueryString); | |||
| const searchOffers = (searchString) => { | |||
| const queryObject = new URLSearchParams(queryString); | |||
| queryObject.set('oname', searchString); | |||
| dispatch(fetchOffers({queryString: "?" + queryObject.toString()})); | |||
| } | |||
| return { | |||
| searchOffers | |||
| } | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../constants/pages"; | |||
| import { sortEnum } from "../enums/sortEnum"; | |||
| import { setFilteredSortOption} from "../store/actions/filters/filtersActions"; | |||
| import { fetchOffers } from "../store/actions/offers/offersActions"; | |||
| import qs from "query-string"; | |||
| import { | |||
| selectQueryString, | |||
| selectSelectedSortOption, | |||
| } from "../store/selectors/filtersSelectors"; | |||
| import useFilters from "./useFilters"; | |||
| const useSorting = () => { | |||
| // Local query string is query string used for fetching data from API | |||
| const [localQueryString, setLocalQueryString] = useState(""); | |||
| // Global query string is query string shown for user (more user-friendly) | |||
| const [globalQueryString, setGlobalQueryString] = useState(""); | |||
| const history = useHistory(); | |||
| const filters = useFilters(); | |||
| const queryString = useSelector(selectQueryString); | |||
| const dispatch = useDispatch(); | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const sortOptions = sortEnum; | |||
| useEffect(() => { | |||
| if (localQueryString.length > 0) { | |||
| if (queryString.length > 1) { | |||
| let queryStringEdited = new URLSearchParams(queryString); | |||
| queryStringEdited.delete('_des_date'); | |||
| queryStringEdited.delete('_des_popular'); | |||
| dispatch( | |||
| fetchOffers({ queryString: "?" + queryStringEdited + "&" + localQueryString }) | |||
| ); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: "?" + queryStringEdited + "&" + globalQueryString, | |||
| }); | |||
| } else { | |||
| dispatch(fetchOffers({queryString: "?" + localQueryString})); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: "?" + globalQueryString, | |||
| }); | |||
| } | |||
| window.scrollTo({ | |||
| top: 0, | |||
| behavior: "smooth", | |||
| }); | |||
| } | |||
| }, [localQueryString]); | |||
| useEffect(() => { | |||
| const queryStringFromUrl = history.location.search.substring(1); | |||
| const queryObjectFromUrl = qs.parse(queryStringFromUrl); | |||
| if (queryObjectFromUrl?.sortBy) { | |||
| if (queryObjectFromUrl.sortBy === sortOptions.OLD.queryString) { | |||
| setSelectedSortOption(sortOptions.OLD) | |||
| } | |||
| if (queryObjectFromUrl.sortBy === sortOptions.NEW.queryString) { | |||
| setSelectedSortOption(sortOptions.NEW) | |||
| } | |||
| if (queryObjectFromUrl.sortBy === sortOptions.POPULAR.queryString) { | |||
| setSelectedSortOption(sortOptions.POPULAR) | |||
| } | |||
| } else { | |||
| setSelectedSortOption(sortOptions.INITIAL) | |||
| } | |||
| setGlobalQueryString(qs.stringify(queryObjectFromUrl)); | |||
| filters.makeQueryString(); | |||
| }, [history.location.search]); | |||
| const setSelectedSortOption = (payload) => { | |||
| dispatch(setFilteredSortOption(payload)); | |||
| let _des_date = null; | |||
| let _des_popular = null; | |||
| if (payload === sortOptions.NEW) { | |||
| setGlobalQueryString(`sortBy=${sortOptions.NEW.queryString}`); | |||
| _des_date = true; | |||
| } | |||
| if (payload === sortOptions.OLD) { | |||
| setGlobalQueryString(`sortBy=${sortOptions.OLD.queryString}`); | |||
| _des_date = false; | |||
| } | |||
| if (payload === sortOptions.POPULAR) { | |||
| setGlobalQueryString(`sortBy=${sortOptions.POPULAR.queryString}`); | |||
| _des_popular = true; | |||
| } | |||
| if (_des_date !== null) { | |||
| setLocalQueryString(`_des_date=${_des_date}`); | |||
| } | |||
| if (_des_popular !== null) { | |||
| setLocalQueryString(`_des_popular=${_des_popular}`); | |||
| } | |||
| }; | |||
| return { | |||
| selectedSortOption, | |||
| setSelectedSortOption, | |||
| sortOptions, | |||
| }; | |||
| }; | |||
| export default useSorting; | |||
| @@ -10,6 +10,7 @@ export const MainLayoutContainer = styled(Container)` | |||
| display: flex; | |||
| flex: 1; | |||
| height: 100%; | |||
| margin-top: 80px; | |||
| ` | |||
| export const LeftCard = styled(Grid)` | |||
| @@ -10,6 +10,7 @@ export const ProfileLayoutContainer = styled(Container)` | |||
| max-width: none; | |||
| flex: 1; | |||
| height: 100%; | |||
| margin-top: 80px; | |||
| ` | |||
| export const LeftCard = styled(Grid)` | |||
| @@ -8,9 +8,9 @@ import * as Yup from "yup"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| clearLoginErrors, | |||
| fetchUser, | |||
| fetchLogin, | |||
| } from "../../store/actions/login/loginActions"; | |||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||
| import { selectLoginError, selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg"; | |||
| @@ -34,11 +34,13 @@ import { | |||
| import selectedTheme from "../../themes"; | |||
| import loginValidation from "../../validations/loginValidation"; | |||
| import loginInitialValues from "../../initialValues/loginInitialValues"; | |||
| import { fetchProfile } from "../../store/actions/profile/profileActions"; | |||
| const LoginPage = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const error = useSelector(selectLoginError); | |||
| const userId = useSelector(selectUserId); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| @@ -64,6 +66,7 @@ const LoginPage = ({ history }) => { | |||
| }, []); | |||
| const handleApiResponseSuccess = () => { | |||
| dispatch(fetchProfile(userId)) | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| @@ -73,10 +76,10 @@ const LoginPage = ({ history }) => { | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| const { email, password: password } = values; | |||
| const { email, password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| fetchLogin({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| @@ -118,6 +118,7 @@ export default { | |||
| '/users?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| updateUserRegistration: '/users/{userUid}', | |||
| invite: '/users/invite', | |||
| getProfile: 'users/' | |||
| }, | |||
| applications: { | |||
| application: '/applications/{applicationUid}', | |||
| @@ -0,0 +1,5 @@ | |||
| import { getRequest } from "." | |||
| export const attemptFetchChats = (payload) => { | |||
| return getRequest(`users/${payload}/chat`); | |||
| } | |||
| @@ -13,7 +13,6 @@ const request = axios.create({ | |||
| }); | |||
| export const getRequest = (url, params = null, options = null) => { | |||
| console.log('url: ', url); | |||
| return request.get(url, { params, ...options }); | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| import { getRequest } from "."; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const attemptFetchProfile = (payload) => | |||
| getRequest(apiEndpoints.users.getProfile + payload); | |||
| @@ -0,0 +1,7 @@ | |||
| import { createFetchType } from "../actionHelpers"; | |||
| const CHAT_SCOPE = "CHAT_SCOPE"; | |||
| export const CHAT_FETCH = createFetchType(CHAT_SCOPE); | |||
| export const CHAT_SET = "CHAT_SET"; | |||
| @@ -0,0 +1,10 @@ | |||
| import { CHAT_FETCH, CHAT_SET } from "./chatActionConstants"; | |||
| export const fetchChats = (payload) => ({ | |||
| type: CHAT_FETCH, | |||
| payload, | |||
| }) | |||
| export const setChats = (payload) => ({ | |||
| type: CHAT_SET, | |||
| payload, | |||
| }) | |||
| @@ -8,3 +8,4 @@ export const SET_SUBCATEGORY = "FILTERS_SET_SUBCATEGORY"; | |||
| export const SET_LOCATIONS = "FILTERS_SET_LOCATIONS"; | |||
| export const SET_SORT_OPTION = "FILTERS_SET_SORT_OPTION"; | |||
| export const SET_IS_APPLIED = "FILTERS_SET_IS_APPLIED"; | |||
| export const SET_QUERY_STRING = "FILTERS_SET_QUERY_STRING"; | |||
| @@ -1,4 +1,4 @@ | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_IS_APPLIED, SET_LOCATIONS, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_IS_APPLIED, SET_LOCATIONS, SET_QUERY_STRING, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| export const setFilters = (payload) => ({ | |||
| type: SET_FILTERS, | |||
| @@ -26,4 +26,8 @@ export const setFilteredSortOption = (payload) => ({ | |||
| export const setIsAppliedStatus = (payload) => ({ | |||
| type: SET_IS_APPLIED, | |||
| payload, | |||
| }) | |||
| export const setQueryString = (payload) => ({ | |||
| type: SET_QUERY_STRING, | |||
| payload, | |||
| }) | |||
| @@ -14,7 +14,7 @@ import { | |||
| } from './loginActionConstants'; | |||
| export const fetchUser = (payload) => ({ | |||
| export const fetchLogin = (payload) => ({ | |||
| type: LOGIN_USER_FETCH, | |||
| payload, | |||
| }); | |||
| @@ -1,6 +1,7 @@ | |||
| import { createClearType, createErrorType, createFetchType, createSuccessType } from "../actionHelpers"; | |||
| const OFFERS_SCOPE = "OFFERS_SCOPE"; | |||
| const OFFERS_MORE_SCOPE = "OFFERS_MORE_SCOPE"; | |||
| export const OFFERS_FETCH_MORE = createFetchType(OFFERS_MORE_SCOPE); | |||
| @@ -9,7 +10,10 @@ export const OFFERS_SUCCESS = createSuccessType(OFFERS_SCOPE); | |||
| export const OFFERS_ERROR = createErrorType(OFFERS_SCOPE); | |||
| export const OFFERS_CLEAR = createClearType(OFFERS_SCOPE); | |||
| export const OFFERS_PINNED_SET = "OFFERS_PINNED_SET"; | |||
| export const OFFERS_PINNED_ADD = "OFFERS_PINNED_ADD"; | |||
| export const OFFERS_SET = "OFFERS_SET"; | |||
| export const OFFERS_ADD = "OFFERS_ADD"; | |||
| export const OFFER_ADD = "OFFER_ADD"; | |||
| export const OFFERS_NO_MORE = "OFFERS_NO_MORE"; | |||
| @@ -1,4 +1,4 @@ | |||
| import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_FETCH_MORE, OFFERS_NO_MORE, OFFERS_SET, OFFERS_SUCCESS, OFFER_ADD } from "./offersActionConstants"; | |||
| import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_FETCH_MORE, OFFERS_NO_MORE, OFFERS_PINNED_ADD, OFFERS_PINNED_SET, OFFERS_SET, OFFERS_SUCCESS} from "./offersActionConstants"; | |||
| export const fetchOffers = (payload) => ({ | |||
| type: OFFERS_FETCH, | |||
| @@ -19,12 +19,16 @@ export const setOffers = (payload) => ({ | |||
| type: OFFERS_SET, | |||
| payload, | |||
| }) | |||
| export const addOffers = (payload) => ({ | |||
| type: OFFERS_ADD, | |||
| export const setPinnedOffers = (payload) => ({ | |||
| type: OFFERS_PINNED_SET, | |||
| payload | |||
| }) | |||
| export const addOffer = (payload) => ({ | |||
| type: OFFER_ADD, | |||
| export const addPinnedOffers = (payload) => ({ | |||
| type: OFFERS_PINNED_ADD, | |||
| payload, | |||
| }) | |||
| export const addOffers = (payload) => ({ | |||
| type: OFFERS_ADD, | |||
| payload | |||
| }) | |||
| export const fetchMoreOffers = (payload) => ({ | |||
| @@ -0,0 +1,8 @@ | |||
| import { createErrorType, createFetchType, createSuccessType } from "../actionHelpers"; | |||
| const PROFILE_SCOPE = "PROFILE_SCOPE"; | |||
| export const PROFILE_FETCH = createFetchType(PROFILE_SCOPE); | |||
| export const PROFILE_SUCCESS = createSuccessType(PROFILE_SCOPE); | |||
| export const PROFILE_ERROR = createErrorType(PROFILE_SCOPE); | |||
| export const PROFILE_SET = "PROFILE_SET"; | |||
| @@ -0,0 +1,19 @@ | |||
| import { PROFILE_ERROR, PROFILE_FETCH, PROFILE_SET, PROFILE_SUCCESS } from "./profileActionConstants"; | |||
| export const fetchProfile = (payload) => ({ | |||
| type: PROFILE_FETCH, | |||
| payload | |||
| }) | |||
| export const fetchSuccessProfile = (payload) => ({ | |||
| type: PROFILE_SUCCESS, | |||
| payload, | |||
| }) | |||
| export const fetchErrorProfile = (payload) => ({ | |||
| type: PROFILE_ERROR, | |||
| payload, | |||
| }) | |||
| export const setProfile = (payload) => ({ | |||
| type: PROFILE_SET, | |||
| payload, | |||
| }) | |||
| @@ -0,0 +1,20 @@ | |||
| import { CHAT_SET } from "../../actions/chat/chatActionConstants" | |||
| import createReducer from "../../utils/createReducer" | |||
| const initialState = { | |||
| latestChats: [], | |||
| } | |||
| export default createReducer( | |||
| { | |||
| [CHAT_SET]: setChats, | |||
| }, | |||
| initialState | |||
| ) | |||
| function setChats(state, action) { | |||
| return { | |||
| ...state, | |||
| latestChats: action.payload | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import { | |||
| SET_FILTERS, | |||
| SET_IS_APPLIED, | |||
| SET_LOCATIONS, | |||
| SET_QUERY_STRING, | |||
| SET_SORT_OPTION, | |||
| SET_SUBCATEGORY, | |||
| } from "../../actions/filters/filtersActionConstants"; | |||
| @@ -16,6 +17,7 @@ const initialState = { | |||
| locations: [], | |||
| sortOption: null, | |||
| isApplied: false, | |||
| queryString: "", | |||
| }, | |||
| }; | |||
| @@ -28,6 +30,7 @@ export default createReducer( | |||
| [SET_LOCATIONS]: setFilteredLocations, | |||
| [SET_SORT_OPTION]: setFilteredSortOption, | |||
| [SET_IS_APPLIED]: setIsAppliedStatus, | |||
| [SET_QUERY_STRING]: setQueryString, | |||
| }, | |||
| initialState | |||
| ); | |||
| @@ -90,3 +93,12 @@ function setIsAppliedStatus(state, {payload}) { | |||
| } | |||
| } | |||
| } | |||
| function setQueryString(state, {payload}) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| queryString: payload, | |||
| } | |||
| } | |||
| } | |||
| @@ -10,6 +10,8 @@ import filtersReducer from "./filters/filtersReducer"; | |||
| import offersReducer from "./offers/offersReducer"; | |||
| import categoriesReducer from "./categories/categoriesReducer"; | |||
| import locationsReducer from "./locations/locationsReducer"; | |||
| import profileReducer from "./profile/profileReducer"; | |||
| import chatReducer from "./chat/chatReducer"; | |||
| const loginPersistConfig = { | |||
| key: "login", | |||
| @@ -50,6 +52,16 @@ const locationsPersistConfig = { | |||
| storage: storage, | |||
| transform: [createFilter("locations", ["_id", "city"])], | |||
| }; | |||
| const profilePersistConfig = { | |||
| key: "profile", | |||
| storage: storage, | |||
| transform: [createFilter("profile", ["profile"])], | |||
| }; | |||
| const chatPersistConfig = { | |||
| key: "chat", | |||
| storage: storage, | |||
| transform: [createFilter("chat", ["latestChats"])] | |||
| } | |||
| export default combineReducers({ | |||
| login: persistReducer(loginPersistConfig, loginReducer), | |||
| @@ -60,4 +72,6 @@ export default combineReducers({ | |||
| offers: offersReducer, | |||
| categories: persistReducer(categoriesPersistConfig, categoriesReducer), | |||
| locations: persistReducer(locationsPersistConfig, locationsReducer), | |||
| profile: persistReducer(profilePersistConfig, profileReducer), | |||
| chat: persistReducer(chatPersistConfig, chatReducer) | |||
| }); | |||
| @@ -3,13 +3,15 @@ import { | |||
| OFFERS_CLEAR, | |||
| OFFERS_ERROR, | |||
| OFFERS_NO_MORE, | |||
| OFFERS_PINNED_ADD, | |||
| OFFERS_PINNED_SET, | |||
| OFFERS_SET, | |||
| OFFER_ADD, | |||
| } from "../../actions/offers/offersActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| offers: [], | |||
| pinnedOffers: [], | |||
| error: "", | |||
| newOffer: "", | |||
| noMoreOffers: false, | |||
| @@ -21,8 +23,9 @@ export default createReducer( | |||
| [OFFERS_CLEAR]: clearOffers, | |||
| [OFFERS_SET]: setOffers, | |||
| [OFFERS_ADD]: addOffers, | |||
| [OFFER_ADD]: addOffer, | |||
| [OFFERS_NO_MORE]: setNoMoreOffersStatus | |||
| [OFFERS_NO_MORE]: setNoMoreOffersStatus, | |||
| [OFFERS_PINNED_ADD]: addPinnedOffers, | |||
| [OFFERS_PINNED_SET]: setPinnedOffers, | |||
| }, | |||
| initialState | |||
| ); | |||
| @@ -46,10 +49,16 @@ function addOffers(state, action) { | |||
| offers: [...state.offers, ...action.payload] | |||
| } | |||
| } | |||
| function addOffer(state, action) { | |||
| function setPinnedOffers(state, action) { | |||
| return { | |||
| ...state, | |||
| offer: action.payload | |||
| pinnedOffers: [...action.payload] | |||
| } | |||
| } | |||
| function addPinnedOffers(state, action) { | |||
| return { | |||
| ...state, | |||
| pinnedOffers: [...state.pinnedOffers, ...action.payload] | |||
| } | |||
| } | |||
| function setNoMoreOffersStatus(state, action) { | |||
| @@ -0,0 +1,20 @@ | |||
| import { PROFILE_SET } from "../../actions/profile/profileActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| profile: {}, | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [PROFILE_SET]: setProfile, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setProfile(state, action) { | |||
| return { | |||
| ...state, | |||
| profile: action.payload, | |||
| }; | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| import { all, takeLatest, call, put } from "@redux-saga/core/effects"; | |||
| import { attemptFetchChats } from "../../request/chatRequest"; | |||
| import { CHAT_FETCH } from "../actions/chat/chatActionConstants"; | |||
| import { setChats } from "../actions/chat/chatActions"; | |||
| function* fetchChats(payload) { | |||
| try { | |||
| const data = yield call(attemptFetchChats, payload.payload); | |||
| console.log(data.data); | |||
| yield put(setChats(data.data)); | |||
| } catch(e) { | |||
| console.log(e); | |||
| } | |||
| } | |||
| export default function* chatSaga() { | |||
| yield all([ | |||
| takeLatest(CHAT_FETCH, fetchChats) | |||
| ]); | |||
| } | |||
| @@ -4,7 +4,6 @@ import { FORGOT_PASSWORD, RESET_PASSWORD } from "../actions/user/userActionConst | |||
| function* forgotPassword({payload}) { | |||
| try { | |||
| console.log(payload) | |||
| const data = yield call(forgotPasswordRequest, payload.email); | |||
| if (data) { | |||
| if (payload.handleResponseSuccess) { | |||
| @@ -1,9 +1,11 @@ | |||
| import { all } from 'redux-saga/effects'; | |||
| import categoriesSaga from './categoriesSaga'; | |||
| import chatSaga from './chatSaga'; | |||
| import forgotPasswordSaga from './forgotPasswordSaga'; | |||
| import locationsSaga from './locationsSaga'; | |||
| import loginSaga from './loginSaga'; | |||
| import offersSaga from './offersSaga'; | |||
| import profileSaga from './profileSaga'; | |||
| import registerSaga from './registerSaga'; | |||
| export default function* rootSaga() { | |||
| @@ -13,6 +15,8 @@ export default function* rootSaga() { | |||
| forgotPasswordSaga(), | |||
| offersSaga(), | |||
| categoriesSaga(), | |||
| locationsSaga() | |||
| locationsSaga(), | |||
| profileSaga(), | |||
| chatSaga() | |||
| ]); | |||
| } | |||
| @@ -5,7 +5,6 @@ import { setLocations } from "../actions/locations/locationsActions"; | |||
| function* fetchLocations() { | |||
| const {data} = yield call(attemptFetchLocations) | |||
| console.log(data); | |||
| yield put(setLocations(data)); | |||
| } | |||
| @@ -33,7 +33,7 @@ import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper" | |||
| import { setUserAccessToken } from "../actions/user/userActions"; | |||
| import i18next from "i18next"; | |||
| function* fetchUser({ payload }) { | |||
| function* fetchLogin({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptLogin, payload); | |||
| if (data.token) { | |||
| @@ -95,7 +95,7 @@ function* authenticateUser() { | |||
| } | |||
| } | |||
| function* logoutUser() { | |||
| function* logout() { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| const user = jwt.decode(JwtToken); | |||
| @@ -125,9 +125,9 @@ function* refreshUserToken({payload}) { | |||
| export default function* loginSaga() { | |||
| yield all([ | |||
| takeLatest(LOGIN_USER_FETCH, fetchUser), | |||
| takeLatest(LOGIN_USER_FETCH, fetchLogin), | |||
| takeLatest(AUTHENTICATE_USER, authenticateUser), | |||
| takeLatest(LOGOUT_USER, logoutUser), | |||
| takeLatest(LOGOUT_USER, logout), | |||
| takeLatest(REFRESH_TOKEN, refreshUserToken) | |||
| ]); | |||
| } | |||
| @@ -1,19 +1,20 @@ | |||
| import { all, takeLatest, call, put } from "@redux-saga/core/effects"; | |||
| import { | |||
| attemptAddOffer, | |||
| attemptFetchMoreOffers, | |||
| attemptFetchOffers, | |||
| } from "../../request/offersRequest"; | |||
| import { setQueryString } from "../actions/filters/filtersActions"; | |||
| import { | |||
| OFFERS_FETCH, | |||
| OFFERS_FETCH_MORE, | |||
| OFFER_ADD, | |||
| } from "../actions/offers/offersActionConstants"; | |||
| import { | |||
| addOffers, | |||
| addPinnedOffers, | |||
| clearOffers, | |||
| setNoMoreOffersStatus, | |||
| setOffers, | |||
| setPinnedOffers, | |||
| } from "../actions/offers/offersActions"; | |||
| function* fetchOffers(payload) { | |||
| @@ -21,12 +22,23 @@ function* fetchOffers(payload) { | |||
| yield put(clearOffers()); | |||
| yield put(setNoMoreOffersStatus(false)); | |||
| const data = yield call(attemptFetchOffers, payload.payload.queryString); | |||
| if (data.data.items.length < 10) { | |||
| if ( | |||
| data.data.items.pinnedOffers.length + | |||
| data.data.items.regularOffers.length < | |||
| 10 || | |||
| data.data.items.pinnedOffers.length + | |||
| data.data.items.regularOffers.length > | |||
| data.data.total | |||
| ) { | |||
| yield put(setNoMoreOffersStatus(true)); | |||
| } | |||
| yield put(setOffers(data.data.items)); | |||
| if (payload.payload.queryString) { | |||
| yield put(setQueryString(payload.payload.queryString)); | |||
| } | |||
| yield put(setOffers(data.data.items.regularOffers)); | |||
| yield put(setPinnedOffers(data.data.items.pinnedOffers)); | |||
| } catch (e) { | |||
| console.log(e); | |||
| yield call(console.log, e); | |||
| } | |||
| } | |||
| function* fetchMoreOffers(payload) { | |||
| @@ -36,8 +48,18 @@ function* fetchMoreOffers(payload) { | |||
| payload.payload?.page, | |||
| payload.payload?.queryString | |||
| ); | |||
| yield put(addOffers(data.data.items)); | |||
| if (data.data.items.length < 10) { | |||
| if (payload.payload.queryString) { | |||
| yield put(setQueryString(payload.payload.queryString)); | |||
| } | |||
| console.log(data.data.items); | |||
| yield put(addOffers(data.data.items.regularOffers)); | |||
| yield put(addPinnedOffers(data.data.items.pinnedOffers)); | |||
| if ( | |||
| data.data.items.pinnedOffers + data.data.items.regularOffers < 10 || | |||
| data.data.items.pinnedOffers.length + | |||
| data.data.items.regularOffers.length > | |||
| data.data.total | |||
| ) { | |||
| yield put(setNoMoreOffersStatus(true)); | |||
| } | |||
| } catch (e) { | |||
| @@ -45,19 +67,9 @@ function* fetchMoreOffers(payload) { | |||
| } | |||
| } | |||
| function* createOffer(payload) { | |||
| try { | |||
| const data = yield call(attemptAddOffer, payload); | |||
| console.log(data); | |||
| } catch (e) { | |||
| console.log(e); | |||
| } | |||
| } | |||
| export default function* offersSaga() { | |||
| yield all([ | |||
| takeLatest(OFFERS_FETCH, fetchOffers), | |||
| takeLatest(OFFERS_FETCH_MORE, fetchMoreOffers), | |||
| takeLatest(OFFER_ADD, createOffer), | |||
| ]); | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import { attemptFetchProfile } from "../../request/profileRequest"; | |||
| import { PROFILE_FETCH } from "../actions/profile/profileActionConstants"; | |||
| import { setProfile } from "../actions/profile/profileActions"; | |||
| function* fetchProfile(payload) { | |||
| try { | |||
| console.log(payload); | |||
| const data = yield call(attemptFetchProfile, payload.payload); | |||
| console.log(data.data); | |||
| if (data) yield put(setProfile(data.data)); | |||
| } | |||
| catch(e) { | |||
| console.log(e); | |||
| } | |||
| } | |||
| export default function* profileSaga() { | |||
| yield all([ | |||
| takeLatest(PROFILE_FETCH, fetchProfile) | |||
| ]) | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| import { createSelector } from "reselect"; | |||
| const chatSelector = (state) => state.chat; | |||
| export const selectLatestChats = createSelector( | |||
| chatSelector, | |||
| (state) => state.latestChats | |||
| ) | |||
| @@ -26,3 +26,7 @@ export const selectAppliedStatus = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.isApplied | |||
| ) | |||
| export const selectQueryString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.queryString | |||
| ) | |||
| @@ -21,8 +21,10 @@ export const selectJWTToken = createSelector( | |||
| loginSelector, | |||
| (state) => state.token, | |||
| ); | |||
| export const selectUserId = createSelector( | |||
| loginSelector, | |||
| (state) => state.token.userId | |||
| ) | |||
| export const selectLoginError = createSelector( | |||
| loginSelector, | |||
| (state) => state.errorMessage, | |||
| @@ -15,3 +15,7 @@ export const selectNoMoreOffers = createSelector( | |||
| offersSelector, | |||
| (state) => state.noMoreOffers | |||
| ) | |||
| export const selectPinnedOffers = createSelector( | |||
| offersSelector, | |||
| (state) => state.pinnedOffers | |||
| ) | |||
| @@ -0,0 +1,12 @@ | |||
| import { createSelector } from "reselect"; | |||
| const profileSelector = (state) => state.profile.profile; | |||
| export const selectProfileName = createSelector( | |||
| profileSelector, | |||
| (state) => state?.company?.name | |||
| ) | |||
| export const selectProfile = createSelector( | |||
| profileSelector, | |||
| (state) => state | |||
| ) | |||
| @@ -10,8 +10,10 @@ export function authScopeGetHelper(key) { | |||
| export function authScopeStringGetHelper(key) { | |||
| if (sessionStorage.getItem(SESSION_STORAGE_SCOPE)) { | |||
| console.log(sessionStorage.getItem(key)) | |||
| return sessionStorage.getItem(key); | |||
| } | |||
| console.log(localStorage.getItem(key)) | |||
| return localStorage.getItem(key); | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| import { sortEnum } from "../../enums/sortEnum"; | |||
| export const convertQueryString = (queryURL) => { | |||
| const queryObject = new URLSearchParams(queryURL); | |||
| const queryObjectToReturn = new URLSearchParams(queryURL); | |||
| if (queryObject.has('_des_date')) { | |||
| queryObjectToReturn.delete('_des_date'); | |||
| if (queryObject.get('_des_date') === 'true') { | |||
| queryObjectToReturn.append('sortBy', sortEnum.NEW.queryString); | |||
| } else { | |||
| queryObjectToReturn.append('sortBy', sortEnum.OLD.queryString); | |||
| } | |||
| } | |||
| if (queryObject.has('_des_popular')) { | |||
| queryObjectToReturn.delete('_des_popular'); | |||
| queryObjectToReturn.append('sortBy', sortEnum.POPULAR.queryString); | |||
| } | |||
| return queryObjectToReturn.toString(); | |||
| } | |||