| @@ -24,6 +24,7 @@ import { | |||
| ABOUT_PAGE, | |||
| ADMIN_HOME_PAGE, | |||
| ADMIN_USERS_PAGE, | |||
| ADMIN_CATEGORIES_PAGE, | |||
| // POLICY_PRIVACY_PAGE, | |||
| } from "./constants/pages"; | |||
| import LoginPage from "./pages/LoginPage/LoginPage"; | |||
| @@ -48,7 +49,6 @@ import AboutPage from "./pages/About/AboutPage"; | |||
| import AuthRoute from "./components/Router/AuthRoute"; | |||
| import AdminRoute from "./components/Router/AdminRoute"; | |||
| import AdminHomePage from "./pages/AdminHomePage/AdminHomePage"; | |||
| // import AdminUsersPage from "./pages/AdminUsersPage/AdminUsersPage"; | |||
| // import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage"; | |||
| const AppRoutes = () => { | |||
| @@ -58,7 +58,6 @@ const AppRoutes = () => { | |||
| <AuthRoute exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} /> | |||
| <AdminRoute path={ADMIN_HOME_PAGE} component={AdminHomePage} /> | |||
| {/* <AdminRoute path={ADMIN_USERS_PAGE} component={AdminUsersPage} /> */} | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <AuthRoute | |||
| @@ -0,0 +1,52 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CategoryCardContainer, | |||
| CategoryCardDetailsContainer, | |||
| CategoryCardLeftContainer, | |||
| CategoryCardRightContainer, | |||
| } from "./CategoryCard.styled"; | |||
| import CategoryCardName from "./CategoryCardName/CategoryCardName"; | |||
| import CategoryDetail from "./CategoryDetail/CategoryDetail"; | |||
| import CategoryCheckButton from "./CategoryCheckButton/CategoryCheckButton"; | |||
| import CategoryEditButton from "./CategoryEditButton/CategoryEditButton"; | |||
| import CategoryRemoveButton from "./CategoryRemoveButton/CategoryRemoveButton"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const CategoryCard = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <CategoryCardContainer> | |||
| <CategoryCardLeftContainer> | |||
| <CategoryCardName categoryName={props?.category?.name} /> | |||
| <CategoryCardDetailsContainer> | |||
| <CategoryDetail | |||
| label={t("admin.categories.noOfOffers")} | |||
| value={props?.category?.offerCount} | |||
| /> | |||
| {!props.hideSecondLabel && ( | |||
| <CategoryDetail | |||
| label={props?.secondLabel} | |||
| value={props?.category?.subcategories?.length} | |||
| /> | |||
| )} | |||
| </CategoryCardDetailsContainer> | |||
| </CategoryCardLeftContainer> | |||
| <CategoryCardRightContainer> | |||
| <CategoryRemoveButton /> | |||
| <CategoryEditButton /> | |||
| {!props.hideCheckButton && <CategoryCheckButton />} | |||
| </CategoryCardRightContainer> | |||
| </CategoryCardContainer> | |||
| ); | |||
| }; | |||
| CategoryCard.propTypes = { | |||
| children: PropTypes.node, | |||
| category: PropTypes.object, | |||
| hideCheckButton: PropTypes.bool, | |||
| secondLabel: PropTypes.string, | |||
| hideSecondLabel: PropTypes.bool, | |||
| }; | |||
| export default CategoryCard; | |||
| @@ -0,0 +1,39 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const CategoryCardContainer = styled(Box)` | |||
| background: white; | |||
| height: 84px; | |||
| width: calc(100% - 10px); | |||
| margin: 5px; | |||
| margin-top: 13px; | |||
| margin-bottom: 13px; | |||
| border: 1px solid ${selectedTheme.colors.borderNormal}; | |||
| border-radius: 4px; | |||
| display: flex; | |||
| flex-direction: row; | |||
| justify-content: space-between; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| height: 102px; | |||
| } | |||
| `; | |||
| export const CategoryCardLeftContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| `; | |||
| export const CategoryCardRightContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| `; | |||
| export const CategoryCardDetailsContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| @media (max-width: 600px) { | |||
| position: absolute; | |||
| bottom: 18px; | |||
| left: 0; | |||
| max-height: 16px; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,21 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CategoryCardNameContainer, | |||
| CategoryCardNameText, | |||
| } from "./CategoryCardName.styled"; | |||
| const CategoryCardName = (props) => { | |||
| return ( | |||
| <CategoryCardNameContainer> | |||
| <CategoryCardNameText>{props.categoryName}</CategoryCardNameText> | |||
| </CategoryCardNameContainer> | |||
| ); | |||
| }; | |||
| CategoryCardName.propTypes = { | |||
| children: PropTypes.node, | |||
| categoryName: PropTypes.string, | |||
| }; | |||
| export default CategoryCardName; | |||
| @@ -0,0 +1,21 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const CategoryCardNameContainer = styled(Box)` | |||
| padding: 18px; | |||
| min-width: 234px; | |||
| `; | |||
| export const CategoryCardNameText = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-weight: 700; | |||
| font-size: 16px; | |||
| line-height: 21px; | |||
| padding-top: 12px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| cursor: pointer; | |||
| @media (max-width: 600px) { | |||
| font-size: 18px; | |||
| padding: 0; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,25 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { CheckButton } from "./CategoryCheckButton.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const CategoryCheckButton = () => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <CheckButton | |||
| variant={"outlined"} | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor={selectedTheme.colors.primaryPurple} | |||
| style={{ fontWeight: "600" }} | |||
| > | |||
| {t("admin.categories.checkCategory")} | |||
| </CheckButton> | |||
| ); | |||
| }; | |||
| CategoryCheckButton.propTypes = { | |||
| category: PropTypes.any, | |||
| }; | |||
| export default CategoryCheckButton; | |||
| @@ -0,0 +1,17 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 224px; | |||
| height: 48px; | |||
| margin-top: 9px; | |||
| margin-right: 9px; | |||
| & button:hover { | |||
| background-color: ${selectedTheme.colors.primaryPurple} !important; | |||
| color: white !important; | |||
| } | |||
| @media (max-width: 850px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,23 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CategoryDetailContainer, | |||
| CategoryDetailLabel, | |||
| CategoryDetailValue, | |||
| } from "./CategoryDetail.styled"; | |||
| const CategoryDetail = (props) => { | |||
| return ( | |||
| <CategoryDetailContainer> | |||
| <CategoryDetailLabel>{props.label}</CategoryDetailLabel> | |||
| <CategoryDetailValue>{props.value}</CategoryDetailValue> | |||
| </CategoryDetailContainer> | |||
| ); | |||
| }; | |||
| CategoryDetail.propTypes = { | |||
| label: PropTypes.string, | |||
| value: PropTypes.string, | |||
| }; | |||
| export default CategoryDetail; | |||
| @@ -0,0 +1,53 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { hexToRGB } from "../../../../util/helpers/colorHelper"; | |||
| export const CategoryDetailContainer = styled(Box)` | |||
| /* display: flex; | |||
| flex-direction: row; */ | |||
| margin-top: 20px; | |||
| margin-bottom: 20px; | |||
| min-width: 150px; | |||
| text-align: center; | |||
| border-left: 1px solid ${hexToRGB(selectedTheme.colors.primaryText, 0.13)}; | |||
| @media (max-width: 600px) { | |||
| margin: 0; | |||
| min-width: 123px; | |||
| &:nth-child(1) { | |||
| border-left: 0; | |||
| } | |||
| &:nth-child(2) { | |||
| padding-left: 18px; | |||
| } | |||
| } | |||
| `; | |||
| export const CategoryDetailLabel = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| font-size: 12px; | |||
| letter-spacing: 0.01rem; | |||
| padding-top: 14px; | |||
| padding-right: 3px; | |||
| @media (max-width: 600px) { | |||
| position: relative; | |||
| bottom: 2.5px; | |||
| padding-top: 0; | |||
| } | |||
| `; | |||
| export const CategoryDetailValue = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-weight: 700; | |||
| font-size: 16px; | |||
| line-height: 21px; | |||
| padding-top: 11px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| @media (max-width: 600px) { | |||
| position: relative; | |||
| bottom: 2.5px; | |||
| padding: 0; | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,20 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CategoryEditButtonContainer, | |||
| EditIcon, | |||
| } from "./CategoryEditButton.styled"; | |||
| const CategoryEditButton = () => { | |||
| return ( | |||
| <CategoryEditButtonContainer> | |||
| <EditIcon /> | |||
| </CategoryEditButtonContainer> | |||
| ); | |||
| }; | |||
| CategoryEditButton.propTypes = { | |||
| category: PropTypes.any, | |||
| }; | |||
| export default CategoryEditButton; | |||
| @@ -0,0 +1,39 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { IconButton } from "../../../Buttons/IconButton/IconButton"; | |||
| import { ReactComponent as Edit } from "../../../../assets/images/svg/edit.svg"; | |||
| export const CategoryEditButtonContainer = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| background-color: ${selectedTheme.colors.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| position: relative; | |||
| top: 22px; | |||
| margin-right: 18px; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| @media (max-width: 600px) { | |||
| width: 32px; | |||
| height: 32px; | |||
| top: 18px; | |||
| right: 18px; | |||
| margin-right: 0; | |||
| padding: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: none; | |||
| `} | |||
| & button svg { | |||
| width: 16px; | |||
| height: 16px; | |||
| position: relative; | |||
| top: -3px; | |||
| left: -1.5px; | |||
| } | |||
| } | |||
| `; | |||
| export const EditIcon = styled(Edit)` | |||
| ` | |||
| @@ -0,0 +1,20 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CategoryRemoveButtonContainer, | |||
| RemoveIcon, | |||
| } from "./CategoryRemoveButton.styled"; | |||
| const CategoryRemoveButton = () => { | |||
| return ( | |||
| <CategoryRemoveButtonContainer> | |||
| <RemoveIcon /> | |||
| </CategoryRemoveButtonContainer> | |||
| ); | |||
| }; | |||
| CategoryRemoveButton.propTypes = { | |||
| category: PropTypes.any, | |||
| }; | |||
| export default CategoryRemoveButton; | |||
| @@ -0,0 +1,38 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { IconButton } from "../../../Buttons/IconButton/IconButton"; | |||
| import { ReactComponent as Remove } from "../../../../assets/images/svg/trash.svg"; | |||
| export const CategoryRemoveButtonContainer = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| background-color: ${selectedTheme.colors.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| position: relative; | |||
| top: 22px; | |||
| margin-right: 18px; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| @media (max-width: 600px) { | |||
| width: 32px; | |||
| height: 32px; | |||
| top: 18px; | |||
| right: 12px; | |||
| padding: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: none; | |||
| `} | |||
| & button svg { | |||
| width: 16px; | |||
| height: 16px; | |||
| position: relative; | |||
| top: -3px; | |||
| left: -1.5px; | |||
| } | |||
| } | |||
| `; | |||
| export const RemoveIcon = styled(Remove)` | |||
| ` | |||
| @@ -28,7 +28,7 @@ const BigProfileCard = (props) => { | |||
| const blockUser = () => {}; | |||
| return ( | |||
| <> | |||
| <ProfileCardContainer> | |||
| <ProfileCardContainer halfwidth={props.halfwidth}> | |||
| <ProfileCardWrapper variant="outlined"> | |||
| <EditButton onClick={() => setEditProfileModal(true)}> | |||
| <EditIcon /> | |||
| @@ -46,6 +46,7 @@ const BigProfileCard = (props) => { | |||
| <ProfileContact profile={props.profile} isAdmin /> | |||
| </ProfileInfoContainer> | |||
| <CheckButton | |||
| halfwidth={props.halfwidth} | |||
| variant={"outlined"} | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor={selectedTheme.colors.primaryPurple} | |||
| @@ -70,6 +71,7 @@ const BigProfileCard = (props) => { | |||
| BigProfileCard.propTypes = { | |||
| profile: PropTypes.any, | |||
| halfwidth: PropTypes.bool, | |||
| }; | |||
| export default BigProfileCard; | |||
| @@ -12,7 +12,7 @@ import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| // import { ReactComponent as Location } from "../../../assets/images/svg/location.svg"; | |||
| export const ProfileCardContainer = styled(Box)` | |||
| width: 100%; | |||
| width: ${(props) => (props.halfwidth ? `49%` : `100%`)}; | |||
| box-sizing: border-box; | |||
| max-height: 184px; | |||
| margin-top: 34px; | |||
| @@ -30,7 +30,6 @@ export const EditIcon = styled(Edit)` | |||
| } | |||
| `; | |||
| export const MessageButton = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| @@ -92,6 +91,7 @@ export const CheckButton = styled(PrimaryButton)` | |||
| position: absolute; | |||
| bottom: 25px; | |||
| right: 9px; | |||
| display: ${(props) => (props.halfwidth ? `none` : `block`)}; | |||
| & button:hover { | |||
| background-color: ${selectedTheme.colors.primaryPurple} !important; | |||
| color: white !important; | |||
| @@ -33,6 +33,7 @@ import MyMessagesButton from "./MyMessagesButton/MyMessagesButton"; | |||
| import UserButton from "./UserButton/UserButton"; | |||
| import LoginButton from "./LoginButton/LoginButton"; | |||
| import RegisterButton from "./RegisterButton/RegisterButton"; | |||
| import useIsMobile from "../../hooks/useIsMobile"; | |||
| const Header = () => { | |||
| const [showCreateOfferModal, setShowCreateOfferModal] = useState(false); | |||
| @@ -47,12 +48,13 @@ const Header = () => { | |||
| const drawerRef = useRef(null); | |||
| const [shouldShow, setShouldShow] = useState(true); | |||
| const routeMatch = useRouteMatch(); | |||
| const { isMobile } = useIsMobile(); | |||
| // Dont show header on auth routes(login, register, etc.) and admin routes | |||
| useEffect(() => { | |||
| if (isAuthRoute() || isAdminRoute()) setShouldShow(false); | |||
| if (isAuthRoute() || (isAdminRoute() && !isMobile)) setShouldShow(false); | |||
| else setShouldShow(true); | |||
| }, [routeMatch]); | |||
| }, [routeMatch, isMobile]); | |||
| // Fetch mine profile on loading home page | |||
| useEffect(() => { | |||
| @@ -76,14 +78,19 @@ const Header = () => { | |||
| } | |||
| // Handling search when user is on marketplace and when he is not | |||
| const handleSearch = (value) => { | |||
| if (!routeMatches(HOME_PAGE) && !routeMatches(BASE_PAGE)) { | |||
| const newQueryString = new URLSearchParams({ search: value }); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: newQueryString.toString(), | |||
| }); | |||
| if (isAdminRoute()) { | |||
| console.log("admin"); | |||
| search.setSearchStringManually(value); | |||
| } else { | |||
| search.searchOffersImmediately(value); | |||
| if (!routeMatches(HOME_PAGE) && !routeMatches(BASE_PAGE)) { | |||
| const newQueryString = new URLSearchParams({ search: value }); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: newQueryString.toString(), | |||
| }); | |||
| } else { | |||
| search.searchOffersImmediately(value); | |||
| } | |||
| } | |||
| }; | |||
| @@ -105,7 +112,7 @@ const Header = () => { | |||
| }; | |||
| if (!shouldShow) { | |||
| return (<></>) | |||
| return <></>; | |||
| } | |||
| return ( | |||
| @@ -180,6 +187,7 @@ const Header = () => { | |||
| Header.propTypes = { | |||
| isGrid: PropTypes.bool, | |||
| showModalHandler: PropTypes.func, | |||
| manualSearch: PropTypes.func, | |||
| }; | |||
| export default Header; | |||
| @@ -4,6 +4,7 @@ import { | |||
| ButtonContainer, | |||
| // ButtonContainer, | |||
| CategoryHeaderIcon, | |||
| CategoryIcon, | |||
| HeaderAltLocation, | |||
| HeaderButton, | |||
| HeaderButtons, | |||
| @@ -89,6 +90,7 @@ const Header = (props) => { | |||
| <> | |||
| <SkeletonHeader skeleton={props?.skeleton} myOffers={props?.myOffers} /> | |||
| <HeaderWrapperContainer | |||
| className={props.className} | |||
| skeleton={props?.skeleton} | |||
| isAdmin={props?.isAdmin} | |||
| > | |||
| @@ -129,18 +131,28 @@ const Header = (props) => { | |||
| <> | |||
| {!isMobile ? ( | |||
| <HeaderTitleContainer> | |||
| {props.users ? <UserIcon /> : <SwapsHeaderIcon />} | |||
| {props.users ? ( | |||
| <UserIcon /> | |||
| ) : props.categories ? ( | |||
| <CategoryIcon /> | |||
| ) : ( | |||
| <SwapsHeaderIcon /> | |||
| )} | |||
| <HeaderTitleText> | |||
| {props.users | |||
| ? t("admin.users.headerTitle") | |||
| : props.categories | |||
| ? t("admin.categories.headerTitle") | |||
| : t("header.myOffers")} | |||
| </HeaderTitleText> | |||
| </HeaderTitleContainer> | |||
| ) : ( | |||
| <ButtonContainer onClick={goBack}> | |||
| <ArrowButton side={"left"}></ArrowButton> | |||
| <HeaderText>{t("messages.goBack")}</HeaderText> | |||
| </ButtonContainer> | |||
| !props.hideBackButton && ( | |||
| <ButtonContainer onClick={goBack}> | |||
| <ArrowButton side={"left"}></ArrowButton> | |||
| <HeaderText>{t("messages.goBack")}</HeaderText> | |||
| </ButtonContainer> | |||
| ) | |||
| )} | |||
| </> | |||
| )} | |||
| @@ -149,33 +161,35 @@ const Header = (props) => { | |||
| {/* ^^^^^^ */} | |||
| <HeaderOptions> | |||
| <HeaderButtons> | |||
| {/* Setting display of offer cards to full width */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.iconStrokeColor | |||
| : selectedTheme.colors.primaryPurple | |||
| } | |||
| onClick={() => props?.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| {!props.hideGrid && ( | |||
| <HeaderButtons> | |||
| {/* Setting display of offer cards to full width */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.iconStrokeColor | |||
| : selectedTheme.colors.primaryPurple | |||
| } | |||
| onClick={() => props?.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.primaryPurple | |||
| : selectedTheme.colors.iconStrokeColor | |||
| } | |||
| onClick={() => props?.setIsGrid(true)} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| </HeaderButtons> | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.primaryPurple | |||
| : selectedTheme.colors.iconStrokeColor | |||
| } | |||
| onClick={() => props?.setIsGrid(true)} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| </HeaderButtons> | |||
| )} | |||
| {/* Select option to choose sorting */} | |||
| <HeaderSelect | |||
| @@ -208,9 +222,21 @@ const Header = (props) => { | |||
| </HeaderContainer> | |||
| {isMobile && ( | |||
| <PageTitleContainer> | |||
| <SwapsIcon /> | |||
| {props.users ? ( | |||
| <UserIcon /> | |||
| ) : props.categories ? ( | |||
| <CategoryIcon /> | |||
| ) : ( | |||
| <SwapsIcon /> | |||
| )} | |||
| <SwapsTitle> | |||
| {props?.myOffers ? t("header.myOffers") : t("offer.offers")} | |||
| {props.users | |||
| ? t("admin.users.headerTitle") | |||
| : props.categories | |||
| ? t("admin.categories.headerTitle") | |||
| : props?.myOffers | |||
| ? t("header.myOffers") | |||
| : t("offer.offers")} | |||
| </SwapsTitle> | |||
| </PageTitleContainer> | |||
| )} | |||
| @@ -224,12 +250,15 @@ Header.propTypes = { | |||
| setIsGrid: PropTypes.func, | |||
| isGrid: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| category: PropTypes.string, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| sorting: PropTypes.any, | |||
| isAdmin: PropTypes.bool, | |||
| users: PropTypes.bool, | |||
| categories: PropTypes.bool, | |||
| hideGrid: PropTypes.bool, | |||
| className: PropTypes.string, | |||
| hideBackButton: PropTypes.bool, | |||
| }; | |||
| Header.defaultProps = { | |||
| isGrid: false, | |||
| @@ -7,6 +7,7 @@ import Select from "../../Select/Select"; | |||
| import { ReactComponent as Swaps } from "../../../assets/images/svg/swaps.svg"; | |||
| import { ReactComponent as CategoryHeader } from "../../../assets/images/svg/category-header.svg"; | |||
| import { ReactComponent as User } from "../../../assets/images/svg/user.svg"; | |||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | |||
| export const HeaderWrapperContainer = styled(Box)` | |||
| display: ${(props) => (props.skeleton ? "none" : "block")}; | |||
| @@ -194,11 +195,23 @@ export const CategoryHeaderIcon = styled(CategoryHeader)` | |||
| top: 1px; | |||
| } | |||
| `; | |||
| export const CategoryIcon = styled(Category)` | |||
| position: relative; | |||
| top: 4px; | |||
| right: 2px; | |||
| `; | |||
| export const PageTitleContainer = styled(Box)` | |||
| position: relative; | |||
| left: 6px; | |||
| margin-top: 36px; | |||
| width: 100px; | |||
| @media (max-width: 600px) { | |||
| & svg { | |||
| width: 12px; | |||
| height: 12px; | |||
| top: 2px; | |||
| } | |||
| } | |||
| `; | |||
| export const SwapsIcon = styled(RefreshIcon)` | |||
| width: 12px; | |||
| @@ -8,14 +8,17 @@ import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import { startChat } from "../../../util/helpers/chatHelper"; | |||
| import OffersNotFound from "./OffersNotFound"; | |||
| import HeadersMyOffers from "./HeaderMyOffers.js/HeadersMyOffers"; | |||
| // import HeadersMyOffers from "./SearchBar/SearchBar"; | |||
| import SkeletonOfferCard from "../../Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard"; | |||
| import BigProfileCard from "../../Cards/ProfileCard/BigProfileCard/BigProfileCard"; | |||
| import SearchField from "../../TextFields/SearchField/SearchField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const Offers = (props) => { | |||
| const chats = useSelector(selectLatestChats); | |||
| const offersRef = useRef(null); | |||
| const userId = useSelector(selectUserId); | |||
| const { t } = useTranslation(); | |||
| const offers = props?.offers; | |||
| // For skeleton screen | |||
| const arrayForMapping = Array.apply(null, Array(4)).map(() => {}); | |||
| @@ -40,12 +43,18 @@ const Offers = (props) => { | |||
| <FilterIcon /> | |||
| </FilterContainer> | |||
| {(props?.myOffers || props?.isAdmin) && ( | |||
| <HeadersMyOffers | |||
| // <HeadersMyOffers | |||
| <SearchField | |||
| searchMyOffers={offers?.search?.searchOffers} | |||
| handleSearch={offers?.apply} | |||
| isAdmin={props?.isAdmin} | |||
| offers={offers} | |||
| isUsers={props.isUsers} | |||
| placeholder={ | |||
| props.isUsers | |||
| ? t("admin.users.searchPlaceholder") | |||
| : t("header.searchOffers") | |||
| } | |||
| /> | |||
| )} | |||
| {offers?.allOffersToShow?.length === 0 ? ( | |||
| @@ -54,7 +63,11 @@ const Offers = (props) => { | |||
| <OffersContainer ref={offersRef}> | |||
| {props.isUsers | |||
| ? props.users?.map((item) => ( | |||
| <BigProfileCard key={item._id} profile={item} /> | |||
| <BigProfileCard | |||
| key={item._id} | |||
| profile={item} | |||
| halfwidth={props?.isGrid} | |||
| /> | |||
| )) | |||
| : offers?.allOffersToShow?.map((item) => { | |||
| return ( | |||
| @@ -102,7 +115,7 @@ Offers.propTypes = { | |||
| Offers.defaultProps = { | |||
| myOffers: false, | |||
| users: [] | |||
| users: [], | |||
| }; | |||
| export default Offers; | |||
| @@ -1,13 +1,11 @@ | |||
| import React, { useCallback, useEffect, useRef } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { EndIcon, SearchIcon, SearchInput } from "./HeadersMyOffers.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import useSearch from "../../../../hooks/useOffers/useSearch"; | |||
| import { EndIcon, SearchIcon, SearchInput } from "./SearchField.styled"; | |||
| import useSearch from "../../../hooks/useOffers/useSearch"; | |||
| const HeadersMyOffers = (props) => { | |||
| const SearchField = (props) => { | |||
| const searchRef = useRef(null); | |||
| const search = useSearch(() => {}); | |||
| const { t } = useTranslation(); | |||
| let listener = useCallback( | |||
| (event) => { | |||
| // Event keycode 13 = ENTER keycode | |||
| @@ -30,7 +28,8 @@ const HeadersMyOffers = (props) => { | |||
| const handleSearch = () => { | |||
| if (props.isAdmin) { | |||
| console.log(searchRef.current.value); | |||
| search.searchOffersImmediately(searchRef.current.value); | |||
| if (props.handleSearch) props.handleSearch(searchRef.current.value); | |||
| else search.searchOffersImmediately(searchRef.current.value); | |||
| } else { | |||
| props.searchMyOffers(searchRef.current.value); | |||
| props.handleSearch(); | |||
| @@ -39,7 +38,7 @@ const HeadersMyOffers = (props) => { | |||
| return ( | |||
| <SearchInput | |||
| isAdmin={props.isAdmin} | |||
| fullWidth | |||
| fullWidth={props.fullWidth} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <EndIcon size="36px"> | |||
| @@ -47,25 +46,28 @@ const HeadersMyOffers = (props) => { | |||
| </EndIcon> | |||
| ), | |||
| }} | |||
| placeholder={ | |||
| props.isUsers | |||
| ? t("admin.users.searchPlaceholder") | |||
| : t("header.searchOffers") | |||
| } | |||
| placeholder={props.placeholder} | |||
| onFocus={handleFocusSearch} | |||
| onBlur={handleBlurSearch} | |||
| ref={searchRef} | |||
| className={props.className} | |||
| /> | |||
| ); | |||
| }; | |||
| HeadersMyOffers.propTypes = { | |||
| SearchField.propTypes = { | |||
| children: PropTypes.node, | |||
| searchMyOffers: PropTypes.func, | |||
| handleSearch: PropTypes.func, | |||
| isAdmin: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| isUsers: PropTypes.bool, | |||
| fullWidth: PropTypes.bool, | |||
| className: PropTypes.string, | |||
| placeholder: PropTypes.string, | |||
| }; | |||
| SearchField.defaultProps = { | |||
| fullWidth: true, | |||
| }; | |||
| export default HeadersMyOffers; | |||
| export default SearchField; | |||
| @@ -1,11 +1,10 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { Icon } from "../../../Icon/Icon"; | |||
| import { ReactComponent as Search } from "../../../../assets/images/svg/magnifying-glass.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { Icon } from "../../Icon/Icon"; | |||
| import { TextField } from "../TextField/TextField"; | |||
| import { ReactComponent as Search } from "../../../assets/images/svg/magnifying-glass.svg"; | |||
| export const HeadersMyOffersContainer = styled(Box)``; | |||
| export const EndIcon = styled(Icon)``; | |||
| export const SearchIcon = styled(Search)` | |||
| position: relative; | |||
| @@ -3,11 +3,33 @@ import { ReactComponent as UserIcon } from "../assets/images/svg/user-gray.svg"; | |||
| import { ReactComponent as CategoryIcon } from "../assets/images/svg/category.svg"; | |||
| import { ReactComponent as LocationIcon } from "../assets/images/svg/location.svg"; | |||
| import { ReactComponent as DollarIcon } from "../assets/images/svg/dollar-sign.svg"; | |||
| import { ADMIN_USERS_PAGE } from "./pages"; | |||
| import { | |||
| ADMIN_CATEGORIES_PAGE, | |||
| ADMIN_LOCATIONS_PAGE, | |||
| ADMIN_PAYMENT_PAGE, | |||
| ADMIN_USERS_PAGE, | |||
| } from "./pages"; | |||
| import i18n from "../i18n"; | |||
| export const ADMIN_NAVIGATION = [ | |||
| { text: "Korisnici", icon: <UserIcon />, route: `${ADMIN_USERS_PAGE}` }, | |||
| { text: "Kategorije", icon: <CategoryIcon />, route: "/admin/categoires" }, | |||
| { text: "Lokacije", icon: <LocationIcon />, route: "/admin/locations" }, | |||
| { text: "Uplate", icon: <DollarIcon />, route: "/admin/payment" }, | |||
| { | |||
| text: i18n.t("admin.navigation.users"), | |||
| icon: <UserIcon />, | |||
| route: `${ADMIN_USERS_PAGE}`, | |||
| }, | |||
| { | |||
| text: i18n.t("admin.navigation.categories"), | |||
| icon: <CategoryIcon />, | |||
| route: `${ADMIN_CATEGORIES_PAGE}`, | |||
| }, | |||
| { | |||
| text: i18n.t("admin.navigation.locations"), | |||
| icon: <LocationIcon />, | |||
| route: `${ADMIN_LOCATIONS_PAGE}`, | |||
| }, | |||
| { | |||
| text: i18n.t("admin.navigation.payment"), | |||
| icon: <DollarIcon />, | |||
| route: `${ADMIN_PAYMENT_PAGE}`, | |||
| }, | |||
| ]; | |||
| @@ -20,3 +20,6 @@ export const PRICES_PAGE = "/prices"; | |||
| export const POLICY_PRIVACY_PAGE = "/policy"; | |||
| export const ADMIN_HOME_PAGE = "/admin"; | |||
| export const ADMIN_USERS_PAGE = "/admin/users"; | |||
| export const ADMIN_CATEGORIES_PAGE = "/admin/categories"; | |||
| export const ADMIN_LOCATIONS_PAGE = "/admin/locations"; | |||
| export const ADMIN_PAYMENT_PAGE = "/admin/payment"; | |||
| @@ -3,8 +3,14 @@ import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { BASE_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { KEY_SEARCH } from "../../constants/queryStringConstants"; | |||
| import { setSearchString } from "../../store/actions/filters/filtersActions"; | |||
| import { selectSearchString } from "../../store/selectors/filtersSelectors"; | |||
| import { | |||
| setManualSearchString, | |||
| setSearchString, | |||
| } from "../../store/actions/filters/filtersActions"; | |||
| import { | |||
| selectManualSearchString, | |||
| selectSearchString, | |||
| } from "../../store/selectors/filtersSelectors"; | |||
| import { routeMatches } from "../../util/helpers/routeHelpers"; | |||
| const useSearch = (applyAllFilters) => { | |||
| @@ -13,6 +19,7 @@ const useSearch = (applyAllFilters) => { | |||
| const dispatch = useDispatch(); | |||
| const searchString = useSelector(selectSearchString); | |||
| const history = useHistory(); | |||
| const manualSearchString = useSelector(selectManualSearchString); | |||
| // On every global change of search string, new request to backend should be sent | |||
| useEffect(() => { | |||
| @@ -48,18 +55,20 @@ const useSearch = (applyAllFilters) => { | |||
| }, [searchStringLocally]); | |||
| const searchOffers = (searchValue) => { | |||
| console.log('searchoff') | |||
| setIsInitiallyLoaded(true); | |||
| setSearchStringLocally(searchValue); | |||
| }; | |||
| const searchOffersImmediately = (searchValue) => { | |||
| console.log('searchoffersimm'); | |||
| setIsInitiallyLoaded(true); | |||
| searchOffers(searchValue); | |||
| applyAllFilters(); | |||
| }; | |||
| const setSearchStringManually = (searchValue) => { | |||
| dispatch(setManualSearchString(searchValue)); | |||
| }; | |||
| const clear = () => { | |||
| setSearchStringLocally(""); | |||
| }; | |||
| @@ -67,9 +76,11 @@ const useSearch = (applyAllFilters) => { | |||
| return { | |||
| searchOffers, | |||
| setSearchStringLocally, | |||
| setSearchStringManually, | |||
| searchOffersImmediately, | |||
| searchStringLocally, | |||
| searchString, | |||
| manualSearchString, | |||
| clear, | |||
| }; | |||
| }; | |||
| @@ -432,7 +432,18 @@ export default { | |||
| navigation: { | |||
| role: "Administrator", | |||
| menu: "Meni", | |||
| users: "Korisnici", | |||
| categories: "Kategorije", | |||
| locations: "Lokacije", | |||
| payment: "Uplate", | |||
| logout: "Odjavi se", | |||
| }, | |||
| categories: { | |||
| checkCategory: "Pogledaj podkategorije", | |||
| noOfOffers: "Broj objava: ", | |||
| noOfSubcategories: "Broj potkategorija: ", | |||
| headerTitle: "Kategorije", | |||
| placeholder: "Pretražite kategorije...", | |||
| }, | |||
| }, | |||
| }; | |||
| @@ -0,0 +1,71 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useEffect } from "react"; | |||
| import { fetchCategories } from "../../store/actions/categories/categoriesActions"; | |||
| import { selectCategories } from "../../store/selectors/categoriesSelectors"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| AdminCategoriesHeader, | |||
| AdminCategoriesPageContainer, | |||
| AdminCategoriesSearchField, | |||
| } from "./AdminCategoriesPage.styled"; | |||
| import { selectManualSearchString } from "../../store/selectors/filtersSelectors"; | |||
| import { useMemo } from "react"; | |||
| import { setManualSearchString } from "../../store/actions/filters/filtersActions"; | |||
| const AdminCategoriesPage = () => { | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const categories = useSelector(selectCategories); | |||
| const manualSearchString = useSelector(selectManualSearchString); | |||
| useEffect(() => { | |||
| dispatch(fetchCategories()); | |||
| }, []); | |||
| const handleSearch = (value) => { | |||
| console.log(value); | |||
| dispatch(setManualSearchString(value)); | |||
| }; | |||
| const categoriesToShow = useMemo(() => { | |||
| if (categories) { | |||
| console.log(categories); | |||
| console.log(manualSearchString); | |||
| if (manualSearchString) | |||
| return categories.filter((item) => | |||
| item.name.toLowerCase().includes(manualSearchString.toLowerCase()) | |||
| ); | |||
| return categories; | |||
| } | |||
| return []; | |||
| }, [categories, manualSearchString]); | |||
| return ( | |||
| <AdminCategoriesPageContainer> | |||
| <AdminCategoriesSearchField | |||
| isAdmin | |||
| handleSearch={handleSearch} | |||
| placeholder={t("admin.categories.placeholder")} | |||
| /> | |||
| <AdminCategoriesHeader | |||
| myOffers | |||
| categories | |||
| hideGrid | |||
| isAdmin | |||
| hideBackButton | |||
| /> | |||
| {categoriesToShow.map((category) => ( | |||
| <CategoryCard | |||
| key={category._id} | |||
| category={category} | |||
| secondLabel={t("admin.categories.noOfSubcategories")} | |||
| /> | |||
| ))} | |||
| </AdminCategoriesPageContainer> | |||
| ); | |||
| }; | |||
| AdminCategoriesPage.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default AdminCategoriesPage; | |||
| @@ -0,0 +1,32 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import Header from "../../components/MarketPlace/Header/Header"; | |||
| import SearchField from "../../components/TextFields/SearchField/SearchField"; | |||
| export const AdminCategoriesPageContainer = styled(Box)` | |||
| padding: 15px 60px 60px 60px; | |||
| @media (max-width: 600px) { | |||
| padding: 18px; | |||
| position: relative; | |||
| top: 65px; | |||
| } | |||
| `; | |||
| export const AdminCategoriesHeader = styled(Header)` | |||
| /* top: 40px; */ | |||
| top: 0; | |||
| @media (max-width: 600px) { | |||
| top: -10px; | |||
| & div { | |||
| margin-top: 0; | |||
| } | |||
| & div div:nth-child(1) { | |||
| top: 22px; | |||
| } | |||
| } | |||
| `; | |||
| export const AdminCategoriesSearchField = styled(SearchField)` | |||
| top: -15px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| @@ -9,10 +9,15 @@ import { useSelector } from "react-redux"; | |||
| // import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| import AdminUsersPage from "../AdminUsersPage/AdminUsersPage"; | |||
| import { selectMineProfile } from "../../store/selectors/profileSelectors"; | |||
| import { Route, useHistory } from "react-router-dom"; | |||
| import { ADMIN_USERS_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { Switch, useHistory } from "react-router-dom"; | |||
| import { | |||
| ADMIN_CATEGORIES_PAGE, | |||
| ADMIN_USERS_PAGE, | |||
| HOME_PAGE, | |||
| } from "../../constants/pages"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| // import AdminRoute from "../../components/Router/AdminRoute"; | |||
| import AdminCategoriesPage from "../AdminCategoriesPage/AdminCategoriesPage"; | |||
| import AdminRoute from "../../components/Router/AdminRoute"; | |||
| const AdminHomePage = () => { | |||
| const profile = useSelector(selectMineProfile); | |||
| @@ -34,10 +39,14 @@ const AdminHomePage = () => { | |||
| leftCard={<Sidebar />} | |||
| content={ | |||
| // <MarketPlace offers={offers} isAdmin skeleton={isLoadingOffers} /> | |||
| <> | |||
| <Route path={ADMIN_USERS_PAGE} component={AdminUsersPage} /> | |||
| <Route component={AdminUsersPage} /> | |||
| </> | |||
| <Switch> | |||
| <AdminRoute path={ADMIN_USERS_PAGE} component={AdminUsersPage} /> | |||
| <AdminRoute | |||
| path={ADMIN_CATEGORIES_PAGE} | |||
| component={AdminCategoriesPage} | |||
| /> | |||
| <AdminRoute component={AdminUsersPage} /> | |||
| </Switch> | |||
| } | |||
| /> | |||
| ); | |||
| @@ -1,6 +1,6 @@ | |||
| import { createClearType, createSetType } from "../actionHelpers" | |||
| import { createClearType, createSetType } from "../actionHelpers"; | |||
| const FILTERS_SCOPE = "FILTERS" | |||
| const FILTERS_SCOPE = "FILTERS"; | |||
| export const SET_FILTERS = createSetType(FILTERS_SCOPE); | |||
| export const CLEAR_FILTERS = createClearType(FILTERS_SCOPE); | |||
| export const SET_CATEGORY = createSetType("FILTERS_SET_CATEGORY"); | |||
| @@ -11,3 +11,6 @@ export const SET_IS_APPLIED = createSetType("FILTERS_SET_IS_APPLIED"); | |||
| export const SET_QUERY_STRING = createSetType("FILTERS_SET_QUERY_STRING"); | |||
| export const SET_HEADER_STRING = createSetType("FILTERS_SET_HEADER_STRING"); | |||
| export const SET_SEARCH_STRING = createSetType("FILTERS_SET_SEARCH"); | |||
| export const SET_MANUAL_SEARCH_STRING = createSetType( | |||
| "FILTERS_SET_MANUAL_SEARCH_STRING" | |||
| ); | |||
| @@ -1,41 +1,57 @@ | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_HEADER_STRING, SET_IS_APPLIED, SET_LOCATIONS, SET_QUERY_STRING, SET_SEARCH_STRING, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| import { | |||
| CLEAR_FILTERS, | |||
| SET_CATEGORY, | |||
| SET_FILTERS, | |||
| SET_HEADER_STRING, | |||
| SET_IS_APPLIED, | |||
| SET_LOCATIONS, | |||
| SET_MANUAL_SEARCH_STRING, | |||
| SET_QUERY_STRING, | |||
| SET_SEARCH_STRING, | |||
| SET_SORT_OPTION, | |||
| SET_SUBCATEGORY, | |||
| } from "./filtersActionConstants"; | |||
| export const setFilters = (payload) => ({ | |||
| type: SET_FILTERS, | |||
| payload, | |||
| }) | |||
| type: SET_FILTERS, | |||
| payload, | |||
| }); | |||
| export const clearFilters = () => ({ | |||
| type: CLEAR_FILTERS | |||
| }) | |||
| type: CLEAR_FILTERS, | |||
| }); | |||
| export const setFilteredCategory = (payload) => ({ | |||
| type: SET_CATEGORY, | |||
| payload | |||
| }) | |||
| type: SET_CATEGORY, | |||
| payload, | |||
| }); | |||
| export const setFilteredSubcategory = (payload) => ({ | |||
| type: SET_SUBCATEGORY, | |||
| payload | |||
| }) | |||
| type: SET_SUBCATEGORY, | |||
| payload, | |||
| }); | |||
| export const setManualSearchString = (payload) => ({ | |||
| type: SET_MANUAL_SEARCH_STRING, | |||
| payload, | |||
| }); | |||
| export const setFilteredLocations = (payload) => ({ | |||
| type: SET_LOCATIONS, | |||
| payload | |||
| }) | |||
| type: SET_LOCATIONS, | |||
| payload, | |||
| }); | |||
| export const setFilteredSortOption = (payload) => ({ | |||
| type: SET_SORT_OPTION, | |||
| payload | |||
| }) | |||
| type: SET_SORT_OPTION, | |||
| payload, | |||
| }); | |||
| export const setIsAppliedStatus = (payload) => ({ | |||
| type: SET_IS_APPLIED, | |||
| payload, | |||
| }) | |||
| type: SET_IS_APPLIED, | |||
| payload, | |||
| }); | |||
| export const setQueryString = (payload) => ({ | |||
| type: SET_QUERY_STRING, | |||
| payload, | |||
| }) | |||
| type: SET_QUERY_STRING, | |||
| payload, | |||
| }); | |||
| export const setHeaderString = (payload) => ({ | |||
| type: SET_HEADER_STRING, | |||
| payload | |||
| }) | |||
| type: SET_HEADER_STRING, | |||
| payload, | |||
| }); | |||
| export const setSearchString = (payload) => ({ | |||
| type: SET_SEARCH_STRING, | |||
| payload | |||
| }) | |||
| type: SET_SEARCH_STRING, | |||
| payload, | |||
| }); | |||
| @@ -5,6 +5,7 @@ import { | |||
| SET_HEADER_STRING, | |||
| SET_IS_APPLIED, | |||
| SET_LOCATIONS, | |||
| SET_MANUAL_SEARCH_STRING, | |||
| SET_SEARCH_STRING, | |||
| SET_SORT_OPTION, | |||
| SET_SUBCATEGORY, | |||
| @@ -23,9 +24,10 @@ const initialState = { | |||
| categoryString: "", | |||
| subcategoryString: "", | |||
| locationsString: "", | |||
| text: "" | |||
| text: "", | |||
| }, | |||
| searchString: "" | |||
| searchString: "", | |||
| manualSearchString: "", | |||
| }, | |||
| }; | |||
| @@ -40,10 +42,21 @@ export default createReducer( | |||
| [SET_IS_APPLIED]: setIsAppliedStatus, | |||
| [SET_HEADER_STRING]: setHeaderString, | |||
| [SET_SEARCH_STRING]: setSearchString, | |||
| [SET_MANUAL_SEARCH_STRING]: setManualSearchString, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setManualSearchString(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| manualSearchString: payload, | |||
| }, | |||
| }; | |||
| } | |||
| function setFilters(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| @@ -84,39 +97,39 @@ function setFilteredLocations(state, { payload }) { | |||
| }, | |||
| }; | |||
| } | |||
| function setFilteredSortOption(state, {payload}) { | |||
| function setFilteredSortOption(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| sortOption: payload, | |||
| } | |||
| } | |||
| }, | |||
| }; | |||
| } | |||
| function setIsAppliedStatus(state, {payload}) { | |||
| function setIsAppliedStatus(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| isApplied: payload, | |||
| } | |||
| } | |||
| }, | |||
| }; | |||
| } | |||
| function setHeaderString(state, {payload}) { | |||
| function setHeaderString(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| headerString: payload | |||
| } | |||
| } | |||
| headerString: payload, | |||
| }, | |||
| }; | |||
| } | |||
| function setSearchString(state, {payload}) { | |||
| function setSearchString(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| searchString: payload | |||
| } | |||
| } | |||
| searchString: payload, | |||
| }, | |||
| }; | |||
| } | |||
| @@ -1,36 +1,40 @@ | |||
| import { createSelector } from 'reselect'; | |||
| import { createSelector } from "reselect"; | |||
| const filtersSelector = (state) => state.filters; | |||
| export const selectFilters = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters, | |||
| (state) => state.filters | |||
| ); | |||
| export const selectSelectedCategory = createSelector( | |||
| filtersSelector, | |||
| (state => state.filters.category) | |||
| ) | |||
| (state) => state.filters.category | |||
| ); | |||
| export const selectSelectedSubcategory = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.subcategory | |||
| ) | |||
| ); | |||
| export const selectSelectedLocations = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.locations | |||
| ) | |||
| ); | |||
| export const selectSelectedSortOption = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.sortOption | |||
| ) | |||
| ); | |||
| export const selectAppliedStatus = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.isApplied | |||
| ) | |||
| ); | |||
| export const selectHeaderString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.headerString | |||
| ) | |||
| ); | |||
| export const selectSearchString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.searchString, | |||
| ) | |||
| (state) => state.filters.searchString | |||
| ); | |||
| export const selectManualSearchString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.manualSearchString | |||
| ); | |||
| @@ -0,0 +1,11 @@ | |||
| export const hexToRGB = (hex, opacity) => { | |||
| var r = parseInt(hex.slice(1, 3), 16), | |||
| g = parseInt(hex.slice(3, 5), 16), | |||
| b = parseInt(hex.slice(5, 7), 16); | |||
| if (opacity) { | |||
| return `rgba(${r}, ${g}, ${b}, ${opacity})`; | |||
| } else { | |||
| return `rgb(${r}, ${g}, ${b})`; | |||
| } | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| import { | |||
| ADMIN_CATEGORIES_PAGE, | |||
| ADMIN_HOME_PAGE, | |||
| ADMIN_LOGIN_PAGE, | |||
| ADMIN_USERS_PAGE, | |||
| @@ -46,7 +47,8 @@ export const isAdminRoute = () => { | |||
| if ( | |||
| routeMatches(ADMIN_LOGIN_PAGE) || | |||
| routeMatches(ADMIN_HOME_PAGE) || | |||
| routeMatches(ADMIN_USERS_PAGE) | |||
| routeMatches(ADMIN_USERS_PAGE) || | |||
| routeMatches(ADMIN_CATEGORIES_PAGE) | |||
| ) | |||
| return true; | |||
| return false; | |||