Просмотр исходного кода

Merged chat with edit-delete

feature/code-cleanup-joca
Djordje Mitrovic 3 лет назад
Родитель
Сommit
8edcd76513
32 измененных файлов: 1337 добавлений и 251 удалений
  1. 4
    0
      src/assets/images/svg/trash-gold.svg
  2. 44
    15
      src/components/Cards/CreateOfferCard/CreateOffer.js
  3. 85
    65
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  4. 77
    16
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  5. 7
    11
      src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.styled.js
  6. 110
    0
      src/components/Cards/OfferCard/DeleteOffer.js
  7. 134
    0
      src/components/Cards/OfferCard/DeleteOffer.styles.js
  8. 41
    3
      src/components/Cards/OfferCard/OfferCard.js
  9. 9
    1
      src/components/Cards/OfferCard/OfferCard.styled.js
  10. 1
    1
      src/components/Cards/UserReviewsCard/UserReviewsCard.js
  11. 1
    0
      src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js
  12. 7
    0
      src/components/ImagePicker/ImagePicker.js
  13. 318
    0
      src/components/ProfileCard/EditProfile.js
  14. 120
    0
      src/components/ProfileCard/EditProfile.styled.js
  15. 61
    16
      src/components/ProfileCard/ProfileCard.js
  16. 1
    1
      src/components/Select/Option/Option.js
  17. 3
    3
      src/components/Select/Select.js
  18. 27
    7
      src/components/UserReviews/UserReviews.js
  19. 2
    1
      src/components/UserReviews/UserReviews.styled.js
  20. 17
    0
      src/i18n/resources/rs.js
  21. 11
    8
      src/request/apiEndpoints.js
  22. 21
    12
      src/request/offersRequest.js
  23. 4
    1
      src/request/profileRequest.js
  24. 4
    1
      src/request/reviewRequest.js
  25. 12
    7
      src/store/actions/offers/offersActionConstants.js
  26. 30
    20
      src/store/actions/offers/offersActions.js
  27. 10
    1
      src/store/actions/profile/profileActionConstants.js
  28. 31
    18
      src/store/actions/profile/profileActions.js
  29. 1
    1
      src/store/actions/review/reviewActions.js
  30. 63
    16
      src/store/saga/offersSaga.js
  31. 75
    24
      src/store/saga/profileSaga.js
  32. 6
    2
      src/store/saga/reviewSaga.js

+ 4
- 0
src/assets/images/svg/trash-gold.svg Просмотреть файл

@@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.25 4.5H3.75H15.75" stroke="#FEB005" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 4.5V3C6 2.60218 6.15804 2.22064 6.43934 1.93934C6.72064 1.65804 7.10218 1.5 7.5 1.5H10.5C10.8978 1.5 11.2794 1.65804 11.5607 1.93934C11.842 2.22064 12 2.60218 12 3V4.5M14.25 4.5V15C14.25 15.3978 14.092 15.7794 13.8107 16.0607C13.5294 16.342 13.1478 16.5 12.75 16.5H5.25C4.85218 16.5 4.47064 16.342 4.18934 16.0607C3.90804 15.7794 3.75 15.3978 3.75 15V4.5H14.25Z" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 44
- 15
src/components/Cards/CreateOfferCard/CreateOffer.js Просмотреть файл

@@ -36,21 +36,25 @@ import { Label } from "../../CheckBox/Label";
import FirstPartCreateOffer from "./FirstPart/FirstPartCreateOffer";
import SecondPartCreateOffer from "./SecondPart/SecondPartCreateOffer";
import ThirdPartCreateOffer from "./ThirdPart/ThirdPartCreateOffer";
import { addOffer } from "../../../store/actions/offers/offersActions";
import {
addOffer,
fetchProfileOffers,
} from "../../../store/actions/offers/offersActions";
import { editOneOffer } from "../../../store/actions/offers/offersActions";
import { ReactComponent as ArrowBack } from "../../../assets/images/svg/arrow-back.svg";
import { ReactComponent as CloseButton } from "../../../assets/images/svg/close-modal.svg";
import BackdropComponent from "../../MUI/BackdropComponent";

const CreateOffer = ({ closeCreateOfferModal }) => {
const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const history = useHistory();
const [informations, setInformations] = useState({});
const [showPassword, setShowPassword] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);
const categories = useSelector((state) => state.categories.categories);
const historyRouter = useHistory();

// When user refreshes page
// useEffect(() => {
@@ -68,14 +72,22 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
);

const handleApiResponseSuccess = (status) => {
// if (history.location.pathname !== HOME_PAGE) {
history.push({
pathname: HOME_PAGE,
state: {
refetch: true,
},
});
// }
if (editOffer === undefined) {
const userId = historyRouter.location.pathname.slice(
9,
historyRouter.location.pathname.length
);
dispatch(fetchProfileOffers(userId));
history.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
},
});
} else {
const userId = offer.userId;
dispatch(fetchProfileOffers(userId));
}
};

const handleSubmit = (values) => {
@@ -94,6 +106,8 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
setCurrentStep((prevState) => prevState + 1);
};

console.log(informations);

const newImgs =
informations.images &&
informations.images
@@ -112,6 +126,8 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
}
}

console.log(informations);

const offerData = {
name: informations.nameOfProduct,
description: informations.description,
@@ -131,8 +147,17 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
dispatch(addOffer({ values, handleApiResponseSuccess }));
};

const submitEditOffer = (id, values) => {
dispatch(editOneOffer(id, values));
};

const handleSubmitOffer = () => {
submitOffer(offerData);
if (editOffer === undefined) {
submitOffer({ offerData, handleApiResponseSuccess });
} else {
const offerId = offer._id;
submitEditOffer({ offerId, offerData, handleApiResponseSuccess });
}
closeCreateOfferModal(false);
};

@@ -184,7 +209,11 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
{currentStep !== 1 ? <ArrowBack /> : ""}
</BackIcon>
<CreateOfferTitle component="h1" variant="h5">
{currentStep === 3 ? "Pregled" : "Nova Objava"}
{currentStep === 3
? "Pregled"
: `${
editOffer !== undefined ? "Izmena Objave" : "Nova Objava"
}`}
</CreateOfferTitle>
<CloseIcon onClick={closeModalHandler}>
<CloseButton />
@@ -197,10 +226,10 @@ const CreateOffer = ({ closeCreateOfferModal }) => {
functions={[() => goStepBack(1), () => goStepBack(2)]}
/>
{currentStep === 1 && (
<FirstPartCreateOffer handleNext={handleNext} />
<FirstPartCreateOffer handleNext={handleNext} offer={offer} />
)}
{currentStep === 2 && (
<SecondPartCreateOffer handleNext={handleNext} />
<SecondPartCreateOffer handleNext={handleNext} offer={offer} />
)}
{currentStep === 3 && (
<ThirdPartCreateOffer

+ 85
- 65
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js Просмотреть файл

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import {
@@ -17,22 +17,39 @@ import { useSelector } from "react-redux";
import useScreenDimensions from "../../../../hooks/useScreenDimensions";

const FirstPartCreateOffer = (props) => {
const [subcat, setSubcat] = useState([{}]);
const [subcat, setSubcat] = useState([]);
const locations = useSelector((state) => state.locations.locations);
const categories = useSelector((state) => state.categories.categories);
const dimensions = useScreenDimensions();

const { t } = useTranslation();

useEffect(() => {
if (props.offer !== undefined) {
let scat = categories.filter(
(cat) => cat.name === props.offer.category.name
);

console.log(categories);
console.log(scat[0].subcategories.map((x) => x.name));
setSubcat(scat[0].subcategories.map((x) => x.name));
}
}, [props.offer]);

const handleSubmit = (values) => {
props.handleNext(values);
};
const formik = useFormik({
initialValues: {
nameOfProduct: "",
description: "",
location: "",
category: "",
subcategory: "",
nameOfProduct: `${props.offer === undefined ? "" : props.offer.name}`,
description: `${
props.offer === undefined ? "" : props.offer.description
}`,
location: `${props.offer === undefined ? "" : props.offer.location.city}`,
category: `${props.offer === undefined ? "" : props.offer.category.name}`,
subcategory: `${
props.offer === undefined ? "" : props.offer.subcategory
}`,
},
validationSchema: Yup.object().shape({
nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")),
@@ -50,7 +67,8 @@ const FirstPartCreateOffer = (props) => {

const handleSubcategories = (category) => {
const filtered = categories.filter((cat) => cat.name === category);
setSubcat(filtered);
console.log(filtered[0].subcategories.map((c) => c.name));
setSubcat(filtered[0].subcategories.map((c) => c.name));
};

return (
@@ -104,69 +122,70 @@ const FirstPartCreateOffer = (props) => {
/>
)}

<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("location", value.target.value.city);
}}
>
<SelectOption value="default">
{t("offer.choseLocation")}
</SelectOption>
{locations.map((loc) => {
return (
<SelectOption key={loc._if} value={loc}>
{loc.city}
</SelectOption>
);
})}
</SelectField>
<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.location.city
}
onChange={(value) => {
formik.setFieldValue("location", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseLocation")}</SelectOption>
{locations.map((loc) => {
return (
<SelectOption key={loc._if} value={loc.city}>
{loc.city}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.category")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.category.name
}
onChange={(value) => {
formik.setFieldValue("category", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseCategory")}</SelectOption>
{categories.map((cat, i) => {
return (
<SelectOption
key={i}
value={cat.name}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.category")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("category", value.target.value.name);
}}
>
<SelectOption value="default">
{t("offer.choseCategory")}
</SelectOption>
{categories.map((cat, i) => {
<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.subcategory
}
// defaultValue="default"
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseSubcategory")}</SelectOption>
{subcat &&
subcat.map((sub, i) => {
return (
<SelectOption
key={i}
value={cat}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
<SelectOption key={i} value={sub}>
{sub}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value.name);
}}
>
<SelectOption value="default">
{t("offer.choseSubcategory")}
</SelectOption>
{subcat?.length > 0 &&
subcat[0]?.subcategories &&
subcat[0].subcategories.map((sub, i) => {
return (
<SelectOption key={i} value={sub}>
{sub.name}
</SelectOption>
);
})}
</SelectField>
</SelectField>
</CreateOfferFormContainer>

<NextButton
type="submit"
variant="contained"
@@ -197,6 +216,7 @@ const FirstPartCreateOffer = (props) => {
FirstPartCreateOffer.propTypes = {
children: PropTypes.any,
handleNext: PropTypes.func,
offer: PropTypes.node,
};

export default FirstPartCreateOffer;

+ 77
- 16
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js Просмотреть файл

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useMemo, useState, useEffect } from "react";
import PropTypes from "prop-types";
import {
CreateOfferFormContainer,
@@ -10,7 +10,10 @@ import {
import ImagePicker from "../../../ImagePicker/ImagePicker";
import { useTranslation, Trans } from "react-i18next";
import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled";
import { NextButton, SelectOption } from "../FirstPart/FirstPartCreateOffer.styled";
import {
NextButton,
SelectOption,
} from "../FirstPart/FirstPartCreateOffer.styled";
import selectedTheme from "../../../../themes";
import { conditionSelectEnum } from "../../../../enums/conditionEnum";
import { useFormik } from "formik";
@@ -22,6 +25,35 @@ const SecondPartCreateOffer = (props) => {
const [images, setImages] = useState(
Array.apply(null, Array(numberOfImages)).map(() => {})
); // 3 images

useEffect(() => {
// let editedImages = [];

// if (props.offer !== undefined && props.offer.images.length === 1) {
// editedImages.push(props.offer.images[0]);
// editedImages.push("");
// editedImages.push("");
// } else if (props.offer !== undefined && props.offer.images.length === 2) {
// editedImages.push(props.offer.images[0]);
// editedImages.push(props.offer.images[1]);
// editedImages.push("");
// }
setImages((prevState) => {
let editedImages = [...prevState];
if (props.offer !== undefined && props.offer.images.length === 1) {
editedImages[0] = props.offer.images[0];
}

if (props.offer !== undefined && props.offer.images.length === 2) {
editedImages[0] = props.offer.images[0];

editedImages[1] = props.offer.images[1];
}

return [...editedImages];
});
}, [props.offer]);

const setImage = (index, image) => {
setImages((prevState) => {
let newState = [...prevState];
@@ -50,7 +82,7 @@ const SecondPartCreateOffer = (props) => {
const formik = useFormik({
initialValues: {
images: images,
condition: "",
condition: `${props.offer === undefined ? "" : props.offer.condition}`,
},
validationSchema: Yup.object().shape({}),
onSubmit: handleSubmit,
@@ -58,19 +90,42 @@ const SecondPartCreateOffer = (props) => {
enableReinitialize: true,
});

console.log("slike", images);

return (
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
<Scroller>
{images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))}
{images.map((item, index) => {
return (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
);
})}
{/* {props.offer === undefined
? images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))
: editedImages.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))} */}
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
@@ -78,12 +133,16 @@ const SecondPartCreateOffer = (props) => {
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue="default"
defaultValue={
props.offer === undefined ? "default" : props.offer.condition
}
onChange={(value) => {
formik.setFieldValue("condition", value.target.value);
}}
>
<SelectOption style={{display: "none"}} value="default">{t("offer.choseCondition")}</SelectOption>
<SelectOption value="default">
{t("offer.choseCondition")}
</SelectOption>
{Object.keys(conditionSelectEnum).map((key) => {
var item = conditionSelectEnum[key];
return (
@@ -102,10 +161,11 @@ const SecondPartCreateOffer = (props) => {
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
onClick={formik.handleSubmit}
textcolor="white"
onClick={formik.handleSubmit}
// disabled={imagesEmpty === numberOfImages}
disabled={
imagesEmpty === numberOfImages || formik.values.condition.length === 0
props.offer === undefined ? imagesEmpty === numberOfImages : false
}
>
{t("offer.continue")}
@@ -117,6 +177,7 @@ const SecondPartCreateOffer = (props) => {
SecondPartCreateOffer.propTypes = {
children: PropTypes.node,
handleNext: PropTypes.func,
offer: PropTypes.node,
};

export default SecondPartCreateOffer;

+ 7
- 11
src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.styled.js Просмотреть файл

@@ -1,16 +1,12 @@
// import { Box } from "@mui/material";
import { Box } from "@mui/material";
import styled from "styled-components";
import ItemDetailsCard from "../../ItemDetailsCard/ItemDetailsCard";

// export const CreateOfferFormContainer = styled(Box)`
// padding-top: 20px;
// margin-top: 20px;
// width:100%;
// `;
export const CreateOfferFormContainer = styled(Box)`
padding-top: 20px;
margin-top: 20px;
width:100%;
`;
export const PreviewCard = styled(ItemDetailsCard)`
position: relative;
top: 10px;
width: 100%;
overflow-y: auto;
height: 400px;

`

+ 110
- 0
src/components/Cards/OfferCard/DeleteOffer.js Просмотреть файл

@@ -0,0 +1,110 @@
import React from "react";
import PropTypes from "prop-types";
import {
ButtonsContainer,
DeleteOfferContainer,
DeleteQuestion,
OfferInfo,
OfferImageContainer,
OfferImage,
OfferDescription,
OfferDescriptionTitle,
OfferDescriptionCategory,
CancelButton,
RemoveIconContainer,
RemoveIcon,
SaveButton,
CategoryIcon,
} from "./DeleteOffer.styles";
import selectedTheme from "../../../themes";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import BackdropComponent from "../../MUI/BackdropComponent";
import { useDispatch } from "react-redux";
import {
fetchProfileOffers,
removeOffer,
} from "../../../store/actions/offers/offersActions";
import { useTranslation, Trans } from "react-i18next";

const DeleteOffer = (props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const userId = props.offer.userId;
const offerId = props.offer._id;
const closeDeleteModalHandler = () => {
props.closeModalHandler();
};

const handleApiResponseSuccess = () => {
dispatch(fetchProfileOffers(userId));
};

const removeOfferHandler = () => {
dispatch(removeOffer({ offerId, handleApiResponseSuccess }));
props.closeModalHandler();
};

return (
<>
<BackdropComponent
isLoading
position="fixed"
handleClose={closeDeleteModalHandler}
/>
<DeleteOfferContainer>
<OfferInfo>
<OfferImageContainer>
<OfferImage src={props.offer.images[0]} />
</OfferImageContainer>
<OfferDescription>
<OfferDescriptionTitle>{props.offer.name}</OfferDescriptionTitle>
<OfferDescriptionCategory>
<CategoryIcon color="#c4c4c4" component="span" size="16px">
<Category />
</CategoryIcon>
{props.offer.category.name}
</OfferDescriptionCategory>
</OfferDescription>
<RemoveIconContainer>
<RemoveIcon />
</RemoveIconContainer>
</OfferInfo>
<DeleteQuestion>
<Trans i18nKey="deleteOffer.areYouSure" />
</DeleteQuestion>
<ButtonsContainer>
<CancelButton
variant="contained"
height="48px"
width="180px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
onClick={closeDeleteModalHandler}
>
{t("deleteOffer.cancel")}
</CancelButton>
<SaveButton
type="submit"
variant="outlined"
height="48px"
width="180px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryPurple}
onClick={removeOfferHandler}
>
{t("deleteOffer.delete")}
</SaveButton>
</ButtonsContainer>
</DeleteOfferContainer>
</>
);
};

DeleteOffer.propTypes = {
offer: PropTypes.node,
closeModalHandler: PropTypes.func,
};

export default DeleteOffer;

+ 134
- 0
src/components/Cards/OfferCard/DeleteOffer.styles.js Просмотреть файл

@@ -0,0 +1,134 @@
import { Typography } from "@mui/material";
import { Box } from "@mui/system";
import styled from "styled-components";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash-gold.svg";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";

export const DeleteOfferContainer = styled(Box)`
width: 537px;
height: 309px;
position: fixed;
top: calc(50% - 155px);
left: calc(50% - 268px);
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 80px;
z-index: 150;

@media screen and (max-width: 600px) {
width: 350px;
display: block;
padding: 0 18px;
left: calc(50% - 175px);
}
`;

export const OfferInfo = styled(Box)`
width: 211px;
height: 90px;
border: 1px solid #d4d4d4;
display: flex;
align-items: center;
padding: 18px;
margin-top: 36px;

@media screen and (max-width: 600px) {
margin-left: calc(50% - 105px) !important;
}
`;

export const OfferImageContainer = styled(Box)`
width: 54px;
height: 54px;
border-radius: 2px;

@media screen and (max-width: 600px) {
margin-right: 13px;
}
`;

export const OfferImage = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 2px;
`;

export const OfferDescription = styled(Box)`
display: flex;
flex-direction: column;
margin-left: 9px;
`;

export const OfferDescriptionTitle = styled(Typography)`
font-size: 16px;
font-weight: 600;
color: ${selectedTheme.primaryPurple};
`;

export const OfferDescriptionCategory = styled(Typography)`
font-size: 12px;
`;

export const CategoryIcon = styled(Icon)`
& svg {
width: 14px;
position: relative;
top: -1px;
}
`;

export const DeleteQuestion = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
font-weight: 600;
text-align: center;
margin: 36px 0;

@media screen and (max-width: 600px) {
margin-left: calc(50% - 104px);
}
`;

export const RemoveIconBorder = styled(IconButton)`
width: 40px;
height: 40px;
position: absolute;
top: 16px;
right: 143px;
background-color: ${selectedTheme.primaryPurple};
border-radius: 100%;
padding-top: 2px;
text-align: center;

@media screen and (max-width: 600px) {
right: 50px;
}
`;

export const RemoveIconContainer = styled(RemoveIconBorder)``;

export const RemoveIcon = styled(Remove)``;

export const ButtonsContainer = styled(Box)`
display: flex;
width: 100%;
justify-content: space-between;
`;

export const CancelButton = styled(PrimaryButton)`
@media screen and (max-width: 600px) {
width: 140px;
}
`;

export const SaveButton = styled(PrimaryButton)`
@media screen and (max-width: 600px) {
width: 140px;
}
`;

+ 41
- 3
src/components/Cards/OfferCard/OfferCard.js Просмотреть файл

@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
@@ -30,12 +30,16 @@ import {
StarIcon,
StarIconContainer,
} from "./OfferCard.styled";
import DeleteOffer from "./DeleteOffer";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import selectedTheme from "../../../themes";
import { useHistory } from "react-router-dom";
import CreateOffer from "../CreateOfferCard/CreateOffer";

const OfferCard = (props) => {
const [deleteOfferModal, setDeleteOfferModal] = useState(false);
const [editOfferModal, setEditOfferModal] = useState(false);
const history = useHistory();

const routeToItem = (itemId) => {
@@ -49,6 +53,21 @@ const OfferCard = (props) => {
props.makeReview(props.offer);
}
};

const closeModalHandler = () => {
setDeleteOfferModal(false);
};

const closeCreateOfferModal = () => {
setEditOfferModal(false);
};

if (deleteOfferModal || editOfferModal) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "auto";
}

return (
<React.Fragment>
<OfferCardContainer
@@ -131,10 +150,16 @@ const OfferCard = (props) => {

{props.isMyOffer ? (
<>
<RemoveIconContainer vertical={props.vertical}>
<RemoveIconContainer
vertical={props.vertical}
onClick={() => setDeleteOfferModal(true)}
>
<RemoveIcon />
</RemoveIconContainer>
<EditIconContainer vertical={props.vertical}>
<EditIconContainer
vertical={props.vertical}
onClick={() => setEditOfferModal(true)}
>
<EditIcon />
</EditIconContainer>
</>
@@ -152,6 +177,19 @@ const OfferCard = (props) => {
)}
</OfferFlexContainer>
</OfferCardContainer>
{deleteOfferModal && (
<DeleteOffer
offer={props.offer}
closeModalHandler={closeModalHandler}
/>
)}
{editOfferModal && (
<CreateOffer
editOffer
closeCreateOfferModal={closeCreateOfferModal}
offer={props.offer}
/>
)}
</React.Fragment>
);
};

+ 9
- 1
src/components/Cards/OfferCard/OfferCard.styled.js Просмотреть файл

@@ -92,6 +92,7 @@ export const OfferTitle = styled(Typography)`
width: calc(100% - 120px);
cursor: pointer;
@media (max-width: 550px) {
width: 100%;
font-size: 18px;
display: none;
${(props) =>
@@ -320,7 +321,14 @@ export const EyeIcon = styled(Eye)`
top: 1px !important;
}
`;
export const RemoveIconContainer = styled(MessageIcon)``;
export const RemoveIconContainer = styled(MessageIcon)`
@media screen and (max-width: 600px) {
position: absolute;
display: block;
right: 20px;
top: 75%;
}
`;
export const RemoveIcon = styled(Remove)``;
export const EditIconContainer = styled(MessageIcon)`
right: 70px;

+ 1
- 1
src/components/Cards/UserReviewsCard/UserReviewsCard.js Просмотреть файл

@@ -83,7 +83,7 @@ const UserReviewsCard = (props) => {
spacing={2}
sx={{ pl: 2, py: 2 }}
>
<ThumbBox item xs={1}>
<ThumbBox item>
{isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />}
</ThumbBox>
<ReviewQuoteBox item xs={11}>

+ 1
- 0
src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js Просмотреть файл

@@ -99,6 +99,7 @@ export const ReviewQuote = styled(Grid)`
left: 8px;
`
export const ThumbBox = styled(Grid)`
max-width: 20px;
`
export const ReviewQuoteBox = styled(Grid)`
`

+ 7
- 0
src/components/ImagePicker/ImagePicker.js Просмотреть файл

@@ -20,6 +20,13 @@ const ImagePicker = (props) => {
const [image, setImage] = useState("");
const [isEditing, setIsEditing] = useState(false);

useEffect(() => {
console.log("image", props);
if (props.image) {
setImage(props.image);
}
}, [props.image]);

let listener;
useEffect(() => {
listener = (event) => {

+ 318
- 0
src/components/ProfileCard/EditProfile.js Просмотреть файл

@@ -0,0 +1,318 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { FieldLabel } from "../Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled";
import BackdropComponent from "../MUI/BackdropComponent";
import {
EditProfileContainer,
ProfileImageContainer,
InputField,
BackButton,
CloseButton,
SaveButton,
ProfileHeader,
BasicInfo,
DetailsInfo,
ButtonsContainer,
MobileInfo,
ErrorMessage,
ProfileImagePicker,
} from "./EditProfile.styled";
import selectedTheme from "../../themes";
import { useFormik } from "formik";
import * as Yup from "yup";
import { ReactComponent as ArrowBack } from "../../assets/images/svg/arrow-back.svg";
import { ReactComponent as CloseIcon } from "../../assets/images/svg/close-modal.svg";
import { useTranslation } from "react-i18next";
import {
editMineProfile,
fetchMineProfile,
} from "../../store/actions/profile/profileActions";
import { useDispatch } from "react-redux";
import useScreenDimensions from "../../hooks/useScreenDimensions";
import { useRouteMatch } from "react-router-dom";

const EditProfile = (props) => {
const [profileImage, setProfileImage] = useState(props.profile.image);
const [showDetails, setShowDetails] = useState(false);
const { t } = useTranslation();
const dispatch = useDispatch();
const dimensions = useScreenDimensions();
const routeMatch = useRouteMatch();
const userId = routeMatch.params.idProfile;

const handleApiResponseSuccess = () => {
dispatch(fetchMineProfile(userId));
props.reFetchProfile();
};

const handleSubmit = (values) => {
dispatch(editMineProfile({ ...values, handleApiResponseSuccess }));
props.closeModalHandler();
};

const formik = useFormik({
initialValues: {
firmName: `${props.profile.company.name}`,
firmPIB: `${props.profile.company.PIB}`,
firmLocation: `${props.profile.company.contacts.location}`,
firmWebsite: `${props.profile.company.contacts.web}`,
firmApplink: "",
firmPhone: `${props.profile.company.contacts.telephone}`,
firmLogo: profileImage,
},
validationSchema: Yup.object().shape({
firmName: Yup.string().required(t("editProfile.labelNameRequired")),
firmPIB: Yup.string()
.required(t("editProfile.labelPIBRequired"))
.min(9, t("register.PIBnoOfCharacters")),
firmLocation: Yup.string().required(
t("editProfile.labelLocationRequired")
),
firmWebsite: Yup.string(),
firmApplink: Yup.string(),
firmPhone: Yup.string().required(t("editProfile.labelPhoneRequired")),
}),
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

const closeEditModalHandler = () => {
props.closeModalHandler();
};

const showDetailsHandler = () => {
setShowDetails(!showDetails);
};

const setImage = (image) => {
setProfileImage(image);
};

return (
<>
<BackdropComponent
handleClose={closeEditModalHandler}
isLoading
position="fixed"
/>
<EditProfileContainer component="form" onSubmit={formik.handleSubmit}>
{showDetails && (
<BackButton onClick={showDetailsHandler}>
<ArrowBack />
</BackButton>
)}
<ProfileImageContainer>
<ProfileImagePicker
image={profileImage}
setImage={setImage}
></ProfileImagePicker>
<ProfileHeader>{props.profile.company.name}</ProfileHeader>
</ProfileImageContainer>
<CloseButton onClick={closeEditModalHandler}>
<CloseIcon />
</CloseButton>
{dimensions.width > 600 ? (
<>
<FieldLabel leftText={t("common.labelFirm").toUpperCase()} />
<InputField
name="firmName"
value={formik.values.firmName}
onChange={formik.handleChange}
error={formik.touched.firmName && formik.errors.firmName}
// helperText={formik.touched.firmName && formik.errors.firmName}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("common.labelPIB")} />
<InputField
name="firmPIB"
value={formik.values.firmPIB}
onChange={formik.handleChange}
error={formik.touched.firmPIB && formik.errors.firmPIB}
// helperText={formik.touched.firmPIB && formik.errors.firmPIB}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("common.labelLocation").toUpperCase()} />
<InputField
name="firmLocation"
value={formik.values.firmLocation}
onChange={formik.handleChange}
error={formik.touched.firmLocation && formik.errors.firmLocation}
// helperText={
// formik.touched.firmLocation && formik.errors.firmLocation
// }
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("editProfile.website").toUpperCase()} />
<InputField
name="firmWebsite"
value={formik.values.firmWebsite}
onChange={formik.handleChange}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("editProfile.applink").toUpperCase()} />
<InputField
name="firmApplink"
values={formik.values.firmApplink}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("editProfile.phoneNumber").toUpperCase()} />
<InputField
name="firmPhone"
value={formik.values.firmPhone}
onChange={formik.handleChange}
error={formik.touched.firmPhone && formik.errors.firmPhone}
// helperText={formik.touched.firmPhone && formik.errors.firmPhone}
margin="normal"
fullWidth
/>
</>
) : (
<MobileInfo>
{!showDetails && (
<BasicInfo>
<FieldLabel leftText={t("common.labelFirm").toUpperCase()} />
<InputField
name="firmName"
value={formik.values.firmName}
onChange={formik.handleChange}
error={formik.touched.firmName && formik.errors.firmName}
// helperText={formik.touched.firmName && formik.errors.firmName}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("common.labelPIB")} />
<InputField
name="firmPIB"
value={formik.values.firmPIB}
onChange={formik.handleChange}
error={formik.touched.firmPIB && formik.errors.firmPIB}
// helperText={formik.touched.firmPIB && formik.errors.firmPIB}
margin="normal"
fullWidth
/>
<FieldLabel
leftText={t("common.labelLocation").toUpperCase()}
/>
<InputField
name="firmLocation"
value={formik.values.firmLocation}
onChange={formik.handleChange}
error={
formik.touched.firmLocation && formik.errors.firmLocation
}
// helperText={
// formik.touched.firmLocation && formik.errors.firmLocation
// }
margin="normal"
fullWidth
/>
</BasicInfo>
)}
{showDetails && (
<DetailsInfo>
<FieldLabel leftText={t("editProfile.website").toUpperCase()} />
<InputField
name="firmWebsite"
value={formik.values.firmWebsite}
onChange={formik.handleChange}
margin="normal"
fullWidth
/>
<FieldLabel leftText={t("editProfile.applink").toUpperCase()} />
<InputField
name="firmApplink"
values={formik.values.firmApplink}
margin="normal"
fullWidth
/>
<FieldLabel
leftText={t("editProfile.phoneNumber").toUpperCase()}
/>
<InputField
name="firmPhone"
value={formik.values.firmPhone}
onChange={formik.handleChange}
error={formik.touched.firmPhone && formik.errors.firmPhone}
// helperText={
// formik.touched.firmPhone && formik.errors.firmPhone
// }
margin="normal"
fullWidth
/>
</DetailsInfo>
)}
</MobileInfo>
)}

{formik.errors.firmName && formik.touched.firmName ? (
<ErrorMessage>{formik.errors.firmName}</ErrorMessage>
) : formik.errors.firmPIB && formik.touched.firmPIB ? (
<ErrorMessage>{formik.errors.firmPIB}</ErrorMessage>
) : formik.errors.firmLocation && formik.touched.firmLocation ? (
<ErrorMessage>{formik.errors.firmLocation}</ErrorMessage>
) : formik.errors.firmPhone && formik.touched.firmPhone ? (
<ErrorMessage>{formik.errors.firmPhone}</ErrorMessage>
) : (
<></>
)}

{dimensions.width > 600 ? (
<SaveButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
>
{t("editProfile.saveChanges")}
</SaveButton>
) : (
<ButtonsContainer>
<SaveButton
// type="submit"
// variant="outlined"
height="48px"
width="155px"
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryPurple}
onClick={showDetailsHandler}
>
{showDetails
? t("editProfile.showBasic")
: t("editProfile.showDetails")}
</SaveButton>
<SaveButton
type="submit"
variant="contained"
height="48px"
width="155px"
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
>
{t("common.save")}
</SaveButton>
</ButtonsContainer>
)}
</EditProfileContainer>
</>
);
};

EditProfile.propTypes = {
children: PropTypes.node,
profile: PropTypes.any,
closeModalHandler: PropTypes.func,
setImage: PropTypes.func,
reFetchProfile: PropTypes.func,
// error: PropTypes.string,
// errorMessage: PropTypes.string,
};

export default EditProfile;

+ 120
- 0
src/components/ProfileCard/EditProfile.styled.js Просмотреть файл

@@ -0,0 +1,120 @@
import styled from "styled-components";
import { Box, TextField, Typography } from "@mui/material";
import ImagePicker from "../ImagePicker/ImagePicker";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";

export const EditProfileContainer = styled(Box)`
background-color: #fff;
position: fixed;
top: 50px;
left: calc(50% - 310px);
z-index: 150;
padding: 0 144px;
width: 600px;
max-height: 90vh;
overflow-y: auto;

@media screen and (max-width: 600px) {
width: 375px;
padding: 0 30px;
top: 60px;
left: calc(50% - 187px);
}
`;

export const ProfileImageContainer = styled(Box)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

export const ProfileImagePicker = styled(ImagePicker)`
background: none;
margin: 36px;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='100' ry='100' stroke='%235A3984FF' stroke-width='2' stroke-dasharray='7%2c 12' stroke-dashoffset='44' stroke-linecap='square'/%3e%3c/svg%3e");
border-radius: 100px;
overflow: hidden;
position: relative;
margin-bottom: 5px;
`;
export const ProfilePicture = styled.img`
position: absolute;
left: 36px;
top: 24px;
z-index: 0;
`;

export const ProfileImage = styled.img`
width: 144px;
height: 144px;
border-radius: 100%;
`;

export const ProfileHeader = styled(Typography)`
font-family: "Open Sans";
font-size: 24px;
font-weight: bold;
margin-top: 6px;

@media screen and (max-width: 600px) {
font-size: 18px;
}
`;

export const BackButton = styled(Box)`
cursor: pointer;
position: absolute;
top: 40px;
left: 40px;

@media screen and (max-width: 600px) {
svg {
width: 20px;
height: 20px;
}
}
`;

export const CloseButton = styled(Box)`
cursor: pointer;
position: absolute;
top: 40px;
right: 40px;

@media screen and (max-width: 600px) {
svg {
width: 20px;
height: 20px;
}
}
`;

export const InputField = styled(TextField)`
& input {
padding: 10px 16px;
}
`;

export const SaveButton = styled(PrimaryButton)`
margin: 16px 0;
`;

export const ButtonsContainer = styled(Box)`
display: flex;
margin-top: 108px;
`;

export const ErrorMessage = styled(Typography)`
color: red;
font-family: "Open Sans";
position: relative;
top: 20px;
font-size: 14px;
`;

export const MobileInfo = styled(Box)``;

export const BasicInfo = styled(Box)``;

export const DetailsInfo = styled(Box)``;

+ 61
- 16
src/components/ProfileCard/ProfileCard.js Просмотреть файл

@@ -26,11 +26,36 @@ import {
import { Grid, Stack } from "@mui/material";

import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
import { useSelector } from "react-redux";
import { useRouteMatch } from "react-router-dom";
import { fetchProfile } from "../../store/actions/profile/profileActions";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { selectProfile } from "../../store/selectors/profileSelectors";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useState } from "react";
import { fetchProfileOffers } from "../../store/actions/offers/offersActions";
import EditProfile from "./EditProfile";

const ProfileCard = (props) => {
const ProfileCard = () => {
const [isMyProfile, setIsMyProfile] = useState(false);
const [editProfileModal, setEditProfileModal] = useState(false);
const routeMatch = useRouteMatch();
const dispatch = useDispatch();
const profile = useSelector(selectProfile);
const userId = useSelector(selectUserId);
const idProfile = routeMatch.params.idProfile;
console.log(idProfile);
useEffect(() => {
if (idProfile?.length > 0) {
reFetchProfile();
}
}, [idProfile]);

const reFetchProfile = () => {
dispatch(fetchProfile(idProfile));
dispatch(fetchProfileOffers(idProfile));
if (userId === idProfile) setIsMyProfile(true);
};

let percentOfSucceededExchanges;
if (profile?.statistics?.exchanges?.succeeded === 0) {
@@ -42,6 +67,18 @@ const ProfileCard = (props) => {
100
);
}

const closeModalHandler = () => {
setEditProfileModal(false);
};

if (editProfileModal) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "auto";
}

console.log(profile);
return (
<>
<ProfileCardContainer>
@@ -53,12 +90,14 @@ const ProfileCard = (props) => {
sx={{ mb: 1.4 }}
>
<PersonOutlineIcon color="action" sx={{ mr: 0.9 }} />
<HeaderTitle>{props.isMyProfile ? "Moj profil" : "Profil kompanije"}</HeaderTitle>
<HeaderTitle>Moj Profil</HeaderTitle>
</Grid>
<ProfileCardWrapper variant="outlined" isMyProfile={props.isMyProfile}>
{props.isMyProfile ? (<EditButton>
<EditIcon />
</EditButton>) : (
<ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}>
{isMyProfile ? (
<EditButton onClick={() => setEditProfileModal(true)}>
<EditIcon />
</EditButton>
) : (
<MessageButton>
<MessageIcon />
</MessageButton>
@@ -89,7 +128,7 @@ const ProfileCard = (props) => {
alignItems="start"
sx={{ ml: 2 }}
>
<ProfileName isMyProfile={props.isMyProfile} variant="h5">
<ProfileName isMyProfile={isMyProfile} variant="h5">
{profile?.company?.name}
</ProfileName>
<ProfilePIBContainer
@@ -99,7 +138,7 @@ const ProfileCard = (props) => {
alignItems="center"
>
<PocketIcon />
<ProfilePIB isMyProfile={props.isMyProfile} variant="subtitle2">
<ProfilePIB isMyProfile={isMyProfile} variant="subtitle2">
PIB: {profile?.company?.PIB}
</ProfilePIB>
</ProfilePIBContainer>
@@ -113,20 +152,20 @@ const ProfileCard = (props) => {
alignItems={{ xs: "start", sm: "center" }}
>
<Stack direction="row">
<LocationIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
<LocationIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.company?.contacts?.location}
</ContactItem>
</Stack>
<Stack direction="row">
<MailIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
<MailIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.email}
</ContactItem>
</Stack>
<Stack direction="row">
<GlobeIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
<GlobeIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.company?.contacts?.web}
</ContactItem>
</Stack>
@@ -171,13 +210,19 @@ const ProfileCard = (props) => {
</Grid>
</ProfileCardWrapper>
</ProfileCardContainer>
{editProfileModal && (
<EditProfile
profile={profile}
closeModalHandler={closeModalHandler}
reFetchProfile={reFetchProfile}
/>
)}
</>
);
};

ProfileCard.propTypes = {
children: PropTypes.node,
isMyProfile: PropTypes.bool,
};

export default ProfileCard;

+ 1
- 1
src/components/Select/Option/Option.js Просмотреть файл

@@ -20,7 +20,7 @@ Option.propTypes = {
color: PropTypes.any,
startIcon: PropTypes.any,
value: PropTypes.any,
// selected: PropTypes.bool,
selected: PropTypes.bool,
};
Option.defaultProps = {
// selected: true

+ 3
- 3
src/components/Select/Select.js Просмотреть файл

@@ -8,8 +8,8 @@ import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg";
const Select = (props) => {
const [isOpened, setIsOpened] = useState(false);
const handleOpen = () => {
setIsOpened(prevState => !prevState);
}
setIsOpened((prevState) => !prevState);
};
return (
<SelectStyled
defaultValue={props.defaultValue}
@@ -37,7 +37,7 @@ Select.propTypes = {
width: PropTypes.string,
height: PropTypes.string,
fullwidth: PropTypes.bool,
defaultValue: PropTypes.number,
defaultValue: PropTypes.any,
className: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.any,

+ 27
- 7
src/components/UserReviews/UserReviews.js Просмотреть файл

@@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import {
ReviewList,
@@ -11,24 +11,44 @@ import StarBorderIcon from "@mui/icons-material/StarBorder";
import { useTranslation } from "react-i18next";
import UserReviewsCard from "../Cards/UserReviewsCard/UserReviewsCard";
import NoReviews from "./NoReviews/NoReviews";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { selectOffer } from "../../store/selectors/offersSelectors";
import { selectSelectedReviews } from "../../store/selectors/reviewSelector";
import { useRouteMatch } from "react-router-dom";
import { fetchReviews } from "../../store/actions/review/reviewActions";

const UserReviews = (props) => {
const { t } = useTranslation();
const offer = useSelector(selectOffer);
const reviews = useSelector(selectSelectedReviews);
const routeMatch = useRouteMatch();
const dispatch = useDispatch();

useEffect(() => {
console.log(routeMatch)
if (props.profileReviews && routeMatch.params?.idProfile) {
let idProfile = routeMatch.params.idProfile;
dispatch(fetchReviews(idProfile));
}
}, [props.profileReviews, routeMatch])
const lastThreeReviews = useMemo(() => {
if (props.givingReview) {
return [...props.profileReviews];
}
if (offer.companyData?.lastThreeReviews) {
return [...offer.companyData.lastThreeReviews]
if (props.isProfileReviews) {
return [...reviews.slice(0, 3)];
}
if (offer?.companyData?.lastThreeReviews) {
return [...offer?.companyData.lastThreeReviews];
}
return []
}, [props.profileReviews, offer]);
return [];
}, [props.profileReviews, offer, props.isProfileReviews, reviews]);

return (
<ReviewsBox className={props.className}>
<ReviewsBox
className={props.className}
numOfReviews={lastThreeReviews?.length}
>
{!props.givingReview && (
<ReviewsHeader
container

+ 2
- 1
src/components/UserReviews/UserReviews.styled.js Просмотреть файл

@@ -6,7 +6,8 @@ import selectedTheme from "../../themes";

export const ReviewsBox = styled(Box)`
width: 100%;
height: calc(100% - 90px);
/* One review is 185px in height and 82 px are header title + padding */
height: ${props => props.numOfReviews > 0 ? props.numOfReviews * 185 + 82 + 'px' : `calc(100% - 90px)`};
max-height: 100vh;
@media (max-width: 1200px) {
padding: 0;

+ 17
- 0
src/i18n/resources/rs.js Просмотреть файл

@@ -193,4 +193,21 @@ export default {
send: "Pošalji",
sendPlaceholder: "Poruka...",
},
editProfile: {
website: "Web Sajt*",
applink: "App Link*",
phoneNumber: "Broj Telefona",
saveChanges: "Sačuvaj Promene",
showDetails: "Prikaži Detalje",
showBasic: "Prikaži osnovno",
labelNameRequired: "Ime firme je obavezno!",
labelPIBRequired: "PIB firme je obavezan!",
labelLocationRequired: "Lokacija je obavezna!",
labelPhoneRequired: "Broj telefona je obavezan!",
},
deleteOffer: {
areYouSure: "Da li ste sigurni da želite da <br /> obrišete proizvod?",
cancel: "Otkaži",
delete: "Obriši",
},
};

+ 11
- 8
src/request/apiEndpoints.js Просмотреть файл

@@ -119,6 +119,7 @@ export default {
updateUserRegistration: "/users/{userUid}",
invite: "/users/invite",
getProfile: "users/",
editProfile: "users",
},
applications: {
application: "/applications/{applicationUid}",
@@ -159,12 +160,14 @@ export default {
setFingerprint: "/affiliate/fingerprint",
},
offers: {
getOneOffer: 'offers',
getOffers: 'offers',
addOffer: 'offers',
categories: 'categories',
locations: 'locations',
mineOffers: 'users'
getOneOffer: "offers",
getOffers: "offers",
addOffer: "offers",
categories: "categories",
locations: "locations",
mineOffers: "users",
removeOffer: "offers",
editOffer: "offers",
},
chat: {
getChat: "chats",
@@ -175,6 +178,6 @@ export default {
validateExchange: "exchanges"
},
reviews: {
postReview: "reviews"
}
postReview: "reviews",
},
};

+ 21
- 12
src/request/offersRequest.js Просмотреть файл

@@ -1,21 +1,30 @@
import { getRequest, postRequest } from "."
import apiEndpoints from "./apiEndpoints"
import { deleteRequest, getRequest, postRequest, putRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchOffers = (payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload)
return getRequest(apiEndpoints.offers.getOffers)
}
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload);
return getRequest(apiEndpoints.offers.getOffers);
};
export const attemptFetchOneOffer = (payload) => {
const url = `${apiEndpoints.offers.getOneOffer}/${payload}/frontend`;
return getRequest(url);
}
export const attemptFetchMoreOffers = (page, payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload + `&size=10&page=${page}`);
return getRequest(apiEndpoints.offers.getOffers + `?size=10&page=${page}`);
}
if (payload)
return getRequest(
apiEndpoints.offers.getOffers + payload + `&size=10&page=${page}`
);
return getRequest(apiEndpoints.offers.getOffers + `?size=10&page=${page}`);
};
export const attemptAddOffer = (payload) => {
return postRequest(apiEndpoints.offers.addOffer, payload)
}
return postRequest(apiEndpoints.offers.addOffer, payload);
};
export const attemptFetchProfileOffers = (payload) => {
return getRequest(`${apiEndpoints.offers.mineOffers}/${payload}/offers`)
}
return getRequest(`${apiEndpoints.offers.mineOffers}/${payload}/offers`);
};
export const attemptRemoveOffer = (payload) => {
return deleteRequest(apiEndpoints.offers.removeOffer + "/" + payload);
};
export const attemptEditOffer = (payload, editedData) => {
return putRequest(apiEndpoints.offers.editOffer + "/" + payload, editedData);
};

+ 4
- 1
src/request/profileRequest.js Просмотреть файл

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

export const attemptFetchProfile = (payload) =>
getRequest(apiEndpoints.users.getProfile + payload);

export const attemptEditProfile = (payload, requestData) =>
putRequest(apiEndpoints.users.editProfile + "/" + payload, requestData);

+ 4
- 1
src/request/reviewRequest.js Просмотреть файл

@@ -1,6 +1,9 @@
import { postRequest } from "."
import { getRequest, postRequest } from "."
import apiEndpoints from "./apiEndpoints"

export const attemptGiveReview = (payload) => {
return postRequest(apiEndpoints.reviews.postReview, payload);
}
export const attemptFetchReview = (payload) => {
return getRequest(`users/${payload}/reviews`)
}

+ 12
- 7
src/store/actions/offers/offersActionConstants.js Просмотреть файл

@@ -1,7 +1,12 @@
import { createClearType, createErrorType, createFetchType, createSuccessType } from "../actionHelpers";
import {
createClearType,
createErrorType,
createFetchType,
createSuccessType,
} from "../actionHelpers";

const OFFERS_SCOPE = "OFFERS_SCOPE";
const ONE_OFFER_SCOPE = "ONE_OFFER_SCOPE"
const ONE_OFFER_SCOPE = "ONE_OFFER_SCOPE";

const OFFERS_MORE_SCOPE = "OFFERS_MORE_SCOPE";
export const OFFERS_FETCH_MORE = createFetchType(OFFERS_MORE_SCOPE);
@@ -16,21 +21,21 @@ export const OFFERS_CLEAR = createClearType(OFFERS_SCOPE);
const OFFERS_PROFILE_SCOPE = "OFFERS_PROFILE_SCOPE";
export const OFFERS_PROFILE_FETCH = createFetchType(OFFERS_PROFILE_SCOPE);



export const ONE_OFFER_FETCH = createFetchType(ONE_OFFER_SCOPE);
export const ONE_OFFER_SUCCESS = createSuccessType(ONE_OFFER_FETCH);
export const ONE_OFFER_ERROR = createErrorType(ONE_OFFER_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 OFFER_SET = "OFFER_SET"
export const OFFER_SET = "OFFER_SET";
export const OFFERS_ADD = "OFFERS_ADD";
export const OFFERS_NO_MORE = "OFFERS_NO_MORE";
export const OFFERS_SET_TOTAL = "OFFERS_SET_TOTAL";
export const OFFERS_MINE_SET = "OFFERS_MY_ADD";
export const OFFER_ADD = "OFFER_ADD";
export const OFFERS_PROFILE_SET = "OFFERS_PROFILE_SET";

export const OFFER_REMOVE = "OFFER_REMOVE";

export const OFFER_EDIT = "OFFER_EDIT";

+ 30
- 20
src/store/actions/offers/offersActions.js Просмотреть файл

@@ -15,6 +15,8 @@ import {
OFFERS_SET_TOTAL,
OFFERS_SUCCESS,
OFFER_ADD,
OFFER_EDIT,
OFFER_REMOVE,
OFFER_SET,
ONE_OFFER_ERROR,
ONE_OFFER_FETCH,
@@ -49,31 +51,39 @@ export const addPinnedOffers = (payload) => ({
payload,
});
export const addOffers = (payload) => ({
type: OFFERS_ADD,
payload
})
type: OFFERS_ADD,
payload,
});
export const addOffer = (payload) => ({
type: OFFER_ADD,
payload
})
type: OFFER_ADD,
payload,
});
export const removeOffer = (payload) => ({
type: OFFER_REMOVE,
payload,
});
export const editOneOffer = (payload) => ({
type: OFFER_EDIT,
payload,
});

export const fetchOneOffer = (payload) => ({
type: ONE_OFFER_FETCH,
payload
})
type: ONE_OFFER_FETCH,
payload,
});
export const fetchOneOfferError = (payload) => ({
type: ONE_OFFER_ERROR,
payload
})
type: ONE_OFFER_ERROR,
payload,
});
export const fetchOneOfferSuccess = (payload) => ({
type: ONE_OFFER_SUCCESS,
payload
})
type: ONE_OFFER_SUCCESS,
payload,
});

export const setOffer = (payload) => ({
type: OFFER_SET,
payload
})
type: OFFER_SET,
payload,
});

export const fetchMoreOffers = (payload) => ({
type: OFFERS_FETCH_MORE,
@@ -97,8 +107,8 @@ export const setMineOffers = (payload) => ({
export const fetchProfileOffers = (payload) => ({
type: OFFERS_PROFILE_FETCH,
payload,
})
});
export const setProfileOffers = (payload) => ({
type: OFFERS_PROFILE_SET,
payload,
})
});

+ 10
- 1
src/store/actions/profile/profileActionConstants.js Просмотреть файл

@@ -1,4 +1,9 @@
import { createErrorType, createFetchType, createSuccessType } from "../actionHelpers";
import {
createErrorType,
createFetchType,
createSuccessType,
createUpdateType,
} from "../actionHelpers";

const PROFILE_SCOPE = "PROFILE_SCOPE";
export const PROFILE_FETCH = createFetchType(PROFILE_SCOPE);
@@ -10,4 +15,8 @@ export const PROFILE_MINE_FETCH = createFetchType(PROFILE_MINE_SCOPE);

export const PROFILE_SET = "PROFILE_SET";
export const PROFILE_MINE_SET = "PROFILE_MINE_SET";

const PROFILE_EDIT_SCOPE = "PROFILE_EDIT_SCOPE";
export const PROFILE_EDIT = createUpdateType(PROFILE_EDIT_SCOPE);

export const PROFILE_CLEAR = "PROFILE_CLEAR";

+ 31
- 18
src/store/actions/profile/profileActions.js Просмотреть файл

@@ -1,29 +1,42 @@
import { PROFILE_CLEAR, PROFILE_ERROR, PROFILE_FETCH, PROFILE_MINE_FETCH, PROFILE_MINE_SET, PROFILE_SET, PROFILE_SUCCESS } from "./profileActionConstants";
import {
PROFILE_CLEAR, PROFILE_ERROR,
PROFILE_FETCH,
PROFILE_MINE_FETCH,
PROFILE_MINE_SET,
PROFILE_SET,
PROFILE_SUCCESS,
PROFILE_EDIT,
} from "./profileActionConstants";

export const fetchProfile = (payload) => ({
type: PROFILE_FETCH,
payload
})
type: PROFILE_FETCH,
payload,
});
export const fetchSuccessProfile = (payload) => ({
type: PROFILE_SUCCESS,
payload,
})
type: PROFILE_SUCCESS,
payload,
});

export const fetchErrorProfile = (payload) => ({
type: PROFILE_ERROR,
payload,
})
type: PROFILE_ERROR,
payload,
});
export const setProfile = (payload) => ({
type: PROFILE_SET,
payload,
})
type: PROFILE_SET,
payload,
});
export const setMineProfile = (payload) => ({
type: PROFILE_MINE_SET,
payload,
})
type: PROFILE_MINE_SET,
payload,
});
export const fetchMineProfile = () => ({
type: PROFILE_MINE_FETCH,
})
type: PROFILE_MINE_FETCH,
});
export const editMineProfile = (payload) => ({
type: PROFILE_EDIT,
payload,
});

export const clearProfile = () => ({
type: PROFILE_CLEAR
})

+ 1
- 1
src/store/actions/review/reviewActions.js Просмотреть файл

@@ -8,7 +8,7 @@ export const giveReview = (payload) => ({
type: REVIEW_GIVE,
payload,
})
export const setReview = (payload) => ({
export const setReviews = (payload) => ({
type: REVIEW_SET,
payload,
})

+ 63
- 16
src/store/saga/offersSaga.js Просмотреть файл

@@ -1,6 +1,23 @@
import { attemptAddOffer, attemptFetchOffers, attemptFetchOneOffer } from "../../request/offersRequest";
import { OFFERS_FETCH, OFFERS_PROFILE_FETCH, OFFER_ADD, ONE_OFFER_FETCH } from "../actions/offers/offersActionConstants";
import { setOffers, setOffer, setProfileOffers } from "../actions/offers/offersActions";
import {
attemptAddOffer,
attemptEditOffer,
attemptFetchOffers,
attemptFetchOneOffer,
attemptRemoveOffer,
} from "../../request/offersRequest";
import {
OFFERS_FETCH,
OFFERS_PROFILE_FETCH,
OFFER_ADD,
OFFER_EDIT,
OFFER_REMOVE,
ONE_OFFER_FETCH,
} from "../actions/offers/offersActionConstants";
import {
setOffers,
setOffer,
setProfileOffers,
} from "../actions/offers/offersActions";
import { all, takeLatest, call, put, select } from "@redux-saga/core/effects";
import {
attemptFetchProfileOffers,
@@ -87,8 +104,9 @@ function* fetchMoreOffers(payload) {
}

function* createOffer(payload) {
console.log(payload);
try {
const data = yield call(attemptAddOffer, payload.payload.values);
const data = yield call(attemptAddOffer, payload.payload.values.offerData);
console.log(data);
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
@@ -103,7 +121,7 @@ function* fetchOneOffer(payload) {
console.log(payload);
const data = yield call(attemptFetchOneOffer, payload.payload)
console.log(data.data);
yield put(setOffer(data.data))
yield put(setOffer(data.data));
} catch (e) {
console.log(e);
}
@@ -123,21 +141,50 @@ function* fetchMineOffers() {
function* fetchProfileOffers(payload) {
try {
const userId = payload.payload;
const data = yield call(attemptFetchProfileOffers, userId)
yield put (setProfileOffers(data.data));
const data = yield call(attemptFetchProfileOffers, userId);
yield put(setProfileOffers(data.data));
} catch (e) {
console.log(e);
}
}

function* removeOffer(payload) {
console.log(payload);
try {
const offerId = payload.payload.offerId;
yield call(attemptRemoveOffer, offerId);
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
}
} catch (e) {
console.log(e);
}
}

function* editOffer(payload) {
yield console.log(payload);

try {
const offerId = payload.payload.offerId;
const editedData = payload.payload.offerData;
yield call(attemptEditOffer, offerId, editedData);
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
}
} catch (e) {
console.log(e);
}
}

export default function* offersSaga() {
yield all(
[
takeLatest(OFFERS_FETCH, fetchOffers),
takeLatest(OFFER_ADD, createOffer),
takeLatest(ONE_OFFER_FETCH, fetchOneOffer),
takeLatest(OFFERS_FETCH_MORE, fetchMoreOffers),
takeLatest(OFFERS_MINE_FETCH, fetchMineOffers),
takeLatest(OFFERS_PROFILE_FETCH, fetchProfileOffers)
]);
yield all([
takeLatest(OFFERS_FETCH, fetchOffers),
takeLatest(OFFER_ADD, createOffer),
takeLatest(ONE_OFFER_FETCH, fetchOneOffer),
takeLatest(OFFERS_FETCH_MORE, fetchMoreOffers),
takeLatest(OFFERS_MINE_FETCH, fetchMineOffers),
takeLatest(OFFERS_PROFILE_FETCH, fetchProfileOffers),
takeLatest(OFFER_REMOVE, removeOffer),
takeLatest(OFFER_EDIT, editOffer),
]);
}

+ 75
- 24
src/store/saga/profileSaga.js Просмотреть файл

@@ -1,36 +1,87 @@
import { all, call, put, takeLatest, select } from "@redux-saga/core/effects";
import { attemptFetchProfile } from "../../request/profileRequest";
import { PROFILE_FETCH, PROFILE_MINE_FETCH } from "../actions/profile/profileActionConstants";
import { setMineProfile, setProfile } from "../actions/profile/profileActions";
import {
attemptEditProfile,
attemptFetchProfile,
} from "../../request/profileRequest";
import {
PROFILE_FETCH,
PROFILE_MINE_FETCH,
PROFILE_EDIT,
} from "../actions/profile/profileActionConstants";
import {
// editMineProfile,
setMineProfile,
setProfile,
} from "../actions/profile/profileActions";
import { selectUserId } from "../selectors/loginSelectors";

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);
}
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);
}
}

function* fetchMineProfile() {
try {
const userId = yield select(selectUserId);
const data = yield call(attemptFetchProfile, userId);
console.log(data);
if (data) yield put(setMineProfile(data.data));
try {
const userId = yield select(selectUserId);
const data = yield call(attemptFetchProfile, userId);
console.log(data);
if (data) yield put(setMineProfile(data.data));
} catch (e) {
console.log(e);
}
}

function* changeMineProfile(payload) {
try {
yield console.log(payload);

let image;

if (payload.payload.firmLogo.includes("data:image")) {
image = payload.payload.firmLogo
.replace("data:image/jpeg;base64,", "")
.replace("data:image/jpg;base64,", "")
.replace("data:image/png;base64,", "");
} else if (payload.payload.firmLogo === "") {
image = "";
}
catch (e) {
console.log(e);

const reqData = {
company: {
name: payload.payload.firmName,
PIB: payload.payload.firmPIB,
contacts: {
telephone: payload.payload.firmPhone,
location: payload.payload.firmLocation,
web: payload.payload.firmWebsite,
},
},
image: image,
};

if (payload.payload.firmLogo.includes("https")) delete reqData.image;

const userId = yield select(selectUserId);
const data = yield call(attemptEditProfile, userId, reqData);
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
}
console.log(data);
} catch (e) {
console.log(e);
}
}

export default function* profileSaga() {
yield all([
takeLatest(PROFILE_FETCH, fetchProfile),
takeLatest(PROFILE_MINE_FETCH, fetchMineProfile)
])
}
yield all([
takeLatest(PROFILE_FETCH, fetchProfile),
takeLatest(PROFILE_MINE_FETCH, fetchMineProfile),
takeLatest(PROFILE_EDIT, changeMineProfile),
]);
}

+ 6
- 2
src/store/saga/reviewSaga.js Просмотреть файл

@@ -1,10 +1,14 @@
import { all, takeLatest, call } from "@redux-saga/core/effects";
import { attemptGiveReview } from "../../request/reviewRequest";
import { all, takeLatest, call, put } from "@redux-saga/core/effects";
import { attemptFetchReview, attemptGiveReview } from "../../request/reviewRequest";
import { REVIEW_GET, REVIEW_GIVE } from "../actions/review/reviewActionConstants";
import { setReviews } from "../actions/review/reviewActions";

function* fetchReviews(payload) {
try {
yield call(console.log, payload);
const data = yield call(attemptFetchReview, payload.payload);
yield put(setReviews([...data.data].reverse()));
yield call(console.log, data);
}
catch(e) {
console.log(e);

Загрузка…
Отмена
Сохранить