Skip to content

Commit 22b1dab

Browse files
admin route created
1 parent 67348b7 commit 22b1dab

File tree

11 files changed

+1151
-176
lines changed

11 files changed

+1151
-176
lines changed

data.json

Lines changed: 48 additions & 137 deletions
Large diffs are not rendered by default.

src/App.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import UserProfilePage from "./pages/UserProfilePage";
2626
import { fetchLoggedInUserAsync } from "./features/user/userSlice";
2727
import LogOut from "./features/auth/components/LogOut";
2828
import ForgotPasswordPage from "./pages/ForgotPasswordPage";
29+
import ProtectedAdmin from "./features/auth/components/ProtectedAdmin";
30+
import AdminHome from "./pages/AdimHome";
31+
import AdminProductDetail from "./features/admin/components/AdminProductDetail";
2932
const router = createBrowserRouter([
3033
{
3134
path: "/",
@@ -36,6 +39,15 @@ const router = createBrowserRouter([
3639
</Protected>
3740
),
3841
},
42+
{
43+
path: "/admin",
44+
45+
element: (
46+
<ProtectedAdmin>
47+
<AdminHome></AdminHome>
48+
</ProtectedAdmin>
49+
),
50+
},
3951
{
4052
path: "/login",
4153
element: <LoginPage></LoginPage>,
@@ -68,6 +80,14 @@ const router = createBrowserRouter([
6880
</Protected>
6981
),
7082
},
83+
{
84+
path: "/admin/product-detail/:id", // :id provided by react-router
85+
element: (
86+
<ProtectedAdmin>
87+
<AdminProductDetail></AdminProductDetail>
88+
</ProtectedAdmin>
89+
),
90+
},
7191
{
7292
path: "/order-success/:id",
7393
element: <OrderSuccessPage></OrderSuccessPage>,
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
import { useEffect, useState } from "react";
2+
import { StarIcon } from "@heroicons/react/20/solid";
3+
import { RadioGroup } from "@headlessui/react";
4+
import { useSelector, useDispatch } from "react-redux";
5+
import {
6+
fetchAllProductsIdAsync,
7+
selectProductById,
8+
} from "../../product/productSlice";
9+
import { useParams } from "react-router-dom";
10+
import { addToCartAsync } from "../../cart/cartSlice";
11+
import { selectLoggedInUser } from "../../auth/authSlice";
12+
13+
// TODO: In server data we will add sizes,colors, highlights to each product
14+
15+
const colors = [
16+
{ name: "White", class: "bg-white", selectedClass: "ring-gray-400" },
17+
{ name: "Gray", class: "bg-gray-200", selectedClass: "ring-gray-400" },
18+
{ name: "Black", class: "bg-gray-900", selectedClass: "ring-gray-900" },
19+
];
20+
const sizes = [
21+
{ name: "XXS", inStock: false },
22+
{ name: "XS", inStock: true },
23+
{ name: "S", inStock: true },
24+
{ name: "M", inStock: true },
25+
{ name: "L", inStock: true },
26+
{ name: "XL", inStock: true },
27+
{ name: "2XL", inStock: true },
28+
{ name: "3XL", inStock: true },
29+
];
30+
const highlights = [
31+
"Hand cut and sewn locally",
32+
"Dyed with our proprietary colors",
33+
"Pre-washed & pre-shrunk",
34+
"Ultra-soft 100% cotton",
35+
];
36+
37+
function classNames(...classes) {
38+
return classes.filter(Boolean).join(" ");
39+
}
40+
41+
export default function AdminProductDetail() {
42+
const [selectedColor, setSelectedColor] = useState(colors[0]);
43+
const [selectedSize, setSelectedSize] = useState(sizes[2]);
44+
const user = useSelector(selectLoggedInUser);
45+
const product = useSelector(selectProductById);
46+
const dispatch = useDispatch();
47+
const params = useParams(); // hook provided by react-router to fetch parameters
48+
49+
const handleCart = (e) => {
50+
e.preventDefault();
51+
const newItem = { ...product, quantity: 1, user: user.id };
52+
delete newItem["id"]; // to avoid creating same id on adding same product to cart....
53+
dispatch(addToCartAsync(newItem));
54+
};
55+
56+
useEffect(() => {
57+
dispatch(fetchAllProductsIdAsync(params.id)); // :id from path in app.js
58+
}, [dispatch, params.id]);
59+
return (
60+
<div className="bg-white">
61+
{product && (
62+
<div className="pt-6">
63+
<nav aria-label="Breadcrumb">
64+
<ol
65+
role="list"
66+
className="mx-auto flex max-w-2xl items-center space-x-2 px-4 sm:px-6 lg:max-w-7xl lg:px-8"
67+
>
68+
{product.breadcrumbs &&
69+
product.breadcrumbs.map((breadcrumb) => (
70+
<li key={breadcrumb.id}>
71+
<div className="flex items-center">
72+
<a
73+
href={breadcrumb.href}
74+
className="mr-2 text-sm font-medium text-gray-900"
75+
>
76+
{breadcrumb.name}
77+
</a>
78+
<svg
79+
width={16}
80+
height={20}
81+
viewBox="0 0 16 20"
82+
fill="currentColor"
83+
aria-hidden="true"
84+
className="h-5 w-4 text-gray-300"
85+
>
86+
<path d="M5.697 4.34L8.98 16.532h1.327L7.025 4.341H5.697z" />
87+
</svg>
88+
</div>
89+
</li>
90+
))}
91+
<li className="text-sm">
92+
<a
93+
href={product.href}
94+
aria-current="page"
95+
className="font-medium text-gray-500 hover:text-gray-600"
96+
>
97+
{product.title}
98+
</a>
99+
</li>
100+
</ol>
101+
</nav>
102+
103+
{/* Image gallery */}
104+
<div className="mx-auto mt-6 max-w-2xl sm:px-6 lg:grid lg:max-w-7xl lg:grid-cols-3 lg:gap-x-8 lg:px-8">
105+
<div className="aspect-h-4 aspect-w-3 hidden overflow-hidden rounded-lg lg:block">
106+
<img
107+
src={product.images[0]}
108+
alt={product.title}
109+
className="h-full w-full object-cover object-center"
110+
/>
111+
</div>
112+
<div className="hidden lg:grid lg:grid-cols-1 lg:gap-y-8">
113+
<div className="aspect-h-2 aspect-w-3 overflow-hidden rounded-lg">
114+
<img
115+
src={product.images[0]}
116+
alt={product.title}
117+
className="h-full w-full object-cover object-center"
118+
/>
119+
</div>
120+
<div className="aspect-h-2 aspect-w-3 overflow-hidden rounded-lg">
121+
<img
122+
src={product.images[0]}
123+
alt={product.title}
124+
className="h-full w-full object-cover object-center"
125+
/>
126+
</div>
127+
</div>
128+
<div className="aspect-h-5 aspect-w-4 lg:aspect-h-4 lg:aspect-w-3 sm:overflow-hidden sm:rounded-lg">
129+
<img
130+
src={product.images[0]}
131+
alt={product.title}
132+
className="h-full w-full object-cover object-center"
133+
/>
134+
</div>
135+
</div>
136+
137+
{/* Product info */}
138+
<div className="mx-auto max-w-2xl px-4 pb-16 pt-10 sm:px-6 lg:grid lg:max-w-7xl lg:grid-cols-3 lg:grid-rows-[auto,auto,1fr] lg:gap-x-8 lg:px-8 lg:pb-24 lg:pt-16">
139+
<div className="lg:col-span-2 lg:border-r lg:border-gray-200 lg:pr-8">
140+
<h1 className="text-2xl font-bold tracking-tight text-gray-900 sm:text-3xl">
141+
{product.title}
142+
</h1>
143+
</div>
144+
145+
{/* Options */}
146+
<div className="mt-4 lg:row-span-3 lg:mt-0">
147+
<h2 className="sr-only">Product information</h2>
148+
<p className="text-3xl tracking-tight text-gray-900">
149+
${product.price}
150+
</p>
151+
152+
{/* Reviews */}
153+
<div className="mt-6">
154+
<h3 className="sr-only">Reviews</h3>
155+
<div className="flex items-center">
156+
<div className="flex items-center">
157+
{[0, 1, 2, 3, 4].map((rating) => (
158+
<StarIcon
159+
key={rating}
160+
className={classNames(
161+
product.rating > rating
162+
? "text-gray-900"
163+
: "text-gray-200",
164+
"h-5 w-5 flex-shrink-0"
165+
)}
166+
aria-hidden="true"
167+
/>
168+
))}
169+
</div>
170+
<p className="sr-only">{product.rating} out of 5 stars</p>
171+
</div>
172+
</div>
173+
174+
<form className="mt-10">
175+
{/* Colors */}
176+
<div>
177+
<h3 className="text-sm font-medium text-gray-900">Color</h3>
178+
179+
<RadioGroup
180+
value={selectedColor}
181+
onChange={setSelectedColor}
182+
className="mt-4"
183+
>
184+
<RadioGroup.Label className="sr-only">
185+
Choose a color
186+
</RadioGroup.Label>
187+
<div className="flex items-center space-x-3">
188+
{colors.map((color) => (
189+
<RadioGroup.Option
190+
key={color.name}
191+
value={color}
192+
className={({ active, checked }) =>
193+
classNames(
194+
color.selectedClass,
195+
active && checked ? "ring ring-offset-1" : "",
196+
!active && checked ? "ring-2" : "",
197+
"relative -m-0.5 flex cursor-pointer items-center justify-center rounded-full p-0.5 focus:outline-none"
198+
)
199+
}
200+
>
201+
<RadioGroup.Label as="span" className="sr-only">
202+
{color.name}
203+
</RadioGroup.Label>
204+
<span
205+
aria-hidden="true"
206+
className={classNames(
207+
color.class,
208+
"h-8 w-8 rounded-full border border-black border-opacity-10"
209+
)}
210+
/>
211+
</RadioGroup.Option>
212+
))}
213+
</div>
214+
</RadioGroup>
215+
</div>
216+
217+
{/* Sizes */}
218+
<div className="mt-10">
219+
<div className="flex items-center justify-between">
220+
<h3 className="text-sm font-medium text-gray-900">Size</h3>
221+
<a
222+
href="#"
223+
className="text-sm font-medium text-indigo-600 hover:text-indigo-500"
224+
>
225+
Size guide
226+
</a>
227+
</div>
228+
229+
<RadioGroup
230+
value={selectedSize}
231+
onChange={setSelectedSize}
232+
className="mt-4"
233+
>
234+
<RadioGroup.Label className="sr-only">
235+
Choose a size
236+
</RadioGroup.Label>
237+
<div className="grid grid-cols-4 gap-4 sm:grid-cols-8 lg:grid-cols-4">
238+
{sizes.map((size) => (
239+
<RadioGroup.Option
240+
key={size.name}
241+
value={size}
242+
disabled={!size.inStock}
243+
className={({ active }) =>
244+
classNames(
245+
size.inStock
246+
? "cursor-pointer bg-white text-gray-900 shadow-sm"
247+
: "cursor-not-allowed bg-gray-50 text-gray-200",
248+
active ? "ring-2 ring-indigo-500" : "",
249+
"group relative flex items-center justify-center rounded-md border py-3 px-4 text-sm font-medium uppercase hover:bg-gray-50 focus:outline-none sm:flex-1 sm:py-6"
250+
)
251+
}
252+
>
253+
{({ active, checked }) => (
254+
<>
255+
<RadioGroup.Label as="span">
256+
{size.name}
257+
</RadioGroup.Label>
258+
{size.inStock ? (
259+
<span
260+
className={classNames(
261+
active ? "border" : "border-2",
262+
checked
263+
? "border-indigo-500"
264+
: "border-transparent",
265+
"pointer-events-none absolute -inset-px rounded-md"
266+
)}
267+
aria-hidden="true"
268+
/>
269+
) : (
270+
<span
271+
aria-hidden="true"
272+
className="pointer-events-none absolute -inset-px rounded-md border-2 border-gray-200"
273+
>
274+
<svg
275+
className="absolute inset-0 h-full w-full stroke-2 text-gray-200"
276+
viewBox="0 0 100 100"
277+
preserveAspectRatio="none"
278+
stroke="currentColor"
279+
>
280+
<line
281+
x1={0}
282+
y1={100}
283+
x2={100}
284+
y2={0}
285+
vectorEffect="non-scaling-stroke"
286+
/>
287+
</svg>
288+
</span>
289+
)}
290+
</>
291+
)}
292+
</RadioGroup.Option>
293+
))}
294+
</div>
295+
</RadioGroup>
296+
</div>
297+
298+
<button
299+
onClick={(e) => {
300+
handleCart(e);
301+
}}
302+
type="submit"
303+
className="mt-10 flex w-full items-center justify-center rounded-md border border-transparent bg-indigo-600 px-8 py-3 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
304+
>
305+
Add to Cart
306+
</button>
307+
</form>
308+
</div>
309+
310+
<div className="py-10 lg:col-span-2 lg:col-start-1 lg:border-r lg:border-gray-200 lg:pb-16 lg:pr-8 lg:pt-6">
311+
{/* Description and details */}
312+
<div>
313+
<h3 className="sr-only">Description</h3>
314+
315+
<div className="space-y-6">
316+
<p className="text-base text-gray-900">
317+
{product.description}
318+
</p>
319+
</div>
320+
</div>
321+
322+
<div className="mt-10">
323+
<h3 className="text-sm font-medium text-gray-900">
324+
Highlights
325+
</h3>
326+
327+
<div className="mt-4">
328+
<ul role="list" className="list-disc space-y-2 pl-4 text-sm">
329+
{highlights.map((highlight) => (
330+
<li key={highlight} className="text-gray-400">
331+
<span className="text-gray-600">{highlight}</span>
332+
</li>
333+
))}
334+
</ul>
335+
</div>
336+
</div>
337+
338+
<div className="mt-10">
339+
<h2 className="text-sm font-medium text-gray-900">Details</h2>
340+
341+
<div className="mt-4 space-y-6">
342+
<p className="text-sm text-gray-600">{product.description}</p>
343+
</div>
344+
</div>
345+
</div>
346+
</div>
347+
</div>
348+
)}
349+
</div>
350+
);
351+
}

0 commit comments

Comments
 (0)