Skip to content

Commit

Permalink
belindas-closet-nextjs_4_256_buttons-functionality (#261)
Browse files Browse the repository at this point in the history
* Added functionality to delete button

* Added functionality to delete and archive buttons

Resolves #256

As a developer, I added functionality to the delete and archive buttons along with confirmation dialogues. When products are deleted or archived from the frontend, they are hidden. They are marked as isHidden (deleted) or isSold (archived) in the database.

I wanted to make the page reroute back to the category page, but the components complicates it and my attempts to incorporate router.back() caused errors.

* Routes back to category page after successful deletion

* Created pop up success/error messages
  • Loading branch information
intisarosman1 authored Mar 12, 2024
1 parent 7d7bfcc commit 6b43b5d
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 77 deletions.
2 changes: 1 addition & 1 deletion app/auth/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const Signin = () => {
setError("Invalid email or password");
} else {
const { token } = await res.json();
localStorage.setItem('token', token);
localStorage.setItem("token", token);
const userRole = JSON.parse(atob(token.split(".")[1])).role; // decode token to get user role
// Redirect to user page
if (userRole === "admin") {
Expand Down
12 changes: 10 additions & 2 deletions app/category-page/[categoryId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface Product {
productSizePantsWaist: string;
productSizePantsInseam: string;
productDescription: string;
isHidden: Boolean;
isSold: Boolean;
}

async function fetchData(
Expand All @@ -36,6 +38,7 @@ async function fetchData(
throw new Error(res.statusText);
} else {
const data = await res.json();
const filteredData = data.filter((product: Product) => !product.isHidden);
setProducts(data);
console.log(data);
}
Expand All @@ -46,11 +49,16 @@ async function fetchData(

const ViewProduct = ({ categoryId }: { categoryId: string }) => {
const [products, setProducts] = useState<Product[]>([]);
const [filteredProducts, setFilteredProducts] = useState<Product[]>([]);

useEffect(() => {
fetchData(categoryId, setProducts); // Pass categoryId to fetchData
}, [categoryId]);

useEffect(() => {
setFilteredProducts(products.filter(product => !product.isHidden && !product.isSold));
}, [products]);

return (
<Container
fixed
Expand All @@ -69,10 +77,10 @@ const ViewProduct = ({ categoryId }: { categoryId: string }) => {
justifyContent={"center"}
align={"center"}
>
Found {products.length} products in {categoryId}
Found {filteredProducts.length} products in {categoryId}
</Typography>
<Grid container spacing={2}>
{products.map((product, index) => (
{filteredProducts.map((product, index) => (
<Grid item key={index} xs={12} sm={6} md={4}>
<ProductCard
image={logo}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import ArchiveIcon from "@mui/icons-material/Archive";
import ConfirmDeleteDialog from "@/components/ConfirmDeleteDialog";
import ConfirmArchiveDialog from "@/components/ConfirmArchiveDialog";

interface Product {
export interface Product {
_id: string;
productType: string[];
productGender: string[];
Expand All @@ -23,9 +25,31 @@ interface Product {
productSizePantsInseam: string[];
productDescription: string;
productImage: string;
isHidden: boolean;
isSold: boolean;
}

const ProductDetailDisplay = ({ product }: { product: Product }) => {
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);

const handleDeleteButtonClick = () => {
setOpenDeleteDialog(true);
};

const handleCloseDeleteDialog = () => {
setOpenDeleteDialog(false);
};

const [openArchiveDialog, setOpenArchiveDialog] = useState(false);

const handleArchiveButtonClick = () => {
setOpenArchiveDialog(true);
};

const handleCloseArchiveDialog = () => {
setOpenArchiveDialog(false);
};

return (
<Container
fixed
Expand Down Expand Up @@ -115,6 +139,7 @@ const ProductDetailDisplay = ({ product }: { product: Product }) => {
variant="contained"
color="error"
startIcon={<DeleteIcon />}
onClick={handleDeleteButtonClick}
>
Delete
</Button>
Expand All @@ -125,11 +150,22 @@ const ProductDetailDisplay = ({ product }: { product: Product }) => {
variant="contained"
color="warning"
startIcon={<ArchiveIcon />}
onClick={handleArchiveButtonClick}
>
Archive
</Button>
</Box>
</Stack>
<ConfirmDeleteDialog
open={openDeleteDialog}
onClose={handleCloseDeleteDialog}
product={product}
/>
<ConfirmArchiveDialog
open={openArchiveDialog}
onClose={handleCloseArchiveDialog}
product={product}
/>
</Paper>
</Box>
</Container>
Expand Down
2 changes: 2 additions & 0 deletions app/category-page/[categoryId]/products/[productId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface Product {
productSizePantsInseam: string[];
productDescription: string;
productImage: string;
isHidden: boolean;
isSold: boolean;
}

const ProductDetail = ({
Expand Down
101 changes: 101 additions & 0 deletions components/ConfirmArchiveDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import DialogTitle from "@mui/material/DialogTitle";
import DialogActions from "@mui/material/DialogActions";
import Dialog from "@mui/material/Dialog";
import React, { useState } from "react";
import { Product } from "@/app/category-page/[categoryId]/products/[productId]/ProductDetailDisplay";
import Snackbar from "@mui/material/Snackbar";

/**
* Props for the ConfirmArchiveDialog component.
*/
interface ConfirmArchiveDialogProps {
open: boolean;
onClose: () => void;
product: Product;
}
/**
* Renders a confirmation dialog for archiving products.
*
* @param props - The component props.
* @returns The rendered ConfirmArchiveDialog component.
*/
export default function ConfirmArchiveDialog({
open,
onClose,
product,
}: ConfirmArchiveDialogProps) {
const [snackBarMessage, setSnackBarMessage] = useState<string>("");

/**
* Handles the click event when the user confirms "No" to archiving product.
* @returns {void}
*/
const handleNo = () => {
onClose();
};
/**
* Handles the click event when the user confirms "Yes" to archiving product.
* @returns {void}
*/
const handleYes = async () => {
// TODO: Confirm Yes to archive product
const token = localStorage.getItem('token');
console.log('Token:', token);
try {
const response = await fetch(`http://localhost:3000/api/products/archive/${product._id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({isSold: Boolean}),
});
if (response.ok) {
onClose();
setSnackBarMessage("Product archived successfully!");
setTimeout(() => {
window.history.back();
}, 2000);
} else {
const errorMessage = await response.json();
setSnackBarMessage(errorMessage.message);
console.error('Failed to archive product', response.statusText);
}
} catch (error) {
setSnackBarMessage("Error archiving product");
console.error('Error archiving product:', error);
}
};

return (
<Box
sx={{
width: "100%",
maxWidth: 800,
bgcolor: "background.paper",
color: "#000",
}}
>
<Dialog
sx={{ "& .MuiDialog-paper": { width: "50%", maxHeight: 435 } }}
maxWidth="xs"
open={open}
>
<DialogTitle>Are you sure you want to archive this product?</DialogTitle>
<DialogActions>
<Button autoFocus onClick={handleNo}>No</Button>
<Button onClick={handleYes}>Yes</Button>
</DialogActions>
</Dialog>
<Snackbar
open={Boolean(snackBarMessage)}
autoHideDuration={6000}
onClose={() => setSnackBarMessage("")}
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
message={snackBarMessage}
/>
</Box>
);
}
115 changes: 115 additions & 0 deletions components/ConfirmDeleteDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import DialogTitle from "@mui/material/DialogTitle";
import DialogActions from "@mui/material/DialogActions";
import Dialog from "@mui/material/Dialog";
import React, { useState } from "react";
import { Product } from "@/app/category-page/[categoryId]/products/[productId]/ProductDetailDisplay";
import Snackbar, { SnackbarOrigin } from "@mui/material/Snackbar";

interface State extends SnackbarOrigin {
open: boolean;
}

interface DeleteSuccessResponse {
status: "delete_success";
message: string;
token?: string;
}

interface DeleteErrorResponse {
status: "delete_error";
message: string;
}

/**
* Props for the ConfirmDeleteDialog component.
*/
interface ConfirmDeleteDialogProps {
open: boolean;
onClose: () => void;
product: Product;
}
/**
* Renders a confirmation dialog for deleting products.
*
* @param props - The component props.
* @returns The rendered ConfirmDeleteDialog component.
*/
export default function ConfirmDeleteDialog({
open,
onClose,
product,
}: ConfirmDeleteDialogProps) {
const [snackBarMessage, setSnackBarMessage] = useState<string>("");
/**
* Handles the click event when the user confirms "No" to deleting product.
* @returns {void}
*/
const handleNo = () => {
onClose();
};
/**
* Handles the click event when the user confirms "Yes" to deleting product.
* @returns {void}
*/
const handleYes = async () => {
// TODO: Confirm Yes to delete product
const token = localStorage.getItem('token');
console.log('Token:', token);
try {
const response = await fetch(`http://localhost:3000/api/products/remove/${product._id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({isHidden: Boolean}),
});
if (response.ok) {
onClose();
setSnackBarMessage("Product deleted successfully!");
setTimeout(() => {
window.history.back();
}, 2000);
} else {
const errorMessage = await response.json();
setSnackBarMessage(errorMessage.message);
console.error('Failed to delete product', response.statusText);
}
} catch (error) {
setSnackBarMessage("Error deleting product");
console.error('Error deleting product:', error);
}
};

return (
<Box
sx={{
width: "100%",
maxWidth: 800,
bgcolor: "background.paper",
color: "#000",
}}
>
<Dialog
sx={{ "& .MuiDialog-paper": { width: "50%", maxHeight: 435 } }}
maxWidth="xs"
open={open}
>
<DialogTitle>Are you sure you want to delete this product?</DialogTitle>
<DialogActions>
<Button autoFocus onClick={handleNo}>No</Button>
<Button onClick={handleYes}>Yes</Button>
</DialogActions>
</Dialog>
<Snackbar
open={Boolean(snackBarMessage)}
autoHideDuration={6000}
onClose={() => setSnackBarMessage("")}
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
message={snackBarMessage}
/>
</Box>
);
}
Loading

0 comments on commit 6b43b5d

Please sign in to comment.