소스 검색

Merged chat with edit-delete

feature/code-cleanup-joca
Djordje Mitrovic 3 년 전
부모
커밋
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);

Loading…
취소
저장