소스 검색

Finished feature 1459

feature/1459
Djordje Mitrovic 3 년 전
부모
커밋
e110153b4a

+ 1
- 0
src/components/Cards/OfferCard/OfferCard.styled.js 파일 보기

@@ -120,6 +120,7 @@ export const OfferTitle = styled(Typography)`
line-height: 22px;
margin-top: 5px;
font-size: 18px;
max-width: none;
margin-bottom: 0px !important;

`}

+ 2
- 1
src/components/Paging/Paging.js 파일 보기

@@ -24,7 +24,7 @@ const Paging = (props) => {
const threeDotsBefore = props.current - 2 > 1;
const threeDotsAfter = props.current + 2 < pages;
return (
<PagingContainer>
<PagingContainer className={props.className}>
{/* Left arrow */}
<Arrow
onClick={() => props.changePage(props.current - 1)}
@@ -88,6 +88,7 @@ Paging.propTypes = {
current: PropTypes.number,
changePage: PropTypes.func,
customPaging: PropTypes.bool,
className: PropTypes.string,
};
Paging.defaultProps = {
elementsPerPage: 10,

+ 5
- 3
src/components/Profile/Profile.js 파일 보기

@@ -7,7 +7,6 @@ import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useRouteMatch } from "react-router-dom";
import { fetchProfile } from "../../store/actions/profile/profileActions";
import { fetchProfileOffers } from "../../store/actions/offers/offersActions";
import Header from "./Header/Header";

const Profile = (props) => {
@@ -18,7 +17,6 @@ const Profile = (props) => {
useEffect(() => {
if (idProfile?.length > 0) {
dispatch(fetchProfile(idProfile));
dispatch(fetchProfileOffers(idProfile));
}
}, [idProfile]);
const isMyProfile = useMemo(() => {
@@ -28,7 +26,11 @@ const Profile = (props) => {
<ProfileContainer className={props.className}>
<Header isAdmin={props.isAdmin} />
<ProfileCard isAdmin={props.isAdmin} isMyProfile={isMyProfile} />
<ProfileOffers isAdmin={props.isAdmin} isMyProfile={isMyProfile} />
<ProfileOffers
isAdmin={props.isAdmin}
isMyProfile={isMyProfile}
idProfile={idProfile}
/>
</ProfileContainer>
);
};

+ 110
- 42
src/components/Profile/ProfileOffers/ProfileOffers.js 파일 보기

@@ -4,13 +4,17 @@ import {
OffersContainer,
OffersScroller,
ProfileOffersContainer,
ProfilePaging,
SkeletonContainer,
} from "./ProfileOffers.styled";
import { useState } from "react";
import { useEffect } from "react";
import { useSelector } from "react-redux";
// import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import { selectProfileOffers } from "../../../store/selectors/offersSelectors";
import {
selectProfileOffers,
selectTotalOffers,
} from "../../../store/selectors/offersSelectors";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import NoProfileOffers from "./NoProfileOffers.js/NoProfileOffers";
@@ -23,9 +27,16 @@ import SelectSortField from "./SelectSortField/SelectSortField";
import HeaderTitle from "./HeaderTitle/HeaderTitle";
import SearchBar from "./SearchBar/SearchBar";
import { messageUserHelper } from "../../../util/helpers/messageHelper";
import { sortEnum } from "../../../enums/sortEnum";
import usePaging from "../../../hooks/useOffers/usePaging";
import { useEffect } from "react";
import {
fetchProfileOffers,
setProfileOffers,
} from "../../../store/actions/offers/offersActions";
import { useRef } from "react";

const ProfileOffers = (props) => {
const [offersToShow, setOffersToShow] = useState([]);
const isLoadingMineOffers = useSelector(
selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE)
);
@@ -34,25 +45,64 @@ const ProfileOffers = (props) => {
const { isMobile } = useIsMobile();
const userId = useSelector(selectUserId);
const arrayForMapping = Array.apply(null, Array(4)).map(() => {});
const [searchValue, setSearchValue] = useState("");
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [append, setAppend] = useState(true);
const paging = usePaging(null, true);
const total = useSelector(selectTotalOffers);
const dispatch = useDispatch();
const searchRef = useRef(null);

useEffect(() => {
dispatch(
fetchProfileOffers({
idProfile: props.idProfile,
searchValue: searchValue,
sortOption: sortOption,
append: isMobile && append,
page: paging.currentPage,
})
);
setAppend(true);
}, [searchValue, sortOption, paging.currentPage]);

useEffect(() => {
if (
isLoadingMineOffers === false &&
searchRef.current &&
searchRef.current?.searchValue !== searchValue
) {
searchRef.current.changeSearchValue(searchValue);
}
}, [isLoadingMineOffers]);

const messageUser = (offer) => {
messageUserHelper(chats, offer, userId);
};

useEffect(() => {
if (profileOffers) setOffersToShow(profileOffers);
}, [profileOffers]);
const handleInfiniteScroll = () => {
if (total !== profileOffers?.length) {
paging.goToNextPage();
}
};

const handleSearch = (value) => {
let newOffersToShow = profileOffers.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setOffersToShow([...newOffersToShow]);
const handleChangeSortOption = (newValue) => {
dispatch(setProfileOffers([]));
setAppend(true);
paging.changePage(1);
setSortOption(newValue);
};

const handleSearch = (newValue) => {
dispatch(setProfileOffers([]));
paging.changePage(1);
setSearchValue(newValue);
};

return (
<ProfileOffersContainer isAdmin={props.isAdmin}>
{isLoadingMineOffers || isLoadingMineOffers === undefined ? (
{(isLoadingMineOffers || isLoadingMineOffers === undefined) &&
profileOffers?.length === 0 ? (
<>
<ProfileOffersHeaderSkeleton />
{isMobile ? (
@@ -72,46 +122,63 @@ const ProfileOffers = (props) => {
) : (
<OffersContainer>
<SelectSortField
offersToShow={offersToShow}
setOffersToShow={setOffersToShow}
offersToShow={profileOffers}
setSortOption={handleChangeSortOption}
/>
<HeaderTitle isMyProfile={props.isMyProfile && !props.isAdmin} />
<SearchBar handleSearch={handleSearch} />
{!isMobile ? (
offersToShow.length !== 0 ? (
offersToShow.map((item) => (
<OfferCard
isAdmin={props.isAdmin}
isMyOffer={props.isMyProfile || props.isAdmin}
offer={item}
key={JSON.stringify(item)}
messageUser={messageUser}
/>
))
) : (
<NoProfileOffers />
)
) : (
<OffersScroller hideArrows noOffers>
{offersToShow.length !== 0 ? (
offersToShow.map((item) => (
<SearchBar
handleSearch={handleSearch}
value={searchValue}
ref={searchRef}
/>
{!isMobile &&
(profileOffers.length !== 0 ? (
<>
{profileOffers.map((item) => (
<OfferCard
isAdmin={props.isAdmin}
vertical
isMyOffer={props.isMyProfile || props.isAdmin}
offer={item}
key={JSON.stringify(item)}
pinned={item.pinned}
messageUser={messageUser}
/>
))
) : (
<NoProfileOffers />
)}
</OffersScroller>
)}
))}
</>
) : (
<NoProfileOffers />
))}
</OffersContainer>
)}

{isMobile ? (
<OffersScroller
hideArrows
noOffers
infiniteScroll={handleInfiniteScroll}
>
{profileOffers.length !== 0 ? (
profileOffers.map((item) => (
<OfferCard
isAdmin={props.isAdmin}
vertical
isMyOffer={props.isMyProfile || props.isAdmin}
offer={item}
key={JSON.stringify(item)}
pinned={item.pinned}
messageUser={messageUser}
/>
))
) : (
<NoProfileOffers />
)}
</OffersScroller>
) : (
<ProfilePaging
current={paging.currentPage}
changePage={paging.changePage}
totalElements={total}
/>
)}
</ProfileOffersContainer>
);
};
@@ -120,6 +187,7 @@ ProfileOffers.propTypes = {
children: PropTypes.node,
isMyProfile: PropTypes.bool,
isAdmin: PropTypes.bool,
idProfile: PropTypes.string,
};

export default ProfileOffers;

+ 5
- 0
src/components/Profile/ProfileOffers/ProfileOffers.styled.js 파일 보기

@@ -1,5 +1,6 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import Paging from "../../Paging/Paging";
import HorizontalScroller from "../../Scroller/HorizontalScroller";

export const ProfileOffersContainer = styled(Box)`
@@ -35,3 +36,7 @@ export const SkeletonContainer = styled(Box)`
max-width: calc(100vw - 36px);
overflow: hidden;
`;
export const ProfilePaging = styled(Paging)`
position: static;
margin-bottom: 9px;
`

+ 18
- 2
src/components/Profile/ProfileOffers/SearchBar/SearchBar.js 파일 보기

@@ -4,10 +4,21 @@ import { IconContainer, SearchIcon, SearchInput } from "./SearchBar.styled";
import { useCallback } from "react";
import { useRef } from "react";
import { useTranslation } from "react-i18next";
import { forwardRef } from "react";
import { useImperativeHandle } from "react";
import { useState } from "react";

const SearchBar = (props) => {
const SearchBar = forwardRef((props, ref) => {
const searchRef = useRef(null);
const [value, setSearchValue] = useState("");
const { t } = useTranslation();

useImperativeHandle(ref, () => ({
changeSearchValue: (newValue) => {
setSearchValue(newValue);
},
searchValue: value,
}));
let listener = useCallback(
(event) => {
if (event.keyCode === 13) {
@@ -29,6 +40,8 @@ const SearchBar = (props) => {
<SearchInput
fullWidth
ref={searchRef}
value={value}
onChange={(event) => setSearchValue(event.target.value)}
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
placeholder={t("header.searchOffers")}
@@ -44,10 +57,13 @@ const SearchBar = (props) => {
}}
/>
);
};
});

SearchBar.displayName = "SearchBar";

SearchBar.propTypes = {
handleSearch: PropTypes.func,
value: PropTypes.string,
};

export default SearchBar;

+ 1
- 0
src/components/Profile/ProfileOffers/SearchBar/SearchBar.styled.js 파일 보기

@@ -13,6 +13,7 @@ export const SearchInput = styled(TextField)`
& div fieldset {
border-color: ${selectedTheme.colors.primaryPurple} !important;
}
margin-bottom: 24px;
@media (max-width: 600px) {
top: 5px;
height: 46px;

+ 2
- 19
src/components/Profile/ProfileOffers/SelectSortField/SelectSortField.js 파일 보기

@@ -7,34 +7,17 @@ import {
} from "./SelectSortField.styled";
import { sortEnum } from "../../../../enums/sortEnum";
import { useState } from "react";
import { useEffect } from "react";

const SelectSortField = (props) => {
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);

useEffect(() => {
let newOffersToShow = [...props.offersToShow];
if (sortOption.value === sortEnum.OLD.value) {
newOffersToShow.sort(
(a, b) => new Date(a._created) - new Date(b._created)
);
}
if (sortOption.value === sortEnum.NEW.value) {
newOffersToShow.sort(
(a, b) => new Date(b._created) - new Date(a._created)
);
}
if (sortOption.value === sortEnum.POPULAR.value) {
newOffersToShow.sort((a, b) => b.views.count - a.views.count);
}
props.setOffersToShow([...newOffersToShow]);
}, [sortOption]);
const handleChangeSelect = (event) => {
let chosenOption;
for (const sortOption in sortEnum) {
if (sortEnum[sortOption].value === event.target.value) {
chosenOption = sortEnum[sortOption];
setSortOption(chosenOption);
props.setSortOption(chosenOption);
}
}
};
@@ -68,7 +51,7 @@ const SelectSortField = (props) => {

SelectSortField.propTypes = {
offersToShow: PropTypes.array,
setOffersToShow: PropTypes.func,
setSortOption: PropTypes.func,
};

export default SelectSortField;

+ 11
- 0
src/components/Scroller/HorizontalScroller.js 파일 보기

@@ -6,6 +6,7 @@ import {
} from "./HorizontalScroller.styled";
import { ArrowButton } from "../Buttons/ArrowButton/ArrowButton";
import useIsMobile from "../../hooks/useIsMobile";
import { debounceHelper } from "../../util/helpers/debounceHelper";

const HorizontalScroller = (props) => {
const scrollRef = useRef(null);
@@ -13,6 +14,7 @@ const HorizontalScroller = (props) => {
const [isDisabledLeftButton, setIsDisabledLeftButton] = useState(true);
const [isDisabledRightButton, setIsDisabledRightButton] = useState(false);


const handleScroll = (event) => {
if (!event.external) {
if (scrollRef.current.scrollLeft === 0) {
@@ -29,6 +31,14 @@ const HorizontalScroller = (props) => {
if (isDisabledRightButton !== false) setIsDisabledRightButton(false);
}
}
if (props.infiniteScroll) {
if (
scrollRef.current.scrollWidth - scrollRef.current.scrollLeft <
scrollRef.current.clientWidth + 170
) {
debounceHelper(() => props.infiniteScroll(), 500);
}
}
};

const handleRight = () => {
@@ -108,6 +118,7 @@ HorizontalScroller.propTypes = {
listStyle: PropTypes.any,
hideArrows: PropTypes.bool,
isCarousel: PropTypes.bool,
infiniteScroll: PropTypes.bool,
};

export default HorizontalScroller;

+ 7
- 5
src/hooks/useOffers/usePaging.js 파일 보기

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";

const usePaging = (applyAllFilters) => {
const usePaging = (applyAllFilters, disableScroll) => {
const [currentPage, setCurrentPage] = useState(1);
const [isInitallyLoaded, setIsInitiallyLoaded] = useState(false);

@@ -10,10 +10,12 @@ const usePaging = (applyAllFilters) => {
if (isInitallyLoaded && applyAllFilters) {
applyAllFilters();
}
window.scrollTo({
top: 0,
behavior: "smooth",
});
if (!disableScroll) {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
}, [currentPage]);

const changePage = (pageNumber) => {

+ 1
- 0
src/request/apiEndpoints.js 파일 보기

@@ -175,6 +175,7 @@ export default {
categories: "categories",
locations: "locations",
mineOffers: "users",
profileOffers: "users/{userId}/offers",
removeOffer: "/users/{userId}/offers/{offerId}",
removeOfferAsAdmin: "/admin/offers/{offerId}",
pinOffer: "admin/offers/{id}/pin",

+ 6
- 2
src/request/offersRequest.js 파일 보기

@@ -35,8 +35,12 @@ export const attemptAddOffer = (payload, data) => {
data
);
};
export const attemptFetchProfileOffers = (payload) => {
return getRequest(`${apiEndpoints.offers.mineOffers}/${payload}/offers`);
export const attemptFetchProfileOffers = (userId, queryString = "") => {
return getRequest(
replaceInUrl(apiEndpoints.offers.profileOffers, {
userId: userId,
}) + `?${queryString}`
);
};
export const attemptRemoveOffer = (payload, offerId) => {
return deleteRequest(

+ 1
- 0
src/store/actions/offers/offersActionConstants.js 파일 보기

@@ -60,6 +60,7 @@ export const OFFERS_ADD = createSetType("OFFERS_ADD");
export const OFFERS_NO_MORE = createSetType("OFFERS_NO_MORE");
export const OFFERS_SET_TOTAL = createSetType("OFFERS_SET_TOTAL");
export const OFFERS_PROFILE_SET = createSetType("OFFERS_PROFILE_SET");
export const OFFERS_PROFILE_ADD = createSetType("OFFERS_PROFILE_ADD");
export const OFFERS_MINE_SET = createSetType("OFFERS_MY_ADD");
export const OFFER_INDEX_SET = createSetType("OFFER_INDEX_SET");
export const OFFER_INDEX_CLEAR = createClearType("OFFER_INDEX_CLEAR");

+ 5
- 0
src/store/actions/offers/offersActions.js 파일 보기

@@ -22,6 +22,7 @@ import {
OFFERS_NO_MORE,
OFFERS_PINNED_ADD,
OFFERS_PINNED_SET,
OFFERS_PROFILE_ADD,
OFFERS_PROFILE_ERROR,
OFFERS_PROFILE_FETCH,
OFFERS_PROFILE_SET,
@@ -243,6 +244,10 @@ export const setProfileOffers = (payload) => ({
type: OFFERS_PROFILE_SET,
payload,
});
export const addProfileOffers = (payload) => ({
type: OFFERS_PROFILE_ADD,
payload,
});
export const setOffer = (payload) => ({
type: OFFER_SET,
payload,

+ 8
- 0
src/store/reducers/offers/offersReducer.js 파일 보기

@@ -21,6 +21,7 @@ import {
OFFER_FEATURED_PAGE_SET,
OFFER_SELECTED_CLEAR,
OFFERS_HEADER_SET,
OFFERS_PROFILE_ADD,
} from "../../actions/offers/offersActionConstants";
import createReducer from "../../utils/createReducer";

@@ -57,6 +58,7 @@ export default createReducer(
[OFFERS_MINE_SET]: setMineOffers,
[OFFERS_HEADER_SET]: setHeaderOffers,
[OFFERS_PROFILE_SET]: setProfileOffers,
[OFFERS_PROFILE_ADD]: addProfileOffers,
[OFFERS_FEATURED_CLEAR]: clearFeaturedOffers,
[OFFERS_FEATURED_SET]: setFeaturedOffers,
[OFFER_INDEX_SET]: setOfferIndex,
@@ -196,3 +198,9 @@ function setHeaderOffers(state, { payload }) {
mineHeaderOffers: payload,
};
}
function addProfileOffers(state, { payload }) {
return {
...state,
profileOffers: [...state.profileOffers, ...payload],
};
}

+ 26
- 3
src/store/saga/offersSaga.js 파일 보기

@@ -46,6 +46,7 @@ import {
fetchMineHeaderOffersError,
pinOfferSuccess,
pinOfferError,
addProfileOffers,
// fetchAllOffersSuccess,
// fetchAllOffersError,
// setFeaturedOfferPage,
@@ -82,6 +83,12 @@ import {
// import { NOT_FOUND_PAGE } from "../../constants/pages";
import { makeErrorToastMessage } from "../utils/makeToastMessage";
import i18next from "i18next";
import {
KEY_NAME,
KEY_PAGE,
KEY_SIZE,
KEY_SORTBY,
} from "../../constants/queryStringConstants";

function* fetchOffers(payload) {
try {
@@ -225,11 +232,27 @@ function* fetchMineHeaderOffers() {

function* fetchProfileOffers(payload) {
try {
const userId = payload.payload;
console.log(payload.payload);
const userId = payload.payload.idProfile;
if (!userId || userId?.length === 0)
throw new Error("User id is not defined!");
const data = yield call(attemptFetchProfileOffers, userId);
yield put(setProfileOffers(data.data.offers));
const queryString = new URLSearchParams();
queryString.set(KEY_SIZE, 10);
queryString.set(KEY_PAGE, payload.payload.page);
if (payload.payload.searchValue?.length > 0)
queryString.set(KEY_NAME, payload.payload.searchValue);
queryString.set(KEY_SORTBY, payload.payload.sortOption.queryString);
const data = yield call(
attemptFetchProfileOffers,
userId,
convertQueryStringForBackend(queryString)
);
if (payload.payload.append) {
yield put(addProfileOffers(data.data.offers));
} else {
yield put(setProfileOffers(data.data.offers));
}
yield put(setTotalOffers(data.data.total));
yield put(fetchProfileOffersSuccess());
} catch (e) {
console.dir(e);

Loading…
취소
저장