Przeglądaj źródła

Partly finished feature 657

feature/657
djordjemitrovic00 3 lat temu
rodzic
commit
d796448a90

+ 11
- 1
src/AppRoutes.js Wyświetl plik

@@ -25,6 +25,7 @@ import {
ADMIN_HOME_PAGE,
ADMIN_USERS_PAGE,
ADMIN_CATEGORIES_PAGE,
ADMIN_SUBCATEGORIES_PAGE,
// POLICY_PRIVACY_PAGE,
} from "./constants/pages";
import LoginPage from "./pages/LoginPage/LoginPage";
@@ -50,6 +51,7 @@ import AuthRoute from "./components/Router/AuthRoute";
import AdminHomePage from "./pages/AdminHomePage/AdminHomePage";
import AdminUsersPage from "./pages/AdminUsersPage/AdminUsersPage";
import AdminCategoriesPage from "./pages/AdminCategoriesPage/AdminCategoriesPage";
import AdminSubcategoriesPage from "./pages/AdminSubcategoriesPage/AdminSubcategoriesPage";
// import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage";

const AppRoutes = () => {
@@ -60,7 +62,15 @@ const AppRoutes = () => {
<AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} />
<Route path={ADMIN_HOME_PAGE} component={AdminHomePage} />
<Route path={ADMIN_USERS_PAGE} component={AdminUsersPage} />
<Route path={ADMIN_CATEGORIES_PAGE} component={AdminCategoriesPage} />
<Route
path={ADMIN_SUBCATEGORIES_PAGE}
component={AdminSubcategoriesPage}
/>
<Route
exact
path={ADMIN_CATEGORIES_PAGE}
component={AdminCategoriesPage}
/>
<Route path={NOT_FOUND_PAGE} component={NotFoundPage} />
<Route path={ERROR_PAGE} component={ErrorPage} />
<AuthRoute

+ 32
- 20
src/components/Cards/CategoryCard/CategoryCard.js Wyświetl plik

@@ -12,32 +12,43 @@ import CategoryCheckButton from "./CategoryCheckButton/CategoryCheckButton";
import CategoryEditButton from "./CategoryEditButton/CategoryEditButton";
import CategoryRemoveButton from "./CategoryRemoveButton/CategoryRemoveButton";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import DeleteCategory from "../../Modals/DeleteCategory/DeleteCategory";

const CategoryCard = (props) => {
const { t } = useTranslation();
const [openedDeleteModal, setOpenedDeleteModal] = useState(false);
return (
<CategoryCardContainer>
<CategoryCardLeftContainer>
<CategoryCardName categoryName={props?.category?.name} />
<CategoryCardDetailsContainer>
<CategoryDetail
label={t("admin.categories.noOfOffers")}
value={props?.category?.offerCount}
/>
{!props.hideSecondLabel && (
<>
<CategoryCardContainer className={props.className}>
<CategoryCardLeftContainer>
<CategoryCardName categoryName={props?.category?.name} />
<CategoryCardDetailsContainer>
<CategoryDetail
label={props?.secondLabel}
value={props?.category?.subcategories?.length}
label={t("admin.categories.noOfOffers")}
value={props?.category?.offerCount}
/>
)}
</CategoryCardDetailsContainer>
</CategoryCardLeftContainer>
<CategoryCardRightContainer>
<CategoryRemoveButton />
<CategoryEditButton />
{!props.hideCheckButton && <CategoryCheckButton />}
</CategoryCardRightContainer>
</CategoryCardContainer>
{!props.hideSecondLabel && (
<CategoryDetail
label={props?.secondLabel}
value={props?.category?.subcategories?.length}
/>
)}
</CategoryCardDetailsContainer>
</CategoryCardLeftContainer>
<CategoryCardRightContainer>
<CategoryRemoveButton onClick={() => setOpenedDeleteModal(true)} />
<CategoryEditButton />
{!props.hideCheckButton && <CategoryCheckButton />}
</CategoryCardRightContainer>
</CategoryCardContainer>
{openedDeleteModal && (
<DeleteCategory
setOpenedDeleteModal={setOpenedDeleteModal}
category={props.category}
/>
)}
</>
);
};

@@ -47,6 +58,7 @@ CategoryCard.propTypes = {
hideCheckButton: PropTypes.bool,
secondLabel: PropTypes.string,
hideSecondLabel: PropTypes.bool,
className: PropTypes.string,
};

export default CategoryCard;

+ 3
- 2
src/components/Cards/CategoryCard/CategoryRemoveButton/CategoryRemoveButton.js Wyświetl plik

@@ -5,9 +5,9 @@ import {
RemoveIcon,
} from "./CategoryRemoveButton.styled";

const CategoryRemoveButton = () => {
const CategoryRemoveButton = (props) => {
return (
<CategoryRemoveButtonContainer>
<CategoryRemoveButtonContainer onClick={props.onClick}>
<RemoveIcon />
</CategoryRemoveButtonContainer>
);
@@ -15,6 +15,7 @@ const CategoryRemoveButton = () => {

CategoryRemoveButton.propTypes = {
category: PropTypes.any,
onClick: PropTypes.func,
};

export default CategoryRemoveButton;

+ 26
- 0
src/components/Cards/LabeledCard/LabeledCard.js Wyświetl plik

@@ -0,0 +1,26 @@
import React from "react";
import PropTypes from "prop-types";
import {
LabeledCardContainer,
LabeledCardIconContainer,
} from "./LabeledCard.styled";

const LabeledCard = (props) => {
return (
<LabeledCardContainer>
<LabeledCardIconContainer width={props.width} height={props.height}>
{props.icon}
</LabeledCardIconContainer>
{props.children}
</LabeledCardContainer>
);
};

LabeledCard.propTypes = {
children: PropTypes.node,
icon: PropTypes.node,
width: PropTypes.string,
height: PropTypes.string,
};

export default LabeledCard;

+ 21
- 0
src/components/Cards/LabeledCard/LabeledCard.styled.js Wyświetl plik

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

export const LabeledCardContainer = styled(Box)`
background: ${selectedTheme.colors.chatHeaderColor};
border-radius: 2px;
border: 1px solid ${selectedTheme.colors.borderNormal};
position: relative;
width: ${(props) => props.width || `min-content`};
height: ${(props) => props.height || `57px`};
padding: 18px;
`;
export const LabeledCardIconContainer = styled(Box)`
width: 40px;
height: 40px;
background: ${selectedTheme.colors.primaryPurple};
position: absolute;
top: -20px;
right: -20px;
border-radius: 100%;
`;

+ 1
- 1
src/components/Header/Header.js Wyświetl plik

@@ -52,7 +52,7 @@ const Header = () => {

// Dont show header on auth routes(login, register, etc.) and admin routes
useEffect(() => {
if (isAuthRoute() || (isAdminRoute && !isMobile)) setShouldShow(false);
if (isAuthRoute() || (isAdminRoute() && !isMobile)) setShouldShow(false);
else setShouldShow(true);
}, [routeMatch, isMobile]);


+ 45
- 27
src/components/MarketPlace/Header/Header.js Wyświetl plik

@@ -24,6 +24,7 @@ import {
IconStyled,
PageTitleContainer,
SelectOption,
SubcategoryIcon,
SwapsHeaderIcon,
SwapsIcon,
SwapsTitle,
@@ -133,8 +134,10 @@ const Header = (props) => {
<HeaderTitleContainer>
{props.users ? (
<UserIcon />
) : props.categories ? (
) : props.categories || props.category ? (
<CategoryIcon />
) : props.subcategories ? (
<SubcategoryIcon />
) : (
<SwapsHeaderIcon />
)}
@@ -143,6 +146,10 @@ const Header = (props) => {
? t("admin.users.headerTitle")
: props.categories
? t("admin.categories.headerTitle")
: props.subcategories
? t("admin.subcategories.subcategoriesHeaderTitle")
: props.category
? t("admin.subcategories.headerTitle")
: t("header.myOffers")}
</HeaderTitleText>
</HeaderTitleContainer>
@@ -192,31 +199,33 @@ const Header = (props) => {
)}

{/* Select option to choose sorting */}
<HeaderSelect
value={
sorting?.selectedSortOption?.value
? sorting?.selectedSortOption
: "default"
}
IconComponent={DownArrow}
onChange={handleChangeSelect}
myOffers={props?.myOffers}
>
<SelectOption style={{ display: "none" }} value="default">
{t("reviews.sortBy")}
</SelectOption>
{Object.keys(sortEnum).map((property) => {
if (sortEnum[property].value === 0) return;
return (
<SelectOption
value={sortEnum[property]}
key={sortEnum[property].value}
>
{sortEnum[property].mainText}
</SelectOption>
);
})}
</HeaderSelect>
{!props.hideSorting && (
<HeaderSelect
value={
sorting?.selectedSortOption?.value
? sorting?.selectedSortOption
: "default"
}
IconComponent={DownArrow}
onChange={handleChangeSelect}
myOffers={props?.myOffers}
>
<SelectOption style={{ display: "none" }} value="default">
{t("reviews.sortBy")}
</SelectOption>
{Object.keys(sortEnum).map((property) => {
if (sortEnum[property].value === 0) return;
return (
<SelectOption
value={sortEnum[property]}
key={sortEnum[property].value}
>
{sortEnum[property].mainText}
</SelectOption>
);
})}
</HeaderSelect>
)}
{/* ^^^^^^ */}
</HeaderOptions>
</HeaderContainer>
@@ -224,8 +233,10 @@ const Header = (props) => {
<PageTitleContainer>
{props.users ? (
<UserIcon />
) : props.categories ? (
) : props.categories || props.category ? (
<CategoryIcon />
) : props.subcategories ? (
<SubcategoryIcon />
) : (
<SwapsIcon />
)}
@@ -234,6 +245,10 @@ const Header = (props) => {
? t("admin.users.headerTitle")
: props.categories
? t("admin.categories.headerTitle")
: props.subcategories
? t("admin.subcategories.subcategoriesHeaderTitle")
: props.category
? t("admin.subcategories.headerTitle")
: props?.myOffers
? t("header.myOffers")
: t("offer.offers")}
@@ -259,6 +274,9 @@ Header.propTypes = {
hideGrid: PropTypes.bool,
className: PropTypes.string,
hideBackButton: PropTypes.bool,
category: PropTypes.bool,
subcategories: PropTypes.bool,
hideSorting: PropTypes.bool,
};
Header.defaultProps = {
isGrid: false,

+ 7
- 0
src/components/MarketPlace/Header/Header.styled.js Wyświetl plik

@@ -8,6 +8,7 @@ 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";
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";

export const HeaderWrapperContainer = styled(Box)`
display: ${(props) => (props.skeleton ? "none" : "block")};
@@ -200,6 +201,11 @@ export const CategoryIcon = styled(Category)`
top: 4px;
right: 2px;
`;
export const SubcategoryIcon = styled(Subcategory)`
position: relative;
top: 4px;
right: 2px;
`;
export const PageTitleContainer = styled(Box)`
position: relative;
left: 6px;
@@ -258,6 +264,7 @@ export const HeaderTitleText = styled(Typography)`
font-size: 16px;
position: relative;
bottom: 2px;
left: 2px;
`;
export const UserIcon = styled(User)`
position: relative;

+ 58
- 0
src/components/Modals/DeleteCategory/DeleteCategory.js Wyświetl plik

@@ -0,0 +1,58 @@
import React from "react";
import PropTypes from "prop-types";
import BackdropComponent from "../../MUI/BackdropComponent";
import {
ButtonsContainer,
CategoryName,
DeleteCategoryContainer,
DeleteIcon,
ReassuranceText,
} from "./DeleteCategory.styled";
import LabeledCard from "../../Cards/LabeledCard/LabeledCard";
import { useTranslation } from "react-i18next";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";

const DeleteCategory = (props) => {
const { t } = useTranslation();
console.log(props.category);
const handleCancel = () => {
props.setOpenedDeleteModal(false);
};
return (
<>
<BackdropComponent
isLoading
handleClose={() => props.setOpenedDeleteModal(false)}
position="fixed"
/>
<DeleteCategoryContainer>
<LabeledCard icon={<DeleteIcon />}>
<CategoryName>{props.category.name}</CategoryName>
</LabeledCard>
<ReassuranceText>
{t("admin.categories.reassuranceDelete")}
</ReassuranceText>
<ButtonsContainer>
<PrimaryButton
onClick={handleCancel}
variant="contained"
height="49px"
width="180px"
>
{t("admin.categories.cancel")}
</PrimaryButton>
<PrimaryButton variant="outlined" height="49px" width="180px">
{t("admin.categories.delete")}
</PrimaryButton>
</ButtonsContainer>
</DeleteCategoryContainer>
</>
);
};

DeleteCategory.propTypes = {
setOpenedDeleteModal: PropTypes.func,
category: PropTypes.object,
};

export default DeleteCategory;

+ 55
- 0
src/components/Modals/DeleteCategory/DeleteCategory.styled.js Wyświetl plik

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

export const DeleteCategoryContainer = styled(Box)`
width: 537px;
height: 274px;
position: fixed;
z-index: 150;
left: calc(50vw - 268px);
top: calc(50vh - 137px);
padding-top: 36px;
padding-bottom: 18px;
background: white;
& > * {
margin-left: auto;
margin-right: auto;
}
`;
export const DeleteIcon = styled(Delete)`
z-index: 155;
position: relative;
top: 10px;
left: 11px;
& path {
stroke: ${selectedTheme.colors.iconYellowColor};
}
`;
export const CategoryName = styled(Typography)`
font-family: ${selectedTheme.fonts.textFont};
font-weight: 700;
font-size: 16px;
white-space: nowrap;
line-height: 21px;
color: ${selectedTheme.colors.primaryPurple};
`;
export const ReassuranceText = styled(Typography)`
font-family: ${selectedTheme.fonts.textFont};
width: 312px;
font-weight: 700;
text-align: center;
display: block;
font-size: 16px;
line-height: 21px;
color: ${selectedTheme.colors.messageText};
margin-top: 36px;
`;
export const ButtonsContainer = styled(Box)`
display: flex;
justify-content: center;
flex-direction: row;
gap: 18px;
margin: 36px auto;
`

+ 1
- 0
src/constants/pages.js Wyświetl plik

@@ -21,3 +21,4 @@ export const POLICY_PRIVACY_PAGE = "/policy";
export const ADMIN_HOME_PAGE = "/admin/home";
export const ADMIN_USERS_PAGE = "/admin/users";
export const ADMIN_CATEGORIES_PAGE = "/admin/categories";
export const ADMIN_SUBCATEGORIES_PAGE = "/admin/categories/:categoryId";

+ 13
- 3
src/i18n/resources/rs.js Wyświetl plik

@@ -427,14 +427,24 @@ export default {
users: {
headerTitle: "Profili korisnika",
searchPlaceholder: "Pretražite korisnike....",
checkProfile: "Pogledaj profil"
checkProfile: "Pogledaj profil",
},
categories: {
checkCategory: "Pogledaj podkategorije",
noOfOffers: "Broj objava: ",
noOfSubcategories: "Broj potkategorija: ",
headerTitle: "Kategorije",
placeholder: "Pretražite kategorije..."
}
placeholder: "Pretražite kategorije...",
reassuranceDelete:
"Da li ste sigurni da želite da obrišete odabranu kategoriju?",
cancel: "Otkaži",
delete: "Obriši",
},
subcategories: {
noOfOffers: "Broj objava: ",
headerTitle: "Kategorija",
subcategoriesHeaderTitle: "Podkategorije",
placeholder: "Pretražite podkategorije...",
},
},
};

+ 98
- 0
src/pages/AdminSubcategoriesPage/AdminSubcategoriesPage.js Wyświetl plik

@@ -0,0 +1,98 @@
import React from "react";
import PropTypes from "prop-types";
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 {
AdminSubcategoriesHeader,
AdminSubcategoriesPageContainer,
AdminSubcategoriesSearchField,
SponsoredCategoryCard,
} from "./AdminSubcategoriesPage.styled";
import { selectManualSearchString } from "../../store/selectors/filtersSelectors";
import { useMemo } from "react";
import { setManualSearchString } from "../../store/actions/filters/filtersActions";
import { useRouteMatch } from "react-router-dom";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard";

const AdminSubcategoriesPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const categories = useSelector(selectCategories);
const routeMatch = useRouteMatch();
const manualSearchString = useSelector(selectManualSearchString);
useEffect(() => {
dispatch(fetchCategories());
}, []);
const handleSearch = (value) => {
console.log(value);
dispatch(setManualSearchString(value));
};
const category = useMemo(() => {
if (routeMatch.params?.categoryId) {
const categoryId = routeMatch.params.categoryId;
return categories.find((item) => item._id === categoryId);
}
return {};
}, [routeMatch, categories]);
const subcategories = useMemo(() => {
if (category) {
if (manualSearchString)
return category.subcategories.filter((subcategory) =>
subcategory.name
.toLowerCase()
.includes(manualSearchString.toLowerCase())
);
return category.subcategories;
}
return [];
}, [category, manualSearchString]);
return (
<>
<AdminSubcategoriesPageContainer>
<AdminSubcategoriesSearchField
isAdmin
handleSearch={handleSearch}
placeholder={t("admin.subcategories.placeholder")}
/>
<AdminSubcategoriesHeader
hideSorting
myOffers
category
hideGrid
isAdmin
hideBackButton
/>
<SponsoredCategoryCard
category={category}
hideSecondLabel
hideCheckButton
/>
<AdminSubcategoriesHeader
hideSorting
myOffers
subcategories
hideGrid
isAdmin
hideBackButton
/>
{subcategories.map((category) => (
<CategoryCard
key={category._id}
category={category}
hideSecondLabel
hideCheckButton
/>
))}
</AdminSubcategoriesPageContainer>
</>
);
};

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

export default AdminSubcategoriesPage;

+ 39
- 0
src/pages/AdminSubcategoriesPage/AdminSubcategoriesPage.styled.js Wyświetl plik

@@ -0,0 +1,39 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard";
import Header from "../../components/MarketPlace/Header/Header";
import SearchField from "../../components/TextFields/SearchField/SearchField";
import selectedTheme from "../../themes";

export const AdminSubcategoriesPageContainer = styled(Box)`
padding: 60px;
@media (max-width: 600px) {
padding: 18px;
position: relative;
top: 65px;
}
`;
export const AdminSubcategoriesHeader = styled(Header)`
/* top: 40px; */
top: 0;
@media (max-width: 600px) {
top: -10px;
margin-top: 18px;
& div {
margin-top: 10px;
}
& div div:nth-child(1) {
top: 22px;
}
}
`;
export const AdminSubcategoriesSearchField = styled(SearchField)`
top: -15px;
@media (max-width: 600px) {
display: none;
}
`;
export const SponsoredCategoryCard = styled(CategoryCard)`
background: ${selectedTheme.colors.backgroundSponsoredColor};
border: 1px solid ${selectedTheme.colors.borderSponsoredColor};
`;

+ 3
- 1
src/util/helpers/routeHelpers.js Wyświetl plik

@@ -2,6 +2,7 @@ import {
ADMIN_CATEGORIES_PAGE,
ADMIN_HOME_PAGE,
ADMIN_LOGIN_PAGE,
ADMIN_SUBCATEGORIES_PAGE,
ADMIN_USERS_PAGE,
FORGOT_PASSWORD_MAIL_SENT,
FORGOT_PASSWORD_PAGE,
@@ -48,7 +49,8 @@ export const isAdminRoute = () => {
routeMatches(ADMIN_LOGIN_PAGE) ||
routeMatches(ADMIN_HOME_PAGE) ||
routeMatches(ADMIN_USERS_PAGE) ||
routeMatches(ADMIN_CATEGORIES_PAGE)
routeMatches(ADMIN_CATEGORIES_PAGE) ||
dynamicRouteMatches(ADMIN_SUBCATEGORIES_PAGE)
)
return true;
return false;

Ładowanie…
Anuluj
Zapisz