소스 검색

Partly done code-cleanup

feature/code-cleanup-joca
Djordje Mitrovic 3 년 전
부모
커밋
7c7834c7c3
100개의 변경된 파일1700개의 추가작업 그리고 3102개의 파일을 삭제
  1. 3
    0
      .eslintrc
  2. 15
    19
      .eslintrc.json
  3. 8
    56
      src/components/Cards/ChatCard/ChatCard.js
  4. 5
    230
      src/components/Cards/ChatCard/ChatCard.styled.js
  5. 37
    0
      src/components/Cards/ChatCard/ChatCommands/ChatCommands.js
  6. 75
    0
      src/components/Cards/ChatCard/ChatCommands/ChatCommands.styled.js
  7. 36
    0
      src/components/Cards/ChatCard/LittleOfferDetails/LittleOfferDetails.js
  8. 85
    0
      src/components/Cards/ChatCard/LittleOfferDetails/LittleOfferDetails.styled.js
  9. 25
    0
      src/components/Cards/ChatCard/MobileOfferDetails/MobileOfferDetails.js
  10. 43
    0
      src/components/Cards/ChatCard/MobileOfferDetails/MobileOfferDetails.styled.js
  11. 21
    0
      src/components/Cards/ChatCard/OfferLocation/OfferLocation.js
  12. 33
    0
      src/components/Cards/ChatCard/OfferLocation/OfferLocation.styled.js
  13. 32
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/Checkbox/Checkbox.js
  14. 0
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/Checkbox/Checkbox.styled.js
  15. 84
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/CheckboxDropdownList.js
  16. 25
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/CheckboxDropdownList.styled.js
  17. 42
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/SearchField/SearchField.js
  18. 17
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/SearchField/SearchField.styled.js
  19. 29
    94
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  20. 4
    37
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.styled.js
  21. 5
    27
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.js
  22. 1
    96
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js
  23. 0
    36
      src/components/Cards/ItemDetailsCard/MockupOfferDetails.js
  24. 41
    9
      src/components/Cards/ItemDetailsCard/OfferDetails/OfferDetails.js
  25. 101
    0
      src/components/Cards/ItemDetailsCard/OfferDetails/OfferDetails.styled.js
  26. 6
    4
      src/components/Cards/MessageCard/MessageCard.js
  27. 0
    24
      src/components/Cards/UserReviewsCard/Mockupdata.js
  28. 12
    36
      src/components/Cards/UserReviewsCard/UserReviewsCard.js
  29. 3
    3
      src/components/ChatColumn/ChatColumn.js
  30. 4
    14
      src/components/CreateReview/CreateReview.js
  31. 13
    18
      src/components/DirectChat/DirectChat.js
  32. 3
    3
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.js
  33. 16
    12
      src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js
  34. 9
    5
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js
  35. 18
    36
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.js
  36. 18
    12
      src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js
  37. 17
    14
      src/components/Dropdown/DropdownList/DropdownList.js
  38. 8
    3
      src/components/Header/Drawer/Drawer.js
  39. 12
    21
      src/components/ImagePicker/ImagePicker.js
  40. 0
    187
      src/components/InputFields/BaseInputField.js
  41. 0
    40
      src/components/InputFields/Checkbox.js
  42. 0
    123
      src/components/InputFields/CurrencyField.js
  43. 0
    33
      src/components/InputFields/EmailField.js
  44. 0
    74
      src/components/InputFields/NumberField.js
  45. 0
    74
      src/components/InputFields/PasswordField.js
  46. 0
    130
      src/components/InputFields/PasswordStrength.js
  47. 0
    45
      src/components/InputFields/PercentageField.js
  48. 0
    49
      src/components/InputFields/PhoneNumberField.js
  49. 0
    54
      src/components/InputFields/Radio.js
  50. 0
    37
      src/components/InputFields/Search.js
  51. 0
    122
      src/components/InputFields/SelectField.js
  52. 0
    72
      src/components/InputFields/TextField.js
  53. 8
    9
      src/components/ItemDetails/Header/Header.js
  54. 19
    21
      src/components/ItemDetails/ItemDetails.js
  55. 8
    71
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js
  56. 2
    69
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.styled.js
  57. 30
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/Category/CategoryDetail.js
  58. 37
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/Category/CategoryDetail.styled.js
  59. 30
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/PIB/PIBDetail.js
  60. 49
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/PIB/PIBDetail.styled.js
  61. 60
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/StatisticDetails/StatisticDetails.js
  62. 30
    0
      src/components/ItemDetails/ItemDetailsHeaderCard/StatisticDetails/StatisticDetails.styled.js
  63. 0
    59
      src/components/ItemDetails/MockupdataDetails.js
  64. 0
    26
      src/components/Loader/BlockSectionLoader.js
  65. 0
    13
      src/components/Loader/FullPageLoader.js
  66. 0
    10
      src/components/Loader/FullPageLoader.styled.js
  67. 0
    20
      src/components/Loader/SectionLoader.js
  68. 19
    0
      src/components/Login/Description/LoginDescription.js
  69. 18
    0
      src/components/Login/Description/LoginDescription.styled.js
  70. 26
    0
      src/components/Login/ErrorMessage/ErrorMessage.js
  71. 11
    0
      src/components/Login/ErrorMessage/ErrorMessage.styled.js
  72. 30
    0
      src/components/Login/Fields/Email/EmailField.js
  73. 0
    0
      src/components/Login/Fields/Email/EmailField.styled.js
  74. 50
    0
      src/components/Login/Fields/Password/PasswordField.js
  75. 0
    0
      src/components/Login/Fields/Password/PasswordField.styled.js
  76. 34
    0
      src/components/Login/ForgotPasswordLink/ForgotPasswordLink.js
  77. 0
    0
      src/components/Login/ForgotPasswordLink/ForgotPasswordLink.styled.js
  78. 98
    0
      src/components/Login/Login.js
  79. 19
    0
      src/components/Login/Login.styled.js
  80. 31
    0
      src/components/Login/LoginButton/LoginButton.js
  81. 0
    0
      src/components/Login/LoginButton/LoginButton.styled.js
  82. 27
    0
      src/components/Login/RegisterLink/RegisterLink.js
  83. 20
    0
      src/components/Login/RegisterLink/RegisterLink.styled.js
  84. 19
    0
      src/components/Login/Title/LoginTitle.js
  85. 17
    0
      src/components/Login/Title/LoginTitle.styled.js
  86. 0
    57
      src/components/MUI/DialogComponent.js
  87. 21
    21
      src/components/MUI/DrawerComponent.js
  88. 0
    15
      src/components/MUI/ErrorMessageComponent.js
  89. 0
    29
      src/components/MUI/Examples/DataGridExample.js
  90. 0
    64
      src/components/MUI/Examples/ModalsExample.js
  91. 0
    183
      src/components/MUI/Examples/PagingSortingFilteringExample.js
  92. 0
    159
      src/components/MUI/Examples/PagingSortingFilteringExampleServerSide.js
  93. 0
    26
      src/components/MUI/MenuListComponent.js
  94. 0
    160
      src/components/MUI/NavbarComponent.js
  95. 35
    10
      src/components/MarketPlace/Header/Header.js
  96. 0
    3
      src/components/MarketPlace/MarketPlace.js
  97. 18
    223
      src/components/MarketPlace/Offers/Offers.js
  98. 14
    1
      src/components/Paging/Paging.js
  99. 14
    4
      src/components/Popovers/HeaderPopover/HeaderPopover.js
  100. 0
    0
      src/components/Popovers/MyMessages/MyMessages.js

+ 3
- 0
.eslintrc 파일 보기

@@ -13,6 +13,9 @@
"react/jsx-filename-extension": "off",
"react/jsx-props-no-spreading": "off",
"react/button-has-type": "off",
"react/max-len": ["error", {
"code": 100
}],
"react/require-default-props": "off",
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off",

+ 15
- 19
.eslintrc.json 파일 보기

@@ -1,22 +1,18 @@
{
"env": {
"browser": true,
"es2021": true
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
}
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"max-lines": ["warn", 100]
}
}

+ 8
- 56
src/components/Cards/ChatCard/ChatCard.js 파일 보기

@@ -1,41 +1,25 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
OfferImage,
OfferTitle,
ChatOffer,
Commands,
ChatInfo,
OfferText,
ChatCardContainer,
Col,
UserImage,
OfferCardContainer,
UserImgWrapper,
OfferImgWrapper,
UserName,
LastMessage,
Line,
LocationContainer,
XSText,
LocationIcon,
OfferCardContainerMobile,
OfferTextMobile,
OfferTitleMobile,
PhoneIconContainer,
PhoneIcon,
LocationIconContainer,
} from "./ChatCard.styled";
import selectedTheme from "../../../themes";
import { useHistory } from "react-router-dom";
import useScreenDimensions from "../../../hooks/useScreenDimensions";
import { useTranslation } from "react-i18next";
import LittleOfferDetails from "./LittleOfferDetails/LittleOfferDetails";
import MobileOfferDetails from "./MobileOfferDetails/MobileOfferDetails";
import OfferLocation from "./OfferLocation/OfferLocation";
import ChatCommands from "./ChatCommands/ChatCommands";

const ChatCard = (props) => {
const history = useHistory();
const dimensions = useScreenDimensions();
const { t } = useTranslation();

const chat = useMemo(() => {
return props.chat;
@@ -47,7 +31,6 @@ const ChatCard = (props) => {
}
return "";
}, [chat]);

const routeToItem = () => {
history.push(`/messages/${chat?.chat?._id}`);
};
@@ -66,51 +49,20 @@ const ChatCard = (props) => {
<UserName>{chat?.interlocutorData?.name}</UserName>

{/* Only shows on Mobile */}
<OfferCardContainerMobile>
<OfferTextMobile>{t("messages.cardProduct")}</OfferTextMobile>
<OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile>
</OfferCardContainerMobile>
<MobileOfferDetails chat={chat} />
{/* ^^^^^ */}

<LastMessage>{lastMessage}</LastMessage>
<LocationContainer>
<LocationIconContainer>
<LocationIcon />
</LocationIconContainer>
<XSText>{chat?.interlocutorData?.location}</XSText>
</LocationContainer>
<OfferLocation chat={chat} />
</ChatInfo>
</Col>
<Line />

{/* Only shows on Desktop */}
<Col mobileDisappear>
<ChatOffer>
<OfferImgWrapper>
<OfferImage src={chat?.offerData?.firstImage} />
</OfferImgWrapper>
<OfferCardContainer>
<OfferText>{t("messages.cardProduct")}</OfferText>
<OfferTitle>{chat?.offerData?.name}</OfferTitle>
</OfferCardContainer>
</ChatOffer>
</Col>
<LittleOfferDetails chat={chat} />
{/* ^^^^^^^ */}

<Commands>
<PhoneIconContainer>
<PhoneIcon />
</PhoneIconContainer>
<CheckButton
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryPurple}
variant={"outlined"}
style={{ fontWeight: "600" }}
onClick={routeToItem}
>
{t("messages.seeChats")}
</CheckButton>
</Commands>
<ChatCommands routeToItem={() => routeToItem(chat?.chat?._id)} />
</ChatCardContainer>
);
};

+ 5
- 230
src/components/Cards/ChatCard/ChatCard.styled.js 파일 보기

@@ -1,10 +1,6 @@
import { Box, Container, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { ReactComponent as Phone } from "../../../assets/images/svg/phone.svg";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";

export const ChatCardContainer = styled(Container)`
display: flex;
@@ -14,13 +10,10 @@ export const ChatCardContainer = styled(Container)`
box-sizing: border-box;
margin: 10px 0;
background-color: ${(props) =>
props.sponsored === "true"
? selectedTheme.backgroundSponsoredColor
: "white"};
props.sponsored === "true" ? selectedTheme.backgroundSponsoredColor : "white"};
border-radius: 4px;
${(props) =>
props.sponsored === "true" &&
`border: 1px solid ${selectedTheme.borderSponsoredColor};`}
props.sponsored === "true" && `border: 1px solid ${selectedTheme.borderSponsoredColor};`}
padding: 16px;
max-width: 2000px;
height: 180px;
@@ -31,11 +24,9 @@ export const ChatCardContainer = styled(Container)`
margin: 0;
${(props) =>
props.vertical &&
`
height: 330px;
`height: 330px;
width: 180px;
margin: 0 18px;
`}
margin: 0 18px;`}
}
`;
export const UserImage = styled.img`
@@ -47,7 +38,6 @@ export const UserImage = styled.img`
height: 72px;
}
`;

export const UserImgWrapper = styled(Box)`
overflow: hidden;
border-radius: 50%;
@@ -59,191 +49,19 @@ export const UserImgWrapper = styled(Box)`
min-width: 80px;
}
`;
export const OfferImgWrapper = styled(Box)`
overflow: hidden;
border-radius: 4px;
width: 72px;
height: 72px;
min-width: 72px;
max-width: 72px;
`;
export const LocationIcon = styled(Location)`
height: 12px;
width: 12px;
`;

export const OfferCardContainer = styled(Container)`
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 16px;
max-width: 2000px;
position: relative;
@media (max-width: 550px) {
}
`;

export const OfferCardContainerMobile = styled(Box)`
display: none;

@media (max-width: 550px) {
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
`;

export const OfferTitle = styled(Typography)`
font-family: "Open Sans";
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 18px;
cursor: pointer;
@media (max-width: 550px) {
font-size: 14px;
display: none;
${(props) =>
props.vertical &&
`
display: flex;
flex: none;
position: relative;
line-height: 22px;
margin-top: 5px;
font-size: 18px;

`}
}
`;
export const OfferTitleMobile = styled(Typography)`
font-family: "Open Sans";
display: none;
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 12px;
cursor: pointer;
@media (max-width: 550px) {
display: block;
${(props) =>
props.vertical &&
`
display: flex;
flex: none;
position: relative;
line-height: 22px;
margin-top: 5px;
font-size: 18px;

`}
}
`;

export const CheckButton = styled(PrimaryButton)`
width: 180px;
height: 48px;
&:hover {
background-color: ${selectedTheme.primaryPurple};
color: white !important;
border-radius: 4px;
}
@media (max-width: 1024px) {
width: 150px;
height: 40px;
margin-left: 2vw;
& button {
padding: 0;
font-size: 11px;
}
}
@media (max-width: 600px) {
display: none;
}
transition: 0.2s all;
`;
export const PhoneIconContainer = styled(IconButton)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor};
border-radius: 100%;
padding-top: 2px;
text-align: center;
@media (max-width: 600px) {
width: 32px;
height: 32px;
top: 16px;
right: 16px;
padding: 0;
${(props) =>
props.vertical &&
`
display: none;
`}
& button svg {
width: 16px;
height: 16px;
position: relative;
top: -3px;
left: -2.4px;
}
}
`;

export const ChatOffer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
padding-left: 36px;
@media (max-width: 600px) {
display: none;
}
`;

export const OfferText = styled(Box)`
font-family: "Open Sans";
font-size: "12px";
color: ${selectedTheme.primaryText};
`;

export const OfferTextMobile = styled(Box)`
font-family: "Open Sans";
font-size: 9px;
color: ${selectedTheme.primaryText};
`;

export const Commands = styled(Box)`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
@media (max-width: 600px) {
align-items: flex-start;
}
`;

export const ChatInfo = styled(Box)`
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
`;

export const Col = styled(Box)`
display: flex;
align-items: center;
flex-direction: row;
gap: 18px;
flex: 1;
@media (max-width: 1024px) {
${(props) => props.mobileDisappear && `display: none;`}
}
@media (max-width: 600px) {
${(props) => props.mobileDisappear && `display: none;`}
}
`;

export const UserName = styled(Typography)`
margin-bottom: 12px;
font-family: "Open Sans";
@@ -255,7 +73,6 @@ export const UserName = styled(Typography)`
font-size: 18px;
}
`;

export const LastMessage = styled(Typography)`
font-family: "Open Sans";
color: ${selectedTheme.primaryDarkTextThird};
@@ -273,39 +90,6 @@ export const LastMessage = styled(Typography)`
display: none;
}
`;

export const LocationContainer = styled(Box)`
display: flex;
flex-direction: row;
gap: 2px;
@media (max-width: 600px) {
display: none;
}
`;

export const LocationIconContainer = styled(Box)`
height: 12px;
width: auto;
position: relative;
`;

export const XSText = styled(Typography)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
font-size: 14px;
position: relative;
`;

export const OfferImage = styled.img`
max-width: 72px;
max-height: 72px;
min-width: 72px;
width: 72px;
height: 72px;
border-radius: 4px;
`;

export const Line = styled(Box)`
border-left: 1px solid rgba(0, 0, 0, 0.15);
height: 100px;
@@ -313,13 +97,4 @@ export const Line = styled(Box)`
margin: auto 0;
@media (max-width: 600px) {
display: none;
}
`;
export const PhoneIcon = styled(Phone)`
@media (max-width: 600px) {
width: 14px;
height: 14px;
position: relative;
top: 1px;
}
`;
}`;

+ 37
- 0
src/components/Cards/ChatCard/ChatCommands/ChatCommands.js 파일 보기

@@ -0,0 +1,37 @@
import React from "react";
import PropTypes from "prop-types";
import {
CheckButton,
Commands,
PhoneIcon,
PhoneIconContainer,
} from "./ChatCommands.styled";
import selectedTheme from "../../../../themes";
import { useTranslation } from "react-i18next";

const ChatCommands = (props) => {
const { t } = useTranslation();

return (
<Commands>
<PhoneIconContainer>
<PhoneIcon />
</PhoneIconContainer>
<CheckButton
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryPurple}
variant={"outlined"}
style={{ fontWeight: "600" }}
onClick={props.routeToItem}
>
{t("messages.seeChats")}
</CheckButton>
</Commands>
);
};

ChatCommands.propTypes = {
routeToItem: PropTypes.func,
};

export default ChatCommands;

+ 75
- 0
src/components/Cards/ChatCard/ChatCommands/ChatCommands.styled.js 파일 보기

@@ -0,0 +1,75 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { ReactComponent as Phone } from "../../../../assets/images/svg/phone.svg";
import selectedTheme from "../../../../themes";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import IconButton from "../../../IconButton/IconButton";


export const Commands = styled(Box)`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
@media (max-width: 600px) {
align-items: flex-start;
}
`;
export const PhoneIcon = styled(Phone)`
@media (max-width: 600px) {
width: 14px;
height: 14px;
position: relative;
top: 1px;
}
`;
export const PhoneIconContainer = styled(IconButton)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor};
border-radius: 100%;
padding-top: 2px;
text-align: center;
@media (max-width: 600px) {
width: 32px;
height: 32px;
top: 16px;
right: 16px;
padding: 0;
${(props) =>
props.vertical &&
`
display: none;
`}
& button svg {
width: 16px;
height: 16px;
position: relative;
top: -3px;
left: -2.4px;
}
}
`;

export const CheckButton = styled(PrimaryButton)`
width: 180px;
height: 48px;
&:hover {
background-color: ${selectedTheme.primaryPurple};
color: white !important;
border-radius: 4px;
}
@media (max-width: 1024px) {
width: 150px;
height: 40px;
margin-left: 2vw;
& button {
padding: 0;
font-size: 11px;
}
}
@media (max-width: 600px) {
display: none;
}
transition: 0.2s all;
`;

+ 36
- 0
src/components/Cards/ChatCard/LittleOfferDetails/LittleOfferDetails.js 파일 보기

@@ -0,0 +1,36 @@
import React from "react";
import PropTypes from "prop-types";
import {
ChatOffer,
OfferCardContainer,
OfferImage,
OfferImgWrapper,
OfferText,
OfferTitle,
} from "./LittleOfferDetails.styled";
import { useTranslation } from "react-i18next";
import { Col } from "../ChatCard.styled";

const LittleOfferDetails = (props) => {
const chat = props.chat;
const { t } = useTranslation();
return (
<Col mobileDisappear>
<ChatOffer>
<OfferImgWrapper>
<OfferImage src={chat?.offerData?.firstImage} />
</OfferImgWrapper>
<OfferCardContainer>
<OfferText>{t("messages.cardProduct")}</OfferText>
<OfferTitle>{chat?.offerData?.name}</OfferTitle>
</OfferCardContainer>
</ChatOffer>
</Col>
);
};

LittleOfferDetails.propTypes = {
chat: PropTypes.any,
};

export default LittleOfferDetails;

+ 85
- 0
src/components/Cards/ChatCard/LittleOfferDetails/LittleOfferDetails.styled.js 파일 보기

@@ -0,0 +1,85 @@
import { Box, Container, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";

export const ChatOffer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
padding-left: 36px;
@media (max-width: 600px) {
display: none;
}
`;

export const OfferText = styled(Box)`
font-family: "Open Sans";
font-size: "12px";
color: ${selectedTheme.primaryText};
`;

export const OfferTitle = styled(Typography)`
font-family: "Open Sans";
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 18px;
cursor: pointer;
@media (max-width: 550px) {
font-size: 14px;
display: none;
${(props) =>
props.vertical &&
`
display: flex;
flex: none;
position: relative;
line-height: 22px;
margin-top: 5px;
font-size: 18px;

`}
}
`;

export const OfferCardContainer = styled(Container)`
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 16px;
max-width: 2000px;
position: relative;
@media (max-width: 550px) {
}
`;

export const OfferImgWrapper = styled(Box)`
overflow: hidden;
border-radius: 4px;
width: 72px;
height: 72px;
min-width: 72px;
max-width: 72px;
`;

export const OfferImage = styled.img`
max-width: 72px;
max-height: 72px;
min-width: 72px;
width: 72px;
height: 72px;
border-radius: 4px;
`;
export const Col = styled(Box)`
display: flex;
align-items: center;
flex-direction: row;
gap: 18px;
flex: 1;
@media (max-width: 1024px) {
${(props) => props.mobileDisappear && `display: none;`}
}
@media (max-width: 600px) {
${(props) => props.mobileDisappear && `display: none;`}
}
`;

+ 25
- 0
src/components/Cards/ChatCard/MobileOfferDetails/MobileOfferDetails.js 파일 보기

@@ -0,0 +1,25 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import {
OfferCardContainerMobile,
OfferTextMobile,
OfferTitleMobile,
} from "./MobileOfferDetails.styled";

const MobileOfferDetails = (props) => {
const chat = props.chat;
const { t } = useTranslation();
return (
<OfferCardContainerMobile>
<OfferTextMobile>{t("messages.cardProduct")}</OfferTextMobile>
<OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile>
</OfferCardContainerMobile>
);
};

MobileOfferDetails.propTypes = {
chat: PropTypes.any,
};

export default MobileOfferDetails;

+ 43
- 0
src/components/Cards/ChatCard/MobileOfferDetails/MobileOfferDetails.styled.js 파일 보기

@@ -0,0 +1,43 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";

export const OfferCardContainerMobile = styled(Box)`
display: none;

@media (max-width: 550px) {
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
`;

export const OfferTitleMobile = styled(Typography)`
font-family: "Open Sans";
display: none;
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 12px;
cursor: pointer;
@media (max-width: 550px) {
display: block;
${(props) =>
props.vertical &&
`
display: flex;
flex: none;
position: relative;
line-height: 22px;
margin-top: 5px;
font-size: 18px;

`}
}
`;
export const OfferTextMobile = styled(Box)`
font-family: "Open Sans";
font-size: 9px;
color: ${selectedTheme.primaryText};
`;

+ 21
- 0
src/components/Cards/ChatCard/OfferLocation/OfferLocation.js 파일 보기

@@ -0,0 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import { LocationContainer, LocationIcon, LocationIconContainer, XSText } from './OfferLocation.styled';

const OfferLocation = (props) => {
const chat = props.chat;
return (
<LocationContainer>
<LocationIconContainer>
<LocationIcon />
</LocationIconContainer>
<XSText>{chat?.interlocutorData?.location}</XSText>
</LocationContainer>
)
}

OfferLocation.propTypes = {
chat: PropTypes.any,
}

export default OfferLocation

+ 33
- 0
src/components/Cards/ChatCard/OfferLocation/OfferLocation.styled.js 파일 보기

@@ -0,0 +1,33 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { ReactComponent as Location } from "../../../../assets/images/svg/location.svg";


export const LocationContainer = styled(Box)`
display: flex;
flex-direction: row;
gap: 2px;
@media (max-width: 600px) {
display: none;
}
`;

export const LocationIconContainer = styled(Box)`
height: 12px;
width: auto;
position: relative;
`;

export const XSText = styled(Typography)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
font-size: 14px;
position: relative;
`;

export const LocationIcon = styled(Location)`
height: 12px;
width: 12px;
`;

+ 32
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/Checkbox/Checkbox.js 파일 보기

@@ -0,0 +1,32 @@
import React from "react";
import PropTypes from "prop-types";
import { CheckBox as CheckboxButton } from "../../../../../CheckBox/CheckBox";

const Checkbox = (props) => {
const item = props.item;
return (
<CheckboxButton
leftText={item.city}
rightText={item.offerCount}
value={item}
checked={
props.filters.find(
(itemInList) =>
itemInList?.city?.toString() === item?.city?.toString()
)
? true
: false
}
onChange={props.onChange}
fullWidth
/>
);
};

Checkbox.propTypes = {
item: PropTypes.any,
filters: PropTypes.any,
onChange: PropTypes.func,
};

export default Checkbox;

+ 0
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/Checkbox/Checkbox.styled.js 파일 보기


+ 84
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/CheckboxDropdownList.js 파일 보기

@@ -0,0 +1,84 @@
import React from "react";
import PropTypes from "prop-types";
import DropdownList from "../../../../../Dropdown/DropdownList/DropdownList";
import selectedTheme from "../../../../../../themes";
import IconWithNumber from "../../../../../Icon/IconWithNumber/IconWithNumber";
import { ReactComponent as DropdownDown } from "../../../../../../assets/images/svg/down-arrow.svg";
import { ReactComponent as DropdownUp } from "../../../../../../assets/images/svg/up-arrow.svg";
import { ReactComponent as Close } from "../../../../../../assets/images/svg/close-white.svg";
import {
SelectedItem,
SelectedItemsContainer,
} from "./CheckboxDropdownList.styled";
import SearchField from "./SearchField/SearchField";

const CheckboxDropdownList = (props) => {
const data = props.data;
const handleDelete = (item) => {
props.setItemsSelected([...props.filters.filter((p) => p !== item)]);
};
return (
<DropdownList
title={props.title}
textcolor={
props.filters.length > 0
? selectedTheme.primaryPurple
: selectedTheme.primaryText
}
dropdownIcon={
<IconWithNumber number={props.filters.length}>
{props.icon}
</IconWithNumber>
}
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
open={props.isOpened}
setIsOpened={props.setIsOpened}
toggleIconStyles={{
backgroundColor: props.isOpened
? "white"
: selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
<React.Fragment>
<SelectedItemsContainer>
{props.filters.map((item) => (
<SelectedItem key={item.city} onClick={() => handleDelete(item)}>
{
data.find(
(p) => p?.city?.toString() === item?.city?.toString()
)?.city
}
<Close style={{ position: "relative", top: "3px" }} />
</SelectedItem>
))}
</SelectedItemsContainer>
<SearchField
placeholder={props.searchPlaceholder}
value={props.toSearch}
onChange={(event) => props.setToSearch(event.target.value)}
/>
</React.Fragment>
}
>
{props.children}
</DropdownList>
);
};

CheckboxDropdownList.propTypes = {
children: PropTypes.node,
title: PropTypes.string,
filters: PropTypes.any,
icon: PropTypes.node,
setToSearch: PropTypes.func,
setItemsSelected: PropTypes.func,
data: PropTypes.any,
searchPlaceholder: PropTypes.string,
toSearch: PropTypes.string,
isOpened: PropTypes.bool,
setIsOpened: PropTypes.func,
};

export default CheckboxDropdownList;

+ 25
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/CheckboxDropdownList.styled.js 파일 보기

@@ -0,0 +1,25 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../../../themes";

export const SelectedItemsContainer = styled(Box)`
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-top: 5px;
`;
export const SelectedItem = styled(Box)`
margin-top: 2px;
background-color: ${selectedTheme.primaryPurple};
border-radius: 8px;
color: white;
padding-left: 8px;
padding-right: 6px;
line-height: 12px;
letter-spacing: 0.02em;
font-family: "Open Sans";
font-size: 12px;
cursor: pointer;
margin-right: 3px;
height: 22px;
`;

+ 42
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/SearchField/SearchField.js 파일 보기

@@ -0,0 +1,42 @@
import React from "react";
import PropTypes from "prop-types";
import { TextField } from "../../../../../../TextFields/TextField/TextField";
import { ReactComponent as CloseBlack } from "../../../../../../../assets/images/svg/close-black.svg";
import { ClearText } from "./SearchField.styled";

const SearchField = (props) => {
const handleClear = () => {
props.onChange("");
};
return (
<TextField
placeholder={props.placeholder}
italicPlaceholder
value={props.value}
onChange={props.onChange}
textsize={"12px"}
font={"Open Sans"}
fullWidth
height={"40px"}
containerStyle={{ marginTop: "6px" }}
InputProps={{
endAdornment:
props.value.length > 0 ? (
<ClearText onClick={handleClear}>
<CloseBlack />
</ClearText>
) : (
<React.Fragment />
),
}}
/>
);
};

SearchField.propTypes = {
value: PropTypes.string,
placeholder: PropTypes.string,
onChange: PropTypes.func,
};

export default SearchField;

+ 17
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/CheckboxDropdownList/SearchField/SearchField.styled.js 파일 보기

@@ -0,0 +1,17 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../../../../themes";

export const ClearText = styled(Box)`
padding-top: 1px;
border-radius: 100%;
cursor: pointer;
padding-right: 2px;
position: relative;
left: 6px;
width: 21px;
height: 21px;
&:hover {
background-color: ${selectedTheme.primaryIconBackgroundColor};
}
`;

+ 29
- 94
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js 파일 보기

@@ -1,30 +1,19 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import DropdownList from "../../../../Dropdown/DropdownList/DropdownList";
import selectedTheme from "../../../../../themes";
import IconWithNumber from "../../../../Icon/IconWithNumber/IconWithNumber";
import { ReactComponent as DropdownDown } from "../../../../../assets/images/svg/down-arrow.svg";
import { ReactComponent as DropdownUp } from "../../../../../assets/images/svg/up-arrow.svg";
import { ReactComponent as Close } from "../../../../../assets/images/svg/close-white.svg";
import { ReactComponent as CloseBlack } from "../../../../../assets/images/svg/close-black.svg";
import {
ClearText,
SelectedItem,
SelectedItemsContainer,
} from "./FilterCheckboxDropdown.styled";
import { TextField } from "../../../../TextFields/TextField/TextField";
import DropdownItem from "../../../../Dropdown/DropdownItem/DropdownItem";
import { CheckBox } from "../../../../CheckBox/CheckBox";
import CheckboxDropdownList from "./CheckboxDropdownList/CheckboxDropdownList";
import Checkbox from "./Checkbox/Checkbox";

const FilterCheckboxDropdown = (props) => {
const [toSearch, setToSearch] = useState("");
const [dataToShow, setDataToShow] = useState([]);
const [isOpened, setIsOpened] = useState(false);
const [toSearch, setToSearch] = useState("");
const { data } = props;

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

useEffect(() => {
if (toSearch.length > 0) {
setDataToShow(
@@ -37,110 +26,57 @@ const FilterCheckboxDropdown = (props) => {
}
}, [toSearch]);


useEffect(() => {
if (props.filters?.length > 0) {
setIsOpened(true)
setIsOpened(true);
}
}, [props.filters])
}, [props.filters]);

const handleChange = (item) => {
if (props.oneValueAllowed) {
props.setItemsSelected([item]);
} else {
if (props.filters.find(itemInList => itemInList?.city?.toString() === item?.city?.toString())) {
props.setItemsSelected([...props.filters.filter((p) => p?.city?.toString() !== item?.city?.toString())]);
if (
props.filters.find(
(itemInList) =>
itemInList?.city?.toString() === item?.city?.toString()
)
) {
props.setItemsSelected([
...props.filters.filter(
(p) => p?.city?.toString() !== item?.city?.toString()
),
]);
} else {
props.setItemsSelected([...props.filters, item]);
}
}
};
const handleDelete = (item) => {
props.setItemsSelected([...props.filters.filter((p) => p !== item)]);
};
const handleClear = () => {
setToSearch("");
};

return (
<DropdownList
<CheckboxDropdownList
toSearch={toSearch}
setToSearch={setToSearch}
title={props.title}
textcolor={
props.filters.length > 0
? selectedTheme.primaryPurple
: selectedTheme.primaryText
}
dropdownIcon={
<IconWithNumber number={props.filters.length}>
{props.icon}
</IconWithNumber>
}
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
open={isOpened}
filters={props.filters}
icon={props.icon}
data={data}
searchPlaceholder={props.searchPlaceholder}
isOpened={isOpened}
setIsOpened={setIsOpened}
toggleIconStyles={{
backgroundColor: isOpened
? "white"
: selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
<React.Fragment>
<SelectedItemsContainer>
{props.filters.map((item) => (
<SelectedItem key={item.city} onClick={() => handleDelete(item)}>
{
data.find((p) => p?.city?.toString() === item?.city?.toString())
?.city
}
<Close style={{ position: "relative", top: "3px" }} />
</SelectedItem>
))}
</SelectedItemsContainer>

<TextField
placeholder={props.searchPlaceholder}
italicPlaceholder
value={toSearch}
onChange={(event) => setToSearch(event.target.value)}
textsize={"12px"}
font={"Open Sans"}
fullWidth
height={"40px"}
containerStyle={{ marginTop: "6px" }}
InputProps={{
endAdornment:
toSearch.length > 0 ? (
<ClearText onClick={handleClear}>
<CloseBlack />
</ClearText>
) : (
<React.Fragment />
),
}}
/>
</React.Fragment>
}
>
{dataToShow.map((item) => {
return (
<DropdownItem key={item.city}>
<CheckBox
leftText={item.city}
rightText={item.offerCount}
value={item}
checked={props.filters.find(
(itemInList) =>
itemInList?.city?.toString() === item?.city?.toString()
) ? true : false}
<Checkbox
item={item}
filters={props.filters}
onChange={() => handleChange(item)}
fullWidth
/>
</DropdownItem>
);
})}
</DropdownList>
</CheckboxDropdownList>
);
};

@@ -157,5 +93,4 @@ FilterCheckboxDropdown.propTypes = {
FilterCheckboxDropdown.defaultProps = {
oneValueAllowed: false,
};

export default FilterCheckboxDropdown;

+ 4
- 37
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.styled.js 파일 보기

@@ -1,38 +1,5 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../../themes";
// import { Box } from "@mui/material";
// import styled from "styled-components";
// import selectedTheme from "../../../../../themes";


export const SelectedItemsContainer = styled(Box)`
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-top: 5px;
`;
export const SelectedItem = styled(Box)`
margin-top: 2px;
background-color: ${selectedTheme.primaryPurple};
border-radius: 8px;
color: white;
padding-left: 8px;
padding-right: 6px;
line-height: 12px;
letter-spacing: 0.02em;
font-family: "Open Sans";
font-size: 12px;
cursor: pointer;
margin-right: 3px;
height: 22px;
`;
export const ClearText = styled(Box)`
padding-top: 1px;
border-radius: 100%;
cursor: pointer;
padding-right: 2px;
position: relative;
left: 6px;
width: 21px;
height: 21px;
&:hover {
background-color: ${selectedTheme.primaryIconBackgroundColor};
}
`;

+ 5
- 27
src/components/Cards/ItemDetailsCard/ItemDetailsCard.js 파일 보기

@@ -6,13 +6,6 @@ import {
OfferInfo,
Info,
PostDate,
OfferTitle,
OfferDescriptionText,
OfferDescriptionTitle,
Details,
OfferDetails,
OfferImage,
Scroller,
CategoryIcon,
SubcategoryIcon,
QuantityIcon,
@@ -28,6 +21,7 @@ import { formatDateLocale } from "../../../util/helpers/dateHelpers";
import { startChat } from "../../../util/helpers/chatHelper";
import Information from "./Information/Information";
import { useTranslation } from "react-i18next";
import OfferDetails from "./OfferDetails/OfferDetails";

const ItemDetailsCard = (props) => {
const offer = props.offer;
@@ -51,7 +45,7 @@ const ItemDetailsCard = (props) => {
const date = formatDateLocale(new Date(offer?.offer?._created));

const startExchange = () => {
startChat(chats, offer, userId);
startChat(chats, offer?.offer, userId);
};
return (
<ItemDetailsCardContainer
@@ -77,25 +71,9 @@ const ItemDetailsCard = (props) => {
</Info>
<PostDate>{date}</PostDate>
</OfferInfo>
<Details
hasScrollBar={!props.showPublishButton}
exchange={props.showExchangeButton}
>
<OfferTitle>{offer?.offer?.name}</OfferTitle>
<Scroller>
{offer?.offer?.images?.map((item) => {
return <OfferImage src={item} key={item} />;
})}
</Scroller>
<OfferDetails>
<OfferDescriptionTitle>
{t("itemDetailsCard.description")}
</OfferDescriptionTitle>
<OfferDescriptionText showBarterButton={props.showExchangeButton}>
{offer?.offer?.description}
</OfferDescriptionText>
</OfferDetails>
</Details>

<OfferDetails offer={offer}/>

{!props.halfwidth && props.showExchangeButton && (
<CheckButton
variant={props.sponsored ? "contained" : "outlined"}

+ 1
- 96
src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js 파일 보기

@@ -4,7 +4,6 @@ import selectedTheme from "../../../themes";
//import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import HorizontalScroller from "../../Scroller/HorizontalScroller";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";
import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg";
@@ -74,31 +73,6 @@ export const Info = styled(Box)`
left: 5px;
}
`;
export const OfferTitle = styled(Typography)`
font-family: "Open Sans";
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 24px;
padding: 0 60px;
@media screen and (max-width: 600px) {
padding: 0;
font-size: 18px;
}
`;
export const OfferImage = styled.img`
min-width: 144px;
min-height: 144px;
width: 144px;
height: 144px;
margin-right: 20px;
object-fit: cover;

@media screen and (max-width: 600px) {
min-width: 144px;
margin-right: 13px;
}
`;
export const OfferAuthor = styled(Box)`
display: flex;
flex: 1;
@@ -116,16 +90,7 @@ export const OfferLocation = styled(Typography)`
line-height: 16px;
font-size: 12px;
`;
export const OfferDetails = styled(Box)`
display: flex;
flex-direction: column;
flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")};
justify-content: space-between;
padding: 0 60px;
@media (max-width: 600px) {
padding: 0;
}
`;

export const OfferCategory = styled(Box)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
@@ -147,34 +112,6 @@ export const OfferViews = styled(Box)`
font-size: 12px;
width: 34%;
`;
export const OfferDescriptionTitle = styled(Box)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
line-height: 16px;
@media (max-width: 600px) {
font-size: 9px;
line-height: 13px;
}
`;
export const OfferDescriptionText = styled(Box)`
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryDarkText};
line-height: 22px;
padding-bottom: 20px;
max-width: ${(props) => props.showBarterButton ? "calc(100% - 230px)" : "100%"};
@media (max-width: 600px) {
font-size: 14px;
max-width: 100%;
max-height: 100px;
}
/* max-width: calc(100% - 230px); */
/* overflow: hidden; */
/* display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical; */
`;
export const OfferDescription = styled(Box)`
flex: 3;
`;
@@ -215,40 +152,8 @@ export const CheckButton = styled(PrimaryButton)`
height: 44px;
}
`;
export const Details = styled(Box)`
display: flex;
flex-direction: column;
gap: 12px;
${(props) => props.hasScrollBar && !props.exchange && `height: 300px;`}
overflow-y: auto;
overflow-x: hidden;
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-thumb {
background: #c4c4c4;
border-radius: 3px;
}

@media screen and (max-width: 600px) {
margin-top: 15px;
${(props) =>
!props.hasScrollBar && props.exchange &&
`
overflow: hidden;
max-height: none;`}
}
`;
// export const OfferImage = styled.img`
// `
export const Scroller = styled(HorizontalScroller)`
min-height: 144px;
min-width: 144px;
max-width: 100%;
/* & div {
margin: 0 9px;
} */
`;

export const PublishButtonContainer = styled(Box)`
display: flex;

+ 0
- 36
src/components/Cards/ItemDetailsCard/MockupOfferDetails.js 파일 보기

@@ -1,36 +0,0 @@
import React from "react"
import {ReactComponent as DummyImage1 } from "../../../assets/images/svg/dummyImages/offer-1.svg"

export const Offer = {
id: 0,
title: "Vino",
category: "Hrana i pice",
subcategory:"Farbe",
status:"novo",
quantity:150,
numberOfViews:45,
description: "Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.",
images: [
{
id:0,
image: <DummyImage1 />
},
{
id:1,
image: <DummyImage1 />
},
{
id:2,
image: <DummyImage1 />
},
{
id:3,
image: <DummyImage1 />
},
{
id:4,
image: <DummyImage1 />
},
],
postDate: "12.04.2022",
}

+ 41
- 9
src/components/Cards/ItemDetailsCard/OfferDetails/OfferDetails.js 파일 보기

@@ -1,14 +1,46 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
import {
Details,
OfferDescriptionText,
OfferDescriptionTitle,
OfferImage,
OfferLittleDetails,
OfferTitle,
Scroller,
} from "./OfferDetails.styled";
import { useTranslation } from "react-i18next";

const OfferDetails = () => {
const OfferDetails = (props) => {
const offer = props.offer;
const { t } = useTranslation();
return (
<div>OfferDetails</div>
)
}
<Details
hasScrollBar={!props.showPublishButton}
exchange={props.showExchangeButton}
>
<OfferTitle>{offer?.offer?.name}</OfferTitle>
<Scroller>
{offer?.offer?.images?.map((item) => {
return <OfferImage src={item} key={item} />;
})}
</Scroller>
<OfferLittleDetails>
<OfferDescriptionTitle>
{t("itemDetailsCard.description")}
</OfferDescriptionTitle>
<OfferDescriptionText showBarterButton={props.showExchangeButton}>
{offer?.offer?.description}
</OfferDescriptionText>
</OfferLittleDetails>
</Details>
);
};

OfferDetails.propTypes = {
offer: PropTypes.any,
}
offer: PropTypes.any,
showExchangeButton: PropTypes.bool,
showPublishButton: PropTypes.bool,
};

export default OfferDetails
export default OfferDetails;

+ 101
- 0
src/components/Cards/ItemDetailsCard/OfferDetails/OfferDetails.styled.js 파일 보기

@@ -0,0 +1,101 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import HorizontalScroller from "../../../Scroller/HorizontalScroller";

export const Details = styled(Box)`
display: flex;
flex-direction: column;
gap: 12px;
${(props) => props.hasScrollBar && !props.exchange && `height: 300px;`}
overflow-y: auto;
overflow-x: hidden;
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-thumb {
background: #c4c4c4;
border-radius: 3px;
}

@media screen and (max-width: 600px) {
margin-top: 15px;
${(props) =>
!props.hasScrollBar && props.exchange &&
`
overflow: hidden;
max-height: none;`}
}
`;

export const OfferTitle = styled(Typography)`
font-family: "Open Sans";
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 24px;
padding: 0 60px;
@media screen and (max-width: 600px) {
padding: 0;
font-size: 18px;
}
`;
export const OfferLittleDetails = styled(Box)`
display: flex;
flex-direction: column;
flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")};
justify-content: space-between;
padding: 0 60px;
@media (max-width: 600px) {
padding: 0;
}
`;
export const Scroller = styled(HorizontalScroller)`
min-height: 144px;
min-width: 144px;
max-width: 100%;
/* & div {
margin: 0 9px;
} */
`;
export const OfferDescriptionTitle = styled(Box)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
line-height: 16px;
@media (max-width: 600px) {
font-size: 9px;
line-height: 13px;
}
`;
export const OfferDescriptionText = styled(Box)`
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryDarkText};
line-height: 22px;
padding-bottom: 20px;
max-width: ${(props) => props.showBarterButton ? "calc(100% - 230px)" : "100%"};
@media (max-width: 600px) {
font-size: 14px;
max-width: 100%;
max-height: 100px;
}
/* max-width: calc(100% - 230px); */
/* overflow: hidden; */
/* display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical; */
`;
export const OfferImage = styled.img`
min-width: 144px;
min-height: 144px;
width: 144px;
height: 144px;
margin-right: 20px;
object-fit: cover;

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

+ 6
- 4
src/components/Cards/MessageCard/MessageCard.js 파일 보기

@@ -11,14 +11,16 @@ import { formatDateTime } from "../../../util/helpers/dateHelpers";

const MessageCard = (props) => {
const message = props.message;
const dateString = formatDateTime(new Date(message._created))
const dateString = formatDateTime(new Date(message._created));
return (
<MessageCardContainer isMyMessage={props.isMyMessage}>
<ProfileImage src={props.image} />
<MessageContent isMyMessage={props.isMyMessage}>
<MessageText isMyMessage={props.isMyMessage} >{props.message.text}</MessageText>
<MessageDate isMyMessage={props.isMyMessage} >{dateString}</MessageDate>
<MessageText isMyMessage={props.isMyMessage}>
{props.message.text}
</MessageText>
<MessageDate isMyMessage={props.isMyMessage}>{dateString}</MessageDate>
</MessageContent>
</MessageCardContainer>
);

+ 0
- 24
src/components/Cards/UserReviewsCard/Mockupdata.js 파일 보기

@@ -1,24 +0,0 @@
export default [{
id: 0,
name: "Coca-Cola",
quote: "Odlična saradnja. Sve preporuke za kompaniju",
isGood: true,
isGoodCommunication: "DA",
isSuccessfulSwap: "DA"
}
,{
id: 1,
name: "Voda Vrnjci",
quote: "Sasvim korektna saradnja, rado bih ponovio poslovanje sa Vama.",
isGood: true,
isGoodCommunication: "DA",
isSuccessfulSwap: "DA"
}
,{
id: 2,
name: "Women's Beauty House",
quote: "Nismo se najbolje razumeli, ali generalno ok",
isGood: false,
isGoodCommunication: "NE",
isSuccessfulSwap: "NE"
}];

+ 12
- 36
src/components/Cards/UserReviewsCard/UserReviewsCard.js 파일 보기

@@ -15,56 +15,35 @@ import {
ThumbDown,
ThumbUp,
} from "./UserReviewsCard.styled";

import { ListItem } from "@mui/material";
import selectedTheme from "../../../themes";
import { useTranslation } from "react-i18next";
import { reviewEnum } from "../../../enums/reviewEnum";
// import { useDispatch } from "react-redux";
// import { fetchProfile } from "../../../store/actions/profile/profileActions";

const UserReviewsCard = (props) => {
const { t } = useTranslation();

// const dispatch = useDispatch();

// useEffect(() => {
// if (props.review?.userId) {
// dispatch(fetchProfile(props.review.userId));
// }
// }, [props.review?.userId])
console.log(props);

const review = useMemo(() => {
if (props.givingReview) {
return {
...props.review
}
...props.review,
};
}
let isSuccessfulSwap = "DA";
if (props.review.succeeded === "failed") isSuccessfulSwap = "NE";
let isGoodCommunication = "DA";
if (props.review.communication === "could be better") isGoodCommunication = "MOŽE BOLJE";
if (props.review.communication === "could be better")
isGoodCommunication = "MOŽE BOLJE";
if (props.review.communication === "no") isGoodCommunication = "NE";
return {
name: props.review.companyName,
image: props.review.image,
isGoodCommunication,
isSuccessfulSwap,
quote: props?.review?.message
}
quote: props?.review?.message,
};
}, [props.review]);

const isGood = useMemo(() => {
if (
review?.isGoodCommunication === reviewEnum.NO.mainText ||
review?.isSuccessfulSwap === reviewEnum.NO.mainText
) {
return false;
}
return true;
}, [review]);

return (
<ReviewContainer key={review?.image}>
<ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}>
@@ -84,17 +63,14 @@ const UserReviewsCard = (props) => {
sx={{ pl: 2, py: 2 }}
>
<ThumbBox item>
{isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />}
{review.isSuccessfulSwap ? (
<ThumbUp color="success" />
) : (
<ThumbDown color="error" />
)}
</ThumbBox>
<ReviewQuoteBox item>
<ReviewQuoteText
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
&quot;{review?.quote}&quot;
</ReviewQuoteText>
<ReviewQuoteText>&quot;{review?.quote}&quot;</ReviewQuoteText>
</ReviewQuoteBox>
</ReviewQuote>
<ReviewDetails sx={{ pl: 2, pb: 2 }}>

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

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import ChatCard from "../Cards/ChatCard/ChatCard";
import {
ChatColumnContainer,
@@ -36,7 +36,7 @@ export const ChatColumn = () => {

useEffect(() => {
dispatch(fetchChats());
}, [])
}, []);

useEffect(() => {
setSortOption(sorting.selectedSortOption);
@@ -82,7 +82,7 @@ export const ChatColumn = () => {
})}
</HeaderSelect>
</TitleSortContainer>
<ListHeader enableSort={true}></ListHeader>
<ListHeader enableSort={true} />
<ListContainer>
{chats.map((item, index) => (
<ChatCard key={index} chat={item} />

+ 4
- 14
src/components/CreateReview/CreateReview.js 파일 보기

@@ -9,15 +9,14 @@ import {
} from "./CreateReview.styled";
import FirstStepCreateReview from "./FirstStep/FirstStepCreateReview";
import SecondStepCreateReview from "./SecondStep/SecondStepCreateReview";
import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview";
import { useDispatch, useSelector } from "react-redux";
import { giveReview } from "../../store/actions/review/reviewActions";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { reviewEnum } from "../../enums/reviewEnum";
import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview";

const CreateReview = (props) => {
const offer = props.offer;
console.log("props aaa: ", props);
const [informations, setInformations] = useState({});
const [currentStep, setCurrentStep] = useState(1);
const dispatch = useDispatch();
@@ -29,15 +28,12 @@ const CreateReview = (props) => {
props.handleGiveReviewSuccess();
};
const submitForm = () => {
let communication;
if (informations.correctCommunication === reviewEnum.YES.mainText)
communication = "yes";
let communication = "yes";
if (informations.correctCommunication === reviewEnum.NO.mainText)
communication = "no";
if (informations.correctCommunication === reviewEnum.NOT_BAD.mainText)
communication = "could be better";
let succeeded;
succeeded = "failed";
let succeeded = "failed";
if (informations.exchangeSucceed === reviewEnum.YES.mainText)
succeeded = "succeeded";
dispatch(
@@ -55,10 +51,6 @@ const CreateReview = (props) => {
};
const goToNextStep = (newInformations) => {
setInformations((prevInformations) => {
console.log({
...prevInformations,
...newInformations,
});
return {
...prevInformations,
...newInformations,
@@ -87,12 +79,10 @@ const CreateReview = (props) => {
<CloseButton onClick={closeModal}>
<CloseIcon />
</CloseButton>
{currentStep === 2 ? (
{currentStep === 2 && (
<BackIcon onClick={goToPrevStep}>
<ArrowBackIcon />
</BackIcon>
) : (
""
)}
{currentStep === 1 && (
<FirstStepCreateReview

+ 13
- 18
src/components/DirectChat/DirectChat.js 파일 보기

@@ -4,11 +4,9 @@ import { DirectChatContainer } from "./DirectChat.styled";
import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle";
import DirectChatHeader from "./DirectChatHeader/DirectChatHeader";
import { useDispatch, useSelector } from "react-redux";
// import { fetchOneOffer } from "../../store/actions/offers/offersActions";
import { useLocation, useRouteMatch } from "react-router-dom";
import { fetchOneChat } from "../../store/actions/chat/chatActions";
import { fetchOneChat, setOneChat } from "../../store/actions/chat/chatActions";
import {
// selectLatestChats,
selectSelectedChat,
} from "../../store/selectors/chatSelectors";
import DirectChatContent from "./DirectChatContent/DirectChatContent";
@@ -20,46 +18,43 @@ const DirectChat = () => {
const offer = useSelector(selectOffer);
const routeMatch = useRouteMatch();
const location = useLocation();
// const allChats = useSelector(selectLatestChats);
// const foundChat = useMemo(
// () => allChats.find((item) => item?.chat?._id === chat?.chat?._id),
// [chat, allChats]
// );
const dispatch = useDispatch();

const offerObject = useMemo(() => {
if (location?.state?.offerId) {
return offer?.offer;
}
return chat?.offer?.offer;
}, [chat, location.state, offer]);

const chatObject = useMemo(() => {
if (location?.state?.offerId) {
return {};
}
return chat?.chat;
}, [chat, location.state]);

const interlocutorObject = useMemo(() => {
if (location?.state?.offerId) {
return {
image: offer?.companyData?.image,
name: offer?.companyData?.company?.name,
location: offer?.companyData?.company?.contacts?.location
}
location: offer?.companyData?.company?.contacts?.location,
};
}
return chat?.interlocutor;
}, [chat,location.state, offer]);
console.log("offerObject: ", offerObject);
console.log("chatObject: ", chatObject);
console.log("interlucatorObject: ", interlocutorObject);
const dispatch = useDispatch();
}, [chat, location.state, offer]);

useEffect(() => {
console.log(location.state)
if (routeMatch.params.idChat && location.state?.offerId) {
if (routeMatch.params.idChat) {
refreshChat();
}
}, [routeMatch.params.idChat, location.state?.offerId]);

const refreshChat = () => {
if (routeMatch.params.idChat === "newMessage") {
dispatch(fetchOneOffer(location.state.offerId))
dispatch(fetchOneOffer(location.state.offerId));
dispatch(setOneChat({}));
} else {
dispatch(fetchOneChat(routeMatch.params.idChat));
}

+ 3
- 3
src/components/DirectChat/DirectChatHeader/DirectChatHeader.js 파일 보기

@@ -32,15 +32,15 @@ const DirectChatHeader = (props) => {
}
return false;
}, [exchange, userId])
const refetchExchange = () => {
dispatch(fetchExchange(chat.chat.exchangeId));
}
const makeReview = () => {
setShowReviewModal(true);
};
const handleGiveReviewSuccess = () => {
refetchExchange();
}
const refetchExchange = () => {
dispatch(fetchExchange(chat.chat.exchangeId));
}
return (
<DirectChatHeaderContainer>
{showReviewModal && (

+ 16
- 12
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js 파일 보기

@@ -1,20 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import { DirectChatHeaderTitleContainer, HeaderTitleContent, MessageIcon } from './DirectChatHeaderTitle.styled'
import { useTranslation } from 'react-i18next'
import React from "react";
import PropTypes from "prop-types";
import {
DirectChatHeaderTitleContainer,
HeaderTitleContent,
MessageIcon,
} from "./DirectChatHeaderTitle.styled";
import { useTranslation } from "react-i18next";

const DirectChatHeaderTitle = () => {
const {t} = useTranslation();
const { t } = useTranslation();
return (
<DirectChatHeaderTitleContainer>
<MessageIcon />
<HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent>
<MessageIcon />
<HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent>
</DirectChatHeaderTitleContainer>
)
}
);
};

DirectChatHeaderTitle.propTypes = {
children: PropTypes.node,
}
children: PropTypes.node,
};

export default DirectChatHeaderTitle
export default DirectChatHeaderTitle;

+ 9
- 5
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js 파일 보기

@@ -8,7 +8,10 @@ import {
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import { useDispatch } from "react-redux";
import { sendMessage, startNewChat } from "../../../store/actions/chat/chatActions";
import {
sendMessage,
startNewChat,
} from "../../../store/actions/chat/chatActions";
import { useHistory, useLocation } from "react-router-dom";

const DirectChatNewMessage = (props) => {
@@ -35,13 +38,14 @@ const DirectChatNewMessage = (props) => {
setTypedValue("");
};
const handleMessageSendSuccess = (newChatId) => {
console.log("NEW CHAT ID: ", newChatId);
history.replace(`${newChatId}`);
}
};
const initiateNewChat = (typedValue) => {
const offerId = location.state.offerId;
dispatch(startNewChat({offerId, message: typedValue, handleMessageSendSuccess}))
}
dispatch(
startNewChat({ offerId, message: typedValue, handleMessageSendSuccess })
);
};
return (
<DirectChatNewMessageContainer>
<NewMessageField

+ 18
- 36
src/components/DirectChat/MiniChatColumn/MiniChatColumn.js 파일 보기

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import { MiniChatColumnContainer } from "./MiniChatColumn.styled";
import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard";
@@ -14,9 +14,7 @@ import { selectOffer } from "../../../store/selectors/offersSelectors";

const MiniChatColumn = () => {
const chats = useSelector(selectLatestChats);
const [chatsToShow, setChatsToShow] = useState([]);
const selectedChat = useSelector(selectSelectedChat);
const [isThereNewChat, setIsThereNewChat] = useState(false);
const offer = useSelector(selectOffer);
const location = useLocation();
const dispatch = useDispatch();
@@ -25,29 +23,15 @@ const MiniChatColumn = () => {
return {
interlocutorData: {
image: offer?.companyData?.image,
name: offer?.companyData?.company?.name
name: offer?.companyData?.company?.name,
},
offerData: {
name: offer?.offer?.name
}
}
}
return {}
}, [offer, location.state])
useEffect(() => {
if (location.state?.offerId) {
setIsThereNewChat(true);
} else {
if (isThereNewChat !== false) {
dispatch(fetchChats());
setIsThereNewChat(false);
}
name: offer?.offer?.name,
},
};
}
}, [location.state])

useEffect(() => {
setChatsToShow([...chats]);
}, [chats])
return {};
}, [offer, location.state]);

useEffect(() => {
dispatch(fetchChats());
@@ -55,20 +39,18 @@ const MiniChatColumn = () => {
return (
<MiniChatColumnContainer>
<MiniChatColumnHeader />
{isThereNewChat && (
<MiniChatCard
chat={newChat}
selected
/>
)}
{chatsToShow.map((item) => {
{location.state?.offerId && <MiniChatCard chat={newChat} selected />}
{chats.map((item) => {
return (
<MiniChatCard
key={Date.now() * Math.random()}
chat={item}
selected={item?.chat?._id === selectedChat?.chat?._id && !isThereNewChat}
/>
)})}
<MiniChatCard
key={Date.now() * Math.random()}
chat={item}
selected={
item?.chat?._id === selectedChat?.chat?._id
}
/>
);
})}
</MiniChatColumnContainer>
);
};

+ 18
- 12
src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js 파일 보기

@@ -1,20 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { HeaderTitleContent, MailIcon, MiniChatColumnHeaderContainer } from './MiniChatColumnHeaderTitle.styled'
import { useTranslation } from 'react-i18next'
import React from "react";
import PropTypes from "prop-types";
import {
HeaderTitleContent,
MailIcon,
MiniChatColumnHeaderContainer,
} from "./MiniChatColumnHeaderTitle.styled";
import { useTranslation } from "react-i18next";

const MiniChatColumnHeader = () => {
const {t} = useTranslation();
const { t } = useTranslation();
return (
<MiniChatColumnHeaderContainer>
<MailIcon/>
<HeaderTitleContent>{t("messages.miniChatHeaderTitle")}</HeaderTitleContent>
<MailIcon />
<HeaderTitleContent>
{t("messages.miniChatHeaderTitle")}
</HeaderTitleContent>
</MiniChatColumnHeaderContainer>
)
}
);
};

MiniChatColumnHeader.propTypes = {
children: PropTypes.node,
}
children: PropTypes.node,
};

export default MiniChatColumnHeader
export default MiniChatColumnHeader;

+ 17
- 14
src/components/Dropdown/DropdownList/DropdownList.js 파일 보기

@@ -15,20 +15,16 @@ import PropTypes from "prop-types";
const DropdownList = (props) => {
const [listShown, setListShown] = useState(props.defaultOpen);
useEffect(() => {
if (props.open !== null || props.open !== undefined) {
if (props.open !== null || props.open !== undefined)
setListShown(props.open);
}
}, [props.open]);
const handleShow = () => {
if (props.setIsOpened) {
if (props.setIsOpened)
props.setIsOpened(!listShown);
}
if (!props.disabled) {
if (!props.disabled)
setListShown((prevState) => !prevState);
if (!(props.open !== null || props.open !== undefined))
setListShown((prevState) => !prevState);
}
if (!(props.open !== null || props.open !== undefined)) {
setListShown(prevState => !prevState)
}
};
return (
<DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}>
@@ -48,7 +44,11 @@ const DropdownList = (props) => {
>
{props.title}
</DropdownTitle>
{(props.open !== null && props.open !== undefined ? props.open : listShown) ? (
{(
props.open !== null && props.open !== undefined
? props.open
: listShown
) ? (
<ToggleIconOpened
style={props.toggleIconStyles}
onClick={!props.disabled ? () => handleShow() : () => {}}
@@ -65,16 +65,20 @@ const DropdownList = (props) => {
</ToggleIconClosed>
)}
</DropdownHeader>
<ToggleContainer shouldShow={props.open !== null && props.open !== undefined ? props.open : listShown}>
<ToggleContainer
shouldShow={
props.open !== null && props.open !== undefined
? props.open
: listShown
}
>
<DropdownOptions>{props.headerOptions}</DropdownOptions>
<ListContainer>{props.children}</ListContainer>
</ToggleContainer>
</DropdownListContainer>
);
};

export default DropdownList;

DropdownList.propTypes = {
title: PropTypes.string,
dropdownIcon: PropTypes.node,
@@ -90,7 +94,6 @@ DropdownList.propTypes = {
open: PropTypes.bool,
disabled: PropTypes.bool,
};

DropdownList.defaultProps = {
fullWidth: false,
defaultOpen: false,

+ 8
- 3
src/components/Header/Drawer/Drawer.js 파일 보기

@@ -21,7 +21,7 @@ import {
ToolsContainer,
UserIcon,
} from "./Drawer.styled";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
@@ -29,12 +29,14 @@ import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { CHAT_PAGE, LOGIN_PAGE, MY_OFFERS_PAGE, REGISTER_PAGE } from "../../../constants/pages";
import { selectProfileName } from "../../../store/selectors/profileSelectors";
import { logoutUser } from "../../../store/actions/login/loginActions";

export const Drawer = (props) => {
const user = useSelector(selectUserId);
const { t } = useTranslation();
const history = useHistory();
const name = useSelector(selectProfileName);
const dispatch = useDispatch();

const goToMyPosts = () => {
props.toggleDrawer();
@@ -60,7 +62,10 @@ export const Drawer = (props) => {
props.toggleDrawer();
props.addOffer();
}
const logoutUser = () => {};
const logout = () => {
props.toggleDrawer();
dispatch(logoutUser());
};
return (
<DrawerContainer>
<CloseButton onClick={props.toggleDrawer}>
@@ -101,7 +106,7 @@ export const Drawer = (props) => {
{t("header.addOffer")}
</AddOfferButton>
<LogoutButton>
<IconButton onClick={logoutUser}>
<IconButton onClick={logout}>
<LogoutIcon />
</IconButton>
<LogoutText>{t("common.logout")}</LogoutText>

+ 12
- 21
src/components/ImagePicker/ImagePicker.js 파일 보기

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import {
AddFile,
@@ -12,8 +12,6 @@ import { IconButton } from "../Buttons/IconButton/IconButton";
import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg";
import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg";

// import { Input } from "@mui/material";

const ImagePicker = (props) => {
const fileInputRef = useRef(null);
const imageRef = useRef(null);
@@ -21,27 +19,23 @@ const ImagePicker = (props) => {
const [isEditing, setIsEditing] = useState(false);

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

let listener;
useEffect(() => {
listener = (event) => {
if (imageRef.current) {
if (imageRef.current.contains(event.target)) {
setIsEditing(true);
} else {
setIsEditing(false);
}
let listener = useCallback((event) => {
if (imageRef.current) {
if (imageRef.current.contains(event.target)) {
setIsEditing(true);
} else {
setIsEditing(false);
}
};
}
}, [imageRef.current])
useEffect(() => {
window.addEventListener("click", listener);
return () => window.removeEventListener("click", listener);
}, [imageRef]);

}, []);
const handleChange = () => {
fileInputRef.current.value = "";
fileInputRef.current.click();
@@ -57,7 +51,6 @@ const ImagePicker = (props) => {
console.log(error);
};
};

const handleDelete = () => {
if (props.deleteImage) props.deleteImage();
setImage("");
@@ -96,7 +89,6 @@ const ImagePicker = (props) => {
</ImagePickerContainer>
);
};

ImagePicker.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
@@ -105,5 +97,4 @@ ImagePicker.propTypes = {
deleteImage: PropTypes.func,
showDeleteIcon: PropTypes.bool,
};

export default ImagePicker;

+ 0
- 187
src/components/InputFields/BaseInputField.js 파일 보기

@@ -1,187 +0,0 @@
import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { ErrorMessage } from 'formik';
import IconButton from '../IconButton/IconButton';
import { ReactComponent as Search } from '../../assets/images/svg/search.svg';
import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg';
import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg';
import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg';

const BaseInputField = ({
type,
label,
field,
form,
placeholder,
clearPlaceholderOnFocus = true,
isSearch,
className,
disabled,
centerText,
link,
errorMessage,
autoFocus,
isCapsLockOn,
...props
}) => {
const [inputPlaceholder, setPlaceholder] = useState(placeholder);

const inputField = useRef(null);

useEffect(() => {
if (autoFocus) {
inputField.current.focus();
}
}, [autoFocus, inputField]);

useEffect(() => {
if (errorMessage) {
form.setFieldError(field.name, errorMessage);
}
}, [errorMessage]); // eslint-disable-line

useEffect(() => {
setPlaceholder(placeholder);
}, [placeholder]);

const [inputType, setInputType] = useState('password');
const passwordInput = type === 'password' ? ' c-input--password' : '';

const showPassword = () => {
if (inputType === 'password') {
setInputType('text');
} else {
setInputType('password');
}
};

// Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this
// If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels?
const fieldName = field.name.split('.');

const formError =
fieldName[0] && fieldName[1]
? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]]
: form.errors[fieldName[0]];

const formTouched =
fieldName[0] && fieldName[1]
? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]]
: form.touched[fieldName[0]];

function styles() {
let style = 'c-input';

if (formError && formTouched) {
style += ` c-input--error`;
}

if (type === 'password') {
style += ` c-input--password`;
}

if (isSearch) {
style += ` c-input--search`;
}

if (centerText) {
style += ` c-input--center-text`;
}

if (type === 'number') {
style += ` c-input--demi-bold`;
}

if (className) {
style += ` ${className}`;
}

return style;
}

const additionalActions = () => {
if (!clearPlaceholderOnFocus) {
return null;
}

return {
onFocus: () => {
setPlaceholder('');
},
onBlur: (e) => {
setPlaceholder(placeholder);
field.onBlur(e);
},
};
};
return (
<div className={styles()}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
{link && <div className="c-input__link">{link}</div>}
<div className="c-input__field-wrap">
<input
ref={inputField}
type={type === 'password' ? inputType : type}
placeholder={inputPlaceholder}
disabled={disabled}
{...field}
{...props}
{...additionalActions()}
className="c-input__field"
/>
{!!isSearch && <Search className="c-input__icon" />}
{!!passwordInput && (
<>
{isCapsLockOn && <CapsLock className="c-input__caps-lock" />}
<IconButton
onClick={() => {
showPassword();
}}
className="c-input__icon"
>
{inputType === 'password' ? <EyeOff /> : <EyeOn />}
</IconButton>
</>
)}
</div>

<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-input__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};
BaseInputField.propTypes = {
type: PropTypes.string,
field: PropTypes.shape({
name: PropTypes.string,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
}),
form: PropTypes.shape({
errors: PropTypes.shape({}),
setFieldError: PropTypes.func,
touched: PropTypes.shape({}),
}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
disabled: PropTypes.bool,
isSearch: PropTypes.bool,
className: PropTypes.string,
link: PropTypes.node,
errorMessage: PropTypes.string,
centerText: PropTypes.bool,
clearPlaceholderOnFocus: PropTypes.bool,
demiBold: PropTypes.bool,
touched: PropTypes.bool,
autoFocus: PropTypes.bool,
isCapsLockOn: PropTypes.bool,
};

export default BaseInputField;

+ 0
- 40
src/components/InputFields/Checkbox.js 파일 보기

@@ -1,40 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ReactComponent as Checked } from '../../assets/images/svg/checked.svg';
import { ReactComponent as Unchecked } from '../../assets/images/svg/unchecked.svg';

const Checkbox = ({ className, children, name, onChange, checked, field }) => (
<label htmlFor={name} className={`c-checkbox ${className || ''}`}>
<input
name={name}
id={name}
className="c-checkbox__field"
type="checkbox"
checked={checked}
{...field}
onChange={onChange || field.onChange}
/>

<div className="c-checkbox__indicator">
{checked ? (
<Checked className="c-checkbox__icon" />
) : (
<Unchecked className="c-checkbox__icon" />
)}
</div>
<div className="c-checkbox__text">{children}</div>
</label>
);

Checkbox.propTypes = {
children: PropTypes.node,
onChange: PropTypes.func,
checked: PropTypes.bool,
name: PropTypes.string,
field: PropTypes.shape({
onChange: PropTypes.func,
}),
className: PropTypes.string,
};

export default Checkbox;

+ 0
- 123
src/components/InputFields/CurrencyField.js 파일 보기

@@ -1,123 +0,0 @@
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { ErrorMessage, useField } from 'formik';
import CurrencyInput from 'react-currency-input-field';
import { formatMoneyNumeral } from '../../util/helpers/numeralHelpers';
import {
PLUS_SYMBOL,
MINUS_SYMBOL,
NUMPAD_MINUS_SYMBOL,
NUMPAD_PLUS_SYMBOL,
K_KEYCODE,
} from '../../constants/keyCodeConstants';

const CurrencyField = ({
autoFocus,
notCentered,
notBold,
label,
onChange,
value,
...props
}) => {
const [field, meta] = useField(props);
const inputField = useRef(null);
function styles() {
let style = 'c-currency-field';

if (meta.error && meta.touched) {
style += ` c-currency-field--error`;
}

if (notCentered) {
style += ` c-currency-field--not-centered`;
}

if (notBold) {
style += ` c-currency-field--not-bold`;
}

return style;
}

useEffect(() => {
if (autoFocus) {
inputField.current.focus();
}
}, [autoFocus, inputField]);

const onKeydownHandler = (event) => {
if (
event.keyCode === MINUS_SYMBOL ||
event.keyCode === PLUS_SYMBOL ||
event.keyCode === NUMPAD_MINUS_SYMBOL ||
event.keyCode === NUMPAD_PLUS_SYMBOL ||
event.keyCode === K_KEYCODE
) {
event.preventDefault();
}
};

const prefix = formatMoneyNumeral(0);
const prefixSymbol = () => {
if (prefix.includes('CAD')) {
return 'CAD ';
}

return '$';
};

return (
<div className={styles()}>
{!!label && (
<label className="c-currency-field__label" htmlFor={field.name}>
{label}
</label>
)}
{value ? (
<CurrencyInput
{...props}
prefix={prefixSymbol()}
onValueChange={(value) => {
onChange(value ? Number(value) : '');
}}
onKeyDown={(event) => onKeydownHandler(event)}
ref={inputField}
defaultValue={0}
value={value}
/>
) : (
<CurrencyInput
{...props}
prefix={prefixSymbol()}
onValueChange={(value) => {
onChange(value ? Number(value) : '');
}}
onKeyDown={(event) => onKeydownHandler(event)}
ref={inputField}
/>
)}
<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-currency-field__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};

CurrencyField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
onChange: PropTypes.func,
autoFocus: PropTypes.bool,
notCentered: PropTypes.bool,
notBold: PropTypes.bool,
value: PropTypes.number,
};

export default CurrencyField;

+ 0
- 33
src/components/InputFields/EmailField.js 파일 보기

@@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';

const EmailField = ({
field,
form,
label,
placeholder,
disabled,
...props
}) => (
<BaseInputField
type="email"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
/>
);

EmailField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
};

export default EmailField;

+ 0
- 74
src/components/InputFields/NumberField.js 파일 보기

@@ -1,74 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import {
PERIOD_SYMBOL,
COMMA_SYMBOL,
PLUS_SYMBOL,
MINUS_SYMBOL,
NUMPAD_PERIOD_SYMBOL,
NUMPAD_MINUS_SYMBOL,
NUMPAD_PLUS_SYMBOL,
DOWN_ARROW_KEYCODE,
UP_ARROW_KEYCODE,
} from '../../constants/keyCodeConstants';

const NumberField = ({
field,
form,
label,
placeholder,
disabled,
preventAllExceptNumbers,
...props
}) => {
const onKeydownHandler = (event) => {
if (preventAllExceptNumbers) {
if (
event.keyCode === PERIOD_SYMBOL ||
event.keyCode === COMMA_SYMBOL ||
event.keyCode === NUMPAD_PERIOD_SYMBOL ||
event.keyCode === DOWN_ARROW_KEYCODE ||
event.keyCode === UP_ARROW_KEYCODE
) {
event.preventDefault();
}
}

if (
event.keyCode === PLUS_SYMBOL ||
event.keyCode === MINUS_SYMBOL ||
event.keyCode === NUMPAD_MINUS_SYMBOL ||
event.keyCode === NUMPAD_PLUS_SYMBOL ||
event.keyCode === DOWN_ARROW_KEYCODE ||
event.keyCode === UP_ARROW_KEYCODE
) {
event.preventDefault();
}
};

return (
<BaseInputField
type="number"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
onKeyDown={(event) => onKeydownHandler(event)}
/>
);
};

NumberField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
disabled: PropTypes.bool,
preventAllExceptNumbers: PropTypes.bool,
};

export default NumberField;

+ 0
- 74
src/components/InputFields/PasswordField.js 파일 보기

@@ -1,74 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import PasswordStrength from './PasswordStrength';

const PasswordField = ({
field,
form,
label,
placeholder,
disabled,
shouldTestPasswordStrength,
autoFocus,
...props
}) => {
const [passwordValue, setPasswordValue] = useState('');
const [isCapsLockOn, setIsCapsLockOn] = useState(false);

const onChange = (e) => {
if (shouldTestPasswordStrength) {
const { value } = e.target;
setPasswordValue(value);
}

field.onChange(e);
};

const onKeyDown = (keyEvent) => {
if (keyEvent.getModifierState('CapsLock')) {
setIsCapsLockOn(true);
} else {
setIsCapsLockOn(false);
}
};

return (
<div className="c-password">
<BaseInputField
type="password"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
{...props}
onChange={onChange}
autoFocus={autoFocus}
onKeyDown={onKeyDown}
isCapsLockOn={isCapsLockOn}
/>
{shouldTestPasswordStrength && (
<PasswordStrength
passwordValue={passwordValue}
shouldTestPasswordStrength
/>
)}
</div>
);
};

PasswordField.propTypes = {
field: PropTypes.shape({
onChange: PropTypes.func,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
shouldTestPasswordStrength: PropTypes.bool,
autoFocus: PropTypes.bool,
};

export default PasswordField;

+ 0
- 130
src/components/InputFields/PasswordStrength.js 파일 보기

@@ -1,130 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import owasp from 'owasp-password-strength-test';
import i18next from 'i18next';

owasp.config({
minOptionalTestsToPass: 3,
});

const passwordStrengthOptions = [
{
strength: 'weak',
color: '#FF5028',
},
{
strength: 'average',
color: '#FDB942',
},
{
strength: 'good',
color: '#06BEE7',
},
{
strength: 'strong',
color: '#00876A',
},
];

/**
* User must pass a required test and at least 3 optional.
* @param result - owasp result
* @returns {number} - index of password strength 0-3
*/
function getPasswordStrengthIndex(result) {
// requirement for strong password is required test passed and at least 3 optional tests
if (result.strong) {
return 3;
}

if (!result.strong && result.optionalTestsPassed >= 3) {
return 2;
}

if (result.optionalTestsPassed <= 0) {
return 0;
}

return result.optionalTestsPassed - 1;
}

const PasswordStrength = ({
shouldTestPasswordStrength,
passwordValue,
passwordStrengthTestsRequired,
}) => {
const strengthContainer = useRef(null);
const [passwordStrength, setPasswordStrength] = useState({
width: 0,
color: 'red',
});
const [error, setError] = useState('');

useEffect(() => {
if (shouldTestPasswordStrength && passwordValue) {
const bBox = strengthContainer.current.getBoundingClientRect();
const result = owasp.test(passwordValue);

const passwordStrengthIndex = getPasswordStrengthIndex(result);
const passwordOption = passwordStrengthOptions[passwordStrengthIndex];

const width = !passwordValue
? 0
: (bBox.width * (passwordStrengthIndex + 1)) /
passwordStrengthTestsRequired;

setPasswordStrength({ width, color: passwordOption.color });
const strength = i18next.t(`password.${passwordOption.strength}`);
setError(i18next.t('login.passwordStrength', { strength }));
}
}, [
passwordValue,
shouldTestPasswordStrength,
passwordStrengthTestsRequired,
]);

if (!shouldTestPasswordStrength || !passwordValue) {
return null;
}

const renderError = () => {
if (!error) {
return null;
}
return (
<div
className="c-input--error"
style={{
color: passwordStrength.color,
}}
>
{error}
</div>
);
};
return (
<div ref={strengthContainer} className="c-password-strength__container">
<div className="c-password-strength__line--wrapper">
<div
className="c-password-strength__line"
style={{
backgroundColor: passwordStrength.color,
width: passwordStrength.width,
}}
/>
</div>
{renderError()}
</div>
);
};
PasswordStrength.propTypes = {
shouldTestPasswordStrength: PropTypes.bool,
passwordValue: PropTypes.string,
passwordStrengthTestsRequired: PropTypes.number,
};

PasswordStrength.defaultProps = {
passwordStrengthTestsRequired: 4,
};

export default PasswordStrength;

+ 0
- 45
src/components/InputFields/PercentageField.js 파일 보기

@@ -1,45 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import NumberFormat from 'react-number-format';

import TextField from './TextField';

const PercentageField = ({ field, ...props }) => {
const handleOnChange = (percentageField) => {
const { floatValue } = percentageField;

if (!props.onChange) {
throw Error('Provide an onChange handler');
}
if (floatValue > 100) {
return props.onChange('100');
}

if (floatValue <= 0 || !floatValue) {
return props.onChange('0');
}

return props.onChange(floatValue.toString());
};

return (
<NumberFormat
format="###%"
value={field.value}
customInput={TextField}
field={field}
{...props}
onValueChange={handleOnChange}
onChange={() => {}}
/>
);
};

PercentageField.propTypes = {
onChange: PropTypes.func,
field: PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
};

export default PercentageField;

+ 0
- 49
src/components/InputFields/PhoneNumberField.js 파일 보기

@@ -1,49 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ErrorMessage, useField } from 'formik';
import PhoneInput from 'react-phone-number-input';
import 'react-phone-number-input/style.css';

const PhoneNumberField = ({ label, ...props }) => {
const [field, meta] = useField(props);
const inputErrorClassName =
meta.error && meta.touched ? 'c-input--error' : '';

return (
<div className={`c-input c-phone-number ${inputErrorClassName}`}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
<PhoneInput
international
defaultCountry="US"
{...field}
{...props}
onChange={(value) => {
props.onPhoneChange(value);
}}
countryOptionsOrder={['US']}
/>
<ErrorMessage name={field.name}>
{(errorMessage) => (
<span className="c-input__error">{errorMessage}</span>
)}
</ErrorMessage>
</div>
);
};

PhoneNumberField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
onChange: PropTypes.func,
onPhoneChange: PropTypes.func,
};

export default PhoneNumberField;

+ 0
- 54
src/components/InputFields/Radio.js 파일 보기

@@ -1,54 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ReactComponent as RadioOn } from '../../assets/images/svg/radio-on.svg';
import { ReactComponent as RadioOff } from '../../assets/images/svg/radio-off.svg';

const Checkbox = ({
className,
children,
name,
checked,
field,
value,
selected,
id,
}) => (
<label
htmlFor={name}
className={`c-radio ${selected ? 'c-radio--selected' : ''} ${
className || ''
}`}
>
<input
name={name}
id={id}
className="c-radio__field"
type="radio"
checked={checked}
value={value}
{...field}
/>
<div className="c-radio__indicator">
{selected ? (
<RadioOn className="c-radio__icon" />
) : (
<RadioOff className="c-radio__icon" />
)}
</div>
<div className="c-radio__text">{children}</div>
</label>
);

Checkbox.propTypes = {
children: PropTypes.node,
checked: PropTypes.bool,
name: PropTypes.string,
field: PropTypes.shape({}),
form: PropTypes.shape({}),
className: PropTypes.string,
value: PropTypes.string,
selected: PropTypes.bool,
id: PropTypes.string,
};

export default Checkbox;

+ 0
- 37
src/components/InputFields/Search.js 파일 보기

@@ -1,37 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';

const Search = ({
field,
form,
label,
placeholder,
disabled,
className,
...props
}) => (
<BaseInputField
type="text"
label=""
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
isSearch
className={className}
{...props}
/>
);

Search.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
placeholder: PropTypes.string,
disabled: PropTypes.bool,
className: PropTypes.string,
};

export default Search;

+ 0
- 122
src/components/InputFields/SelectField.js 파일 보기

@@ -1,122 +0,0 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Select, { components, createFilter } from 'react-select';
import { ErrorMessage, useField } from 'formik';
import { ReactComponent as FilledChevronDown } from '../../assets/images/svg/filled-chevron-down.svg';

const SelectField = ({
label,
disabled,
options,
link,
defaultSelected = null,
dropdownFullHeight,
selectOption,
...props
}) => {
const [field, meta, helpers] = useField(props);

const filterConfig = {
ignoreCase: true,
ignoreAccents: true,
trim: true,
matchFrom: 'start',
};

useEffect(() => {
if (defaultSelected) {
helpers.setValue(defaultSelected);
}
}, [defaultSelected]); // eslint-disable-line

const DropdownIndicator = (props) =>
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
<FilledChevronDown />
</components.DropdownIndicator>
);

function styles() {
let style = 'c-input';

if (meta.error && meta.touched) {
style += ` c-input--error`;
}

if (dropdownFullHeight) {
style += ` c-input--dropdown-full-height`;
}

return style;
}

return (
<div className={styles()}>
{!!label && (
<label className="c-input__label" htmlFor={field.name}>
{label}
</label>
)}
{!!link && <div className="c-input__link">{link}</div>}
<Select
defaultValue={defaultSelected || options[0]}
components={{ DropdownIndicator }}
isSearchable={false}
classNamePrefix="c-select"
options={options}
isDisabled={disabled}
{...field}
{...props}
onBlur={(e) => {
helpers.setTouched(true);
field.onBlur(e);
}}
onChange={(selectedOption) => {
helpers.setValue(selectedOption);

if (props.onChange) {
props.onChange();
}

if (selectOption) {
selectOption(selectedOption);
}
}}
filterOption={createFilter(filterConfig)}
/>
<ErrorMessage name={field.name}>
{(errorMessage) => {
if (typeof errorMessage === 'string') {
return <span className="c-input__error">{errorMessage}</span>;
}
return <span className="c-input__error">{errorMessage.value}</span>;
}}
</ErrorMessage>
</div>
);
};

SelectField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string,
}),
form: PropTypes.shape({}),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]),
disabled: PropTypes.bool,
options: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
),
onChange: PropTypes.func,
link: PropTypes.node,
defaultSelected: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
dropdownFullHeight: PropTypes.bool,
selectOption: PropTypes.func,
};

export default SelectField;

+ 0
- 72
src/components/InputFields/TextField.js 파일 보기

@@ -1,72 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

import BaseInputField from './BaseInputField';
import {
BACKSPACE_KEYCODE,
TAB_KEYCODE,
RIGHT_ARROW_KEYCODE,
LEFT_ARROW_KEYCODE,
} from '../../constants/keyCodeConstants';

const TextField = ({
field,
form,
label,
placeholder,
disabled,
centerText,
autoFocus,
preventAllExceptNumbers,
...props
}) => {
const onKeydownHandler = (event) => {
if (preventAllExceptNumbers) {
if (
event.keyCode === BACKSPACE_KEYCODE ||
event.keyCode === TAB_KEYCODE ||
event.keyCode === RIGHT_ARROW_KEYCODE ||
event.keyCode === LEFT_ARROW_KEYCODE
) {
return;
}

if (
(event.keyCode < 58 && event.keyCode > 47) ||
(event.keyCode < 106 && event.keyCode > 95)
) {
return;
}

event.preventDefault();
}
};

return (
<BaseInputField
autoFocus={autoFocus}
type="text"
label={label}
placeholder={placeholder}
disabled={disabled}
form={form}
field={field}
centerText={centerText}
{...props}
onKeyDown={(event) => onKeydownHandler(event)}
/>
);
};

TextField.propTypes = {
field: PropTypes.shape({}),
form: PropTypes.shape({}),
label: PropTypes.string,
placeholder: PropTypes.string,
disabled: PropTypes.bool,
centerText: PropTypes.bool,
autoFocus: PropTypes.bool,
preventAllExceptNumbers: PropTypes.bool,
};

export default TextField;

+ 8
- 9
src/components/ItemDetails/Header/Header.js 파일 보기

@@ -1,28 +1,27 @@
import React from "react";
import PropTypes from "prop-types";
import { useHistory } from "react-router-dom";
//import { IconButton } from "../../Buttons/IconButton/IconButton";
import { HeaderContainer, HeaderText, ButtonContainer } from "./Header.styled";
import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton";

// const DownArrow = (props) => (
// <IconStyled {...props}>
// <Down />
// </IconStyled>
// );
import { useTranslation } from "react-i18next";

const Header = (props) => {
const history = useHistory();
const {t} = useTranslation();

const handleBackButton = () => {
history.goBack();
};

return (
<HeaderContainer onClick={handleBackButton} component="header" className={props.className}>
<HeaderContainer
onClick={handleBackButton}
component="header"
className={props.className}
>
<ButtonContainer>
<ArrowButton side={"left"}></ArrowButton>
<HeaderText>Nazad na objave</HeaderText>
<HeaderText>{t("itemDetailsCard.headerTitle")}</HeaderText>
</ButtonContainer>
</HeaderContainer>
);

+ 19
- 21
src/components/ItemDetails/ItemDetails.js 파일 보기

@@ -1,31 +1,29 @@
import React, { useMemo } from 'react';
import React, { useMemo } from "react";
import Header from "./Header/Header";
import { useSelector } from "react-redux";
import { ItemDetailsContainer } from "./ItemDetails.styled";
import ItemDetailsCard from "../Cards/ItemDetailsCard/ItemDetailsCard";
import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard";
import { selectOffer } from '../../store/selectors/offersSelectors';
import { selectUserId } from '../../store/selectors/loginSelectors';
import { selectOffer } from "../../store/selectors/offersSelectors";
import { selectUserId } from "../../store/selectors/loginSelectors";
// import { useHistory } from 'react-router-dom';



const ItemDetails = () => {
const offer = useSelector(selectOffer);
const userId = useSelector(selectUserId);
let isMyProfile = useMemo(() => {
if (offer?.offer?.userId?.toString() === userId.toString()) {
return true;
}
return false;
}, [offer, userId])
return (
<ItemDetailsContainer>
<Header/>
<ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} />
<ItemDetailsCard offer={offer} isMyOffer={isMyProfile}/>
</ItemDetailsContainer>
)
}
const offer = useSelector(selectOffer);
const userId = useSelector(selectUserId);
let isMyProfile = useMemo(() => {
if (offer?.offer?.userId?.toString() === userId.toString()) {
return true;
}
return false;
}, [offer, userId]);
return (
<ItemDetailsContainer>
<Header />
<ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} />
<ItemDetailsCard offer={offer} isMyOffer={isMyProfile} />
</ItemDetailsContainer>
);
};

export default ItemDetails;

+ 8
- 71
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js 파일 보기

@@ -1,49 +1,30 @@
import React from "react";
import PropTypes from "prop-types";
import {
DetailIcon,
DetailText,
MessageIcon,
OfferDetails,
OfferImage,
OfferTitle,
DetailContainer,
HeaderTop,
HeaderDetails,
BottomDetails,
StatusText,
PIBIcon,
UserIcon,
UserIconContainer,
} from "./ItemDetailsHeaderCard.styled";
import { ItemDetailsHeaderContainer } from "./ItemDetailsHeaderCard.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as PIB } from "../../../assets/images/svg/pib.svg";
import { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg";
import selectedTheme from "../../../themes";
import { useHistory } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import StatisticDetails from "./StatisticDetails/StatisticDetails";
import PIBDetail from "./OfferDetail/PIB/PIBDetail";
import CategoryDetail from "./OfferDetail/Category/CategoryDetail";

const ItemDetailsHeaderCard = (props) => {
const history = useHistory();
const chats = useSelector(selectLatestChats);
const offer = props.offer;
const userId = useSelector(selectUserId);
if (!props.offer) {
return <div>Loading...</div>;
}
let percentOfSucceededExchanges;
if (offer?.companyData?.statistics?.exchanges?.succeeded === 0) {
percentOfSucceededExchanges = 0;
} else {
percentOfSucceededExchanges = Math.ceil(
(offer?.companyData?.statistics?.exchanges?.total /
offer?.companyData?.statistics?.exchanges?.succeeded) *
100
);
}

const handleGoProfile = () => {
history.push(`/profile/${offer?.offer?.userId}`);
};
@@ -70,26 +51,8 @@ const ItemDetailsHeaderCard = (props) => {
<OfferTitle isMyProfile={props.isMyProfile} onClick={handleGoProfile}>
{offer?.companyData?.company?.name}
</OfferTitle>
<DetailContainer>
<PIBIcon color={selectedTheme.iconStrokeColor} component="span">
<PIB />
</PIBIcon>
<DetailText isMyProfile={props.isMyProfile}>
PIB - {offer?.companyData?.company?.PIB}
</DetailText>
</DetailContainer>
<DetailContainer shouldHideResponsive>
<DetailIcon
color={selectedTheme.iconStrokeColor}
component="span"
size="22px"
>
<Category width={"22px"} />
</DetailIcon>
<DetailText isMyProfile={props.isMyProfile}>
{offer?.companyData?.company?.contacts?.location}
</DetailText>
</DetailContainer>
<PIBDetail offer={props.offer}/>
<CategoryDetail offer={props.offer}/>
</OfferDetails>
{props.isMyProfile ? (
<UserIconContainer onClick={handleGoProfile}>
@@ -101,23 +64,8 @@ const ItemDetailsHeaderCard = (props) => {
</MessageIcon>
)}
</HeaderTop>
<HeaderDetails>
<BottomDetails>
<StatusText>
<b>{offer?.companyData?.statistics?.publishes?.count}</b> objava
</StatusText>
<StatusText>
<b>{offer?.companyData?.statistics?.views?.count}</b> ukupnih
pregleda
</StatusText>
<StatusText>
<b>{percentOfSucceededExchanges}</b> % uspesnih trampi
</StatusText>
<StatusText>
<b>{percentOfSucceededExchanges}</b> % korektna komunikacija
</StatusText>
</BottomDetails>
</HeaderDetails>
<StatisticDetails offer={offer} />
</ItemDetailsHeaderContainer>
);
};
@@ -138,17 +86,6 @@ ItemDetailsHeaderCard.propTypes = {
sponsored: PropTypes.bool,
offer: PropTypes.any,
isMyProfile: PropTypes.bool,
// offer: PropTypes.shape({
// images: PropTypes.any,
// name:PropTypes.string,
// description:PropTypes.string,
// category:PropTypes.shape({
// name:PropTypes.string
// }),
// location:PropTypes.shape({
// city:PropTypes.string
// })
// })
};
ItemDetailsHeaderCard.defaultProps = {
halfwidth: false,

+ 2
- 69
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.styled.js 파일 보기

@@ -1,9 +1,8 @@
import { Box, Grid, Typography } from "@mui/material";
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import { ReactComponent as User} from "../../../assets/images/svg/user.svg";


@@ -22,42 +21,12 @@ export const ItemDetailsHeaderContainer = styled(Box)`
max-width: 2000px;
position: relative;
`;
export const DetailContainer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
gap: 7px;
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
margin-bottom: 7px;
font-size: 12px;
@media (max-width: 600px) {
${(props) => props.shouldHideResponsive && `display: none;`}
}
`;
export const HeaderTop = styled(Box)`
display: flex;
flex-direction: row;
padding: 18px;
gap: 18px;
`;
export const HeaderDetails = styled(Box)`
background-color: ${selectedTheme.primaryIconBackgroundColor};
`;
export const BottomDetails = styled(Box)`
max-width: fit-content;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-column-gap: 12px;
grid-row-gap: 12px;
padding: 18px;
@media (max-width: 600px) {
display: flex;
flex-direction: column;
}
`;
export const OfferImage = styled.img`
border-radius: 50%;
width: 144px;
@@ -116,13 +85,6 @@ export const OfferDetails = styled(Box)`
`;

export const StatusText = styled(Grid)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
@media (max-width: 600px) {
font-size: 12px;
}
`;
export const OfferCategory = styled(Box)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
@@ -173,24 +135,7 @@ export const Line = styled(Box)`
width: 0;
margin: auto 0;
`;
export const DetailIcon = styled(Icon)`
display: flex;
align-items: center;
& svg {
width: 22px;
position: relative;
}
`;
export const DetailText = styled(Typography)`
font-family: "Open Sans";
color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText};
line-height: 16px;
font-size: 16px;
position: relative;
@media (max-width: 600px) {
font-size: 14px;
}
`;

export const CheckButton = styled(PrimaryButton)`
width: 180px;
height: 48px;
@@ -224,18 +169,6 @@ export const MessageIcon = styled(IconButton)`
}
}
`;
export const PIBIcon = styled(DetailIcon)`
position: relative;
top: 1px;
& span svg {
width: 22px;
height: 22px;
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
}
`;
export const UserIconContainer = styled(MessageIcon)`
background-color: ${selectedTheme.primaryIconBackgroundColor};
`

+ 30
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/Category/CategoryDetail.js 파일 보기

@@ -0,0 +1,30 @@
import React from "react";
import PropTypes from "prop-types";
import {
DetailContainer,
DetailIcon,
DetailText,
} from "./CategoryDetail.styled";
import selectedTheme from "../../../../../themes";
import { ReactComponent as Category } from "../../../../../assets/images/svg/category.svg";

const CategoryDetail = (props) => {
const offer = props.offer;
return (
<DetailContainer shouldHideResponsive>
<DetailIcon color={selectedTheme.iconStrokeColor} size="22px">
<Category width={"22px"} />
</DetailIcon>
<DetailText isMyProfile={props.isMyProfile}>
{offer?.companyData?.company?.contacts?.location}
</DetailText>
</DetailContainer>
);
};

CategoryDetail.propTypes = {
offer: PropTypes.any,
isMyProfile: PropTypes.bool,
};

export default CategoryDetail;

+ 37
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/Category/CategoryDetail.styled.js 파일 보기

@@ -0,0 +1,37 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../../themes";
import { Icon } from "../../../../Icon/Icon";

export const DetailContainer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
gap: 7px;
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
margin-bottom: 7px;
font-size: 12px;
@media (max-width: 600px) {
${(props) => props.shouldHideResponsive && `display: none;`}
}
`;
export const DetailIcon = styled(Icon)`
display: flex;
align-items: center;
& svg {
width: 22px;
position: relative;
}
`;
export const DetailText = styled(Typography)`
font-family: "Open Sans";
color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText};
line-height: 16px;
font-size: 16px;
position: relative;
@media (max-width: 600px) {
font-size: 14px;
}
`;

+ 30
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/PIB/PIBDetail.js 파일 보기

@@ -0,0 +1,30 @@
import React from "react";
import PropTypes from "prop-types";
import { DetailContainer, DetailText, PIBIcon } from "./PIBDetail.styled";
import selectedTheme from "../../../../../themes";
import { useTranslation } from "react-i18next";
import { ReactComponent as PIB } from "../../../../../assets/images/svg/pib.svg";


const PIBDetail = (props) => {
const { t } = useTranslation();
const offer = props.offer;
return (
<DetailContainer>
<PIBIcon color={selectedTheme.iconStrokeColor} component="span">
<PIB />
</PIBIcon>
<DetailText isMyProfile={props.isMyProfile}>
{`${t("itemDetailsCard.PIB")}${offer?.companyData?.company?.PIB}`}
</DetailText>
</DetailContainer>
);
};

PIBDetail.propTypes = {
isMyProfile: PropTypes.bool,
offer: PropTypes.any,
icon: PropTypes.node,
};

export default PIBDetail;

+ 49
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/OfferDetail/PIB/PIBDetail.styled.js 파일 보기

@@ -0,0 +1,49 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../../themes";
import { Icon } from "../../../../Icon/Icon";

export const DetailContainer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
gap: 7px;
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
margin-bottom: 7px;
font-size: 12px;
@media (max-width: 600px) {
${(props) => props.shouldHideResponsive && `display: none;`}
}
`;
export const DetailIcon = styled(Icon)`
display: flex;
align-items: center;
& svg {
width: 22px;
position: relative;
}
`;
export const DetailText = styled(Typography)`
font-family: "Open Sans";
color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText};
line-height: 16px;
font-size: 16px;
position: relative;
@media (max-width: 600px) {
font-size: 14px;
}
`;
export const PIBIcon = styled(DetailIcon)`
position: relative;
top: 1px;
& span svg {
width: 22px;
height: 22px;
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
}
`;

+ 60
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/StatisticDetails/StatisticDetails.js 파일 보기

@@ -0,0 +1,60 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import {
BottomDetails,
HeaderDetails,
StatusText,
StatusValue,
} from "./StatisticDetails.styled";
import { useTranslation } from "react-i18next";

const StatisticDetails = (props) => {
const { t } = useTranslation();
const offer = props.offer;
const percentOfSucceededExchanges = useMemo(() => {
if (offer?.companyData?.statistics?.exchanges?.succeeded === 0) {
return 0 + "%";
} else {
return (
Math.ceil(
(offer?.companyData?.statistics?.exchanges?.total /
offer?.companyData?.statistics?.exchanges?.succeeded) *
100
) + "%"
);
}
});

return (
<HeaderDetails>
<BottomDetails>
<StatusText>
<StatusValue>
{offer?.companyData?.statistics?.publishes?.count}
</StatusValue>
{t("itemDetailsCard.offers")}
</StatusText>
<StatusText>
<StatusValue>
{offer?.companyData?.statistics?.views?.count}
</StatusValue>
{t("itemDetailsCard.totalViews")}
</StatusText>
<StatusText>
<StatusValue>{percentOfSucceededExchanges}</StatusValue>
{t("itemDetailsCard.successfulExchanges")}
</StatusText>
<StatusText>
<StatusValue>{percentOfSucceededExchanges}</StatusValue>
{t("itemDetailsCard.correctCommunications")}
</StatusText>
</BottomDetails>
</HeaderDetails>
);
};

StatisticDetails.propTypes = {
offer: PropTypes.any,
};

export default StatisticDetails;

+ 30
- 0
src/components/ItemDetails/ItemDetailsHeaderCard/StatisticDetails/StatisticDetails.styled.js 파일 보기

@@ -0,0 +1,30 @@
import { Box, Grid } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";

export const HeaderDetails = styled(Box)`
background-color: ${selectedTheme.primaryIconBackgroundColor};
`;
export const BottomDetails = styled(Box)`
max-width: fit-content;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-column-gap: 12px;
grid-row-gap: 12px;
padding: 18px;
@media (max-width: 600px) {
display: flex;
flex-direction: column;
}
`;
export const StatusText = styled(Grid)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
@media (max-width: 600px) {
font-size: 12px;
}
`;
export const StatusValue = styled.b`
font-weight: bold;
`

+ 0
- 59
src/components/ItemDetails/MockupdataDetails.js 파일 보기

@@ -1,59 +0,0 @@
import React from 'react'
import {ReactComponent as DummyImage1 } from "../../assets/images/svg/dummyImages/offer-1.svg"
import {ReactComponent as DummyAuthorImage1} from "../../assets/images/svg/dummyImages/DummyAuthorImage1.svg"
// import {ReactComponent as DummyImage2 } from "../../assets/images/svg/dummyImages/offer-2.svg"
// import {ReactComponent as DummyImage3 } from "../../assets/images/svg/dummyImages/offer-3.svg"
// import {ReactComponent as DummyImage4 } from "../../assets/images/svg/dummyImages/offer-4.svg"

export const packageEnum = {
package: "PACKAGE",
palette: "PALETTE",
piece: "PIECE"
}

export const Author = {
id: 0,
image: <DummyAuthorImage1 />,
title: "Women's Beauty House",
pib: 123456789,
location: "Nis, Serbia",
numberOfOffers: 9,
numberOfViews: 1200,
successSwapsProcent: "75%",
goodCommunicationProcent: "90%",
}

export const Offer = {
id: 0,
title: "Vino",
category: "Hrana i pice",
subcategory:"Farbe",
status:"novo",
quantity:150,
numberOfViews:45,
description: "Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.",
images: [
{
id:0,
image: <DummyImage1 />
},
{
id:1,
image: <DummyImage1 />
},
{
id:2,
image: <DummyImage1 />
},
{
id:3,
image: <DummyImage1 />
},
{
id:4,
image: <DummyImage1 />
},
],
package: packageEnum.package,
postDate: "12.04.2022",
}

+ 0
- 26
src/components/Loader/BlockSectionLoader.js 파일 보기

@@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

const BlockSectionLoader = ({ children, isLoading, fullHeight, noShadow }) => (
<div
className={`c-loader__wrapper c-loader__wrapper--block ${
fullHeight ? 'c-loader__wrapper--full-height' : ''
} ${noShadow ? 'c-loader__wrapper--no-shadow' : ''}`}
>
{children}
{isLoading && (
<div className="c-loader">
<div className="c-loader__icon" />
</div>
)}
</div>
);

BlockSectionLoader.propTypes = {
children: PropTypes.node,
isLoading: PropTypes.bool,
fullHeight: PropTypes.bool,
noShadow: PropTypes.bool,
};

export default BlockSectionLoader;

+ 0
- 13
src/components/Loader/FullPageLoader.js 파일 보기

@@ -1,13 +0,0 @@
import React from "react";
import { ReactComponent as Logo } from "../../assets/images/svg/big-logo-vertical.svg";
import { FullPageLoaderContainer } from "./FullPageLoader.styled";

const FullPageLoader = () => {
return (
<FullPageLoaderContainer>
<Logo />
</FullPageLoaderContainer>
);
};

export default FullPageLoader;

+ 0
- 10
src/components/Loader/FullPageLoader.styled.js 파일 보기

@@ -1,10 +0,0 @@

import { Container } from "@mui/material";
import styled from "styled-components";

export const FullPageLoaderContainer = styled(Container)`
height: 100%;
width: 100vw;
padding-top: 250px;
text-align: center;
`

+ 0
- 20
src/components/Loader/SectionLoader.js 파일 보기

@@ -1,20 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';

const SectionLoader = ({ children, isLoading }) => (
<div className="c-loader__wrapper">
{children}
{isLoading && (
<div className="c-loader">
<div className="c-loader__icon" />
</div>
)}
</div>
);

SectionLoader.propTypes = {
children: PropTypes.node,
isLoading: PropTypes.bool,
};

export default SectionLoader;

+ 19
- 0
src/components/Login/Description/LoginDescription.js 파일 보기

@@ -0,0 +1,19 @@
import React from "react";
import PropTypes from "prop-types";
import { LoginDescription as Description } from "./LoginDescription.styled";
import { useTranslation } from "react-i18next";

const LoginDescription = () => {
const { t } = useTranslation();
return (
<Description component="h1" variant="h6">
{t("login.welcomeText")}
</Description>
);
};

LoginDescription.propTypes = {
children: PropTypes.node,
};

export default LoginDescription;

+ 18
- 0
src/components/Login/Description/LoginDescription.styled.js 파일 보기

@@ -0,0 +1,18 @@
import { Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";

export const LoginDescription = styled(Typography)`
font-family: "Open Sans";
margin-top: 9px;
width: 221px;
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 22px;
display: flex;
align-items: center;
text-align: center;
color: ${selectedTheme.primaryGrayText};
margin-bottom: 20px;
`;

+ 26
- 0
src/components/Login/ErrorMessage/ErrorMessage.js 파일 보기

@@ -0,0 +1,26 @@
import React from "react";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { selectLoginError } from "../../../store/selectors/loginSelectors";
import { ErrorText } from "./ErrorMessage.styled";

const ErrorMessage = (props) => {
const formik = props.formik;
const error = useSelector(selectLoginError);
return (
<>
{formik.errors.password && formik.touched.password && (
<ErrorText>{formik.errors.password}</ErrorText>
)}
{error.length > 0 && !formik.errors.password && (
<ErrorText>{error}</ErrorText>
)}
</>
);
};

ErrorMessage.propTypes = {
formik: PropTypes.any,
};

export default ErrorMessage;

+ 11
- 0
src/components/Login/ErrorMessage/ErrorMessage.styled.js 파일 보기

@@ -0,0 +1,11 @@
import { Typography } from "@mui/material";
import styled from "styled-components";

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

+ 30
- 0
src/components/Login/Fields/Email/EmailField.js 파일 보기

@@ -0,0 +1,30 @@
import React from "react";
import PropTypes from "prop-types";
import { selectLoginError } from "../../../../store/selectors/loginSelectors";
import { useSelector } from "react-redux";
import { TextField } from "../../../TextFields/TextField/TextField";
import { useTranslation } from "react-i18next";

const EmailField = (props) => {
const { t } = useTranslation();
const error = useSelector(selectLoginError);
const formik = props.formik;
return (
<TextField
name="email"
placeholder={t("common.labelEmail")}
value={formik.values.email}
onChange={formik.handleChange}
error={(formik.touched.email && formik.errors.email) || error.length > 0}
helperText={formik.touched.email && formik.errors.email}
autoFocus
fullWidth
/>
);
};

EmailField.propTypes = {
formik: PropTypes.any,
};

export default EmailField;

+ 0
- 0
src/components/Login/Fields/Email/EmailField.styled.js 파일 보기


+ 50
- 0
src/components/Login/Fields/Password/PasswordField.js 파일 보기

@@ -0,0 +1,50 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import IconButton from "../../../IconButton/IconButton";
import { ReactComponent as VisibilityOn } from "../../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../../../assets/images/svg/eye.svg";
import { useSelector } from "react-redux";
import { selectLoginError } from "../../../../store/selectors/loginSelectors";
import { TextField } from "../../../TextFields/TextField/TextField";
import { useTranslation } from "react-i18next";

const PasswordField = (props) => {
const formik = props.formik;
const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);
const error = useSelector(selectLoginError);
const {t} = useTranslation();

return (
<TextField
name="password"
placeholder={t("common.labelPassword")}
margin="normal"
type={showPassword ? "text" : "password"}
value={formik.values.password}
onChange={formik.handleChange}
error={
(formik.touched.password && formik.errors.password) || error.length > 0
}
helperText={formik.touched.password && formik.errors.password}
fullWidth
InputProps={{
endAdornment: (
<IconButton
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <VisibilityOn /> : <VisibilityOff />}
</IconButton>
),
}}
/>
);
};

PasswordField.propTypes = {
formik: PropTypes.any,
};

export default PasswordField;

+ 0
- 0
src/components/Login/Fields/Password/PasswordField.styled.js 파일 보기


+ 34
- 0
src/components/Login/ForgotPasswordLink/ForgotPasswordLink.js 파일 보기

@@ -0,0 +1,34 @@
import React from "react";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { selectLoginError } from "../../../store/selectors/loginSelectors";
import { FORGOT_PASSWORD_PAGE } from "../../../constants/pages";
import Link from "../../Link/Link";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";

const ForgotPasswordLink = () => {
const error = useSelector(selectLoginError);
const { t } = useTranslation();
return (
<Link
to={FORGOT_PASSWORD_PAGE}
textsize="12px"
component={NavLink}
underline="hover"
align="right"
style={{
marginTop: error.length > 0 ? "0" : "18px",
marginBottom: "18px",
}}
>
{t("login.forgotYourPassword")}
</Link>
);
};

ForgotPasswordLink.propTypes = {
children: PropTypes.node,
};

export default ForgotPasswordLink;

+ 0
- 0
src/components/Login/ForgotPasswordLink/ForgotPasswordLink.styled.js 파일 보기


+ 98
- 0
src/components/Login/Login.js 파일 보기

@@ -0,0 +1,98 @@
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import {
clearLoginErrors,
fetchLogin,
} from "../../store/actions/login/loginActions";
import { selectLoginError } from "../../store/selectors/loginSelectors";
import { HOME_PAGE } from "../../constants/pages";
import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg";
import { LoginPageContainer, LoginFormContainer } from "./Login.styled";
import loginValidation from "../../validations/loginValidation";
import loginInitialValues from "../../initialValues/loginInitialValues";
import LoginTitle from "./Title/LoginTitle";
import LoginDescription from "./Description/LoginDescription";
import EmailField from "./Fields/Email/EmailField";
import PasswordField from "./Fields/Password/PasswordField";
import ErrorMessage from "./ErrorMessage/ErrorMessage";
import ForgotPasswordLink from "./ForgotPasswordLink/ForgotPasswordLink";
import LoginButton from "./LoginButton/LoginButton";
import RegisterLink from "./RegisterLink/RegisterLink";

const Login = () => {
const dispatch = useDispatch();
const error = useSelector(selectLoginError);
const history = useHistory();

useEffect(() => {
dispatch(clearLoginErrors());
}, []);

const handleApiResponseSuccess = () => {
history.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
},
});
};

const handleSubmit = (values) => {
const { email, password } = values;
console.log(values);
dispatch(clearLoginErrors());
dispatch(
fetchLogin({
email,
password,
handleApiResponseSuccess,
})
);
};

const formik = useFormik({
initialValues: loginInitialValues,
validationSchema: loginValidation,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

useEffect(() => {
if (error) {
if (formik.errors.email || formik.errors.password) {
dispatch(clearLoginErrors());
}
}
}, [formik.errors.email, formik.errors.password]);

return (
<LoginPageContainer>
<Logo />
<LoginTitle />
<LoginDescription />
<LoginFormContainer component="form" onSubmit={formik.handleSubmit}>
<EmailField formik={formik} />
<PasswordField formik={formik} />
<ErrorMessage formik={formik} />
<ForgotPasswordLink />
<LoginButton formik={formik} />
<RegisterLink />
</LoginFormContainer>
</LoginPageContainer>
);
};

Login.propTypes = {
history: PropTypes.shape({
replace: PropTypes.func,
push: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string,
}),
}),
};
export default Login;

+ 19
- 0
src/components/Login/Login.styled.js 파일 보기

@@ -0,0 +1,19 @@
import { Box, Container } from "@mui/material";
import styled from "styled-components";

export const LoginPageContainer = styled(Container)`
margin-top: 150px;
display: flex;
flex-direction: column;
align-items: center;
@media (max-height: 900px) {
margin-top: 110px;
}
@media (max-height: 800px) {
margin-top: 70px;
}
`;
export const LoginFormContainer = styled(Box)`
width: 335px;
height: 216px;
`;

+ 31
- 0
src/components/Login/LoginButton/LoginButton.js 파일 보기

@@ -0,0 +1,31 @@
import React from "react";
import PropTypes from "prop-types";
import selectedTheme from "../../../themes";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { useTranslation } from "react-i18next";

const LoginButton = (props) => {
const { t } = useTranslation();
const formik = props.formik;
return (
<PrimaryButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
disabled={
formik.values.email.length === 0 || formik.values.password.length === 0
}
>
{t("login.logIn")}
</PrimaryButton>
);
};

LoginButton.propTypes = {
formik: PropTypes.any,
};

export default LoginButton;

+ 0
- 0
src/components/Login/LoginButton/LoginButton.styled.js 파일 보기


+ 27
- 0
src/components/Login/RegisterLink/RegisterLink.js 파일 보기

@@ -0,0 +1,27 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { RegisterAltText, RegisterTextContainer } from "./RegisterLink.styled";
import Link from "../../Link/Link";
import { NavLink } from "react-router-dom";

const RegisterLink = () => {
const { t } = useTranslation();
return (
<RegisterTextContainer>
<RegisterAltText>
{t("login.dontHaveAccount").padEnd(2, " ")}
</RegisterAltText>

<Link to="/register" component={NavLink} underline="hover" align="center">
{t("login.signUp")}
</Link>
</RegisterTextContainer>
);
};

RegisterLink.propTypes = {
children: PropTypes.any,
};

export default RegisterLink;

+ 20
- 0
src/components/Login/RegisterLink/RegisterLink.styled.js 파일 보기

@@ -0,0 +1,20 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";

export const RegisterAltText = styled(Typography)`
font-family: "Poppins";
color: ${selectedTheme.primaryText};
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`;
export const RegisterTextContainer = styled(Box)`
display: flex;
flex-direction: row;
margin-top: 36px;
justify-content: center;
@media (max-width: 600px) {
padding-bottom: 36px;
}
`;

+ 19
- 0
src/components/Login/Title/LoginTitle.js 파일 보기

@@ -0,0 +1,19 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { LoginTitle as Title } from "./LoginTitle.styled";

const LoginTitle = () => {
const { t } = useTranslation();
return (
<Title component="h1" variant="h5">
{t("login.logInTitle")}
</Title>
);
};

LoginTitle.propTypes = {
children: PropTypes.node,
};

export default LoginTitle;

+ 17
- 0
src/components/Login/Title/LoginTitle.styled.js 파일 보기

@@ -0,0 +1,17 @@
import { Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";

export const LoginTitle = styled(Typography)`
font-family: "Open Sans";
width: 328px;
height: 33px;
text-align: center;
flex: 1;
font-style: normal;
font-weight: 400;
font-size: 24px;
line-height: 33px;
color: ${selectedTheme.primaryPurple};
margin-top: 36px;
`;

+ 0
- 57
src/components/MUI/DialogComponent.js 파일 보기

@@ -1,57 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Dialog,
DialogContent,
DialogTitle,
DialogActions,
Button,
useMediaQuery,
useTheme,
} from '@mui/material';

const DialogComponent = ({
title,
content,
onClose,
open,
maxWidth,
fullWidth,
responsive,
}) => {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

const handleClose = () => {
onClose();
};

return (
<Dialog
maxWidth={maxWidth}
fullWidth={fullWidth}
fullScreen={responsive && fullScreen}
onClose={handleClose}
open={open}
>
<DialogTitle>{title}</DialogTitle>
{content && <DialogContent>{content}</DialogContent>}
<DialogActions>
<Button onClick={handleClose}>OK</Button>
<Button onClick={handleClose}>Cancel</Button>
</DialogActions>
</Dialog>
);
};

DialogComponent.propTypes = {
title: PropTypes.string,
open: PropTypes.bool.isRequired,
content: PropTypes.any,
onClose: PropTypes.func.isRequired,
maxWidth: PropTypes.any,
fullWidth: PropTypes.bool,
responsive: PropTypes.bool,
};

export default DialogComponent;

+ 21
- 21
src/components/MUI/DrawerComponent.js 파일 보기

@@ -1,28 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Drawer } from '@mui/material';
import React from "react";
import PropTypes from "prop-types";
import { Drawer } from "@mui/material";

const DrawerComponent = ({ open, toggleOpen, content, anchor = 'right' }) => (
<Drawer
sx={{
minWidth: 250,
'& .MuiDrawer-paper': {
minWidth: 250,
},
}}
anchor={anchor}
open={open}
onClose={toggleOpen}
>
{content ? content : null}
</Drawer>
const DrawerComponent = ({ open, toggleOpen, content, anchor = "right" }) => (
<Drawer
sx={{
minWidth: 250,
"& .MuiDrawer-paper": {
minWidth: 250,
},
}}
anchor={anchor}
open={open}
onClose={toggleOpen}
>
{content ? content : null}
</Drawer>
);

DrawerComponent.propTypes = {
open: PropTypes.bool,
toggleOpen: PropTypes.func,
content: PropTypes.any,
anchor: PropTypes.oneOf(['top', 'right', 'left', 'bottom']),
open: PropTypes.bool,
toggleOpen: PropTypes.func,
content: PropTypes.any,
anchor: PropTypes.oneOf(["top", "right", "left", "bottom"]),
};

export default DrawerComponent;

+ 0
- 15
src/components/MUI/ErrorMessageComponent.js 파일 보기

@@ -1,15 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Typography } from '@mui/material';

const ErrorMessageComponent = ({ error }) => (
<Typography variant="body1" color="error" my={2}>
{error}
</Typography>
);

ErrorMessageComponent.propTypes = {
error: PropTypes.string.isRequired,
};

export default ErrorMessageComponent;

+ 0
- 29
src/components/MUI/Examples/DataGridExample.js 파일 보기

@@ -1,29 +0,0 @@
import React from 'react';
import { Paper, Typography } from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';

// Use these values from REDUX?
const rows = [
{ id: 1, col1: 'Example', col2: 'Row', col3: '1' },
{ id: 2, col1: 'Row', col2: 'Example', col3: '2' },
{ id: 3, col1: '3', col2: 'Row', col3: 'Example' },
];

const columns = [
{ field: 'col1', headerName: 'Column 1', flex: 1 },
{ field: 'col2', headerName: 'Column 2', flex: 1 },
{ field: 'col3', headerName: 'Column 2', flex: 1 },
];

const DataGridExample = () => {
return (
<Paper sx={{ p: 2 }} elevation={5}>
<Typography variant="h4" gutterBottom align="center">
DataGrid Example
</Typography>
<DataGrid autoHeight rows={rows} columns={columns} />
</Paper>
);
};

export default DataGridExample;

+ 0
- 64
src/components/MUI/Examples/ModalsExample.js 파일 보기

@@ -1,64 +0,0 @@
import React from 'react';
// import { Button, Divider, Paper, Typography } from '@mui/material';
// import DialogComponent from '../DialogComponent';
// import DrawerComponent from '../DrawerComponent';
// import PopoverComponent from '../PopoverComponent';

const Modals = () => {
// const [dialogOpen, setDialogOpen] = useState(false);
// const [drawerOpen, setDrawerOpen] = useState(false);
// const [popoverOpen, setPopoverOpen] = useState(false);
// const [anchorEl, setAnchorEl] = useState(null);

return (<></>
// <Paper
// sx={{
// p: 2,
// display: 'flex',
// flexDirection: 'column',
// }}
// elevation={5}
// >
// <Typography variant="h4" gutterBottom align="center">
// Modals Example
// </Typography>
// <Divider />
// <Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
// <Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
// <Button
// onClick={(e) => {
// setPopoverOpen(true);
// setAnchorEl(e.currentTarget);
// }}
// >
// Open Popover
// </Button>
// <DialogComponent
// title="Dialog Title"
// content={<Typography>Dialog Content</Typography>}
// open={dialogOpen}
// onClose={() => setDialogOpen(false)}
// maxWidth="md"
// fullWidth
// responsive
// />
// <DrawerComponent
// anchor="left"
// content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>}
// open={drawerOpen}
// toggleOpen={() => setDrawerOpen(!drawerOpen)}
// />
// <PopoverComponent
// anchorEl={anchorEl}
// open={popoverOpen}
// onClose={() => {
// setPopoverOpen(false);
// setAnchorEl(null);
// }}
// content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
// />
// </Paper>
);
};

export default Modals;

+ 0
- 183
src/components/MUI/Examples/PagingSortingFilteringExample.js 파일 보기

@@ -1,183 +0,0 @@
import React, { useEffect, useState } from 'react';
import {
Paper,
Box,
Grid,
Typography,
Divider,
TablePagination,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
} from '@mui/material';
// import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector, batch } from 'react-redux';
import useDebounce from '../../../hooks/useDebounceHook';
import {
itemsSelector,
pageSelector,
itemsPerPageSelector,
countSelector,
sortSelector,
} from '../../../store/selectors/randomDataSelectors';
import {
loadData,
updatePage,
updateItemsPerPage,
updateFilter,
updateSort,
} from '../../../store/actions/randomData/randomDataActions';

const PagingSortingFilteringExample = () => {
const [filterText, setFilterText] = useState('');

const dispatch = useDispatch();
// const { t } = useTranslation();
const items = useSelector(itemsSelector);
const currentPage = useSelector(pageSelector);
const itemsPerPage = useSelector(itemsPerPageSelector);
const totalCount = useSelector(countSelector);
const sort = useSelector(sortSelector) || 'name-asc';

// Use debounce to prevent too many rerenders
const debouncedFilterText = useDebounce(filterText, 500);

useEffect(() => {
dispatch(loadData(30));
dispatch(updateSort(sort));
}, []);

useEffect(() => {
batch(() => {
dispatch(updateFilter(filterText));
currentPage > 0 && dispatch(updatePage(0));
});
}, [debouncedFilterText]);

const handleFilterTextChange = (event) => {
const filterText = event.target.value;
setFilterText(filterText);
};

const handleSortChange = (event) => {
const sort = event.target.value;
dispatch(updateSort(sort));
};

const handlePageChange = (event, newPage) => {
dispatch(updatePage(newPage));
};

const handleItemsPerPageChange = (event) => {
const itemsPerPage = parseInt(event.target.value);
batch(() => {
dispatch(updateItemsPerPage(itemsPerPage));
dispatch(updatePage(0));
});
};


return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'start',
py: 2,
minHeight: 500,
}}
elevation={5}
>
<Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center">
Pagination, Filtering and Sorting Example Client Side
</Typography>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
flexWrap: 'wrap',
mx: 2,
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
{/* TODO Separate into SelectComponent */}
<FormControl sx={{ flexGrow: 1 }}>
<InputLabel id="sort-label">Sort</InputLabel>
<Select
label="Sort"
labelId="sort-label"
id="sort-select-helper"
value={sort}
onChange={handleSortChange}
>
<MenuItem value="name-asc">Name - A-Z</MenuItem>
<MenuItem value="name-desc">Name - Z-A</MenuItem>
<MenuItem value="price-asc">Price - Lowest to Highest</MenuItem>
<MenuItem value="price-desc">Price - Highest to Lowest</MenuItem>
</Select>
</FormControl>
<TextField
sx={{ flexGrow: 1 }}
variant="outlined"
label="Filter"
placeholder="Filter"
value={filterText}
onChange={handleFilterTextChange}
/>
</Box>
</Box>
<Grid container>
{items &&
items.length > 0 &&
items
.slice(
currentPage * itemsPerPage,
currentPage * itemsPerPage + itemsPerPage
)
.map((product, index) => (
// ! DON'T USE index for key, this is for example only
<Grid item sx={{ p: 2 }} xs={12} sm={6} md={4} lg={3} key={index}>
{/* TODO separate into component */}
<Paper sx={{ p: 3, height: '100%' }} elevation={3}>
<Typography sx={{ fontWeight: 600 }}>Name: </Typography>
<Typography display="inline"> {product.name}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Designer: </Typography>
<Typography display="inline"> {product.designer}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Type: </Typography>
<Typography display="inline"> {product.type}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Price: </Typography>
<Typography display="inline"> ${product.price}</Typography>
</Paper>
</Grid>
))}
</Grid>
<Box sx={{ width: '100%' }}>
<TablePagination
component="div"
count={totalCount}
page={currentPage}
onPageChange={handlePageChange}
rowsPerPage={itemsPerPage}
onRowsPerPageChange={handleItemsPerPageChange}
rowsPerPageOptions={[12, 24, 48, 96]}
labelRowsPerPage="Items per page"
showFirstButton
showLastButton
/>
</Box>
</Paper>
);
};

export default PagingSortingFilteringExample;

+ 0
- 159
src/components/MUI/Examples/PagingSortingFilteringExampleServerSide.js 파일 보기

@@ -1,159 +0,0 @@
import React, { useEffect, useState } from 'react';
import {
Paper,
Box,
Grid,
Typography,
Divider,
TablePagination,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
} from '@mui/material';
// import { useTranslation } from 'react-i18next';
import Backdrop from '../BackdropComponent';
import useDebounce from '../../../hooks/useDebounceHook';
import { useRandomData } from '../../../context/RandomDataContext';

const PagingSortingFilteringExampleServerSide = () => {
const [filterText, setFilterText] = useState('');
const { state, data } = useRandomData();
const { items, loading, totalCount, currentPage, itemsPerPage, sort } = data;
const { setPage, setItemsPerPage, setSort, setFilter } = state;
// const { t } = useTranslation();

// Use debounce to prevent too many rerenders
const debouncedFilterText = useDebounce(filterText, 500);

useEffect(() => {
setFilter(filterText);
}, [debouncedFilterText]);

const handleFilterTextChange = (event) => {
const filterText = event.target.value;
setFilterText(filterText);
};

const handleSortChange = (event) => {
const sort = event.target.value;
setSort(sort);
};

const handlePageChange = (event, newPage) => {
setPage(newPage);
};

const handleItemsPerPageChange = (event) => {
const itemsPerPage = parseInt(event.target.value);
setItemsPerPage(itemsPerPage);
setPage(0);
};

return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'start',
py: 2,
minHeight: 500,
position: 'relative',
}}
elevation={5}
>
{loading && <Backdrop isLoading position="absolute" />}
<Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center">
Pagination, Filtering and Sorting Example Server Side
</Typography>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
flexWrap: 'wrap',
mx: 2,
}}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
<FormControl sx={{ flexGrow: 1 }}>
<InputLabel id="sort-label">Sort</InputLabel>
<Select
label="Sort"
labelId="sort-label"
id="sort-select-helper"
value={sort || ''}
onChange={handleSortChange}
>
<MenuItem value="">None</MenuItem>
<MenuItem value="name-asc">Name - A-Z</MenuItem>
<MenuItem value="name-desc">Name - Z-A</MenuItem>
<MenuItem value="price-asc">Price - Lowest to Highest</MenuItem>
<MenuItem value="price-desc">Price - Highest to Lowest</MenuItem>
</Select>
</FormControl>
<TextField
sx={{ flexGrow: 1 }}
// variant="outlined"
label="Filter"
placeholder="Filter"
value={filterText}
onChange={handleFilterTextChange}
/>
</Box>
<Grid container sx={{ position: 'relative' }}>
{items &&
items.length > 0 &&
items.map((item) => (
<Grid
item
sx={{ p: 2 }}
xs={12}
sm={6}
md={4}
lg={3}
key={item.id}
>
{/* TODO separate into component */}
<Paper sx={{ p: 3, height: '100%' }} elevation={3}>
<Typography sx={{ fontWeight: 600 }}>Name: </Typography>
<Typography display="inline"> {item.name}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Company: </Typography>
<Typography display="inline"> {item.company}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Color: </Typography>
<Typography display="inline"> {item.color}</Typography>
<Divider />
<Typography sx={{ fontWeight: 600 }}>Price: </Typography>
<Typography display="inline"> {item.price}</Typography>
</Paper>
</Grid>
))}
</Grid>
<Box sx={{ width: '100%' }}>
<TablePagination
component="div"
count={totalCount}
page={currentPage}
onPageChange={handlePageChange}
rowsPerPage={itemsPerPage}
onRowsPerPageChange={handleItemsPerPageChange}
rowsPerPageOptions={[12, 24, 48, 96]}
labelRowsPerPage="Items per page"
showFirstButton
showLastButton
/>
</Box>
</Box>
</Paper>
);
};

export default PagingSortingFilteringExampleServerSide;

+ 0
- 26
src/components/MUI/MenuListComponent.js 파일 보기

@@ -1,26 +0,0 @@
import React, { useState } from 'react';
import { Button, Menu, MenuItem } from '@mui/material';

const MenuListComponent = () => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};

return (
<div>
<Button onClick={handleClick}>Menu List</Button>
<Menu id="menu-list" anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={handleClose}>Menu Item 1</MenuItem>
<MenuItem onClick={handleClose}>Menu Item 2</MenuItem>
<MenuItem onClick={handleClose}>Menu Item 3</MenuItem>
</Menu>
</div>
);
};

export default MenuListComponent;

+ 0
- 160
src/components/MUI/NavbarComponent.js 파일 보기

@@ -1,160 +0,0 @@
import React, { useState, useMemo, useContext } from "react";
import {
AppBar,
Badge,
Box,
IconButton,
Toolbar,
Typography,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
useMediaQuery,
} from "@mui/material";
import { useTheme } from "@mui/system";
import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
import ShoppingBasketIcon from "@mui/icons-material/ShoppingBasket";
import Brightness4Icon from "@mui/icons-material/Brightness4";
import Brightness7Icon from "@mui/icons-material/Brightness7";
import MenuList from "./MenuListComponent";
import Drawer from "./DrawerComponent";
import { ColorModeContext } from "../../context/ColorModeContext";

const NavbarComponent = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const toggleColorMode = useContext(ColorModeContext);

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};

const drawerContent = useMemo(
() => (
<List>
<ListItemButton divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 1</ListItemText>
</ListItemIcon>
</ListItemButton>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 2</ListItemText>
</ListItemIcon>
</ListItem>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemText>Link 3</ListItemText>
</ListItem>
<ListItem divider>
<IconButton onClick={toggleColorMode}>
<ListItemText>Toggle {theme.palette.mode} mode</ListItemText>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</ListItem>
</List>
),
[handleToggleDrawer]
);

return (
<AppBar
elevation={2}
sx={{ backgroundColor: "background.default", position: "relative" }}
>
<Toolbar>
<Box
component="div"
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
}}
>
{matches ? (
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={drawerContent}
/>
) : (
<Box sx={{ display: "flex" }}>
<Typography
variant="h6"
sx={{
marginRight: 3,
cursor: "pointer",
color: "text.primary",
}}
>
Link 1
</Typography>
<Typography
variant="body1"
sx={{
marginRight: 3,
cursor: "pointer",
color: "text.primary",
}}
>
Link 2
</Typography>
<Typography
variant="subtitle1"
sx={{
marginRight: 3,
cursor: "pointer",
color: "text.primary",
}}
>
Link 3
</Typography>
</Box>
)}
<Box>
<MenuList />
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
{matches ? (
<Box>
<IconButton onClick={handleToggleDrawer}>
<MenuOutlinedIcon />
</IconButton>
</Box>
) : (
<Box>
<IconButton>
<Badge badgeContent={3} color="primary">
<ShoppingBasketIcon color="action" />
</Badge>
</IconButton>
<IconButton sx={{ ml: 1 }} onClick={toggleColorMode}>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</Box>
)}
</Box>
</Box>
</Toolbar>
</AppBar>
);
};

export default NavbarComponent;

+ 35
- 10
src/components/MarketPlace/Header/Header.js 파일 보기

@@ -22,6 +22,11 @@ import useFilters from "../../../hooks/useFilters";
import useSorting from "../../../hooks/useSorting";
import { useTranslation } from "react-i18next";
import { Tooltip } from "@mui/material";
import {
ALL_CATEGORIES,
COMMA,
SPREAD,
} from "../../../constants/marketplaceHeaderTitle";

const DownArrow = (props) => (
<IconStyled {...props}>
@@ -34,38 +39,45 @@ const Header = (props) => {
const sorting = useSorting();
const { t } = useTranslation();
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [headerString, setHeaderString] = useState("SVE KATEGORIJE");
const [headerString, setHeaderString] = useState(ALL_CATEGORIES);

//Changing shown sort option on select menu
useEffect(() => {
setSortOption(sorting.selectedSortOption);
}, [sorting.selectedSortOption]);

// Changing header string on refresh or on load
useEffect(() => {
let headerStringLocal = ALL_CATEGORIES;
if (filters.isApplied) {
let headerStringLocal = "Sve kategorije";
// Adding category to header string
if (filters.selectedCategory?.name) {
headerStringLocal = filters.selectedCategory.name;
// Adding subcategories to header string
if (filters.selectedSubcategory?.name) {
headerStringLocal += ` | ${filters.selectedSubcategory.name}`;
headerStringLocal += `${SPREAD}${filters.selectedSubcategory.name}`;
}
}
// Adding locations to header string
if (filters.selectedLocations && filters.selectedLocations?.length > 0) {
headerStringLocal += " | ";
headerStringLocal += SPREAD;

filters.selectedLocations.forEach((location, index) => {
// Checking if item is last
if (index + 1 === filters.selectedLocations.length) {
headerStringLocal += location.city;
} else {
headerStringLocal += location.city + ", ";
headerStringLocal += location.city + COMMA;
}
});
}
setHeaderString(headerStringLocal);
}
setHeaderString(headerStringLocal);
}, [
filters.isApplied,
filters.selectedCategory,
filters.selectedLocations,
filters.selectedSubcategory,
filters.isApplied,
filters.selectedLocations,
]);

const handleChangeSelect = (event) => {
@@ -80,8 +92,9 @@ const Header = (props) => {

return (
<HeaderContainer>
{/* Setting appropriate header title if page is market place or my offers */}
<Tooltip title={headerString}>
{props.myOffers !== true ? (
{!props.myOffers ? (
headerString === "Sve kategorije" &&
(sorting.selectedSortOption === sortEnum.INITIAL ||
sorting.selectedSortOption === sortEnum.NEW) ? (
@@ -93,11 +106,16 @@ const Header = (props) => {
<HeaderLocation>{headerString}</HeaderLocation>
)
) : (
<MySwapsTitle> <RefreshIcon /> {t("header.myOffers")}</MySwapsTitle>
<MySwapsTitle>
<RefreshIcon /> {t("header.myOffers")}
</MySwapsTitle>
)}
</Tooltip>
{/* ^^^^^^ */}

<HeaderOptions>
<HeaderButtons>
{/* Setting display of offer cards to full width */}
<HeaderButton
iconColor={
props.isGrid
@@ -108,6 +126,9 @@ const Header = (props) => {
>
<GridLine />
</HeaderButton>
{/* ^^^^^^ */}

{/* Setting display of offer cards to half width (Grid) */}
<HeaderButton
iconColor={
props.isGrid
@@ -118,7 +139,10 @@ const Header = (props) => {
>
<GridSquare />
</HeaderButton>
{/* ^^^^^^ */}
</HeaderButtons>

{/* Select option to choose sorting */}
<HeaderSelect
value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value}
IconComponent={DownArrow}
@@ -135,6 +159,7 @@ const Header = (props) => {
);
})}
</HeaderSelect>
{/* ^^^^^^ */}
</HeaderOptions>
</HeaderContainer>
);

+ 0
- 3
src/components/MarketPlace/MarketPlace.js 파일 보기

@@ -19,8 +19,5 @@ MarketPlace.propTypes = {
children: PropTypes.node,
myOffers: PropTypes.bool,
};
// MarketPlace.defaultProps = {
// myOffers: false,
// }

export default MarketPlace;

+ 18
- 223
src/components/MarketPlace/Offers/Offers.js 파일 보기

@@ -1,247 +1,42 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import React, { useRef } from "react";
import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import {
fetchMineOffers,
fetchOffers,
} from "../../../store/actions/offers/offersActions";
import { useDispatch, useSelector } from "react-redux";
import {
selectMineOffers,
selectOffers,
selectPinnedOffers,
selectTotalOffers,
} from "../../../store/selectors/offersSelectors";
import { useSelector } from "react-redux";
import Paging from "../../Paging/Paging";
import { HOME_PAGE } from "../../../constants/pages";
import { useHistory } from "react-router-dom";
import { useQueryString } from "../../../hooks/useQueryString";
import {
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSortOption,
selectSelectedSubcategory,
} from "../../../store/selectors/filtersSelectors";
import { sortEnum } from "../../../enums/sortEnum";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { fetchChats } from "../../../store/actions/chat/chatActions";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import { startChat } from "../../../util/helpers/chatHelper";
import useOffers from "../../../hooks/useOffers";

const Offers = (props) => {
const [page, setPage] = useState(1);
// const [pinnedLength, setPinnedLength] = useState(0);
const pinnedOffers = useSelector(selectPinnedOffers);
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
const selectedSortOption = useSelector(selectSelectedSortOption);
const offers = useSelector(selectOffers);
const mineOffers = useSelector(selectMineOffers);
const chats = useSelector(selectLatestChats);
const total = useSelector(selectTotalOffers);
const history = useHistory();
const dispatch = useDispatch();
const offersRef = useRef(null);
const queryStringHook = useQueryString(props.myOffers);
const userId = useSelector(selectUserId);

useEffect(() => {
dispatch(fetchChats());
}, [])

useEffect(() => {
let queryObject = queryStringHook.getQueryObject();
if (queryObject.page && queryObject.page !== 1) {
setPage(parseInt(queryObject.page));
}
}, [history.location.search]);

useEffect(() => {
if (history?.location?.state?.logo || history?.location?.state?.refetch) {
dispatch(fetchOffers({ queryString: "" }));
setPage(1);
history.location.state = undefined;
}
}, [history.location.state]);

useEffect(() => {
if (queryStringHook.loadedFromURL) {
refetch();
} else {
queryStringHook.appendMultipleToQueryString([
{ key: "size", value: "10" },
{ key: "page", value: "1" },
]);
}
}, [queryStringHook.loadedFromURL, queryStringHook.queryString]);

useEffect(() => {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString()) {
queryStringHook.appendToQueryString("page", page);
} else {
refetch();
}
} else {
queryStringHook.appendToQueryString("page", page);
}
}, [page]);

const pinnedOffersToShow = useMemo(() => {
if (props.myOffers) {
return mineOffers.filter((item) => item.pinned === true);
}
return pinnedOffers;
}, [pinnedOffers, mineOffers, page, props.myOffers]);

const offersToShow = useMemo(() => {
if (props.myOffers) {
return mineOffers.filter((item) => item.pinned === false);
}
return offers;
}, [offers, mineOffers, page, props.myOffers]);

const allOffersToShow = useMemo(() => {
let newOffers = [...pinnedOffersToShow, ...offersToShow];
if (props.myOffers) {
if (selectedCategory && selectedCategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.category.name === selectedCategory.name
);
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.subcategory === selectedSubcategory.name
);
}
if (selectedLocations && selectedLocations?.length > 0) {
newOffers = newOffers.filter((item) => {
let isInOneOfLocations = false;
selectedLocations?.forEach((location) => {
if (item.location.city === location.city) {
isInOneOfLocations = true;
}
});
return isInOneOfLocations;
});
}
let oldOffers = [...offersToShow];
let oldPinnedOffers = [...pinnedOffersToShow];
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.NEW.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.OLD.value
) {
newOffers = newOffers.sort(
(itemA, itemB) => new Date(itemA._created) - new Date(itemB._created)
);
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.POPULAR.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
...oldOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
];
}
newOffers = newOffers.slice((page - 1) * 10, page * 10);
}
return newOffers;
}, [pinnedOffersToShow, offersToShow, props.myOffers, page]);

const totalOffers = useMemo(() => {
if (props.myOffers) {
return mineOffers?.length;
}
return total;
}, [mineOffers, total]);

const handleDifferentPage = (pageNum) => {
setPage(pageNum);
};

const refetch = () => {
if (!props.myOffers) {
dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString }));
history.replace({
pathname: HOME_PAGE,
search: queryStringHook.getGlobalQueryString(),
});
} else {
dispatch(fetchMineOffers());
}
window.scrollTo({
top: 0,
behavior: "smooth",
});
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString())
setPage(parseInt(queryObject.get("page")));
} else {
setPage(1);
}
};
const offers = useOffers(props.myOffers);

const messageOneUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?._id);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`)
} else {
if (offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?._id
})
}
}
}
startChat(chats, offer, userId);
};

return (
<OffersContainer ref={offersRef}>
{allOffersToShow.map((item) => {
{offers.allOffersToShow.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} messageUser={messageOneUser} />
<OfferCard
key={item._id}
offer={item}
halfwidth={props.isGrid}
messageUser={messageOneUser}
/>
);
})}
{allOffersToShow?.length === 0 && (
<>akjshdkjhadsjkasjhkd</>
)}
{offers.allOffersToShow?.length === 0 && <>akjshdkjhadsjkasjhkd</>}
<Paging
totalElements={totalOffers}
totalElements={offers.totalOffers}
elementsPerPage={10}
current={page}
changePage={handleDifferentPage}
current={offers.page}
changePage={offers.handleDifferentPage}
/>
</OffersContainer>
);

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

@@ -9,32 +9,42 @@ import {
} from "./Paging.styled";

const Paging = (props) => {
// Determining total pages
const pages = props.pages
? props.pages
: props.totalElements
? Math.ceil(props.totalElements / props.elementsPerPage)
: 1;

let moving = 0;
// Making array of pages which contains 2 pages before and after current page
const pagesAsArray = Array.apply(null, Array(5)).map(() => {});

// Showing 3 dots if current page is away more than 3 of starting or ending page
const threeDotsBefore = props.current - 2 > 1;
const threeDotsAfter = props.current + 2 < pages;
return (
<PagingContainer>
{/* Left arrow */}
<Arrow
onClick={() => props.changePage(props.current - 1)}
disabled={props.current - 1 < 1}
>
<ArrowIcon side="left" />
</Arrow>


{threeDotsBefore && (
<React.Fragment>
<PageNumber onClick={() => props.changePage(1)}>1</PageNumber>
{props.current - 3 !== 1 && <ThreeDots>...</ThreeDots>}
</React.Fragment>
)}

{/* Pages */}
{pagesAsArray.map((item, index) => {
const pageNum = props.current - 2 + moving++;
if (pageNum > pages ) return;
if (pageNum > pages) return;
if (pageNum < 1) return;
return (
<PageNumber
@@ -46,6 +56,7 @@ const Paging = (props) => {
</PageNumber>
);
})}
{threeDotsAfter && (
<React.Fragment>
{props.current + 3 !== pages && <ThreeDots>...</ThreeDots>}
@@ -54,6 +65,8 @@ const Paging = (props) => {
</PageNumber>
</React.Fragment>
)}

{/* Right arrow */}
<Arrow
onClick={() => props.changePage(props.current + 1)}
disabled={props.current + 1 > pages}

+ 14
- 4
src/components/Popovers/HeaderPopover/HeaderPopover.js 파일 보기

@@ -13,8 +13,10 @@ import {
PopoverNoItemsText,
PopoverTitle,
} from "./HeaderPopover.styled";
import { useTranslation } from "react-i18next";

const HeaderPopover = (props) => {
const { t } = useTranslation();
return (
<HeaderPopoverContainer>
<PopoverTitle p={2}>{props.title}</PopoverTitle>
@@ -24,14 +26,22 @@ const HeaderPopover = (props) => {
<PopoverListItem key={index}>
<PopoverListItemAvatarContainer>
{props.isProfile ? (
<PopoverListItemProfileAvatar alt={item.alt} src={item.src} onClick={item?.onClick} />
<PopoverListItemProfileAvatar
alt={item.alt}
src={item.src}
onClick={item?.onClick}
/>
) : (
<PopoverListItemAvatar alt={item.alt} src={item.src} onClick={item?.onClick} />
<PopoverListItemAvatar
alt={item.alt}
src={item.src}
onClick={item?.onClick}
/>
)}
</PopoverListItemAvatarContainer>
<PopoverListItemTextContainer
primaryTypographyProps={{
onClick: item.onClick
onClick: item.onClick,
}}
primary={item.title}
secondary={item.text}
@@ -39,7 +49,7 @@ const HeaderPopover = (props) => {
</PopoverListItem>
))
) : (
<PopoverNoItemsText>No items at the moment...</PopoverNoItemsText>
<PopoverNoItemsText>{t("header.noItems")}</PopoverNoItemsText>
)}
</PopoverList>
<PopoverButtonsContainer>

+ 0
- 0
src/components/Popovers/MyMessages/MyMessages.js 파일 보기


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.

Loading…
취소
저장