Skip to content

Commit

Permalink
belindas-closet-nextjs_10_504_implement-role-based-authorization (#505)
Browse files Browse the repository at this point in the history
* Restricted Add Product page to admins & creators

* Restricted Archived Products page to admins & creators

* Restricted Creator page to creators

* Restricted Admin page to admins

* Restricted Edit User Role page to admins

* Updated tests to fix errors and improve coverage

* Restricted Dashboard page to admins & creators

I also updated the dashboard page tests
  • Loading branch information
heosman authored Jul 22, 2024
1 parent c54c904 commit b629c35
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 373 deletions.
370 changes: 192 additions & 178 deletions app/add-product-page/page.tsx

Large diffs are not rendered by default.

91 changes: 53 additions & 38 deletions app/admin-page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,64 @@ import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import ButtonGroup from '@mui/material/ButtonGroup'
import { useMediaQuery, useTheme } from '@mui/material';
import UnauthorizedPageMessage from '@/components/UnauthorizedPageMessage';
import { useState, useEffect } from 'react';

const Admin = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

return (
<div>
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ mt: 4, mb: isMobile ? 4 : 6 }}
>
My Account
</Typography>
<ButtonGroup
color="primary"
variant="contained"
sx={{ flexDirection: isMobile ? "column" : "row",
'& .MuiButton-root': {
borderRadius: '4px',
mr: isMobile ? 0 : 1,
ml: isMobile ? 0 : 1,
mt: isMobile ? 1 : 0,
mb: isMobile ? 2 : 0,
},
}}
>
<Button href="/add-product-page">
Add Product
</Button>
<Button href="/category-page/all-products">
All Products
</Button>
<Button href="/edit-user-role-page">
Edit User Roles
</Button>
<Button href="/archived-products-page">
Archived Products
</Button>
</ButtonGroup>
</div>
);
const [userRole, setUserRole] = useState("");
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const userRole = JSON.parse(atob(token.split(".")[1])).role;
setUserRole(userRole);
}
}, []);

if ((userRole === "admin")) {
return (
<div>
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ mt: 4, mb: isMobile ? 4 : 6 }}
>
My Account
</Typography>
<ButtonGroup
color="primary"
variant="contained"
sx={{ flexDirection: isMobile ? "column" : "row",
'& .MuiButton-root': {
borderRadius: '4px',
mr: isMobile ? 0 : 1,
ml: isMobile ? 0 : 1,
mt: isMobile ? 1 : 0,
mb: isMobile ? 2 : 0,
},
}}
>
<Button href="/add-product-page">
Add Product
</Button>
<Button href="/category-page/all-products">
All Products
</Button>
<Button href="/edit-user-role-page">
Edit User Roles
</Button>
<Button href="/archived-products-page">
Archived Products
</Button>
</ButtonGroup>
</div>
);
} else {
return <UnauthorizedPageMessage />
}
};

export default Admin;
80 changes: 47 additions & 33 deletions app/archived-products-page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
import ProductCard from "@/components/ProductCard";
import logo from "@/public/belinda-images/logo.png";
import { Container, Grid, Typography } from "@mui/material";
import UnauthorizedPageMessage from "@/components/UnauthorizedPageMessage";
// WARNING: You won't be able to connect to local backend unless you remove the env variable below.
const URL = process.env.BELINDAS_CLOSET_PUBLIC_API_URL || "http://localhost:3000/api";
const placeholderImg = logo;
Expand Down Expand Up @@ -62,39 +63,52 @@ const ViewProduct = ({ categoryId }: { categoryId: string }) => {
);
}, [products]);

return (
<Container sx={{ py: 4 }} maxWidth="lg">
<Typography
variant="h4"
gutterBottom
justifyContent={"center"}
align={"center"}
mb={3}
>
Found {filteredProducts.length} products in Archived Products
</Typography>
<Grid container spacing={2}>
{filteredProducts.map((product, index) => (
<Grid item key={index} xs={12} sm={4} md={3}>
<ProductCard
image={logo}
categories={product.productType}
gender={product.productGender}
sizeShoe=''
size=''
sizePantsWaist=''
sizePantsInseam=''
description={product.productDescription}
href={`/category-page/${categoryId}/products/${product._id}`} // Construct the URL
_id={product._id}
isHidden={false}
isSold={false}
/>
</Grid>
))}
</Grid>
</Container>
);
const [userRole, setUserRole] = useState("");
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const userRole = JSON.parse(atob(token.split(".")[1])).role;
setUserRole(userRole);
}
}, []);

if ((userRole === "admin" || userRole === "creator")) {
return (
<Container sx={{ py: 4 }} maxWidth="lg">
<Typography
variant="h4"
gutterBottom
justifyContent={"center"}
align={"center"}
mb={3}
>
Found {filteredProducts.length} products in Archived Products
</Typography>
<Grid container spacing={2}>
{filteredProducts.map((product, index) => (
<Grid item key={index} xs={12} sm={4} md={3}>
<ProductCard
image={logo}
categories={product.productType}
gender={product.productGender}
sizeShoe=''
size=''
sizePantsWaist=''
sizePantsInseam=''
description={product.productDescription}
href={`/category-page/${categoryId}/products/${product._id}`} // Construct the URL
_id={product._id}
isHidden={false}
isSold={false}
/>
</Grid>
))}
</Grid>
</Container>
);
} else {
return <UnauthorizedPageMessage />;
}
};
export default function ProductList({
params,
Expand Down
85 changes: 50 additions & 35 deletions app/creator-page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,61 @@ import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import ButtonGroup from '@mui/material/ButtonGroup';
import { useMediaQuery, useTheme } from '@mui/material';
import UnauthorizedPageMessage from '@/components/UnauthorizedPageMessage';
import { useState, useEffect } from 'react';

const Creator = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

return (
<div>
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ mt: 4, mb: isMobile ? 4 : 6 }}
>
My Account
</Typography>
<ButtonGroup
color="primary"
variant="contained"
sx={{ flexDirection: isMobile ? "column" : "row",
'& .MuiButton-root': {
borderRadius: '4px',
mr: isMobile ? 0 : 1,
ml: isMobile ? 0 : 1,
mt: isMobile ? 1 : 0,
mb: isMobile ? 2 : 0,
},
}}
>
<Button href="/add-product-page">
Add Product
</Button>
<Button href="/category-page/all-products">
All Products
</Button>
<Button href="/archived-products-page">
Archived Products
</Button>
</ButtonGroup>
</div>
);
const [userRole, setUserRole] = useState("");
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const userRole = JSON.parse(atob(token.split(".")[1])).role;
setUserRole(userRole);
}
}, []);

if ((userRole === "creator")) {
return (
<div>
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ mt: 4, mb: isMobile ? 4 : 6 }}
>
My Account
</Typography>
<ButtonGroup
color="primary"
variant="contained"
sx={{ flexDirection: isMobile ? "column" : "row",
'& .MuiButton-root': {
borderRadius: '4px',
mr: isMobile ? 0 : 1,
ml: isMobile ? 0 : 1,
mt: isMobile ? 1 : 0,
mb: isMobile ? 2 : 0,
},
}}
>
<Button href="/add-product-page">
Add Product
</Button>
<Button href="/category-page/all-products">
All Products
</Button>
<Button href="/archived-products-page">
Archived Products
</Button>
</ButtonGroup>
</div>
);
} else {
return <UnauthorizedPageMessage />;
}
};

export default Creator;
52 changes: 33 additions & 19 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";
import { Typography, Drawer, List, ListItem, ListItemText, IconButton } from "@mui/material";
import { SetStateAction, useState } from "react";
import { SetStateAction, useEffect, useState } from "react";
import MenuIcon from '@mui/icons-material/Menu';
import UnauthorizedPageMessage from "@/components/UnauthorizedPageMessage";

const Dashboard = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
Expand Down Expand Up @@ -53,24 +54,37 @@ const Dashboard = () => {
</div>
);

return (
<div>
<Drawer anchor="left" open={drawerOpen} onClose={handleDrawerToggle}>
{drawerContent}
</Drawer>
<IconButton onClick={handleDrawerToggle} sx={{ position: 'fixed', top: '80px', left: '16px' }}>
{drawerOpen ? <MenuIcon sx={{ color: "black" }} /> : <MenuIcon sx={{ color: "black" }} />}
</IconButton>
<Typography
component="h1"
variant="h3"
sx={{ color: "white", marginLeft: drawerOpen ? 240 : 0 }}
>
Dashboard
</Typography>
{/* Add your dashboard content here */}
</div>
);
const [userRole, setUserRole] = useState("");
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const userRole = JSON.parse(atob(token.split(".")[1])).role;
setUserRole(userRole);
}
}, []);

if ((userRole === "admin" || userRole === "creator")) {
return (
<div>
<Drawer anchor="left" open={drawerOpen} onClose={handleDrawerToggle}>
{drawerContent}
</Drawer>
<IconButton onClick={handleDrawerToggle} sx={{ position: 'fixed', top: '80px', left: '16px' }}>
{drawerOpen ? <MenuIcon sx={{ color: "black" }} /> : <MenuIcon sx={{ color: "black" }} />}
</IconButton>
<Typography
component="h1"
variant="h3"
sx={{ color: "white", marginLeft: drawerOpen ? 240 : 0 }}
>
Dashboard
</Typography>
{/* Add your dashboard content here */}
</div>
);
} else {
return <UnauthorizedPageMessage />;
};
};

export default Dashboard;
Loading

0 comments on commit b629c35

Please sign in to comment.