ソースを参照

Partly finished feature 657

feature/657
djordjemitrovic00 3年前
コミット
d796448a90

+ 11
- 1
src/AppRoutes.js ファイルの表示

ADMIN_HOME_PAGE, ADMIN_HOME_PAGE,
ADMIN_USERS_PAGE, ADMIN_USERS_PAGE,
ADMIN_CATEGORIES_PAGE, ADMIN_CATEGORIES_PAGE,
ADMIN_SUBCATEGORIES_PAGE,
// POLICY_PRIVACY_PAGE, // POLICY_PRIVACY_PAGE,
} from "./constants/pages"; } from "./constants/pages";
import LoginPage from "./pages/LoginPage/LoginPage"; import LoginPage from "./pages/LoginPage/LoginPage";
import AdminHomePage from "./pages/AdminHomePage/AdminHomePage"; import AdminHomePage from "./pages/AdminHomePage/AdminHomePage";
import AdminUsersPage from "./pages/AdminUsersPage/AdminUsersPage"; import AdminUsersPage from "./pages/AdminUsersPage/AdminUsersPage";
import AdminCategoriesPage from "./pages/AdminCategoriesPage/AdminCategoriesPage"; import AdminCategoriesPage from "./pages/AdminCategoriesPage/AdminCategoriesPage";
import AdminSubcategoriesPage from "./pages/AdminSubcategoriesPage/AdminSubcategoriesPage";
// import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage"; // import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage";


const AppRoutes = () => { const AppRoutes = () => {
<AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} /> <AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} />
<Route path={ADMIN_HOME_PAGE} component={AdminHomePage} /> <Route path={ADMIN_HOME_PAGE} component={AdminHomePage} />
<Route path={ADMIN_USERS_PAGE} component={AdminUsersPage} /> <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={NOT_FOUND_PAGE} component={NotFoundPage} />
<Route path={ERROR_PAGE} component={ErrorPage} /> <Route path={ERROR_PAGE} component={ErrorPage} />
<AuthRoute <AuthRoute

+ 32
- 20
src/components/Cards/CategoryCard/CategoryCard.js ファイルの表示

import CategoryEditButton from "./CategoryEditButton/CategoryEditButton"; import CategoryEditButton from "./CategoryEditButton/CategoryEditButton";
import CategoryRemoveButton from "./CategoryRemoveButton/CategoryRemoveButton"; import CategoryRemoveButton from "./CategoryRemoveButton/CategoryRemoveButton";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState } from "react";
import DeleteCategory from "../../Modals/DeleteCategory/DeleteCategory";


const CategoryCard = (props) => { const CategoryCard = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [openedDeleteModal, setOpenedDeleteModal] = useState(false);
return ( 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 <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}
/>
)}
</>
); );
}; };


hideCheckButton: PropTypes.bool, hideCheckButton: PropTypes.bool,
secondLabel: PropTypes.string, secondLabel: PropTypes.string,
hideSecondLabel: PropTypes.bool, hideSecondLabel: PropTypes.bool,
className: PropTypes.string,
}; };


export default CategoryCard; export default CategoryCard;

+ 3
- 2
src/components/Cards/CategoryCard/CategoryRemoveButton/CategoryRemoveButton.js ファイルの表示

RemoveIcon, RemoveIcon,
} from "./CategoryRemoveButton.styled"; } from "./CategoryRemoveButton.styled";


const CategoryRemoveButton = () => {
const CategoryRemoveButton = (props) => {
return ( return (
<CategoryRemoveButtonContainer>
<CategoryRemoveButtonContainer onClick={props.onClick}>
<RemoveIcon /> <RemoveIcon />
</CategoryRemoveButtonContainer> </CategoryRemoveButtonContainer>
); );


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


export default CategoryRemoveButton; export default CategoryRemoveButton;

+ 26
- 0
src/components/Cards/LabeledCard/LabeledCard.js ファイルの表示

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 ファイルの表示

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 ファイルの表示



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



+ 45
- 27
src/components/MarketPlace/Header/Header.js ファイルの表示

IconStyled, IconStyled,
PageTitleContainer, PageTitleContainer,
SelectOption, SelectOption,
SubcategoryIcon,
SwapsHeaderIcon, SwapsHeaderIcon,
SwapsIcon, SwapsIcon,
SwapsTitle, SwapsTitle,
<HeaderTitleContainer> <HeaderTitleContainer>
{props.users ? ( {props.users ? (
<UserIcon /> <UserIcon />
) : props.categories ? (
) : props.categories || props.category ? (
<CategoryIcon /> <CategoryIcon />
) : props.subcategories ? (
<SubcategoryIcon />
) : ( ) : (
<SwapsHeaderIcon /> <SwapsHeaderIcon />
)} )}
? t("admin.users.headerTitle") ? t("admin.users.headerTitle")
: props.categories : props.categories
? t("admin.categories.headerTitle") ? t("admin.categories.headerTitle")
: props.subcategories
? t("admin.subcategories.subcategoriesHeaderTitle")
: props.category
? t("admin.subcategories.headerTitle")
: t("header.myOffers")} : t("header.myOffers")}
</HeaderTitleText> </HeaderTitleText>
</HeaderTitleContainer> </HeaderTitleContainer>
)} )}


{/* Select option to choose sorting */} {/* 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> </HeaderOptions>
</HeaderContainer> </HeaderContainer>
<PageTitleContainer> <PageTitleContainer>
{props.users ? ( {props.users ? (
<UserIcon /> <UserIcon />
) : props.categories ? (
) : props.categories || props.category ? (
<CategoryIcon /> <CategoryIcon />
) : props.subcategories ? (
<SubcategoryIcon />
) : ( ) : (
<SwapsIcon /> <SwapsIcon />
)} )}
? t("admin.users.headerTitle") ? t("admin.users.headerTitle")
: props.categories : props.categories
? t("admin.categories.headerTitle") ? t("admin.categories.headerTitle")
: props.subcategories
? t("admin.subcategories.subcategoriesHeaderTitle")
: props.category
? t("admin.subcategories.headerTitle")
: props?.myOffers : props?.myOffers
? t("header.myOffers") ? t("header.myOffers")
: t("offer.offers")} : t("offer.offers")}
hideGrid: PropTypes.bool, hideGrid: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
hideBackButton: PropTypes.bool, hideBackButton: PropTypes.bool,
category: PropTypes.bool,
subcategories: PropTypes.bool,
hideSorting: PropTypes.bool,
}; };
Header.defaultProps = { Header.defaultProps = {
isGrid: false, isGrid: false,

+ 7
- 0
src/components/MarketPlace/Header/Header.styled.js ファイルの表示

import { ReactComponent as CategoryHeader } from "../../../assets/images/svg/category-header.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 User } from "../../../assets/images/svg/user.svg";
import { ReactComponent as Category } from "../../../assets/images/svg/category.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)` export const HeaderWrapperContainer = styled(Box)`
display: ${(props) => (props.skeleton ? "none" : "block")}; display: ${(props) => (props.skeleton ? "none" : "block")};
top: 4px; top: 4px;
right: 2px; right: 2px;
`; `;
export const SubcategoryIcon = styled(Subcategory)`
position: relative;
top: 4px;
right: 2px;
`;
export const PageTitleContainer = styled(Box)` export const PageTitleContainer = styled(Box)`
position: relative; position: relative;
left: 6px; left: 6px;
font-size: 16px; font-size: 16px;
position: relative; position: relative;
bottom: 2px; bottom: 2px;
left: 2px;
`; `;
export const UserIcon = styled(User)` export const UserIcon = styled(User)`
position: relative; position: relative;

+ 58
- 0
src/components/Modals/DeleteCategory/DeleteCategory.js ファイルの表示

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 ファイルの表示

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 ファイルの表示

export const ADMIN_HOME_PAGE = "/admin/home"; export const ADMIN_HOME_PAGE = "/admin/home";
export const ADMIN_USERS_PAGE = "/admin/users"; export const ADMIN_USERS_PAGE = "/admin/users";
export const ADMIN_CATEGORIES_PAGE = "/admin/categories"; export const ADMIN_CATEGORIES_PAGE = "/admin/categories";
export const ADMIN_SUBCATEGORIES_PAGE = "/admin/categories/:categoryId";

+ 13
- 3
src/i18n/resources/rs.js ファイルの表示

users: { users: {
headerTitle: "Profili korisnika", headerTitle: "Profili korisnika",
searchPlaceholder: "Pretražite korisnike....", searchPlaceholder: "Pretražite korisnike....",
checkProfile: "Pogledaj profil"
checkProfile: "Pogledaj profil",
}, },
categories: { categories: {
checkCategory: "Pogledaj podkategorije", checkCategory: "Pogledaj podkategorije",
noOfOffers: "Broj objava: ", noOfOffers: "Broj objava: ",
noOfSubcategories: "Broj potkategorija: ", noOfSubcategories: "Broj potkategorija: ",
headerTitle: "Kategorije", 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 ファイルの表示

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 ファイルの表示

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 ファイルの表示

ADMIN_CATEGORIES_PAGE, ADMIN_CATEGORIES_PAGE,
ADMIN_HOME_PAGE, ADMIN_HOME_PAGE,
ADMIN_LOGIN_PAGE, ADMIN_LOGIN_PAGE,
ADMIN_SUBCATEGORIES_PAGE,
ADMIN_USERS_PAGE, ADMIN_USERS_PAGE,
FORGOT_PASSWORD_MAIL_SENT, FORGOT_PASSWORD_MAIL_SENT,
FORGOT_PASSWORD_PAGE, FORGOT_PASSWORD_PAGE,
routeMatches(ADMIN_LOGIN_PAGE) || routeMatches(ADMIN_LOGIN_PAGE) ||
routeMatches(ADMIN_HOME_PAGE) || routeMatches(ADMIN_HOME_PAGE) ||
routeMatches(ADMIN_USERS_PAGE) || routeMatches(ADMIN_USERS_PAGE) ||
routeMatches(ADMIN_CATEGORIES_PAGE)
routeMatches(ADMIN_CATEGORIES_PAGE) ||
dynamicRouteMatches(ADMIN_SUBCATEGORIES_PAGE)
) )
return true; return true;
return false; return false;

読み込み中…
キャンセル
保存