浏览代码

Added locations and categories saga

feature/code-cleanup-joca
Djordje Mitrovic 3 年前
父节点
当前提交
ef2933d366
共有 40 个文件被更改,包括 875 次插入156 次删除
  1. 4
    0
      src/assets/images/svg/plus.svg
  2. 24
    4
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  3. 65
    3
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  4. 49
    3
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js
  5. 94
    63
      src/components/Cards/FilterCard/FilterCard.js
  6. 1
    1
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  7. 51
    19
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  8. 4
    1
      src/components/Cards/OfferCard/OfferCard.styled.js
  9. 28
    8
      src/components/Dropdown/DropdownList/DropdownList.js
  10. 32
    8
      src/components/Dropdown/DropdownList/DropdownList.styled.js
  11. 109
    0
      src/components/ImagePicker/ImagePicker.js
  12. 67
    0
      src/components/ImagePicker/ImagePicker.styled.js
  13. 17
    26
      src/components/MarketPlace/Header/Header.js
  14. 1
    1
      src/components/MarketPlace/Header/Header.styled.js
  15. 25
    1
      src/components/MarketPlace/MarketPlace.js
  16. 1
    1
      src/components/Scroller/HorizontalScroller.js
  17. 0
    1
      src/components/Scroller/HorizontalScroller.styled.js
  18. 10
    4
      src/components/Select/Select.js
  19. 4
    2
      src/components/Select/Select.styled.js
  20. 17
    0
      src/enums/conditionEnum.js
  21. 18
    0
      src/enums/sortEnum.js
  22. 82
    0
      src/hooks/useFilters.js
  23. 2
    0
      src/request/apiEndpoints.js
  24. 5
    0
      src/request/categoriesRequest.js
  25. 6
    3
      src/request/index.js
  26. 5
    0
      src/request/locationsRequest.js
  27. 6
    0
      src/store/actions/categories/categoriesActionConstants.js
  28. 10
    0
      src/store/actions/categories/categoriesActions.js
  29. 6
    0
      src/store/actions/locations/locationsActionConstants.js
  30. 10
    0
      src/store/actions/locations/locationsActions.js
  31. 7
    4
      src/store/middleware/accessTokensMiddleware.js
  32. 20
    0
      src/store/reducers/categories/categoriesReducer.js
  33. 16
    1
      src/store/reducers/index.js
  34. 20
    0
      src/store/reducers/locations/locationsReducer.js
  35. 13
    0
      src/store/saga/categoriesSaga.js
  36. 5
    1
      src/store/saga/index.js
  37. 16
    0
      src/store/saga/locationsSaga.js
  38. 14
    0
      src/store/selectors/categoriesSelectors.js
  39. 8
    0
      src/store/selectors/locationsSelectors.js
  40. 3
    1
      src/themes/primaryTheme/primaryThemeColors.js

+ 4
- 0
src/assets/images/svg/plus.svg 查看文件

@@ -0,0 +1,4 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36 15V57" stroke="#5A3984" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 36H57" stroke="#5A3984" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 24
- 4
src/components/Cards/CreateOfferCard/CreateOffer.styled.js 查看文件

@@ -50,13 +50,13 @@ export const RegisterAltText = styled(Typography)`
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`
`;
export const RegisterTextContainer = styled(Box)`
display: flex;
flex-direction: row;
margin-top: 36px;
justify-content: center;
`
`;
export const FieldLabel = styled(Label)`
position: relative;
bottom: -14px;
@@ -68,9 +68,29 @@ export const FieldLabel = styled(Label)`
cursor: auto;
letter-spacing: 0.2px;
}
`
`;
export const SelectText = styled(Typography)`
font-size: 16px;
font-family: "Open Sans";
font-weight: 400;
`;
export const SelectField = styled(Select)`
position: relative;
top: 15px;
margin-bottom: 18px;
`
& div {
${SelectText} {
font-weight: 600;
}
}
`;

export const SelectAltText = styled(Typography)`
font-family: "Open Sans";

font-style: italic;
white-space: pre;
font-size: 12px;
position: relative;
bottom: -1px;
`;

+ 65
- 3
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js 查看文件

@@ -1,9 +1,71 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { CreateOfferFormContainer } from "./SecondPartCreateOffer.styled";
import {
CreateOfferFormContainer,
FieldLabel,
Scroller,
// ImageListStyled,
} from "./SecondPartCreateOffer.styled";
import ImagePicker from "../../../ImagePicker/ImagePicker";
// import Select from "../../../Select/Select";
import Option from "../../../Select/Option/Option";
import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled";
import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled";
import selectedTheme from "../../../../themes";
import { conditionSelectEnum } from "../../../../enums/conditionEnum";

const SecondPartCreateOffer = () => {
return <CreateOfferFormContainer>Aaaa</CreateOfferFormContainer>;
const [images, setImages] = useState([null, null, null]); // 3 images
const setImage = (index, image) => {
console.log(images);
setImages((prevState) => {
let newState = [...prevState];
newState[index] = image;
return [...newState];
});
};

return (
<CreateOfferFormContainer>
<Scroller>
{images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
/>
))}
</Scroller>
<FieldLabel leftText="STANJE" />
<SelectField defaultValue={conditionSelectEnum.NEW.value}>
{Object.keys(conditionSelectEnum).map((key) => {
var item = conditionSelectEnum[key];
return (
<Option value={item.value} key={item.value}>
<SelectText>{item.mainText}</SelectText>
<SelectAltText>{item.altText}</SelectAltText>
</Option>
);
})}
</SelectField>

<NextButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
>
NASTAVI
</NextButton>
</CreateOfferFormContainer>
);
};

SecondPartCreateOffer.propTypes = {

+ 49
- 3
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js 查看文件

@@ -1,8 +1,54 @@
import { Box } from "@mui/material";
import { Box, ImageList } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { Label } from "../../../CheckBox/Label";
import HorizontalScroller from "../../../Scroller/HorizontalScroller";
import { ReactComponent as TrashIcon } from "../../../../assets/images/svg/trash.svg";

export const CreateOfferFormContainer = styled(Box)`
width: 335px;
width: 350px;
height: 700px;
padding-top: 20px;
`;
`;

export const ImageCard = styled.img`
width: 216px;
height: 144px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
`;
export const ImageListStyled = styled(ImageList)`
display: flex;
flex: 1;
overflow: hidden;
cursor: grab;
`;
export const FieldLabel = styled(Label)`
position: relative;
top: 12px;
& label {
font-size: 12px;
font-weight: 600;
line-height: 20px;
color: ${selectedTheme.primaryGrayText};
cursor: auto;
letter-spacing: 0.2px;
}
`;
export const Scroller = styled(HorizontalScroller)`
min-width: 640px;
position: relative;
left: calc(-320px + 50%);
margin-bottom: 36px;
`;
export const Trash = styled(TrashIcon)`
cursor: pointer;
margin: auto;
width: 22px;
height: 22px;
& path {
stroke: white;
}
`;

+ 94
- 63
src/components/Cards/FilterCard/FilterCard.js 查看文件

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import {
ContentContainer,
@@ -16,73 +16,91 @@ import Link from "../../Link/Link";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import FilterCheckboxDropdown from "./FilterDropdown/Checkbox/FilterCheckboxDropdown";
import Mockupdata from "./Mockupdata";
import { useState } from "react";
// import { useState } from "react";
import FilterRadioDropdown from "./FilterDropdown/Radio/FilterRadioDropdown";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../../../constants/pages";
import qs from "query-string";
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import useFilters from "../../../hooks/useFilters";

const FilterCard = () => {
const [appliedFilters, setAppliedFilters] = useState([]);
const [selectedCategory, setSelectedCategory] = useState(0);
const [selectedSubcategory, setSelectedSubcategory] = useState(0);
const history = useHistory();
const { t } = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);

useEffect(() => {
const queryString = history.location.search.substring(1);
const queryObject = qs.parse(queryString);
if (queryObject.category) {
setSelectedCategory(
Mockupdata[1].find(
(item) => item.string === queryObject.category.toString()
).id
);
}
if (queryObject.subcategory) {
setSelectedSubcategory(
Mockupdata[1].find(
(item) => item.string === queryObject.subcategory.toString()
).id
);
}
if (queryObject.city) {
let filters = [];
if (Array.isArray(queryObject.city)) {
queryObject.city.forEach((item) => {
filters.push(Mockupdata[0].find((p) => p.string === item).id);
});
} else {
filters.push(
Mockupdata[0].find((p) => p.string === queryObject.city).id
);
}
setAppliedFilters([...filters]);
const filters = useFilters();

// useEffect(() => {
// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);
// if (queryObject.category) {
// setSelectedCategory(
// Mockupdata[1].find(
// (item) => item.string === queryObject.category.toString()
// ).id
// );
// }
// if (queryObject.subcategory) {
// setSelectedSubcategory(
// Mockupdata[1].find(
// (item) => item.string === queryObject.subcategory.toString()
// ).id
// );
// }
// if (queryObject.city) {
// let filters = [];
// if (Array.isArray(queryObject.city)) {
// queryObject.city.forEach((item) => {
// filters.push(Mockupdata[0].find((p) => p.string === item).id);
// });
// } else {
// filters.push(
// Mockupdata[0].find((p) => p.string === queryObject.city).id
// );
// }
// setfilters.selectedLocations([...filters]);
// }
// }, []);

const handleSelectCategory = (category) => {
filters.setSelectedCategory(category);
filters.setSelectedSubcategory();
if (category?._id === 0) {
setIsOpened(false);
setIsDisabled(true);
} else {
setIsDisabled(false);
}
}, []);
}

const handleOpen = () => {
setIsOpened(prevState => !prevState);
}

const handleFilters = () => {
let queryObject = {};
if (selectedCategory !== 0) {
if (filters.selectedCategory !== 0) {
queryObject = {
category: Mockupdata[1].find(
(item) => item.id.toString() === selectedCategory.toString()
(item) => item.id.toString() === filters.selectedCategory.toString()
).string,
};
if (selectedSubcategory !== 0) {
if (filters.selectedSubcategory !== 0) {
queryObject = {
...queryObject,
subcategory: Mockupdata[1].find(
(item) => item.id.toString() === selectedSubcategory.toString()
(item) =>
item.id.toString() === filters.selectedSubcategory.toString()
).string,
};
}
}
if (appliedFilters.length > 0) {
if (filters.selectedLocations.length > 0) {
let arrayObject = [];
appliedFilters.forEach((item) => {
filters.selectedLocations.forEach((item) => {
arrayObject.push(
Mockupdata[0].find((p) => p.id.toString() === item.toString()).string
);
@@ -100,9 +118,9 @@ const FilterCard = () => {
});
};
const clearFilters = () => {
setAppliedFilters([]);
setSelectedCategory(0);
setSelectedSubcategory(0);
filters.setSelectedLocations([]);
filters.setSelectedCategory(0);
filters.setSelectedSubcategory(0);
history.push({
pathname: HOME_PAGE,
search: "",
@@ -117,48 +135,61 @@ const FilterCard = () => {
<FilterCardContainer>
<Header>
<Title>{t("filters.title")}</Title>
<Link to="#" textsize={"12px"} font={"Open Sans"} onClick={clearFilters}>
<Link
to="#"
textsize={"12px"}
font={"Open Sans"}
onClick={clearFilters}
>
{t("filters.cancel")}
</Link>
</Header>
<ContentContainer>
{/* Categories */}
<FilterRadioDropdown
data={[...Mockupdata[1]]}
data={[...filters.categories]}
icon={
selectedCategory && selectedCategory !== 0 ? (
<CategoryChosen />
) : (
<Category />
)
filters.selectedCategory?.name ? <CategoryChosen /> : <Category />
}
title={
selectedCategory && selectedCategory !== 0
? Mockupdata[1].find(
(item) => item.id.toString() === selectedCategory.toString()
).string
filters.selectedCategory?.name
? filters.selectedCategory?.name
: t("filters.categories.title")
}
searchPlaceholder={t("filters.categories.placeholder")}
setSelected={setSelectedCategory}
selected={selectedCategory}
setSelected={handleSelectCategory}
selected={filters.selectedCategory}
firstOption={{
label: "SVE KATEGORIJE",
value: { _id: 0 },
}}
/>

{/* Subcategories */}
<FilterRadioDropdown
data={[...Mockupdata[1]]}
data={filters.subcategories ? [...filters.subcategories] : []}
icon={<Subcategory />}
title={t("filters.subcategories.title")}
searchPlaceholder={t("filters.subcategories.placeholder")}
setSelected={setSelectedSubcategory}
selected={selectedSubcategory}
setSelected={filters.setSelectedSubcategory}
selected={filters.selectedSubcategory}
open={isOpened}
disabled={isDisabled}
handleOpen={handleOpen}
firstOption={{
label: "SVE PODKATEGORIJE",
value: { _id: 0 },
}}
/>

{/* Locations */}
<FilterCheckboxDropdown
searchPlaceholder={t("filters.location.placeholder")}
data={[...Mockupdata[0]]}
filters={appliedFilters}
data={[...filters.locations]}
filters={filters.selectedLocations}
icon={<Location />}
title={t("filters.location.title")}
setItemsSelected={setAppliedFilters}
setItemsSelected={filters.setSelectedLocations}
/>
</ContentContainer>


+ 1
- 1
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js 查看文件

@@ -33,7 +33,7 @@ const FilterCheckboxDropdown = (props) => {
if (toSearch.length > 0) {
setDataToShow(
data.filter((item) =>
item.string.toLowerCase().includes(toSearch.toLowerCase())
item.city.toLowerCase().includes(toSearch.toLowerCase())
)
);
} else {

+ 51
- 19
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js 查看文件

@@ -16,16 +16,17 @@ const FilterRadioDropdown = (props) => {
const [dataToShow, setDataToShow] = useState([]);
const [isOpened, setIsOpened] = useState(false);
const { data } = props;

useEffect(() => {
setDataToShow([...data]);
}, []);
}, [data]);

useEffect(() => {
if (toSearch.length > 0) {
setDataToShow(
data.filter((item) =>
item.string.toLowerCase().includes(toSearch.toLowerCase())
item.name
? item.name.toLowerCase().includes(toSearch.toLowerCase())
: item.toLowerCase().includes(toSearch.toLowerCase())
)
);
} else {
@@ -36,27 +37,37 @@ const FilterRadioDropdown = (props) => {
const handleClear = () => {
setToSearch("");
};
const handleChange = (value) => {
props.setSelected(value);
const handleOpen = () => {
setIsOpened((prevState) => !prevState);
if (props.handleOpen) props.handleOpen();
};

return (
<DropdownList
title={props.title}
textcolor={
props.selected !== 0
? selectedTheme.primaryPurple
: selectedTheme.primaryText
!props.selected || props.selected?._id === 0
? selectedTheme.primaryText
: selectedTheme.primaryPurple
}
dropdownIcon={props.icon}
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
setIsOpened={setIsOpened}
open={props.open}
disabled={props.disabled}
setIsOpened={handleOpen}
toggleIconStyles={{
backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor,
backgroundColor: (
props.open !== undefined || props.open !== null
? props.open
: isOpened
)
? "white"
: selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
// SearchBar
<React.Fragment>
<TextField
placeholder={props.searchPlaceholder}
@@ -82,19 +93,36 @@ const FilterRadioDropdown = (props) => {
</React.Fragment>
}
>
<RadioGroup onChange={(event) => handleChange(event.target.value)}>
{dataToShow.map((item) => (
<DropdownItem key={item.id}>
<RadioGroup>
{props.firstOption && (
<DropdownItem key={0}>
<RadioButton
value={item.id}
label={item.string}
number={item.numberOfProducts}
value={props.firstOption.value}
label={props.firstOption.label}
// number={item.numberOfProducts}
fullWidth
checked={props.selected.toString() === item.id.toString()}
onChange={handleChange}
checked={!props.selected || props.selected._id === 0}
onChange={props.setSelected}
/>
</DropdownItem>
))}
)}
{dataToShow.map((item) => {
console.log("item: ", item);
return (
<DropdownItem key={item.name} onClick={() => props.setSelected(item)}>
<RadioButton
value={item}
label={item.name ? item.name : item}
// number={item.numberOfProducts}
fullWidth
checked={
JSON.stringify(props.selected) === JSON.stringify(item)
}
onChange={props.setSelected}
/>
</DropdownItem>
);
})}
</RadioGroup>
</DropdownList>
);
@@ -110,6 +138,10 @@ FilterRadioDropdown.propTypes = {
searchPlaceholder: PropTypes.string,
setSelected: PropTypes.func,
selected: PropTypes.number,
firstOption: PropTypes.any,
disabled: PropTypes.bool,
open: PropTypes.bool,
handleOpen: PropTypes.func,
};
FilterRadioDropdown.defaultProps = {
oneValueAllowed: false,

+ 4
- 1
src/components/Cards/OfferCard/OfferCard.styled.js 查看文件

@@ -22,7 +22,10 @@ export const OfferCardContainer = styled(Container)`
height: 180px;
position: relative;
`;
export const OfferImage = styled.img``;
export const OfferImage = styled.img`
max-width: 144px;
max-height: 144px;
`;
export const OfferInfo = styled(Box)`
display: flex;
flex: 2;

+ 28
- 8
src/components/Dropdown/DropdownList/DropdownList.js 查看文件

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
DropdownListContainer,
DropdownHeader,
@@ -14,40 +14,58 @@ import PropTypes from "prop-types";

const DropdownList = (props) => {
const [listShown, setListShown] = useState(props.defaultOpen);
useEffect(() => {
if (props.open !== null || props.open !== undefined) {
setListShown(props.open);
}
}, [props.open]);
const handleShow = () => {
if (props.setIsOpened) {
props.setIsOpened(!listShown);
}
setListShown((prevState) => !prevState);
if (!props.disabled) {
setListShown((prevState) => !prevState);
}
if (!(props.open !== null || props.open !== undefined)) {
setListShown(prevState => !prevState)
}
};
return (
<DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}>
<DropdownHeader>
{props.dropdownIcon && (
<DropdownIcon onClick={() => handleShow()}>
<DropdownIcon
onClick={!props.disabled ? () => handleShow() : () => {}}
disabled={props.disabled}
>
{props.dropdownIcon}
</DropdownIcon>
)}
<DropdownTitle onClick={() => handleShow()} textcolor={props.textcolor}>
<DropdownTitle
onClick={!props.disabled ? () => handleShow() : () => {}}
textcolor={props.textcolor}
disabled={props.disabled}
>
{props.title}
</DropdownTitle>
{listShown ? (
{(props.open !== null && props.open !== undefined ? props.open : listShown) ? (
<ToggleIconOpened
style={props.toggleIconStyles}
onClick={() => handleShow()}
onClick={!props.disabled ? () => handleShow() : () => {}}
>
{props.toggleIconOpened}
</ToggleIconOpened>
) : (
<ToggleIconClosed
style={props.toggleIconStyles}
onClick={() => handleShow()}
onClick={!props.disabled ? () => handleShow() : () => {}}
disabled={props.disabled}
>
{props.toggleIconClosed}
</ToggleIconClosed>
)}
</DropdownHeader>
<ToggleContainer shouldShow={listShown}>
<ToggleContainer shouldShow={props.open !== null && props.open !== undefined ? props.open : listShown}>
<DropdownOptions>{props.headerOptions}</DropdownOptions>
<ListContainer>{props.children}</ListContainer>
</ToggleContainer>
@@ -69,6 +87,8 @@ DropdownList.propTypes = {
headerOptions: PropTypes.node,
textcolor: PropTypes.string,
setIsOpened: PropTypes.func,
open: PropTypes.bool,
disabled: PropTypes.bool,
};

DropdownList.defaultProps = {

+ 32
- 8
src/components/Dropdown/DropdownList/DropdownList.styled.js 查看文件

@@ -20,32 +20,57 @@ export const DropdownTitle = styled(Typography)`
padding-top: 5px;
padding-right: 0.9rem;
font-family: "Open Sans";
color: ${props => props.textcolor ? props.textcolor : selectedTheme.primaryText};
color: ${(props) =>
props.disabled
? selectedTheme.iconStrokeDisabledColor
: props.textcolor
? props.textcolor
: selectedTheme.primaryText};
`;

export const ToggleIconOpened = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
${(props) =>
props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
}
`;

export const ToggleIconClosed = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
&:hover button {
${(props) =>
props.disabled &&
`background-color: ${selectedTheme.primaryIconBackgroundColor}`}
}
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
${(props) =>
props.backgroundColor || !props.disabled
? `background-color: ${props.backgroundColor}`
: ``}
}
& svg path {
${(props) =>
props.disabled &&
`
stroke: ${selectedTheme.iconStrokeDisabledColor};
`}
}
`;

export const DropdownIcon = styled(IconButton)`
font-size: 22px !important;

& span {
& svg {
font-size: 22px;
& svg {
font-size: 22px;
& path {
${(props) =>
props.disabled &&
`
stroke: ${selectedTheme.iconStrokeDisabledColor};
`}
}
}
`;
@@ -59,8 +84,7 @@ export const DropdownHeader = styled(Box)`
display: flex;
flex-direction: row;
`;
export const DropdownOptions = styled(Box)`
`;
export const DropdownOptions = styled(Box)``;
export const ToggleContainer = styled(Box)`
display: ${(props) => (props.shouldShow ? "block" : "none")};
`;

+ 109
- 0
src/components/ImagePicker/ImagePicker.js 查看文件

@@ -0,0 +1,109 @@
import React, { useRef, useState } from "react";
import PropTypes from "prop-types";
import {
AddFile,
AddIcon,
ImagePickerContainer,
ImageUploaded,
} from "./ImagePicker.styled";
import { Tooltip } from "@mui/material";
import { Trash } from "../Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled";
import { Icon } from "../Icon/Icon";
// import { Input } from "@mui/material";

const ImagePicker = (props) => {
const fileInputRef = useRef(null);
const [isOpened, setIsOpened] = useState(false);

const handleChange = () => {
fileInputRef.current.click();
console.log(fileInputRef.current.click);
};
const handleImage = (event) => {
console.log("fileEvent: ", event.target.files[0]);
let reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onload = () => {
console.log(reader.result.toString());
props.setImage(reader.result);
};
reader.onerror = () => {};
};
// let timeoutObject = {
// timeoutFunctionSet: false,
// timeoutFunction: () => {},
// };
// const showMessage = (event) => {
// console.log(event);
// };
// const handleMouseEnter = (event) => {
// timeoutObject.timeoutFunctionSet = true;
// timeoutObject.timeoutFunction = setTimeout(() => {
// showMessage(event);
// }, 1000);
// };
// const handleMouseMove = (event) => {
// if (timeoutObject.timeoutFunctionSet) {
// clearTimeout(timeoutObject.timeoutFunction);
// timeoutObject.timeoutFunction = setTimeout(() => {
// showMessage(event);
// }, 1000);
// }
// };
// const handleMouseLeave = () => {
// if (timeoutObject.timeoutFunctionSet) {
// clearTimeout(timeoutObject.timeoutFunction);
// timeoutObject.timeoutFunctionSet = false;
// }
// };
const handleOpen = () => {
setIsOpened(true);
if (!props.image) setIsOpened(false);
};
const handleClose = () => {
setIsOpened(false);
};
const handleDelete = () => {
props.deleteImage();
setIsOpened(false)
}
return (
<Tooltip
open={isOpened}
onOpen={handleOpen}
onClose={handleClose}
enterDelay={500}
enterNextDelay={500}
arrow
title={
<Icon style={{width: "50px", height: "42px", paddingTop: "10px"}}>
<Trash onClick={handleDelete} />
</Icon>
}
d
>
<ImagePickerContainer
className={props.className}
onClick={!props.image ? handleChange : () => {}}
hasImage={props.image}
>
<AddFile type="file" ref={fileInputRef} onInput={handleImage} />
{props.image ? (
<ImageUploaded src={props.image} draggable={false}></ImageUploaded>
) : (
<AddIcon />
)}
</ImagePickerContainer>
</Tooltip>
);
};

ImagePicker.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
setImage: PropTypes.func,
image: PropTypes.func,
deleteImage: PropTypes.func
};

export default ImagePicker;

+ 67
- 0
src/components/ImagePicker/ImagePicker.styled.js 查看文件

@@ -0,0 +1,67 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../themes";
import {ReactComponent as Plus} from "../../assets/images/svg/plus.svg";

export const ImagePickerContainer = styled(Box)`
flex: 1;
display: flex;
flex-basis: 216px;
flex-grow: 0;
flex-shrink: 0;
height: 144px;
margin: 0 9px;
border-radius: 4px;
cursor: pointer;
background-color: ${selectedTheme.imagePickerBackground};
background-image: linear-gradient(
to right,
${selectedTheme.primaryPurple} 50%,
rgba(255, 255, 255, 0) 0%
),
linear-gradient(
${selectedTheme.primaryPurple} 50%,
rgba(255, 255, 255, 0) 0%
),
linear-gradient(
to right,
${selectedTheme.primaryPurple} 50%,
rgba(255, 255, 255, 0) 0%
),
linear-gradient(
${selectedTheme.primaryPurple} 50%,
rgba(255, 255, 255, 0) 0%
);
background-position: bottom, right, top, left;
background-size: 20px 1px, 1px 20px, 20px 1px, 1px 20px;
background-repeat: repeat-x, repeat-y, repeat-x, repeat-y;
&:first-of-type {
margin-left: 0;
}
&:last-of-type {
margin-right: 0;
}
${props => props.hasImage && `
background-image: none;
border: 1px solid ${selectedTheme.primaryPurple};
`}
`;
export const AddIcon = styled(Plus)`
margin: auto;
`
export const AddFile = styled.input`
display: none;
`
export const ImageUploaded = styled.img`
width: 216px;
height: 144px;
object-fit: scale-down;
`
export const Tooltip = styled(Box)`
background-color: rgba(255, 255, 255, 0.5);
width: 100px;
height: 100px;
position: absolute;
left: 0;
top: 0;
`

+ 17
- 26
src/components/MarketPlace/Header/Header.js 查看文件

@@ -18,6 +18,7 @@ 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";

const DownArrow = (props) => (
<IconStyled {...props}>
@@ -25,25 +26,6 @@ const DownArrow = (props) => (
</IconStyled>
);

const MockupdataForSelect = [
{
id: 0,
string: "Sortiraj po",
},
{
id: 1,
string: "Najpopularnije",
},
{
id: 2,
string: "Najnovije",
},
{
id: 3,
string: "Najstarije",
},
];

const Header = (props) => {
const [categoryString, setCategoryString] = useState("");
const [filtersString, setFiltersString] = useState("");
@@ -77,7 +59,14 @@ const Header = (props) => {
}, [category, cities]);

const handleChangeSelect = (value) => {
let chosenOption = MockupdataForSelect.find(item => item.id === value);
let chosenOption;
for (const sortOption in sortEnum) {
console.log(sortEnum[sortOption])
if (sortEnum[sortOption].value === value) {
chosenOption = sortEnum[sortOption];
}
}

console.log(location);
console.log(history);
console.log(chosenOption);
@@ -117,15 +106,17 @@ const Header = (props) => {
height="34px"
onChange={handleChangeSelect}
>
{MockupdataForSelect.map((item) => (
{Object.keys(sortEnum).map((property) => {
console.log(sortEnum[property])
return (
<Option
value={item.id}
key={item.id}
style={{ display: item.id === 0 ? "none" : "flex" }}
value={sortEnum[property].value}
key={sortEnum[property].value}
style={{ display: sortEnum[property].value === 0 ? "none" : "flex" }}
>
{item.string}
{sortEnum[property].mainText}
</Option>
))}
)})}
</HeaderSelect>
</HeaderOptions>
</HeaderContainer>

+ 1
- 1
src/components/MarketPlace/Header/Header.styled.js 查看文件

@@ -38,7 +38,7 @@ export const HeaderSelect = styled(Select)`
font-weight: 400;
position: relative;
left: -5px;
& div {
& div:first-child {
padding-left: 8px;
}
`

+ 25
- 1
src/components/MarketPlace/MarketPlace.js 查看文件

@@ -1,11 +1,35 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { MarketPlaceContainer } from "./MarketPlace.styled";
import Header from "./Header/Header";
import Offers from "./Offers/Offers";
import { useDispatch, useSelector } from "react-redux";
import { fetchCategories } from "../../store/actions/categories/categoriesActions";
import { fetchLocations } from "../../store/actions/locations/locationsActions";
import { selectSubcategories } from "../../store/selectors/categoriesSelectors";

const MarketPlace = () => {
const [isGrid, setIsGrid] = useState(false);
const dispatch = useDispatch();
const categories = useSelector(selectSubcategories("Automobili"));
useEffect(() => {
dispatch(fetchCategories());
dispatch(fetchLocations());
}, []);
useEffect(() => {
let newSubcategories = [];

if (categories) {
for (let i = 0; i < categories.length; i++) {
let subcategoryString = "";
Object.keys(categories[i]).forEach(item => {
subcategoryString += categories[i][item]
})
newSubcategories.push(subcategoryString)
}
}
console.log("selektor: ", newSubcategories);
}, [categories]);

return (
<MarketPlaceContainer>

+ 1
- 1
src/components/Scroller/HorizontalScroller.js 查看文件

@@ -55,7 +55,7 @@ const HorizontalScroller = (props) => {
scrollRef.current.scrollBy({ left: -50, behaviour: "smooth" });
};
return (
<HorizontalScrollerContainer style={props.containerStyle}>
<HorizontalScrollerContainer style={props.containerStyle} className={props.className}>
<Arrow onClick={handleLeft} disabled={isDisabledLeftButton}>
<ArrowIcon side={"left"} />
</Arrow>

+ 0
- 1
src/components/Scroller/HorizontalScroller.styled.js 查看文件

@@ -46,7 +46,6 @@ export const ListContainer = styled(ScrollContainer)`
flex: 1;
flex-direction: row;
flex-wrap: nowrap;
cursor: grab;
scroll-behavior: smooth;
margin: 0 18px;
user-select: none;

+ 10
- 4
src/components/Select/Select.js 查看文件

@@ -1,15 +1,21 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { SelectIcon, SelectStyled } from "./Select.styled";
import { SelectStyled, SelectIcon } from "./Select.styled";
import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg";

// import {Select as SelectMUI} from "@mui/material";

const Select = (props) => {
const [isOpened, setIsOpened] = useState(false);
const handleOpen = () => {
setIsOpened(prevState => !prevState);
}
return (
<SelectStyled
defaultValue={props.defaultValue}
defaultValue={props.defaultValue}
fullWidth={props.fullwidth}
open={isOpened}
onClick={() => handleOpen()}
width={props.width}
height={props.height}
className={props.className}
@@ -36,7 +42,7 @@ Select.propTypes = {
};
Select.defaultProps = {
fullwidth: true,
height: "48px"
height: "48px",
};

export default Select;

+ 4
- 2
src/components/Select/Select.styled.js 查看文件

@@ -8,9 +8,11 @@ export const SelectStyled = styled(Select)`
font-size: 16px;
font-weight: 600;
font-family: "Open Sans";
cursor: pointer;
`
export const SelectIcon = styled(Box)`
position: relative;
top: 0px;
left: -15px;
top: -1px;
left: -8px;
cursor: pointer;
`

+ 17
- 0
src/enums/conditionEnum.js 查看文件

@@ -0,0 +1,17 @@
export const conditionSelectEnum = {
NEW: {
value: 1,
mainText: "Novo",
altText: ""
},
USED: {
value: 2,
mainText: "Polovno ",
altText: " (korišćeno)"
},
LIKE_NEW: {
value: 3,
mainText: "Kao novo",
altText: " (nekorišćeno)"
}
}

+ 18
- 0
src/enums/sortEnum.js 查看文件

@@ -0,0 +1,18 @@
export const sortEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po"
},
POPULAR: {
value: 1,
mainText: "Najpopularnije"
},
NEW: {
value: 2,
mainText: "Najnovije"
},
OLD: {
value: 3,
mainText: "Najstarije"
}
}

+ 82
- 0
src/hooks/useFilters.js 查看文件

@@ -0,0 +1,82 @@
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { fetchCategories } from "../store/actions/categories/categoriesActions";
import { fetchLocations } from "../store/actions/locations/locationsActions";
import {
selectCategories,
selectSubcategories,
} from "../store/selectors/categoriesSelectors";
// import qs from "query-string";
import { selectLocations } from "../store/selectors/locationsSelectors";

const useFilters = () => {
const [selectedCategory, setSelectedCategory] = useState();
const [selectedSubcategory, setSelectedSubcategory] = useState();
const [selectedLocations, setSelectedLocations] = useState([]);
const [selectedSortOption, setSelectedSortOption] = useState({});
const categories = useSelector(selectCategories);
const subcategories = useSelector(
selectSubcategories(selectedCategory?.name)
);
const locations = useSelector(selectLocations);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchCategories());
dispatch(fetchLocations());
}, []);
useEffect(() => {
console.log("selectedCategory changed: ", selectedCategory);
}, [selectedCategory])
// useEffect(() => {
// if (categories && locations) {
// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);
// let category;
// if (queryObject.category) {
// category = categories.find(
// (item) => item.mainText === queryObject.category.toString()
// );
// setSelectedCategory(category);
// }
// if (queryObject.subcategory) {
// setSelectedSubcategory(
// category.subcategories.find(
// (item) => item.mainText === queryObject.subcategory.toString()
// ).id
// );
// }
// if (queryObject.city) {
// let filters = [];
// if (Array.isArray(queryObject.city)) {
// queryObject.city.forEach((item) => {
// filters.push(Mockupdata[0].find((p) => p.string === item).id);
// });
// } else {
// filters.push(
// Mockupdata[0].find((p) => p.string === queryObject.city).id
// );
// }
// setAppliedFilters([...filters]);
// }
// }
// }, [categories, locations]);
useEffect(() => {
console.log("subcategories: ", subcategories);
}, [subcategories]);
return {
selectedCategory,
setSelectedCategory,
selectedSubcategory,
setSelectedSubcategory,
selectedLocations,
setSelectedLocations,
selectedSortOption,
setSelectedSortOption,
categories,
subcategories,
locations,
};
};

export default useFilters;

+ 2
- 0
src/request/apiEndpoints.js 查看文件

@@ -160,5 +160,7 @@ export default {
offers: {
getOffers: 'offers',
addOffer: 'offers',
categories: 'categories',
locations: 'locations'
}
};

+ 5
- 0
src/request/categoriesRequest.js 查看文件

@@ -0,0 +1,5 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchCategories = () =>
getRequest(apiEndpoints.offers.categories);

+ 6
- 3
src/request/index.js 查看文件

@@ -2,7 +2,8 @@ import axios from "axios";
import queryString from "qs";

const request = axios.create({
baseURL: "http://192.168.88.150:3001/",
// baseURL: "http://192.168.88.150:3001/",
baseURL: "http://192.168.88.175:3005/",
headers: {
"Content-Type": "application/json",
},
@@ -11,8 +12,10 @@ const request = axios.create({
queryString.stringify(params, { arrayFormat: "comma" }),
});

export const getRequest = (url, params = null, options = null) =>
request.get(url, { params, ...options });
export const getRequest = (url, params = null, options = null) => {
console.log('poziv')
return request.get(url, { params, ...options });
}

export const postRequest = (url, data, params = null, options = null) =>
request.post(url, data, { params, ...options });

+ 5
- 0
src/request/locationsRequest.js 查看文件

@@ -0,0 +1,5 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchLocations = () =>
getRequest(apiEndpoints.offers.locations);

+ 6
- 0
src/store/actions/categories/categoriesActionConstants.js 查看文件

@@ -0,0 +1,6 @@
import { createFetchType } from "../actionHelpers";

const CATEGORIES_SCOPE = "CATEGORIES";
export const CATEGORIES_FETCH = createFetchType(CATEGORIES_SCOPE);

export const CATEGORIES_SET = "CATEGORIES_SET";

+ 10
- 0
src/store/actions/categories/categoriesActions.js 查看文件

@@ -0,0 +1,10 @@
import { CATEGORIES_FETCH, CATEGORIES_SET } from "./categoriesActionConstants";

export const fetchCategories = () => ({
type: CATEGORIES_FETCH
})

export const setCategories = (payload) => ({
type: CATEGORIES_SET,
payload
})

+ 6
- 0
src/store/actions/locations/locationsActionConstants.js 查看文件

@@ -0,0 +1,6 @@
import { createFetchType } from "../actionHelpers";

const LOCATIONS_SCOPE = "LOCATIONS_SCOPE";
export const LOCATIONS_FETCH = createFetchType(LOCATIONS_SCOPE);

export const LOCATIONS_SET = "LOCATIONS_SET";

+ 10
- 0
src/store/actions/locations/locationsActions.js 查看文件

@@ -0,0 +1,10 @@
import { LOCATIONS_FETCH, LOCATIONS_SET } from "./locationsActionConstants";

export const fetchLocations = () => ({
type: LOCATIONS_FETCH,
});

export const setLocations = (payload) => ({
type: LOCATIONS_SET,
payload
})

+ 7
- 4
src/store/middleware/accessTokensMiddleware.js 查看文件

@@ -13,7 +13,8 @@ import { logoutUser, refreshUserToken } from "../actions/login/loginActions";
// import { setUserAccessToken } from "../actions/user/userActions";

//Change URL with .env
const baseURL = "http://192.168.88.150:3001/";
//const baseURL = "http://192.168.88.150:3001/";
const baseURL = "http://192.168.88.175:3005/";

//Interceptor unique name
export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR";
@@ -25,16 +26,17 @@ export default ({ dispatch }) =>
const jwtToken = authScopeStringGetHelper(JWT_TOKEN);
const refresh = authScopeStringGetHelper(JWT_REFRESH_TOKEN);
if (!jwtToken || !refresh) return Promise.resolve(response);
const jwtTokenDecoded = jwt.decode(jwtToken);
const refreshTokenDecoded = jwt.decode(refresh);
if (!response.headers?.Authorization) {
response.headers.Authorization = `Bearer ${jwtToken}`;
}
const jwtTokenDecoded = jwt.decode(jwtToken);
const refreshTokenDecoded = jwt.decode(refresh);

// If refresh token is expired, log out user
if (new Date() > new Date(refreshTokenDecoded?.exp * 1000)) {
dispatch(logoutUser());
}

// If access token is expired, refresh access token
if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) {
const axiosResponse = await axios.post(`${baseURL}auth/refresh`, {
@@ -43,6 +45,7 @@ export default ({ dispatch }) =>
const newToken = axiosResponse.data.token;
dispatch(refreshUserToken(newToken));
}
return Promise.resolve(response);
}, accessTokensMiddlewareInterceptorName);


+ 20
- 0
src/store/reducers/categories/categoriesReducer.js 查看文件

@@ -0,0 +1,20 @@
import { CATEGORIES_SET } from "../../actions/categories/categoriesActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
categories: [],
};

export default createReducer(
{
[CATEGORIES_SET]: setCategories
},
initialState
);

function setCategories(state, action) {
return {
...state,
categories: action.payload
}
}

+ 16
- 1
src/store/reducers/index.js 查看文件

@@ -8,6 +8,8 @@ import createFilter from "redux-persist-transform-filter";
import persistReducer from "redux-persist/es/persistReducer";
import filtersReducer from "./filters/filtersReducer";
import offersReducer from "./offers/offersReducer";
import categoriesReducer from "./categories/categoriesReducer";
import locationsReducer from "./locations/locationsReducer";

const loginPersistConfig = {
key: "login",
@@ -37,11 +39,24 @@ const randomDataPersistConfig = {
],
};

const categoriesPersistConfig = {
key: "categories",
storage: storage,
transform: [createFilter("categories", ["_id", "name", "subcategories"])],
};
const locationsPersistConfig = {
key: "locations",
storage: storage,
transform: [createFilter("locations", ["_id", "city"])],
};

export default combineReducers({
login: persistReducer(loginPersistConfig, loginReducer),
user: persistReducer(userPersistConfig, userReducer),
loading: loadingReducer,
filters: filtersReducer,
randomData: persistReducer(randomDataPersistConfig, randomDataReducer),
offers: offersReducer
offers: offersReducer,
categories: persistReducer(categoriesPersistConfig, categoriesReducer),
locations: persistReducer(locationsPersistConfig, locationsReducer),
});

+ 20
- 0
src/store/reducers/locations/locationsReducer.js 查看文件

@@ -0,0 +1,20 @@
import { LOCATIONS_SET } from "../../actions/locations/locationsActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
locations: [],
};

export default createReducer(
{
[LOCATIONS_SET]: setLocations,
},
initialState
);

function setLocations(state, action) {
return {
...state,
locations: action.payload,
};
}

+ 13
- 0
src/store/saga/categoriesSaga.js 查看文件

@@ -0,0 +1,13 @@
import { all, call, put, takeLatest } from "@redux-saga/core/effects";
import { attemptFetchCategories } from "../../request/categoriesRequest";
import { CATEGORIES_FETCH } from "../actions/categories/categoriesActionConstants";
import { setCategories } from "../actions/categories/categoriesActions";

function* fetchCategories() {
const { data } = yield call(attemptFetchCategories);
yield put(setCategories(data));
}

export default function* categoriesSaga() {
yield all([takeLatest(CATEGORIES_FETCH, fetchCategories)]);
}

+ 5
- 1
src/store/saga/index.js 查看文件

@@ -1,5 +1,7 @@
import { all } from 'redux-saga/effects';
import categoriesSaga from './categoriesSaga';
import forgotPasswordSaga from './forgotPasswordSaga';
import locationsSaga from './locationsSaga';
import loginSaga from './loginSaga';
import offersSaga from './offersSaga';
import registerSaga from './registerSaga';
@@ -9,6 +11,8 @@ export default function* rootSaga() {
loginSaga(),
registerSaga(),
forgotPasswordSaga(),
offersSaga()
offersSaga(),
categoriesSaga(),
locationsSaga()
]);
}

+ 16
- 0
src/store/saga/locationsSaga.js 查看文件

@@ -0,0 +1,16 @@
import { all, call, put, takeLatest } from "@redux-saga/core/effects";
import { attemptFetchLocations } from "../../request/locationsRequest";
import { LOCATIONS_FETCH } from "../actions/locations/locationsActionConstants";
import { setLocations } from "../actions/locations/locationsActions";

function* fetchLocations() {
const {data} = yield call(attemptFetchLocations)
console.log(data);
yield put(setLocations(data));
}

export default function* locationsSaga() {
yield all([
takeLatest(LOCATIONS_FETCH, fetchLocations)
])
}

+ 14
- 0
src/store/selectors/categoriesSelectors.js 查看文件

@@ -0,0 +1,14 @@
import { createSelector } from "reselect";

const categoriesSelector = (state) => state.categories;

export const selectCategories = createSelector(
categoriesSelector,
(state) => state.categories
);
export const selectSubcategories = (category) =>
createSelector(categoriesSelector, (state) =>
state.categories?.find(
(item) => item?.name?.toString() === category?.toString()
)?.subcategories
);

+ 8
- 0
src/store/selectors/locationsSelectors.js 查看文件

@@ -0,0 +1,8 @@
import { createSelector } from "reselect";

const locationSelector = (state) => state.locations;

export const selectLocations = createSelector(
locationSelector,
(state) => state.locations
);

+ 3
- 1
src/themes/primaryTheme/primaryThemeColors.js 查看文件

@@ -13,5 +13,7 @@ export const primaryThemeColors = {
offerBackgroundColor: "#F5F5F5",
selectOptionTextColor: "#1D1D1D",
primaryDarkText: "#505050",
iconStrokeColor: "#8C8C8C"
iconStrokeColor: "#8C8C8C",
iconStrokeDisabledColor: '#C4C4C4',
imagePickerBackground: "#E4E4E4"
}

正在加载...
取消
保存