Skip to content

Commit 8927896

Browse files
add/remove new product functionality added in admin panel
1 parent 1a5a4b2 commit 8927896

File tree

9 files changed

+160
-91
lines changed

9 files changed

+160
-91
lines changed

Diff for: data.json

+24-21
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"products": [
33
{
44
"id": "1",
5-
"title": "Essence Mascara Lash Princess",
5+
"title": "Essence Mascara Lash",
66
"description": "The Essence Mascara Lash Princess is a popular mascara known for its volumizing and lengthening effects. Achieve dramatic lashes with this long-lasting and cruelty-free formula.",
77
"category": "beauty",
88
"price": 9.99,
99
"discountPercentage": 7.17,
10-
"rating": 4.94,
10+
"rating": 0,
1111
"stock": 5,
1212
"tags": [
1313
"beauty",
@@ -56,9 +56,12 @@
5656
"qrCode": "https://dummyjson.com/public/qr-code.png"
5757
},
5858
"images": [
59+
"https://cdn.dummyjson.com/products/images/beauty/Essence%20Mascara%20Lash%20Princess/1.png",
5960
"https://cdn.dummyjson.com/products/images/beauty/Essence%20Mascara%20Lash%20Princess/1.png"
6061
],
61-
"thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Essence%20Mascara%20Lash%20Princess/thumbnail.png"
62+
"thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Essence%20Mascara%20Lash%20Princess/1.png",
63+
"discount": null,
64+
"deleted": true
6265
},
6366
{
6467
"id": "2",
@@ -125,9 +128,9 @@
125128
"title": "Powder Canister",
126129
"description": "The Powder Canister is a finely milled setting powder designed to set makeup and control shine. With a lightweight and translucent formula, it provides a smooth and matte finish.",
127130
"category": "beauty",
128-
"price": 14.99,
131+
"price": 500,
129132
"discountPercentage": 18.14,
130-
"rating": 3.82,
133+
"rating": 0,
131134
"stock": 59,
132135
"tags": [
133136
"beauty",
@@ -176,9 +179,11 @@
176179
"qrCode": "https://dummyjson.com/public/qr-code.png"
177180
},
178181
"images": [
179-
"https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/1.png"
182+
"https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/1.png",
183+
"https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/thumbnail.png"
180184
],
181-
"thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/thumbnail.png"
185+
"thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/thumbnail.png",
186+
"discount": null
182187
},
183188
{
184189
"id": "4",
@@ -1797,22 +1802,20 @@
17971802
"thumbnail": "https://cdn.dummyjson.com/products/images/groceries/Kiwi/thumbnail.png"
17981803
},
17991804
{
1800-
"id": "ac1d",
1801-
"title": "Raj Furniture",
1802-
"description": "Raj Furniture",
1803-
"brand": "Furniture Co.",
1804-
"category": "furniture",
1805-
"price": "520",
1806-
"discountPercentage": "5",
1807-
"stock": "20",
1808-
"thumbnail": "https://m.media-amazon.com/images/I/71u3F2NZ9gL.jpg",
1805+
"id": "caeb",
1806+
"title": "Gucci Belt",
1807+
"description": "Gucci Belt",
1808+
"brand": "Gucci",
1809+
"category": "beauty",
1810+
"price": 45,
1811+
"discountPercentage": 35,
1812+
"stock": 9,
1813+
"thumbnail": "https://images-cdn.ubuy.co.in/6340119aa00848332523d5b9-gucci-belt-u-07-gu-24186.jpg",
18091814
"images": [
1810-
"https://m.media-amazon.com/images/I/71u3F2NZ9gL.jpg",
1811-
"https://m.media-amazon.com/images/I/71u3F2NZ9gL.jpg",
1812-
"https://m.media-amazon.com/images/I/71u3F2NZ9gL.jpg",
1813-
"https://m.media-amazon.com/images/I/71u3F2NZ9gL.jpg"
1815+
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3GFf5cstnkS6xmvA5ySarmKkhtYsHquDC_A&s",
1816+
"https://images-cdn.ubuy.co.in/6340119aa00848332523d5b9-gucci-belt-u-07-gu-24186.jpg"
18141817
],
1815-
"rating": 0
1818+
"discount": null
18161819
}
18171820
],
18181821
"brands": [

Diff for: src/App.js

+8
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ const router = createBrowserRouter([
9797
</ProtectedAdmin>
9898
),
9999
},
100+
{
101+
path: "/admin/product-form/edit/:id",
102+
element: (
103+
<ProtectedAdmin>
104+
<ProductFormPage></ProductFormPage>
105+
</ProtectedAdmin>
106+
),
107+
},
100108
{
101109
path: "/order-success/:id",
102110
element: <OrderSuccessPage></OrderSuccessPage>,

Diff for: src/features/admin/components/AdminProductDetail.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StarIcon } from "@heroicons/react/20/solid";
33
import { RadioGroup } from "@headlessui/react";
44
import { useSelector, useDispatch } from "react-redux";
55
import {
6-
fetchAllProductsIdAsync,
6+
fetchProductByIdAsync,
77
selectProductById,
88
} from "../../product/productSlice";
99
import { useParams } from "react-router-dom";
@@ -54,7 +54,7 @@ export default function AdminProductDetail() {
5454
};
5555

5656
useEffect(() => {
57-
dispatch(fetchAllProductsIdAsync(params.id)); // :id from path in app.js
57+
dispatch(fetchProductByIdAsync(params.id)); // :id from path in app.js
5858
}, [dispatch, params.id]);
5959
return (
6060
<div className="bg-white">

Diff for: src/features/admin/components/AdminProductList.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export default function AdminProductList() {
201201
{/* This is our products list */}
202202
<Link
203203
to="/admin/product-form"
204-
className="rounded-md mx-10 bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
204+
className="rounded-md mx-10 bg-green-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
205205
>
206206
Add Product
207207
</Link>
@@ -448,13 +448,21 @@ function ProductGrid({ products }) {
448448
</p>
449449
</div>
450450
</div>
451+
{product.deleted && (
452+
<div>
453+
<p className="text-sm text-red-400">Product Deleted</p>
454+
</div>
455+
)}
451456
</div>
452457
</div>
453458
</Link>
454-
<div>
455-
<button className="rounded-md my-2 bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
459+
<div className="mt-5">
460+
<Link
461+
to={`/admin/product-form/edit/${product.id}`}
462+
className="rounded-md my-2 bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
463+
>
456464
Edit Product
457-
</button>
465+
</Link>
458466
</div>
459467
</div>
460468
))}

Diff for: src/features/admin/components/ProductForm.js

+68-55
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,84 @@
11
import { useDispatch, useSelector } from "react-redux";
22
import {
3+
clearSelectedProduct,
34
createProductAsync,
5+
fetchProductByIdAsync,
46
selectBrands,
57
selectCategories,
8+
selectProductById,
9+
updateProductAsync,
610
} from "../../product/productSlice";
711
import { PhotoIcon, UserCircleIcon } from "@heroicons/react/24/solid";
812
import { useForm } from "react-hook-form";
13+
import { useEffect } from "react";
14+
import { useParams } from "react-router-dom";
915
function ProductForm() {
1016
const categories = useSelector(selectCategories);
1117
const brands = useSelector(selectBrands);
1218
const dispatch = useDispatch();
19+
const params = useParams();
20+
const selectedProduct = useSelector(selectProductById);
1321
const {
1422
register,
1523
handleSubmit,
24+
setValue,
25+
reset,
1626
formState: { errors },
1727
} = useForm();
1828

29+
useEffect(() => {
30+
if (params.id) {
31+
dispatch(fetchProductByIdAsync(params.id));
32+
} else {
33+
dispatch(clearSelectedProduct());
34+
}
35+
}, [params.id, dispatch]);
36+
37+
useEffect(() => {
38+
if (selectedProduct && params.id) {
39+
setValue("title", selectedProduct.title);
40+
setValue("description", selectedProduct.description);
41+
setValue("brand", selectedProduct.brand);
42+
setValue("category", selectedProduct.category);
43+
setValue("price", selectedProduct.price);
44+
setValue("discountPercentage", selectedProduct.discountPercentage);
45+
setValue("stock", selectedProduct.stock);
46+
setValue("thumbnail", selectedProduct.thumbnail);
47+
setValue("image", selectedProduct.images[0]);
48+
}
49+
}, [setValue, params.id, selectedProduct]);
50+
51+
const handleDelete = () => {
52+
const product = { ...selectedProduct };
53+
product.deleted = true;
54+
dispatch(updateProductAsync(product));
55+
};
1956
return (
2057
<form
2158
noValidate
2259
onSubmit={handleSubmit((data) => {
2360
console.log(data);
2461
const product = { ...data };
25-
product.images = [
26-
product.image1,
27-
product.image2,
28-
product.image3,
29-
product.thumbnail,
30-
];
62+
product.images = [product.image, product.thumbnail];
63+
delete product["image"];
3164
product.rating = 0;
32-
delete product["image1"];
33-
delete product["image2"];
34-
delete product["image3"];
65+
66+
product.price = +product.price;
67+
68+
product.discountPercentage = +product.discountPercentage;
69+
product.stock = +product.stock;
70+
product.discount = +product.discount;
3571
console.log(product);
36-
dispatch(createProductAsync(product));
72+
73+
if (params.id) {
74+
product.id = params.id;
75+
product.rating = selectedProduct.rating || 0;
76+
dispatch(updateProductAsync(product));
77+
reset();
78+
} else {
79+
dispatch(createProductAsync(product));
80+
reset();
81+
}
3782
})}
3883
>
3984
<div className="space-y-12 bg-white p-12">
@@ -214,59 +259,19 @@ function ProductForm() {
214259

215260
<div className="sm:col-span-6">
216261
<label
217-
htmlFor="image1"
218-
className="block text-sm font-medium leading-6 text-gray-900"
219-
>
220-
Image1
221-
</label>
222-
<div className="mt-2">
223-
<div className="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
224-
<input
225-
type="text"
226-
{...register("image1", {
227-
required: "image1 is required",
228-
})}
229-
id="image1"
230-
className="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
231-
/>
232-
</div>
233-
</div>
234-
</div>
235-
<div className="sm:col-span-6">
236-
<label
237-
htmlFor="image2"
238-
className="block text-sm font-medium leading-6 text-gray-900"
239-
>
240-
Image2
241-
</label>
242-
<div className="mt-2">
243-
<div className="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
244-
<input
245-
type="text"
246-
{...register("image2", {
247-
required: "image2 is required",
248-
})}
249-
id="image2"
250-
className="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
251-
/>
252-
</div>
253-
</div>
254-
</div>
255-
<div className="sm:col-span-6">
256-
<label
257-
htmlFor="image3"
262+
htmlFor="image"
258263
className="block text-sm font-medium leading-6 text-gray-900"
259264
>
260-
Image3
265+
Image
261266
</label>
262267
<div className="mt-2">
263268
<div className="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
264269
<input
265270
type="text"
266-
{...register("image3", {
267-
required: "image3 is required",
271+
{...register("image", {
272+
required: "image is required",
268273
})}
269-
id="image3"
274+
id="image"
270275
className="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
271276
/>
272277
</div>
@@ -360,6 +365,14 @@ function ProductForm() {
360365
>
361366
Cancel
362367
</button>
368+
{selectedProduct && (
369+
<button
370+
onClick={handleDelete}
371+
className="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
372+
>
373+
Delete
374+
</button>
375+
)}
363376
<button
364377
type="submit"
365378
className="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"

Diff for: src/features/product/components/ProductDetail.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
22
import { StarIcon } from "@heroicons/react/20/solid";
33
import { RadioGroup } from "@headlessui/react";
44
import { useSelector, useDispatch } from "react-redux";
5-
import { fetchAllProductsIdAsync, selectProductById } from "../productSlice";
5+
import { fetchProductByIdAsync, selectProductById } from "../productSlice";
66
import { useParams } from "react-router-dom";
77
import { addToCartAsync } from "../../cart/cartSlice";
88
import { selectLoggedInUser } from "../../auth/authSlice";
@@ -51,7 +51,7 @@ export default function ProductDetail() {
5151
};
5252

5353
useEffect(() => {
54-
dispatch(fetchAllProductsIdAsync(params.id)); // :id from path in app.js
54+
dispatch(fetchProductByIdAsync(params.id)); // :id from path in app.js
5555
}, [dispatch, params.id]);
5656
return (
5757
<div className="bg-white">

Diff for: src/features/product/components/ProductList.js

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default function ProductList() {
9393
useEffect(() => {
9494
const pagination = { _page: page, _per_page: ITEMS_PER_PAGE };
9595
dispatch(fetchProductsByFiltersAsync({ filter, sort, pagination }));
96+
// TODO:server will filter the deleted products
9697
}, [dispatch, filter, sort, page]);
9798

9899
useEffect(() => {

Diff for: src/features/product/productAPI.js

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export function fetchAllProducts() {
22
return new Promise(async (resolve) => {
33
// TODO: we will not hard coded server url here...
44
const response = await fetch("http://localhost:8080/products");
5+
56
const data = await response.json();
67
resolve({ data });
78
});
@@ -26,9 +27,25 @@ export function createProduct(product) {
2627
resolve({ data });
2728
});
2829
}
30+
export function updateProduct(update) {
31+
return new Promise(async (resolve) => {
32+
const response = await fetch(
33+
"http://localhost:8080/products/" + update.id,
34+
{
35+
method: "PATCH",
36+
body: JSON.stringify(update),
37+
headers: { "content-type": "application/json" },
38+
}
39+
);
40+
const data = await response.json();
41+
42+
resolve({ data });
43+
});
44+
}
2945
export function fetchProductsByFilters(filter, sort, pagination) {
3046
// filter ={"brand":"Essence"}
3147
// TODO:we will on server support mutilple value
48+
// TODO:server will filter the deleted products in case of non-admin
3249

3350
// reference for sorting functionality
3451
// JSON Server's behavior for sorting changed with different versions.

0 commit comments

Comments
 (0)